From d1ec6bed5c82a31717d6a6d00eef800c8a8eea66 Mon Sep 17 00:00:00 2001 From: nickpons666 Date: Fri, 6 Feb 2026 17:54:34 -0600 Subject: [PATCH 1/3] =?UTF-8?q?feat:=20Mejorar=20bot=20Discord=20con=20tra?= =?UTF-8?q?ducci=C3=B3n=20interactiva=20ef=C3=ADmera?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Cambia traducción automática por botones interactivos con banderas - Implementa traducciones privadas (solo visibles para usuario que presiona) - Agrega opción de caché interactivo en script de deploy - Simplifica mensaje de selección de idiomas - Corrige errores de DatabaseConnection duplicada --- discord_bot.php | 226 +++++++++++++++++++++++++++++++---------------- docker/deploy.sh | 80 +++++++++++------ 2 files changed, 205 insertions(+), 101 deletions(-) diff --git a/discord_bot.php b/discord_bot.php index 0df8e78..8bd3043 100755 --- a/discord_bot.php +++ b/discord_bot.php @@ -8,7 +8,7 @@ require_once __DIR__ . '/includes/db.php'; require_once __DIR__ . '/includes/logger.php'; require_once __DIR__ . '/discord/DiscordSender.php'; require_once __DIR__ . '/discord/converters/HtmlToDiscordMarkdownConverter.php'; -require_once __DIR__ . '/includes/Translate.php'; +require_once __DIR__ . '/src/Translate.php'; // Importar clases necesarias use Discord\Discord; @@ -79,66 +79,116 @@ try { try { if (strpos($customId, 'translate_manual:') === 0) { $targetLang = substr($customId, strlen('translate_manual:')); + $userId = $interaction->user->id; $originalMessage = $interaction->message; - $channelId = $originalMessage->channel_id; + $channel = $interaction->channel; - // Responder de inmediato para evitar timeout de interacción - $interaction->respondWithMessage(MessageBuilder::new()->setContent('⌛ Procesando traducción...'), true); - - // Extraer contenido del mensaje: primero content plano, luego embeds como fallback - $originalContent = trim((string) ($originalMessage->content ?? '')); - if ($originalContent === '' && count($originalMessage->embeds) > 0) { - foreach ($originalMessage->embeds as $embed) { - $originalContent .= trim((string) ($embed->description ?? '')) . "\n"; - } - $originalContent = trim($originalContent); - } - - if (!empty($originalContent)) { - try { - $translator = new Translate(); - $sourceLang = $translator->detectLanguage($originalContent) ?? 'es'; - if ($sourceLang === $targetLang) { - // Fallback: muchas plantillas están en ES; intenta forzar ES como origen - $fallbackSrc = 'es'; - if ($fallbackSrc !== $targetLang) { - $translatedText = $translator->translateText($originalContent, $fallbackSrc, $targetLang); - $sourceLang = $fallbackSrc; - } else { - $translatedText = null; - } - } else { - $translatedText = $translator->translateText($originalContent, $sourceLang, $targetLang); - } - - // Obtener bandera desde supported_languages - $flag = ''; + try { + // Buscar el mensaje original que se está traduciendo + // Para interacciones, el mensaje original está en el mensaje de la interacción + $originalContent = ''; + + // Primero intentar obtener el mensaje referenciado si existe + $messageReference = $originalMessage->message_reference; + if ($messageReference && $messageReference->message_id) { + $referencedMessageId = $messageReference->message_id; + $channel->messages->fetch($referencedMessageId)->done(function (Message $referencedMessage) use ($interaction, $targetLang, $logger, $discord, $userId, $pdo) { try { + $originalContent = trim((string) ($referencedMessage->content ?? '')); + + if (empty($originalContent)) { + $interaction->respondWithMessage( + MessageBuilder::new()->setContent('❌ No se encontró contenido para traducir.'), + true + ); + return; + } + + $translator = new Translate(LIBRETRANSLATE_URL); + $sourceLang = $translator->detectLanguage($originalContent) ?? 'es'; + + if ($sourceLang === $targetLang) { + $interaction->respondWithMessage( + MessageBuilder::new()->setContent('ℹ️ El mensaje ya está en este idioma.'), + true + ); + return; + } + + $translatedText = $translator->translateText($originalContent, $sourceLang, $targetLang); + + // Obtener bandera $stmt = $pdo->prepare("SELECT flag_emoji FROM supported_languages WHERE language_code = ? AND is_active = 1"); $stmt->execute([$targetLang]); $flag = $stmt->fetchColumn() ?: ''; - } catch (\Throwable $e) { /* noop */ } + + $flag = $flag !== '' ? $flag : '🏳️'; - $flag = $flag !== '' ? $flag : '🏳️'; - - // Enviar resultado como mensaje normal al canal, mencionando al usuario - $sender = new DiscordSender(DISCORD_BOT_TOKEN); - $mention = '<@' . $userId . '>'; - $content = "{$mention} {$flag} Traducción ({$sourceLang} → {$targetLang}):\n> " . $translatedText; - // Pequeña espera para que el mensaje efímero aparezca primero - usleep(300000); - $sender->sendRawMessage($channelId, $content); - if (empty($translatedText)) { - $sender = new DiscordSender(DISCORD_BOT_TOKEN); - $sender->sendRawMessage($channelId, '<@' . $userId . '> El mensaje ya está en este idioma.'); + // Responder efímeramente con la traducción + $response = "{$flag} **Traducción ({$sourceLang} → {$targetLang}):**\n\n" . $translatedText; + $interaction->respondWithMessage( + MessageBuilder::new()->setContent($response), + true // Efímero - solo visible para el usuario + ); + + $logger->info("[TRADUCCIÓN EFÍMERA] Traducción {$sourceLang}→{$targetLang} enviada efímeramente"); + + } catch (\Throwable $e) { + $logger->error("[Error Traducción Manual]", ['error' => $e->getMessage()]); + $interaction->respondWithMessage( + MessageBuilder::new()->setContent('❌ Error al procesar la traducción: ' . $e->getMessage()), + true + ); + } + }); + return; // Salir después de iniciar el fetch + } else { + // Fallback: usar el contenido del mensaje original si no hay referencia + $originalContent = trim((string) ($originalMessage->content ?? '')); + + if (empty($originalContent)) { + $interaction->respondWithMessage( + MessageBuilder::new()->setContent('❌ No se encontró contenido para traducir.'), + true + ); + return; } - } catch (\Throwable $e) { - $sender = new DiscordSender(DISCORD_BOT_TOKEN); - $sender->sendRawMessage($channelId, '<@' . $userId . "> Error al traducir: " . $e->getMessage()); + + $translator = new Translate(LIBRETRANSLATE_URL); + $sourceLang = $translator->detectLanguage($originalContent) ?? 'es'; + + if ($sourceLang === $targetLang) { + $interaction->respondWithMessage( + MessageBuilder::new()->setContent('ℹ️ El mensaje ya está en este idioma.'), + true + ); + return; + } + + $translatedText = $translator->translateText($originalContent, $sourceLang, $targetLang); + + // Obtener bandera + $stmt = $pdo->prepare("SELECT flag_emoji FROM supported_languages WHERE language_code = ? AND is_active = 1"); + $stmt->execute([$targetLang]); + $flag = $stmt->fetchColumn() ?: ''; + + $flag = $flag !== '' ? $flag : '🏳️'; + + // Responder efímeramente con la traducción + $response = "{$flag} **Traducción ({$sourceLang} → {$targetLang}):**\n\n" . $translatedText; + $interaction->respondWithMessage( + MessageBuilder::new()->setContent($response), + true // Efímero - solo visible para el usuario + ); + + $logger->info("[TRADUCCIÓN EFÍMERA] Traducción {$sourceLang}→{$targetLang} enviada efímeramente"); } - } else { - $sender = new DiscordSender(DISCORD_BOT_TOKEN); - $sender->sendRawMessage($channelId, '<@' . $userId . '> No se encontró contenido para traducir.'); + } catch (\Throwable $e) { + $logger->error("[Error Interacción Manual]", ['error' => $e->getMessage()]); + $interaction->respondWithMessage( + MessageBuilder::new()->setContent('❌ Error al procesar la solicitud: ' . $e->getMessage()), + true + ); } return; // Salir después de manejar la interacción } @@ -183,7 +233,7 @@ try { return; } - $translator = new Translate(); + $translator = new Translate(LIBRETRANSLATE_URL); $sourceLang = $translator->detectLanguage($fullText) ?? 'es'; if ($sourceLang === $targetLang) { // Fallback: fuerza ES como origen si coincide con el destino (común en nuestras plantillas) @@ -499,37 +549,61 @@ function handleDiscordCommand(Message $message, PDO $pdo, Logger $logger) function handleDiscordTranslation(Message $message, PDO $pdo, Logger $logger) { try { - $translator = new Translate(); - $text = $message->content; + $translator = new Translate(LIBRETRANSLATE_URL); $detectedLang = $translator->detectLanguage(strip_tags($text)) ?? 'es'; - // Obtener idiomas activos y encolar traducciones - $langStmt = $pdo->query("SELECT language_code FROM supported_languages WHERE is_active = 1"); - $activeLangs = $langStmt->fetchAll(PDO::FETCH_COLUMN); + // Obtener idiomas activos disponibles + $langStmt = $pdo->query("SELECT language_code, language_name, flag_emoji FROM supported_languages WHERE is_active = 1"); + $activeLangs = $langStmt->fetchAll(PDO::FETCH_ASSOC); - if (in_array($detectedLang, $activeLangs)) { - foreach ($activeLangs as $targetLang) { - if ($detectedLang === $targetLang) { - continue; // No traducir al mismo idioma - } + // Filtrar idiomas diferentes al detectado + $targetLangs = array_filter($activeLangs, function($lang) use ($detectedLang) { + return $lang['language_code'] !== $detectedLang; + }); - $sql = "INSERT INTO translation_queue (platform, message_id, chat_id, user_id, text_to_translate, source_lang, target_lang) VALUES (?, ?, ?, ?, ?, ?, ?)"; - $stmt = $pdo->prepare($sql); - $stmt->execute([ - 'discord', - $message->id, - $message->channel_id, - $message->author->id, - $text, - $detectedLang, - $targetLang - ]); + if (empty($targetLangs)) { + $logger->info("[TRADUCCIÓN] No hay idiomas disponibles para traducir desde '$detectedLang'"); + return; + } - $logger->info("[QUEUE] Mensaje de Discord #{$message->id} encolado para traducción de '$detectedLang' a '$targetLang'."); + // Crear botones con banderas + $components = []; + $actionRow = ActionRow::new(); + $buttonCount = 0; + + foreach ($targetLangs as $lang) { + $button = Button::new(Button::STYLE_SECONDARY, 'translate_manual:' . $lang['language_code']) + ->setLabel($lang['language_name']); + + if (!empty($lang['flag_emoji'])) { + $button->setEmoji($lang['flag_emoji']); + } + + $actionRow->addComponent($button); + $buttonCount++; + + // Discord permite máximo 5 botones por ActionRow + if ($buttonCount % 5 === 0) { + $components[] = $actionRow; + $actionRow = ActionRow::new(); } } + + if ($buttonCount % 5 !== 0) { + $components[] = $actionRow; + } + + // Enviar mensaje con botones efímeros + $builder = MessageBuilder::new() + ->setContent("🌍 **Selecciona idioma para traducir:**") + ->setComponents($components); + + $message->reply($builder, true); // true = ephemeral (solo visible para el usuario) + + $logger->info("[TRADUCCIÓN] Botones de traducción enviados para mensaje #{$message->id}"); + } catch (Throwable $e) { - $logger->error("[Error Encolando Traducción Discord]", ['error' => $e->getMessage(), 'trace' => $e->getTraceAsString()]); + $logger->error("[Error Traducción Discord]", ['error' => $e->getMessage(), 'trace' => $e->getTraceAsString()]); } } diff --git a/docker/deploy.sh b/docker/deploy.sh index 376ad7e..ef34521 100755 --- a/docker/deploy.sh +++ b/docker/deploy.sh @@ -1,38 +1,68 @@ #!/bin/bash -# ============================================ -# Script para construir y subir imagen Docker -# Target: 10.10.4.3:5000/bot-lastwar:v2 -# ============================================ +# ================================================== +# Script para construir y subir una imagen Docker. +# +# Uso: +# ./deploy.sh +# +# El script te preguntará interactivamente: +# - Tag para la imagen +# - Si deseas usar caché al construir +# ================================================== set -e -# Cambiar al directorio del proyecto (carpeta padre de donde está este script) +# --- Configuración --- +REGISTRY_URL="10.10.4.3:5000" +IMAGE_NAME="bot-lastwar" + +# Preguntar por el tag si no se proporciona como argumento +if [ -n "$1" ]; then + TAG="$1" +else + read -p "Introduce el tag para la imagen [latest]: " TAG + TAG="${TAG:-latest}" +fi + +# Preguntar si quiere usar caché +read -p "¿Usar caché al construir? (s/n) [n]: " USE_CACHE +if [ "${USE_CACHE,,}" = "s" ]; then + BUILD_CACHE_FLAG="" + CACHE_STATUS="con caché" +else + BUILD_CACHE_FLAG="--no-cache" + CACHE_STATUS="sin caché" +fi +# --- Fin de la Configuración --- + +# Nombres completos de la imagen +LOCAL_IMAGE_NAME="${IMAGE_NAME}:${TAG}" +REMOTE_IMAGE_NAME="${REGISTRY_URL}/${IMAGE_NAME}:${TAG}" + +# Cambiar al directorio raíz del proyecto (un nivel arriba de este script) cd "$(dirname "$0")/.." echo "==========================================" -echo "Construyendo imagen Docker..." -echo "Directorio: $(pwd)" +echo "Directorio de trabajo: $(pwd)" +echo "Construyendo imagen: ${LOCAL_IMAGE_NAME}" +echo "Opción de build: ${CACHE_STATUS}" +echo "Destino del registry: ${REMOTE_IMAGE_NAME}" echo "==========================================" -# Construir la imagen usando el Dockerfile en docker/ -docker build -t bot-lastwar:latest -f docker/Dockerfile . +# 1. Construir la imagen Docker +echo "Paso 1: Construyendo imagen Docker ${CACHE_STATUS}..." +docker build ${BUILD_CACHE_FLAG} -t "${LOCAL_IMAGE_NAME}" -f docker/Dockerfile . + +# 2. Etiquetar la imagen para el registry +echo "Paso 2: Etiquetando imagen como ${REMOTE_IMAGE_NAME}..." +docker tag "${LOCAL_IMAGE_NAME}" "${REMOTE_IMAGE_NAME}" + +# 3. Subir la imagen al registry +echo "Paso 3: Subiendo imagen al registry..." +docker push "${REMOTE_IMAGE_NAME}" echo "==========================================" -echo "Etiquetando imagen para el registry..." -echo "==========================================" - -# Etiquetar la imagen con la versión v2 -docker tag bot-lastwar:latest 10.10.4.3:5000/bot-lastwar:v2 - -echo "==========================================" -echo "Subiendo imagen al registry..." -echo "==========================================" - -# Subir al registry -docker push 10.10.4.3:5000/bot-lastwar:v2 - -echo "==========================================" -echo "Imagen subida exitosamente:" -echo "10.10.4.3:5000/bot-lastwar:v2" +echo "✅ Imagen subida exitosamente:" +echo "${REMOTE_IMAGE_NAME}" echo "==========================================" From 7af0e95e0ba0edf954b4d37403340c9df0c88c11 Mon Sep 17 00:00:00 2001 From: nickpons666 Date: Sat, 7 Feb 2026 20:03:56 -0600 Subject: [PATCH 2/3] =?UTF-8?q?Mejora=20traducci=C3=B3n=20bot=20Discord:?= =?UTF-8?q?=20detecci=C3=B3n=20de=20im=C3=A1genes/videos=20y=20preservaci?= =?UTF-8?q?=C3=B3n=20de=20emojis?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Evita mostrar botones de traducción en mensajes con solo imágenes/videos - Detecta y traduce captions de imágenes/videos - Preserva emojis, menciones y elementos de Discord durante traducción - Soluciona problema de placeholders en emojis Unicode estándar --- discord_bot.php | 107 ++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 99 insertions(+), 8 deletions(-) diff --git a/discord_bot.php b/discord_bot.php index 8bd3043..a3da799 100755 --- a/discord_bot.php +++ b/discord_bot.php @@ -104,8 +104,10 @@ try { return; } + // Extraer y preservar emojis, stickers y elementos de Discord + $processedContent = preserveDiscordElements($originalContent); $translator = new Translate(LIBRETRANSLATE_URL); - $sourceLang = $translator->detectLanguage($originalContent) ?? 'es'; + $sourceLang = $translator->detectLanguage($processedContent['text']) ?? 'es'; if ($sourceLang === $targetLang) { $interaction->respondWithMessage( @@ -115,7 +117,10 @@ try { return; } - $translatedText = $translator->translateText($originalContent, $sourceLang, $targetLang); + $translatedText = $translator->translateText($processedContent['text'], $sourceLang, $targetLang); + + // Restaurar emojis y elementos de Discord + $finalText = restoreDiscordElements($translatedText, $processedContent['placeholders']); // Obtener bandera $stmt = $pdo->prepare("SELECT flag_emoji FROM supported_languages WHERE language_code = ? AND is_active = 1"); @@ -125,7 +130,7 @@ try { $flag = $flag !== '' ? $flag : '🏳️'; // Responder efímeramente con la traducción - $response = "{$flag} **Traducción ({$sourceLang} → {$targetLang}):**\n\n" . $translatedText; + $response = "{$flag} **Traducción ({$sourceLang} → {$targetLang}):**\n\n" . $finalText; $interaction->respondWithMessage( MessageBuilder::new()->setContent($response), true // Efímero - solo visible para el usuario @@ -154,8 +159,10 @@ try { return; } + // Extraer y preservar emojis, stickers y elementos de Discord + $processedContent = preserveDiscordElements($originalContent); $translator = new Translate(LIBRETRANSLATE_URL); - $sourceLang = $translator->detectLanguage($originalContent) ?? 'es'; + $sourceLang = $translator->detectLanguage($processedContent['text']) ?? 'es'; if ($sourceLang === $targetLang) { $interaction->respondWithMessage( @@ -165,7 +172,10 @@ try { return; } - $translatedText = $translator->translateText($originalContent, $sourceLang, $targetLang); + $translatedText = $translator->translateText($processedContent['text'], $sourceLang, $targetLang); + + // Restaurar emojis y elementos de Discord + $finalText = restoreDiscordElements($translatedText, $processedContent['placeholders']); // Obtener bandera $stmt = $pdo->prepare("SELECT flag_emoji FROM supported_languages WHERE language_code = ? AND is_active = 1"); @@ -175,7 +185,7 @@ try { $flag = $flag !== '' ? $flag : '🏳️'; // Responder efímeramente con la traducción - $response = "{$flag} **Traducción ({$sourceLang} → {$targetLang}):**\n\n" . $translatedText; + $response = "{$flag} **Traducción ({$sourceLang} → {$targetLang}):**\n\n" . $finalText; $interaction->respondWithMessage( MessageBuilder::new()->setContent($response), true // Efímero - solo visible para el usuario @@ -550,8 +560,39 @@ function handleDiscordTranslation(Message $message, PDO $pdo, Logger $logger) { try { $text = $message->content; + $attachments = $message->attachments; + + // Verificar si el mensaje tiene contenido de texto + $hasTextContent = !empty(trim($text)); + + // Verificar si hay texto en los attachments (captions de imágenes/videos) + $attachmentText = ''; + if (!empty($attachments)) { + foreach ($attachments as $attachment) { + // Algunos attachments tienen propiedad 'description' o 'caption' + if (!empty($attachment->description)) { + $attachmentText .= $attachment->description . ' '; + } + if (!empty($attachment->caption)) { + $attachmentText .= $attachment->caption . ' '; + } + } + } + + // Combinar texto principal con texto de attachments + $fullText = trim($text . ' ' . $attachmentText); + $hasAnyText = !empty($fullText); + + // Verificar si el mensaje tiene solo imágenes/adjuntos sin texto + $hasOnlyAttachments = !$hasAnyText && !empty($attachments) && count($attachments) > 0; + + // Si el mensaje no tiene texto o tiene solo imágenes, no mostrar botones de traducción + if (!$hasAnyText || $hasOnlyAttachments) { + $logger->info("[TRADUCCIÓN] Mensaje #{$message->id} no tiene contenido de texto, omitiendo botones de traducción."); + return; + } $translator = new Translate(LIBRETRANSLATE_URL); - $detectedLang = $translator->detectLanguage(strip_tags($text)) ?? 'es'; + $detectedLang = $translator->detectLanguage(strip_tags($fullText)) ?? 'es'; // Obtener idiomas activos disponibles $langStmt = $pdo->query("SELECT language_code, language_name, flag_emoji FROM supported_languages WHERE is_active = 1"); @@ -596,7 +637,7 @@ function handleDiscordTranslation(Message $message, PDO $pdo, Logger $logger) // Enviar mensaje con botones efímeros $builder = MessageBuilder::new() - ->setContent("🌍 **Selecciona idioma para traducir:**") +// ->setContent("🌍 **Selecciona idioma para traducir:**") ->setComponents($components); $message->reply($builder, true); // true = ephemeral (solo visible para el usuario) @@ -607,3 +648,53 @@ function handleDiscordTranslation(Message $message, PDO $pdo, Logger $logger) $logger->error("[Error Traducción Discord]", ['error' => $e->getMessage(), 'trace' => $e->getTraceAsString()]); } } + +/** + * Función para preservar emojis, stickers y elementos de Discord durante la traducción + */ +function preserveDiscordElements($text) { + $placeholders = []; + $processedText = $text; + + // Patrones para detectar elementos de Discord (excluyendo emojis Unicode) + $patterns = [ + // Emojis personalizados de Discord <:name:id> + '//', + // Menciones de usuarios <@id> y <@!id> + '/<@!?(\d+)>/', + // Menciones de canales <#id> + '/<#(\d+)>/', + // Menciones de roles <@&id> + '/<@&(\d+)>/', + // Stickers y GIFs animados (pueden venir como URLs especiales) + '/https?:\/\/(?:media|cdn)\.discordapp\.(?:com|net)\/(stickers|attachments)\/\S+/i' + ]; + + $index = 0; + foreach ($patterns as $pattern) { + $processedText = preg_replace_callback($pattern, function($matches) use (&$placeholders, &$index) { + $placeholder = "DISCORD_ELEMENT_{$index}"; + $placeholders[$placeholder] = $matches[0]; + $index++; + return $placeholder; + }, $processedText); + } + + return [ + 'text' => $processedText, + 'placeholders' => $placeholders + ]; +} + +/** + * Función para restaurar emojis, stickers y elementos de Discord después de la traducción + */ +function restoreDiscordElements($translatedText, $placeholders) { + $restoredText = $translatedText; + + foreach ($placeholders as $placeholder => $originalElement) { + $restoredText = str_replace($placeholder, $originalElement, $restoredText); + } + + return $restoredText; +} From 26414094d4262e5ab092028955a4f0de57092f43 Mon Sep 17 00:00:00 2001 From: nickpons666 Date: Sat, 7 Feb 2026 20:09:16 -0600 Subject: [PATCH 3/3] =?UTF-8?q?Soluci=C3=B3n=20cr=C3=ADtica:=20Reconexi?= =?UTF-8?q?=C3=B3n=20autom=C3=A1tica=20bot=20Discord=20y=20sistema=20de=20?= =?UTF-8?q?health=20check?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Implementa heartbeat cada 30 segundos para mantener conexión WebSocket activa - Agrega manejo de eventos de conexión/desconexión/reconexión - Sistema de health check cada 2 minutos para verificar estado del bot - Configuración mejorada de timeouts y reconexión automática - Soluciona problema de bot no responde después de períodos de inactividad --- discord_bot.php | 40 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/discord_bot.php b/discord_bot.php index a3da799..51d0cb4 100755 --- a/discord_bot.php +++ b/discord_bot.php @@ -39,9 +39,15 @@ try { $discord = new Discord([ 'token' => DISCORD_BOT_TOKEN, 'intents' => Intents::GUILDS | Intents::GUILD_MESSAGES | Intents::DIRECT_MESSAGES | Intents::GUILD_MEMBERS | Intents::GUILD_MESSAGE_REACTIONS, - 'logger' => $logger + 'logger' => $logger, + 'loop' => \React\EventLoop\Loop::get(), + 'socket_options' => [ + 'heartbeat_interval' => 30, // Enviar heartbeat cada 30 segundos + 'reconnect_timeout' => 60, // Timeout para reconexión + ] ]); + // Manejar eventos de conexión y desconexión $discord->on('ready', function (Discord $discord) { $discord->getLogger()->info("=================================================="); $discord->getLogger()->info("Bot conectado y listo para escuchar!"); @@ -49,6 +55,21 @@ try { $discord->getLogger()->info("=================================================="); }); + // Evento de reconexión + $discord->on('reconnected', function (Discord $discord) { + $logger->info("[RECONEXIÓN] Bot reconectado exitosamente"); + }); + + // Evento de desconexión + $discord->on('disconnected', function (Discord $discord, $reason) { + $logger->warning("[DESCONEXIÓN] Bot desconectado. Razón: $reason"); + }); + + // Evento de error + $discord->on('error', function ($error, Discord $discord) { + $logger->error("[ERROR DISCORD] Error en la conexión: " . $error->getMessage()); + }); + // Evento para nuevos miembros en el servidor $discord->on(Event::GUILD_MEMBER_ADD, function (Member $member, Discord $discord) use ($pdo, $logger) { $logger->info("[NUEVO MIEMBRO] Usuario {$member->user->username} ({$member->id}) se ha unido al servidor."); @@ -400,6 +421,23 @@ try { } }); + // Sistema de verificación de salud periódica + $discord->on('ready', function (Discord $discord) use ($logger) { + // Programar verificación de salud cada 2 minutos + $discord->getLoop()->addPeriodicTimer(120, function() use ($discord, $logger) { + try { + // Verificar si el bot sigue conectado + if ($discord->user && $discord->user->id) { + $logger->info("[HEALTH CHECK] Bot conectado y respondiendo. Usuario: {$discord->user->username}"); + } else { + $logger->warning("[HEALTH CHECK] Bot no responde correctamente"); + } + } catch (\Throwable $e) { + $logger->error("[HEALTH CHECK] Error en verificación: " . $e->getMessage()); + } + }); + }); + $discord->run(); } catch (Throwable $e) {