Files
sistema_funcionando_lastwar/process_translation_queue.php
nickpons666 7953a56501 Merge: Complete merge of remote changes, including user's requested additions.
This commit completes the merge process, incorporating remote changes that conflicted with local modifications. It also stages and commits all remaining modified and untracked files as per the user's instruction to 'upload everything without exception'.
2026-02-08 16:33:43 -06:00

235 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(LIBRETRANSLATE_URL);
$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(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(TELEGRAM_BOT_TOKEN, $pdo, BOT_BASE_URL);
$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;
}