From a20e9eb9f08bee9176e5cbdbc0300f76e8f8bc94 Mon Sep 17 00:00:00 2001 From: ctih1 Date: Sun, 27 Jul 2025 12:24:51 +0300 Subject: [PATCH] added sync hub --- assets/cogs/breaking_news.py | 6 ++ assets/cogs/internal/base_commands.py | 20 ++++++ bot.py | 41 +++++++----- modules/prestartchecks.py | 8 +++ modules/settings.py | 13 ++++ modules/sync_conenctor.py | 93 +++++++++++++++++++++++++++ requirements.txt | 3 +- settings/settings.example.json | 8 ++- 8 files changed, 173 insertions(+), 19 deletions(-) create mode 100644 modules/sync_conenctor.py diff --git a/assets/cogs/breaking_news.py b/assets/cogs/breaking_news.py index 880db22..eee3f82 100644 --- a/assets/cogs/breaking_news.py +++ b/assets/cogs/breaking_news.py @@ -10,6 +10,7 @@ import logging from modules.settings import instance as settings_manager import re import time +from modules.sync_conenctor import instance as sync_hub logger = logging.getLogger("goober") @@ -54,6 +55,11 @@ class BreakingNews(commands.Cog): if not message.content.lower().startswith("breaking news:"): logger.debug("Ignoring message - doesnt start with breaking news:") return + + if not sync_hub.can_breaking_news(message.id): + logger.debug("Sync hub denied breaking news request") + return + texts = re.split("breaking news:", message.content, flags=re.IGNORECASE) diff --git a/assets/cogs/internal/base_commands.py b/assets/cogs/internal/base_commands.py index f1625cf..cf2373a 100644 --- a/assets/cogs/internal/base_commands.py +++ b/assets/cogs/internal/base_commands.py @@ -14,6 +14,7 @@ import cpuinfo import sys import subprocess import updater +from modules.sync_conenctor import instance as sync_connector settings = settings_manager.settings @@ -204,6 +205,25 @@ class BaseCommands(commands.Cog): await send_message(ctx, response.text) + @requires_admin() + @commands.command() + async def test_synchub(self, ctx: commands.Context, message_id: str | None) -> None: + message_id = message_id or "0" + status = sync_connector.can_react(int(message_id)) + + await send_message(ctx, f"Is allowed to react to message id {message_id}? {status} (connection active? {sync_connector.connected})") + + + @requires_admin() + @commands.command() + async def connect_synchub(self, ctx: commands.Context) -> None: + await send_message(ctx, "Trying to connect...") + + connected = sync_connector.try_to_connect() + if connected: + await send_message(ctx, "Succesfully connected to sync hub!") + else: + await send_message(ctx, "Failed to connect to sync hub") async def setup(bot: discord.ext.commands.Bot): print("Setting up base_commands") diff --git a/bot.py b/bot.py index 8092b1d..6c5a30a 100644 --- a/bot.py +++ b/bot.py @@ -1,3 +1,20 @@ +import logging +from modules.logger import GooberFormatter + +logger = logging.getLogger("goober") +logger.setLevel(logging.DEBUG) + +console_handler = logging.StreamHandler() +console_handler.setLevel(logging.DEBUG) +console_handler.setFormatter(GooberFormatter()) + +file_handler = logging.FileHandler("log.txt", mode="w+", encoding="UTF-8") +file_handler.setLevel(logging.DEBUG) +file_handler.setFormatter(GooberFormatter(colors=False)) + +logger.addHandler(console_handler) +logger.addHandler(file_handler) + import os import re import json @@ -25,7 +42,6 @@ from typing import ( ) import logging from modules.prestartchecks import start_checks -from modules.logger import GooberFormatter import modules.keys as k from modules import key_compiler import logging @@ -33,6 +49,8 @@ from watchdog.observers import Observer from watchdog.events import FileSystemEventHandler from modules.settings import instance as settings_manager, ActivityType from modules.permission import requires_admin +from modules.sync_conenctor import instance as sync_connector + import threading @@ -49,20 +67,6 @@ def build_keys(): build_keys() -logger = logging.getLogger("goober") -logger.setLevel(logging.DEBUG) - -console_handler = logging.StreamHandler() -console_handler.setLevel(logging.DEBUG) -console_handler.setFormatter(GooberFormatter()) - -file_handler = logging.FileHandler("log.txt", mode="w+", encoding="UTF-8") -file_handler.setLevel(logging.DEBUG) -file_handler.setFormatter(GooberFormatter(colors=False)) - -logger.addHandler(console_handler) -logger.addHandler(file_handler) - settings = settings_manager.settings splash_text: str = "" @@ -272,7 +276,7 @@ async def demotivator(ctx: commands.Context) -> None: shutil.copy(fallback_image, temp_input) input_path = temp_input - output_path: Optional[str] = await gen_demotivator(input_path) + output_path: Optional[str] = await gen_demotivator(input_path) # type: ignore if output_path is None or not os.path.isfile(output_path): if temp_input and os.path.exists(temp_input): @@ -354,6 +358,11 @@ async def on_message(message: discord.Message) -> None: if sentiment_score > 0.8: if not settings["bot"]["react_to_messages"]: return + if not sync_connector.can_react(message.id): + logger.info("Sync hub determined that this instance cannot react") + return + + emoji = random.choice(EMOJIS) try: await message.add_reaction(emoji) diff --git a/modules/prestartchecks.py b/modules/prestartchecks.py index 1c50175..f4f4898 100644 --- a/modules/prestartchecks.py +++ b/modules/prestartchecks.py @@ -12,6 +12,7 @@ import importlib.metadata import logging import modules.keys as k from modules.settings import instance as settings_manager +from modules.sync_conenctor import instance as sync_hub settings = settings_manager.settings @@ -68,6 +69,7 @@ def check_requirements(): "better_profanity": "better-profanity", "dotenv": "python-dotenv", "pil": "pillow", + "websocket": "websocket-client" } parent_dir = os.path.dirname(os.path.abspath(__file__)) @@ -260,6 +262,11 @@ def presskey2skip(timeout): finally: termios.tcsetattr(fd, termios.TCSADRAIN, old_settings) +def check_synchub(): + if not sync_hub.connected: + logger.warning("Sync hub not connected properly! The bot will not be able to react to messages, or create breaking news unless you disable synchub in settings") + else: + logger.info("Sync hub is conencted") beta = beta @@ -277,6 +284,7 @@ def start_checks(): check_memory() check_memoryjson() check_cpu() + check_synchub() if os.path.exists(".env"): pass else: diff --git a/modules/settings.py b/modules/settings.py index af34e38..8943453 100644 --- a/modules/settings.py +++ b/modules/settings.py @@ -9,6 +9,9 @@ logger = logging.getLogger("goober") ActivityType = Literal["listening", "playing", "streaming", "competing", "watching"] +class SyncHub(TypedDict): + url: str + enabled: bool class Activity(TypedDict): content: str @@ -32,6 +35,7 @@ class BotSettings(TypedDict): misc: MiscBotOptions enabled_cogs: List[str] active_memory: str + sync_hub: SyncHub class SettingsType(TypedDict): @@ -93,6 +97,15 @@ class Settings: del self.settings["bot"]["misc"]["active_song"] # type: ignore + sync_hub: SyncHub | None = self.settings.get("bot", {}).get("sync_hub") + + if not sync_hub: + logger.warning("Adding sync hub settings") + self.settings["bot"]["sync_hub"] = { + "enabled": True, + "url": "ws://goober.frii.site" + } + self.commit() def reload_settings(self) -> None: diff --git a/modules/sync_conenctor.py b/modules/sync_conenctor.py new file mode 100644 index 0000000..4076e2f --- /dev/null +++ b/modules/sync_conenctor.py @@ -0,0 +1,93 @@ +import websocket +from modules.settings import instance as settings_manager +import logging + + +logger = logging.getLogger("goober") +settings = settings_manager.settings + +class SyncConnector: + def __init__(self, url: str): + self.connected: bool = True + self.url = url + self.client: websocket.WebSocket | None = None + + self.try_to_connect() + + def __connect(self) -> bool: + try: + self.client = websocket.create_connection(self.url) + except OSError as e: + logger.debug(e) + return False + + return True + + def try_to_connect(self) -> bool: + if self.__connect(): + logger.info("Connected to sync hub!") + self.connected = True + else: + logger.error("Failed to connect to sync hub.. Disabling for the time being") + self.connected = False + + return self.connected + + + def can_react(self, message_id: int) -> bool: + """ + Checks if goober can react to a messsage + """ + + return self.can_event(message_id, "react") + + + def can_breaking_news(self, message_id: int) -> bool: + """ + Checks if goober can send a breaking news alert + """ + + return self.can_event(message_id, "breaking_news") + + + def can_event(self, message_id: int, event: str, retry_depth: int = 0) -> bool: + """ + Checks if goober can send a breaking news alert + """ + + logger.debug(f"Checking {event} for message {message_id}") + + if not settings["bot"]["sync_hub"]["enabled"]: + logger.info("Skipping sync hub check") + return True + + if retry_depth > 2: + logger.error("Too many retries. Returning false") + return False + + if not self.client: + logger.error("Client no connected") + return False + + if not self.connected: + logger.warning("Not connected to sync hub.. Returning False to avoid conflicts") + return False + + try: + self.client.send(f"event={event};ref={message_id}") + return self.client.recv() == "unhandled" + except ConnectionResetError: + logger.error("Connection to sync hub reset! Retrying...") + + if not self.__connect(): + logger.error("Failed to reconnect to sync hub... Disabling") + self.connected = False + return False + + logger.info("Managed to reconnect to sync hub! Retrying requests") + self.connected = True + return self.can_event(message_id, event, retry_depth+1) + + + +instance = SyncConnector("ws://localhost:80") \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 447934f..0a82eff 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,4 +9,5 @@ python-dotenv dotenv pillow watchdog -py-cpuinfo \ No newline at end of file +py-cpuinfo +websocket-client \ No newline at end of file diff --git a/settings/settings.example.json b/settings/settings.example.json index 3f7b6af..e0111a6 100644 --- a/settings/settings.example.json +++ b/settings/settings.example.json @@ -1,6 +1,6 @@ { "bot": { - "prefix": "p.", + "prefix": "o.", "owner_ids": [ 642441889181728810 ], @@ -25,7 +25,11 @@ "enabled_cogs": [ "pulse", "breaking_news" - ] + ], + "sync_hub": { + "url": "ws://goober.frii.site", + "enabled": true + } }, "locale": "en", "name": "gubert",