From ccef6617bb3829f1e3b8f109c17b186867bd80ca Mon Sep 17 00:00:00 2001 From: Nikolaos Karaolidis Date: Tue, 28 Mar 2023 22:33:41 +0300 Subject: [PATCH] Add basic thread handling Signed-off-by: Nikolaos Karaolidis --- src/handlers.py | 107 +++++++++++++++++++++++++++++++++++++++++++ src/main.py | 36 ++------------- src/message.py | 8 ---- src/util/database.py | 2 +- src/util/logger.py | 2 +- 5 files changed, 114 insertions(+), 41 deletions(-) create mode 100644 src/handlers.py delete mode 100644 src/message.py diff --git a/src/handlers.py b/src/handlers.py new file mode 100644 index 0000000..a8700e5 --- /dev/null +++ b/src/handlers.py @@ -0,0 +1,107 @@ +import fbchat +from util.logger import logger +from util.database import database +from util.session import session + + +def activate_thread(thread: fbchat.Thread): + thread_db = database.threads.update_one( + {"_id": thread.id}, {"$setOnInsert": { + "type": "group" if isinstance(thread, fbchat.Group) else "user" if isinstance(thread, fbchat.User) else "other", + "messages": [] + }}, upsert=True) + + database.threads.create_index( + "messages.created_at", expireAfterSeconds=900) + + thread.send_text("> Admina activated in thread") + return thread_db + + +def deactivate_thread(thread: fbchat.Thread): + thread.send_text("> Admina deactivated in thread") + return database.threads.delete_one({"_id": thread.id}) + + +COMMANDS = { + "ping": { + "description": "Pong!", + "usage": "!ping", + "admin_only": False, + "handler": lambda event: event.thread.send_text("Pong!"), + }, + "activate": { + "description": "Activate a thread", + "usage": "!activate", + "admin_only": False, + "handler": lambda event: activate_thread(event.thread), + }, + "deactivate": { + "description": "Deactivate a thread", + "usage": "!deactivate", + "admin_only": True, + "handler": lambda event: deactivate_thread(event.thread), + }, +} + + +def handle_message(_, event: fbchat.MessageEvent): + if not event.message.text: + return + + if not database.threads.find_one({"_id": event.thread.id}): + if not event.message.text.lower().startswith("!activate"): + return + + return COMMANDS["activate"]["handler"](event) + + database.threads.update_one( + {"_id": event.thread.id}, {"$push": { + "messages": { + "id": event.message.id, + "role": + "assistant" if event.message.author == session.user.id and event.message.text.startswith("#>") + else "user" if "@admina" in event.message.text.lower() + else None, + "author": event.message.author, + "created_at": event.message.created_at.timestamp(), + "text": event.message.text, + "attachments": [{ + "url": attachment.url, + "original_url": attachment.original_url, + "title": attachment.title, + "description": attachment.description, + "source": attachment.source, + "image": attachment.image.url if attachment.image else None, + "original_image_url": attachment.original_image_url, + } for attachment in event.message.attachments] + } + }}) + + logger.info( + f"Received message from {event.message.author} in {event.thread.id}" + ) + + if event.message.text.startswith("!"): + command = event.message.text[1:].split(" ") + + if command[0].lower() not in COMMANDS: + return + + if COMMANDS[command[0].lower()]["admin_only"]: + if not isinstance(event.thread, fbchat.Group): + return event.thread.send_text("> This command can only be used in groups") + + if session.user.id not in event.thread.admins: + return event.thread.send_text("> Account running the bot must be an admin to use this command") + + if event.message.author not in event.thread.admins: + return event.thread.send_text("> You must be an admin to use this command") + + return COMMANDS[command[0]]["handler"](event) + + if "@everyone" in event.message.text.lower(): + return event.thread.send_text(">TODO: @everyone") + + if "@admina" in event.message.text.lower(): + return event.thread.send_text(">TODO: @admina") diff --git a/src/main.py b/src/main.py index dca1088..4ecf97b 100644 --- a/src/main.py +++ b/src/main.py @@ -1,6 +1,7 @@ import fbchat -from message import handle_message +from handlers import handle_message from util.logger import logger +from util.session import session import atexit from blinker import Signal @@ -8,43 +9,16 @@ from threading import Thread from os import environ -def on_event(sender, session, event): +def on_event(sender, event): match sender: case fbchat.MessageEvent: Thread(target=handle_message, args=( - sender, session, event)).start() + sender, event)).start() case _: logger.debug("Ignoring event: %s", event) def main(): - email = environ.get("FB_EMAIL") - password = environ.get("FB_PASSWORD") - cookie_path = environ.get("FB_COOKIE_PATH") or "session.json" - - if not email or not password: - raise ValueError("FB_EMAIL and FB_PASSWORD must be set") - - try: - logger.info("Loading cookies from %s", cookie_path) - session = fbchat.Session.from_file(cookie_path) - except (FileNotFoundError, fbchat.FacebookError): - logger.warning("Failed to load cookies from %s", cookie_path) - logger.info("Logging in with email and password") - session = fbchat.Session.login( - email, password, on_2fa_callback=lambda: input("Input 2FA code: ") - ) - session.to_file(cookie_path) - logger.info("Saved cookies to %s", cookie_path) - - finally: - if session.is_logged_in(): - logger.info("Logged in as %s", session.user.id) - else: - raise ValueError("Failed to log in") - - atexit.register(lambda: session.to_file(cookie_path)) - listener = fbchat.Listener( session=session, chat_on=False, foreground=False) @@ -52,7 +26,7 @@ def main(): events.connect(on_event) for event in listener.listen(): - events.send(type(event), session=session, event=event) + events.send(type(event), event=event) if __name__ == "__main__": diff --git a/src/message.py b/src/message.py deleted file mode 100644 index 69282d2..0000000 --- a/src/message.py +++ /dev/null @@ -1,8 +0,0 @@ -import fbchat -from util.logger import logger -from util.database import database -from util.session import session - - -def handle_message(_, event: fbchat.MessageEvent): - logger.info("Received message from %s: %s", event.author.id, event.text) diff --git a/src/util/database.py b/src/util/database.py index 27c06b4..f1a38c7 100644 --- a/src/util/database.py +++ b/src/util/database.py @@ -12,4 +12,4 @@ if not MONGO_HOST or not MONGO_PORT or not MONGO_USERNAME or not MONGO_PASSWORD "MONGO_HOST, MONGO_PORT, MONGO_USERNAME, MONGO_PASSWORD and MONGO_DATABASE must be set") database = pymongo.MongoClient(MONGO_HOST, int( - MONGO_PORT), username=MONGO_USERNAME, password=MONGO_PASSWORD, authSource=MONGO_DATABASE)[MONGO_DATABASE].client + MONGO_PORT), username=MONGO_USERNAME, password=MONGO_PASSWORD, authSource=MONGO_DATABASE)[MONGO_DATABASE] diff --git a/src/util/logger.py b/src/util/logger.py index ecb4548..dbd1f43 100644 --- a/src/util/logger.py +++ b/src/util/logger.py @@ -16,7 +16,7 @@ logger = logging.getLogger() logger.setLevel(logging.DEBUG) formatter = logging.Formatter( - '%(asctime)s:%(levelname)s:%(name)s:%(module)s:%(funcName)s:%(lineno)s:%(message)s') + '%(asctime)s:%(levelname)s:%(module)s:%(funcName)s:%(lineno)s:%(message)s') stream_handler = logging.StreamHandler(sys.stdout) stream_handler.setLevel(logging.INFO)