Add basic thread handling
Signed-off-by: Nikolaos Karaolidis <nick@karaolidis.com>
This commit is contained in:
107
src/handlers.py
Normal file
107
src/handlers.py
Normal 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")
|
36
src/main.py
36
src/main.py
@@ -1,6 +1,7 @@
|
|||||||
import fbchat
|
import fbchat
|
||||||
from message import handle_message
|
from handlers import handle_message
|
||||||
from util.logger import logger
|
from util.logger import logger
|
||||||
|
from util.session import session
|
||||||
|
|
||||||
import atexit
|
import atexit
|
||||||
from blinker import Signal
|
from blinker import Signal
|
||||||
@@ -8,43 +9,16 @@ from threading import Thread
|
|||||||
from os import environ
|
from os import environ
|
||||||
|
|
||||||
|
|
||||||
def on_event(sender, session, event):
|
def on_event(sender, event):
|
||||||
match sender:
|
match sender:
|
||||||
case fbchat.MessageEvent:
|
case fbchat.MessageEvent:
|
||||||
Thread(target=handle_message, args=(
|
Thread(target=handle_message, args=(
|
||||||
sender, session, event)).start()
|
sender, event)).start()
|
||||||
case _:
|
case _:
|
||||||
logger.debug("Ignoring event: %s", event)
|
logger.debug("Ignoring event: %s", event)
|
||||||
|
|
||||||
|
|
||||||
def main():
|
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(
|
listener = fbchat.Listener(
|
||||||
session=session, chat_on=False, foreground=False)
|
session=session, chat_on=False, foreground=False)
|
||||||
|
|
||||||
@@ -52,7 +26,7 @@ def main():
|
|||||||
events.connect(on_event)
|
events.connect(on_event)
|
||||||
|
|
||||||
for event in listener.listen():
|
for event in listener.listen():
|
||||||
events.send(type(event), session=session, event=event)
|
events.send(type(event), event=event)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
@@ -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)
|
|
@@ -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")
|
"MONGO_HOST, MONGO_PORT, MONGO_USERNAME, MONGO_PASSWORD and MONGO_DATABASE must be set")
|
||||||
|
|
||||||
database = pymongo.MongoClient(MONGO_HOST, int(
|
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]
|
||||||
|
@@ -16,7 +16,7 @@ logger = logging.getLogger()
|
|||||||
logger.setLevel(logging.DEBUG)
|
logger.setLevel(logging.DEBUG)
|
||||||
|
|
||||||
formatter = logging.Formatter(
|
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 = logging.StreamHandler(sys.stdout)
|
||||||
stream_handler.setLevel(logging.INFO)
|
stream_handler.setLevel(logging.INFO)
|
||||||
|
Reference in New Issue
Block a user