236 lines
10 KiB
PHP
Executable File
236 lines
10 KiB
PHP
Executable File
<?php
|
|
// process_translation_queue.php
|
|
|
|
// Permitir que el script se ejecute indefinidamente
|
|
set_time_limit(0);
|
|
|
|
require_once __DIR__ . '/includes/logger.php';
|
|
|
|
custom_log("--- INICIANDO PROCESADOR DE COLA DE TRADUCCIÓN ---");
|
|
|
|
// Cargar configuración y dependencias
|
|
require_once __DIR__ . '/config/config.php';
|
|
require_once __DIR__ . '/vendor/autoload.php'; // Asegurarse de que el autoloader de Composer esté cargado
|
|
require_once __DIR__ . '/includes/db.php';
|
|
require_once __DIR__ . '/src/TelegramSender.php';
|
|
require_once __DIR__ . '/src/Translate.php';
|
|
require_once __DIR__ . '/src/DiscordSender.php';
|
|
|
|
// Usar la clase Embed de DiscordPHP
|
|
use Discord\Parts\Embed\Embed;
|
|
|
|
// Instanciar las clases necesarias
|
|
$translator = new Translate();
|
|
|
|
$running = true;
|
|
|
|
// Manejador de señales para una terminación controlada
|
|
pcntl_async_signals(true);
|
|
pcntl_signal(SIGINT, function($sig) use (&$running) {
|
|
custom_log("Señal SIGINT recibida. Terminando el worker...");
|
|
$running = false;
|
|
});
|
|
pcntl_signal(SIGTERM, function($sig) use (&$running) {
|
|
custom_log("Señal SIGTERM recibida. Terminando el worker...");
|
|
$running = false;
|
|
});
|
|
|
|
while ($running) {
|
|
$job = null;
|
|
try {
|
|
// Iniciar transacción
|
|
$pdo->beginTransaction();
|
|
|
|
// Obtener un trabajo pendiente y bloquear la fila
|
|
$stmt = $pdo->prepare("SELECT * FROM translation_queue WHERE status = 'pending' AND attempts < 5 ORDER BY created_at ASC LIMIT 1 FOR UPDATE SKIP LOCKED");
|
|
$stmt->execute();
|
|
$job = $stmt->fetch(PDO::FETCH_ASSOC);
|
|
|
|
if ($job) {
|
|
// Marcar el trabajo como 'processing'
|
|
$updateStmt = $pdo->prepare("UPDATE translation_queue SET status = 'processing', attempts = attempts + 1, processed_at = NOW() WHERE id = ?");
|
|
$updateStmt->execute([$job['id']]);
|
|
custom_log("[JOB #{$job['id']}] Trabajo obtenido. Marcado como 'processing'. Intento #" . ($job['attempts'] + 1));
|
|
}
|
|
|
|
// Confirmar la transacción
|
|
$pdo->commit();
|
|
|
|
} catch (Exception $e) {
|
|
if ($pdo->inTransaction()) {
|
|
$pdo->rollBack();
|
|
}
|
|
custom_log("Error al obtener trabajo de la cola: " . $e->getMessage());
|
|
sleep(5); // Esperar antes de reintentar
|
|
continue;
|
|
}
|
|
|
|
if ($job) {
|
|
try {
|
|
$success = false;
|
|
|
|
if ($job['platform'] === 'discord') {
|
|
// --- LÓGICA MEJORADA PARA DISCORD USANDO UN ARRAY ---
|
|
custom_log("[JOB #{$job['id']}] Procesando trabajo para Discord con múltiples idiomas.");
|
|
$targetLangs = explode(',', $job['target_lang']);
|
|
|
|
$discordSender = new DiscordSender($_ENV['DISCORD_BOT_TOKEN']);
|
|
|
|
// Pre-procesar el texto para proteger las menciones de Discord
|
|
$originalText = $job['text_to_translate'];
|
|
$textToTranslateClean = handleDiscordMentions($originalText, 'to_placeholder');
|
|
|
|
// Construir el embed como un array simple
|
|
$embedData = [
|
|
'title' => 'Traducciones',
|
|
'color' => 0x4A90E2, // Un color azul
|
|
'timestamp' => date('c'),
|
|
'fields' => []
|
|
];
|
|
|
|
$translationCount = 0;
|
|
foreach ($targetLangs as $langCode) {
|
|
if (empty($langCode)) continue;
|
|
|
|
$translatedText = $translator->translateHtml($textToTranslateClean, $job['source_lang'], $langCode);
|
|
|
|
if ($translatedText && trim(strtolower($translatedText)) !== trim(strtolower($textToTranslateClean))) {
|
|
// Post-procesar para restaurar las menciones de Discord
|
|
// Primero, decodificar cualquier entidad HTML que el traductor haya podido introducir
|
|
$decodedText = html_entity_decode($translatedText, ENT_QUOTES | ENT_HTML5, 'UTF-8');
|
|
$restoredText = handleDiscordMentions($decodedText, 'from_placeholder');
|
|
|
|
// Obtener nombre del idioma y bandera
|
|
$langInfoStmt = $pdo->prepare("SELECT language_name, flag_emoji FROM supported_languages WHERE language_code = ?");
|
|
$langInfoStmt->execute([$langCode]);
|
|
$langInfo = $langInfoStmt->fetch(PDO::FETCH_ASSOC);
|
|
|
|
$fieldName = ($langInfo['flag_emoji'] ?? '🏳️') . ' ' . ($langInfo['language_name'] ?? strtoupper($langCode));
|
|
|
|
$embedData['fields'][] = ['name' => $fieldName, 'value' => $restoredText];
|
|
$translationCount++;
|
|
}
|
|
}
|
|
|
|
if ($translationCount > 0) {
|
|
custom_log("[JOB #{$job['id']}] Enviando embed con {$translationCount} traducciones.");
|
|
$response = $discordSender->sendEmbedData($job['chat_id'], $embedData);
|
|
if ($response) {
|
|
$success = true;
|
|
}
|
|
} else {
|
|
// Si no hubo traducciones válidas, consideramos el trabajo completado para no reintentarlo.
|
|
custom_log("[JOB #{$job['id']}] No se generaron traducciones válidas. Marcando como completado.");
|
|
$success = true;
|
|
}
|
|
|
|
} else {
|
|
// --- LÓGICA MEJORADA PARA TELEGRAM USANDO UN SOLO MENSAJE HTML ---
|
|
custom_log("[JOB #{$job['id']}] Procesando trabajo para Telegram con múltiples idiomas.");
|
|
$targetLangs = explode(',', $job['target_lang']);
|
|
$telegram = new TelegramSender($_ENV['TELEGRAM_BOT_TOKEN'], $pdo);
|
|
|
|
$htmlOutput = "<b>Traducciones:</b>\n\n";
|
|
$translationCount = 0;
|
|
|
|
foreach ($targetLangs as $langCode) {
|
|
if (empty($langCode)) continue;
|
|
|
|
$translatedText = $translator->translateHtml($job['text_to_translate'], $job['source_lang'], $langCode);
|
|
|
|
if ($translatedText && trim(strtolower($translatedText)) !== trim(strtolower($job['text_to_translate']))) {
|
|
// Obtener nombre del idioma y bandera
|
|
$langInfoStmt = $pdo->prepare("SELECT language_name, flag_emoji FROM supported_languages WHERE language_code = ?");
|
|
$langInfoStmt->execute([$langCode]);
|
|
$langInfo = $langInfoStmt->fetch(PDO::FETCH_ASSOC);
|
|
|
|
$lineTitle = ($langInfo['flag_emoji'] ?? '🏳️') . ' <b>' . ($langInfo['language_name'] ?? strtoupper($langCode)) . '</b>';
|
|
|
|
// Append to the HTML string
|
|
$htmlOutput .= $lineTitle . "\n" . $translatedText . "\n\n";
|
|
$translationCount++;
|
|
}
|
|
}
|
|
|
|
if ($translationCount > 0) {
|
|
custom_log("[JOB #{$job['id']}] Enviando mensaje HTML con {$translationCount} traducciones a Telegram.");
|
|
$response = $telegram->sendMessage($job['chat_id'], trim($htmlOutput), [
|
|
'parse_mode' => 'HTML',
|
|
'reply_to_message_id' => $job['message_id']
|
|
]);
|
|
if ($response) {
|
|
$success = true;
|
|
}
|
|
} else {
|
|
custom_log("[JOB #{$job['id']}] No se generaron traducciones válidas para Telegram. Marcando como completado.");
|
|
$success = true;
|
|
}
|
|
}
|
|
|
|
// Marcar estado final del trabajo
|
|
if ($success) {
|
|
$finalStmt = $pdo->prepare("UPDATE translation_queue SET status = 'completed' WHERE id = ?");
|
|
$finalStmt->execute([$job['id']]);
|
|
custom_log("[JOB #{$job['id']}] Trabajo completado exitosamente.");
|
|
} else {
|
|
throw new Exception("El envío a la plataforma '{$job['platform']}' falló o no se generaron traducciones.");
|
|
}
|
|
|
|
} catch (Exception $e) {
|
|
$errorMessage = $e->getMessage();
|
|
custom_log("[JOB #{$job['id']}] Error al procesar el trabajo: " . $errorMessage);
|
|
$failStmt = $pdo->prepare("UPDATE translation_queue SET status = 'failed', error_message = ? WHERE id = ?");
|
|
$failStmt->execute([$errorMessage, $job['id']]);
|
|
}
|
|
} else {
|
|
// Si no hay trabajos, esperar
|
|
custom_log("No hay trabajos pendientes. Esperando...");
|
|
sleep(2);
|
|
}
|
|
|
|
pcntl_signal_dispatch();
|
|
}
|
|
|
|
custom_log("--- PROCESADOR DE COLA DETENIDO ---");
|
|
|
|
/**
|
|
* Maneja las menciones de Discord, reemplazándolas por placeholders antes de la traducción
|
|
* y restaurándolas después.
|
|
*
|
|
* @param string $text El texto a procesar.
|
|
* @param string $direction 'to_placeholder' para reemplazar menciones por placeholders, 'from_placeholder' para restaurarlas.
|
|
* @return string El texto procesado.
|
|
*/
|
|
function handleDiscordMentions($text, $direction = 'to_placeholder') {
|
|
static $mentionMap = [];
|
|
|
|
if ($direction === 'to_placeholder') {
|
|
$mentionMap = [];
|
|
$placeholderCounter = 0;
|
|
// Simplificado para incluir usuarios, roles y canales
|
|
$pattern = '/<(@!?\d+|@&\d+|#\d+)>/';
|
|
|
|
return preg_replace_callback($pattern, function($matches) use (&$mentionMap, &$placeholderCounter) {
|
|
$originalMention = $matches[0]; // Ej: <@12345>
|
|
$placeholder = "MENTION{$placeholderCounter}"; // Ej: MENTION0
|
|
$mentionMap[$placeholder] = $originalMention;
|
|
$placeholderCounter++;
|
|
return '<span class="notranslate">' . $placeholder . '</span>';
|
|
}, $text);
|
|
|
|
} elseif ($direction === 'from_placeholder') {
|
|
// Decodificar entidades HTML primero
|
|
$decodedText = html_entity_decode($text, ENT_QUOTES | ENT_HTML5, 'UTF-8');
|
|
|
|
// Iterar sobre el mapa y reemplazar cada placeholder
|
|
foreach ($mentionMap as $placeholder => $originalMention) {
|
|
// El patrón busca el placeholder, permitiendo espacios y sin ser sensible a mayúsculas/minúsculas
|
|
$patt = '/<span class="notranslate">\s*' . preg_quote($placeholder, '/') . '\s*<\/span>/i';
|
|
$decodedText = preg_replace($patt, $originalMention, $decodedText, 1);
|
|
}
|
|
return $decodedText;
|
|
}
|
|
return $text;
|
|
}
|
|
|