From 621c8cf6e37715c60f52bb3631d2b71077f50afd Mon Sep 17 00:00:00 2001 From: Remy Moll Date: Sat, 1 Jan 2022 22:41:54 +0100 Subject: [PATCH] Adjusted bot functions for new backend --- bot/api/weather.py | 16 +++++++++----- bot/commands/lists.py | 44 +++++++++++++------------------------ bot/commands/plaintext.py | 4 ++-- bot/commands/status.py | 13 +++++------ bot/commands/template.py | 22 +++++-------------- bot/commands/weather.py | 2 +- bot/main.py | 4 ++-- persistence/database.py | 46 +++++++++++++++++++++------------------ persistence/models.py | 7 +++--- req_pi.txt | 34 ++++++++++++++--------------- server.py | 2 +- 11 files changed, 87 insertions(+), 107 deletions(-) diff --git a/bot/api/weather.py b/bot/api/weather.py index d379cea..ff15700 100644 --- a/bot/api/weather.py +++ b/bot/api/weather.py @@ -6,7 +6,8 @@ logger = logging.getLogger(__name__) class WeatherFetch(): def __init__(self, key): self.last_fetch = datetime.datetime.fromtimestamp(0) - self.last_weather = "" + self.last_fetch_location = [] + self.last_weather = [] self.calls = 0 self.url = "https://api.openweathermap.org/data/2.5/onecall?" @@ -14,8 +15,10 @@ class WeatherFetch(): def show_weather(self, location): delta = datetime.datetime.now() - self.last_fetch - if delta.total_seconds()/60 > 60 or "\n" not in self.last_weather: # 1 hour passed: - + # 1 hour passed, error, or location change + if delta.total_seconds() > 3600 \ + or len(self.last_weather) == 0\ + or self.last_fetch_location != location: data = {"lat" : location[0], "lon" : location[1], "exclude" : "minutely,hourly", "appid" : self.key, "units" : "metric"} self.calls += 1 @@ -35,11 +38,12 @@ class WeatherFetch(): "short" : day["weather"][0]["main"], "temps" : [int(day["temp"]["min"]),int(day["temp"]["max"])] }) + + self.last_fetch_location = location + self.last_weather = ret_weather + self.last_fetch = datetime.datetime.now() except: ret_weather = [] - - self.last_weather = ret_weather - self.last_fetch = datetime.datetime.now() else: ret_weather = self.last_weather diff --git a/bot/commands/lists.py b/bot/commands/lists.py index ea4a1d4..40d1354 100644 --- a/bot/commands/lists.py +++ b/bot/commands/lists.py @@ -6,9 +6,10 @@ NAME, NEW, ACTION, ITEMADD, ITEMREMOVE = range(5) class Lists(BotFunc): """Create and edit lists""" - def __init__(self, db): - super().__init__(db) + def __init__(self, db_utils): + super().__init__(db_utils) self.current_name = "" + # self.db_utils set through super() def create_handler(self): @@ -38,10 +39,8 @@ class Lists(BotFunc): def entry_point(self, update: Update, context: CallbackContext) -> None: super().entry_point(update, context) - # TODO Change DB - lists = self.db.lists.select() - sl = [l.name for l in lists] - keyboard = [[InlineKeyboardButton(k, callback_data="list-"+k)] for k in sl] + [[InlineKeyboardButton("New list", callback_data="new")]] + lists = self.db_utils.list_get() + keyboard = [[InlineKeyboardButton(k, callback_data="list-"+k)] for k in lists] + [[InlineKeyboardButton("New list", callback_data="new")]] reply_markup = InlineKeyboardMarkup(keyboard) super().log_activity(read=True, execute=False, send=True) @@ -96,12 +95,12 @@ class Lists(BotFunc): def new_listname(self, update: Update, context: CallbackContext) -> None: name = update.message.text try: - data = self.db.lists(name=name, content="") - data.save() + self.db_utils.list_create(name) keyboard = [[InlineKeyboardButton("Add an item", callback_data="add"), InlineKeyboardButton("To the menu!", callback_data="overview")]] reply_markup = InlineKeyboardMarkup(keyboard) self.current_name = name update.message.reply_text("Thanks. List " + name + " was successfully created.", reply_markup=reply_markup) + super().log_activity(read=False, execute=True, send=True) return ACTION except Exception as e: update.message.reply_text("Oh no! Encountered exception: {}".format(e)) @@ -118,8 +117,7 @@ class Lists(BotFunc): def list_remove(self, update: Update, context: CallbackContext) -> None: query = update.callback_query query.answer() - it = self.db.lists.get(self.db.lists.name == self.current_name) - sl = it.content.split("<-->") + sl = self.db_utils.list_get(self.current_name) keyboard = [[InlineKeyboardButton(k, callback_data=i)] for i,k in enumerate(sl)] reply_markup = InlineKeyboardMarkup(keyboard) @@ -131,7 +129,7 @@ class Lists(BotFunc): def list_clear(self, update: Update, context: CallbackContext) -> None: query = update.callback_query query.answer() - self.db.lists.update(content="").where(self.db.lists.name == self.current_name).execute() + self.db_utils.list_update(self.current_name, replace=[]) keyboard = [[InlineKeyboardButton("Add an item", callback_data="add"), InlineKeyboardButton("Back to the menu", callback_data="overview")]] reply_markup = InlineKeyboardMarkup(keyboard) query.edit_message_text("List " + self.current_name + " cleared", reply_markup=reply_markup) @@ -141,7 +139,7 @@ class Lists(BotFunc): def list_delete(self, update: Update, context: CallbackContext) -> None: query = update.callback_query query.answer() - self.db.lists.delete().where(self.db.lists.name == self.current_name).execute() + self.db_utils.list_delete(self.current_name) query.edit_message_text("List " + self.current_name + " deleted") return ConversationHandler.END @@ -149,10 +147,9 @@ class Lists(BotFunc): def list_print(self, update: Update, context: CallbackContext) -> None: query = update.callback_query query.answer() - it = self.db.lists.get(self.db.lists.name == self.current_name) + it = self.db_utils.list_get(self.current_name) if it: - content = it.content.split("<-->") - content = "\n".join(content) + content = "\n".join(it) else: content = "List empty" @@ -164,14 +161,7 @@ class Lists(BotFunc): def list_add_item(self, update: Update, context: CallbackContext) -> None: item = update.message.text - it = self.db.lists.get(self.db.lists.name == self.current_name) - if it: - sl = it.content - else: - sl = "" - sl += item + "<-->" - self.db.lists.update(content=sl).where(self.db.lists.name == self.current_name).execute() - + self.db_utils.list_update(self.current_name, append=item) keyboard = [[InlineKeyboardButton("Add some more", callback_data="add"), InlineKeyboardButton("Back to the menu", callback_data="overview")]] reply_markup = InlineKeyboardMarkup(keyboard) update.message.reply_text("Added " + item, reply_markup=reply_markup) @@ -183,13 +173,9 @@ class Lists(BotFunc): ind = int(query.data) query.answer() - it = self.db.lists.get(self.db.lists.name == self.current_name) - old = it.content.split("<-->") - # todo make better - + old = self.db_utils.list_get(self.current_name) name = old.pop(ind) - new = "<-->".join(old) - self.db.lists.update(content=new).where(self.db.lists.name == self.current_name).execute() + self.db_utils.list_update(self.current_name, replace=old) keyboard = [[InlineKeyboardButton("Remove another", callback_data="remove"), InlineKeyboardButton("Back to the menu", callback_data="overview")]] reply_markup = InlineKeyboardMarkup(keyboard) diff --git a/bot/commands/plaintext.py b/bot/commands/plaintext.py index 9cc1e74..d9e1d8a 100644 --- a/bot/commands/plaintext.py +++ b/bot/commands/plaintext.py @@ -3,8 +3,8 @@ from .template import * class Plain(BotFunc): """Not a command: just keeps logs and usage_data""" - def __init__(self, db): - super().__init__(db) + def __init__(self, db_utils): + super().__init__(db_utils) def create_handler(self): h = MessageHandler(Filters.text, callback=self.add_to_log) diff --git a/bot/commands/status.py b/bot/commands/status.py index 2960511..02e2b72 100644 --- a/bot/commands/status.py +++ b/bot/commands/status.py @@ -3,8 +3,6 @@ from .template import * import datetime import requests import socket -import numpy as np -import os import json @@ -13,8 +11,8 @@ FIRST = 1 class Status(BotFunc): """Shows a short status of the program.""" - def __init__(self, name, version, db): - super().__init__(db) + def __init__(self, name, version, db_utils): + super().__init__(db_utils) self.start_time = datetime.datetime.now() self.name = name self.version = version @@ -63,13 +61,13 @@ class Status(BotFunc): message += "URL: [" + u + "](" + u + ")\n" # TODO new DB - tot_r = self.db.chats.select().where(self.db.chats.read == True).count() + tot_r = self.db_utils.chat_count("read") message += "Total messages read: `{}`\n".format(tot_r) - tot_s = self.db.chats.select().where(self.db.chats.send == True).count() + tot_s = self.db_utils.chat_count("send") message += "Total messages sent: `{}`\n".format(tot_s) - tot_e = self.db.chats.select().where(self.db.chats.execute == True).count() + tot_e = self.db_utils.chat_count("execute") message += "Total commands executed: `{}`\n".format(tot_e) if update.message: @@ -101,6 +99,5 @@ class Status(BotFunc): for i in res_json["tunnels"]: if i['name'] == 'command_line': return i['public_url'] - break except: return "Not available" \ No newline at end of file diff --git a/bot/commands/template.py b/bot/commands/template.py index cc9d502..a494c99 100644 --- a/bot/commands/template.py +++ b/bot/commands/template.py @@ -15,25 +15,13 @@ import datetime class BotFunc(): """Base class for a specific bot-functionality""" - def __init__(self, db): + def __init__(self, db_utils): self.logger = logging.getLogger(__name__) - self.db = db + self.db_utils = db_utils - - # def log_activity(self, **kwargs): - # # mark that a new command has been executed - # try: - # data = self.db.chats( - # time=datetime.datetime.now(), - # **kwargs - # ) - # # kwargs can look like - # # receive=True, - # # execute=True, - # # send=False, - # data.save() - # except Exception as e: - # self.logger.error("sql error: {}".format(e)) + def log_activity(self, **kwargs): + # mark that a new command has been executed + self.db_utils.sensor_log(**kwargs) def entry_point(self, update: Update, context: CallbackContext) -> None: if update.message.text: diff --git a/bot/commands/weather.py b/bot/commands/weather.py index 0712893..db14949 100644 --- a/bot/commands/weather.py +++ b/bot/commands/weather.py @@ -75,7 +75,7 @@ class Weather(BotFunc): forecast_time = query.data.replace("time-","") weather = self.get_weather(self.city, forecast_time) query.edit_message_text( - text = "Weather: \n\n" + weather, + text = "Broadcast for {}: \n\n{}".format(self.city, weather), parse_mode = ParseMode.HTML ) super().log_activity(read = True, execute = True, send = True) diff --git a/bot/main.py b/bot/main.py index 5713f60..b1774ac 100644 --- a/bot/main.py +++ b/bot/main.py @@ -36,7 +36,7 @@ class ChatBot(): def add_commands(self): # Mark modules as available - db = self.db + db = self.db_utils self.help_module = self.commands.help.Help(db) self.sub_modules = { "weather": self.commands.weather.Weather(self.api_weather, db), @@ -60,7 +60,7 @@ class ChatBot(): self.help_module.add_commands(self.sub_modules) def start(self): - self.sub_modules = {"clock" : self.commands.clock.Clock(self.db, self.modules["clock"], self.api_art)} + self.sub_modules = {"clock" : self.commands.clock.Clock(self.db_utils, self.modules["clock"], self.api_art)} self.add_commands() self.telegram.start_polling( poll_interval=0.2, diff --git a/persistence/database.py b/persistence/database.py index 5998db9..ad80a00 100644 --- a/persistence/database.py +++ b/persistence/database.py @@ -22,19 +22,17 @@ db_connection = PooledMySQLDatabase( ) -def auto_connect_db(action): - def decorated(func): - def wrapper(*args, **kwargs): - #before: - db_connection.connect() - ret = func(*args, **kwargs) - #after: - db_connection.close() - # also, action is in scope now - return ret - - return wrapper - return decorated +def auto_connect_db(func): + def wrapper(*args, **kwargs): + #before: + db_connection.connect() + ret = func(*args, **kwargs) + #after: + db_connection.close() + # also, action is in scope now + return ret + + return wrapper @@ -57,20 +55,21 @@ class DatabaseUtils: else: # does not exist return -1 - + @auto_connect_db def chat_log(self, **kwargs): models.ChatMetric(**kwargs) - + @auto_connect_db def list_get(self, list_name=""): if not list_name: # return all - return models.List.select() + cursor = models.List.select(models.List.name).execute() + return [k.name for k in cursor] else: - return models.List.get(models.List.name == list_name) + return models.List.get(models.List.name == list_name).content_as_list - - def list_update(self, list_name, append="", replace=[]): - if replace: + @auto_connect_db + def list_update(self, list_name, append="", replace=None): + if replace != None: models.List.get(models.List.name == list_name).set_content(replace) elif append: l_obj = models.List.get(models.List.name == list_name) @@ -80,9 +79,14 @@ class DatabaseUtils: else: logger.warning("Empty update_list() query was made. Ignoring") + @auto_connect_db + def list_create(self, list_name): + models.List(name=list_name).save() + @auto_connect_db def list_delete(self, list_name): - models.List.delete().where(self.db.lists.name == list_name).execute() + models.List.delete().where(models.List.name == list_name).execute() + @auto_connect_db def sensor_log(self, **kwargs): models.SensorMetric(**kwargs).save() \ No newline at end of file diff --git a/persistence/models.py b/persistence/models.py index 5954b23..ab2360e 100644 --- a/persistence/models.py +++ b/persistence/models.py @@ -45,11 +45,12 @@ class ErrorMetric(Metric): class List(DBModel): name = CharField(unique=True) - content = TextField() # unlimited length, use to serialise list into + content = TextField(default="") # unlimited length, use to serialise list into @property def content_as_list(self): - return json.loads(self.content) + return json.loads(self.content or '[]') def set_content(self, list_content): - self.content = json.dumps(list_content) \ No newline at end of file + self.content = json.dumps(list_content) + self.save() \ No newline at end of file diff --git a/req_pi.txt b/req_pi.txt index 17ecda2..5f3efb5 100644 --- a/req_pi.txt +++ b/req_pi.txt @@ -1,17 +1,17 @@ -Adafruit-Blinka==6.10.3 -adafruit-circuitpython-dht==3.6.1 -Adafruit-PlatformDetect==3.14.2 -Adafruit-PureIO==1.1.9 -board==1.0 -importlib-metadata==4.6.0 -pkg-resources==0.0.0 -psycopg2==2.9.1 -psycopg2-binary==2.9.1 -pyftdi==0.53.1 -pyserial==3.5 -pyusb==1.1.1 -rpi-ws281x==4.3.0 -RPi.GPIO==0.7.0 -sysv-ipc==1.1.0 -typing-extensions==3.10.0.0 -zipp==3.4.1 \ No newline at end of file +Adafruit-Blinka>=6.10.3 +adafruit-circuitpython-dht>=3.6.1 +Adafruit-PlatformDetect>=3.14.2 +Adafruit-PureIO>=1.1.9 +board>=1.0 +importlib-metadata>=4.6.0 +pkg-resources>=0.0.0 +psycopg2>=2.9.1 +psycopg2-binary>=2.9.1 +pyftdi>=0.53.1 +pyserial>=3.5 +pyusb>=1.1.1 +rpi-ws281x>=4.3.0 +RPi.GPIO>=0.7.0 +sysv-ipc>=1.1.0 +typing-extensions>=3.10.0.0 +zipp>=3.4.1 \ No newline at end of file diff --git a/server.py b/server.py index 38e0757..87c2a87 100644 --- a/server.py +++ b/server.py @@ -28,7 +28,7 @@ class BroadcastLauncher(launcher.Launcher): """Launcher for all server-side modules. The hard-computations""" def __init__(self): - self.bot_module = main.ChatBot(name="Norbit", version="4.0a") # ??? + self.bot_module = main.ChatBot(name="Norbit", version="4.1a") # ??? self.clock_backend_module = c_back.ClockBackend() # threaded through threading.Timer self.broadcast_module = b_out.BroadcastUpdates(port="1111") # Thread # self.dashboard_module = d_out.DashBoard(port="80") # ??? threaded as Thread