diff --git a/botdiscord/bot.py b/botdiscord/bot.py index 02e2539..0a2c3d4 100644 --- a/botdiscord/bot.py +++ b/botdiscord/bot.py @@ -9,14 +9,15 @@ import re import html from botdiscord.config import get_discord_token, load_config, get_languages -from botdiscord.database import init_db, get_active_languages, get_bot_languages, save_message -from botdiscord.ui import PersistentTranslationView, ConfigView +from botdiscord.database import init_db, get_active_languages, get_bot_languages, save_message, get_welcome_message +from botdiscord.ui import PersistentTranslationView, ConfigView, WelcomeTranslationView from botdiscord.translate import get_reverse_mapping, load_lang_mappings load_config() intents = discord.Intents.default() intents.message_content = True +intents.members = True bot = commands.Bot(command_prefix="!", intents=intents) @bot.event @@ -24,8 +25,6 @@ async def on_ready(): init_db() load_lang_mappings("discord") - # Registramos la vista persistente GLOBALMENTE - # Esto asegura que cualquier botón con el custom_id adecuado sea escuchado bot.add_view(PersistentTranslationView()) print(f"Bot Discord conectado como {bot.user}") @@ -35,6 +34,70 @@ async def on_ready(): except Exception as e: print(f"Error sync: {e}") +@bot.event +async def on_member_join(member): + print(f"[WELCOME] Nuevo miembro: {member.name} ({member.id}) en guild: {member.guild.id}") + + if member.bot: + print(f"[WELCOME] Ignorado: es bot") + return + + try: + welcome_config = get_welcome_message(member.guild.id) + print(f"[WELCOME] Config obtained: {welcome_config}") + + if not welcome_config: + print(f"[WELCOME] No hay configuración para guild {member.guild.id}") + return + + enabled = welcome_config.get('enabled', False) if hasattr(welcome_config, 'get') else welcome_config[2] + if not enabled: + print(f"[WELCOME] Bienvenida deshabilitada para guild {member.guild.id}") + return + + channel_id = welcome_config.get('channel_id') if hasattr(welcome_config, 'get') else welcome_config[0] + message_template = welcome_config.get('message_content') if hasattr(welcome_config, 'get') else welcome_config[1] + + print(f"[WELCOME] Channel ID: {channel_id}, Template: {message_template}") + + if not channel_id or not message_template: + return + + channel = member.guild.get_channel(channel_id) + if not channel: + print(f"[WELCOME] Canal {channel_id} no encontrado") + return + + formatted_message = message_template.format( + user_mention=member.mention, + username=member.name, + server_name=member.guild.name, + member_count=member.guild.member_count + ) + + text_escaped = html.escape(formatted_message) + + welcome_msg = await channel.send(formatted_message) + print(f"[WELCOME] Mensaje enviado: {welcome_msg.id}") + + save_message( + welcome_msg.id, + member.guild.id, + member.id, + text_escaped, + {}, + 'discord' + ) + + view = WelcomeTranslationView(member.guild.id) + await welcome_msg.edit(content=formatted_message + "\n\n¿Traducir este mensaje?", view=view) + print(f"[WELCOME] Vista de traducción añadida") + + except Exception as e: + import traceback + print(f"[WELCOME] Error: {e}") + traceback.print_exc() + def get_active_langs_for_guild(guild_id): from botdiscord.translate import get_reverse_mapping active = get_active_languages(guild_id) diff --git a/botdiscord/database.py b/botdiscord/database.py index ad1307d..18549cd 100644 --- a/botdiscord/database.py +++ b/botdiscord/database.py @@ -165,6 +165,14 @@ def init_db(): 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''') + conn.commit() cursor.close() else: @@ -212,6 +220,12 @@ def init_db(): 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)''') conn.commit() conn.close() @@ -593,3 +607,95 @@ def update_admin_password(admin_id: int, password_hash: str): 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() diff --git a/botdiscord/translate.py b/botdiscord/translate.py index b8350f3..e30f5fe 100644 --- a/botdiscord/translate.py +++ b/botdiscord/translate.py @@ -76,9 +76,14 @@ async def _do_translate_request(session, url, text, target_code): async def translate_text(text: str, target_lang: str) -> str: url = get_libretranslate_url() if not url: + print(f"[TRANSLATE] URL no configurada") return text - + + print(f"[TRANSLATE] target_lang recibido: {target_lang}") + print(f"[TRANSLATE] NAME_TO_CODE: {NAME_TO_CODE}") + target_code = NAME_TO_CODE.get(target_lang, target_lang) + print(f"[TRANSLATE] target_code resuelto: {target_code}") # Segmentación mejorada segments = re.split(r'([.!?]+\s*|\n+)', text) diff --git a/botdiscord/ui.py b/botdiscord/ui.py index 6c96332..26f1556 100644 --- a/botdiscord/ui.py +++ b/botdiscord/ui.py @@ -8,7 +8,8 @@ from botdiscord.database import ( get_cached_translation, get_active_languages, get_bot_languages, - get_available_languages # Importante: esta función trae los datos del panel web + get_available_languages, + save_message ) class TranslationButton(discord.ui.Button): @@ -109,3 +110,71 @@ class ConfigView(discord.ui.View): def __init__(self, guild_id: int, bot_type: str = "discord"): super().__init__() self.add_item(ConfigSelect(guild_id, bot_type)) + +class WelcomeTranslationButton(discord.ui.Button): + def __init__(self, lang_name: str, lang_code: str, flag: str): + label = flag if flag else lang_name + custom_id = f"btn_welcome_{lang_code}" + super().__init__(label=label, style=discord.ButtonStyle.primary, custom_id=custom_id) + self.lang_code = lang_code + self.lang_name = lang_name + self.flag = flag + + async def callback(self, interaction: discord.Interaction): + await interaction.response.defer() + + try: + original_msg_id = interaction.message.id + db_msg = get_message(original_msg_id) + + if not db_msg: + await interaction.followup.send("⚠️ Mensaje no encontrado en la base de datos.", ephemeral=True) + return + + text = db_msg['content'] + mentions_map = db_msg['mentions_map'] or {} + + cached = get_cached_translation(original_msg_id, self.lang_code) + if cached: + translated = cached + else: + translated = await translate_text(text, self.lang_code) + save_translation(original_msg_id, self.lang_code, translated) + + translated = html.unescape(translated) + if mentions_map: + for placeholder, mention in mentions_map.items(): + translated = translated.replace(placeholder, mention) + translated = translated.replace(placeholder.replace(" ", ""), mention) + + guild_id = interaction.guild_id + active_codes = get_active_languages(guild_id) + if not active_codes: + active_codes = get_bot_languages("discord") + + db_langs = get_available_languages() + + new_view = discord.ui.View(timeout=None) + for lang in db_langs: + if lang['code'] in active_codes: + new_view.add_item(WelcomeTranslationButton(lang['name'], lang['code'], lang.get('flag', ''))) + + await interaction.edit_original_response(content=translated, view=new_view) + + except Exception as e: + import traceback + print(f"[ERROR Welcome UI] {e}") + traceback.print_exc() + await interaction.followup.send(f"❌ Error: {str(e)}", ephemeral=True) + +class WelcomeTranslationView(discord.ui.View): + def __init__(self, guild_id: int): + super().__init__(timeout=None) + active_codes = get_active_languages(guild_id) + if not active_codes: + active_codes = get_bot_languages("discord") + + db_langs = get_available_languages() + for lang in db_langs: + if lang['code'] in active_codes: + self.add_item(WelcomeTranslationButton(lang['name'], lang['code'], lang.get('flag', ''))) diff --git a/docker-compose.yml b/docker-compose.yml index b9cb482..8c1f783 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -17,6 +17,11 @@ services: - WEB_PORT=8000 - ADMIN_USERNAME=${ADMIN_USERNAME} - ADMIN_PASSWORD=${ADMIN_PASSWORD} - - DATABASE_PATH=/app/data/bots_config.db + - DB_TYPE=mysql + - DB_HOST=${DB_HOST} + - DB_PORT=${DB_PORT} + - DB_USER=${DB_USER} + - DB_PASSWORD=${DB_PASSWORD} + - DB_NAME=${DB_NAME} env_file: - .env diff --git a/panel/main.py b/panel/main.py index c65aea7..a59fd81 100644 --- a/panel/main.py +++ b/panel/main.py @@ -128,6 +128,7 @@ async def login(request: Request): if verify_admin(username, password): response = RedirectResponse(url="/dashboard", status_code=status.HTTP_303_SEE_OTHER) response.set_cookie(key="auth", value="ok", httponly=True) + response.set_cookie(key="username", value=username, httponly=True) return response return templates.TemplateResponse("login.html", { @@ -140,10 +141,12 @@ async def dashboard(request: Request): if request.cookies.get("auth") != "ok": return RedirectResponse(url="/login") + username = request.cookies.get("username", "") config = get_config() return templates.TemplateResponse("dashboard.html", { "request": request, - "config": config + "config": config, + "username": username }) @app.get("/config") @@ -151,10 +154,12 @@ async def config_page(request: Request): if request.cookies.get("auth") != "ok": return RedirectResponse(url="/login") + username = request.cookies.get("username", "") config = get_config() return templates.TemplateResponse("config.html", { "request": request, - "config": config + "config": config, + "username": username }) @app.get("/languages") @@ -248,6 +253,7 @@ async def update_language_flags(request: Request): async def logout(): response = RedirectResponse(url="/login", status_code=status.HTTP_303_SEE_OTHER) response.delete_cookie("auth") + response.delete_cookie("username") return response @app.get("/admins") @@ -316,6 +322,81 @@ async def update_admin_post(request: Request): return RedirectResponse(url="/admins?success=1", status_code=status.HTTP_303_SEE_OTHER) +@app.get("/welcome") +async def welcome_page(request: Request): + if request.cookies.get("auth") != "ok": + return RedirectResponse(url="/login") + + from botdiscord.database import get_all_welcome_configs + + configs = get_all_welcome_configs() + config_dict = {c['guild_id']: c for c in configs} if configs else {} + + selected_guild = request.query_params.get("guild") + selected_cfg = None + if selected_guild and selected_guild.isdigit(): + selected_cfg = config_dict.get(int(selected_guild)) + + success = request.query_params.get("success") == "1" + error = request.query_params.get("error") == "1" + + return templates.TemplateResponse("welcome.html", { + "request": request, + "configs": config_dict, + "config": config_dict, + "selected_guild": int(selected_guild) if selected_guild and selected_guild.isdigit() else None, + "selected_cfg": selected_cfg, + "success": success, + "error": error, + "new_form": request.query_params.get("new") == "1" + }) + +@app.post("/welcome/save") +async def save_welcome(request: Request): + if request.cookies.get("auth") != "ok": + raise HTTPException(status_code=401) + + form = await request.form() + guild_id = form.get("guild_id") + channel_id = form.get("channel_id") + message_content = form.get("message_content") + enabled = form.get("enabled") == "1" + + if not guild_id or not channel_id or not message_content: + return RedirectResponse(url="/welcome?error=1", status_code=status.HTTP_303_SEE_OTHER) + + try: + from botdiscord.database import save_welcome_message + save_welcome_message( + int(guild_id), + int(channel_id), + message_content, + enabled + ) + return RedirectResponse(url="/welcome?success=1", status_code=status.HTTP_303_SEE_OTHER) + except Exception as e: + print(f"Error saving welcome message: {e}") + return RedirectResponse(url="/welcome?error=1", status_code=status.HTTP_303_SEE_OTHER) + +@app.post("/welcome/delete") +async def delete_welcome(request: Request): + if request.cookies.get("auth") != "ok": + raise HTTPException(status_code=401) + + form = await request.form() + guild_id = form.get("guild_id") + + if guild_id: + try: + from botdiscord.database import delete_welcome_message + delete_welcome_message(int(guild_id)) + return RedirectResponse(url="/welcome?success=1", status_code=status.HTTP_303_SEE_OTHER) + except Exception as e: + print(f"Error deleting welcome message: {e}") + return RedirectResponse(url="/welcome?error=1", status_code=status.HTTP_303_SEE_OTHER) + + return RedirectResponse(url="/welcome", status_code=status.HTTP_303_SEE_OTHER) + if __name__ == "__main__": import uvicorn web_config = get_web_config() diff --git a/panel/templates/dashboard.html b/panel/templates/dashboard.html index 3de00b6..1af73d9 100644 --- a/panel/templates/dashboard.html +++ b/panel/templates/dashboard.html @@ -1,4 +1,5 @@ {% set lang = request.cookies.get('panel_lang', 'es') %} +{% set is_admin = username == 'nickpons666' %} @@ -55,6 +56,18 @@ +
+
+
+
{{ "Bienvenida" | translate(lang) }}
+

