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 <noreply@anthropic.com>
This commit is contained in:
@@ -104,6 +104,14 @@ class BCSBot(commands.Bot):
|
|||||||
loaded = self.drama_tracker.load_user_states(states)
|
loaded = self.drama_tracker.load_user_states(states)
|
||||||
logger.info("Loaded %d user states from database.", loaded)
|
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.sentiment")
|
||||||
await self.load_extension("cogs.commands")
|
await self.load_extension("cogs.commands")
|
||||||
await self.load_extension("cogs.chat")
|
await self.load_extension("cogs.chat")
|
||||||
|
|||||||
@@ -559,6 +559,9 @@ class CommandsCog(commands.Cog):
|
|||||||
self.bot.current_mode = mode
|
self.bot.current_mode = mode
|
||||||
new_config = self.bot.get_mode_config()
|
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
|
# Update bot status to reflect the mode
|
||||||
status_text = new_config.get("description", "Monitoring vibes...")
|
status_text = new_config.get("description", "Monitoring vibes...")
|
||||||
await self.bot.change_presence(
|
await self.bot.change_presence(
|
||||||
|
|||||||
@@ -126,6 +126,15 @@ class Database:
|
|||||||
ALTER TABLE UserState ADD UserNotes NVARCHAR(MAX) NULL
|
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("""
|
cursor.execute("""
|
||||||
IF NOT EXISTS (SELECT * FROM sys.tables WHERE name = 'LlmLog')
|
IF NOT EXISTS (SELECT * FROM sys.tables WHERE name = 'LlmLog')
|
||||||
CREATE TABLE LlmLog (
|
CREATE TABLE LlmLog (
|
||||||
@@ -414,6 +423,57 @@ class Database:
|
|||||||
finally:
|
finally:
|
||||||
conn.close()
|
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):
|
async def close(self):
|
||||||
"""No persistent connection to close (connections are per-operation)."""
|
"""No persistent connection to close (connections are per-operation)."""
|
||||||
pass
|
pass
|
||||||
|
|||||||
Reference in New Issue
Block a user