diff --git a/botdiscord/bot.py b/botdiscord/bot.py index 70fee3f..4b0737e 100644 --- a/botdiscord/bot.py +++ b/botdiscord/bot.py @@ -22,6 +22,17 @@ bot = commands.Bot(command_prefix="!", intents=intents) async def on_ready(): init_db() load_lang_mappings("discord") + + # Registramos la vista persistente para que los botones funcionen tras reinicios + # Primero obtenemos todos los idiomas habilitados globalmente para crear la vista base + try: + from botdiscord.translate import get_lang_mapping + lang_mapping = get_lang_mapping("discord") + all_langs = list(lang_mapping.keys()) + bot.add_view(TranslationView(all_langs)) + except Exception as e: + print(f"Error registrando vista persistente: {e}") + print(f"Bot Discord conectado como {bot.user}") try: synced = await bot.tree.sync() @@ -100,7 +111,7 @@ async def on_message(message): if langs_to_show: print(f"[BOT DEBUG] Creating TranslationView with langs: {langs_to_show}") - view = TranslationView(text_to_translate, langs_to_show, message, message.attachments, mentions_map) + view = TranslationView(langs_to_show) print(f"[BOT DEBUG] View created, sending message...") try: await message.reply("¿Traducir este mensaje?", view=view, mention_author=False) diff --git a/botdiscord/ui.py b/botdiscord/ui.py index 436bd1d..e39f28c 100644 --- a/botdiscord/ui.py +++ b/botdiscord/ui.py @@ -3,90 +3,69 @@ from botdiscord.translate import get_lang_mapping, get_flag_mapping, get_name_to from botdiscord.database import get_message, save_translation, get_cached_translation class TranslationView(discord.ui.View): - def __init__(self, text: str, languages: list, original_message, attachments=None, mentions_map=None): + def __init__(self, languages: list = None): super().__init__(timeout=None) - self.text = text - self.original_message = original_message - self.attachments = attachments or [] - self.mentions_map = mentions_map or {} - flag_mapping = get_flag_mapping() - name_to_code = get_name_to_code() - - for lang in languages: - lang_code = name_to_code.get(lang) - flag = flag_mapping.get(lang_code, "") if lang_code else "" - self.add_item(TranslationButton(lang, lang_code, flag, text, original_message, self.attachments, self.mentions_map)) + if languages: + flag_mapping = get_flag_mapping() + name_to_code = get_name_to_code() + + for lang in languages: + lang_code = name_to_code.get(lang) + flag = flag_mapping.get(lang_code, "") if lang_code else "" + # El custom_id es vital para la persistencia + custom_id = f"btn_trans_{lang_code}" + self.add_item(TranslationButton(lang, lang_code, flag, custom_id)) class TranslationButton(discord.ui.Button): - def __init__(self, lang_name: str, lang_code: str, flag: str, text: str, original_message, attachments=None, mentions_map=None): - # Ahora el label es solo la bandera, si no hay bandera mostramos el nombre + def __init__(self, lang_name: str, lang_code: str, flag: str, custom_id: str): label = flag if flag else lang_name - super().__init__(label=label, style=discord.ButtonStyle.primary) - self.lang_name = lang_name + super().__init__(label=label, style=discord.ButtonStyle.primary, custom_id=custom_id) self.lang_code = lang_code - self.flag = flag - self.text = text - self.original_message = original_message - self.attachments = attachments or [] - self.mentions_map = mentions_map or {} 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'] + # Para botones persistentes, recuperamos la info del mensaje original + # En el caso de reply, interaction.message.reference.message_id es el original + if not interaction.message.reference: + await interaction.response.send_message("⚠️ No se pudo encontrar el mensaje original.", ephemeral=True) + return + + original_msg_id = interaction.message.reference.message_id + db_msg = get_message(original_msg_id) - # Verificamos si ya tenemos la traducción en caché - cached = get_cached_translation(self.original_message.id, self.lang_code) + if not db_msg: + await interaction.response.send_message("⚠️ El mensaje original no está en la base de datos (pudo ser borrado o es muy antiguo).", ephemeral=True) + return + + text = db_msg['content'] + mentions_map = db_msg['mentions_map'] + + # Verificamos caché + cached = get_cached_translation(original_msg_id, self.lang_code) if cached: translated = cached else: - # Traducimos el texto - translated = await translate_text(self.text, self.lang_code) - # Guardamos en caché - save_translation(self.original_message.id, self.lang_code, translated) + # Traducimos + translated = await translate_text(text, self.lang_code) + save_translation(original_msg_id, self.lang_code, translated) - # Desescapamos el HTML para recuperar caracteres especiales + # Procesar traducción (HTML unescape y menciones) import html import re translated = html.unescape(translated) - # Reemplazamos menciones si existen - for placeholder, mention in self.mentions_map.items(): - # Extraer el número del tag, ej: m0 de + for placeholder, mention in mentions_map.items(): match = re.search(r'm\d+', placeholder) if not match: continue tag_num = match.group() - - # 1. Reemplazamos la etiqueta de apertura (o autocontenida) por la mención - # Patrón: < \s* mX \s* /? \s* > open_pattern = re.compile(rf'<\s*{tag_num}\s*/?\s*>') translated = open_pattern.sub(mention, translated) - - # 2. Eliminamos cualquier etiqueta de cierre que el traductor haya "inventado" - # Patrón: < \s* / \s* mX \s* > close_pattern = re.compile(rf'<\s*/\s*{tag_num}\s*>') translated = close_pattern.sub('', translated) - # En lugar de enviar un mensaje nuevo, EDITAMOS el mensaje actual (el de los botones) - if self.attachments: - # Si hay archivos, los adjuntamos de nuevo al editar - files = [] - for attachment in self.attachments: - file = await attachment.to_file() - files.append(file) - - await interaction.response.edit_message(content=translated, attachments=files, view=self.view) - else: - # Editamos el texto "¿Traducir este mensaje?" por la traducción directamente - await interaction.response.edit_message(content=translated, view=self.view) + # Si el mensaje original tenía archivos, intentamos mantener la coherencia + # Aunque al ser persistente, simplemente editamos el mensaje de la respuesta + await interaction.response.edit_message(content=translated, view=self.view) class ConfigSelect(discord.ui.Select): def __init__(self, guild_id: int, bot_type: str = "discord"):