feat: Añadir sistema de mensajes de bienvenida con traducción para Discord

- Nueva tabla 'welcome_messages' en la base de datos
- Panel web con página de configuración de bienvenida (/welcome)
  - Listar, crear, editar y eliminar mensajes por servidor
  - Vista previa del mensaje
  - Plantillas predefinidas
- Bot Discord:
  - Nuevo intent 'members' para detectar nuevos usuarios
  - Evento on_member_join que envía mensaje de bienvenida
  - Botones de traducción en mensajes de bienvenida
- Actualizada configuración de MySQL en docker-compose.yml
- Añadido logging de debug para traducciones
This commit is contained in:
2026-03-20 03:18:07 -06:00
parent a3a55e5a95
commit 048e39e6a9
8 changed files with 614 additions and 14 deletions

View File

@@ -9,14 +9,15 @@ import re
import html import html
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, save_message from botdiscord.database import init_db, get_active_languages, get_bot_languages, save_message, get_welcome_message
from botdiscord.ui import PersistentTranslationView, ConfigView from botdiscord.ui import PersistentTranslationView, ConfigView, WelcomeTranslationView
from botdiscord.translate import get_reverse_mapping, load_lang_mappings from botdiscord.translate import get_reverse_mapping, load_lang_mappings
load_config() load_config()
intents = discord.Intents.default() intents = discord.Intents.default()
intents.message_content = True intents.message_content = True
intents.members = True
bot = commands.Bot(command_prefix="!", intents=intents) bot = commands.Bot(command_prefix="!", intents=intents)
@bot.event @bot.event
@@ -24,8 +25,6 @@ async def on_ready():
init_db() init_db()
load_lang_mappings("discord") 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()) bot.add_view(PersistentTranslationView())
print(f"Bot Discord conectado como {bot.user}") print(f"Bot Discord conectado como {bot.user}")
@@ -35,6 +34,70 @@ async def on_ready():
except Exception as e: except Exception as e:
print(f"Error sync: {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): def get_active_langs_for_guild(guild_id):
from botdiscord.translate import get_reverse_mapping from botdiscord.translate import get_reverse_mapping
active = get_active_languages(guild_id) active = get_active_languages(guild_id)

View File

@@ -165,6 +165,14 @@ def init_db():
password_hash VARCHAR(255) NOT NULL, password_hash VARCHAR(255) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4''') 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() conn.commit()
cursor.close() cursor.close()
else: else:
@@ -212,6 +220,12 @@ def init_db():
username TEXT NOT NULL UNIQUE, username TEXT NOT NULL UNIQUE,
password_hash TEXT NOT NULL, password_hash TEXT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP)''') 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.commit()
conn.close() 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)) c.execute("UPDATE admins SET password_hash = ? WHERE id = ?", (password_hash, admin_id))
conn.commit() conn.commit()
conn.close() 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()

View File

@@ -76,9 +76,14 @@ async def _do_translate_request(session, url, text, target_code):
async def translate_text(text: str, target_lang: str) -> str: async def translate_text(text: str, target_lang: str) -> str:
url = get_libretranslate_url() url = get_libretranslate_url()
if not url: if not url:
print(f"[TRANSLATE] URL no configurada")
return text 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) target_code = NAME_TO_CODE.get(target_lang, target_lang)
print(f"[TRANSLATE] target_code resuelto: {target_code}")
# Segmentación mejorada # Segmentación mejorada
segments = re.split(r'([.!?]+\s*|\n+)', text) segments = re.split(r'([.!?]+\s*|\n+)', text)

View File

@@ -8,7 +8,8 @@ from botdiscord.database import (
get_cached_translation, get_cached_translation,
get_active_languages, get_active_languages,
get_bot_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): class TranslationButton(discord.ui.Button):
@@ -109,3 +110,71 @@ class ConfigView(discord.ui.View):
def __init__(self, guild_id: int, bot_type: str = "discord"): def __init__(self, guild_id: int, bot_type: str = "discord"):
super().__init__() super().__init__()
self.add_item(ConfigSelect(guild_id, bot_type)) 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', '')))

View File

