feat: implementar caché de traducciones y persistencia de mensajes en MySQL para Discord y Telegram
This commit is contained in:
@@ -8,7 +8,7 @@ from discord import app_commands
|
|||||||
import re
|
import re
|
||||||
|
|
||||||
from botdiscord.config import get_discord_token, load_config, get_languages
|
from botdiscord.config import get_discord_token, load_config, get_languages
|
||||||
from botdiscord.database import init_db, get_active_languages, get_bot_languages
|
from botdiscord.database import init_db, get_active_languages, get_bot_languages, save_message
|
||||||
from botdiscord.ui import TranslationView, ConfigView
|
from botdiscord.ui import TranslationView, ConfigView
|
||||||
from botdiscord.translate import get_reverse_mapping, load_lang_mappings
|
from botdiscord.translate import get_reverse_mapping, load_lang_mappings
|
||||||
|
|
||||||
@@ -93,6 +93,9 @@ async def on_message(message):
|
|||||||
|
|
||||||
text_to_translate = mention_pattern.sub(replace_mention, text_escaped)
|
text_to_translate = mention_pattern.sub(replace_mention, text_escaped)
|
||||||
|
|
||||||
|
# Guardamos el mensaje en la base de datos para persistencia y caché
|
||||||
|
save_message(message.id, message.guild.id, message.author.id, text_to_translate, mentions_map, 'discord')
|
||||||
|
|
||||||
langs_to_show = active_langs
|
langs_to_show = active_langs
|
||||||
|
|
||||||
if langs_to_show:
|
if langs_to_show:
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import sqlite3
|
import sqlite3
|
||||||
import mysql.connector
|
import mysql.connector
|
||||||
|
import json
|
||||||
from mysql.connector import Error as MySQLError
|
from mysql.connector import Error as MySQLError
|
||||||
from botdiscord.config import get_db_config, get_db_type
|
from botdiscord.config import get_db_config, get_db_type
|
||||||
|
|
||||||
@@ -87,6 +88,26 @@ def init_db():
|
|||||||
cursor.execute('''CREATE TABLE IF NOT EXISTS bot_languages
|
cursor.execute('''CREATE TABLE IF NOT EXISTS bot_languages
|
||||||
(bot_type VARCHAR(50) NOT NULL, lang_code VARCHAR(10) NOT NULL,
|
(bot_type VARCHAR(50) NOT NULL, lang_code VARCHAR(10) NOT NULL,
|
||||||
PRIMARY KEY (bot_type, lang_code)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4''')
|
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''')
|
||||||
|
|
||||||
conn.commit()
|
conn.commit()
|
||||||
cursor.close()
|
cursor.close()
|
||||||
else:
|
else:
|
||||||
@@ -100,6 +121,24 @@ def init_db():
|
|||||||
(code TEXT PRIMARY KEY, name TEXT, flag TEXT)''')
|
(code TEXT PRIMARY KEY, name TEXT, flag TEXT)''')
|
||||||
c.execute('''CREATE TABLE IF NOT EXISTS bot_languages
|
c.execute('''CREATE TABLE IF NOT EXISTS bot_languages
|
||||||
(bot_type TEXT, lang_code TEXT, PRIMARY KEY (bot_type, lang_code))''')
|
(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))''')
|
||||||
conn.commit()
|
conn.commit()
|
||||||
conn.close()
|
conn.close()
|
||||||
|
|
||||||
@@ -256,3 +295,87 @@ def set_config_value(key: str, value: str):
|
|||||||
c.execute("INSERT OR REPLACE INTO bot_config (key, value) VALUES (?, ?)", (key, value))
|
c.execute("INSERT OR REPLACE INTO bot_config (key, value) VALUES (?, ?)", (key, value))
|
||||||
conn.commit()
|
conn.commit()
|
||||||
conn.close()
|
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
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import discord
|
import discord
|
||||||
from botdiscord.translate import get_lang_mapping, get_flag_mapping, get_name_to_code, translate_text
|
from botdiscord.translate import get_lang_mapping, get_flag_mapping, get_name_to_code, translate_text
|
||||||
|
from botdiscord.database import get_message, save_translation, get_cached_translation
|
||||||
|
|
||||||
class TranslationView(discord.ui.View):
|
class TranslationView(discord.ui.View):
|
||||||
def __init__(self, text: str, languages: list, original_message, attachments=None, mentions_map=None):
|
def __init__(self, text: str, languages: list, original_message, attachments=None, mentions_map=None):
|
||||||
@@ -31,8 +32,26 @@ class TranslationButton(discord.ui.Button):
|
|||||||
self.mentions_map = mentions_map or {}
|
self.mentions_map = mentions_map or {}
|
||||||
|
|
||||||
async def callback(self, interaction: discord.Interaction):
|
async def callback(self, interaction: discord.Interaction):
|
||||||
|
# Intentamos recuperar el mensaje de la base de datos si falta contexto
|
||||||
|
msg_context = None
|
||||||
|
if not self.text or not self.mentions_map:
|
||||||
|
# Recuperamos de la BD usando el ID del mensaje al que se responde
|
||||||
|
# En Discord, si es un reply, el original_message es el mensaje original
|
||||||
|
msg_id = self.original_message.id
|
||||||
|
db_msg = get_message(msg_id)
|
||||||
|
if db_msg:
|
||||||
|
self.text = db_msg['content']
|
||||||
|
self.mentions_map = db_msg['mentions_map']
|
||||||
|
|
||||||
|
# Verificamos si ya tenemos la traducción en caché
|
||||||
|
cached = get_cached_translation(self.original_message.id, self.lang_code)
|
||||||
|
if cached:
|
||||||
|
translated = cached
|
||||||
|
else:
|
||||||
# Traducimos el texto
|
# Traducimos el texto
|
||||||
translated = await translate_text(self.text, self.lang_code)
|
translated = await translate_text(self.text, self.lang_code)
|
||||||
|
# Guardamos en caché
|
||||||
|
save_translation(self.original_message.id, self.lang_code, translated)
|
||||||
|
|
||||||
# Desescapamos el HTML para recuperar caracteres especiales
|
# Desescapamos el HTML para recuperar caracteres especiales
|
||||||
import html
|
import html
|
||||||
|
|||||||
@@ -10,7 +10,10 @@ from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup
|
|||||||
from telegram.ext import Application, CommandHandler, MessageHandler, CallbackQueryHandler, ContextTypes, filters
|
from telegram.ext import Application, CommandHandler, MessageHandler, CallbackQueryHandler, ContextTypes, filters
|
||||||
|
|
||||||
from botdiscord.config import load_config, get_telegram_token, get_libretranslate_url
|
from botdiscord.config import load_config, get_telegram_token, get_libretranslate_url
|
||||||
from botdiscord.database import init_db as init_shared_db
|
from botdiscord.database import (
|
||||||
|
init_db as init_shared_db,
|
||||||
|
save_message, get_message, save_translation, get_cached_translation
|
||||||
|
)
|
||||||
from botdiscord.translate import load_lang_mappings, get_lang_mapping, get_flag_mapping, get_name_to_code
|
from botdiscord.translate import load_lang_mappings, get_lang_mapping, get_flag_mapping, get_name_to_code
|
||||||
|
|
||||||
load_config()
|
load_config()
|
||||||
@@ -132,9 +135,8 @@ async def handle_message(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
|||||||
reply_markup=reply_markup
|
reply_markup=reply_markup
|
||||||
)
|
)
|
||||||
|
|
||||||
with lock:
|
# Guardamos en MySQL en lugar del JSON local
|
||||||
cleanup_pending_translations()
|
save_message(sent_message.message_id, update.effective_chat.id, update.effective_user.id, text, {}, 'telegram')
|
||||||
pending_translations[str(sent_message.message_id)] = text
|
|
||||||
|
|
||||||
async def handle_photo(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
async def handle_photo(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||||
caption = update.message.caption
|
caption = update.message.caption
|
||||||
@@ -147,23 +149,40 @@ async def handle_photo(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
|||||||
parse_mode="Markdown",
|
parse_mode="Markdown",
|
||||||
reply_markup=reply_markup
|
reply_markup=reply_markup
|
||||||
)
|
)
|
||||||
with lock:
|
# Guardamos en MySQL
|
||||||
cleanup_pending_translations()
|
save_message(sent_message.message_id, update.effective_chat.id, update.effective_user.id, caption, {}, 'telegram')
|
||||||
pending_translations[str(sent_message.message_id)] = caption
|
|
||||||
|
|
||||||
async def translation_callback(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
async def translation_callback(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||||
query = update.callback_query
|
query = update.callback_query
|
||||||
await query.answer()
|
await query.answer()
|
||||||
|
|
||||||
lang_code = query.data.replace("trans_", "")
|
lang_code = query.data.replace("trans_", "")
|
||||||
message_id = str(query.message.message_id)
|
message_id = query.message.message_id
|
||||||
|
|
||||||
text = pending_translations.get(message_id)
|
# Intentamos obtener del caché de traducciones primero
|
||||||
if not text:
|
cached = get_cached_translation(message_id, lang_code)
|
||||||
await query.message.reply_text("⚠️ No se encontró el texto original. Es posible que el bot se haya reiniciado recientemente.")
|
|
||||||
|
# Recuperamos el mensaje original de MySQL
|
||||||
|
db_msg = get_message(message_id)
|
||||||
|
|
||||||
|
if not db_msg:
|
||||||
|
await query.message.reply_text("⚠️ No se encontró el texto original en la base de datos.")
|
||||||
return
|
return
|
||||||
|
|
||||||
translated = await translate_text_telegram(text, lang_code)
|
text = db_msg['content']
|
||||||
|
|
||||||
|
if cached:
|
||||||
|
translated = cached
|
||||||
|
else:
|
||||||
|
# Traducimos usando la lógica compartida
|
||||||
|
from botdiscord.translate import translate_text
|
||||||
|
translated = await translate_text(text, lang_code)
|
||||||
|
# Guardamos en caché MySQL
|
||||||
|
save_translation(message_id, lang_code, translated)
|
||||||
|
|
||||||
|
# Desescapamos el HTML para Telegram
|
||||||
|
import html
|
||||||
|
translated = html.unescape(translated)
|
||||||
|
|
||||||
lang_mapping = get_lang_mapping("telegram")
|
lang_mapping = get_lang_mapping("telegram")
|
||||||
flag_mapping = get_flag_mapping("telegram")
|
flag_mapping = get_flag_mapping("telegram")
|
||||||
|
|||||||
Reference in New Issue
Block a user