Compare commits
8 Commits
e8213b916b
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 223c44f1d8 | |||
| 8170931f3d | |||
| bf960f3fc3 | |||
| 082e01b358 | |||
| c439ecd689 | |||
| 2dcff94a22 | |||
| 880940f515 | |||
| 509c7e1709 |
48
.dockerignore
Normal file
48
.dockerignore
Normal 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
81
README.md
Normal file → Executable file
@@ -1,14 +1,30 @@
|
||||
# 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
|
||||
|
||||
### Mensajería
|
||||
- **Discord Bot**: Envío de mensajes, traducción automática, comandos (#lista)
|
||||
- **Telegram Bot**: Webhook para mensajes, traducción con botones inline
|
||||
- **Traducción Automática**: LibreTranslate con detección de idioma
|
||||
- **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
|
||||
|
||||
@@ -16,6 +32,9 @@ Sistema de mensajería automatizada para Discord y Telegram con traducción auto
|
||||
- MySQL 8.0+
|
||||
- Composer
|
||||
- Servidor web (Apache/Nginx)
|
||||
- LibreTranslate (para traducciones)
|
||||
- Cuenta de Discord Developer
|
||||
- Bot de Telegram
|
||||
|
||||
## 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
|
||||
4. Importar estructura de base de datos
|
||||
5. Configurar webhooks de Telegram y Discord
|
||||
6. Configurar idiomas activos en el panel admin
|
||||
|
||||
## Variables de Entorno
|
||||
|
||||
```env
|
||||
# Aplicación
|
||||
APP_ENV=production
|
||||
APP_URL=https://tudominio.com
|
||||
TZ=America/Mexico_City
|
||||
|
||||
# Base de datos
|
||||
DB_HOST=localhost
|
||||
DB_PORT=3306
|
||||
@@ -37,8 +62,12 @@ DB_PASS=
|
||||
|
||||
# Telegram
|
||||
TELEGRAM_BOT_TOKEN=
|
||||
TELEGRAM_WEBHOOK_TOKEN=
|
||||
|
||||
# Discord
|
||||
DISCORD_GUILD_ID=
|
||||
DISCORD_CLIENT_ID=
|
||||
DISCORD_CLIENT_SECRET=
|
||||
DISCORD_BOT_TOKEN=
|
||||
|
||||
# LibreTranslate
|
||||
@@ -47,38 +76,76 @@ LIBRETRANSLATE_URL=http://localhost:5000
|
||||
# IA (Groq)
|
||||
GROQ_API_KEY=
|
||||
|
||||
# Knowledge Base
|
||||
# Knowledge Base (para IA)
|
||||
KB_DB_HOST=
|
||||
KB_DB_PORT=
|
||||
KB_DB_NAME=
|
||||
KB_DB_USER=
|
||||
KB_DB_PASS=
|
||||
|
||||
# JWT
|
||||
JWT_SECRET=
|
||||
```
|
||||
|
||||
## Comandos
|
||||
|
||||
### Telegram
|
||||
- `#lista` - Enviar plantilla de lista
|
||||
- `hola` - Mostrar botones de traducción
|
||||
- `/start` - Iniciar el bot
|
||||
- `#plantilla` - Enviar plantilla por nombre
|
||||
- `/comandos` - Ver comandos disponibles
|
||||
- `/setlang [código]` - Cambiar idioma
|
||||
- `/bienvenida` - Mensaje de bienvenida
|
||||
- `/agente` - Activar modo IA
|
||||
|
||||
### Discord
|
||||
- `#lista` - Enviar plantilla de lista
|
||||
- `#plantilla` - Enviar plantilla por nombre
|
||||
- `/comandos` - Ver comandos disponibles
|
||||
- `/setlang [código]` - Cambiar idioma
|
||||
- `/bienvenida` - Mensaje de bienvenida
|
||||
- `/agente` - Activar modo IA
|
||||
|
||||
## Estructura
|
||||
|
||||
```
|
||||
├── 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
|
||||
│ ├── senders/ # Enviadores de mensajes
|
||||
│ └── converters/ # Conversores de formato
|
||||
├── 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
|
||||
│ ├── admin/ # Configuración Telegram
|
||||
│ ├── senders/ # Enviadores de mensajes
|
||||
│ └── converters/ # Conversores de formato
|
||||
├── templates/ # Plantillas HTML
|
||||
│ ├── header.php # Cabecera con navbar
|
||||
│ └── footer.php # Pie de página
|
||||
├── galeria/ # Imágenes para mensajes
|
||||
├── logs/ # Logs del sistema
|
||||
└── *.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
|
||||
|
||||
MIT
|
||||
|
||||
@@ -83,6 +83,22 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) {
|
||||
} catch (Exception $e) {
|
||||
$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">
|
||||
<h2><i class="bi bi-translate"></i> Gestión de Idiomas</h2>
|
||||
<div>
|
||||
<h2><i class="bi bi-translate"></i> <?= t('Gestión de Idiomas') ?></h2>
|
||||
<div class="d-flex gap-2">
|
||||
<form method="POST" class="d-inline">
|
||||
<input type="hidden" name="action" value="sync_libretranslate">
|
||||
<button type="submit" class="btn btn-outline-primary">
|
||||
<i class="bi bi-cloud-download"></i> Sincronizar con LibreTranslate
|
||||
<button type="submit" class="btn btn-outline-secondary">
|
||||
<i class="bi bi-arrow-repeat"></i> <?= t('Sincronizar con LibreTranslate') ?>
|
||||
</button>
|
||||
</form>
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
@@ -290,11 +306,13 @@ require_once __DIR__ . '/../templates/header.php';
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Bandera</th>
|
||||
<th>Código</th>
|
||||
<th>Nombre</th>
|
||||
<th>Estado</th>
|
||||
<th>Acciones</th>
|
||||
<th><?= t('Bandera') ?></th>
|
||||
<th><?= t('Código') ?></th>
|
||||
<th><?= t('Nombre') ?></th>
|
||||
<th><?= t('Estado') ?></th>
|
||||
<th><?= t('Telegram') ?></th>
|
||||
<th><?= t('Discord') ?></th>
|
||||
<th><?= t('Acciones') ?></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -305,9 +323,35 @@ require_once __DIR__ . '/../templates/header.php';
|
||||
<td><?= htmlspecialchars($lang['language_name']) ?></td>
|
||||
<td>
|
||||
<?php if ($lang['is_active']): ?>
|
||||
<span class="badge bg-success">Activo</span>
|
||||
<span class="badge bg-success"><?= t('Activo') ?></span>
|
||||
<?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; ?>
|
||||
</td>
|
||||
<td>
|
||||
@@ -320,7 +364,7 @@ require_once __DIR__ . '/../templates/header.php';
|
||||
</form>
|
||||
|
||||
<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>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -333,17 +377,17 @@ require_once __DIR__ . '/../templates/header.php';
|
||||
<input type="hidden" name="action" value="update_flag">
|
||||
<input type="hidden" name="id" value="<?= $lang['id'] ?>">
|
||||
<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>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<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>
|
||||
|
||||
<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="row g-2">
|
||||
<?php foreach ($availableFlags as $flag): ?>
|
||||
@@ -360,14 +404,14 @@ require_once __DIR__ . '/../templates/header.php';
|
||||
</div>
|
||||
|
||||
<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="🇲🇽">
|
||||
<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 class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancelar</button>
|
||||
<button type="submit" class="btn btn-primary">Guardar</button>
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal"><?= t('Cancelar') ?></button>
|
||||
<button type="submit" class="btn btn-primary"><?= t('Guardar') ?></button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
@@ -395,20 +439,20 @@ require_once __DIR__ . '/../templates/header.php';
|
||||
<form method="POST">
|
||||
<input type="hidden" name="action" value="add">
|
||||
<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>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<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">
|
||||
</div>
|
||||
<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>
|
||||
</div>
|
||||
<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="row g-2">
|
||||
<?php foreach ($availableFlags as $flag): ?>
|
||||
@@ -424,12 +468,12 @@ require_once __DIR__ . '/../templates/header.php';
|
||||
</div>
|
||||
</div>
|
||||
<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="🇲🇽">
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="submit" class="btn btn-primary">Agregar</button>
|
||||
<button type="submit" class="btn btn-primary"><?= t('Agregar') ?></button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -10,16 +10,19 @@ requireAdmin();
|
||||
|
||||
function getTranslationButtons(PDO $pdo, string $text): array
|
||||
{
|
||||
$stmt = $pdo->query("SELECT language_code, flag_emoji FROM supported_languages WHERE is_active = 1");
|
||||
$languages = $stmt->fetchAll();
|
||||
$stmtTelegram = $pdo->query("SELECT language_code, flag_emoji FROM supported_languages WHERE is_active = 1 AND telegram_enabled = 1");
|
||||
$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 [
|
||||
'telegram' => buildTelegramTranslationButtons($pdo, $languages, $text),
|
||||
'discord' => buildDiscordTranslationButtons($languages, $text)
|
||||
'telegram' => count($telegramLanguages) > 1 ? buildTelegramTranslationButtons($pdo, $telegramLanguages, $text) : [],
|
||||
'discord' => count($discordLanguages) > 1 ? buildDiscordTranslationButtons($discordLanguages, $text) : []
|
||||
];
|
||||
}
|
||||
|
||||
@@ -140,32 +143,45 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$sender = \Common\Helpers\SenderFactory::create($schedule['platform']);
|
||||
|
||||
// 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 = preg_replace('/\s+/', ' ', $plainText); // Normalizar espacios
|
||||
$plainText = $schedule['content'];
|
||||
// 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);
|
||||
|
||||
// Parsear el contenido HTML en segmentos manteniendo el orden
|
||||
$segments = $sender->parseContent($schedule['content']);
|
||||
|
||||
$messageCount = 0;
|
||||
$totalSegments = count($segments);
|
||||
$currentSegment = 0;
|
||||
|
||||
// Enviar cada segmento en el orden correcto
|
||||
foreach ($segments as $segment) {
|
||||
$currentSegment++;
|
||||
$isLastSegment = ($currentSegment === $totalSegments);
|
||||
|
||||
if ($segment['type'] === 'text') {
|
||||
// Convertir el texto al formato de la plataforma
|
||||
$textContent = \Common\Helpers\ConverterFactory::convert($schedule['platform'], $segment['content']);
|
||||
|
||||
if (!empty(trim($textContent))) {
|
||||
// Agregar botones de traducción al último segmento de texto
|
||||
$buttons = null;
|
||||
if ($segment === end($segments)) {
|
||||
$buttons = $schedule['platform'] === 'telegram'
|
||||
? $translationButtons['telegram']
|
||||
: $translationButtons['discord'];
|
||||
if ($isLastSegment && $schedule['platform'] !== 'telegram') {
|
||||
$buttons = $translationButtons['discord'];
|
||||
}
|
||||
|
||||
if ($schedule['platform'] === 'telegram') {
|
||||
$sender->sendMessage($schedule['platform_id'], $textContent, $buttons);
|
||||
$sender->sendMessage($schedule['platform_id'], $textContent);
|
||||
} else {
|
||||
$sender->sendMessage($schedule['platform_id'], $textContent, null, $buttons);
|
||||
}
|
||||
@@ -174,22 +190,43 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
} elseif ($segment['type'] === 'image') {
|
||||
$imagePath = $segment['src'];
|
||||
|
||||
// Quitar parámetros de URL si los hay
|
||||
$imgPath = parse_url($imagePath, PHP_URL_PATH) ?: $imagePath;
|
||||
$appUrl = $_ENV['APP_URL'] ?? getenv('APP_URL') ?? '';
|
||||
$baseUrl = rtrim($appUrl, '/');
|
||||
|
||||
if (file_exists($imgPath)) {
|
||||
// Es un archivo local
|
||||
$sender->sendMessageWithAttachments($schedule['platform_id'], '', [$imgPath]);
|
||||
$messageCount++;
|
||||
} elseif (strpos($imagePath, 'http') === 0) {
|
||||
// Es una URL remota
|
||||
$embed = ['image' => ['url' => $imagePath]];
|
||||
$sender->sendMessage($schedule['platform_id'], '', $embed);
|
||||
$buttons = null;
|
||||
if ($isLastSegment && $schedule['platform'] !== 'telegram') {
|
||||
$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++;
|
||||
} 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("
|
||||
INSERT INTO sent_messages (schedule_id, recipient_id, platform_message_id, message_count, sent_at)
|
||||
VALUES (?, ?, '', ?, NOW())
|
||||
|
||||
@@ -10,16 +10,19 @@ require_once __DIR__ . '/includes/i18n.php';
|
||||
|
||||
function getTranslationButtons(PDO $pdo, string $text): array
|
||||
{
|
||||
$stmt = $pdo->query("SELECT language_code, flag_emoji FROM supported_languages WHERE is_active = 1");
|
||||
$languages = $stmt->fetchAll();
|
||||
$stmtTelegram = $pdo->query("SELECT language_code, flag_emoji FROM supported_languages WHERE is_active = 1 AND telegram_enabled = 1");
|
||||
$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 [
|
||||
'telegram' => buildTelegramTranslationButtons($pdo, $languages, $text),
|
||||
'discord' => buildDiscordTranslationButtons($languages, $text)
|
||||
'telegram' => count($telegramLanguages) > 1 ? buildTelegramTranslationButtons($pdo, $telegramLanguages, $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)
|
||||
$plainText = $schedule['content'];
|
||||
// Convertir saltos de párrafo a saltos de línea
|
||||
$plainText = preg_replace('/<\/p>/i', "\n", $plainText);
|
||||
// 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
|
||||
// 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
|
||||
// Limpiar espacios múltiples pero preservar saltos de línea (máximo 2)
|
||||
$plainText = preg_replace('/[ \t]+/', ' ', $plainText);
|
||||
$plainText = preg_replace('/\n\s*\n/', "\n", $plainText);
|
||||
$plainText = preg_replace('/\n{3,}/', "\n\n", $plainText);
|
||||
$plainText = trim($plainText);
|
||||
$translationButtons = getTranslationButtons($pdo, $plainText);
|
||||
|
||||
$segments = $sender->parseContent($schedule['content']);
|
||||
$messageCount = 0;
|
||||
$totalSegments = count($segments);
|
||||
$currentSegment = 0;
|
||||
|
||||
foreach ($segments as $segment) {
|
||||
$currentSegment++;
|
||||
$isLastSegment = ($currentSegment === $totalSegments);
|
||||
|
||||
if ($segment['type'] === 'text') {
|
||||
$textContent = \Common\Helpers\ConverterFactory::convert($schedule['platform'], $segment['content']);
|
||||
if (!empty(trim($textContent))) {
|
||||
// Agregar botones de traducción al último segmento de texto
|
||||
$buttons = null;
|
||||
if ($segment === end($segments)) {
|
||||
$buttons = $schedule['platform'] === 'telegram'
|
||||
? $translationButtons['telegram']
|
||||
: $translationButtons['discord'];
|
||||
if ($isLastSegment && $schedule['platform'] !== 'telegram') {
|
||||
$buttons = $translationButtons['discord'];
|
||||
}
|
||||
|
||||
if ($schedule['platform'] === 'telegram') {
|
||||
$sender->sendMessage($schedule['platform_id'], $textContent, $buttons);
|
||||
$sender->sendMessage($schedule['platform_id'], $textContent);
|
||||
} else {
|
||||
$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') {
|
||||
$imagePath = $segment['src'];
|
||||
$imgPath = parse_url($imagePath, PHP_URL_PATH) ?: $imagePath;
|
||||
|
||||
if (file_exists($imgPath)) {
|
||||
$sender->sendMessageWithAttachments($schedule['platform_id'], '', [$imgPath]);
|
||||
$messageCount++;
|
||||
} elseif (strpos($imagePath, 'http') === 0) {
|
||||
$embed = ['image' => ['url' => $imagePath]];
|
||||
$sender->sendMessage($schedule['platform_id'], '', $embed);
|
||||
$appUrl = $_ENV['APP_URL'] ?? getenv('APP_URL') ?? '';
|
||||
$baseUrl = rtrim($appUrl, '/');
|
||||
|
||||
$buttons = null;
|
||||
if ($isLastSegment && $schedule['platform'] !== 'telegram') {
|
||||
$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++;
|
||||
} 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("
|
||||
INSERT INTO sent_messages (schedule_id, recipient_id, platform_message_id, message_count, sent_at)
|
||||
VALUES (?, ?, '', ?, NOW())
|
||||
|
||||
0
database/.gitkeep
Normal file
0
database/.gitkeep
Normal file
101
discord_bot.php
101
discord_bot.php
@@ -34,14 +34,19 @@ $discord->on(Event::GUILD_MEMBER_ADD, function (Member $member, Discord $discord
|
||||
try {
|
||||
$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("
|
||||
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)
|
||||
");
|
||||
$stmt->execute([
|
||||
$member->user->id,
|
||||
$member->user->username
|
||||
$member->user->username,
|
||||
$defaultLang
|
||||
]);
|
||||
|
||||
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
|
||||
{
|
||||
$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();
|
||||
|
||||
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)
|
||||
$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
|
||||
$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();
|
||||
|
||||
if (count($activeLanguages) <= 1) {
|
||||
@@ -356,13 +366,11 @@ function handleAutoTranslationWithButtons(PDO $pdo, Message $message, string $te
|
||||
// Preparar botones
|
||||
$buttons = [];
|
||||
foreach ($activeLanguages as $lang) {
|
||||
if ($lang['language_code'] !== $detectedLang) {
|
||||
$buttons[] = [
|
||||
'label' => $lang['flag_emoji'] . ' ' . strtoupper($lang['language_code']),
|
||||
'custom_id' => 'translate_' . $lang['language_code'] . ':' . $textHash,
|
||||
'style' => 1
|
||||
];
|
||||
}
|
||||
$buttons[] = [
|
||||
'label' => $lang['flag_emoji'] . ' ' . strtoupper($lang['language_code']),
|
||||
'custom_id' => 'translate_' . $lang['language_code'] . ':' . $textHash,
|
||||
'style' => 1
|
||||
];
|
||||
}
|
||||
|
||||
// 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
|
||||
{
|
||||
// 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("
|
||||
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)
|
||||
");
|
||||
|
||||
$name = $user->username ?? 'Usuario';
|
||||
$stmt->execute([$user->id, $name]);
|
||||
$stmt->execute([$user->id, $name, $defaultLang]);
|
||||
}
|
||||
|
||||
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]);
|
||||
$textHash = $parts[1] ?? '';
|
||||
|
||||
// Obtener texto de la base de datos primero
|
||||
$stmt = $pdo->prepare("SELECT original_text FROM translation_cache WHERE text_hash = ?");
|
||||
$stmt->execute([$textHash]);
|
||||
$row = $stmt->fetch();
|
||||
|
||||
if (!$row || empty($row['original_text'])) {
|
||||
// Enviar error efímero
|
||||
$builder = \Discord\Builders\MessageBuilder::new()
|
||||
->setContent('❌ Error: Texto no encontrado');
|
||||
$interaction->respondWithMessage($builder, true);
|
||||
@@ -523,34 +533,24 @@ function handleTranslateInteraction($interaction, string $customId): void
|
||||
|
||||
$originalText = $row['original_text'];
|
||||
|
||||
// Responder inmediatamente con "Traduciendo..."
|
||||
$loadingBuilder = \Discord\Builders\MessageBuilder::new()
|
||||
->setContent("⏳ Traduciendo a " . strtoupper($targetLang) . "...");
|
||||
$interaction->respondWithMessage($loadingBuilder, true);
|
||||
try {
|
||||
$interaction->acknowledge();
|
||||
} catch (\Exception $e) {
|
||||
error_log("Acknowledge error: " . $e->getMessage());
|
||||
}
|
||||
|
||||
// Ahora traducir (esto puede tardar)
|
||||
// Usar texto sin emojis para detectar idioma y traducir con mayor precisión
|
||||
$textForDetection = stripEmojisForDetection($originalText);
|
||||
$sourceLang = $translator->detectLanguage($textForDetection) ?? 'es';
|
||||
$translated = $translator->translate($textForDetection, $sourceLang, $targetLang);
|
||||
$textForTranslation = stripEmojisForDetection($originalText);
|
||||
$translated = $translator->translatePartial($textForTranslation, $targetLang);
|
||||
|
||||
if ($translated) {
|
||||
// Limpiar todo el HTML
|
||||
$translated = strip_tags($translated);
|
||||
// Limpiar asteriscos duplicados
|
||||
$translated = preg_replace('/\*+/', '', $translated);
|
||||
// Limpiar comandos con espacios
|
||||
$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);
|
||||
|
||||
// Actualizar la respuesta con la traducción real
|
||||
// Preservar emojis del original si los hay
|
||||
$displayText = trim($translated);
|
||||
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 = '';
|
||||
$emojiSuffix = '';
|
||||
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;
|
||||
}
|
||||
|
||||
$finalBuilder = \Discord\Builders\MessageBuilder::new()
|
||||
->setContent("🌐 **Traducción (" . strtoupper($targetLang) . "):**\n\n" . $displayText);
|
||||
$interaction->updateOriginalResponse($finalBuilder);
|
||||
$messageText = "🌐 **Traducción (" . strtoupper($targetLang) . "):**\n\n" . $displayText;
|
||||
|
||||
$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 {
|
||||
// Actualizar con error
|
||||
$errorBuilder = \Discord\Builders\MessageBuilder::new()
|
||||
->setContent("❌ Error al traducir");
|
||||
$interaction->updateOriginalResponse($errorBuilder);
|
||||
$interaction->message->edit(\Discord\Builders\MessageBuilder::new()->setContent("❌ Error al traducir"));
|
||||
}
|
||||
|
||||
} catch (Exception $e) {
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 \
|
||||
libcurl4-openssl-dev \
|
||||
libzip-dev \
|
||||
libpng-dev \
|
||||
libjpeg-dev \
|
||||
libfreetype6-dev \
|
||||
unzip \
|
||||
supervisor \
|
||||
nano \
|
||||
&& pecl install curl \
|
||||
&& docker-php-ext-enable curl \
|
||||
&& docker-php-ext-install pdo_mysql zip \
|
||||
cron \
|
||||
&& docker-php-ext-configure gd --with-freetype --with-jpeg \
|
||||
&& docker-php-ext-install pdo_mysql zip gd curl \
|
||||
&& pecl install redis \
|
||||
&& docker-php-ext-enable redis \
|
||||
&& apt-get clean \
|
||||
&& 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
|
||||
|
||||
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
33
docker/apache.conf
Normal 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>
|
||||
@@ -1,34 +1,52 @@
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
bot:
|
||||
image: php:8.2-cli
|
||||
container_name: lastwar_bot
|
||||
app:
|
||||
build:
|
||||
context: ..
|
||||
dockerfile: docker/Dockerfile
|
||||
container_name: lastwar_app
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- ../lastwar:/var/www/html/lastwar
|
||||
working_dir: /var/www/html/lastwar
|
||||
command: /usr/local/bin/supervisord -c /var/www/html/lastwar/docker/supervisord.conf
|
||||
ports:
|
||||
- "8080:80"
|
||||
env_file:
|
||||
- ../.env
|
||||
environment:
|
||||
- PHP_DISPLAY_ERRORS=On
|
||||
- PHP_ERROR_REPORTING=E_ALL
|
||||
- DB_HOST=db
|
||||
- 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:
|
||||
- bot_network
|
||||
- lastwar_network
|
||||
depends_on:
|
||||
- db
|
||||
db:
|
||||
condition: service_healthy
|
||||
libretranslate:
|
||||
condition: service_started
|
||||
|
||||
db:
|
||||
image: mysql:8.0
|
||||
container_name: lastwar_db
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: ${DB_PASS:-}
|
||||
MYSQL_DATABASE: ${DB_NAME:-bot}
|
||||
MYSQL_ROOT_PASSWORD: ${DB_PASS:-rootpassword}
|
||||
MYSQL_DATABASE: ${DB_NAME:-lastwar}
|
||||
MYSQL_USER: ${DB_USER:-lastwar}
|
||||
MYSQL_PASSWORD: ${DB_PASS:-}
|
||||
volumes:
|
||||
- db_data:/var/lib/mysql
|
||||
- ./db:/docker-entrypoint-initdb.d
|
||||
- ../database:/docker-entrypoint-initdb.d:ro
|
||||
networks:
|
||||
- bot_network
|
||||
- lastwar_network
|
||||
healthcheck:
|
||||
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
|
||||
timeout: 20s
|
||||
retries: 10
|
||||
|
||||
libretranslate:
|
||||
image: libretranslate/libretranslate
|
||||
@@ -37,11 +55,18 @@ services:
|
||||
ports:
|
||||
- "5000:5000"
|
||||
networks:
|
||||
- bot_network
|
||||
- lastwar_network
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "curl -f http://localhost:5000/languages || exit 1"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
|
||||
networks:
|
||||
bot_network:
|
||||
lastwar_network:
|
||||
driver: bridge
|
||||
|
||||
volumes:
|
||||
db_data:
|
||||
app_logs:
|
||||
app_galeria:
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -1,44 +1,61 @@
|
||||
[unix_http_server]
|
||||
file=/var/run/supervisor.sock
|
||||
chmod=0700
|
||||
|
||||
[supervisord]
|
||||
nodaemon=true
|
||||
logfile=/var/www/html/lastwar/logs/supervisor.log
|
||||
logfile=/var/www/html/logs/supervisor.log
|
||||
logfile_maxbytes=50MB
|
||||
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]
|
||||
process_name=%(program_name)s
|
||||
command=php /var/www/html/lastwar/discord_bot.php
|
||||
command=php /var/www/html/discord_bot.php
|
||||
autostart=true
|
||||
autorestart=true
|
||||
user=www-data
|
||||
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
|
||||
stderr_logfile=/var/www/html/lastwar/logs/discord_bot_error.log
|
||||
stderr_logfile=/var/www/html/logs/discord_bot_error.log
|
||||
redirect_stderr=true
|
||||
|
||||
[program:bot_process_queue]
|
||||
process_name=%(program_name)s
|
||||
command=php /var/www/html/lastwar/process_queue.php
|
||||
command=php /var/www/html/process_queue.php
|
||||
autostart=true
|
||||
autorestart=true
|
||||
user=www-data
|
||||
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
|
||||
stderr_logfile=/var/www/html/lastwar/logs/process_queue_error.log
|
||||
stderr_logfile=/var/www/html/logs/process_queue_error.log
|
||||
redirect_stderr=true
|
||||
|
||||
[program:bot_translation_queue]
|
||||
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
|
||||
autorestart=true
|
||||
user=www-data
|
||||
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
|
||||
stderr_logfile=/var/www/html/lastwar/logs/translation_queue_error.log
|
||||
stderr_logfile=/var/www/html/logs/translation_queue_error.log
|
||||
redirect_stderr=true
|
||||
|
||||
[group:bot_workers]
|
||||
|
||||
0
galeria/.gitkeep
Normal file
0
galeria/.gitkeep
Normal file
@@ -2,6 +2,7 @@
|
||||
require_once __DIR__ . '/includes/db.php';
|
||||
require_once __DIR__ . '/includes/session_check.php';
|
||||
require_once __DIR__ . '/includes/i18n.php';
|
||||
require_once __DIR__ . '/includes/activity_logger.php';
|
||||
checkSession();
|
||||
|
||||
$pageTitle = t('Galería de Imágenes');
|
||||
|
||||
11
login.php
11
login.php
@@ -2,12 +2,9 @@
|
||||
require_once __DIR__ . '/includes/db.php';
|
||||
require_once __DIR__ . '/includes/env_loader.php';
|
||||
require_once __DIR__ . '/includes/auth.php';
|
||||
require_once __DIR__ . '/includes/i18n.php';
|
||||
|
||||
handleLanguageChange();
|
||||
|
||||
$domain = $_ENV['APP_URL'] ?? getenv('APP_URL') ?? '';
|
||||
if ($domain) {
|
||||
if ($domain && session_status() === PHP_SESSION_NONE) {
|
||||
$parsed = parse_url($domain);
|
||||
$host = $parsed['host'] ?? '';
|
||||
if ($host) {
|
||||
@@ -22,7 +19,9 @@ if ($domain) {
|
||||
}
|
||||
}
|
||||
|
||||
session_start();
|
||||
require_once __DIR__ . '/includes/i18n.php';
|
||||
|
||||
handleLanguageChange();
|
||||
|
||||
if (isset($_SESSION['user_id'])) {
|
||||
header('Location: index.php');
|
||||
@@ -341,7 +340,9 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
<ul class="dropdown-menu dropdown-menu-end">
|
||||
<li><a class="dropdown-item <?= $currentLang === 'es' ? 'active' : '' ?>" href="?lang=es">🇲🇽 <?= t('Español') ?></a></li>
|
||||
<?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>
|
||||
<?php endif; ?>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
0
logs/.gitkeep
Normal file
0
logs/.gitkeep
Normal file
@@ -13,16 +13,19 @@ define('SLEEP_INTERVAL', 5);
|
||||
|
||||
function getTranslationButtons(PDO $pdo, string $text): array
|
||||
{
|
||||
$stmt = $pdo->query("SELECT language_code, flag_emoji FROM supported_languages WHERE is_active = 1");
|
||||
$languages = $stmt->fetchAll();
|
||||
$stmtTelegram = $pdo->query("SELECT language_code, flag_emoji FROM supported_languages WHERE is_active = 1 AND telegram_enabled = 1");
|
||||
$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 [
|
||||
'telegram' => buildTelegramTranslationButtons($pdo, $languages, $text),
|
||||
'discord' => buildDiscordTranslationButtons($languages, $text)
|
||||
'telegram' => count($telegramLanguages) > 1 ? buildTelegramTranslationButtons($pdo, $telegramLanguages, $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)
|
||||
$plainText = $schedule['content'];
|
||||
// Convertir saltos de párrafo a saltos de línea
|
||||
$plainText = preg_replace('/<\/p>/i', "\n", $plainText);
|
||||
// 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
|
||||
// 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
|
||||
// Limpiar espacios múltiples pero preservar saltos de línea (máximo 2)
|
||||
$plainText = preg_replace('/[ \t]+/', ' ', $plainText);
|
||||
$plainText = preg_replace('/\n\s*\n/', "\n", $plainText);
|
||||
$plainText = preg_replace('/\n{3,}/', "\n\n", $plainText);
|
||||
$plainText = trim($plainText);
|
||||
$translationButtons = getTranslationButtons($pdo, $plainText);
|
||||
|
||||
@@ -120,24 +125,26 @@ function processScheduledMessages(): array
|
||||
$segments = $sender->parseContent($schedule['content']);
|
||||
|
||||
$messageCount = 0;
|
||||
$totalSegments = count($segments);
|
||||
$currentSegment = 0;
|
||||
|
||||
// Enviar cada segmento en el orden correcto
|
||||
foreach ($segments as $segment) {
|
||||
$currentSegment++;
|
||||
$isLastSegment = ($currentSegment === $totalSegments);
|
||||
|
||||
if ($segment['type'] === 'text') {
|
||||
// Convertir el texto al formato de la plataforma
|
||||
$textContent = ConverterFactory::convert($schedule['platform'], $segment['content']);
|
||||
|
||||
if (!empty(trim($textContent))) {
|
||||
// Agregar botones de traducción al último segmento de texto
|
||||
$buttons = null;
|
||||
if ($segment === end($segments)) {
|
||||
$buttons = $schedule['platform'] === 'telegram'
|
||||
? $translationButtons['telegram']
|
||||
: $translationButtons['discord'];
|
||||
if ($isLastSegment && $schedule['platform'] !== 'telegram') {
|
||||
$buttons = $translationButtons['discord'];
|
||||
}
|
||||
|
||||
if ($schedule['platform'] === 'telegram') {
|
||||
$sender->sendMessage($schedule['platform_id'], $textContent, $buttons);
|
||||
$sender->sendMessage($schedule['platform_id'], $textContent);
|
||||
} else {
|
||||
$sender->sendMessage($schedule['platform_id'], $textContent, null, $buttons);
|
||||
}
|
||||
@@ -146,22 +153,43 @@ function processScheduledMessages(): array
|
||||
} elseif ($segment['type'] === 'image') {
|
||||
$imagePath = $segment['src'];
|
||||
|
||||
// Quitar parámetros de URL si los hay
|
||||
$imgPath = parse_url($imagePath, PHP_URL_PATH) ?: $imagePath;
|
||||
$appUrl = $_ENV['APP_URL'] ?? getenv('APP_URL') ?? '';
|
||||
$baseUrl = rtrim($appUrl, '/');
|
||||
|
||||
if (file_exists($imgPath)) {
|
||||
// Es un archivo local
|
||||
$sender->sendMessageWithAttachments($schedule['platform_id'], '', [$imgPath]);
|
||||
$messageCount++;
|
||||
} elseif (strpos($imagePath, 'http') === 0) {
|
||||
// Es una URL remota
|
||||
$embed = ['image' => ['url' => $imagePath]];
|
||||
$sender->sendMessage($schedule['platform_id'], '', $embed);
|
||||
$buttons = null;
|
||||
if ($isLastSegment && $schedule['platform'] !== 'telegram') {
|
||||
$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++;
|
||||
} 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("
|
||||
INSERT INTO sent_messages (schedule_id, recipient_id, platform_message_id, message_count, sent_at)
|
||||
VALUES (?, ?, ?, ?, NOW())
|
||||
|
||||
@@ -34,12 +34,10 @@ class Translate
|
||||
}
|
||||
|
||||
try {
|
||||
// Primero intentar obtener del caché
|
||||
$cacheKey = $this->generateCacheKey($text, $sourceLang, $targetLang);
|
||||
$cached = $this->getFromCache($cacheKey);
|
||||
|
||||
if ($cached !== null) {
|
||||
error_log("Translation cache hit for: $sourceLang -> $targetLang");
|
||||
return $cached;
|
||||
}
|
||||
|
||||
@@ -47,24 +45,29 @@ class Translate
|
||||
$translatedLines = [];
|
||||
|
||||
foreach ($lines as $line) {
|
||||
if (trim($line) === '') {
|
||||
$trimmed = trim($line);
|
||||
if ($trimmed === '') {
|
||||
$translatedLines[] = '';
|
||||
continue;
|
||||
}
|
||||
|
||||
$response = $this->request('/translate', [
|
||||
'q' => trim($line),
|
||||
'source' => $sourceLang,
|
||||
'target' => $targetLang,
|
||||
'format' => 'text'
|
||||
]);
|
||||
|
||||
$translatedLines[] = $response['translatedText'] ?? trim($line);
|
||||
try {
|
||||
$response = $this->request('/translate', [
|
||||
'q' => $trimmed,
|
||||
'source' => $sourceLang,
|
||||
'target' => $targetLang,
|
||||
'format' => 'text'
|
||||
]);
|
||||
|
||||
$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);
|
||||
|
||||
// Guardar en caché
|
||||
$this->saveToCache($cacheKey, $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
|
||||
{
|
||||
$results = [];
|
||||
|
||||
@@ -27,7 +27,7 @@ class TelegramSender
|
||||
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 = [
|
||||
'chat_id' => $chatId,
|
||||
@@ -35,6 +35,12 @@ class TelegramSender
|
||||
'text' => $text,
|
||||
];
|
||||
|
||||
if ($inlineMessageId) {
|
||||
unset($data['chat_id']);
|
||||
unset($data['message_id']);
|
||||
$data['inline_message_id'] = $inlineMessageId;
|
||||
}
|
||||
|
||||
if ($parseMode) {
|
||||
$data['parse_mode'] = $parseMode;
|
||||
}
|
||||
|
||||
@@ -23,11 +23,11 @@ if (!$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'])) {
|
||||
$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 {
|
||||
@@ -82,11 +82,12 @@ try {
|
||||
$callbackData = $callbackQuery['data'];
|
||||
$chatId = $callbackQuery['message']['chat']['id'];
|
||||
$messageId = $callbackQuery['message']['message_id'];
|
||||
$inlineMessageId = $callbackQuery['inline_message_id'] ?? null;
|
||||
$userId = $callbackQuery['from']['id'] ?? '';
|
||||
|
||||
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) {
|
||||
@@ -95,6 +96,10 @@ try {
|
||||
|
||||
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("
|
||||
INSERT INTO recipients (platform_id, name, type, platform, language_code, chat_mode)
|
||||
VALUES (?, ?, 'user', 'telegram', ?, 'bot')
|
||||
@@ -102,16 +107,15 @@ function registerTelegramUser(PDO $pdo, array $user): void
|
||||
");
|
||||
|
||||
$name = trim(($user['first_name'] ?? '') . ' ' . ($user['last_name'] ?? ''));
|
||||
$languageCode = $user['language_code'] ?? 'es';
|
||||
$languageCode = $user['language_code'] ?? $defaultLang;
|
||||
|
||||
$stmt->execute([$user['id'], $name, $languageCode]);
|
||||
}
|
||||
|
||||
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
|
||||
$textForDetection = stripEmojisForDetection($text);
|
||||
$keyboard = getTelegramTranslationButtons($pdo, $textForDetection ?: $text);
|
||||
// Usar el texto original completo para generar los botones y guardar en caché
|
||||
$keyboard = getTelegramTranslationButtons($pdo, $text);
|
||||
|
||||
if (!empty($keyboard)) {
|
||||
$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 {
|
||||
$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();
|
||||
|
||||
if (count($activeLanguages) <= 1) {
|
||||
@@ -135,10 +139,6 @@ function getTelegramTranslationButtons(PDO $pdo, string $text, ?string $excludeL
|
||||
|
||||
$buttons = [];
|
||||
foreach ($activeLanguages as $lang) {
|
||||
if ($excludeLang && $lang['language_code'] === $excludeLang) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$callbackData = "translate:" . $lang['language_code'] . ":" . $textHash;
|
||||
|
||||
$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);
|
||||
$action = $parts[0] ?? '';
|
||||
@@ -369,7 +369,7 @@ function handleTelegramCallback(PDO $pdo, Telegram\TelegramSender $sender, src\T
|
||||
$targetLang = $parts[1] ?? 'es';
|
||||
$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
|
||||
$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();
|
||||
|
||||
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");
|
||||
return;
|
||||
}
|
||||
@@ -385,20 +385,17 @@ function handleTelegramCallback(PDO $pdo, Telegram\TelegramSender $sender, src\T
|
||||
$originalText = $row['original_text'];
|
||||
|
||||
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");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Obtener el idioma original (usar texto sin emojis para mayor precisión)
|
||||
$textForDetection = stripEmojisForDetection($originalText);
|
||||
$sourceLang = $translator->detectLanguage($textForDetection) ?? 'es';
|
||||
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);
|
||||
|
||||
// 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);
|
||||
// Traducción parcial - detecta el idioma de cada segmento y traduce solo lo necesario
|
||||
$textForTranslation = stripEmojisForDetection($originalText);
|
||||
$translated = $translator->translatePartial($textForTranslation ?: $originalText, $targetLang);
|
||||
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);
|
||||
|
||||
if ($translated) {
|
||||
// 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);
|
||||
|
||||
$keyboard = getTelegramTranslationButtons($pdo, $originalText, $targetLang);
|
||||
$keyboard = getTelegramTranslationButtons($pdo, $originalText);
|
||||
|
||||
$result = $sender->editMessageText($chatId, $messageId, "🌐 <b>Traducción (" . strtoupper($targetLang) . "):</b>\n\n" . $translated, $keyboard);
|
||||
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);
|
||||
$result = $sender->editMessageText($chatId, $messageId, "🌐 <b>Traducción (" . strtoupper($targetLang) . "):</b>\n\n" . $translated, $keyboard, 'HTML', $inlineMessageId);
|
||||
file_put_contents(__DIR__ . '/../../logs/telegram_debug.log', date('Y-m-d H:i:s') . " - editMessageText result: " . json_encode($result) . "\n", FILE_APPEND);
|
||||
} 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);
|
||||
}
|
||||
} 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);
|
||||
}
|
||||
} elseif ($action === 'chat_mode') {
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
</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://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>
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
session_start();
|
||||
if (session_status() === PHP_SESSION_NONE) {
|
||||
session_start();
|
||||
}
|
||||
require_once __DIR__ . '/../includes/session_check.php';
|
||||
require_once __DIR__ . '/../includes/url_helper.php';
|
||||
require_once __DIR__ . '/../includes/i18n.php';
|
||||
@@ -131,6 +133,19 @@ $activeLanguages = getActiveLanguages();
|
||||
background-color: var(--bs-body-bg);
|
||||
color: var(--bs-body-color);
|
||||
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 {
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
.main-content {
|
||||
padding: 1.5rem;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.card {
|
||||
background-color: var(--bs-card-bg);
|
||||
border: 1px solid var(--military-green);
|
||||
@@ -690,11 +700,13 @@ $activeLanguages = getActiveLanguages();
|
||||
</a>
|
||||
</li>
|
||||
<?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>
|
||||
<?php endif; ?>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
</li>
|
||||
|
||||
Reference in New Issue
Block a user