Compare commits

...

10 Commits

Author SHA1 Message Date
223c44f1d8 Feature: Agregar selección de idiomas por plataforma (Telegram/Discord)
- Agregar columnas telegram_enabled y discord_enabled a supported_languages
- Nueva interfaz en admin/languages.php con checkboxes para Telegram y Discord
- Los bots ahora solo muestran botones de traducción para los idiomas seleccionados por plataforma
2026-03-10 18:52:30 -06:00
8170931f3d Fix: Motor de traducción mejorado para mensajes multi-idioma y dinámicos
- Rediseñado translatePartial para manejar mezclas de idiomas (ej: 'Hello a todos')
- Eliminados hardcodes de idiomas ES/PT, ahora es 100% dinámico
- Corregido truncado de texto original en el webhook de Telegram
- Mejorada la deduplicación y preservación de párrafos en traducciones
- Fallback dinámico al primer idioma activo de la base de datos
2026-03-10 18:23:56 -06:00
bf960f3fc3 Fix: Botones de traducción - editar mensaje en Telegram y Discord 2026-03-10 16:30:52 -06:00
082e01b358 Feature: Reescribir configuración Docker completa
- Dockerfile: PHP 8.3 con Apache, copia todo el código a la imagen
- docker-compose.yml: Sin volúmenes de código, usa env_file
- supervisord.conf: Incluye Apache, Discord bot y colas de procesos
- apache.conf: Configuración de virtualhost con rewrite
- .dockerignore: Excluye archivos innecesarios de la imagen
- Eliminados archivos duplicados de supervisor
- Creada carpeta database/ para init scripts
- Telegram funciona vía webhook (no necesita supervisor)
2026-02-20 21:41:24 -06:00
c439ecd689 Fix: Evitar duplicar Español en selector de idioma 2026-02-20 17:12:24 -06:00
2dcff94a22 Feature: Agregar footer con licencia MIT y desarrollador 2026-02-20 17:10:07 -06:00
880940f515 Docs: Actualizar README con nuevas características
- Diseño militar/táctico
- Sistema multi-idioma con LibreTranslate
- Tema claro/oscuro
- Navbar horizontal
- Estructura completa del proyecto
- Variables de entorno actualizadas
- Comandos de Telegram y Discord
2026-02-20 17:01:00 -06:00
509c7e1709 Feature: Traducir admin/languages.php - tablas, modales, botones 2026-02-20 16:09:17 -06:00
e8213b916b Feature: Traducir todas las vistas - parte 2
- admin_send_message.php: formulario, modal galería
- telegram/admin/telegram_bot_interactions.php: configuración bot
- telegram/admin/telegram_welcome.php: mensajes bienvenida, modales
2026-02-20 16:05:51 -06:00
2dd99c04dd Feature: Traducir todas las vistas - parte 1
- admin/recipients.php: tablas, modales, labels
- admin/comandos.php: títulos, tablas, descripciones
- admin/test_discord_connection.php: formularios, alertas
- admin/ia_agent.php: configuración, parámetros
- profile.php: información, formulario contraseña
- set_webhook.php: alertas, formularios
- chat_telegram.php: usuarios, historial
- translate_message.php: formulario de traducción
2026-02-20 16:01:06 -06:00
33 changed files with 1082 additions and 486 deletions

48
.dockerignore Normal file
View File