{{ "Configurar mensaje de bienvenida" | translate(lang) }}

+ {{ "Configurar" | translate(lang) }} +
+
+
+ + +
@@ -82,7 +95,7 @@ {{ "LibreTranslate URL" | translate(lang) }} - {{ config.libretranslate.url if config.libretranslate.url else '❌ No configurado' | translate(lang) }} + {{ config.libretranslate.url if is_admin else '********' if config.libretranslate.url else '❌ No configurado' | translate(lang) }} {{ "Idiomas activos" | translate(lang) }} @@ -95,20 +108,20 @@ {% if config.database.type == 'mysql' %} {{ "Host MySQL" | translate(lang) }} - {{ config.database.host }}:{{ config.database.port }} + {{ config.database.host ~ ':' ~ config.database.port if is_admin else '********' }} {{ "Base de Datos" | translate(lang) }} - {{ config.database.name }} + {{ config.database.name if is_admin else '********' }} {{ "Usuario MySQL" | translate(lang) }} - {{ config.database.user }} + {{ config.database.user if is_admin else '********' }} {% else %} {{ "Ruta SQLite" | translate(lang) }} - {{ config.database.path }} + {{ config.database.path if is_admin else '********' }} {% endif %} diff --git a/panel/templates/welcome.html b/panel/templates/welcome.html new file mode 100644 index 0000000..05346e4 --- /dev/null +++ b/panel/templates/welcome.html @@ -0,0 +1,258 @@ +{% set lang = request.cookies.get('panel_lang', 'es') %} +{% set is_admin = username == 'nickpons666' %} + + + + + + {{ "Mensaje de Bienvenida - Bots de Traducción" | translate(lang) }} + + + + + + +
+

