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

@@ -1,4 +1,5 @@
{% set lang = request.cookies.get('panel_lang', 'es') %}
{% set is_admin = username == 'nickpons666' %}
<!DOCTYPE html>
<html lang="{{ lang }}">
<head>
@@ -55,6 +56,18 @@
</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="card text-white bg-dark mb-3">
<div class="card-body">
@@ -82,7 +95,7 @@
</tr>
<tr>
<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>
<th>{{ "Idiomas activos" | translate(lang) }}</th>
@@ -95,20 +108,20 @@
{% if config.database.type == 'mysql' %}
<tr>
<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>
<th>{{ "Base de Datos" | translate(lang) }}</th>
<td>{{ config.database.name }}</td>
<td>{{ config.database.name if is_admin else '********' }}</td>
</tr>
<tr>
<th>{{ "Usuario MySQL" | translate(lang) }}</th>
<td>{{ config.database.user }}</td>
<td>{{ config.database.user if is_admin else '********' }}</td>
</tr>
{% else %}
<tr>
<th>{{ "Ruta SQLite" | translate(lang) }}</th>
<td>{{ config.database.path }}</td>
<td>{{ config.database.path if is_admin else '********' }}</td>
</tr>
{% endif %}
</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>