@@ -0,0 +1,48 @@
# Git
.git
.gitignore
# Docker
docker
# Node modules (si hubiera)
node_modules
# Logs
logs/*
!logs/.gitkeep
# Archivos temporales
*.log
*.tmp
*.swp
*.swo
*~
# IDE
.idea
.vscode
*.sublime-*
# OS
.DS_Store
Thumbs.db
# Archivos de desarrollo
.env.local
.env.*.local
*.md
!README.md
# Tests
tests
phpunit.xml
.phpunit.result.cache
# Composer
vendor
!vendor/.gitkeep
# Archivos de upload temporales
galeria/*
!galeria/.gitkeep

81
README.md Normal file → Executable file
View File

@@ -1,14 +1,30 @@
# Last War - Sistema de Mensajería Multiplataforma # Last War - Sistema de Mensajería Multiplataforma
Sistema de mensajería automatizada para Discord y Telegram con traducción automática y asistente IA. Sistema de mensajería automatizada para Discord y Telegram con traducción automática, asistente IA y soporte multi-idioma.
## Características ## Características
### Mensajería
- **Discord Bot**: Envío de mensajes, traducción automática, comandos (#lista) - **Discord Bot**: Envío de mensajes, traducción automática, comandos (#lista)
- **Telegram Bot**: Webhook para mensajes, traducción con botones inline - **Telegram Bot**: Webhook para mensajes, traducción con botones inline
- **Traducción Automática**: LibreTranslate con detección de idioma - **Traducción Automática**: LibreTranslate con detección de idioma
- **Asistente IA**: Integración con Groq para respuestas inteligentes - **Asistente IA**: Integración con Groq para respuestas inteligentes
- **Panel de Administración**: Gestiona usuarios, mensajes, plantillas y configuración
### Panel de Administración
- Gestión de usuarios y permisos (admin/user)
- Administración de destinatarios por plataforma
- Gestión de idiomas con banderas personalizables
- Mensajes programados y recurrentes
- Galería de imágenes integrada
- Configuración del bot de Telegram
- Configuración del agente IA
### Diseño y UX
- **Tema Militar/Táctico**: Diseño inspirado en interfaces de videojuegos
- **Multi-idioma**: Soporte completo para traducción de la interfaz vía LibreTranslate
- **Tema Claro/Oscuro**: Cambio dinámico de tema
- **Navbar Horizontal**: Navegación optimizada con dropdown para admin
- **Responsive**: Adaptado para móviles y tablets
## Requisitos ## Requisitos
@@ -16,6 +32,9 @@ Sistema de mensajería automatizada para Discord y Telegram con traducción auto
- MySQL 8.0+ - MySQL 8.0+
- Composer - Composer
- Servidor web (Apache/Nginx) - Servidor web (Apache/Nginx)
- LibreTranslate (para traducciones)
- Cuenta de Discord Developer
- Bot de Telegram
## Instalación ## Instalación
@@ -24,10 +43,16 @@ Sistema de mensajería automatizada para Discord y Telegram con traducción auto
3. Configurar `.env` con las variables de entorno 3. Configurar `.env` con las variables de entorno
4. Importar estructura de base de datos 4. Importar estructura de base de datos
5. Configurar webhooks de Telegram y Discord 5. Configurar webhooks de Telegram y Discord
6. Configurar idiomas activos en el panel admin
## Variables de Entorno ## Variables de Entorno
```env ```env
# Aplicación
APP_ENV=production
APP_URL=https://tudominio.com
TZ=America/Mexico_City
# Base de datos # Base de datos
DB_HOST=localhost DB_HOST=localhost
DB_PORT=3306 DB_PORT=3306
@@ -37,8 +62,12 @@ DB_PASS=
# Telegram # Telegram
TELEGRAM_BOT_TOKEN= TELEGRAM_BOT_TOKEN=
TELEGRAM_WEBHOOK_TOKEN=
# Discord # Discord
DISCORD_GUILD_ID=
DISCORD_CLIENT_ID=
DISCORD_CLIENT_SECRET=
DISCORD_BOT_TOKEN= DISCORD_BOT_TOKEN=
# LibreTranslate # LibreTranslate
@@ -47,38 +76,76 @@ LIBRETRANSLATE_URL=http://localhost:5000
# IA (Groq) # IA (Groq)
GROQ_API_KEY= GROQ_API_KEY=
# Knowledge Base # Knowledge Base (para IA)
KB_DB_HOST= KB_DB_HOST=
KB_DB_PORT= KB_DB_PORT=
KB_DB_NAME= KB_DB_NAME=
KB_DB_USER= KB_DB_USER=
KB_DB_PASS= KB_DB_PASS=
# JWT
JWT_SECRET=
``` ```
## Comandos ## Comandos
### Telegram ### Telegram
- `#lista` - Enviar plantilla de lista - `/start` - Iniciar el bot
- `hola` - Mostrar botones de traducción - `#plantilla` - Enviar plantilla por nombre
- `/comandos` - Ver comandos disponibles
- `/setlang [código]` - Cambiar idioma
- `/bienvenida` - Mensaje de bienvenida
- `/agente` - Activar modo IA
### Discord ### Discord
- `#lista` - Enviar plantilla de lista - `#plantilla` - Enviar plantilla por nombre
- `/comandos` - Ver comandos disponibles - `/comandos` - Ver comandos disponibles
- `/setlang [código]` - Cambiar idioma
- `/bienvenida` - Mensaje de bienvenida
- `/agente` - Activar modo IA - `/agente` - Activar modo IA
## Estructura ## Estructura
``` ```
├── admin/ # Panel de administración ├── admin/ # Panel de administración
│ ├── users.php # Gestión de usuarios
│ ├── recipients.php # Gestión de destinatarios
│ ├── languages.php # Gestión de idiomas
│ ├── comandos.php # Lista de comandos
│ ├── ia_agent.php # Configuración IA
│ └── system.php # Info del sistema
├── discord/ # Archivos de Discord ├── discord/ # Archivos de Discord
│ ├── senders/ # Enviadores de mensajes
│ └── converters/ # Conversores de formato
├── includes/ # Funciones principales ├── includes/ # Funciones principales
├── src/ # Clases (IA, Translate) │ ├── db.php # Conexión BD
│ ├── i18n.php # Sistema de traducción
│ ├── auth.php # Autenticación
│ └── session_check.php
├── src/ # Clases principales
│ ├── IA/ # Agente IA
│ └── Translate.php # Traductor
├── telegram/ # Archivos de Telegram ├── telegram/ # Archivos de Telegram
│ ├── admin/ # Configuración Telegram
│ ├── senders/ # Enviadores de mensajes
│ └── converters/ # Conversores de formato
├── templates/ # Plantillas HTML ├── templates/ # Plantillas HTML
│ ├── header.php # Cabecera con navbar
│ └── footer.php # Pie de página
├── galeria/ # Imágenes para mensajes
├── logs/ # Logs del sistema ├── logs/ # Logs del sistema
└── *.php # Archivos principales └── *.php # Archivos principales
``` ```
## Sistema Multi-idioma
El sistema traduce automáticamente toda la interfaz usando LibreTranslate:
- Los idiomas se gestionan desde `Admin > Idiomas`
- Solo los idiomas activos aparecen en el selector
- El idioma base es español
- Las traducciones se cachean automáticamente
## Licencia ## Licencia
MIT MIT

View File

@@ -20,11 +20,11 @@ require_once __DIR__ . '/../templates/header.php';
?> ?>
<div class="d-flex justify-content-between align-items-center mb-4"> <div class="d-flex justify-content-between align-items-center mb-4">
<h2><i class="bi bi-terminal"></i> Gestión de Comandos</h2> <h2><i class="bi bi-terminal"></i> <?= t('Gestión de Comandos') ?></h2>
</div> </div>
<div class="alert alert-info"> <div class="alert alert-info">
<i class="bi bi-info-circle"></i> Los comandos se usan en Discord y Telegram anteponiendo <code>#</code> al nombre del comando. <i class="bi bi-info-circle"></i> <?= t('Los comandos se usan en Discord y Telegram anteponiendo') ?> <code>#</code> <?= t('al nombre del comando') ?>.
</div> </div>
<div class="row"> <div class="row">
@@ -32,16 +32,16 @@ require_once __DIR__ . '/../templates/header.php';
<div class="card border-0 shadow-sm"> <div class="card border-0 shadow-sm">
<div class="card-body"> <div class="card-body">
<?php if (empty($templates)): ?> <?php if (empty($templates)): ?>
<p class="text-muted text-center py-4">No hay plantillas con comandos</p> <p class="text-muted text-center py-4"><?= t('No hay plantillas con comandos') ?></p>
<?php else: ?> <?php else: ?>
<div class="table-responsive"> <div class="table-responsive">
<table class="table table-hover"> <table class="table table-hover">
<thead> <thead>
<tr> <tr>
<th>#</th> <th>#</th>
<th>Nombre</th> <th><?= t('Nombre') ?></th>
<th>Comando</th> <th><?= t('Comando') ?></th>
<th>Uso</th> <th><?= t('Uso') ?></th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@@ -53,7 +53,7 @@ require_once __DIR__ . '/../templates/header.php';
<?php if ($template['telegram_command']): ?> <?php if ($template['telegram_command']): ?>
<code>#<?= htmlspecialchars($template['telegram_command']) ?></code> <code>#<?= htmlspecialchars($template['telegram_command']) ?></code>
<?php else: ?> <?php else: ?>
<span class="text-muted">Sin comando</span> <span class="text-muted"><?= t('Sin comando') ?></span>
<?php endif; ?> <?php endif; ?>
</td> </td>
<td> <td>
@@ -74,37 +74,37 @@ require_once __DIR__ . '/../templates/header.php';
<div class="row mt-4"> <div class="row mt-4">
<div class="col-md-12"> <div class="col-md-12">
<div class="card border-0 shadow-sm"> <div class="card border-0 shadow-sm">
<div class="card-header bg-white border-0"> <div class="card-header border-0">
<h5 class="mb-0"><i class="bi bi-discord"></i> Comandos de Discord</h5> <h5 class="mb-0"><i class="bi bi-discord"></i> <?= t('Comandos de Discord') ?></h5>
</div> </div>
<div class="card-body"> <div class="card-body">
<table class="table table-sm"> <table class="table table-sm">
<thead> <thead>
<tr> <tr>
<th>Comando</th> <th><?= t('Comando') ?></th>
<th>Descripción</th> <th><?= t('Descripción') ?></th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<tr> <tr>
<td><code>#comando</code></td> <td><code>#comando</code></td>
<td>Envía la plantilla asociada</td> <td><?= t('Envía la plantilla asociada') ?></td>
</tr> </tr>
<tr> <tr>
<td><code>/comandos</code></td> <td><code>/comandos</code></td>
<td>Lista de comandos disponibles</td> <td><?= t('Lista de comandos disponibles') ?></td>
</tr> </tr>
<tr> <tr>
<td><code>/setlang [código]</code></td> <td><code>/setlang [código]</code></td>
<td>Establece el idioma del usuario</td> <td><?= t('Establece el idioma del usuario') ?></td>
</tr> </tr>
<tr> <tr>
<td><code>/bienvenida</code></td> <td><code>/bienvenida</code></td>
<td>Envía mensaje de bienvenida</td> <td><?= t('Envía mensaje de bienvenida') ?></td>
</tr> </tr>
<tr> <tr>
<td><code>/agente</code></td> <td><code>/agente</code></td>
<td>Cambia a modo IA</td> <td><?= t('Cambia a modo IA') ?></td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
@@ -116,41 +116,41 @@ require_once __DIR__ . '/../templates/header.php';
<div class="row mt-4"> <div class="row mt-4">
<div class="col-md-12"> <div class="col-md-12">
<div class="card border-0 shadow-sm"> <div class="card border-0 shadow-sm">
<div class="card-header bg-white border-0"> <div class="card-header border-0">
<h5 class="mb-0"><i class="bi bi-telegram"></i> Comandos de Telegram</h5> <h5 class="mb-0"><i class="bi bi-telegram"></i> <?= t('Comandos de Telegram') ?></h5>
</div> </div>
<div class="card-body"> <div class="card-body">
<table class="table table-sm"> <table class="table table-sm">
<thead> <thead>
<tr> <tr>
<th>Comando</th> <th><?= t('Comando') ?></th>
<th>Descripción</th> <th><?= t('Descripción') ?></th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<tr> <tr>
<td><code>#comando</code></td> <td><code>#comando</code></td>
<td>Envía la plantilla asociada</td> <td><?= t('Envía la plantilla asociada') ?></td>
</tr> </tr>
<tr> <tr>
<td><code>/start</code></td> <td><code>/start</code></td>
<td>Inicia el bot</td> <td><?= t('Inicia el bot') ?></td>
</tr> </tr>
<tr> <tr>
<td><code>/comandos</code></td> <td><code>/comandos</code></td>
<td>Lista de comandos disponibles</td> <td><?= t('Lista de comandos disponibles') ?></td>
</tr> </tr>
<tr> <tr>
<td><code>/setlang [código]</code></td> <td><code>/setlang [código]</code></td>
<td>Establece el idioma del usuario</td> <td><?= t('Establece el idioma del usuario') ?></td>
</tr> </tr>
<tr> <tr>
<td><code>/bienvenida</code></td> <td><code>/bienvenida</code></td>
<td>Envía mensaje de bienvenida</td> <td><?= t('Envía mensaje de bienvenida') ?></td>
</tr> </tr>
<tr> <tr>
<td><code>/agente</code></td> <td><code>/agente</code></td>
<td>Cambia a modo IA</td> <td><?= t('Cambia a modo IA') ?></td>
</tr> </tr>
</tbody> </tbody>
</table> </table>

View File

@@ -88,7 +88,7 @@ $config = $agent->getAllConfig();
?> ?>
<div class="d-flex justify-content-between align-items-center mb-4"> <div class="d-flex justify-content-between align-items-center mb-4">
<h2><i class="bi bi-robot"></i> Configuración del Agente IA</h2> <h2><i class="bi bi-cpu"></i> <?= t('Configuración del Agente IA') ?></h2>
</div> </div>
<?php if (!empty($message)): ?> <?php if (!empty($message)): ?>
@@ -101,37 +101,37 @@ $config = $agent->getAllConfig();
<div class="row"> <div class="row">
<div class="col-md-6"> <div class="col-md-6">
<div class="card border-0 shadow-sm mb-4"> <div class="card border-0 shadow-sm mb-4">
<div class="card-header bg-white border-0"> <div class="card-header border-0">
<h5 class="mb-0"><i class="bi bi-database"></i> Conexión a Knowledge Base</h5> <h5 class="mb-0"><i class="bi bi-database"></i> <?= t('Conexión a Knowledge Base') ?></h5>
</div> </div>
<div class="card-body"> <div class="card-body">
<form method="POST"> <form method="POST">
<div class="mb-3"> <div class="mb-3">
<label class="form-label">Host</label> <label class="form-label"><?= t('Host') ?></label>
<input type="text" name="kb_host" class="form-control" value="<?= htmlspecialchars($config['kb_db_host'] ?? $_ENV['KB_DB_HOST'] ?? '') ?>"> <input type="text" name="kb_host" class="form-control" value="<?= htmlspecialchars($config['kb_db_host'] ?? $_ENV['KB_DB_HOST'] ?? '') ?>">
</div> </div>
<div class="mb-3"> <div class="mb-3">
<label class="form-label">Puerto</label> <label class="form-label"><?= t('Puerto') ?></label>
<input type="text" name="kb_port" class="form-control" value="<?= htmlspecialchars($config['kb_db_port'] ?? $_ENV['KB_DB_PORT'] ?? '') ?>"> <input type="text" name="kb_port" class="form-control" value="<?= htmlspecialchars($config['kb_db_port'] ?? $_ENV['KB_DB_PORT'] ?? '') ?>">
</div> </div>
<div class="mb-3"> <div class="mb-3">
<label class="form-label">Base de Datos</label> <label class="form-label"><?= t('Base de Datos') ?></label>
<input type="text" name="kb_dbname" class="form-control" value="<?= htmlspecialchars($config['kb_db_name'] ?? $_ENV['KB_DB_NAME'] ?? '') ?>"> <input type="text" name="kb_dbname" class="form-control" value="<?= htmlspecialchars($config['kb_db_name'] ?? $_ENV['KB_DB_NAME'] ?? '') ?>">
</div> </div>
<div class="mb-3"> <div class="mb-3">
<label class="form-label">Usuario</label> <label class="form-label"><?= t('Usuario') ?></label>
<input type="text" name="kb_user" class="form-control" value="<?= htmlspecialchars($config['kb_db_user'] ?? $_ENV['KB_DB_USER'] ?? '') ?>"> <input type="text" name="kb_user" class="form-control" value="<?= htmlspecialchars($config['kb_db_user'] ?? $_ENV['KB_DB_USER'] ?? '') ?>">
</div> </div>
<div class="mb-3"> <div class="mb-3">
<label class="form-label">Contraseña</label> <label class="form-label"><?= t('Contraseña') ?></label>
<input type="password" name="kb_pass" class="form-control" value="<?= htmlspecialchars($config['kb_db_pass'] ?? '') ?>" placeholder="Dejar vacío para mantener actual"> <input type="password" name="kb_pass" class="form-control" value="<?= htmlspecialchars($config['kb_db_pass'] ?? '') ?>" placeholder="<?= t('Dejar vacío para mantener actual') ?>">
</div> </div>
<div class="d-grid gap-2 d-md-flex"> <div class="d-grid gap-2 d-md-flex">
<button type="submit" name="update_kb_config" class="btn btn-primary"> <button type="submit" name="update_kb_config" class="btn btn-primary">
<i class="bi bi-save"></i> Guardar <i class="bi bi-save"></i> <?= t('Guardar') ?>
</button> </button>
<button type="submit" name="test_connection" class="btn btn-outline-primary"> <button type="submit" name="test_connection" class="btn btn-outline-primary">
<i class="bi bi-plug"></i> Probar Conexión <i class="bi bi-plug"></i> <?= t('Probar Conexión') ?>
</button> </button>
</div> </div>
</form> </form>
@@ -139,31 +139,31 @@ $config = $agent->getAllConfig();
</div> </div>
<div class="card border-0 shadow-sm mb-4"> <div class="card border-0 shadow-sm mb-4">
<div class="card-header bg-white border-0"> <div class="card-header border-0">
<h5 class="mb-0"><i class="bi bi-cpu"></i> Configuración de IA</h5> <h5 class="mb-0"><i class="bi bi-cpu"></i> <?= t('Configuración de IA') ?></h5>
</div> </div>
<div class="card-body"> <div class="card-body">
<form method="POST"> <form method="POST">
<div class="mb-3"> <div class="mb-3">
<label class="form-label">Habilitar Knowledge Base</label> <label class="form-label"><?= t('Habilitar Knowledge Base') ?></label>
<select name="config_value" class="form-select"> <select name="config_value" class="form-select">
<option value="1" <?= ($config['kb_enabled'] ?? '1') === '1' ? 'selected' : '' ?>>Habilitado</option> <option value="1" <?= ($config['kb_enabled'] ?? '1') === '1' ? 'selected' : '' ?>><?= t('Habilitado') ?></option>
<option value="0" <?= ($config['kb_enabled'] ?? '1') === '0' ? 'selected' : '' ?>>Deshabilitado</option> <option value="0" <?= ($config['kb_enabled'] ?? '1') === '0' ? 'selected' : '' ?>><?= t('Deshabilitado') ?></option>
</select> </select>
<input type="hidden" name="config_key" value="kb_enabled"> <input type="hidden" name="config_key" value="kb_enabled">
</div> </div>
<div class="mb-3"> <div class="mb-3">
<label class="form-label">Modelo de IA (Groq)</label> <label class="form-label"><?= t('Modelo de IA') ?> (Groq)</label>
<select name="config_value_model" class="form-select"> <select name="config_value_model" class="form-select">
<option value="llama-3.1-8b-instant" <?= ($config['ai_model'] ?? 'llama-3.1-8b-instant') === 'llama-3.1-8b-instant' ? 'selected' : '' ?>>Llama 3.1 8B (Rápido - Recomendado)</option> <option value="llama-3.1-8b-instant" <?= ($config['ai_model'] ?? 'llama-3.1-8b-instant') === 'llama-3.1-8b-instant' ? 'selected' : '' ?>>Llama 3.1 8B (<?= t('Rápido - Recomendado') ?>)</option>
<option value="mixtral-8x7b-32768" <?= ($config['ai_model'] ?? '') === 'mixtral-8x7b-32768' ? 'selected' : '' ?>>Mixtral 8x7B</option> <option value="mixtral-8x7b-32768" <?= ($config['ai_model'] ?? '') === 'mixtral-8x7b-32768' ? 'selected' : '' ?>>Mixtral 8x7B</option>
<option value="llama3-70b-8192" <?= ($config['ai_model'] ?? '') === 'llama3-70b-8192' ? 'selected' : '' ?>>Llama 3 70B</option> <option value="llama3-70b-8192" <?= ($config['ai_model'] ?? '') === 'llama3-70b-8192' ? 'selected' : '' ?>>Llama 3 70B</option>
</select> </select>
<small class="text-muted">Solo modelos gratuitos. Si se agotan los tokens, cambiará automáticamente al siguiente.</small> <small class="text-muted"><?= t('Solo modelos gratuitos. Si se agotan los tokens, cambiará automáticamente al siguiente.') ?></small>
</div> </div>
<div class="d-grid gap-2"> <div class="d-grid gap-2">
<button type="submit" name="update_config" class="btn btn-primary"> <button type="submit" name="update_config" class="btn btn-primary">
<i class="bi bi-check-circle"></i> Guardar Configuración <i class="bi bi-check-circle"></i> <?= t('Guardar Configuración') ?>
</button> </button>
</div> </div>
</form> </form>
@@ -173,20 +173,20 @@ $config = $agent->getAllConfig();
<div class="col-md-6"> <div class="col-md-6">
<div class="card border-0 shadow-sm mb-4"> <div class="card border-0 shadow-sm mb-4">
<div class="card-header bg-white border-0"> <div class="card-header border-0">
<h5 class="mb-0"><i class="bi bi-chat-left-text"></i> Prompt del Sistema</h5> <h5 class="mb-0"><i class="bi bi-chat-left-text"></i> <?= t('Prompt del Sistema') ?></h5>
</div> </div>
<div class="card-body"> <div class="card-body">
<form method="POST"> <form method="POST">
<div class="mb-3"> <div class="mb-3">
<label class="form-label">Instrucciones para el agente</label> <label class="form-label"><?= t('Instrucciones para el agente') ?></label>
<textarea name="config_value" class="form-control" rows="6"><?= htmlspecialchars($config['system_prompt'] ?? '') ?></textarea> <textarea name="config_value" class="form-control" rows="6"><?= htmlspecialchars($config['system_prompt'] ?? '') ?></textarea>
<small class="text-muted">Instrucciones que seguirá el agente al responder.</small> <small class="text-muted"><?= t('Instrucciones que seguirá el agente al responder.') ?></small>
<input type="hidden" name="config_key" value="system_prompt"> <input type="hidden" name="config_key" value="system_prompt">
</div> </div>
<div class="d-grid"> <div class="d-grid">
<button type="submit" name="update_config" class="btn btn-primary"> <button type="submit" name="update_config" class="btn btn-primary">
<i class="bi bi-check-circle"></i> Guardar Prompt <i class="bi bi-check-circle"></i> <?= t('Guardar Prompt') ?>
</button> </button>
</div> </div>
</form> </form>
@@ -194,25 +194,25 @@ $config = $agent->getAllConfig();
</div> </div>
<div class="card border-0 shadow-sm mb-4"> <div class="card border-0 shadow-sm mb-4">
<div class="card-header bg-white border-0"> <div class="card-header border-0">
<h5 class="mb-0"><i class="bi bi-sliders"></i> Parámetros Adicionales</h5> <h5 class="mb-0"><i class="bi bi-sliders"></i> <?= t('Parámetros Adicionales') ?></h5>
</div> </div>
<div class="card-body"> <div class="card-body">
<form method="POST"> <form method="POST">
<div class="mb-3"> <div class="mb-3">
<label class="form-label">Máximo de resultados de KB</label> <label class="form-label"><?= t('Máximo de resultados de KB') ?></label>
<input type="number" name="config_value" class="form-control" value="<?= htmlspecialchars($config['kb_max_results'] ?? '5') ?>" min="1" max="20"> <input type="number" name="config_value" class="form-control" value="<?= htmlspecialchars($config['kb_max_results'] ?? '5') ?>" min="1" max="20">
<small class="text-muted">Cantidad de artículos a buscar en la base de conocimientos.</small> <small class="text-muted"><?= t('Cantidad de artículos a buscar en la base de conocimientos.') ?></small>
<input type="hidden" name="config_key" value="kb_max_results"> <input type="hidden" name="config_key" value="kb_max_results">
</div> </div>
<div class="mb-3"> <div class="mb-3">
<label class="form-label">Máximo de caracteres en respuesta</label> <label class="form-label"><?= t('Máximo de caracteres en respuesta') ?></label>
<input type="number" name="config_value2" class="form-control" value="<?= htmlspecialchars($config['response_max_length'] ?? '1500') ?>" min="100" max="4000"> <input type="number" name="config_value2" class="form-control" value="<?= htmlspecialchars($config['response_max_length'] ?? '1500') ?>" min="100" max="4000">
<small class="text-muted">Límite de caracteres en las respuestas del agente.</small> <small class="text-muted"><?= t('Límite de caracteres en las respuestas del agente.') ?></small>
</div> </div>
<div class="d-grid"> <div class="d-grid">
<button type="submit" name="update_config" class="btn btn-primary"> <button type="submit" name="update_config" class="btn btn-primary">
<i class="bi bi-check-circle"></i> Guardar Parámetros <i class="bi bi-check-circle"></i> <?= t('Guardar Parámetros') ?>
</button> </button>
</div> </div>
</form> </form>
@@ -224,23 +224,23 @@ $config = $agent->getAllConfig();
<div class="row"> <div class="row">
<div class="col-md-12"> <div class="col-md-12">
<div class="card border-0 shadow-sm"> <div class="card border-0 shadow-sm">
<div class="card-header bg-white border-0"> <div class="card-header border-0">
<h5 class="mb-0"><i class="bi bi-chat-dots"></i> Prueba del Agente</h5> <h5 class="mb-0"><i class="bi bi-chat-dots"></i> <?= t('Prueba del Agente') ?></h5>
</div> </div>
<div class="card-body"> <div class="card-body">
<form method="POST" class="mb-3"> <form method="POST" class="mb-3">
<div class="input-group"> <div class="input-group">
<span class="input-group-text"><i class="bi bi-question-circle"></i></span> <span class="input-group-text"><i class="bi bi-question-circle"></i></span>
<input type="text" name="test_question" class="form-control" placeholder="Escribe una pregunta para probar el agente..." value="<?= htmlspecialchars($_POST['test_question'] ?? '') ?>"> <input type="text" name="test_question" class="form-control" placeholder="<?= t('Escribe una pregunta para probar el agente...') ?>" value="<?= htmlspecialchars($_POST['test_question'] ?? '') ?>">
<button type="submit" class="btn btn-success"> <button type="submit" class="btn btn-success">
<i class="bi bi-send"></i> Enviar <i class="bi bi-send"></i> <?= t('Enviar') ?>
</button> </button>
</div> </div>
</form> </form>
<?php if (isset($testResponse)): ?> <?php if (isset($testResponse)): ?>
<div class="mt-3"> <div class="mt-3">
<label class="form-label text-muted">Respuesta del agente:</label> <label class="form-label text-muted"><?= t('Respuesta del agente') ?>:</label>
<div class="p-3 bg-light rounded border"> <div class="p-3 bg-light rounded border">
<pre class="mb-0 white-space: pre-wrap;"><?= htmlspecialchars($testResponse) ?></pre> <pre class="mb-0 white-space: pre-wrap;"><?= htmlspecialchars($testResponse) ?></pre>
</div> </div>

View File

@@ -83,6 +83,22 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) {
} catch (Exception $e) { } catch (Exception $e) {
$syncError = "Error al conectar con LibreTranslate: " . $e->getMessage() . ". Verifica que el servicio esté configurado correctamente en el archivo .env"; $syncError = "Error al conectar con LibreTranslate: " . $e->getMessage() . ". Verifica que el servicio esté configurado correctamente en el archivo .env";
} }
} elseif ($action === 'toggle_telegram') {
$id = (int) $_POST['id'];
$stmt = $pdo->prepare("UPDATE supported_languages SET telegram_enabled = NOT telegram_enabled WHERE id = ? AND is_active = TRUE");
$stmt->execute([$id]);
logActivity(getCurrentUserId(), 'toggle_telegram_language', "Idioma Telegram ID: $id");
header('Location: languages.php');
exit;
} elseif ($action === 'toggle_discord') {
$id = (int) $_POST['id'];
$stmt = $pdo->prepare("UPDATE supported_languages SET discord_enabled = NOT discord_enabled WHERE id = ? AND is_active = TRUE");
$stmt->execute([$id]);
logActivity(getCurrentUserId(), 'toggle_discord_language', "Idioma Discord ID: $id");
header('Location: languages.php');
exit;
} }
} }
@@ -256,16 +272,16 @@ require_once __DIR__ . '/../templates/header.php';
?> ?>
<div class="d-flex justify-content-between align-items-center mb-4"> <div class="d-flex justify-content-between align-items-center mb-4">
<h2><i class="bi bi-translate"></i> Gestión de Idiomas</h2> <h2><i class="bi bi-translate"></i> <?= t('Gestión de Idiomas') ?></h2>
<div> <div class="d-flex gap-2">
<form method="POST" class="d-inline"> <form method="POST" class="d-inline">
<input type="hidden" name="action" value="sync_libretranslate"> <input type="hidden" name="action" value="sync_libretranslate">
<button type="submit" class="btn btn-outline-primary"> <button type="submit" class="btn btn-outline-secondary">
<i class="bi bi-cloud-download"></i> Sincronizar con LibreTranslate <i class="bi bi-arrow-repeat"></i> <?= t('Sincronizar con LibreTranslate') ?>
</button> </button>
</form> </form>
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#languageModal"> <button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#languageModal">
<i class="bi bi-plus-circle"></i> Nuevo Idioma <i class="bi bi-plus-circle"></i> <?= t('Nuevo Idioma') ?>
</button> </button>
</div> </div>
</div> </div>
@@ -290,11 +306,13 @@ require_once __DIR__ . '/../templates/header.php';
<table class="table table-hover"> <table class="table table-hover">
<thead> <thead>
<tr> <tr>
<th>Bandera</th> <th><?= t('Bandera') ?></th>
<th>Código</th> <th><?= t('Código') ?></th>
<th>Nombre</th> <th><?= t('Nombre') ?></th>
<th>Estado</th> <th><?= t('Estado') ?></th>
<th>Acciones</th> <th><?= t('Telegram') ?></th>
<th><?= t('Discord') ?></th>
<th><?= t('Acciones') ?></th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@@ -305,9 +323,35 @@ require_once __DIR__ . '/../templates/header.php';
<td><?= htmlspecialchars($lang['language_name']) ?></td> <td><?= htmlspecialchars($lang['language_name']) ?></td>
<td> <td>
<?php if ($lang['is_active']): ?> <?php if ($lang['is_active']): ?>
<span class="badge bg-success">Activo</span> <span class="badge bg-success"><?= t('Activo') ?></span>
<?php else: ?> <?php else: ?>
<span class="badge bg-secondary">Inactivo</span> <span class="badge bg-secondary"><?= t('Inactivo') ?></span>
<?php endif; ?>
</td>
<td>
<?php if ($lang['is_active']): ?>
<form method="POST" class="d-inline">
<input type="hidden" name="action" value="toggle_telegram">
<input type="hidden" name="id" value="<?= $lang['id'] ?>">
<button type="submit" class="btn btn-sm btn-outline-<?= $lang['telegram_enabled'] ? 'info' : 'secondary' ?>">
<i class="bi bi-<?= $lang['telegram_enabled'] ? 'check-circle-fill' : 'circle' ?>"></i>
</button>
</form>
<?php else: ?>
<span class="text-muted"><i class="bi bi-circle"></i></span>
<?php endif; ?>
</td>
<td>
<?php if ($lang['is_active']): ?>
<form method="POST" class="d-inline">
<input type="hidden" name="action" value="toggle_discord">
<input type="hidden" name="id" value="<?= $lang['id'] ?>">
<button type="submit" class="btn btn-sm btn-outline-<?= $lang['discord_enabled'] ? 'primary' : 'secondary' ?>">
<i class="bi bi-<?= $lang['discord_enabled'] ? 'check-circle-fill' : 'circle' ?>"></i>
</button>
</form>
<?php else: ?>
<span class="text-muted"><i class="bi bi-circle"></i></span>
<?php endif; ?> <?php endif; ?>
</td> </td>
<td> <td>
@@ -320,7 +364,7 @@ require_once __DIR__ . '/../templates/header.php';
</form> </form>
<button class="btn btn-sm btn-outline-primary" data-bs-toggle="modal" data-bs-target="#flagModal<?= $lang['id'] ?>"> <button class="btn btn-sm btn-outline-primary" data-bs-toggle="modal" data-bs-target="#flagModal<?= $lang['id'] ?>">
<i class="bi bi-flag"></i> Cambiar <i class="bi bi-flag"></i> <?= t('Cambiar') ?>
</button> </button>
</td> </td>
</tr> </tr>
@@ -333,17 +377,17 @@ require_once __DIR__ . '/../templates/header.php';
<input type="hidden" name="action" value="update_flag"> <input type="hidden" name="action" value="update_flag">
<input type="hidden" name="id" value="<?= $lang['id'] ?>"> <input type="hidden" name="id" value="<?= $lang['id'] ?>">
<div class="modal-header"> <div class="modal-header">
<h5 class="modal-title">Seleccionar Bandera - <?= htmlspecialchars($lang['language_name']) ?></h5> <h5 class="modal-title"><?= t('Seleccionar Bandera') ?> - <?= htmlspecialchars($lang['language_name']) ?></h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button> <button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<div class="mb-3"> <div class="mb-3">
<label class="form-label">Bandera actual</label> <label class="form-label"><?= t('Bandera actual') ?></label>
<div class="display-4"><?= htmlspecialchars($lang['flag_emoji']) ?></div> <div class="display-4"><?= htmlspecialchars($lang['flag_emoji']) ?></div>
</div> </div>
<div class="mb-3"> <div class="mb-3">
<label class="form-label">Seleccionar nueva bandera</label> <label class="form-label"><?= t('Seleccionar nueva bandera') ?></label>
<div class="flag-selector" style="max-height: 400px; overflow-y: auto;"> <div class="flag-selector" style="max-height: 400px; overflow-y: auto;">
<div class="row g-2"> <div class="row g-2">
<?php foreach ($availableFlags as $flag): ?> <?php foreach ($availableFlags as $flag): ?>
@@ -360,14 +404,14 @@ require_once __DIR__ . '/../templates/header.php';
</div> </div>
<div class="mt-3"> <div class="mt-3">
<label class="form-label">O escribir emoji manualmente</label> <label class="form-label"><?= t('O escribir emoji manualmente') ?></label>
<input type="text" name="flag_emoji_custom" id="customFlag<?= $lang['id'] ?>" class="form-control" value="<?= htmlspecialchars($lang['flag_emoji']) ?>" maxlength="10" placeholder="🇲🇽"> <input type="text" name="flag_emoji_custom" id="customFlag<?= $lang['id'] ?>" class="form-control" value="<?= htmlspecialchars($lang['flag_emoji']) ?>" maxlength="10" placeholder="🇲🇽">
<small class="text-muted">Puedes copiar y pegar cualquier emoji de bandera aquí</small> <small class="text-muted"><?= t('Puedes copiar y pegar cualquier emoji de bandera aquí') ?></small>
</div> </div>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancelar</button> <button type="button" class="btn btn-secondary" data-bs-dismiss="modal"><?= t('Cancelar') ?></button>
<button type="submit" class="btn btn-primary">Guardar</button> <button type="submit" class="btn btn-primary"><?= t('Guardar') ?></button>
</div> </div>
</form> </form>
</div> </div>
@@ -395,20 +439,20 @@ require_once __DIR__ . '/../templates/header.php';
<form method="POST"> <form method="POST">
<input type="hidden" name="action" value="add"> <input type="hidden" name="action" value="add">
<div class="modal-header"> <div class="modal-header">
<h5 class="modal-title">Nuevo Idioma</h5> <h5 class="modal-title"><?= t('Nuevo Idioma') ?></h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button> <button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<div class="mb-3"> <div class="mb-3">
<label class="form-label">Código de idioma (ej: ca, gl)</label> <label class="form-label"><?= t('Código de idioma') ?> (ej: ca, gl)</label>
<input type="text" name="language_code" class="form-control" required maxlength="10"> <input type="text" name="language_code" class="form-control" required maxlength="10">
</div> </div>
<div class="mb-3"> <div class="mb-3">
<label class="form-label">Nombre del idioma</label> <label class="form-label"><?= t('Nombre del idioma') ?></label>
<input type="text" name="language_name" class="form-control" required> <input type="text" name="language_name" class="form-control" required>
</div> </div>
<div class="mb-3"> <div class="mb-3">
<label class="form-label">Seleccionar bandera</label> <label class="form-label"><?= t('Seleccionar bandera') ?></label>
<div class="flag-selector-new" style="max-height: 300px; overflow-y: auto;"> <div class="flag-selector-new" style="max-height: 300px; overflow-y: auto;">
<div class="row g-2"> <div class="row g-2">
<?php foreach ($availableFlags as $flag): ?> <?php foreach ($availableFlags as $flag): ?>
@@ -424,12 +468,12 @@ require_once __DIR__ . '/../templates/header.php';
</div> </div>
</div> </div>
<div class="mt-3"> <div class="mt-3">
<label class="form-label">O escribir emoji manualmente</label> <label class="form-label"><?= t('O escribir emoji manualmente') ?></label>
<input type="text" id="newFlagInput" name="flag_emoji" class="form-control" maxlength="10" placeholder="🇲🇽"> <input type="text" id="newFlagInput" name="flag_emoji" class="form-control" maxlength="10" placeholder="🇲🇽">
</div> </div>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button type="submit" class="btn btn-primary">Agregar</button> <button type="submit" class="btn btn-primary"><?= t('Agregar') ?></button>
</div> </div>
</form> </form>
</div> </div>

View File

@@ -69,9 +69,9 @@ require_once __DIR__ . '/../templates/header.php';
?> ?>
<div class="d-flex justify-content-between align-items-center mb-4"> <div class="d-flex justify-content-between align-items-center mb-4">
<h2><i class="bi bi-person-check"></i> Gestión de Destinatarios</h2> <h2><i class="bi bi-person-check"></i> <?= t('Gestión de Destinatarios') ?></h2>
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addRecipientModal"> <button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addRecipientModal">
<i class="bi bi-plus-circle"></i> Nuevo Destinatario <i class="bi bi-plus-circle"></i> <?= t('Nuevo Destinatario') ?>
</button> </button>
</div> </div>
@@ -94,19 +94,19 @@ require_once __DIR__ . '/../templates/header.php';
<div class="card-body"> <div class="card-body">
<?php $discordRecipients = array_filter($recipients, fn($r) => $r['platform'] === 'discord'); ?> <?php $discordRecipients = array_filter($recipients, fn($r) => $r['platform'] === 'discord'); ?>
<?php if (empty($discordRecipients)): ?> <?php if (empty($discordRecipients)): ?>
<p class="text-muted">No hay destinatarios de Discord</p> <p class="text-muted"><?= t('No hay destinatarios de Discord') ?></p>
<?php else: ?> <?php else: ?>
<div class="table-responsive"> <div class="table-responsive">
<table class="table table-hover"> <table class="table table-hover">
<thead> <thead>
<tr> <tr>
<th>ID</th> <th><?= t('ID') ?></th>
<th>Platform ID</th> <th>Platform ID</th>
<th>Nombre</th> <th><?= t('Nombre') ?></th>
<th>Tipo</th> <th><?= t('Tipo') ?></th>
<th>Idioma</th> <th><?= t('Idioma') ?></th>
<th>Creado</th> <th><?= t('Creado') ?></th>
<th>Acciones</th> <th><?= t('Acciones') ?></th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@@ -115,7 +115,7 @@ require_once __DIR__ . '/../templates/header.php';
<td><?= $recipient['id'] ?></td> <td><?= $recipient['id'] ?></td>
<td><code><?= $recipient['platform_id'] ?></code></td> <td><code><?= $recipient['platform_id'] ?></code></td>
<td><?= htmlspecialchars($recipient['name']) ?></td> <td><?= htmlspecialchars($recipient['name']) ?></td>
<td><?= $recipient['type'] ?></td> <td><?= t($recipient['type'] === 'channel' ? 'Canal' : 'Usuario') ?></td>
<td> <td>
<form method="POST" class="d-inline"> <form method="POST" class="d-inline">
<input type="hidden" name="action" value="update_language"> <input type="hidden" name="action" value="update_language">
@@ -131,7 +131,7 @@ require_once __DIR__ . '/../templates/header.php';
</td> </td>
<td><?= date('d/m/Y', strtotime($recipient['created_at'])) ?></td> <td><?= date('d/m/Y', strtotime($recipient['created_at'])) ?></td>
<td> <td>
<form method="POST" onsubmit="return confirm('¿Eliminar?');" class="d-inline"> <form method="POST" onsubmit="return confirm('<?= t('¿Eliminar?') ?>');" class="d-inline">
<input type="hidden" name="action" value="delete"> <input type="hidden" name="action" value="delete">
<input type="hidden" name="recipient_id" value="<?= $recipient['id'] ?>"> <input type="hidden" name="recipient_id" value="<?= $recipient['id'] ?>">
<button type="submit" class="btn btn-outline-danger btn-sm"> <button type="submit" class="btn btn-outline-danger btn-sm">
@@ -154,19 +154,19 @@ require_once __DIR__ . '/../templates/header.php';
<div class="card-body"> <div class="card-body">
<?php $telegramRecipients = array_filter($recipients, fn($r) => $r['platform'] === 'telegram'); ?> <?php $telegramRecipients = array_filter($recipients, fn($r) => $r['platform'] === 'telegram'); ?>
<?php if (empty($telegramRecipients)): ?> <?php if (empty($telegramRecipients)): ?>
<p class="text-muted">No hay destinatarios de Telegram</p> <p class="text-muted"><?= t('No hay destinatarios de Telegram') ?></p>
<?php else: ?> <?php else: ?>
<div class="table-responsive"> <div class="table-responsive">
<table class="table table-hover"> <table class="table table-hover">
<thead> <thead>
<tr> <tr>
<th>ID</th> <th><?= t('ID') ?></th>
<th>Platform ID</th> <th>Platform ID</th>
<th>Nombre</th> <th><?= t('Nombre') ?></th>
<th>Tipo</th> <th><?= t('Tipo') ?></th>
<th>Idioma</th> <th><?= t('Idioma') ?></th>
<th>Creado</th> <th><?= t('Creado') ?></th>
<th>Acciones</th> <th><?= t('Acciones') ?></th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@@ -175,7 +175,7 @@ require_once __DIR__ . '/../templates/header.php';
<td><?= $recipient['id'] ?></td> <td><?= $recipient['id'] ?></td>
<td><code><?= $recipient['platform_id'] ?></code></td> <td><code><?= $recipient['platform_id'] ?></code></td>
<td><?= htmlspecialchars($recipient['name']) ?></td> <td><?= htmlspecialchars($recipient['name']) ?></td>
<td><?= $recipient['type'] ?></td> <td><?= t($recipient['type'] === 'channel' ? 'Canal' : 'Usuario') ?></td>
<td> <td>
<form method="POST" class="d-inline"> <form method="POST" class="d-inline">
<input type="hidden" name="action" value="update_language"> <input type="hidden" name="action" value="update_language">
@@ -191,7 +191,7 @@ require_once __DIR__ . '/../templates/header.php';
</td> </td>
<td><?= date('d/m/Y', strtotime($recipient['created_at'])) ?></td> <td><?= date('d/m/Y', strtotime($recipient['created_at'])) ?></td>
<td> <td>
<form method="POST" onsubmit="return confirm('¿Eliminar?');" class="d-inline"> <form method="POST" onsubmit="return confirm('<?= t('¿Eliminar?') ?>');" class="d-inline">
<input type="hidden" name="action" value="delete"> <input type="hidden" name="action" value="delete">
<input type="hidden" name="recipient_id" value="<?= $recipient['id'] ?>"> <input type="hidden" name="recipient_id" value="<?= $recipient['id'] ?>">
<button type="submit" class="btn btn-outline-danger btn-sm"> <button type="submit" class="btn btn-outline-danger btn-sm">
@@ -218,13 +218,13 @@ require_once __DIR__ . '/../templates/header.php';
<input type="hidden" name="action" value="add"> <input type="hidden" name="action" value="add">
<div class="modal-header"> <div class="modal-header">
<h5 class="modal-title">Nuevo Destinatario</h5> <h5 class="modal-title"><?= t('Nuevo Destinatario') ?></h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button> <button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<div class="mb-3"> <div class="mb-3">
<label class="form-label">Plataforma</label> <label class="form-label"><?= t('Plataforma') ?></label>
<select name="platform" class="form-select" required> <select name="platform" class="form-select" required>
<option value="discord">Discord</option> <option value="discord">Discord</option>
<option value="telegram">Telegram</option> <option value="telegram">Telegram</option>
@@ -232,26 +232,26 @@ require_once __DIR__ . '/../templates/header.php';
</div> </div>
<div class="mb-3"> <div class="mb-3">
<label class="form-label">ID en la plataforma</label> <label class="form-label"><?= t('ID en la plataforma') ?></label>
<input type="text" name="platform_id" class="form-control" required placeholder="Ej: 123456789"> <input type="text" name="platform_id" class="form-control" required placeholder="<?= t('Ej') ?>: 123456789">
<small class="text-muted">ID del canal/usuario en Discord o Telegram</small> <small class="text-muted"><?= t('ID del canal/usuario en Discord o Telegram') ?></small>
</div> </div>
<div class="mb-3"> <div class="mb-3">
<label class="form-label">Nombre</label> <label class="form-label"><?= t('Nombre') ?></label>
<input type="text" name="name" class="form-control" required> <input type="text" name="name" class="form-control" required>
</div> </div>
<div class="mb-3"> <div class="mb-3">
<label class="form-label">Tipo</label> <label class="form-label"><?= t('Tipo') ?></label>
<select name="type" class="form-select" required> <select name="type" class="form-select" required>
<option value="channel">Canal</option> <option value="channel"><?= t('Canal') ?></option>
<option value="user">Usuario</option> <option value="user"><?= t('Usuario') ?></option>
</select> </select>
</div> </div>
<div class="mb-3"> <div class="mb-3">
<label class="form-label">Idioma</label> <label class="form-label"><?= t('Idioma') ?></label>
<select name="language_code" class="form-select"> <select name="language_code" class="form-select">
<?php foreach ($languages as $lang): ?> <?php foreach ($languages as $lang): ?>
<option value="<?= $lang['language_code'] ?>"> <option value="<?= $lang['language_code'] ?>">
@@ -263,8 +263,8 @@ require_once __DIR__ . '/../templates/header.php';
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancelar</button> <button type="button" class="btn btn-secondary" data-bs-dismiss="modal"><?= t('Cancelar') ?></button>
<button type="submit" class="btn btn-primary">Agregar</button> <button type="submit" class="btn btn-primary"><?= t('Agregar') ?></button>
</div> </div>
</form> </form>
</div> </div>

View File

@@ -82,7 +82,7 @@ require_once __DIR__ . '/../templates/header.php';
?> ?>
<div class="d-flex justify-content-between align-items-center mb-4"> <div class="d-flex justify-content-between align-items-center mb-4">
<h2><i class="bi bi-discord"></i> Test de Conexión Discord</h2> <h2><i class="bi bi-discord"></i> <?= t('Test de Conexión Discord') ?></h2>
</div> </div>
<?php if ($error): ?> <?php if ($error): ?>
@@ -92,11 +92,11 @@ require_once __DIR__ . '/../templates/header.php';
<?php if (!empty($results)): ?> <?php if (!empty($results)): ?>
<?php if ($results['http_code'] === 200): ?> <?php if ($results['http_code'] === 200): ?>
<div class="alert alert-success"> <div class="alert alert-success">
<i class="bi bi-check-circle"></i> <strong>Conexión exitosa!</strong> El bot está conectado como <strong><?= htmlspecialchars($results['user']['username']) ?></strong> <i class="bi bi-check-circle"></i> <strong><?= t('Conexión exitosa') ?>!</strong> <?= t('El bot está conectado como') ?> <strong><?= htmlspecialchars($results['user']['username']) ?></strong>
</div> </div>
<?php else: ?> <?php else: ?>
<div class="alert alert-danger"> <div class="alert alert-danger">
<i class="bi bi-x-circle"></i> <strong>Error de conexión:</strong> Código HTTP <?= $results['http_code'] ?> <i class="bi bi-x-circle"></i> <strong><?= t('Error de conexión') ?>:</strong> <?= t('Código HTTP') ?> <?= $results['http_code'] ?>
<?php if (isset($results['user']['message'])): ?> <?php if (isset($results['user']['message'])): ?>
<br><?= htmlspecialchars($results['user']['message']) ?> <br><?= htmlspecialchars($results['user']['message']) ?>
<?php endif; ?> <?php endif; ?>
@@ -105,16 +105,16 @@ require_once __DIR__ . '/../templates/header.php';
<?php if (isset($results['guild'])): ?> <?php if (isset($results['guild'])): ?>
<div class="card border-0 shadow-sm mb-3"> <div class="card border-0 shadow-sm mb-3">
<div class="card-header bg-white border-0"> <div class="card-header border-0">
<h5 class="mb-0">Servidor</h5> <h5 class="mb-0"><?= t('Servidor') ?></h5>
</div> </div>
<div class="card-body"> <div class="card-body">
<?php if ($results['guild_code'] === 200): ?> <?php if ($results['guild_code'] === 200): ?>
<p><strong>Nombre:</strong> <?= htmlspecialchars($results['guild']['name']) ?></p> <p><strong><?= t('Nombre') ?>:</strong> <?= htmlspecialchars($results['guild']['name']) ?></p>
<p><strong>ID:</strong> <?= htmlspecialchars($results['guild']['id']) ?></p> <p><strong>ID:</strong> <?= htmlspecialchars($results['guild']['id']) ?></p>
<p><strong>Miembros:</strong> <?= $results['guild']['approximate_member_count'] ?? 'N/A' ?></p> <p><strong><?= t('Miembros') ?>:</strong> <?= $results['guild']['approximate_member_count'] ?? 'N/A' ?></p>
<?php else: ?> <?php else: ?>
<div class="alert alert-warning">Error al obtener servidor: Código <?= $results['guild_code'] ?></div> <div class="alert alert-warning"><?= t('Error al obtener servidor') ?>: <?= t('Código') ?> <?= $results['guild_code'] ?></div>
<?php endif; ?> <?php endif; ?>
</div> </div>
</div> </div>
@@ -122,14 +122,14 @@ require_once __DIR__ . '/../templates/header.php';
<?php if (isset($results['test_message'])): ?> <?php if (isset($results['test_message'])): ?>
<div class="card border-0 shadow-sm mb-3"> <div class="card border-0 shadow-sm mb-3">
<div class="card-header bg-white border-0"> <div class="card-header border-0">
<h5 class="mb-0">Prueba de Envío</h5> <h5 class="mb-0"><?= t('Prueba de Envío') ?></h5>
</div> </div>
<div class="card-body"> <div class="card-body">
<?php if ($results['test_message_code'] === 200): ?> <?php if ($results['test_message_code'] === 200): ?>
<div class="alert alert-success"><i class="bi bi-check-circle"></i> Mensaje enviado correctamente</div> <div class="alert alert-success"><i class="bi bi-check-circle"></i> <?= t('Mensaje enviado correctamente') ?></div>
<?php else: ?> <?php else: ?>
<div class="alert alert-danger">Error al enviar mensaje: Código <?= $results['test_message_code'] ?></div> <div class="alert alert-danger"><?= t('Error al enviar mensaje') ?>: <?= t('Código') ?> <?= $results['test_message_code'] ?></div>
<?php endif; ?> <?php endif; ?>
</div> </div>
</div> </div>
@@ -137,22 +137,22 @@ require_once __DIR__ . '/../templates/header.php';
<?php endif; ?> <?php endif; ?>
<div class="card border-0 shadow-sm mb-4"> <div class="card border-0 shadow-sm mb-4">
<div class="card-header bg-white border-0"> <div class="card-header border-0">
<h5 class="mb-0">Probar Conexión</h5> <h5 class="mb-0"><?= t('Probar Conexión') ?></h5>
</div> </div>
<div class="card-body"> <div class="card-body">
<form method="POST"> <form method="POST">
<input type="hidden" name="action" value="test"> <input type="hidden" name="action" value="test">
<button type="submit" class="btn btn-primary"> <button type="submit" class="btn btn-primary">
<i class="bi bi-plug"></i> Verificar Conexión <i class="bi bi-plug"></i> <?= t('Verificar Conexión') ?>
</button> </button>
</form> </form>
</div> </div>
</div> </div>
<div class="card border-0 shadow-sm"> <div class="card border-0 shadow-sm">
<div class="card-header bg-white border-0"> <div class="card-header border-0">
<h5 class="mb-0">Enviar Mensaje de Prueba</h5> <h5 class="mb-0"><?= t('Enviar Mensaje de Prueba') ?></h5>
</div> </div>
<div class="card-body"> <div class="card-body">
<form method="POST"> <form method="POST">
@@ -160,17 +160,17 @@ require_once __DIR__ . '/../templates/header.php';
<input type="hidden" name="test_message" value="1"> <input type="hidden" name="test_message" value="1">
<div class="mb-3"> <div class="mb-3">
<label class="form-label">ID del Canal</label> <label class="form-label"><?= t('ID del Canal') ?></label>
<input type="text" name="test_channel_id" class="form-control" placeholder="123456789012345678"> <input type="text" name="test_channel_id" class="form-control" placeholder="123456789012345678">
</div> </div>
<div class="mb-3"> <div class="mb-3">
<label class="form-label">Mensaje</label> <label class="form-label"><?= t('Mensaje') ?></label>
<textarea name="test_message_text" class="form-control" rows="3">✅ Prueba de conexión desde el sistema de mensajería</textarea> <textarea name="test_message_text" class="form-control" rows="3">✅ <?= t('Prueba de conexión desde el sistema de mensajería') ?></textarea>
</div> </div>
<button type="submit" class="btn btn-success"> <button type="submit" class="btn btn-success">
<i class="bi bi-send"></i> Enviar Mensaje <i class="bi bi-send"></i> <?= t('Enviar Mensaje') ?>
</button> </button>
</form> </form>
</div> </div>

View File

@@ -10,16 +10,19 @@ requireAdmin();
function getTranslationButtons(PDO $pdo, string $text): array function getTranslationButtons(PDO $pdo, string $text): array
{ {
$stmt = $pdo->query("SELECT language_code, flag_emoji FROM supported_languages WHERE is_active = 1"); $stmtTelegram = $pdo->query("SELECT language_code, flag_emoji FROM supported_languages WHERE is_active = 1 AND telegram_enabled = 1");
$languages = $stmt->fetchAll(); $telegramLanguages = $stmtTelegram->fetchAll();
if (count($languages) <= 1) { $stmtDiscord = $pdo->query("SELECT language_code, flag_emoji FROM supported_languages WHERE is_active = 1 AND discord_enabled = 1");
$discordLanguages = $stmtDiscord->fetchAll();
if (count($telegramLanguages) <= 1 && count($discordLanguages) <= 1) {
return []; return [];
} }
return [ return [
'telegram' => buildTelegramTranslationButtons($pdo, $languages, $text), 'telegram' => count($telegramLanguages) > 1 ? buildTelegramTranslationButtons($pdo, $telegramLanguages, $text) : [],
'discord' => buildDiscordTranslationButtons($languages, $text) 'discord' => count($discordLanguages) > 1 ? buildDiscordTranslationButtons($discordLanguages, $text) : []
]; ];
} }
@@ -70,7 +73,7 @@ function buildDiscordTranslationButtons(array $languages, string $text): array
]; ];
} }
$pageTitle = 'Enviar Mensaje Directo'; $pageTitle = t('Enviar Mensaje Directo');
$recipients = []; $recipients = [];
$galleryImages = []; $galleryImages = [];
@@ -140,32 +143,45 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$sender = \Common\Helpers\SenderFactory::create($schedule['platform']); $sender = \Common\Helpers\SenderFactory::create($schedule['platform']);
// Obtener botones de traducción (convertir HTML a texto plano) // Obtener botones de traducción (convertir HTML a texto plano)
$plainText = html_entity_decode(strip_tags($schedule['content']), ENT_QUOTES | ENT_HTML5, 'UTF-8'); $plainText = $schedule['content'];
$plainText = preg_replace('/\s+/', ' ', $plainText); // Normalizar espacios // Marcar donde hay imágenes
$plainText = preg_replace('/<img[^>]+src=["\']([^"\']+)["\'][^>]*>/i', "\n[IMAGEN]\n", $plainText);
// Convertir saltos de párrafo y br a saltos de línea dobles
$plainText = preg_replace('/<\/p>/i', "\n\n", $plainText);
$plainText = preg_replace('/<p[^>]*>/i', '', $plainText);
$plainText = preg_replace('/<br\s*\/?>/i', "\n", $plainText);
// Eliminar HTML restante
$plainText = html_entity_decode(strip_tags($plainText), ENT_QUOTES | ENT_HTML5, 'UTF-8');
// Limpiar espacios múltiples pero preservar saltos de línea (máximo 2)
$plainText = preg_replace('/[ \t]+/', ' ', $plainText);
$plainText = preg_replace('/\n{3,}/', "\n\n", $plainText);
$plainText = trim($plainText);
$translationButtons = getTranslationButtons($pdo, $plainText); $translationButtons = getTranslationButtons($pdo, $plainText);
// Parsear el contenido HTML en segmentos manteniendo el orden // Parsear el contenido HTML en segmentos manteniendo el orden
$segments = $sender->parseContent($schedule['content']); $segments = $sender->parseContent($schedule['content']);
$messageCount = 0; $messageCount = 0;
$totalSegments = count($segments);
$currentSegment = 0;
// Enviar cada segmento en el orden correcto // Enviar cada segmento en el orden correcto
foreach ($segments as $segment) { foreach ($segments as $segment) {
$currentSegment++;
$isLastSegment = ($currentSegment === $totalSegments);
if ($segment['type'] === 'text') { if ($segment['type'] === 'text') {
// Convertir el texto al formato de la plataforma // Convertir el texto al formato de la plataforma
$textContent = \Common\Helpers\ConverterFactory::convert($schedule['platform'], $segment['content']); $textContent = \Common\Helpers\ConverterFactory::convert($schedule['platform'], $segment['content']);
if (!empty(trim($textContent))) { if (!empty(trim($textContent))) {
// Agregar botones de traducción al último segmento de texto
$buttons = null; $buttons = null;
if ($segment === end($segments)) { if ($isLastSegment && $schedule['platform'] !== 'telegram') {
$buttons = $schedule['platform'] === 'telegram' $buttons = $translationButtons['discord'];
? $translationButtons['telegram']
: $translationButtons['discord'];
} }
if ($schedule['platform'] === 'telegram') { if ($schedule['platform'] === 'telegram') {
$sender->sendMessage($schedule['platform_id'], $textContent, $buttons); $sender->sendMessage($schedule['platform_id'], $textContent);
} else { } else {
$sender->sendMessage($schedule['platform_id'], $textContent, null, $buttons); $sender->sendMessage($schedule['platform_id'], $textContent, null, $buttons);
} }
@@ -174,22 +190,43 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
} elseif ($segment['type'] === 'image') { } elseif ($segment['type'] === 'image') {
$imagePath = $segment['src']; $imagePath = $segment['src'];
// Quitar parámetros de URL si los hay $appUrl = $_ENV['APP_URL'] ?? getenv('APP_URL') ?? '';
$imgPath = parse_url($imagePath, PHP_URL_PATH) ?: $imagePath; $baseUrl = rtrim($appUrl, '/');
if (file_exists($imgPath)) { $buttons = null;
// Es un archivo local if ($isLastSegment && $schedule['platform'] !== 'telegram') {
$sender->sendMessageWithAttachments($schedule['platform_id'], '', [$imgPath]); $buttons = $translationButtons['discord'];
$messageCount++; }
} elseif (strpos($imagePath, 'http') === 0) {
// Es una URL remota if ($schedule['platform'] === 'telegram') {
$embed = ['image' => ['url' => $imagePath]]; if (strpos($imagePath, 'http') !== 0) {
$sender->sendMessage($schedule['platform_id'], '', $embed); $imageUrl = $baseUrl . '/' . ltrim($imagePath, '/');
} else {
$imageUrl = $imagePath;
}
$sender->sendPhoto($schedule['platform_id'], $imageUrl);
$messageCount++; $messageCount++;
} else {
$imgPath = parse_url($imagePath, PHP_URL_PATH) ?: $imagePath;
if (file_exists($imgPath)) {
$sender->sendMessageWithImages($schedule['platform_id'], '', [$imgPath], $buttons);
$messageCount++;
} elseif (strpos($imagePath, 'http') === 0) {
$embed = ['image' => ['url' => $imagePath]];
$sender->sendMessage($schedule['platform_id'], '', $embed, $buttons);
$messageCount++;
}
} }
} }
} }
// Enviar botones de traducción en mensaje separado para Telegram
if ($schedule['platform'] === 'telegram' && !empty($translationButtons['telegram'])) {
$translationMessage = "🌐 <b>Traducciones disponibles:</b>\nHaz clic en una bandera para ver la traducción";
$sender->sendMessage($schedule['platform_id'], $translationMessage, $translationButtons['telegram']);
$messageCount++;
}
$stmt = $pdo->prepare(" $stmt = $pdo->prepare("
INSERT INTO sent_messages (schedule_id, recipient_id, platform_message_id, message_count, sent_at) INSERT INTO sent_messages (schedule_id, recipient_id, platform_message_id, message_count, sent_at)
VALUES (?, ?, '', ?, NOW()) VALUES (?, ?, '', ?, NOW())
@@ -218,7 +255,7 @@ require_once __DIR__ . '/templates/header.php';
?> ?>
<div class="d-flex justify-content-between align-items-center mb-4"> <div class="d-flex justify-content-between align-items-center mb-4">
<h2><i class="bi bi-send"></i> Enviar Mensaje Directo</h2> <h2><i class="bi bi-send"></i> <?= t('Enviar Mensaje Directo') ?></h2>
</div> </div>
<?php if ($success): ?> <?php if ($success): ?>
@@ -233,28 +270,28 @@ require_once __DIR__ . '/templates/header.php';
<div class="card border-0 shadow-sm"> <div class="card border-0 shadow-sm">
<div class="card-body"> <div class="card-body">
<div class="mb-3"> <div class="mb-3">
<label class="form-label">Plataforma</label> <label class="form-label"><?= t('Plataforma') ?></label>
<select name="platform" id="platformSelect" class="form-select" required> <select name="platform" id="platformSelect" class="form-select" required>
<option value="">-- Seleccionar --</option> <option value="">-- <?= t('Seleccionar') ?> --</option>
<option value="discord">Discord</option> <option value="discord">Discord</option>
<option value="telegram">Telegram</option> <option value="telegram">Telegram</option>
</select> </select>
</div> </div>
<div class="mb-3"> <div class="mb-3">
<label class="form-label">Destinatario</label> <label class="form-label"><?= t('Destinatario') ?></label>
<select name="recipient_id" id="recipientSelect" class="form-select" required disabled> <select name="recipient_id" id="recipientSelect" class="form-select" required disabled>
<option value="">Selecciona una plataforma primero</option> <option value=""><?= t('Selecciona una plataforma primero') ?></option>
</select> </select>
</div> </div>
<div class="mb-3"> <div class="mb-3">
<label class="form-label">Mensaje</label> <label class="form-label"><?= t('Mensaje') ?></label>
<textarea name="content" id="messageContent" class="form-control" rows="10" required></textarea> <textarea name="content" id="messageContent" class="form-control" rows="10" required></textarea>
</div> </div>
<button type="submit" class="btn btn-primary"> <button type="submit" class="btn btn-primary">
<i class="bi bi-send"></i> Enviar Ahora <i class="bi bi-send"></i> <?= t('Enviar Ahora') ?>
</button> </button>
</div> </div>
</div> </div>
@@ -265,7 +302,7 @@ require_once __DIR__ . '/templates/header.php';
<div class="modal-dialog modal-lg modal-dialog-scrollable"> <div class="modal-dialog modal-lg modal-dialog-scrollable">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
<h5 class="modal-title"><i class="bi bi-images"></i> Galería de Imágenes</h5> <h5 class="modal-title"><i class="bi bi-images"></i> <?= t('Galería de Imágenes') ?></h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button> <button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div> </div>
<div class="modal-body"> <div class="modal-body">
@@ -273,7 +310,7 @@ require_once __DIR__ . '/templates/header.php';
<?php if (empty($galleryImages)): ?> <?php if (empty($galleryImages)): ?>
<div class="col-12 text-center text-muted py-5"> <div class="col-12 text-center text-muted py-5">
<i class="bi bi-images" style="font-size: 3rem;"></i> <i class="bi bi-images" style="font-size: 3rem;"></i>
<p class="mt-3">No hay imágenes en la galería</p> <p class="mt-3"><?= t('No hay imágenes en la galería') ?></p>
</div> </div>
<?php else: ?> <?php else: ?>
<?php foreach ($galleryImages as $image): ?> <?php foreach ($galleryImages as $image): ?>

View File

@@ -41,7 +41,7 @@ require_once __DIR__ . '/templates/header.php';
?> ?>
<div class="d-flex justify-content-between align-items-center mb-4"> <div class="d-flex justify-content-between align-items-center mb-4">
<h2><i class="bi bi-telegram"></i> Chat Telegram</h2> <h2><i class="bi bi-telegram"></i> <?= t('Chat Telegram') ?></h2>
</div> </div>
<?php if (isset($error)): ?> <?php if (isset($error)): ?>
@@ -51,26 +51,26 @@ require_once __DIR__ . '/templates/header.php';
<div class="row"> <div class="row">
<div class="col-md-4"> <div class="col-md-4">
<div class="card border-0 shadow-sm"> <div class="card border-0 shadow-sm">
<div class="card-header bg-white border-0"> <div class="card-header border-0">
<h5 class="mb-0">Usuarios</h5> <h5 class="mb-0"><?= t('Usuarios') ?></h5>
</div> </div>
<div class="card-body p-0"> <div class="card-body p-0">
<?php if (empty($interactions)): ?> <?php if (empty($interactions)): ?>
<p class="text-muted p-3">No hay interacciones</p> <p class="text-muted p-3"><?= t('No hay interacciones') ?></p>
<?php else: ?> <?php else: ?>
<div class="list-group list-group-flush"> <div class="list-group list-group-flush">
<?php if ($selectedUser): ?> <?php if ($selectedUser): ?>
<a href="chat_telegram.php" class="list-group-item list-group-item-action"> <a href="chat_telegram.php" class="list-group-item list-group-item-action">
<i class="bi bi-arrow-left"></i> Volver a lista <i class="bi bi-arrow-left"></i> <?= t('Volver a lista') ?>
</a> </a>
<?php else: ?> <?php else: ?>
<?php foreach ($interactions as $user): ?> <?php foreach ($interactions as $user): ?>
<a href="chat_telegram.php?user_id=<?= $user['user_id'] ?>" class="list-group-item list-group-item-action"> <a href="chat_telegram.php?user_id=<?= $user['user_id'] ?>" class="list-group-item list-group-item-action">
<div class="d-flex w-100 justify-content-between"> <div class="d-flex w-100 justify-content-between">
<h6 class="mb-1"><?= htmlspecialchars($user['first_name'] ?? 'Usuario') ?></h6> <h6 class="mb-1"><?= htmlspecialchars($user['first_name'] ?? t('Usuario')) ?></h6>
<small><?= $user['total_interactions'] ?></small> <small><?= $user['total_interactions'] ?></small>
</div> </div>
<small class="text-muted">@<?= htmlspecialchars($user['username'] ?? 'sin username') ?></small> <small class="text-muted">@<?= htmlspecialchars($user['username'] ?? t('sin username')) ?></small>
</a> </a>
<?php endforeach; ?> <?php endforeach; ?>
<?php endif; ?> <?php endif; ?>
@@ -82,8 +82,8 @@ require_once __DIR__ . '/templates/header.php';
<div class="col-md-8"> <div class="col-md-8">
<div class="card border-0 shadow-sm"> <div class="card border-0 shadow-sm">
<div class="card-header bg-white border-0"> <div class="card-header border-0">
<h5 class="mb-0">Historial de Mensajes</h5> <h5 class="mb-0"><?= t('Historial de Mensajes') ?></h5>
</div> </div>
<div class="card-body" style="max-height: 500px; overflow-y: auto;"> <div class="card-body" style="max-height: 500px; overflow-y: auto;">
<?php if ($selectedUser && !empty($interactions)): ?> <?php if ($selectedUser && !empty($interactions)): ?>
@@ -91,13 +91,13 @@ require_once __DIR__ . '/templates/header.php';
<div class="mb-3 p-2 <?= $msg['interaction_type'] === 'in' ? 'bg-light' : 'bg-white' ?> rounded"> <div class="mb-3 p-2 <?= $msg['interaction_type'] === 'in' ? 'bg-light' : 'bg-white' ?> rounded">
<small class="text-muted"> <small class="text-muted">
<?= date('d/m/Y H:i:s', strtotime($msg['interaction_date'])) ?> <?= date('d/m/Y H:i:s', strtotime($msg['interaction_date'])) ?>
- <?= $msg['interaction_type'] === 'in' ? '📥 Usuario' : '📤 Bot' ?> - <?= $msg['interaction_type'] === 'in' ? '📥 ' . t('Usuario') : '📤 Bot' ?>
</small> </small>
<p class="mb-0 mt-1"><?= htmlspecialchars($msg['interaction_type'] ?? '') ?></p> <p class="mb-0 mt-1"><?= htmlspecialchars($msg['interaction_type'] ?? '') ?></p>
</div> </div>
<?php endforeach; ?> <?php endforeach; ?>
<?php else: ?> <?php else: ?>
<p class="text-muted text-center">Selecciona un usuario para ver el historial</p> <p class="text-muted text-center"><?= t('Selecciona un usuario para ver el historial') ?></p>
<?php endif; ?> <?php endif; ?>
</div> </div>
</div> </div>

View File

@@ -10,16 +10,19 @@ require_once __DIR__ . '/includes/i18n.php';
function getTranslationButtons(PDO $pdo, string $text): array function getTranslationButtons(PDO $pdo, string $text): array
{ {
$stmt = $pdo->query("SELECT language_code, flag_emoji FROM supported_languages WHERE is_active = 1"); $stmtTelegram = $pdo->query("SELECT language_code, flag_emoji FROM supported_languages WHERE is_active = 1 AND telegram_enabled = 1");
$languages = $stmt->fetchAll(); $telegramLanguages = $stmtTelegram->fetchAll();
if (count($languages) <= 1) { $stmtDiscord = $pdo->query("SELECT language_code, flag_emoji FROM supported_languages WHERE is_active = 1 AND discord_enabled = 1");
$discordLanguages = $stmtDiscord->fetchAll();
if (count($telegramLanguages) <= 1 && count($discordLanguages) <= 1) {
return []; return [];
} }
return [ return [
'telegram' => buildTelegramTranslationButtons($pdo, $languages, $text), 'telegram' => count($telegramLanguages) > 1 ? buildTelegramTranslationButtons($pdo, $telegramLanguages, $text) : [],
'discord' => buildDiscordTranslationButtons($languages, $text) 'discord' => count($discordLanguages) > 1 ? buildDiscordTranslationButtons($discordLanguages, $text) : []
]; ];
} }
@@ -127,35 +130,39 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['
// Obtener botones de traducción (convertir HTML a texto plano) // Obtener botones de traducción (convertir HTML a texto plano)
$plainText = $schedule['content']; $plainText = $schedule['content'];
// Convertir saltos de párrafo a saltos de línea // Marcar donde hay imágenes
$plainText = preg_replace('/<\/p>/i', "\n", $plainText); $plainText = preg_replace('/<img[^>]+src=["\']([^"\']+)["\'][^>]*>/i', "\n[IMAGEN]\n", $plainText);
// Convertir saltos de párrafo y br a saltos de línea dobles
$plainText = preg_replace('/<\/p>/i', "\n\n", $plainText);
$plainText = preg_replace('/<p[^>]*>/i', '', $plainText); $plainText = preg_replace('/<p[^>]*>/i', '', $plainText);
$plainText = preg_replace('/<br\s*\/?>/i', "\n", $plainText); $plainText = preg_replace('/<br\s*\/?>/i', "\n", $plainText);
// Eliminar HTML // Eliminar HTML restante
$plainText = html_entity_decode(strip_tags($plainText), ENT_QUOTES | ENT_HTML5, 'UTF-8'); $plainText = html_entity_decode(strip_tags($plainText), ENT_QUOTES | ENT_HTML5, 'UTF-8');
// Limpiar espacios múltiples pero preservar saltos de línea // Limpiar espacios múltiples pero preservar saltos de línea (máximo 2)
$plainText = preg_replace('/[ \t]+/', ' ', $plainText); $plainText = preg_replace('/[ \t]+/', ' ', $plainText);
$plainText = preg_replace('/\n\s*\n/', "\n", $plainText); $plainText = preg_replace('/\n{3,}/', "\n\n", $plainText);
$plainText = trim($plainText); $plainText = trim($plainText);
$translationButtons = getTranslationButtons($pdo, $plainText); $translationButtons = getTranslationButtons($pdo, $plainText);
$segments = $sender->parseContent($schedule['content']); $segments = $sender->parseContent($schedule['content']);
$messageCount = 0; $messageCount = 0;
$totalSegments = count($segments);
$currentSegment = 0;
foreach ($segments as $segment) { foreach ($segments as $segment) {
$currentSegment++;
$isLastSegment = ($currentSegment === $totalSegments);
if ($segment['type'] === 'text') { if ($segment['type'] === 'text') {
$textContent = \Common\Helpers\ConverterFactory::convert($schedule['platform'], $segment['content']); $textContent = \Common\Helpers\ConverterFactory::convert($schedule['platform'], $segment['content']);
if (!empty(trim($textContent))) { if (!empty(trim($textContent))) {
// Agregar botones de traducción al último segmento de texto
$buttons = null; $buttons = null;
if ($segment === end($segments)) { if ($isLastSegment && $schedule['platform'] !== 'telegram') {
$buttons = $schedule['platform'] === 'telegram' $buttons = $translationButtons['discord'];
? $translationButtons['telegram']
: $translationButtons['discord'];
} }
if ($schedule['platform'] === 'telegram') { if ($schedule['platform'] === 'telegram') {
$sender->sendMessage($schedule['platform_id'], $textContent, $buttons); $sender->sendMessage($schedule['platform_id'], $textContent);
} else { } else {
$sender->sendMessage($schedule['platform_id'], $textContent, null, $buttons); $sender->sendMessage($schedule['platform_id'], $textContent, null, $buttons);
} }
@@ -163,19 +170,44 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['
} }
} elseif ($segment['type'] === 'image') { } elseif ($segment['type'] === 'image') {
$imagePath = $segment['src']; $imagePath = $segment['src'];
$imgPath = parse_url($imagePath, PHP_URL_PATH) ?: $imagePath;
if (file_exists($imgPath)) { $appUrl = $_ENV['APP_URL'] ?? getenv('APP_URL') ?? '';
$sender->sendMessageWithAttachments($schedule['platform_id'], '', [$imgPath]); $baseUrl = rtrim($appUrl, '/');
$messageCount++;
} elseif (strpos($imagePath, 'http') === 0) { $buttons = null;
$embed = ['image' => ['url' => $imagePath]]; if ($isLastSegment && $schedule['platform'] !== 'telegram') {
$sender->sendMessage($schedule['platform_id'], '', $embed); $buttons = $translationButtons['discord'];
}
if ($schedule['platform'] === 'telegram') {
if (strpos($imagePath, 'http') !== 0) {
$imageUrl = $baseUrl . '/' . ltrim($imagePath, '/');
} else {
$imageUrl = $imagePath;
}
$sender->sendPhoto($schedule['platform_id'], $imageUrl);
$messageCount++; $messageCount++;
} else {
$imgPath = parse_url($imagePath, PHP_URL_PATH) ?: $imagePath;
if (file_exists($imgPath)) {
$sender->sendMessageWithImages($schedule['platform_id'], '', [$imgPath], $buttons);
$messageCount++;
} elseif (strpos($imagePath, 'http') === 0) {
$embed = ['image' => ['url' => $imagePath]];
$sender->sendMessage($schedule['platform_id'], '', $embed, $buttons);
$messageCount++;
}
} }
} }
} }
// Enviar botones de traducción en mensaje separado para Telegram
if ($schedule['platform'] === 'telegram' && !empty($translationButtons['telegram'])) {
$translationMessage = "🌐 <b>Traducciones disponibles:</b>\nHaz clic en una bandera para ver la traducción";
$sender->sendMessage($schedule['platform_id'], $translationMessage, $translationButtons['telegram']);
$messageCount++;
}
$stmt = $pdo->prepare(" $stmt = $pdo->prepare("
INSERT INTO sent_messages (schedule_id, recipient_id, platform_message_id, message_count, sent_at) INSERT INTO sent_messages (schedule_id, recipient_id, platform_message_id, message_count, sent_at)
VALUES (?, ?, '', ?, NOW()) VALUES (?, ?, '', ?, NOW())

0
database/.gitkeep Normal file
View File

View File

@@ -34,14 +34,19 @@ $discord->on(Event::GUILD_MEMBER_ADD, function (Member $member, Discord $discord
try { try {
$pdo = getDbConnection(); $pdo = getDbConnection();
// Obtener idioma por defecto dinámico
$stmtDefault = $pdo->query("SELECT language_code FROM supported_languages WHERE is_active = 1 LIMIT 1");
$defaultLang = $stmtDefault->fetchColumn() ?: 'es';
$stmt = $pdo->prepare(" $stmt = $pdo->prepare("
INSERT INTO recipients (platform_id, name, type, platform, language_code, chat_mode) INSERT INTO recipients (platform_id, name, type, platform, language_code, chat_mode)
VALUES (?, ?, 'user', 'discord', 'es', 'agent') VALUES (?, ?, 'user', 'discord', ?, 'agent')
ON DUPLICATE KEY UPDATE name = VALUES(name) ON DUPLICATE KEY UPDATE name = VALUES(name)
"); ");
$stmt->execute([ $stmt->execute([
$member->user->id, $member->user->id,
$member->user->username $member->user->username,
$defaultLang
]); ]);
echo "Usuario registrado en la base de datos" . PHP_EOL; echo "Usuario registrado en la base de datos" . PHP_EOL;
@@ -145,7 +150,7 @@ function handleTemplateCommand(PDO $pdo, Message $message, string $command): voi
function getDiscordTranslationButtons(PDO $pdo, string $text): array function getDiscordTranslationButtons(PDO $pdo, string $text): array
{ {
$stmt = $pdo->query("SELECT language_code, flag_emoji FROM supported_languages WHERE is_active = 1"); $stmt = $pdo->query("SELECT language_code, flag_emoji FROM supported_languages WHERE is_active = 1 AND discord_enabled = 1");
$languages = $stmt->fetchAll(); $languages = $stmt->fetchAll();
if (count($languages) <= 1) { if (count($languages) <= 1) {
@@ -338,10 +343,15 @@ function handleAutoTranslationWithButtons(PDO $pdo, Message $message, string $te
// Detectar idioma del mensaje (sin emojis para mejor precisión) // Detectar idioma del mensaje (sin emojis para mejor precisión)
$textForDetection = stripEmojisForDetection($text); $textForDetection = stripEmojisForDetection($text);
$detectedLang = $translator->detectLanguage($textForDetection) ?? 'es';
// Obtener idioma por defecto dinámico
$stmtDefault = $pdo->query("SELECT language_code FROM supported_languages WHERE is_active = 1 LIMIT 1");
$defaultLang = $stmtDefault->fetchColumn() ?: 'es';
$detectedLang = $translator->detectLanguage($textForDetection) ?? $defaultLang;
// Obtener idiomas activos de la base de datos // Obtener idiomas activos de la base de datos
$stmt = $pdo->query("SELECT language_code, flag_emoji FROM supported_languages WHERE is_active = 1"); $stmt = $pdo->query("SELECT language_code, flag_emoji FROM supported_languages WHERE is_active = 1 AND discord_enabled = 1");
$activeLanguages = $stmt->fetchAll(); $activeLanguages = $stmt->fetchAll();
if (count($activeLanguages) <= 1) { if (count($activeLanguages) <= 1) {
@@ -356,13 +366,11 @@ function handleAutoTranslationWithButtons(PDO $pdo, Message $message, string $te
// Preparar botones // Preparar botones
$buttons = []; $buttons = [];
foreach ($activeLanguages as $lang) { foreach ($activeLanguages as $lang) {
if ($lang['language_code'] !== $detectedLang) { $buttons[] = [
$buttons[] = [ 'label' => $lang['flag_emoji'] . ' ' . strtoupper($lang['language_code']),
'label' => $lang['flag_emoji'] . ' ' . strtoupper($lang['language_code']), 'custom_id' => 'translate_' . $lang['language_code'] . ':' . $textHash,
'custom_id' => 'translate_' . $lang['language_code'] . ':' . $textHash, 'style' => 1
'style' => 1 ];
];
}
} }
// Enviar mensaje con botones usando MessageBuilder correctamente // Enviar mensaje con botones usando MessageBuilder correctamente
@@ -472,14 +480,18 @@ function sendDiscordWelcomeMessage(PDO $pdo, Member $member, Discord $discord):
function registerDiscordUser(PDO $pdo, $user): void function registerDiscordUser(PDO $pdo, $user): void
{ {
// Obtener idioma por defecto de la base de datos (el primero activo)
$stmtDefault = $pdo->query("SELECT language_code FROM supported_languages WHERE is_active = 1 LIMIT 1");
$defaultLang = $stmtDefault->fetchColumn() ?: 'es';
$stmt = $pdo->prepare(" $stmt = $pdo->prepare("
INSERT INTO recipients (platform_id, name, type, platform, language_code, chat_mode) INSERT INTO recipients (platform_id, name, type, platform, language_code, chat_mode)
VALUES (?, ?, 'user', 'discord', 'es', 'agent') VALUES (?, ?, 'user', 'discord', ?, 'agent')
ON DUPLICATE KEY UPDATE name = VALUES(name) ON DUPLICATE KEY UPDATE name = VALUES(name)
"); ");
$name = $user->username ?? 'Usuario'; $name = $user->username ?? 'Usuario';
$stmt->execute([$user->id, $name]); $stmt->execute([$user->id, $name, $defaultLang]);
} }
function sendDiscordWelcomeMessageOnMessage(PDO $pdo, Message $message, string $username): void function sendDiscordWelcomeMessageOnMessage(PDO $pdo, Message $message, string $username): void
@@ -508,13 +520,11 @@ function handleTranslateInteraction($interaction, string $customId): void
$targetLang = str_replace('translate_', '', $parts[0]); $targetLang = str_replace('translate_', '', $parts[0]);
$textHash = $parts[1] ?? ''; $textHash = $parts[1] ?? '';
// Obtener texto de la base de datos primero
$stmt = $pdo->prepare("SELECT original_text FROM translation_cache WHERE text_hash = ?"); $stmt = $pdo->prepare("SELECT original_text FROM translation_cache WHERE text_hash = ?");
$stmt->execute([$textHash]); $stmt->execute([$textHash]);
$row = $stmt->fetch(); $row = $stmt->fetch();
if (!$row || empty($row['original_text'])) { if (!$row || empty($row['original_text'])) {
// Enviar error efímero
$builder = \Discord\Builders\MessageBuilder::new() $builder = \Discord\Builders\MessageBuilder::new()
->setContent('❌ Error: Texto no encontrado'); ->setContent('❌ Error: Texto no encontrado');
$interaction->respondWithMessage($builder, true); $interaction->respondWithMessage($builder, true);
@@ -523,34 +533,24 @@ function handleTranslateInteraction($interaction, string $customId): void
$originalText = $row['original_text']; $originalText = $row['original_text'];
// Responder inmediatamente con "Traduciendo..." try {
$loadingBuilder = \Discord\Builders\MessageBuilder::new() $interaction->acknowledge();
->setContent("⏳ Traduciendo a " . strtoupper($targetLang) . "..."); } catch (\Exception $e) {
$interaction->respondWithMessage($loadingBuilder, true); error_log("Acknowledge error: " . $e->getMessage());
}
// Ahora traducir (esto puede tardar) $textForTranslation = stripEmojisForDetection($originalText);
// Usar texto sin emojis para detectar idioma y traducir con mayor precisión $translated = $translator->translatePartial($textForTranslation, $targetLang);
$textForDetection = stripEmojisForDetection($originalText);
$sourceLang = $translator->detectLanguage($textForDetection) ?? 'es';
$translated = $translator->translate($textForDetection, $sourceLang, $targetLang);
if ($translated) { if ($translated) {
// Limpiar todo el HTML
$translated = strip_tags($translated); $translated = strip_tags($translated);
// Limpiar asteriscos duplicados
$translated = preg_replace('/\*+/', '', $translated); $translated = preg_replace('/\*+/', '', $translated);
// Limpiar comandos con espacios
$translated = preg_replace('/\/(\s+)(\w+)/', '/$2', $translated); $translated = preg_replace('/\/(\s+)(\w+)/', '/$2', $translated);
$translated = preg_replace('/#(\s+)(\w+)/', '#$2', $translated); $translated = preg_replace('/#(\s+)(\w+)/', '#$2', $translated);
// Limpiar saltos de línea extras
$translated = preg_replace('/\n\s*\n/', "\n", $translated); $translated = preg_replace('/\n\s*\n/', "\n", $translated);
// Actualizar la respuesta con la traducción real
// Preservar emojis del original si los hay
$displayText = trim($translated); $displayText = trim($translated);
if (strpos($originalText, '👍') !== false || preg_match('/[\x{1F300}-\x{1F9FF}]/u', $originalText)) { if (strpos($originalText, '👍') !== false || preg_match('/[\x{1F300}-\x{1F9FF}]/u', $originalText)) {
// Si el original tenía emojis, intentar identificarlos
// Para mensajes simples, los emojis generalmente están al inicio o final
$emojiPrefix = ''; $emojiPrefix = '';
$emojiSuffix = ''; $emojiSuffix = '';
if (preg_match('/^([\x{1F300}-\x{1F9FF}]+)\s*/u', $originalText, $match)) { if (preg_match('/^([\x{1F300}-\x{1F9FF}]+)\s*/u', $originalText, $match)) {
@@ -562,25 +562,32 @@ function handleTranslateInteraction($interaction, string $customId): void
$displayText = $emojiPrefix . $displayText . $emojiSuffix; $displayText = $emojiPrefix . $displayText . $emojiSuffix;
} }
$finalBuilder = \Discord\Builders\MessageBuilder::new() $messageText = "🌐 **Traducción (" . strtoupper($targetLang) . "):**\n\n" . $displayText;
->setContent("🌐 **Traducción (" . strtoupper($targetLang) . "):**\n\n" . $displayText);
$interaction->updateOriginalResponse($finalBuilder); $buttons = getDiscordTranslationButtons($pdo, $originalText);
$builder = \Discord\Builders\MessageBuilder::new()
->setContent($messageText);
if (!empty($buttons)) {
$actionRow = new \Discord\Builders\Components\ActionRow();
foreach ($buttons[0]['components'] as $btn) {
$button = \Discord\Builders\Components\Button::new(\Discord\Builders\Components\Button::STYLE_PRIMARY)
->setLabel($btn['label'])
->setCustomId($btn['custom_id']);
$actionRow->addComponent($button);
}
$builder->addComponent($actionRow);
}
$interaction->message->edit($builder);
} else { } else {
// Actualizar con error $interaction->message->edit(\Discord\Builders\MessageBuilder::new()->setContent("❌ Error al traducir"));
$errorBuilder = \Discord\Builders\MessageBuilder::new()
->setContent("❌ Error al traducir");
$interaction->updateOriginalResponse($errorBuilder);
} }
} catch (Exception $e) { } catch (Exception $e) {
error_log("Discord translate error: " . $e->getMessage()); error_log("Discord translate error: " . $e->getMessage());
try {
$builder = \Discord\Builders\MessageBuilder::new()
->setContent('❌ Error en el proceso de traducción');
$interaction->respondWithMessage($builder, true);
} catch (Exception $inner) {
error_log("Error responding to interaction: " . $inner->getMessage());
}
} }
} }

