Add basic thread handling

Signed-off-by: Nikolaos Karaolidis <nick@karaolidis.com>
This commit is contained in:
2023-03-28 22:33:41 +03:00
parent aef5a27d02
commit ccef6617bb
5 changed files with 114 additions and 41 deletions

107
src/handlers.py Normal file
View File

@@ -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")

View File

@@ -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__":

View File

@@ -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)

View File

@@ -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]

View File

@@ -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)