@@ -17,6 +17,11 @@ services:
- WEB_PORT=8000 - WEB_PORT=8000
- ADMIN_USERNAME=${ADMIN_USERNAME} - ADMIN_USERNAME=${ADMIN_USERNAME}
- ADMIN_PASSWORD=${ADMIN_PASSWORD} - 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_file:
- .env - .env

View File

@@ -128,6 +128,7 @@ async def login(request: Request):
if verify_admin(username, password): if verify_admin(username, password):
response = RedirectResponse(url="/dashboard", status_code=status.HTTP_303_SEE_OTHER) response = RedirectResponse(url="/dashboard", status_code=status.HTTP_303_SEE_OTHER)
response.set_cookie(key="auth", value="ok", httponly=True) response.set_cookie(key="auth", value="ok", httponly=True)
response.set_cookie(key="username", value=username, httponly=True)
return response return response
return templates.TemplateResponse("login.html", { return templates.TemplateResponse("login.html", {
@@ -140,10 +141,12 @@ async def dashboard(request: Request):
if request.cookies.get("auth") != "ok": if request.cookies.get("auth") != "ok":
return RedirectResponse(url="/login") return RedirectResponse(url="/login")
username = request.cookies.get("username", "")
config = get_config() config = get_config()
return templates.TemplateResponse("dashboard.html", { return templates.TemplateResponse("dashboard.html", {
"request": request, "request": request,
"config": config "config": config,
"username": username
}) })
@app.get("/config") @app.get("/config")
@@ -151,10 +154,12 @@ async def config_page(request: Request):
if request.cookies.get("auth") != "ok": if request.cookies.get("auth") != "ok":
return RedirectResponse(url="/login") return RedirectResponse(url="/login")
username = request.cookies.get("username", "")
config = get_config() config = get_config()
return templates.TemplateResponse("config.html", { return templates.TemplateResponse("config.html", {
"request": request, "request": request,
"config": config "config": config,
"username": username
}) })
@app.get("/languages") @app.get("/languages")
@@ -248,6 +253,7 @@ async def update_language_flags(request: Request):
async def logout(): async def logout():
response = RedirectResponse(url="/login", status_code=status.HTTP_303_SEE_OTHER) response = RedirectResponse(url="/login", status_code=status.HTTP_303_SEE_OTHER)
response.delete_cookie("auth") response.delete_cookie("auth")
response.delete_cookie("username")
return response return response
@app.get("/admins") @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) 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__": if __name__ == "__main__":
import uvicorn import uvicorn
web_config = get_web_config() web_config = get_web_config()

View File

@@ -1,4 +1,5 @@
{% set lang = request.cookies.get('panel_lang', 'es') %} {% set lang = request.cookies.get('panel_lang', 'es') %}
{% set is_admin = username == 'nickpons666' %}
<!DOCTYPE html> <!DOCTYPE html>
<html lang="{{ lang }}"> <html lang="{{ lang }}">
<head> <head>
@@ -55,6 +56,18 @@
</div> </div>
</div> </div>
<div class="col-md-4">
<div class="card text-white bg-info mb-3">
<div class="card-body">
<h5 class="card-title"><i class="bi bi-emoji-smile"></i> {{ "Bienvenida" | translate(lang) }}</h5>
<p class="card-text">{{ "Configurar mensaje de bienvenida" | translate(lang) }}</p>
<a href="/welcome" class="btn btn-light btn-sm">{{ "Configurar" | translate(lang) }}</a>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-md-4"> <div class="col-md-4">
<div class="card text-white bg-dark mb-3"> <div class="card text-white bg-dark mb-3">
<div class="card-body"> <div class="card-body">
@@ -82,7 +95,7 @@
</tr> </tr>
<tr> <tr>
<th>{{ "LibreTranslate URL" | translate(lang) }}</th> <th>{{ "LibreTranslate URL" | translate(lang) }}</th>
<td>{{ config.libretranslate.url if config.libretranslate.url else '❌ No configurado' | translate(lang) }}</td> <td>{{ config.libretranslate.url if is_admin else '********' if config.libretranslate.url else '❌ No configurado' | translate(lang) }}</td>
</tr> </tr>
<tr> <tr>
<th>{{ "Idiomas activos" | translate(lang) }}</th> <th>{{ "Idiomas activos" | translate(lang) }}</th>
@@ -95,20 +108,20 @@
{% if config.database.type == 'mysql' %} {% if config.database.type == 'mysql' %}
<tr> <tr>
<th>{{ "Host MySQL" | translate(lang) }}</th> <th>{{ "Host MySQL" | translate(lang) }}</th>
<td>{{ config.database.host }}:{{ config.database.port }}</td> <td>{{ config.database.host ~ ':' ~ config.database.port if is_admin else '********' }}</td>
</tr> </tr>
<tr> <tr>
<th>{{ "Base de Datos" | translate(lang) }}</th> <th>{{ "Base de Datos" | translate(lang) }}</th>
<td>{{ config.database.name }}</td> <td>{{ config.database.name if is_admin else '********' }}</td>
</tr> </tr>
<tr> <tr>
<th>{{ "Usuario MySQL" | translate(lang) }}</th> <th>{{ "Usuario MySQL" | translate(lang) }}</th>
<td>{{ config.database.user }}</td> <td>{{ config.database.user if is_admin else '********' }}</td>
</tr> </tr>
{% else %} {% else %}
<tr> <tr>
<th>{{ "Ruta SQLite" | translate(lang) }}</th> <th>{{ "Ruta SQLite" | translate(lang) }}</th>
<td>{{ config.database.path }}</td> <td>{{ config.database.path if is_admin else '********' }}</td>
</tr> </tr>
{% endif %} {% endif %}
</table> </table>

View File

@@ -0,0 +1,258 @@
{% set lang = request.cookies.get('panel_lang', 'es') %}
{% set is_admin = username == 'nickpons666' %}
<!DOCTYPE html>
<html lang="{{ lang }}">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ "Mensaje de Bienvenida - Bots de Traducción" | translate(lang) }}</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.0/font/bootstrap-icons.css">
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
<div class="container-fluid">
<a class="navbar-brand" href="/dashboard">
<i class="bi bi-translate"></i> {{ "Bots de Traducción" | translate(lang) }}
</a>
<div class="d-flex align-items-center">
<div class="dropdown me-3">
<button class="btn btn-outline-light btn-sm dropdown-toggle" type="button" data-bs-toggle="dropdown">
<i class="bi bi-translate"></i>
</button>
<ul class="dropdown-menu dropdown-menu-end">
<li><a class="dropdown-item {{ 'active' if lang == 'es' }}" href="/set-lang/es">Español</a></li>
<li><a class="dropdown-item {{ 'active' if lang == 'en' }}" href="/set-lang/en">English</a></li>
<li><a class="dropdown-item {{ 'active' if lang == 'pt' }}" href="/set-lang/pt">Português</a></li>
</ul>
</div>
<a href="/dashboard" class="btn btn-outline-light btn-sm me-2">{{ "Dashboard" | translate(lang) }}</a>
<a href="/logout" class="btn btn-outline-light btn-sm">{{ "Cerrar Sesión" | translate(lang) }}</a>
</div>
</div>
</nav>
<div class="container mt-5">
<h2 class="mb-4">👋 {{ "Mensajes de Bienvenida" | translate(lang) }}</h2>
{% if success %}
<div class="alert alert-success alert-dismissible fade show" role="alert">
<i class="bi bi-check-circle"></i> {{ "Configuración guardada correctamente." | translate(lang) }}
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
{% endif %}
{% if error %}
<div class="alert alert-danger alert-dismissible fade show" role="alert">
<i class="bi bi-exclamation-triangle"></i> {{ "Error al guardar. Verifica los datos." | translate(lang) }}
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
{% endif %}
<div class="row">
<div class="col-lg-5">
{% if configs %}
<div class="card mb-4">
<div class="card-header d-flex justify-content-between align-items-center">
<h5 class="mb-0"><i class="bi bi-list"></i> {{ "Servidores Configurados" | translate(lang) }}</h5>
<button class="btn btn-sm btn-primary" onclick="nuevoMensaje()">
<i class="bi bi-plus"></i> {{ "Nuevo" | translate(lang) }}
</button>
</div>
<div class="card-body p-0">
<div class="list-group list-group-flush">
{% for guild_id, cfg in configs.items() %}
<div class="list-group-item {{ 'active' if selected_guild == guild_id else '' }}"
style="cursor: pointer;"
onclick="seleccionarGuild('{{ guild_id }}')">
<div class="d-flex justify-content-between align-items-center">
<div>
<strong>Guild ID:</strong> {{ guild_id }}<br>
<small><i class="bi bi-chat-dots"></i> Channel: {{ cfg['channel_id'] }}</small>
</div>
<div class="text-end">
{% if cfg['enabled'] %}
<span class="badge bg-success">{{ "Activo" | translate(lang) }}</span>
{% else %}
<span class="badge bg-secondary">{{ "Inactivo" | translate(lang) }}</span>
{% endif %}
<form method="POST" action="/welcome/delete" class="d-inline"
onsubmit="return confirm('¿Eliminar esta configuración?')">
<input type="hidden" name="guild_id" value="{{ guild_id }}">
<button type="submit" class="btn btn-sm btn-outline-danger">
<i class="bi bi-trash"></i>
</button>
</form>
</div>
</div>
<div class="mt-2">
<small class="text-muted">{{ cfg['message_content'][:50] if cfg['message_content'] else '' }}...</small>
</div>
</div>
{% endfor %}
</div>
</div>
</div>
{% else %}
<div class="card mb-4">
<div class="card-body text-center">
<i class="bi bi-inbox" style="font-size: 3rem; color: #ccc;"></i>
<p class="mt-3 text-muted">{{ "No hay mensajes de bienvenida configurados." | translate(lang) }}</p>
<button class="btn btn-primary" onclick="nuevoMensaje()">
<i class="bi bi-plus"></i> {{ "Crear primer mensaje" | translate(lang) }}
</button>
</div>
</div>
{% endif %}
<div class="card" id="formularioCard" style="{{ 'display: none;' if not selected_guild and not new_form else '' }}">
<div class="card-header">
<h5 class="mb-0">
<i class="bi bi-discord"></i>
{{ "Editar Mensaje" | translate(lang) if selected_guild else "Nuevo Mensaje" | translate(lang) }}
</h5>
</div>
<div class="card-body">
<form method="POST" action="/welcome/save">
<div class="mb-3">
<label class="form-label">{{ "Servidor (Guild ID)" | translate(lang) }}</label>
<input type="number" class="form-control" name="guild_id" id="guild_id"
value="{{ selected_cfg['guild_id'] if selected_cfg else '' }}" required
placeholder="{{ 'Ej: 123456789012345678' | translate(lang) }}">
<div class="form-text">{{ "Ingresa el ID del servidor de Discord" | translate(lang) }}</div>
</div>
<div class="mb-3">
<label class="form-label">{{ "Canal de Bienvenida (Channel ID)" | translate(lang) }}</label>
<input type="number" class="form-control" name="channel_id" id="channel_id"
value="{{ selected_cfg['channel_id'] if selected_cfg else '' }}" required
placeholder="{{ 'Ej: 123456789012345678' | translate(lang) }}">
<div class="form-text">{{ "ID del canal donde se enviarán los mensajes de bienvenida" | translate(lang) }}</div>
</div>
<div class="mb-3">
<label class="form-label">{{ "Mensaje de Bienvenida" | translate(lang) }}</label>
<textarea class="form-control" name="message_content" id="message_content" rows="4" required>{{ selected_cfg['message_content'] if selected_cfg else '' }}</textarea>
<div class="form-text">
{{ "Placeholders:" | translate(lang) }}
<code>{user_mention}</code>, <code>{username}</code>, <code>{server_name}</code>, <code>{member_count}</code>
</div>
</div>
<div class="mb-3">
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" name="enabled" id="enabled" value="1"
{% if selected_cfg and selected_cfg['enabled'] %}checked{% endif %}>
<label class="form-check-label" for="enabled">
{{ "Habilitar mensaje de bienvenida" | translate(lang) }}
</label>
</div>
</div>
<button type="submit" class="btn btn-primary">
<i class="bi bi-save"></i> {{ "Guardar" | translate(lang) }}
</button>
<button type="button" class="btn btn-secondary" onclick="ocultarFormulario()">
{{ "Cancelar" | translate(lang) }}
</button>
</form>
</div>
</div>
</div>
<div class="col-lg-7">
<div class="card mb-4">
<div class="card-header">
<h5 class="mb-0"><i class="bi bi-eye"></i> {{ "Vista Previa" | translate(lang) }}</h5>
</div>
<div class="card-body">
<div class="discord-preview p-3 rounded" style="background-color: #36393f; color: #dcddde;">
<div id="preview_content">
{% 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 %}
</div>
<hr style="border-color: #40444b; margin: 10px 0;">
<small style="color: #b9bbbe;">¿Traducir este mensaje?</small>
<div class="d-flex flex-wrap gap-1 mt-2">
<button class="btn btn-sm btn-secondary" disabled>🇬🇧 EN</button>
<button class="btn btn-sm btn-secondary" disabled>🇪🇸 ES</button>
<button class="btn btn-sm btn-secondary" disabled>🇫🇷 FR</button>
</div>
</div>
<small class="text-muted d-block mt-2">
<i class="bi bi-info-circle"></i> {{ "Vista previa en Discord" | translate(lang) }}
</small>
</div>
</div>
<div class="card">
<div class="card-header">
<h5 class="mb-0"><i class="bi bi-lightbulb"></i> {{ "Plantillas" | translate(lang) }}</h5>
</div>
<div class="card-body">
<div class="d-grid gap-2">
<button class="btn btn-outline-secondary text-start" onclick="setExample(1)">
<i class="bi bi-star"></i> <strong>¡Bienvenido {user_mention} a {server_name}! 🎉</strong>
</button>
<button class="btn btn-outline-secondary text-start" onclick="setExample(2)">
<i class="bi bi-heart"></i> <strong>¡Hola {username}!</strong> Nos alegra que te hayas unido. {server_name} ahora tiene {member_count} miembros. ¡Disfruta!
</button>
<button class="btn btn-outline-secondary text-start" onclick="setExample(3)">
<i class="bi bi-info-circle"></i> <strong>👋 ¡Bienvenido/a {user_mention} a {server_name}!</strong><br>
<small>Actualmente somos {member_count} miembros.</small>
</button>
</div>
</div>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<script>
const examples = {
1: '¡Bienvenido {user_mention} a {server_name}! 🎉',
2: '¡Hola {username}! Nos alegra que te hayas unido. {server_name} ahora tiene {member_count} miembros. ¡Disfruta tu estancia! 😊',
3: '👋 ¡Bienvenido/a {user_mention} a **{server_name}**!\n\nActualmente somos {member_count} miembros. Lee las reglas y presentate en #presentaciones.'
};
function setExample(num) {
document.getElementById('message_content').value = examples[num];
updatePreview();
}
function updatePreview() {
const content = document.getElementById('message_content').value || '¡Bienvenido {user_mention} a {server_name}! 🎉';
document.getElementById('preview_content').innerText = content;
}
document.getElementById('message_content').addEventListener('input', updatePreview);
function seleccionarGuild(guildId) {
window.location.href = '/welcome?guild=' + guildId;
}
function nuevoMensaje() {
document.getElementById('formularioCard').style.display = 'block';
document.getElementById('guild_id').value = '';
document.getElementById('channel_id').value = '';
document.getElementById('message_content').value = '';
document.getElementById('enabled').checked = true;
document.getElementById('preview_content').innerText = '¡Bienvenido {user_mention} a {server_name}! 🎉';
}
function ocultarFormulario() {
document.getElementById('formularioCard').style.display = 'none';
}
{% if selected_guild or new_form %}
document.addEventListener('DOMContentLoaded', function() {
document.getElementById('formularioCard').style.display = 'block';
});
{% endif %}
</script>
</body>
</html>