import sqlite3 import mysql.connector import json import time from mysql.connector import Error as MySQLError from botdiscord.config import get_db_config, get_db_type _connection = None MAX_CONNECTION_RETRIES = 30 CONNECTION_RETRY_DELAY = 2 def get_connection(): global _connection db_type = get_db_type() if db_type == "mysql": db_config = get_db_config() if _connection is None: for attempt in range(1, MAX_CONNECTION_RETRIES + 1): try: _connection = mysql.connector.connect( host=db_config.get("host"), port=db_config.get("port"), user=db_config.get("user"), password=db_config.get("password"), database=db_config.get("name"), consume_results=True, connection_timeout=10 ) print(f"[DB] MySQL conectado exitosamente") break except MySQLError as e: if attempt < MAX_CONNECTION_RETRIES: print(f"[DB] Intento {attempt}/{MAX_CONNECTION_RETRIES}: MySQL no disponible, reintentando en {CONNECTION_RETRY_DELAY}s...") time.sleep(CONNECTION_RETRY_DELAY) _connection = None else: print(f"[DB] CRITICAL: No se pudo conectar a MySQL después de {MAX_CONNECTION_RETRIES} intentos") raise try: _connection.ping(reconnect=True, attempts=3, delay=1) except MySQLError: for attempt in range(1, 6): try: print(f"[DB] Reconectando a MySQL (intento {attempt}/5)...") _connection = mysql.connector.connect( host=db_config.get("host"), port=db_config.get("port"), user=db_config.get("user"), password=db_config.get("password"), database=db_config.get("name"), consume_results=True, connection_timeout=10 ) print(f"[DB] MySQL reconectado exitosamente") break except MySQLError as e: if attempt < 5: time.sleep(2) _connection = None else: print(f"[DB] CRITICAL: MySQL reconnection failed: {e}") raise return _connection else: import os from botdiscord.config import get_db_path db_path = get_db_path() db_dir = os.path.dirname(db_path) if db_dir and not os.path.exists(db_dir): os.makedirs(db_dir, exist_ok=True) return sqlite3.connect(db_path) def close_connection(): global _connection if _connection and _connection.is_connected(): _connection.close() _connection = None def _execute_query(query, params=None, fetch=False): db_type = get_db_type() if db_type == "mysql": conn = get_connection() cursor = conn.cursor() try: cursor.execute(query, params or ()) if fetch: result = cursor.fetchall() else: conn.commit() result = cursor.lastrowid return result finally: cursor.close() else: conn = get_connection() cursor = conn.cursor() try: cursor.execute(query, params or ()) if fetch: result = cursor.fetchall() else: conn.commit() result = cursor.lastrowid return result finally: cursor.close() def init_db(): db_type = get_db_type() if db_type == "mysql": conn = get_connection() cursor = conn.cursor() cursor.execute('''CREATE TABLE IF NOT EXISTS guild_languages (guild_id BIGINT NOT NULL, lang_code VARCHAR(10) NOT NULL, PRIMARY KEY (guild_id, lang_code)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4''') cursor.execute('''CREATE TABLE IF NOT EXISTS bot_config (`key` VARCHAR(255) NOT NULL, value TEXT, PRIMARY KEY (`key`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4''') cursor.execute('''CREATE TABLE IF NOT EXISTS available_languages (code VARCHAR(10) NOT NULL, name VARCHAR(100) NOT NULL, flag VARCHAR(20), PRIMARY KEY (code)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4''') cursor.execute('''CREATE TABLE IF NOT EXISTS bot_languages (bot_type VARCHAR(50) NOT NULL, lang_code VARCHAR(10) NOT NULL, PRIMARY KEY (bot_type, lang_code)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4''') # Nuevas tablas para caché y persistencia cursor.execute('''CREATE TABLE IF NOT EXISTS messages (message_id BIGINT PRIMARY KEY, guild_id BIGINT, author_id BIGINT, content TEXT NOT NULL, mentions_map JSON, bot_type ENUM('discord', 'telegram') NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4''') cursor.execute('''CREATE TABLE IF NOT EXISTS translations (id INT AUTO_INCREMENT PRIMARY KEY, message_id BIGINT NOT NULL, target_lang VARCHAR(10) NOT NULL, translated_text TEXT NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (message_id) REFERENCES messages(message_id) ON DELETE CASCADE, UNIQUE KEY idx_msg_lang (message_id, target_lang)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4''') # Tabla para traducciones de la interfaz web cursor.execute('''CREATE TABLE IF NOT EXISTS ui_translations (id INT AUTO_INCREMENT PRIMARY KEY, text_hash VARCHAR(64) NOT NULL, original_text TEXT NOT NULL, target_lang VARCHAR(10) NOT NULL, translated_text TEXT NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY idx_ui_hash_lang (text_hash, target_lang)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4''') # Migración automática: Añadir text_hash si no existe try: cursor.execute("SHOW COLUMNS FROM ui_translations LIKE 'text_hash'") if not cursor.fetchone(): print("Migrating ui_translations: Adding text_hash column...") # Primero borramos el índice viejo si existe (basado en el código anterior) try: cursor.execute("ALTER TABLE ui_translations DROP INDEX idx_ui_lang") except: pass # Añadimos la columna y el nuevo índice cursor.execute("ALTER TABLE ui_translations ADD COLUMN text_hash VARCHAR(64) NOT NULL AFTER id") cursor.execute("ALTER TABLE ui_translations ADD UNIQUE KEY idx_ui_hash_lang (text_hash, target_lang)") # Limpiamos la tabla para evitar conflictos de hash con datos viejos cursor.execute("DELETE FROM ui_translations") except Exception as e: print(f"Migration warning: {e}") conn.commit() # Tabla para administradores del panel web cursor.execute('''CREATE TABLE IF NOT EXISTS admins (id INT AUTO_INCREMENT PRIMARY KEY, username VARCHAR(50) NOT NULL UNIQUE, password_hash VARCHAR(255) NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4''') cursor.execute('''CREATE TABLE IF NOT EXISTS welcome_messages (guild_id BIGINT PRIMARY KEY, channel_id BIGINT NOT NULL, message_content TEXT NOT NULL, enabled BOOLEAN DEFAULT TRUE, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4''') cursor.execute('''CREATE TABLE IF NOT EXISTS discord_servers (server_id BIGINT NOT NULL PRIMARY KEY, server_name VARCHAR(255) NOT NULL, added_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4''') cursor.execute('''CREATE TABLE IF NOT EXISTS discord_channels (channel_id BIGINT NOT NULL PRIMARY KEY, channel_name VARCHAR(255) NOT NULL, server_id BIGINT NOT NULL, is_active BOOLEAN DEFAULT TRUE, added_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (server_id) REFERENCES discord_servers(server_id) ON DELETE CASCADE) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4''') conn.commit() cursor.close() else: conn = get_connection() c = conn.cursor() c.execute('''CREATE TABLE IF NOT EXISTS guild_languages (guild_id INTEGER, lang_code TEXT, PRIMARY KEY (guild_id, lang_code))''') c.execute('''CREATE TABLE IF NOT EXISTS bot_config (key TEXT PRIMARY KEY, value TEXT)''') c.execute('''CREATE TABLE IF NOT EXISTS available_languages (code TEXT PRIMARY KEY, name TEXT, flag TEXT)''') c.execute('''CREATE TABLE IF NOT EXISTS bot_languages (bot_type TEXT, lang_code TEXT, PRIMARY KEY (bot_type, lang_code))''') # SQLite equivalents c.execute('''CREATE TABLE IF NOT EXISTS messages (message_id INTEGER PRIMARY KEY, guild_id INTEGER, author_id INTEGER, content TEXT NOT NULL, mentions_map TEXT, bot_type TEXT NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP)''') c.execute('''CREATE TABLE IF NOT EXISTS translations (id INTEGER PRIMARY KEY AUTOINCREMENT, message_id INTEGER NOT NULL, target_lang TEXT NOT NULL, translated_text TEXT NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, UNIQUE(message_id, target_lang))''') # SQLite equivalent for UI translations c.execute('''CREATE TABLE IF NOT EXISTS ui_translations (id INTEGER PRIMARY KEY AUTOINCREMENT, original_text TEXT NOT NULL, target_lang TEXT NOT NULL, translated_text TEXT NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, UNIQUE(original_text, target_lang))''') # SQLite equivalent for admins c.execute('''CREATE TABLE IF NOT EXISTS admins (id INTEGER PRIMARY KEY AUTOINCREMENT, username TEXT NOT NULL UNIQUE, password_hash TEXT NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP)''') c.execute('''CREATE TABLE IF NOT EXISTS welcome_messages (guild_id INTEGER PRIMARY KEY, channel_id INTEGER NOT NULL, message_content TEXT NOT NULL, enabled INTEGER DEFAULT 1)''') c.execute('''CREATE TABLE IF NOT EXISTS discord_servers (server_id INTEGER PRIMARY KEY, server_name TEXT NOT NULL, added_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP)''') c.execute('''CREATE TABLE IF NOT EXISTS discord_channels (channel_id INTEGER PRIMARY KEY, channel_name TEXT NOT NULL, server_id INTEGER NOT NULL, is_active INTEGER DEFAULT 1, added_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (server_id) REFERENCES discord_servers(server_id))''') conn.commit() conn.close() def set_available_languages(languages: list): db_type = get_db_type() if db_type == "mysql": conn = get_connection() cursor = conn.cursor() cursor.execute("DELETE FROM available_languages") for lang in languages: cursor.execute("INSERT INTO available_languages (code, name, flag) VALUES (%s, %s, %s)", (lang.get("code"), lang.get("name"), lang.get("flag", ""))) conn.commit() cursor.close() else: conn = get_connection() c = conn.cursor() c.execute("DELETE FROM available_languages") for lang in languages: c.execute("INSERT OR REPLACE INTO available_languages (code, name, flag) VALUES (?, ?, ?)", (lang.get("code"), lang.get("name"), lang.get("flag", ""))) conn.commit() conn.close() def get_available_languages() -> list: db_type = get_db_type() if db_type == "mysql": conn = get_connection() cursor = conn.cursor() cursor.execute("SELECT code, name, flag FROM available_languages ORDER BY name") rows = cursor.fetchall() cursor.close() return [{"code": row[0], "name": row[1], "flag": row[2] or ""} for row in rows] else: conn = get_connection() c = conn.cursor() c.execute("SELECT code, name, flag FROM available_languages ORDER BY name") langs = [{"code": row[0], "name": row[1], "flag": row[2] or ""} for row in c.fetchall()] conn.close() return langs def set_bot_languages(bot_type: str, lang_codes: list): db_type = get_db_type() if db_type == "mysql": conn = get_connection() cursor = conn.cursor() cursor.execute("DELETE FROM bot_languages WHERE bot_type = %s", (bot_type,)) for code in lang_codes: cursor.execute("INSERT INTO bot_languages (bot_type, lang_code) VALUES (%s, %s)", (bot_type, code)) conn.commit() cursor.close() else: conn = get_connection() c = conn.cursor() c.execute("DELETE FROM bot_languages WHERE bot_type = ?", (bot_type,)) for code in lang_codes: c.execute("INSERT INTO bot_languages (bot_type, lang_code) VALUES (?, ?)", (bot_type, code)) conn.commit() conn.close() def get_bot_languages(bot_type: str) -> list: db_type = get_db_type() if db_type == "mysql": conn = get_connection() cursor = conn.cursor() cursor.execute("SELECT lang_code FROM bot_languages WHERE bot_type = %s", (bot_type,)) rows = cursor.fetchall() cursor.close() return [row[0] for row in rows] else: conn = get_connection() c = conn.cursor() c.execute("SELECT lang_code FROM bot_languages WHERE bot_type = ?", (bot_type,)) langs = [row[0] for row in c.fetchall()] conn.close() return langs def get_active_languages(guild_id: int) -> list: db_type = get_db_type() if db_type == "mysql": conn = get_connection() cursor = conn.cursor() cursor.execute("SELECT lang_code FROM guild_languages WHERE guild_id = %s", (guild_id,)) rows = cursor.fetchall() cursor.close() return [row[0] for row in rows] else: conn = get_connection() c = conn.cursor() c.execute("SELECT lang_code FROM guild_languages WHERE guild_id = ?", (guild_id,)) langs = [row[0] for row in c.fetchall()] conn.close() return langs def set_active_languages(guild_id: int, lang_codes: list): db_type = get_db_type() if db_type == "mysql": conn = get_connection() cursor = conn.cursor() cursor.execute("DELETE FROM guild_languages WHERE guild_id = %s", (guild_id,)) for code in lang_codes: cursor.execute("INSERT INTO guild_languages (guild_id, lang_code) VALUES (%s, %s)", (guild_id, code)) conn.commit() cursor.close() else: conn = get_connection() c = conn.cursor() c.execute("DELETE FROM guild_languages WHERE guild_id = ?", (guild_id,)) for code in lang_codes: c.execute("INSERT INTO guild_languages (guild_id, lang_code) VALUES (?, ?)", (guild_id, code)) conn.commit() conn.close() def get_config_value(key: str) -> str: db_type = get_db_type() if db_type == "mysql": conn = get_connection() cursor = conn.cursor() cursor.execute("SELECT value FROM bot_config WHERE `key` = %s", (key,)) row = cursor.fetchone() cursor.close() return row[0] if row else None else: conn = get_connection() c = conn.cursor() c.execute("SELECT value FROM bot_config WHERE key = ?", (key,)) row = c.fetchone() conn.close() return row[0] if row else None def set_config_value(key: str, value: str): db_type = get_db_type() if db_type == "mysql": conn = get_connection() cursor = conn.cursor() cursor.execute("INSERT INTO bot_config (`key`, value) VALUES (%s, %s) ON DUPLICATE KEY UPDATE value = %s", (key, value, value)) conn.commit() cursor.close() else: conn = get_connection() c = conn.cursor() c.execute("INSERT OR REPLACE INTO bot_config (key, value) VALUES (?, ?)", (key, value)) conn.commit() conn.close() def save_message(message_id: int, guild_id: int, author_id: int, content: str, mentions_map: dict, bot_type: str): db_type = get_db_type() mentions_json = json.dumps(mentions_map) if db_type == "mysql": conn = get_connection() cursor = conn.cursor() query = """INSERT INTO messages (message_id, guild_id, author_id, content, mentions_map, bot_type) VALUES (%s, %s, %s, %s, %s, %s) ON DUPLICATE KEY UPDATE content = %s, mentions_map = %s""" cursor.execute(query, (message_id, guild_id, author_id, content, mentions_json, bot_type, content, mentions_json)) conn.commit() cursor.close() else: conn = get_connection() c = conn.cursor() c.execute("INSERT OR REPLACE INTO messages (message_id, guild_id, author_id, content, mentions_map, bot_type) VALUES (?, ?, ?, ?, ?, ?)", (message_id, guild_id, author_id, content, mentions_json, bot_type)) conn.commit() conn.close() def get_message(message_id: int): db_type = get_db_type() if db_type == "mysql": conn = get_connection() cursor = conn.cursor(dictionary=True) cursor.execute("SELECT content, mentions_map, bot_type FROM messages WHERE message_id = %s", (message_id,)) row = cursor.fetchone() cursor.close() if row: if row['mentions_map']: row['mentions_map'] = json.loads(row['mentions_map']) return row else: conn = get_connection() c = conn.cursor() c.execute("SELECT content, mentions_map, bot_type FROM messages WHERE message_id = ?", (message_id,)) row = c.fetchone() conn.close() if row: return { 'content': row[0], 'mentions_map': json.loads(row[1]) if row[1] else {}, 'bot_type': row[2] } return None def save_translation(message_id: int, target_lang: str, translated_text: str): db_type = get_db_type() if db_type == "mysql": conn = get_connection() cursor = conn.cursor() query = "INSERT INTO translations (message_id, target_lang, translated_text) VALUES (%s, %s, %s) ON DUPLICATE KEY UPDATE translated_text = %s" cursor.execute(query, (message_id, target_lang, translated_text, translated_text)) conn.commit() cursor.close() else: conn = get_connection() c = conn.cursor() c.execute("INSERT OR REPLACE INTO translations (message_id, target_lang, translated_text) VALUES (?, ?, ?)", (message_id, target_lang, translated_text)) conn.commit() conn.close() def get_cached_translation(message_id: int, target_lang: str) -> str: db_type = get_db_type() if db_type == "mysql": conn = get_connection() cursor = conn.cursor() cursor.execute("SELECT translated_text FROM translations WHERE message_id = %s AND target_lang = %s", (message_id, target_lang)) row = cursor.fetchone() cursor.close() return row[0] if row else None else: conn = get_connection() c = conn.cursor() c.execute("SELECT translated_text FROM translations WHERE message_id = ? AND target_lang = ?", (message_id, target_lang)) row = c.fetchone() conn.close() return row[0] if row else None import hashlib def _normalize_text(text: str) -> str: """Normaliza el texto para que el caché sea consistente.""" if not text: return "" # Quitar espacios en extremos, normalizar saltos de línea y quitar espacios dobles text = text.strip().replace('\r\n', '\n') return " ".join(text.split()) def _get_text_hash(text: str) -> str: """Genera un hash único para el texto normalizado.""" norm = _normalize_text(text) return hashlib.sha256(norm.encode('utf-8')).hexdigest() def get_ui_translation(text: str, target_lang: str) -> str: db_type = get_db_type() text_hash = _get_text_hash(text) try: if db_type == "mysql": conn = get_connection() cursor = conn.cursor() cursor.execute("SELECT translated_text FROM ui_translations WHERE text_hash = %s AND target_lang = %s", (text_hash, target_lang)) row = cursor.fetchone() cursor.close() return row[0] if row else None else: conn = get_connection() c = conn.cursor() c.execute("SELECT translated_text FROM ui_translations WHERE text_hash = ? AND target_lang = ?", (text_hash, target_lang)) row = c.fetchone() conn.close() return row[0] if row else None except Exception as e: print(f"Database error in get_ui_translation: {e}") return None def save_ui_translation(text: str, target_lang: str, translated_text: str): db_type = get_db_type() text_hash = _get_text_hash(text) try: if db_type == "mysql": conn = get_connection() cursor = conn.cursor() query = """INSERT INTO ui_translations (text_hash, original_text, target_lang, translated_text) VALUES (%s, %s, %s, %s) ON DUPLICATE KEY UPDATE translated_text = %s""" cursor.execute(query, (text_hash, text, target_lang, translated_text, translated_text)) conn.commit() cursor.close() else: conn = get_connection() c = conn.cursor() c.execute("INSERT OR REPLACE INTO ui_translations (text_hash, original_text, target_lang, translated_text) VALUES (?, ?, ?, ?)", (text_hash, text, target_lang, translated_text)) conn.commit() conn.close() except Exception as e: print(f"Database error in save_ui_translation: {e}") # Funciones para administradores def get_admins(): db_type = get_db_type() if db_type == "mysql": conn = get_connection() cursor = conn.cursor(dictionary=True) cursor.execute("SELECT id, username, created_at FROM admins ORDER BY username") rows = cursor.fetchall() cursor.close() return rows else: conn = get_connection() c = conn.cursor() c.execute("SELECT id, username, created_at FROM admins ORDER BY username") rows = [{"id": r[0], "username": r[1], "created_at": r[2]} for r in c.fetchall()] conn.close() return rows def get_admin_by_username(username: str): db_type = get_db_type() if db_type == "mysql": conn = get_connection() cursor = conn.cursor(dictionary=True) cursor.execute("SELECT * FROM admins WHERE username = %s", (username,)) row = cursor.fetchone() cursor.close() return row else: conn = get_connection() c = conn.cursor() c.execute("SELECT id, username, password_hash, created_at FROM admins WHERE username = ?", (username,)) row = c.fetchone() conn.close() if row: return {"id": row[0], "username": row[1], "password_hash": row[2], "created_at": row[3]} return None def add_admin(username: str, password_hash: str): db_type = get_db_type() if db_type == "mysql": conn = get_connection() cursor = conn.cursor() cursor.execute("INSERT INTO admins (username, password_hash) VALUES (%s, %s)", (username, password_hash)) conn.commit() cursor.close() else: conn = get_connection() c = conn.cursor() c.execute("INSERT INTO admins (username, password_hash) VALUES (?, ?)", (username, password_hash)) conn.commit() conn.close() def delete_admin(admin_id: int): db_type = get_db_type() if db_type == "mysql": conn = get_connection() cursor = conn.cursor() cursor.execute("DELETE FROM admins WHERE id = %s", (admin_id,)) conn.commit() cursor.close() else: conn = get_connection() c = conn.cursor() c.execute("DELETE FROM admins WHERE id = ?", (admin_id,)) conn.commit() conn.close() def update_admin_password(admin_id: int, password_hash: str): db_type = get_db_type() if db_type == "mysql": conn = get_connection() cursor = conn.cursor() cursor.execute("UPDATE admins SET password_hash = %s WHERE id = %s", (password_hash, admin_id)) conn.commit() cursor.close() else: conn = get_connection() c = conn.cursor() c.execute("UPDATE admins SET password_hash = ? WHERE id = ?", (password_hash, admin_id)) conn.commit() conn.close() def save_welcome_message(guild_id: int, channel_id: int, message_content: str, enabled: bool = True): db_type = get_db_type() if db_type == "mysql": conn = get_connection() cursor = conn.cursor() query = """INSERT INTO welcome_messages (guild_id, channel_id, message_content, enabled) VALUES (%s, %s, %s, %s) ON DUPLICATE KEY UPDATE channel_id = %s, message_content = %s, enabled = %s""" cursor.execute(query, (guild_id, channel_id, message_content, enabled, channel_id, message_content, enabled)) conn.commit() cursor.close() else: conn = get_connection() c = conn.cursor() c.execute("""INSERT OR REPLACE INTO welcome_messages (guild_id, channel_id, message_content, enabled) VALUES (?, ?, ?, ?)""", (guild_id, channel_id, message_content, 1 if enabled else 0)) conn.commit() conn.close() def get_welcome_message(guild_id: int): db_type = get_db_type() if db_type == "mysql": conn = get_connection() cursor = conn.cursor(dictionary=True) cursor.execute("SELECT channel_id, message_content, enabled FROM welcome_messages WHERE guild_id = %s", (guild_id,)) row = cursor.fetchone() cursor.close() if row: return { 'channel_id': int(row['channel_id']) if row['channel_id'] else 0, 'message_content': row['message_content'] or '', 'enabled': bool(row['enabled']) } return None else: conn = get_connection() c = conn.cursor() c.execute("SELECT channel_id, message_content, enabled FROM welcome_messages WHERE guild_id = ?", (guild_id,)) row = c.fetchone() conn.close() if row: return { "channel_id": int(row[0]) if row[0] else 0, "message_content": row[1] or '', "enabled": bool(row[2]) } return None def get_all_welcome_configs(): db_type = get_db_type() if db_type == "mysql": conn = get_connection() cursor = conn.cursor(dictionary=True) cursor.execute("SELECT guild_id, channel_id, message_content, enabled FROM welcome_messages") rows = cursor.fetchall() cursor.close() result = [] for row in rows: result.append({ 'guild_id': int(row['guild_id']) if row['guild_id'] else 0, 'channel_id': int(row['channel_id']) if row['channel_id'] else 0, 'message_content': row['message_content'] or '', 'enabled': bool(row['enabled']) }) return result else: conn = get_connection() c = conn.cursor() c.execute("SELECT guild_id, channel_id, message_content, enabled FROM welcome_messages") rows = [{"guild_id": r[0], "channel_id": r[1], "message_content": r[2], "enabled": bool(r[3])} for r in c.fetchall()] conn.close() return rows def delete_welcome_message(guild_id: int): db_type = get_db_type() if db_type == "mysql": conn = get_connection() cursor = conn.cursor() cursor.execute("DELETE FROM welcome_messages WHERE guild_id = %s", (guild_id,)) conn.commit() cursor.close() else: conn = get_connection() c = conn.cursor() c.execute("DELETE FROM welcome_messages WHERE guild_id = ?", (guild_id,)) conn.commit() conn.close() # Funciones para gestión de servidores y canales de Discord def sync_discord_servers(servers: list): db_type = get_db_type() if db_type == "mysql": conn = get_connection() cursor = conn.cursor() for server in servers: server_id = server.get('id') server_name = server.get('name', '') cursor.execute("""INSERT INTO discord_servers (server_id, server_name) VALUES (%s, %s) ON DUPLICATE KEY UPDATE server_name = %s""", (server_id, server_name, server_name)) conn.commit() cursor.close() else: conn = get_connection() c = conn.cursor() for server in servers: server_id = server.get('id') server_name = server.get('name', '') c.execute("""INSERT OR REPLACE INTO discord_servers (server_id, server_name) VALUES (?, ?)""", (server_id, server_name)) conn.commit() conn.close() def get_discord_servers() -> list: db_type = get_db_type() if db_type == "mysql": conn = get_connection() cursor = conn.cursor(dictionary=True) cursor.execute("SELECT server_id, server_name, added_at FROM discord_servers ORDER BY server_name") rows = cursor.fetchall() cursor.close() return rows else: conn = get_connection() c = conn.cursor() c.execute("SELECT server_id, server_name, added_at FROM discord_servers ORDER BY server_name") rows = [{"server_id": r[0], "server_name": r[1], "added_at": r[2]} for r in c.fetchall()] conn.close() return rows def sync_discord_channels(server_id: int, channels: list): db_type = get_db_type() try: if db_type == "mysql": conn = get_connection() cursor = conn.cursor() for channel in channels: channel_id = channel.get('id') channel_name = channel.get('name', '') cursor.execute("""INSERT INTO discord_channels (channel_id, channel_name, server_id) VALUES (%s, %s, %s) ON DUPLICATE KEY UPDATE channel_name = %s, server_id = %s""", (channel_id, channel_name, server_id, channel_name, server_id)) conn.commit() cursor.close() else: conn = get_connection() c = conn.cursor() for channel in channels: channel_id = channel.get('id') channel_name = channel.get('name', '') c.execute("""INSERT OR REPLACE INTO discord_channels (channel_id, channel_name, server_id) VALUES (?, ?, ?)""", (channel_id, channel_name, server_id)) conn.commit() conn.close() # Limpiar todo el cache de canales después de sincronizar clear_channel_cache() print(f"[DB] Canales sincronizados para servidor {server_id}, cache limpiado") except Exception as e: print(f"[DB] Error sincronizando canales del servidor {server_id}: {e}") raise def get_discord_channels(server_id: int = None) -> list: db_type = get_db_type() if db_type == "mysql": conn = get_connection() cursor = conn.cursor(dictionary=True) if server_id: cursor.execute("SELECT channel_id, channel_name, server_id, is_active, added_at FROM discord_channels WHERE server_id = %s ORDER BY channel_name", (server_id,)) else: cursor.execute("SELECT channel_id, channel_name, server_id, is_active, added_at FROM discord_channels ORDER BY server_id, channel_name") rows = cursor.fetchall() cursor.close() return rows else: conn = get_connection() c = conn.cursor() if server_id: c.execute("SELECT channel_id, channel_name, server_id, is_active, added_at FROM discord_channels WHERE server_id = ? ORDER BY channel_name", (server_id,)) else: c.execute("SELECT channel_id, channel_name, server_id, is_active, added_at FROM discord_channels ORDER BY server_id, channel_name") rows = [{"channel_id": r[0], "channel_name": r[1], "server_id": r[2], "is_active": bool(r[3]), "added_at": r[4]} for r in c.fetchall()] conn.close() return rows def toggle_channel_status(channel_id: int, is_active: bool): db_type = get_db_type() try: if db_type == "mysql": conn = get_connection() cursor = conn.cursor() cursor.execute("UPDATE discord_channels SET is_active = %s WHERE channel_id = %s", (1 if is_active else 0, channel_id)) conn.commit() cursor.close() else: conn = get_connection() c = conn.cursor() c.execute("UPDATE discord_channels SET is_active = ? WHERE channel_id = ?", (1 if is_active else 0, channel_id)) conn.commit() conn.close() # Limpiar el cache para forzar re-lectura clear_channel_cache(channel_id) print(f"[DB] Canal {channel_id} actualizado a {is_active}, cache limpiado") except Exception as e: print(f"[DB] Error actualizando canal {channel_id}: {e}") raise # Cache simple para estados de canales (evita consultas excesivas) _channel_status_cache = {} _cache_timeout = 5 # segundos _last_cache_update = {} def is_channel_enabled(channel_id: int) -> bool: global _channel_status_cache, _last_cache_update db_type = get_db_type() current_time = time.time() # Verificar si tenemos un cache válido if (channel_id in _channel_status_cache and channel_id in _last_cache_update and current_time - _last_cache_update[channel_id] < _cache_timeout): result = _channel_status_cache[channel_id] print(f"[DB Cache] Canal {channel_id} estado (cache): {result}") return result try: if db_type == "mysql": conn = get_connection() cursor = conn.cursor() cursor.execute("SELECT is_active FROM discord_channels WHERE channel_id = %s", (channel_id,)) row = cursor.fetchone() cursor.close() print(f"[DB] MySQL Query: SELECT is_active FROM discord_channels WHERE channel_id = {channel_id}") print(f"[DB] MySQL Row: {row}") result = bool(row[0]) if row else False else: conn = get_connection() c = conn.cursor() c.execute("SELECT is_active FROM discord_channels WHERE channel_id = ?", (channel_id,)) row = c.fetchone() conn.close() print(f"[DB] SQLite Query: SELECT is_active FROM discord_channels WHERE channel_id = {channel_id}") print(f"[DB] SQLite Row: {row}") result = bool(row[0]) if row else False # Actualizar cache _channel_status_cache[channel_id] = result _last_cache_update[channel_id] = current_time print(f"[DB] Canal {channel_id} estado (DB): {result} - Timestamp: {time.strftime('%H:%M:%S')}") return result except Exception as e: print(f"[DB] Error verificando canal {channel_id}: {e}") # En caso de error, devolver False por seguridad return False def clear_channel_cache(channel_id: int = None): """Limpia el cache de un canal específico o de todos""" global _channel_status_cache, _last_cache_update if channel_id: _channel_status_cache.pop(channel_id, None) _last_cache_update.pop(channel_id, None) print(f"[DB] Cache limpiado para canal {channel_id}") # Forzar expiración inmediata poniendo un timestamp antiguo _last_cache_update[channel_id] = 0 else: _channel_status_cache.clear() _last_cache_update.clear() print(f"[DB] Todo el cache de canales limpiado") def delete_server_channels(server_id: int): """Elimina todos los canales de un servidor específico""" db_type = get_db_type() if db_type == "mysql": conn = get_connection() cursor = conn.cursor() cursor.execute("DELETE FROM discord_channels WHERE server_id = %s", (server_id,)) conn.commit() cursor.close() else: conn = get_connection() c = conn.cursor() c.execute("DELETE FROM discord_channels WHERE server_id = ?", (server_id,)) conn.commit() conn.close() def delete_discord_server(server_id: int): """Elimina un servidor y todos sus canales""" db_type = get_db_type() if db_type == "mysql": conn = get_connection() cursor = conn.cursor() cursor.execute("DELETE FROM discord_servers WHERE server_id = %s", (server_id,)) conn.commit() cursor.close() else: conn = get_connection() c = conn.cursor() c.execute("DELETE FROM discord_servers WHERE server_id = ?", (server_id,)) conn.commit() conn.close()