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:
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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', '')))
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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>
|
||||
|
||||
258
panel/templates/welcome.html
Normal file
258
panel/templates/welcome.html
Normal 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>
|
||||
Reference in New Issue
Block a user