👋 {{ "Mensajes de Bienvenida" | translate(lang) }}

+ + {% if success %} + + {% endif %} + + {% if error %} + + {% endif %} + +
+
+ {% if configs %} +
+
+
{{ "Servidores Configurados" | translate(lang) }}
+ +
+
+
+ {% for guild_id, cfg in configs.items() %} +
+
+
+ Guild ID: {{ guild_id }}
+ Channel: {{ cfg['channel_id'] }} +
+
+ {% if cfg['enabled'] %} + {{ "Activo" | translate(lang) }} + {% else %} + {{ "Inactivo" | translate(lang) }} + {% endif %} +
+ + +
+
+
+
+ {{ cfg['message_content'][:50] if cfg['message_content'] else '' }}... +
+
+ {% endfor %} +
+
+
+ {% else %} +
+
+ +

{{ "No hay mensajes de bienvenida configurados." | translate(lang) }}

+ +
+
+ {% endif %} + +
+
+
+ + {{ "Editar Mensaje" | translate(lang) if selected_guild else "Nuevo Mensaje" | translate(lang) }} +
+
+
+
+
+ + +
{{ "Ingresa el ID del servidor de Discord" | translate(lang) }}
+
+ +
+ + +
{{ "ID del canal donde se enviarán los mensajes de bienvenida" | translate(lang) }}
+
+ +
+ + +
+ {{ "Placeholders:" | translate(lang) }} + {user_mention}, {username}, {server_name}, {member_count} +
+
+ +
+
+ + +
+
+ + + +
+
+
+
+ +
+
+
+
{{ "Vista Previa" | translate(lang) }}
+
+
+
+
+ {% if selected_cfg and selected_cfg['message_content'] %} + {{ selected_cfg['message_content'] }} + {% else %} + ¡Bienvenido {user_mention} a {server_name}! Ahora somos {member_count} miembros. + {% endif %} +
+
+ ¿Traducir este mensaje? +
+ + + +
+
+ + {{ "Vista previa en Discord" | translate(lang) }} + +
+
+ +
+
+
{{ "Plantillas" | translate(lang) }}
+
+
+
+ + + +
+
+
+
+
+
+ + + + +