View File

@@ -1,19 +1,52 @@
FROM php:8.2-cli FROM php:8.3-apache
# Instalar dependencias del sistema
RUN apt-get update && apt-get install -y \ RUN apt-get update && apt-get install -y \
libcurl4-openssl-dev \ libcurl4-openssl-dev \
libzip-dev \ libzip-dev \
libpng-dev \
libjpeg-dev \
libfreetype6-dev \
unzip \ unzip \
supervisor \ supervisor \
nano \ nano \
&& pecl install curl \ cron \
&& docker-php-ext-enable curl \ && docker-php-ext-configure gd --with-freetype --with-jpeg \
&& docker-php-ext-install pdo_mysql zip \ && docker-php-ext-install pdo_mysql zip gd curl \
&& pecl install redis \
&& docker-php-ext-enable redis \
&& apt-get clean \ && apt-get clean \
&& rm -rf /var/lib/apt/lists/* && rm -rf /var/lib/apt/lists/*
# Habilitar módulos de Apache
RUN a2enmod rewrite headers ssl
# Copiar Composer
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
WORKDIR /var/www/html # Crear directorio de logs
RUN mkdir -p /var/www/html/logs && chown -R www-data:www-data /var/www/html/logs
CMD ["/usr/bin/supervisord", "-c", "/var/www/html/docker/supervisord.conf"] # Copiar configuración de Apache
COPY docker/apache.conf /etc/apache2/sites-available/000-default.conf
# Copiar configuración de Supervisor
COPY docker/supervisord.conf /etc/supervisor/conf.d/supervisord.conf
# Copiar el proyecto completo
COPY . /var/www/html/
# Instalar dependencias de PHP
RUN composer install --no-dev --optimize-autoloader
# Permisos correctos
RUN chown -R www-data:www-data /var/www/html \
&& chmod -R 755 /var/www/html \
&& chmod -R 775 /var/www/html/logs \
&& chmod -R 775 /var/www/html/galeria
# Puerto expuesto
EXPOSE 80
# Iniciar Apache y Supervisor
CMD ["/usr/bin/supervisord", "-c", "/etc/supervisor/conf.d/supervisord.conf"]

33
docker/apache.conf Normal file
View File

@@ -0,0 +1,33 @@
<VirtualHost *:80>
ServerAdmin webmaster@localhost
DocumentRoot /var/www/html
<Directory /var/www/html>
Options Indexes FollowSymLinks
AllowOverride All
Require all granted
# Permitir acceso a archivos estáticos
<FilesMatch "\.(jpg|jpeg|png|gif|ico|css|js|svg|woff|woff2|ttf|eot)$">
Require all granted
</FilesMatch>
</Directory>
# Logs
ErrorLog ${APACHE_LOG_DIR}/error.log
CustomLog ${APACHE_LOG_DIR}/access.log combined
# Aumentar límites para uploads
LimitRequestBody 10485760
# Timeout para scripts largos
TimeOut 300
</VirtualHost>
# Configuración de PHP
<IfModule mod_php.c>
php_value upload_max_filesize 10M
php_value post_max_size 10M
php_value max_execution_time 300
php_value max_input_time 300
</IfModule>

View File

@@ -1,34 +1,52 @@
version: '3.8' version: '3.8'
services: services:
bot: app:
image: php:8.2-cli build:
container_name: lastwar_bot context: ..
dockerfile: docker/Dockerfile
container_name: lastwar_app
restart: unless-stopped restart: unless-stopped
volumes: ports:
- ../lastwar:/var/www/html/lastwar - "8080:80"
working_dir: /var/www/html/lastwar env_file:
command: /usr/local/bin/supervisord -c /var/www/html/lastwar/docker/supervisord.conf - ../.env
environment: environment:
- PHP_DISPLAY_ERRORS=On - DB_HOST=db
- PHP_ERROR_REPORTING=E_ALL - DB_PORT=3306
- DB_NAME=${DB_NAME:-lastwar}
- DB_USER=${DB_USER:-lastwar}
- DB_PASS=${DB_PASS:-}
- LIBRETRANSLATE_URL=http://libretranslate:5000
volumes:
- app_logs:/var/www/html/logs
- app_galeria:/var/www/html/galeria
networks: networks:
- bot_network - lastwar_network
depends_on: depends_on:
- db db:
condition: service_healthy
libretranslate:
condition: service_started
db: db:
image: mysql:8.0 image: mysql:8.0
container_name: lastwar_db container_name: lastwar_db
restart: unless-stopped restart: unless-stopped
environment: environment:
MYSQL_ROOT_PASSWORD: ${DB_PASS:-} MYSQL_ROOT_PASSWORD: ${DB_PASS:-rootpassword}
MYSQL_DATABASE: ${DB_NAME:-bot} MYSQL_DATABASE: ${DB_NAME:-lastwar}
MYSQL_USER: ${DB_USER:-lastwar}
MYSQL_PASSWORD: ${DB_PASS:-}
volumes: volumes:
- db_data:/var/lib/mysql - db_data:/var/lib/mysql
- ./db:/docker-entrypoint-initdb.d - ../database:/docker-entrypoint-initdb.d:ro
networks: networks:
- bot_network - lastwar_network
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
timeout: 20s
retries: 10
libretranslate: libretranslate:
image: libretranslate/libretranslate image: libretranslate/libretranslate
@@ -37,11 +55,18 @@ services:
ports: ports:
- "5000:5000" - "5000:5000"
networks: networks:
- bot_network - lastwar_network
healthcheck:
test: ["CMD-SHELL", "curl -f http://localhost:5000/languages || exit 1"]
interval: 30s
timeout: 10s
retries: 3
networks: networks:
bot_network: lastwar_network:
driver: bridge driver: bridge
volumes: volumes:
db_data: db_data:
app_logs:
app_galeria:

View File

@@ -1,11 +0,0 @@
[program:bot_process_queue]
process_name=%(program_name)s_%(process_num)02d
command=php /var/www/html/lastwar/process_queue.php
autostart=true
autorestart=true
user=www-data
numprocs=1
stdout_logfile=/var/www/html/lastwar/logs/process_queue.log
stdout_logfile_maxbytes=10MB
stderr_logfile=/var/www/html/lastwar/logs/process_queue_error.log
redirect_stderr=true

View File

@@ -1,11 +0,0 @@
[program:bot_translation_queue]
process_name=%(program_name)s_%(process_num)02d
command=php /var/www/html/lastwar/process_translation_queue.php
autostart=true
autorestart=true
user=www-data
numprocs=1
stdout_logfile=/var/www/html/lastwar/logs/translation_queue.log
stdout_logfile_maxbytes=10MB
stderr_logfile=/var/www/html/lastwar/logs/translation_queue_error.log
redirect_stderr=true

View File

@@ -1,44 +1,61 @@
[unix_http_server]
file=/var/run/supervisor.sock
chmod=0700
[supervisord] [supervisord]
nodaemon=true nodaemon=true
logfile=/var/www/html/lastwar/logs/supervisor.log logfile=/var/www/html/logs/supervisor.log
logfile_maxbytes=50MB logfile_maxbytes=50MB
pidfile=/var/run/supervisord.pid pidfile=/var/run/supervisord.pid
childlogdir=/var/www/html/lastwar/logs childlogdir=/var/www/html/logs
[rpcinterface:supervisor]
supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface
[supervisorctl]
serverurl=unix:///var/run/supervisor.sock
[program:apache]
process_name=%(program_name)s
command=/usr/sbin/apache2ctl -D FOREGROUND
autostart=true
autorestart=true
stdout_logfile=/var/www/html/logs/apache.log
stdout_logfile_maxbytes=10MB
stderr_logfile=/var/www/html/logs/apache_error.log
redirect_stderr=true
[program:bot_discord] [program:bot_discord]
process_name=%(program_name)s process_name=%(program_name)s
command=php /var/www/html/lastwar/discord_bot.php command=php /var/www/html/discord_bot.php
autostart=true autostart=true
autorestart=true autorestart=true
user=www-data
numprocs=1 numprocs=1
stdout_logfile=/var/www/html/lastwar/logs/discord_bot.log stdout_logfile=/var/www/html/logs/discord_bot.log
stdout_logfile_maxbytes=10MB stdout_logfile_maxbytes=10MB
stderr_logfile=/var/www/html/lastwar/logs/discord_bot_error.log stderr_logfile=/var/www/html/logs/discord_bot_error.log
redirect_stderr=true redirect_stderr=true
[program:bot_process_queue] [program:bot_process_queue]
process_name=%(program_name)s process_name=%(program_name)s
command=php /var/www/html/lastwar/process_queue.php command=php /var/www/html/process_queue.php
autostart=true autostart=true
autorestart=true autorestart=true
user=www-data
numprocs=1 numprocs=1
stdout_logfile=/var/www/html/lastwar/logs/process_queue.log stdout_logfile=/var/www/html/logs/process_queue.log
stdout_logfile_maxbytes=10MB stdout_logfile_maxbytes=10MB
stderr_logfile=/var/www/html/lastwar/logs/process_queue_error.log stderr_logfile=/var/www/html/logs/process_queue_error.log
redirect_stderr=true redirect_stderr=true
[program:bot_translation_queue] [program:bot_translation_queue]
process_name=%(program_name)s process_name=%(program_name)s
command=php /var/www/html/lastwar/process_translation_queue.php command=php /var/www/html/process_translation_queue.php
autostart=true autostart=true
autorestart=true autorestart=true
user=www-data
numprocs=1 numprocs=1
stdout_logfile=/var/www/html/lastwar/logs/translation_queue.log stdout_logfile=/var/www/html/logs/translation_queue.log
stdout_logfile_maxbytes=10MB stdout_logfile_maxbytes=10MB
stderr_logfile=/var/www/html/lastwar/logs/translation_queue_error.log stderr_logfile=/var/www/html/logs/translation_queue_error.log
redirect_stderr=true redirect_stderr=true
[group:bot_workers] [group:bot_workers]

0
galeria/.gitkeep Normal file
View File

View File

@@ -2,6 +2,7 @@
require_once __DIR__ . '/includes/db.php'; require_once __DIR__ . '/includes/db.php';
require_once __DIR__ . '/includes/session_check.php'; require_once __DIR__ . '/includes/session_check.php';
require_once __DIR__ . '/includes/i18n.php'; require_once __DIR__ . '/includes/i18n.php';
require_once __DIR__ . '/includes/activity_logger.php';
checkSession(); checkSession();
$pageTitle = t('Galería de Imágenes'); $pageTitle = t('Galería de Imágenes');

View File

@@ -2,12 +2,9 @@
require_once __DIR__ . '/includes/db.php'; require_once __DIR__ . '/includes/db.php';
require_once __DIR__ . '/includes/env_loader.php'; require_once __DIR__ . '/includes/env_loader.php';
require_once __DIR__ . '/includes/auth.php'; require_once __DIR__ . '/includes/auth.php';
require_once __DIR__ . '/includes/i18n.php';
handleLanguageChange();
$domain = $_ENV['APP_URL'] ?? getenv('APP_URL') ?? ''; $domain = $_ENV['APP_URL'] ?? getenv('APP_URL') ?? '';
if ($domain) { if ($domain && session_status() === PHP_SESSION_NONE) {
$parsed = parse_url($domain); $parsed = parse_url($domain);
$host = $parsed['host'] ?? ''; $host = $parsed['host'] ?? '';
if ($host) { if ($host) {
@@ -22,7 +19,9 @@ if ($domain) {
} }
} }
session_start(); require_once __DIR__ . '/includes/i18n.php';
handleLanguageChange();
if (isset($_SESSION['user_id'])) { if (isset($_SESSION['user_id'])) {
header('Location: index.php'); header('Location: index.php');
@@ -341,7 +340,9 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
<ul class="dropdown-menu dropdown-menu-end"> <ul class="dropdown-menu dropdown-menu-end">
<li><a class="dropdown-item <?= $currentLang === 'es' ? 'active' : '' ?>" href="?lang=es">🇲🇽 <?= t('Español') ?></a></li> <li><a class="dropdown-item <?= $currentLang === 'es' ? 'active' : '' ?>" href="?lang=es">🇲🇽 <?= t('Español') ?></a></li>
<?php foreach ($activeLanguages as $lang): ?> <?php foreach ($activeLanguages as $lang): ?>
<?php if ($lang['language_code'] !== 'es'): ?>
<li><a class="dropdown-item <?= $currentLang === $lang['language_code'] ? 'active' : '' ?>" href="?lang=<?= urlencode($lang['language_code']) ?>"><?= htmlspecialchars($lang['flag_emoji']) ?> <?= t($lang['language_name']) ?></a></li> <li><a class="dropdown-item <?= $currentLang === $lang['language_code'] ? 'active' : '' ?>" href="?lang=<?= urlencode($lang['language_code']) ?>"><?= htmlspecialchars($lang['flag_emoji']) ?> <?= t($lang['language_name']) ?></a></li>
<?php endif; ?>
<?php endforeach; ?> <?php endforeach; ?>
</ul> </ul>
</div> </div>

0
logs/.gitkeep Normal file
View File

View File

@@ -13,16 +13,19 @@ define('SLEEP_INTERVAL', 5);
function getTranslationButtons(PDO $pdo, string $text): array function getTranslationButtons(PDO $pdo, string $text): array
{ {
$stmt = $pdo->query("SELECT language_code, flag_emoji FROM supported_languages WHERE is_active = 1"); $stmtTelegram = $pdo->query("SELECT language_code, flag_emoji FROM supported_languages WHERE is_active = 1 AND telegram_enabled = 1");
$languages = $stmt->fetchAll(); $telegramLanguages = $stmtTelegram->fetchAll();
if (count($languages) <= 1) { $stmtDiscord = $pdo->query("SELECT language_code, flag_emoji FROM supported_languages WHERE is_active = 1 AND discord_enabled = 1");
$discordLanguages = $stmtDiscord->fetchAll();
if (count($telegramLanguages) <= 1 && count($discordLanguages) <= 1) {
return []; return [];
} }
return [ return [
'telegram' => buildTelegramTranslationButtons($pdo, $languages, $text), 'telegram' => count($telegramLanguages) > 1 ? buildTelegramTranslationButtons($pdo, $telegramLanguages, $text) : [],
'discord' => buildDiscordTranslationButtons($languages, $text) 'discord' => count($discordLanguages) > 1 ? buildDiscordTranslationButtons($discordLanguages, $text) : []
]; ];
} }
@@ -104,15 +107,17 @@ function processScheduledMessages(): array
// Obtener botones de traducción (convertir HTML a texto plano) // Obtener botones de traducción (convertir HTML a texto plano)
$plainText = $schedule['content']; $plainText = $schedule['content'];
// Convertir saltos de párrafo a saltos de línea // Marcar donde hay imágenes
$plainText = preg_replace('/<\/p>/i', "\n", $plainText); $plainText = preg_replace('/<img[^>]+src=["\']([^"\']+)["\'][^>]*>/i', "\n[IMAGEN]\n", $plainText);
// Convertir saltos de párrafo y br a saltos de línea dobles
$plainText = preg_replace('/<\/p>/i', "\n\n", $plainText);
$plainText = preg_replace('/<p[^>]*>/i', '', $plainText); $plainText = preg_replace('/<p[^>]*>/i', '', $plainText);
$plainText = preg_replace('/<br\s*\/?>/i', "\n", $plainText); $plainText = preg_replace('/<br\s*\/?>/i', "\n", $plainText);
// Eliminar HTML // Eliminar HTML restante
$plainText = html_entity_decode(strip_tags($plainText), ENT_QUOTES | ENT_HTML5, 'UTF-8'); $plainText = html_entity_decode(strip_tags($plainText), ENT_QUOTES | ENT_HTML5, 'UTF-8');
// Limpiar espacios múltiples pero preservar saltos de línea // Limpiar espacios múltiples pero preservar saltos de línea (máximo 2)
$plainText = preg_replace('/[ \t]+/', ' ', $plainText); $plainText = preg_replace('/[ \t]+/', ' ', $plainText);
$plainText = preg_replace('/\n\s*\n/', "\n", $plainText); $plainText = preg_replace('/\n{3,}/', "\n\n", $plainText);
$plainText = trim($plainText); $plainText = trim($plainText);
$translationButtons = getTranslationButtons($pdo, $plainText); $translationButtons = getTranslationButtons($pdo, $plainText);
@@ -120,24 +125,26 @@ function processScheduledMessages(): array
$segments = $sender->parseContent($schedule['content']); $segments = $sender->parseContent($schedule['content']);
$messageCount = 0; $messageCount = 0;
$totalSegments = count($segments);
$currentSegment = 0;
// Enviar cada segmento en el orden correcto // Enviar cada segmento en el orden correcto
foreach ($segments as $segment) { foreach ($segments as $segment) {
$currentSegment++;
$isLastSegment = ($currentSegment === $totalSegments);
if ($segment['type'] === 'text') { if ($segment['type'] === 'text') {
// Convertir el texto al formato de la plataforma // Convertir el texto al formato de la plataforma
$textContent = ConverterFactory::convert($schedule['platform'], $segment['content']); $textContent = ConverterFactory::convert($schedule['platform'], $segment['content']);
if (!empty(trim($textContent))) { if (!empty(trim($textContent))) {
// Agregar botones de traducción al último segmento de texto
$buttons = null; $buttons = null;
if ($segment === end($segments)) { if ($isLastSegment && $schedule['platform'] !== 'telegram') {
$buttons = $schedule['platform'] === 'telegram' $buttons = $translationButtons['discord'];
? $translationButtons['telegram']
: $translationButtons['discord'];
} }
if ($schedule['platform'] === 'telegram') { if ($schedule['platform'] === 'telegram') {
$sender->sendMessage($schedule['platform_id'], $textContent, $buttons); $sender->sendMessage($schedule['platform_id'], $textContent);
} else { } else {
$sender->sendMessage($schedule['platform_id'], $textContent, null, $buttons); $sender->sendMessage($schedule['platform_id'], $textContent, null, $buttons);
} }
@@ -146,22 +153,43 @@ function processScheduledMessages(): array
} elseif ($segment['type'] === 'image') { } elseif ($segment['type'] === 'image') {
$imagePath = $segment['src']; $imagePath = $segment['src'];
// Quitar parámetros de URL si los hay $appUrl = $_ENV['APP_URL'] ?? getenv('APP_URL') ?? '';
$imgPath = parse_url($imagePath, PHP_URL_PATH) ?: $imagePath; $baseUrl = rtrim($appUrl, '/');
if (file_exists($imgPath)) { $buttons = null;
// Es un archivo local if ($isLastSegment && $schedule['platform'] !== 'telegram') {
$sender->sendMessageWithAttachments($schedule['platform_id'], '', [$imgPath]); $buttons = $translationButtons['discord'];
$messageCount++; }
} elseif (strpos($imagePath, 'http') === 0) {
// Es una URL remota if ($schedule['platform'] === 'telegram') {
$embed = ['image' => ['url' => $imagePath]]; if (strpos($imagePath, 'http') !== 0) {
$sender->sendMessage($schedule['platform_id'], '', $embed); $imageUrl = $baseUrl . '/' . ltrim($imagePath, '/');
} else {
$imageUrl = $imagePath;
}
$sender->sendPhoto($schedule['platform_id'], $imageUrl);
$messageCount++; $messageCount++;
} else {
$imgPath = parse_url($imagePath, PHP_URL_PATH) ?: $imagePath;
if (file_exists($imgPath)) {
$sender->sendMessageWithImages($schedule['platform_id'], '', [$imgPath], $buttons);
$messageCount++;
} elseif (strpos($imagePath, 'http') === 0) {
$embed = ['image' => ['url' => $imagePath]];
$sender->sendMessage($schedule['platform_id'], '', $embed, $buttons);
$messageCount++;
}
} }
} }
} }
// Enviar botones de traducción en mensaje separado para Telegram
if ($schedule['platform'] === 'telegram' && !empty($translationButtons['telegram'])) {
$translationMessage = "🌐 <b>Traducciones disponibles:</b>\nHaz clic en una bandera para ver la traducción";
$sender->sendMessage($schedule['platform_id'], $translationMessage, $translationButtons['telegram']);
$messageCount++;
}
$stmt = $pdo->prepare(" $stmt = $pdo->prepare("
INSERT INTO sent_messages (schedule_id, recipient_id, platform_message_id, message_count, sent_at) INSERT INTO sent_messages (schedule_id, recipient_id, platform_message_id, message_count, sent_at)
VALUES (?, ?, ?, ?, NOW()) VALUES (?, ?, ?, ?, NOW())

View File

@@ -57,7 +57,7 @@ require_once __DIR__ . '/templates/header.php';
?> ?>
<div class="d-flex justify-content-between align-items-center mb-4"> <div class="d-flex justify-content-between align-items-center mb-4">
<h2><i class="bi bi-person"></i> Mi Perfil</h2> <h2><i class="bi bi-person"></i> <?= t('Mi Perfil') ?></h2>
</div> </div>
<div class="row"> <div class="row">
@@ -69,11 +69,11 @@ require_once __DIR__ . '/templates/header.php';
</div> </div>
<h4><?= htmlspecialchars($user['username']) ?></h4> <h4><?= htmlspecialchars($user['username']) ?></h4>
<span class="badge bg-<?= $user['role'] === 'admin' ? 'danger' : 'primary' ?>"> <span class="badge bg-<?= $user['role'] === 'admin' ? 'danger' : 'primary' ?>">
<?= strtoupper($user['role']) ?> <?= strtoupper($user['role'] === 'admin' ? t('Administrador') : t('Usuario')) ?>
</span> </span>
<hr> <hr>
<p class="text-muted mb-1">ID: <?= $user['id'] ?></p> <p class="text-muted mb-1">ID: <?= $user['id'] ?></p>
<p class="text-muted mb-0">Miembro desde: <?= date('d/m/Y', strtotime($user['created_at'])) ?></p> <p class="text-muted mb-0"><?= t('Miembro desde') ?>: <?= date('d/m/Y', strtotime($user['created_at'])) ?></p>
</div> </div>
</div> </div>
</div> </div>
@@ -88,30 +88,30 @@ require_once __DIR__ . '/templates/header.php';
<?php endif; ?> <?php endif; ?>
<div class="card border-0 shadow-sm"> <div class="card border-0 shadow-sm">
<div class="card-header bg-white border-0"> <div class="card-header border-0">
<h5 class="mb-0"><i class="bi bi-key"></i> Cambiar Contraseña</h5> <h5 class="mb-0"><i class="bi bi-key"></i> <?= t('Cambiar Contraseña') ?></h5>
</div> </div>
<div class="card-body"> <div class="card-body">
<form method="POST"> <form method="POST">
<input type="hidden" name="action" value="change_password"> <input type="hidden" name="action" value="change_password">
<div class="mb-3"> <div class="mb-3">
<label class="form-label">Contraseña Actual</label> <label class="form-label"><?= t('Contraseña Actual') ?></label>
<input type="password" name="current_password" class="form-control" required> <input type="password" name="current_password" class="form-control" required>
</div> </div>
<div class="mb-3"> <div class="mb-3">
<label class="form-label">Nueva Contraseña</label> <label class="form-label"><?= t('Nueva Contraseña') ?></label>
<input type="password" name="new_password" class="form-control" required minlength="6"> <input type="password" name="new_password" class="form-control" required minlength="6">
</div> </div>
<div class="mb-3"> <div class="mb-3">
<label class="form-label">Confirmar Nueva Contraseña</label> <label class="form-label"><?= t('Confirmar Nueva Contraseña') ?></label>
<input type="password" name="confirm_password" class="form-control" required minlength="6"> <input type="password" name="confirm_password" class="form-control" required minlength="6">
</div> </div>
<button type="submit" class="btn btn-primary"> <button type="submit" class="btn btn-primary">
<i class="bi bi-check-circle"></i> Actualizar Contraseña <i class="bi bi-check-circle"></i> <?= t('Actualizar Contraseña') ?>
</button> </button>
</form> </form>
</div> </div>

View File

@@ -58,47 +58,47 @@ require_once __DIR__ . '/templates/header.php';
?> ?>
<div class="d-flex justify-content-between align-items-center mb-4"> <div class="d-flex justify-content-between align-items-center mb-4">
<h2><i class="bi bi-telegram"></i> Configurar Webhook de Telegram</h2> <h2><i class="bi bi-telegram"></i> <?= t('Configurar Webhook de Telegram') ?></h2>
</div> </div>
<div class="alert alert-warning"> <div class="alert alert-warning">
<i class="bi bi-exclamation-triangle"></i> <strong>Importante:</strong> Telegram requiere obligatoriamente HTTPS con un certificado SSL válido. <i class="bi bi-exclamation-triangle"></i> <strong><?= t('Importante') ?>:</strong> <?= t('Telegram requiere obligatoriamente HTTPS con un certificado SSL válido.') ?>
Asegúrate de que tu dominio tenga SSL activado. La URL propuesta usa: <code><?= htmlspecialchars($currentUrl) ?></code> <?= t('Asegúrate de que tu dominio tenga SSL activado.') ?> <?= t('La URL propuesta usa') ?>: <code><?= htmlspecialchars($currentUrl) ?></code>
</div> </div>
<?php if (!empty($results)): ?> <?php if (!empty($results)): ?>
<?php if (isset($results['set'])): ?> <?php if (isset($results['set'])): ?>
<?php if ($results['set']['ok']): ?> <?php if ($results['set']['ok']): ?>
<div class="alert alert-success"> <div class="alert alert-success">
<i class="bi bi-check-circle"></i> <strong>Webhook configurado correctamente!</strong> <i class="bi bi-check-circle"></i> <strong><?= t('Webhook configurado correctamente') ?>!</strong>
</div> </div>
<?php else: ?> <?php else: ?>
<div class="alert alert-danger"> <div class="alert alert-danger">
<i class="bi bi-x-circle"></i> <strong>Error:</strong> <?= htmlspecialchars($results['set']['description'] ?? 'Unknown error') ?> <i class="bi bi-x-circle"></i> <strong><?= t('Error') ?>:</strong> <?= htmlspecialchars($results['set']['description'] ?? 'Unknown error') ?>
</div> </div>
<?php endif; ?> <?php endif; ?>
<?php endif; ?> <?php endif; ?>
<?php if (isset($results['delete'])): ?> <?php if (isset($results['delete'])): ?>
<?php if ($results['delete']['ok']): ?> <?php if ($results['delete']['ok']): ?>
<div class="alert alert-success">Webhook eliminado correctamente</div> <div class="alert alert-success"><?= t('Webhook eliminado correctamente') ?></div>
<?php else: ?> <?php else: ?>
<div class="alert alert-danger">Error: <?= htmlspecialchars($results['delete']['description'] ?? 'Unknown error') ?></div> <div class="alert alert-danger"><?= t('Error') ?>: <?= htmlspecialchars($results['delete']['description'] ?? 'Unknown error') ?></div>
<?php endif; ?> <?php endif; ?>
<?php endif; ?> <?php endif; ?>
<?php if (isset($results['info'])): ?> <?php if (isset($results['info'])): ?>
<div class="card border-0 shadow-sm mb-4"> <div class="card border-0 shadow-sm mb-4">
<div class="card-header bg-white border-0"> <div class="card-header border-0">
<h5 class="mb-0">Estado Actual del Webhook</h5> <h5 class="mb-0"><?= t('Estado Actual del Webhook') ?></h5>
</div> </div>
<div class="card-body"> <div class="card-body">
<?php if ($results['info']['ok'] && !empty($results['info']['result']['url'])): ?> <?php if ($results['info']['ok'] && !empty($results['info']['result']['url'])): ?>
<p><strong>URL:</strong> <?= htmlspecialchars($results['info']['result']['url']) ?></p> <p><strong>URL:</strong> <?= htmlspecialchars($results['info']['result']['url']) ?></p>
<p><strong>Activo:</strong> <?= $results['info']['result']['url'] ? '✅ Sí' : '❌ No' ?></p> <p><strong><?= t('Activo') ?>:</strong> <?= $results['info']['result']['url'] ? '✅ ' . t('Sí') : '❌ ' . t('No') ?></p>
<p><strong>Errores:</strong> <?= $results['info']['result']['last_error_message'] ?? 'Ninguno' ?></p> <p><strong><?= t('Errores') ?>:</strong> <?= $results['info']['result']['last_error_message'] ?? t('Ninguno') ?></p>
<?php else: ?> <?php else: ?>
<p class="text-muted">No hay webhook configurado</p> <p class="text-muted"><?= t('No hay webhook configurado') ?></p>
<?php endif; ?> <?php endif; ?>
</div> </div>
</div> </div>
@@ -106,11 +106,11 @@ require_once __DIR__ . '/templates/header.php';
<?php endif; ?> <?php endif; ?>
<div class="card border-0 shadow-sm mb-4"> <div class="card border-0 shadow-sm mb-4">
<div class="card-header bg-white border-0"> <div class="card-header border-0">
<h5 class="mb-0">URL del Webhook</h5> <h5 class="mb-0"><?= t('URL del Webhook') ?></h5>
</div> </div>
<div class="card-body"> <div class="card-body">
<p class="text-muted">URL base para el webhook (se añadirá automáticamente el token de autenticación):</p> <p class="text-muted"><?= t('URL base para el webhook (se añadirá automáticamente el token de autenticación)') ?>:</p>
<div class="input-group"> <div class="input-group">
<input type="text" class="form-control" value="<?= htmlspecialchars($currentUrl) ?>" readonly> <input type="text" class="form-control" value="<?= htmlspecialchars($currentUrl) ?>" readonly>
<button class="btn btn-outline-secondary" onclick="navigator.clipboard.writeText('<?= htmlspecialchars($currentUrl) ?>')"> <button class="btn btn-outline-secondary" onclick="navigator.clipboard.writeText('<?= htmlspecialchars($currentUrl) ?>')">
@@ -123,18 +123,18 @@ require_once __DIR__ . '/templates/header.php';
<div class="row"> <div class="row">
<div class="col-md-6"> <div class="col-md-6">
<div class="card border-0 shadow-sm"> <div class="card border-0 shadow-sm">
<div class="card-header bg-white border-0"> <div class="card-header border-0">
<h5 class="mb-0">Configurar Webhook</h5> <h5 class="mb-0"><?= t('Configurar Webhook') ?></h5>
</div> </div>
<div class="card-body"> <div class="card-body">
<form method="POST"> <form method="POST">
<input type="hidden" name="action" value="set"> <input type="hidden" name="action" value="set">
<div class="mb-3"> <div class="mb-3">
<label class="form-label">URL del webhook</label> <label class="form-label"><?= t('URL del webhook') ?></label>
<input type="text" name="webhook_url" class="form-control" value="<?= htmlspecialchars($currentUrl) ?>" required> <input type="text" name="webhook_url" class="form-control" value="<?= htmlspecialchars($currentUrl) ?>" required>
</div> </div>
<button type="submit" class="btn btn-primary"> <button type="submit" class="btn btn-primary">
<i class="bi bi-plug"></i> Establecer Webhook <i class="bi bi-plug"></i> <?= t('Establecer Webhook') ?>
</button> </button>
</form> </form>
</div> </div>
@@ -143,21 +143,21 @@ require_once __DIR__ . '/templates/header.php';
<div class="col-md-6"> <div class="col-md-6">
<div class="card border-0 shadow-sm"> <div class="card border-0 shadow-sm">
<div class="card-header bg-white border-0"> <div class="card-header border-0">
<h5 class="mb-0">Opciones</h5> <h5 class="mb-0"><?= t('Opciones') ?></h5>
</div> </div>
<div class="card-body"> <div class="card-body">
<form method="POST" class="mb-2"> <form method="POST" class="mb-2">
<input type="hidden" name="action" value="check"> <input type="hidden" name="action" value="check">
<button type="submit" class="btn btn-secondary w-100 mb-2"> <button type="submit" class="btn btn-secondary w-100 mb-2">
<i class="bi bi-info-circle"></i> Verificar Estado <i class="bi bi-info-circle"></i> <?= t('Verificar Estado') ?>
</button> </button>
</form> </form>
<form method="POST" onsubmit="return confirm('¿Eliminar el webhook?');"> <form method="POST" onsubmit="return confirm('<?= t('¿Eliminar el webhook?') ?>');">
<input type="hidden" name="action" value="delete"> <input type="hidden" name="action" value="delete">
<button type="submit" class="btn btn-danger w-100"> <button type="submit" class="btn btn-danger w-100">
<i class="bi bi-trash"></i> Eliminar Webhook <i class="bi bi-trash"></i> <?= t('Eliminar Webhook') ?>
</button> </button>
</form> </form>
</div> </div>

View File

@@ -34,12 +34,10 @@ class Translate
} }
try { try {
// Primero intentar obtener del caché
$cacheKey = $this->generateCacheKey($text, $sourceLang, $targetLang); $cacheKey = $this->generateCacheKey($text, $sourceLang, $targetLang);
$cached = $this->getFromCache($cacheKey); $cached = $this->getFromCache($cacheKey);
if ($cached !== null) { if ($cached !== null) {
error_log("Translation cache hit for: $sourceLang -> $targetLang");
return $cached; return $cached;
} }
@@ -47,24 +45,29 @@ class Translate
$translatedLines = []; $translatedLines = [];
foreach ($lines as $line) { foreach ($lines as $line) {
if (trim($line) === '') { $trimmed = trim($line);
if ($trimmed === '') {
$translatedLines[] = ''; $translatedLines[] = '';
continue; continue;
} }
$response = $this->request('/translate', [ try {
'q' => trim($line), $response = $this->request('/translate', [
'source' => $sourceLang, 'q' => $trimmed,
'target' => $targetLang, 'source' => $sourceLang,
'format' => 'text' 'target' => $targetLang,
]); 'format' => 'text'
]);
$translatedLines[] = $response['translatedText'] ?? trim($line);
$translated = $response['translatedText'] ?? $trimmed;
$translatedLines[] = $translated;
} catch (\Exception $e) {
error_log("Line translation error: " . $e->getMessage());
$translatedLines[] = $line; // Mantener original en caso de error
}
} }
$result = implode("\n", $translatedLines); $result = implode("\n", $translatedLines);
// Guardar en caché
$this->saveToCache($cacheKey, $result); $this->saveToCache($cacheKey, $result);
return $result; return $result;
@@ -74,6 +77,225 @@ class Translate
} }
} }
public function translatePartial(string $text, string $targetLang): ?string
{
if (empty(trim($text))) {
return $text;
}
try {
$cacheKey = $this->generateCacheKey($text . '_partial_v8', 'partial', $targetLang);
$cached = $this->getFromCache($cacheKey);
if ($cached !== null) {
return $cached;
}
// 1. Intentar procesar texto con marcadores [:es]...[:es]
$processed = $this->processMarkedText($text, $targetLang);
if ($processed !== $text) {
$this->saveToCache($cacheKey, $processed);
return $processed;
}
// 2. Detección inicial dinámica
if (!function_exists('stripEmojisForDetection')) {
require_once __DIR__ . '/../includes/emoji_helper.php';
}
$textForDetection = stripEmojisForDetection($text);
$mainLang = $this->detectLanguage($textForDetection);
error_log("translatePartial: hash=" . md5($text) . " mainLang=" . ($mainLang ?? 'null') . " target=$targetLang");
// 3. Procesamiento por líneas
$lines = explode("\n", $text);
// Si es una sola línea, intentar traducción global directa
if (count($lines) <= 1) {
// Si el idioma detectado coincide con el destino, forzamos 'auto' para procesar posibles mezclas
$srcLang = ($mainLang === $targetLang) ? 'auto' : ($mainLang ?? 'auto');
$result = $this->translate($text, $srcLang, $targetLang);
if ($result) {
$this->saveToCache($cacheKey, $result);
return $result;
}
return $text;
}
$translatedLines = [];
$seenNorm = [];
$translatedAny = false;
foreach ($lines as $line) {
$trimmed = trim($line);
if (empty($trimmed)) {
$translatedLines[] = "";
continue;
}
// Si la línea tiene separadores como " / ", " | ", intentar separarla
if (preg_match('/\s+[\/|]\s+/', $trimmed)) {
$parts = preg_split('/\s+[\/|]\s+/', $trimmed);
$translatedParts = [];
foreach ($parts as $part) {
$partTrimmed = trim($part);
if (empty($partTrimmed)) continue;
$partLang = $this->detectLanguage($partTrimmed);
// Si el idioma detectado de la parte coincide con el destino, usamos 'auto' para ver si LibreTranslate traduce algo interno
$srcLang = ($partLang === $targetLang) ? 'auto' : ($partLang ?? 'auto');
$translatedPart = $this->translate($partTrimmed, $srcLang, $targetLang);
if ($translatedPart !== $partTrimmed) $translatedAny = true;
if ($translatedPart) {
$normPart = mb_strtolower(preg_replace('/[[:punct:]\s]+/u', '', $translatedPart));
if (!isset($seenNorm[$normPart])) {
$translatedParts[] = $translatedPart;
$seenNorm[$normPart] = true;
}
}
}
if (!empty($translatedParts)) {
$translatedLines[] = implode(" / ", $translatedParts);
}
} else {
$lineLang = $this->detectLanguage($trimmed);
// Mismo principio: si coincide el idioma, usamos 'auto' para permitir traducción de mezclas
$srcLang = ($lineLang === $targetLang) ? 'auto' : ($lineLang ?? 'auto');
$translatedLine = $this->translate($trimmed, $srcLang, $targetLang);
if ($translatedLine !== $trimmed) $translatedAny = true;
if ($translatedLine) {
$normLine = mb_strtolower(preg_replace('/[[:punct:]\s]+/u', '', $translatedLine));
if (!isset($seenNorm[$normLine])) {
$translatedLines[] = $translatedLine;
$seenNorm[$normLine] = true;
}
}
}
}
// Reconstruir el mensaje
$result = trim(implode("\n", $translatedLines));
$result = preg_replace('/\n{3,}/', "\n\n", $result);
// Si el resultado es igual al original y no se detectó ninguna traducción efectiva, forzar global con auto
if ($result === trim($text) && !$translatedAny) {
$globalResult = $this->translate($text, 'auto', $targetLang);
if ($globalResult) $result = $globalResult;
}
if ($result) {
$this->saveToCache($cacheKey, $result);
return $result;
}
return $text;
} catch (\Exception $e) {
error_log("Partial translation error: " . $e->getMessage());
return $text;
}
}
private function processMarkedText(string $text, string $targetLang): string
{
error_log("processMarkedText called with text: $text, target: $targetLang");
$pattern = '/\[:([a-z]{2})\](.*?)\[:\1\]/su';
$result = preg_replace_callback($pattern, function($matches) use ($targetLang) {
$wordLang = $matches[1];
$word = trim($matches[2]);
if ($wordLang === $targetLang) {
return $word;
}
$translated = $this->translate($word, $wordLang, $targetLang);
return $translated ?? $word;
}, $text);
if ($result !== $text) {
$lines = explode("\n", $result);
$uniqueLines = [];
$seen = [];
foreach ($lines as $line) {
$trimmed = trim($line);
if (empty($trimmed)) {
$uniqueLines[] = "";
continue;
}
$norm = mb_strtolower(preg_replace('/[[:punct:]\s]+/u', '', $trimmed));
if (mb_strlen($norm) < 10 || !isset($seen[$norm])) {
$uniqueLines[] = $trimmed;
$seen[$norm] = true;
}
}
$result = implode("\n", $uniqueLines);
}
return $result;
}
private function splitIntoSegments(string $text): array
{
if (empty(trim($text))) {
return [['text' => $text]];
}
$words = preg_split('/(\s+)/u', $text, -1, PREG_SPLIT_DELIM_CAPTURE);
$result = '';
foreach ($words as $word) {
$trimmed = trim($word);
if ($trimmed === '') {
$result .= $word;
continue;
}
if (strlen($trimmed) < 3) {
$result .= $word;
continue;
}
$wordLang = $this->detectLanguage($trimmed);
if ($wordLang) {
$result .= '[:' . $wordLang . ']' . $trimmed . '[:' . $wordLang . ']';
} else {
$result .= $word;
}
}
return [['text' => $result, 'processed' => true]];
}
private function reconstructText(array $segments): string
{
$result = '';
foreach ($segments as $i => $segment) {
$text = $segment['translated'];
if ($i > 0) {
$lastChar = substr($result, -1);
$firstChar = substr($text, 0, 1);
if ($lastChar !== ' ' && $lastChar !== "\n" && $firstChar !== ' ' && $firstChar !== "\n") {
$result .= ' ';
}
}
$result .= $text;
}
return $result;
}
public function translateToMultiple(string $text, string $sourceLang, array $targetLangs): array public function translateToMultiple(string $text, string $sourceLang, array $targetLangs): array
{ {
$results = []; $results = [];

View File

@@ -27,7 +27,7 @@ class TelegramSender
return $this->request('sendMessage', $data); return $this->request('sendMessage', $data);
} }
public function editMessageText(int $chatId, int $messageId, string $text, ?array $keyboard = null, ?string $parseMode = 'HTML'): array public function editMessageText(int $chatId, int $messageId, string $text, ?array $keyboard = null, ?string $parseMode = 'HTML', ?string $inlineMessageId = null): array
{ {
$data = [ $data = [
'chat_id' => $chatId, 'chat_id' => $chatId,
@@ -35,6 +35,12 @@ class TelegramSender
'text' => $text, 'text' => $text,
]; ];
if ($inlineMessageId) {
unset($data['chat_id']);
unset($data['message_id']);
$data['inline_message_id'] = $inlineMessageId;
}
if ($parseMode) { if ($parseMode) {
$data['parse_mode'] = $parseMode; $data['parse_mode'] = $parseMode;
} }

View File

@@ -33,7 +33,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$stmt->execute([$messageText, $buttonText, $groupInviteLink, $isActive, $registerUsers]); $stmt->execute([$messageText, $buttonText, $groupInviteLink, $isActive, $registerUsers]);
logActivity(getCurrentUserId(), 'update_telegram_config', 'Configuración del bot de Telegram actualizada'); logActivity(getCurrentUserId(), 'update_telegram_config', 'Configuración del bot de Telegram actualizada');
$success = 'Configuración guardada correctamente'; $success = t('Configuración guardada correctamente');
$stmt = $pdo->query("SELECT * FROM telegram_bot_messages WHERE id = 1"); $stmt = $pdo->query("SELECT * FROM telegram_bot_messages WHERE id = 1");
$config = $stmt->fetch(); $config = $stmt->fetch();
@@ -43,7 +43,7 @@ require_once __DIR__ . '/../../templates/header.php';
?> ?>
<div class="d-flex justify-content-between align-items-center mb-4"> <div class="d-flex justify-content-between align-items-center mb-4">
<h2><i class="bi bi-telegram"></i> Configuración del Bot de Telegram</h2> <h2><i class="bi bi-telegram"></i> <?= t('Configuración del Bot de Telegram') ?></h2>
</div> </div>
<?php if ($success): ?> <?php if ($success): ?>
@@ -56,47 +56,47 @@ require_once __DIR__ . '/../../templates/header.php';
<form method="POST"> <form method="POST">
<div class="card border-0 shadow-sm mb-4"> <div class="card border-0 shadow-sm mb-4">
<div class="card-header bg-white border-0"> <div class="card-header border-0">
<h5 class="mb-0">Mensaje de Bienvenida</h5> <h5 class="mb-0"><?= t('Mensaje de Bienvenida') ?></h5>
</div> </div>
<div class="card-body"> <div class="card-body">
<div class="mb-3"> <div class="mb-3">
<label class="form-label">Mensaje de bienvenida</label> <label class="form-label"><?= t('Mensaje de bienvenida') ?></label>
<textarea name="message_text" class="form-control" rows="5"><?= htmlspecialchars($config['message_text'] ?? '') ?></textarea> <textarea name="message_text" class="form-control" rows="5"><?= htmlspecialchars($config['message_text'] ?? '') ?></textarea>
<small class="text-muted">Usa <code>{user_name}</code> para el nombre del usuario</small> <small class="text-muted"><?= t('Usa') ?> <code>{user_name}</code> <?= t('para el nombre del usuario') ?></small>
</div> </div>
<div class="mb-3"> <div class="mb-3">
<label class="form-label">Texto del botón</label> <label class="form-label"><?= t('Texto del botón') ?></label>
<input type="text" name="button_text" class="form-control" value="<?= htmlspecialchars($config['button_text'] ?? '') ?>" placeholder="Unirse al grupo"> <input type="text" name="button_text" class="form-control" value="<?= htmlspecialchars($config['button_text'] ?? '') ?>" placeholder="<?= t('Unirse al grupo') ?>">
</div> </div>
<div class="mb-3"> <div class="mb-3">
<label class="form-label">Enlace de invitación al grupo</label> <label class="form-label"><?= t('Enlace de invitación al grupo') ?></label>
<input type="text" name="group_invite_link" class="form-control" value="<?= htmlspecialchars($config['group_invite_link'] ?? '') ?>" placeholder="https://t.me/..."> <input type="text" name="group_invite_link" class="form-control" value="<?= htmlspecialchars($config['group_invite_link'] ?? '') ?>" placeholder="https://t.me/...">
</div> </div>
</div> </div>
</div> </div>
<div class="card border-0 shadow-sm mb-4"> <div class="card border-0 shadow-sm mb-4">
<div class="card-header bg-white border-0"> <div class="card-header border-0">
<h5 class="mb-0">Opciones</h5> <h5 class="mb-0"><?= t('Opciones') ?></h5>
</div> </div>
<div class="card-body"> <div class="card-body">
<div class="form-check form-switch mb-3"> <div class="form-check form-switch mb-3">
<input class="form-check-input" type="checkbox" name="is_active" id="isActive" <?= ($config['is_active'] ?? true) ? 'checked' : '' ?>> <input class="form-check-input" type="checkbox" name="is_active" id="isActive" <?= ($config['is_active'] ?? true) ? 'checked' : '' ?>>
<label class="form-check-label" for="isActive">Mensaje de bienvenida activo</label> <label class="form-check-label" for="isActive"><?= t('Mensaje de bienvenida activo') ?></label>
</div> </div>
<div class="form-check form-switch"> <div class="form-check form-switch">
<input class="form-check-input" type="checkbox" name="register_users" id="registerUsers" <?= ($config['register_users'] ?? true) ? 'checked' : '' ?>> <input class="form-check-input" type="checkbox" name="register_users" id="registerUsers" <?= ($config['register_users'] ?? true) ? 'checked' : '' ?>>
<label class="form-check-label" for="registerUsers">Registrar usuarios automáticamente</label> <label class="form-check-label" for="registerUsers"><?= t('Registrar usuarios automáticamente') ?></label>
</div> </div>
</div> </div>
</div> </div>
<button type="submit" class="btn btn-primary"> <button type="submit" class="btn btn-primary">
<i class="bi bi-save"></i> Guardar Configuración <i class="bi bi-save"></i> <?= t('Guardar Configuración') ?>
</button> </button>
</form> </form>

View File

@@ -79,9 +79,9 @@ require_once __DIR__ . '/../../templates/header.php';
?> ?>
<div class="d-flex justify-content-between align-items-center mb-4"> <div class="d-flex justify-content-between align-items-center mb-4">
<h2><i class="bi bi-telegram"></i> Mensajes de Bienvenida por Grupo</h2> <h2><i class="bi bi-telegram"></i> <?= t('Mensajes de Bienvenida por Grupo') ?></h2>
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#welcomeModal"> <button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#welcomeModal">
<i class="bi bi-plus-circle"></i> Nuevo Mensaje <i class="bi bi-plus-circle"></i> <?= t('Nuevo Mensaje') ?>
</button> </button>
</div> </div>
@@ -92,19 +92,19 @@ require_once __DIR__ . '/../../templates/header.php';
<div class="row"> <div class="row">
<?php if (empty($welcomeMessages)): ?> <?php if (empty($welcomeMessages)): ?>
<div class="col-12"> <div class="col-12">
<p class="text-muted text-center py-4">No hay mensajes de bienvenida configurados</p> <p class="text-muted text-center py-4"><?= t('No hay mensajes de bienvenida configurados') ?></p>
</div> </div>
<?php else: ?> <?php else: ?>
<?php foreach ($welcomeMessages as $msg): ?> <?php foreach ($welcomeMessages as $msg): ?>
<div class="col-md-6 mb-4"> <div class="col-md-6 mb-4">
<div class="card border-0 shadow-sm"> <div class="card border-0 shadow-sm">
<div class="card-header bg-white border-0 d-flex justify-content-between align-items-center"> <div class="card-header border-0 d-flex justify-content-between align-items-center">
<h6 class="mb-0"><?= htmlspecialchars($msg['flag_emoji'] ?? '') ?> <?= htmlspecialchars($msg['language_name'] ?? 'Grupo') ?></h6> <h6 class="mb-0"><?= htmlspecialchars($msg['flag_emoji'] ?? '') ?> <?= htmlspecialchars($msg['language_name'] ?? t('Grupo')) ?></h6>
<div class="btn-group btn-group-sm"> <div class="btn-group btn-group-sm">
<button class="btn btn-outline-primary" data-bs-toggle="modal" data-bs-target="#editModal<?= $msg['id'] ?>"> <button class="btn btn-outline-primary" data-bs-toggle="modal" data-bs-target="#editModal<?= $msg['id'] ?>">
<i class="bi bi-pencil"></i> <i class="bi bi-pencil"></i>
</button> </button>
<form method="POST" onsubmit="return confirm('¿Eliminar?');" class="d-inline"> <form method="POST" onsubmit="return confirm('<?= t('¿Eliminar?') ?>');" class="d-inline">
<input type="hidden" name="action" value="delete"> <input type="hidden" name="action" value="delete">
<input type="hidden" name="id" value="<?= $msg['id'] ?>"> <input type="hidden" name="id" value="<?= $msg['id'] ?>">
<button type="submit" class="btn btn-outline-danger"> <button type="submit" class="btn btn-outline-danger">
@@ -116,7 +116,7 @@ require_once __DIR__ . '/../../templates/header.php';
<div class="card-body"> <div class="card-body">
<p class="text-muted small mb-1">Chat ID: <?= $msg['chat_id'] ?></p> <p class="text-muted small mb-1">Chat ID: <?= $msg['chat_id'] ?></p>
<p class="mb-1"><?= nl2br(htmlspecialchars($msg['welcome_message'] ?? '')) ?></p> <p class="mb-1"><?= nl2br(htmlspecialchars($msg['welcome_message'] ?? '')) ?></p>
<small class="text-muted"><?= $msg['is_active'] ? '✅ Activo' : '❌ Inactivo' ?></small> <small class="text-muted"><?= $msg['is_active'] ? '✅ ' . t('Activo') : '❌ ' . t('Inactivo') ?></small>
</div> </div>
</div> </div>
</div> </div>
@@ -128,29 +128,29 @@ require_once __DIR__ . '/../../templates/header.php';
<input type="hidden" name="action" value="update"> <input type="hidden" name="action" value="update">
<input type="hidden" name="id" value="<?= $msg['id'] ?>"> <input type="hidden" name="id" value="<?= $msg['id'] ?>">
<div class="modal-header"> <div class="modal-header">
<h5 class="modal-title">Editar Mensaje</h5> <h5 class="modal-title"><?= t('Editar Mensaje') ?></h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button> <button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<div class="mb-3"> <div class="mb-3">
<label class="form-label">Mensaje de bienvenida</label> <label class="form-label"><?= t('Mensaje de bienvenida') ?></label>
<textarea name="welcome_message" class="form-control" rows="3"><?= htmlspecialchars($msg['welcome_message'] ?? '') ?></textarea> <textarea name="welcome_message" class="form-control" rows="3"><?= htmlspecialchars($msg['welcome_message'] ?? '') ?></textarea>
</div> </div>
<div class="mb-3"> <div class="mb-3">
<label class="form-label">Texto del botón</label> <label class="form-label"><?= t('Texto del botón') ?></label>
<input type="text" name="button_text" class="form-control" value="<?= htmlspecialchars($msg['button_text'] ?? '') ?>"> <input type="text" name="button_text" class="form-control" value="<?= htmlspecialchars($msg['button_text'] ?? '') ?>">
</div> </div>
<div class="mb-3"> <div class="mb-3">
<label class="form-label">Enlace de invitación</label> <label class="form-label"><?= t('Enlace de invitación') ?></label>
<input type="text" name="group_invite_link" class="form-control" value="<?= htmlspecialchars($msg['group_invite_link'] ?? '') ?>"> <input type="text" name="group_invite_link" class="form-control" value="<?= htmlspecialchars($msg['group_invite_link'] ?? '') ?>">
</div> </div>
<div class="row mb-3"> <div class="row mb-3">
<div class="col-4"> <div class="col-4">
<label class="form-label">Código idioma</label> <label class="form-label"><?= t('Código idioma') ?></label>
<input type="text" name="language_code" class="form-control" value="<?= htmlspecialchars($msg['language_code'] ?? 'es') ?>"> <input type="text" name="language_code" class="form-control" value="<?= htmlspecialchars($msg['language_code'] ?? 'es') ?>">
</div> </div>
<div class="col-4"> <div class="col-4">
<label class="form-label">Nombre idioma</label> <label class="form-label"><?= t('Nombre idioma') ?></label>
<input type="text" name="language_name" class="form-control" value="<?= htmlspecialchars($msg['language_name'] ?? 'Español') ?>"> <input type="text" name="language_name" class="form-control" value="<?= htmlspecialchars($msg['language_name'] ?? 'Español') ?>">
</div> </div>
<div class="col-4"> <div class="col-4">
@@ -160,11 +160,11 @@ require_once __DIR__ . '/../../templates/header.php';
</div> </div>
<div class="form-check"> <div class="form-check">
<input class="form-check-input" type="checkbox" name="is_active" id="editActive<?= $msg['id'] ?>" <?= $msg['is_active'] ? 'checked' : '' ?>> <input class="form-check-input" type="checkbox" name="is_active" id="editActive<?= $msg['id'] ?>" <?= $msg['is_active'] ? 'checked' : '' ?>>
<label class="form-check-label" for="editActive<?= $msg['id'] ?>">Activo</label> <label class="form-check-label" for="editActive<?= $msg['id'] ?>"><?= t('Activo') ?></label>
</div> </div>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button type="submit" class="btn btn-primary">Guardar</button> <button type="submit" class="btn btn-primary"><?= t('Guardar') ?></button>
</div> </div>
</form> </form>
</div> </div>
@@ -180,33 +180,33 @@ require_once __DIR__ . '/../../templates/header.php';
<form method="POST"> <form method="POST">
<input type="hidden" name="action" value="add"> <input type="hidden" name="action" value="add">
<div class="modal-header"> <div class="modal-header">
<h5 class="modal-title">Nuevo Mensaje de Bienvenida</h5> <h5 class="modal-title"><?= t('Nuevo Mensaje de Bienvenida') ?></h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button> <button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<div class="mb-3"> <div class="mb-3">
<label class="form-label">Chat ID del grupo</label> <label class="form-label"><?= t('Chat ID del grupo') ?></label>
<input type="text" name="chat_id" class="form-control" required> <input type="text" name="chat_id" class="form-control" required>
</div> </div>
<div class="mb-3"> <div class="mb-3">
<label class="form-label">Mensaje de bienvenida</label> <label class="form-label"><?= t('Mensaje de bienvenida') ?></label>
<textarea name="welcome_message" class="form-control" rows="3" required></textarea> <textarea name="welcome_message" class="form-control" rows="3" required></textarea>
</div> </div>
<div class="mb-3"> <div class="mb-3">
<label class="form-label">Texto del botón</label> <label class="form-label"><?= t('Texto del botón') ?></label>
<input type="text" name="button_text" class="form-control"> <input type="text" name="button_text" class="form-control">
</div> </div>
<div class="mb-3"> <div class="mb-3">
<label class="form-label">Enlace de invitación</label> <label class="form-label"><?= t('Enlace de invitación') ?></label>
<input type="text" name="group_invite_link" class="form-control"> <input type="text" name="group_invite_link" class="form-control">
</div> </div>
<div class="row mb-3"> <div class="row mb-3">
<div class="col-4"> <div class="col-4">
<label class="form-label">Código idioma</label> <label class="form-label"><?= t('Código idioma') ?></label>
<input type="text" name="language_code" class="form-control" value="es"> <input type="text" name="language_code" class="form-control" value="es">
</div> </div>
<div class="col-4"> <div class="col-4">
<label class="form-label">Nombre idioma</label> <label class="form-label"><?= t('Nombre idioma') ?></label>
<input type="text" name="language_name" class="form-control" value="Español"> <input type="text" name="language_name" class="form-control" value="Español">
</div> </div>
<div class="col-4"> <div class="col-4">
@@ -216,11 +216,11 @@ require_once __DIR__ . '/../../templates/header.php';
</div> </div>
<div class="form-check"> <div class="form-check">
<input class="form-check-input" type="checkbox" name="is_active" id="newActive" checked> <input class="form-check-input" type="checkbox" name="is_active" id="newActive" checked>
<label class="form-check-label" for="newActive">Activo</label> <label class="form-check-label" for="newActive"><?= t('Activo') ?></label>
</div> </div>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button type="submit" class="btn btn-primary">Agregar</button> <button type="submit" class="btn btn-primary"><?= t('Agregar') ?></button>
</div> </div>
</form> </form>
</div> </div>

View File

@@ -23,11 +23,11 @@ if (!$update) {
exit('No update'); exit('No update');
} }
file_put_contents('/var/www/html/lastwar/logs/telegram_debug.log', date('Y-m-d H:i:s') . " -收到更新\n", FILE_APPEND); file_put_contents(__DIR__ . '/../../logs/telegram_debug.log', date('Y-m-d H:i:s') . " - Received update\n", FILE_APPEND);
if (isset($update['callback_query'])) { if (isset($update['callback_query'])) {
$callbackData = $update['callback_query']['data'] ?? 'no data'; $callbackData = $update['callback_query']['data'] ?? 'no data';
file_put_contents('/var/www/html/lastwar/logs/telegram_debug.log', date('Y-m-d H:i:s') . " - Callback: $callbackData\n", FILE_APPEND); file_put_contents(__DIR__ . '/../../logs/telegram_debug.log', date('Y-m-d H:i:s') . " - Callback: $callbackData\n", FILE_APPEND);
} }
try { try {
@@ -82,11 +82,12 @@ try {
$callbackData = $callbackQuery['data']; $callbackData = $callbackQuery['data'];
$chatId = $callbackQuery['message']['chat']['id']; $chatId = $callbackQuery['message']['chat']['id'];
$messageId = $callbackQuery['message']['message_id']; $messageId = $callbackQuery['message']['message_id'];
$inlineMessageId = $callbackQuery['inline_message_id'] ?? null;
$userId = $callbackQuery['from']['id'] ?? ''; $userId = $callbackQuery['from']['id'] ?? '';
error_log("Telegram callback - chatId: $chatId, messageId: $messageId, userId: $userId, callbackData: $callbackData"); error_log("Telegram callback - chatId: $chatId, messageId: $messageId, userId: $userId, callbackData: $callbackData");
handleTelegramCallback($pdo, $sender, $translator, $chatId, $messageId, $callbackQuery['id'], $callbackData); handleTelegramCallback($pdo, $sender, $translator, $chatId, $messageId, $inlineMessageId, $callbackQuery['id'], $callbackData);
} }
} catch (Exception $e) { } catch (Exception $e) {
@@ -95,6 +96,10 @@ try {
function registerTelegramUser(PDO $pdo, array $user): void function registerTelegramUser(PDO $pdo, array $user): void
{ {
// Obtener idioma por defecto de la base de datos (el primero activo)
$stmtDefault = $pdo->query("SELECT language_code FROM supported_languages WHERE is_active = 1 LIMIT 1");
$defaultLang = $stmtDefault->fetchColumn() ?: 'es';
$stmt = $pdo->prepare(" $stmt = $pdo->prepare("
INSERT INTO recipients (platform_id, name, type, platform, language_code, chat_mode) INSERT INTO recipients (platform_id, name, type, platform, language_code, chat_mode)
VALUES (?, ?, 'user', 'telegram', ?, 'bot') VALUES (?, ?, 'user', 'telegram', ?, 'bot')
@@ -102,16 +107,15 @@ function registerTelegramUser(PDO $pdo, array $user): void
"); ");
$name = trim(($user['first_name'] ?? '') . ' ' . ($user['last_name'] ?? '')); $name = trim(($user['first_name'] ?? '') . ' ' . ($user['last_name'] ?? ''));
$languageCode = $user['language_code'] ?? 'es'; $languageCode = $user['language_code'] ?? $defaultLang;
$stmt->execute([$user['id'], $name, $languageCode]); $stmt->execute([$user['id'], $name, $languageCode]);
} }
function handleAutoTranslation(PDO $pdo, Telegram\TelegramSender $sender, src\Translate $translator, int $chatId, string $text): void function handleAutoTranslation(PDO $pdo, Telegram\TelegramSender $sender, src\Translate $translator, int $chatId, string $text): void
{ {
// Usar texto sin emojis para detección de idioma, pero guardar el original para mostrar // Usar el texto original completo para generar los botones y guardar en caché
$textForDetection = stripEmojisForDetection($text); $keyboard = getTelegramTranslationButtons($pdo, $text);
$keyboard = getTelegramTranslationButtons($pdo, $textForDetection ?: $text);
if (!empty($keyboard)) { if (!empty($keyboard)) {
$message = "🌐 <b>Traducciones disponibles:</b>\nHaz clic en una bandera para ver la traducción"; $message = "🌐 <b>Traducciones disponibles:</b>\nHaz clic en una bandera para ver la traducción";
@@ -119,10 +123,10 @@ function handleAutoTranslation(PDO $pdo, Telegram\TelegramSender $sender, src\Tr
} }
} }
function getTelegramTranslationButtons(PDO $pdo, string $text, ?string $excludeLang = null): ?array function getTelegramTranslationButtons(PDO $pdo, string $text): ?array
{ {
try { try {
$stmt = $pdo->query("SELECT language_code, flag_emoji FROM supported_languages WHERE is_active = 1"); $stmt = $pdo->query("SELECT language_code, flag_emoji FROM supported_languages WHERE is_active = 1 AND telegram_enabled = 1");
$activeLanguages = $stmt->fetchAll(); $activeLanguages = $stmt->fetchAll();
if (count($activeLanguages) <= 1) { if (count($activeLanguages) <= 1) {
@@ -135,10 +139,6 @@ function getTelegramTranslationButtons(PDO $pdo, string $text, ?string $excludeL
$buttons = []; $buttons = [];
foreach ($activeLanguages as $lang) { foreach ($activeLanguages as $lang) {
if ($excludeLang && $lang['language_code'] === $excludeLang) {
continue;
}
$callbackData = "translate:" . $lang['language_code'] . ":" . $textHash; $callbackData = "translate:" . $lang['language_code'] . ":" . $textHash;
$buttons[] = [ $buttons[] = [
@@ -360,7 +360,7 @@ function sendTemplateByCommand(PDO $pdo, Telegram\TelegramSender $sender, int $c
} }
} }
function handleTelegramCallback(PDO $pdo, Telegram\TelegramSender $sender, src\Translate $translator, int $chatId, int $messageId, string $callbackQueryId, string $callbackData): void function handleTelegramCallback(PDO $pdo, Telegram\TelegramSender $sender, src\Translate $translator, int $chatId, int $messageId, ?string $inlineMessageId, string $callbackQueryId, string $callbackData): void
{ {
$parts = explode(':', $callbackData, 3); $parts = explode(':', $callbackData, 3);
$action = $parts[0] ?? ''; $action = $parts[0] ?? '';
@@ -369,7 +369,7 @@ function handleTelegramCallback(PDO $pdo, Telegram\TelegramSender $sender, src\T
$targetLang = $parts[1] ?? 'es'; $targetLang = $parts[1] ?? 'es';
$textHash = $parts[2] ?? ''; $textHash = $parts[2] ?? '';
file_put_contents('/var/www/html/lastwar/logs/telegram_debug.log', date('Y-m-d H:i:s') . " - targetLang: $targetLang, textHash: $textHash\n", FILE_APPEND); file_put_contents(__DIR__ . '/../../logs/telegram_debug.log', date('Y-m-d H:i:s') . " - targetLang: $targetLang, textHash: $textHash\n", FILE_APPEND);
// Recuperar texto de la base de datos // Recuperar texto de la base de datos
$stmt = $pdo->prepare("SELECT original_text FROM translation_cache WHERE text_hash = ?"); $stmt = $pdo->prepare("SELECT original_text FROM translation_cache WHERE text_hash = ?");
@@ -377,7 +377,7 @@ function handleTelegramCallback(PDO $pdo, Telegram\TelegramSender $sender, src\T
$row = $stmt->fetch(); $row = $stmt->fetch();
if (!$row) { if (!$row) {
file_put_contents('/var/www/html/lastwar/logs/telegram_debug.log', date('Y-m-d H:i:s') . " - ERROR: No text found\n", FILE_APPEND); file_put_contents(__DIR__ . '/../../logs/telegram_debug.log', date('Y-m-d H:i:s') . " - ERROR: No text found\n", FILE_APPEND);
$sender->answerCallbackQuery($callbackQueryId, "❌ Error: Texto no encontrado"); $sender->answerCallbackQuery($callbackQueryId, "❌ Error: Texto no encontrado");
return; return;
} }
@@ -385,20 +385,17 @@ function handleTelegramCallback(PDO $pdo, Telegram\TelegramSender $sender, src\T
$originalText = $row['original_text']; $originalText = $row['original_text'];
if (empty($originalText)) { if (empty($originalText)) {
file_put_contents('/var/www/html/lastwar/logs/telegram_debug.log', date('Y-m-d H:i:s') . " - ERROR: Empty text\n", FILE_APPEND); file_put_contents(__DIR__ . '/../../logs/telegram_debug.log', date('Y-m-d H:i:s') . " - ERROR: Empty text\n", FILE_APPEND);
$sender->answerCallbackQuery($callbackQueryId, "❌ Error: No se pudo recuperar el texto"); $sender->answerCallbackQuery($callbackQueryId, "❌ Error: No se pudo recuperar el texto");
return; return;
} }
try { try {
// Obtener el idioma original (usar texto sin emojis para mayor precisión) // Traducción parcial - detecta el idioma de cada segmento y traduce solo lo necesario
$textForDetection = stripEmojisForDetection($originalText); $textForTranslation = stripEmojisForDetection($originalText);
$sourceLang = $translator->detectLanguage($textForDetection) ?? 'es'; $translated = $translator->translatePartial($textForTranslation ?: $originalText, $targetLang);
file_put_contents('/var/www/html/lastwar/logs/telegram_debug.log', date('Y-m-d H:i:s') . " - sourceLang: $sourceLang, targetLang: $targetLang, originalText: " . substr($originalText, 0, 50) . "\n", FILE_APPEND); file_put_contents(__DIR__ . '/../../logs/telegram_debug.log', date('Y-m-d H:i:s') . " - targetLang: $targetLang, originalText: " . substr($originalText, 0, 50) . "\n", FILE_APPEND);
file_put_contents(__DIR__ . '/../../logs/telegram_debug.log', date('Y-m-d H:i:s') . " - translated: $translated\n", FILE_APPEND);
// Traducir (usar texto sin emojis para evitar interferencias)
$translated = $translator->translate($textForDetection ?: $originalText, $sourceLang, $targetLang);
file_put_contents('/var/www/html/lastwar/logs/telegram_debug.log', date('Y-m-d H:i:s') . " - translated: $translated\n", FILE_APPEND);
if ($translated) { if ($translated) {
// Limpiar TODAS las etiquetas HTML problemáticas (con espacios como < b > o </ b >) // Limpiar TODAS las etiquetas HTML problemáticas (con espacios como < b > o </ b >)
@@ -422,16 +419,16 @@ function handleTelegramCallback(PDO $pdo, Telegram\TelegramSender $sender, src\T
$sender->answerCallbackQuery($callbackQueryId, "", false); $sender->answerCallbackQuery($callbackQueryId, "", false);
$keyboard = getTelegramTranslationButtons($pdo, $originalText, $targetLang); $keyboard = getTelegramTranslationButtons($pdo, $originalText);
$result = $sender->editMessageText($chatId, $messageId, "🌐 <b>Traducción (" . strtoupper($targetLang) . "):</b>\n\n" . $translated, $keyboard); $result = $sender->editMessageText($chatId, $messageId, "🌐 <b>Traducción (" . strtoupper($targetLang) . "):</b>\n\n" . $translated, $keyboard, 'HTML', $inlineMessageId);
file_put_contents('/var/www/html/lastwar/logs/telegram_debug.log', date('Y-m-d H:i:s') . " - editMessageText result: " . json_encode($result) . "\n", FILE_APPEND); file_put_contents(__DIR__ . '/../../logs/telegram_debug.log', date('Y-m-d H:i:s') . " - editMessageText result: " . json_encode($result) . "\n", FILE_APPEND);
} else { } else {
file_put_contents('/var/www/html/lastwar/logs/telegram_debug.log', date('Y-m-d H:i:s') . " - ERROR: Translation failed\n", FILE_APPEND); file_put_contents(__DIR__ . '/../../logs/telegram_debug.log', date('Y-m-d H:i:s') . " - ERROR: Translation failed\n", FILE_APPEND);
$sender->answerCallbackQuery($callbackQueryId, "❌ Error al traducir el mensaje", false); $sender->answerCallbackQuery($callbackQueryId, "❌ Error al traducir el mensaje", false);
} }
} catch (Exception $e) { } catch (Exception $e) {
file_put_contents('/var/www/html/lastwar/logs/telegram_debug.log', date('Y-m-d H:i:s') . " - EXCEPTION: " . $e->getMessage() . "\n", FILE_APPEND); file_put_contents(__DIR__ . '/../../logs/telegram_debug.log', date('Y-m-d H:i:s') . " - EXCEPTION: " . $e->getMessage() . "\n", FILE_APPEND);
$sender->answerCallbackQuery($callbackQueryId, "❌ Error en la traducción", false); $sender->answerCallbackQuery($callbackQueryId, "❌ Error en la traducción", false);
} }
} elseif ($action === 'chat_mode') { } elseif ($action === 'chat_mode') {

View File

@@ -1,5 +1,13 @@
</main> </main>
<footer class="text-center py-3 mt-auto" style="background: var(--military-dark); border-top: 1px solid var(--military-green);">
<small class="text-muted">
© <?= date('Y') ?> <strong>LASTWAR</strong> - <?= t('Sistema de Mensajería') ?> |
<?= t('Desarrollado por') ?> <a href="https://github.com/nickpons666" target="_blank" style="color: var(--accent-orange);">nickpons666</a> |
<a href="https://opensource.org/licenses/MIT" target="_blank" style="color: var(--military-sand);">MIT License</a>
</small>
</footer>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script> <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/summernote@0.8.18/dist/summernote-lite.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/summernote@0.8.18/dist/summernote-lite.min.js"></script>

View File

@@ -1,5 +1,7 @@
<?php <?php
session_start(); if (session_status() === PHP_SESSION_NONE) {
session_start();
}
require_once __DIR__ . '/../includes/session_check.php'; require_once __DIR__ . '/../includes/session_check.php';
require_once __DIR__ . '/../includes/url_helper.php'; require_once __DIR__ . '/../includes/url_helper.php';
require_once __DIR__ . '/../includes/i18n.php'; require_once __DIR__ . '/../includes/i18n.php';
@@ -131,6 +133,19 @@ $activeLanguages = getActiveLanguages();
background-color: var(--bs-body-bg); background-color: var(--bs-body-bg);
color: var(--bs-body-color); color: var(--bs-body-color);
padding-top: var(--navbar-height); padding-top: var(--navbar-height);
min-height: 100vh;
display: flex;
flex-direction: column;
}
.main-content {
flex: 1;
padding: 1.5rem;
width: 100%;
}
footer {
margin-top: auto;
} }
.navbar-military { .navbar-military {
@@ -286,11 +301,6 @@ $activeLanguages = getActiveLanguages();
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba(196, 185, 152, 1)' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e"); background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba(196, 185, 152, 1)' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e");
} }
.main-content {
padding: 1.5rem;
width: 100%;
}
.card { .card {
background-color: var(--bs-card-bg); background-color: var(--bs-card-bg);
border: 1px solid var(--military-green); border: 1px solid var(--military-green);
@@ -690,11 +700,13 @@ $activeLanguages = getActiveLanguages();
</a> </a>
</li> </li>
<?php foreach ($activeLanguages as $lang): ?> <?php foreach ($activeLanguages as $lang): ?>
<?php if ($lang['language_code'] !== 'es'): ?>
<li> <li>
<a class="dropdown-item <?= $currentLang === $lang['language_code'] ? 'active' : '' ?>" href="?lang=<?= urlencode($lang['language_code']) ?>"> <a class="dropdown-item <?= $currentLang === $lang['language_code'] ? 'active' : '' ?>" href="?lang=<?= urlencode($lang['language_code']) ?>">
<?= htmlspecialchars($lang['flag_emoji']) ?> <?= t($lang['language_name']) ?> <?= htmlspecialchars($lang['flag_emoji']) ?> <?= t($lang['language_name']) ?>
</a> </a>
</li> </li>
<?php endif; ?>
<?php endforeach; ?> <?php endforeach; ?>
</ul> </ul>
</li> </li>

View File

@@ -25,7 +25,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$translatedText = $translator->translate($sourceText, $sourceLang, $targetLang); $translatedText = $translator->translate($sourceText, $sourceLang, $targetLang);
if (!$translatedText) { if (!$translatedText) {
$error = 'Error al traducir. Verifica la conexión con LibreTranslate.'; $error = t('Error al traducir. Verifica la conexión con LibreTranslate.');
} }
} catch (Exception $e) { } catch (Exception $e) {
$error = $e->getMessage(); $error = $e->getMessage();
@@ -45,7 +45,7 @@ require_once __DIR__ . '/templates/header.php';
?> ?>
<div class="d-flex justify-content-between align-items-center mb-4"> <div class="d-flex justify-content-between align-items-center mb-4">
<h2><i class="bi bi-translate"></i> Traducir Mensaje</h2> <h2><i class="bi bi-translate"></i> <?= t('Traducir Mensaje') ?></h2>
</div> </div>
<?php if ($error): ?> <?php if ($error): ?>
@@ -55,13 +55,13 @@ require_once __DIR__ . '/templates/header.php';
<div class="row"> <div class="row">
<div class="col-md-6"> <div class="col-md-6">
<div class="card border-0 shadow-sm"> <div class="card border-0 shadow-sm">
<div class="card-header bg-white border-0"> <div class="card-header border-0">
<h5 class="mb-0">Texto Original</h5> <h5 class="mb-0"><?= t('Texto Original') ?></h5>
</div> </div>
<div class="card-body"> <div class="card-body">
<form method="POST"> <form method="POST">
<div class="mb-3"> <div class="mb-3">
<label class="form-label">Idioma de origen</label> <label class="form-label"><?= t('Idioma de origen') ?></label>
<select name="source_lang" class="form-select"> <select name="source_lang" class="form-select">
<?php foreach ($languages as $lang): ?> <?php foreach ($languages as $lang): ?>
<option value="<?= $lang['language_code'] ?>" <?= $sourceLang === $lang['language_code'] ? 'selected' : '' ?>> <option value="<?= $lang['language_code'] ?>" <?= $sourceLang === $lang['language_code'] ? 'selected' : '' ?>>
@@ -72,12 +72,12 @@ require_once __DIR__ . '/templates/header.php';
</div> </div>
<div class="mb-3"> <div class="mb-3">
<label class="form-label">Texto a traducir</label> <label class="form-label"><?= t('Texto a traducir') ?></label>
<textarea name="source_text" class="form-control" rows="8" required><?= htmlspecialchars($sourceText) ?></textarea> <textarea name="source_text" class="form-control" rows="8" required><?= htmlspecialchars($sourceText) ?></textarea>
</div> </div>
<div class="mb-3"> <div class="mb-3">
<label class="form-label">Idioma de destino</label> <label class="form-label"><?= t('Idioma de destino') ?></label>
<select name="target_lang" class="form-select"> <select name="target_lang" class="form-select">
<?php foreach ($languages as $lang): ?> <?php foreach ($languages as $lang): ?>
<option value="<?= $lang['language_code'] ?>" <?= $targetLang === $lang['language_code'] ? 'selected' : '' ?>> <option value="<?= $lang['language_code'] ?>" <?= $targetLang === $lang['language_code'] ? 'selected' : '' ?>>
@@ -88,7 +88,7 @@ require_once __DIR__ . '/templates/header.php';
</div> </div>
<button type="submit" class="btn btn-primary"> <button type="submit" class="btn btn-primary">
<i class="bi bi-translate"></i> Traducir <i class="bi bi-translate"></i> <?= t('Traducir') ?>
</button> </button>
</form> </form>
</div> </div>
@@ -97,17 +97,17 @@ require_once __DIR__ . '/templates/header.php';
<div class="col-md-6"> <div class="col-md-6">
<div class="card border-0 shadow-sm"> <div class="card border-0 shadow-sm">
<div class="card-header bg-white border-0"> <div class="card-header border-0">
<h5 class="mb-0">Traducción</h5> <h5 class="mb-0"><?= t('Traducción') ?></h5>
</div> </div>
<div class="card-body"> <div class="card-body">
<?php if ($translatedText): ?> <?php if ($translatedText): ?>
<textarea class="form-control" rows="12" readonly><?= htmlspecialchars($translatedText) ?></textarea> <textarea class="form-control" rows="12" readonly><?= htmlspecialchars($translatedText) ?></textarea>
<button class="btn btn-outline-secondary mt-2" onclick="navigator.clipboard.writeText('<?= htmlspecialchars(addslashes($translatedText)) ?>')"> <button class="btn btn-outline-secondary mt-2" onclick="navigator.clipboard.writeText('<?= htmlspecialchars(addslashes($translatedText)) ?>')">
<i class="bi bi-clipboard"></i> Copiar <i class="bi bi-clipboard"></i> <?= t('Copiar') ?>
</button> </button>
<?php else: ?> <?php else: ?>
<p class="text-muted text-center py-5">Traducción aparecerá aquí</p> <p class="text-muted text-center py-5"><?= t('Traducción aparecerá aquí') ?></p>
<?php endif; ?> <?php endif; ?>
</div> </div>
</div> </div>