From 6e1a73847d1a7f1908def399c01393a4b4e72475 Mon Sep 17 00:00:00 2001 From: AJ Isaacs Date: Mon, 23 Feb 2026 09:26:00 -0500 Subject: [PATCH] Persist bot mode across restarts via database Adds a BotSettings key-value table. The active mode is saved when changed via /bcs-mode and restored on startup. Co-Authored-By: Claude Opus 4.6 --- bot.py | 8 +++++++ cogs/commands.py | 3 +++ utils/database.py | 60 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 71 insertions(+) diff --git a/bot.py b/bot.py index 7049a0d..9cf26ad 100644 --- a/bot.py +++ b/bot.py @@ -104,6 +104,14 @@ class BCSBot(commands.Bot): loaded = self.drama_tracker.load_user_states(states) logger.info("Loaded %d user states from database.", loaded) + # Restore saved mode + saved_mode = await self.db.load_setting("current_mode") + if saved_mode: + modes = self.config.get("modes", {}) + if saved_mode in modes and isinstance(modes.get(saved_mode), dict): + self.current_mode = saved_mode + logger.info("Restored saved mode: %s", saved_mode) + await self.load_extension("cogs.sentiment") await self.load_extension("cogs.commands") await self.load_extension("cogs.chat") diff --git a/cogs/commands.py b/cogs/commands.py index ca6efdf..12f9527 100644 --- a/cogs/commands.py +++ b/cogs/commands.py @@ -559,6 +559,9 @@ class CommandsCog(commands.Cog): self.bot.current_mode = mode new_config = self.bot.get_mode_config() + # Persist mode to database + asyncio.create_task(self.bot.db.save_setting("current_mode", mode)) + # Update bot status to reflect the mode status_text = new_config.get("description", "Monitoring vibes...") await self.bot.change_presence( diff --git a/utils/database.py b/utils/database.py index 67ee0de..e61c56d 100644 --- a/utils/database.py +++ b/utils/database.py @@ -126,6 +126,15 @@ class Database: ALTER TABLE UserState ADD UserNotes NVARCHAR(MAX) NULL """) + cursor.execute(""" + IF NOT EXISTS (SELECT * FROM sys.tables WHERE name = 'BotSettings') + CREATE TABLE BotSettings ( + SettingKey NVARCHAR(100) NOT NULL PRIMARY KEY, + SettingValue NVARCHAR(MAX) NULL, + UpdatedAt DATETIME2 NOT NULL DEFAULT SYSUTCDATETIME() + ) + """) + cursor.execute(""" IF NOT EXISTS (SELECT * FROM sys.tables WHERE name = 'LlmLog') CREATE TABLE LlmLog ( @@ -414,6 +423,57 @@ class Database: finally: conn.close() + # ------------------------------------------------------------------ + # Bot Settings (key-value store) + # ------------------------------------------------------------------ + async def save_setting(self, key: str, value: str) -> None: + if not self._available: + return + try: + await asyncio.to_thread(self._save_setting_sync, key, value) + except Exception: + logger.exception("Failed to save setting %s", key) + + def _save_setting_sync(self, key: str, value: str): + conn = self._connect() + try: + cursor = conn.cursor() + cursor.execute( + """MERGE BotSettings AS target + USING (SELECT ? AS SettingKey) AS source + ON target.SettingKey = source.SettingKey + WHEN MATCHED THEN + UPDATE SET SettingValue = ?, UpdatedAt = SYSUTCDATETIME() + WHEN NOT MATCHED THEN + INSERT (SettingKey, SettingValue) VALUES (?, ?);""", + key, value, key, value, + ) + cursor.close() + finally: + conn.close() + + async def load_setting(self, key: str, default: str | None = None) -> str | None: + if not self._available: + return default + try: + return await asyncio.to_thread(self._load_setting_sync, key, default) + except Exception: + logger.exception("Failed to load setting %s", key) + return default + + def _load_setting_sync(self, key: str, default: str | None) -> str | None: + conn = self._connect() + try: + cursor = conn.cursor() + cursor.execute( + "SELECT SettingValue FROM BotSettings WHERE SettingKey = ?", key + ) + row = cursor.fetchone() + cursor.close() + return row[0] if row else default + finally: + conn.close() + async def close(self): """No persistent connection to close (connections are per-operation).""" pass