$token,
'intents' => Intents::GUILDS | Intents::GUILD_MESSAGES | Intents::DIRECT_MESSAGES | Intents::GUILD_MEMBERS | Intents::GUILD_MESSAGE_REACTIONS | Intents::MESSAGE_CONTENT,
]);
$discord->on('ready', function (Discord $discord) {
echo "Bot de Discord conectado como: {$discord->user->username}" . PHP_EOL;
$guild = $discord->guilds->first();
if ($guild) {
echo "Servidor: {$guild->name}" . PHP_EOL;
}
});
$discord->on(Event::GUILD_MEMBER_ADD, function (Member $member, Discord $discord) {
echo "Nuevo miembro: {$member->user->username}" . PHP_EOL;
try {
$pdo = getDbConnection();
$stmt = $pdo->prepare("
INSERT INTO recipients (platform_id, name, type, platform, language_code, chat_mode)
VALUES (?, ?, 'user', 'discord', 'es', 'agent')
ON DUPLICATE KEY UPDATE name = VALUES(name)
");
$stmt->execute([
$member->user->id,
$member->user->username
]);
echo "Usuario registrado en la base de datos" . PHP_EOL;
if (!isExistingDiscordUser($pdo, $member->user->id)) {
sendDiscordWelcomeMessage($pdo, $member, $discord);
}
} catch (Exception $e) {
echo "Error al registrar usuario: " . $e->getMessage() . PHP_EOL;
}
});
$discord->on(Event::MESSAGE_CREATE, function (Message $message, Discord $discord) {
if ($message->author->bot) {
return;
}
$content = $message->content;
$channelId = $message->channel_id;
$userId = $message->author->id;
$username = $message->author->username;
try {
$pdo = getDbConnection();
$isNewUser = !isExistingDiscordUser($pdo, $userId);
registerDiscordUser($pdo, $message->author);
if ($isNewUser) {
sendDiscordWelcomeMessageOnMessage($pdo, $message, $username);
}
if (str_starts_with($content, '#')) {
$command = ltrim($content, '#');
handleTemplateCommand($pdo, $message, $command);
} elseif (str_starts_with($content, '/')) {
handleSlashCommand($pdo, $message, $content);
} else {
handleRegularMessage($pdo, $message, $content);
}
} catch (Exception $e) {
echo "Error: " . $e->getMessage() . PHP_EOL;
}
});
$discord->on(Event::INTERACTION_CREATE, function ($interaction, Discord $discord) {
try {
handleButtonInteraction($interaction, $discord);
} catch (Exception $e) {
echo "Error en interacción: " . $e->getMessage() . PHP_EOL;
}
});
function handleTemplateCommand(PDO $pdo, Message $message, string $command): void
{
try {
$stmt = $pdo->prepare("SELECT * FROM recurrent_messages WHERE telegram_command = ?");
$stmt->execute([$command]);
$template = $stmt->fetch();
if ($template) {
require_once __DIR__ . '/discord/converters/HtmlToDiscordMarkdownConverter.php';
require_once __DIR__ . '/discord/DiscordSender.php';
require_once __DIR__ . '/src/Translate.php';
$converter = new \Discord\Converters\HtmlToDiscordMarkdownConverter();
$images = $converter->extractImages($template['message_content']);
$contentWithoutImages = $converter->removeImages($template['message_content']);
$content = $converter->convert($contentWithoutImages);
require_once __DIR__ . '/discord/DiscordSender.php';
require_once __DIR__ . '/src/Translate.php';
$sender = new \Discord\DiscordSender();
$plainText = $template['message_content'];
$plainText = preg_replace('/
/i', "\n", $plainText);
$plainText = preg_replace('/<\/p>/i', "\n", $plainText);
$plainText = preg_replace('/
]*>/i', '', $plainText); $plainText = strip_tags($plainText); $plainText = html_entity_decode($plainText, ENT_QUOTES | ENT_HTML5, 'UTF-8'); $plainText = trim($plainText); $translationButtons = getDiscordTranslationButtons($pdo, $plainText); if (!empty($images)) { $sender->sendMessageWithImages((string)$message->channel_id, $content, $images, $translationButtons); } else { $sender->sendMessage((string)$message->channel_id, $content, null, $translationButtons); } } else { $message->channel->sendMessage("❌ Plantilla no encontrada: #{$command}"); } } catch (Exception $e) { $message->channel->sendMessage("❌ Error: " . $e->getMessage()); } } function getDiscordTranslationButtons(PDO $pdo, string $text): array { $stmt = $pdo->query("SELECT language_code, flag_emoji FROM supported_languages WHERE is_active = 1"); $languages = $stmt->fetchAll(); if (count($languages) <= 1) { return []; } // Guardar texto en BD con hash consistente $textHash = md5($text); $stmt = $pdo->prepare("INSERT INTO translation_cache (text_hash, original_text) VALUES (?, ?) ON DUPLICATE KEY UPDATE original_text = VALUES(original_text)"); $stmt->execute([$textHash, $text]); $buttons = []; foreach ($languages as $lang) { $buttons[] = [ 'label' => $lang['flag_emoji'] . ' ' . strtoupper($lang['language_code']), 'custom_id' => 'translate_' . $lang['language_code'] . ':' . $textHash, 'style' => 1 ]; } return [ [ 'type' => 1, 'components' => array_slice($buttons, 0, 5) ] ]; } function handleSlashCommand(PDO $pdo, Message $message, string $content): void { $parts = explode(' ', $content); $cmd = strtolower(str_replace('/', '', $parts[0])); $args = array_slice($parts, 1); echo "Comando recibido: {$cmd}" . PHP_EOL; switch ($cmd) { case 'comandos': require_once __DIR__ . '/discord/DiscordSender.php'; $msg = "📋 **Comandos disponibles:**\n\n"; $msg .= "`/comandos` - Ver comandos\n"; $msg .= "`/setlang [código]` - Establecer idioma\n"; $msg .= "`/bienvenida` - Mensaje de bienvenida\n"; $msg .= "`/agente` - Cambiar a modo IA\n"; $stmt = $pdo->query("SELECT name, telegram_command FROM recurrent_messages WHERE telegram_command IS NOT NULL AND telegram_command != '' ORDER BY name"); $customCommands = $stmt->fetchAll(); if (!empty($customCommands)) { $msg .= "\n📦 **Plantillas:**\n"; foreach ($customCommands as $cmd) { $msg .= "`#" . htmlspecialchars($cmd['telegram_command']) . "` - " . htmlspecialchars($cmd['name']) . "\n"; } } $translationButtons = getDiscordTranslationButtons($pdo, $msg); $sender = new \Discord\DiscordSender(); $sender->sendMessage((string)$message->channel_id, $msg, null, $translationButtons); break; case 'setlang': $langCode = $args[0] ?? 'es'; $stmt = $pdo->prepare("UPDATE recipients SET language_code = ? WHERE platform_id = ? AND platform = 'discord'"); $stmt->execute([$langCode, $message->author->id]); $message->channel->sendMessage("✅ Idioma actualizado a: " . strtoupper($langCode)); break; case 'bienvenida': $stmt = $pdo->query("SELECT * FROM telegram_bot_messages WHERE id = 1"); $config = $stmt->fetch(); if ($config && $config['is_active']) { require_once __DIR__ . '/discord/DiscordSender.php'; $text = str_replace('{user_name}', $message->author->username, $config['message_text']); // Convertir HTML a texto plano para botones de traducción $plainText = html_entity_decode(strip_tags($text), ENT_QUOTES | ENT_HTML5, 'UTF-8'); // Limpiar espacios múltiples pero preservar saltos de línea $plainText = preg_replace('/[ \t]+/', ' ', $plainText); $plainText = preg_replace('/\n\s*\n/', "\n", $plainText); $translationButtons = getDiscordTranslationButtons($pdo, $plainText); $sender = new \Discord\DiscordSender(); $sender->sendMessage((string)$message->channel_id, $text, null, $translationButtons); } break; case 'agente': $builder = \Discord\Builders\MessageBuilder::new(); $builder->setContent("🤖 **Selecciona un modo de chat:**"); $row = new \Discord\Builders\Components\ActionRow(); $btnBot = \Discord\Builders\Components\Button::new(\Discord\Builders\Components\Button::STYLE_PRIMARY) ->setLabel('Seguir con Bot') ->setCustomId('chat_mode_bot:' . $message->author->id); $btnIa = \Discord\Builders\Components\Button::new(\Discord\Builders\Components\Button::STYLE_SUCCESS) ->setLabel('Platicar con IA') ->setCustomId('chat_mode_ia:' . $message->author->id); $row->addComponent($btnBot); $row->addComponent($btnIa); $builder->addComponent($row); $message->channel->sendMessage($builder); break; default: $message->channel->sendMessage("Comando desconocido. Usa /comandos para ver los disponibles."); } } function handleRegularMessage(PDO $pdo, Message $message, string $content): void { $stmt = $pdo->prepare("SELECT chat_mode FROM recipients WHERE platform_id = ? AND platform = 'discord'"); $stmt->execute([$message->author->id]); $recipient = $stmt->fetch(); if ($recipient && $recipient['chat_mode'] === 'ia') { sendToN8NIA($message, $content); } else { handleAutoTranslationWithButtons($pdo, $message, $content); } } function sendToN8NIA(Message $message, string $userMessage): void { require_once __DIR__ . '/includes/env_loader.php'; $webhookUrl = trim($_ENV['N8N_IA_WEBHOOK_URL_DISCORD'] ?? getenv('N8N_IA_WEBHOOK_URL_DISCORD') ?? ''); $webhookUrl = trim($webhookUrl, '"'); if (!empty($webhookUrl)) { $data = [ 'user_id' => (string)$message->author->id, 'username' => $message->author->username, 'message' => $userMessage ]; $jsonData = json_encode($data); $ch = curl_init($webhookUrl); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, $jsonData); curl_setopt($ch, CURLOPT_HTTPHEADER, [ 'Content-Type: application/json', 'Content-Length: ' . strlen($jsonData) ]); curl_setopt($ch, CURLOPT_TIMEOUT, 30); $response = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); if ($httpCode >= 200 && $httpCode < 300) { $result = json_decode($response, true); $reply = $result['reply'] ?? $result['message'] ?? $response; $message->channel->sendMessage($reply); return; } error_log("N8N Discord IA Error: HTTP $httpCode - Response: $response"); } require_once __DIR__ . '/src/IA/Agent.php'; $agent = new \IA\Agent(); try { $response = $agent->generateResponse($userMessage); $message->channel->sendMessage($response); } catch (\Exception $e) { $message->channel->sendMessage("❌ Error: " . $e->getMessage()); error_log("IA Agent Discord Error: " . $e->getMessage()); } } function handleAutoTranslationWithButtons(PDO $pdo, Message $message, string $text): void { try { // Si no hay texto, ignorar (ej: solo imagen/video sin caption) if (empty(trim($text))) { return; } require_once __DIR__ . '/src/Translate.php'; $translator = new src\Translate(); // Detectar idioma del mensaje $detectedLang = $translator->detectLanguage($text) ?? 'es'; // Obtener idiomas activos de la base de datos $stmt = $pdo->query("SELECT language_code, flag_emoji FROM supported_languages WHERE is_active = 1"); $activeLanguages = $stmt->fetchAll(); if (count($activeLanguages) <= 1) { return; // No hay suficientes idiomas para traducir } // Guardar texto en la base de datos con hash consistente $textHash = md5($text); $stmt = $pdo->prepare("INSERT INTO translation_cache (text_hash, original_text) VALUES (?, ?) ON DUPLICATE KEY UPDATE original_text = VALUES(original_text)"); $stmt->execute([$textHash, $text]); // 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 ]; } } // Enviar mensaje con botones usando MessageBuilder correctamente if (!empty($buttons)) { $messageText = "🌐 **Traducciones disponibles:**\nHaz clic en una bandera para ver la traducción (solo tú la verás)"; // Crear MessageBuilder $builder = \Discord\Builders\MessageBuilder::new(); $builder->setContent($messageText); // Crear ActionRow con botones $row = new \Discord\Builders\Components\ActionRow(); foreach ($buttons as $btn) { $button = \Discord\Builders\Components\Button::new(\Discord\Builders\Components\Button::STYLE_PRIMARY) ->setLabel($btn['label']) ->setCustomId($btn['custom_id']); $row->addComponent($button); } $builder->addComponent($row); $message->channel->sendMessage($builder); } } catch (Exception $e) { error_log("Discord translation buttons error: " . $e->getMessage()); } } function handleButtonInteraction($interaction, Discord $discord): void { $data = $interaction->data; $customId = $data->custom_id ?? ''; if (str_starts_with($customId, 'translate_')) { handleTranslateInteraction($interaction, $customId); } elseif (str_starts_with($customId, 'chat_mode_')) { try { require_once __DIR__ . '/includes/db.php'; $pdo = getDbConnection(); $parts = explode(':', $customId, 2); $mode = str_replace('chat_mode_', '', $parts[0]); $userId = $parts[1] ?? ''; if (empty($userId)) { $builder = \Discord\Builders\MessageBuilder::new() ->setContent('❌ Error: Usuario no identificado'); $interaction->respondWithMessage($builder, true); return; } $chatMode = ($mode === 'ia') ? 'ia' : 'bot'; $stmt = $pdo->prepare("UPDATE recipients SET chat_mode = ? WHERE platform_id = ? AND platform = 'discord'"); $stmt->execute([$chatMode, $userId]); if ($chatMode === 'ia') { $builder = \Discord\Builders\MessageBuilder::new() ->setContent("✅ **Modo IA activado.** Ahora puedes platicar conmigo. Escribe cualquier cosa y la enviaré a la IA."); } else { $builder = \Discord\Builders\MessageBuilder::new() ->setContent("✅ **Modo Bot activado.** Ahora puedes usar comandos como #comando y traducción."); } $interaction->respondWithMessage($builder, true); } catch (Exception $e) { error_log("Discord chat mode error: " . $e->getMessage()); $builder = \Discord\Builders\MessageBuilder::new() ->setContent('❌ Error al cambiar el modo de chat'); $interaction->respondWithMessage($builder, true); } } } function isExistingDiscordUser(PDO $pdo, int $userId): bool { $stmt = $pdo->prepare("SELECT id FROM recipients WHERE platform_id = ? AND platform = 'discord'"); $stmt->execute([$userId]); return $stmt->fetch() !== false; } function sendDiscordWelcomeMessage(PDO $pdo, Member $member, Discord $discord): void { $stmt = $pdo->query("SELECT * FROM telegram_bot_messages WHERE id = 1"); $config = $stmt->fetch(); $username = $member->user->username; if ($config && $config['is_active']) { $text = str_replace('{user_name}', $username, $config['message_text']); try { $member->sendMessage($text); } catch (Exception $e) { echo "No se pudo enviar mensaje privado: " . $e->getMessage() . PHP_EOL; } } else { try { $member->sendMessage("¡Hola {$username}! 👋\n\nUsa /comandos para ver los comandos disponibles."); } catch (Exception $e) { echo "No se pudo enviar mensaje privado: " . $e->getMessage() . PHP_EOL; } } } function registerDiscordUser(PDO $pdo, $user): void { $stmt = $pdo->prepare(" INSERT INTO recipients (platform_id, name, type, platform, language_code, chat_mode) VALUES (?, ?, 'user', 'discord', 'es', 'agent') ON DUPLICATE KEY UPDATE name = VALUES(name) "); $name = $user->username ?? 'Usuario'; $stmt->execute([$user->id, $name]); } function sendDiscordWelcomeMessageOnMessage(PDO $pdo, Message $message, string $username): void { $stmt = $pdo->query("SELECT * FROM telegram_bot_messages WHERE id = 1"); $config = $stmt->fetch(); if ($config && $config['is_active']) { $text = str_replace('{user_name}', $username, $config['message_text']); $message->author->sendMessage($text); } else { $message->author->sendMessage("¡Hola {$username}! 👋\n\nUsa /comandos para ver los comandos disponibles."); } } function handleTranslateInteraction($interaction, string $customId): void { try { require_once __DIR__ . '/includes/db.php'; require_once __DIR__ . '/src/Translate.php'; $pdo = getDbConnection(); $translator = new src\Translate(); $parts = explode(':', $customId, 2); $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); return; } $originalText = $row['original_text']; // Responder inmediatamente con "Traduciendo..." $loadingBuilder = \Discord\Builders\MessageBuilder::new() ->setContent("⏳ Traduciendo a " . strtoupper($targetLang) . "..."); $interaction->respondWithMessage($loadingBuilder, true); // Ahora traducir (esto puede tardar) $sourceLang = $translator->detectLanguage($originalText) ?? 'es'; $translated = $translator->translate($originalText, $sourceLang, $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 $finalBuilder = \Discord\Builders\MessageBuilder::new() ->setContent("🌐 **Traducción (" . strtoupper($targetLang) . "):**\n\n" . trim($translated)); $interaction->updateOriginalResponse($finalBuilder); } else { // Actualizar con error $errorBuilder = \Discord\Builders\MessageBuilder::new() ->setContent("❌ Error al traducir"); $interaction->updateOriginalResponse($errorBuilder); } } 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()); } } } $discord->run();