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 = "Traducciones:\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'] ?? '🏳️') . ' ' . ($langInfo['language_name'] ?? strtoupper($langCode)) . ''; // 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 '' . $placeholder . ''; }, $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 = '/\s*' . preg_quote($placeholder, '/') . '\s*<\/span>/i'; $decodedText = preg_replace($patt, $originalMention, $decodedText, 1); } return $decodedText; } return $text; }