# database.py import sqlite3 import random from datetime import datetime, timedelta from pathlib import Path from typing import Any, Dict, Tuple DB_FILE = "data/database.db" # guild-state queries CREATE_GUILD_STATE_QUERY = """ INSERT INTO guild_state (guild_id, cooldown_until, cooldown_ratio, self_control, last_updated) VALUES (?, datetime('now'), 1.0, 1.0, datetime('now')) """ GET_GUILD_STATE_QUERY = """ SELECT cooldown_until, cooldown_ratio, self_control, last_updated FROM guild_state WHERE guild_id = ? """ CONFIGURE_COOLDOWN_RATIO_QUERY = """ UPDATE guild_state SET cooldown_ratio = ? WHERE guild_id = ? """ DAMAGE_SELF_CONTROL_QUERY = """ UPDATE guild_state SET self_control = self_control * 0.9 WHERE guild_id = ? """ RESET_COOLDOWN_QUERY = """ UPDATE guild_state SET cooldown_until = ?, self_control = self_control + 1.0, last_updated = datetime('now') WHERE guild_id = ? """ def ensure_db() -> None: """Initialize SQLite database and create tables if needed.""" Path(DB_FILE).parent.mkdir(parents=True, exist_ok=True) with sqlite3.connect(DB_FILE) as conn: cursor = conn.cursor() cursor.execute(""" CREATE TABLE IF NOT EXISTS guild_state ( guild_id TEXT PRIMARY KEY, cooldown_until TEXT NOT NULL DEFAULT '1970-01-01T00:00:00', cooldown_ratio REAL NOT NULL DEFAULT 1.0, self_control REAL NOT NULL DEFAULT 1.0, last_updated TEXT NOT NULL DEFAULT (datetime('now')) ) """) cursor.execute(""" CREATE TABLE IF NOT EXISTS active_riddles ( channel_id TEXT PRIMARY KEY, riddle_index INTEGER NOT NULL, nb_clues INTEGER NOT NULL DEFAULT -1, message_id INTEGER NOT NULL, solver_id TEXT, created_at TEXT NOT NULL DEFAULT (datetime('now')), updated_at TEXT NOT NULL DEFAULT (datetime('now')) ) """) conn.commit() def get_connection() -> sqlite3.Connection: """Return SQLite connection with row factory for named column access.""" conn = sqlite3.connect(DB_FILE) conn.row_factory = sqlite3.Row return conn def get_guild_state(guild_id: str) -> Dict[str, Any]: with get_connection() as conn: cursor = conn.cursor() cursor.execute(GET_GUILD_STATE_QUERY,(guild_id,)) row = cursor.fetchone() if not row: cursor.execute(CREATE_GUILD_STATE_QUERY, (guild_id,)) conn.commit() cursor.execute(GET_GUILD_STATE_QUERY,(guild_id,)) row = cursor.fetchone() if row: return { "cooldown_until": row["cooldown_until"], "cooldown_ratio": row["cooldown_ratio"], "self_control": row["self_control"], "last_updated": row["last_updated"], } return {} def rhyme_configure_cooldown_ratio(guild_id: str, cooldown_ratio: float) -> bool: with get_connection() as conn: cursor = conn.cursor() cursor.execute(CONFIGURE_COOLDOWN_RATIO_QUERY, (cooldown_ratio, guild_id,)) conn.commit() def rhyme_damage_self_control(guild_id: str) -> None: with get_connection() as conn: cursor = conn.cursor() cursor.execute(DAMAGE_SELF_CONTROL_QUERY, (guild_id,)) conn.commit() def rhyme_reset_cooldown( guild_id: str, ratio: float ) -> None: with get_connection() as conn: # Calculate new cooldown duration wait_time = random.randint(900, 10800) if bool(random.getrandbits(1)) else random.randint(0, 900) wait_time = 0 if ratio < 0 else int(wait_time * ratio) cooldown_until = datetime.now().replace(second=0, microsecond=0) + timedelta(seconds=wait_time) cursor = conn.cursor() cursor.execute(RESET_COOLDOWN_QUERY, (cooldown_until.isoformat(), guild_id,)) conn.commit() def get_active_riddle(channel_id: str): with get_connection() as conn: cursor = conn.cursor() cursor.execute("SELECT * FROM active_riddles WHERE channel_id = ?", (str(channel_id),)) row = cursor.fetchone() if row: return { "channel_id": row["channel_id"], "riddle_index": row["riddle_index"], "nb_clues": row["nb_clues"], "message_id": row["message_id"], "solver_id": row["solver_id"], "created_at": row["created_at"], "updated_at": row["updated_at"], } return None def save_active_riddle( channel_id: str, riddle_index: int, nb_clues: int, message_id: int, solver_id: str = None, ): with get_connection() as conn: cursor = conn.cursor() # Si solver_id existe, on considère que c'est résolu et on garde l'entrée pour historique # Sinon on met à jour l'énigme en cours cursor.execute( """ INSERT OR REPLACE INTO active_riddles (channel_id, riddle_index, nb_clues, message_id, solver_id, updated_at) VALUES (?, ?, ?, ?, ?, datetime('now')) """, (str(channel_id), riddle_index, nb_clues, message_id, solver_id) ) conn.commit() def remove_active_riddle(channel_id: str) -> bool: """Supprimer une énigme active du suivi.""" with get_connection() as conn: cursor = conn.cursor() cursor.execute("DELETE FROM active_riddles WHERE channel_id = ?", (str(channel_id),)) conn.commit() return cursor.rowcount > 0 def get_all_active_riddles(): """Récupérer toutes les énigmes actives (pour debug).""" with get_connection() as conn: cursor = conn.cursor() cursor.execute("SELECT * FROM active_riddles") return [ { "channel_id": row["channel_id"], "riddle_index": row["riddle_index"], "nb_clues": row["nb_clues"], "message_id": row["message_id"], "solver_id": row["solver_id"], "created_at": row["created_at"], "updated_at": row["updated_at"], } for row in cursor.fetchall() ]