Files
sistema_funcionando_lastwar/telegram_bot_webhook.php

554 lines
26 KiB
PHP
Executable File
Raw Permalink Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
require_once __DIR__ . '/includes/logger.php';
custom_log("--- INICIO DE EJECUCIÓN DEL WEBHOOK ---");
require_once __DIR__ . '/config/config.php';
require_once __DIR__ . '/includes/db.php';
require_once __DIR__ . '/src/TelegramSender.php';
require_once __DIR__ . '/src/HtmlToTelegramHtmlConverter.php';
require_once __DIR__ . '/src/Translate.php';
require_once __DIR__ . '/src/CommandLocker.php';
// Configuración de manejo de errores
ini_set('display_errors', 0);
error_reporting(E_ALL);
ini_set('log_errors', 1);
// Asegurarse de que el directorio de logs exista
$logDir = __DIR__ . '/logs';
if (!file_exists($logDir)) {
mkdir($logDir, 0755, true);
}
// Verificación de autenticación
$authToken = $_GET['auth_token'] ?? '';
$expectedToken = $_ENV['TELEGRAM_WEBHOOK_TOKEN'] ?? '';
if (!empty($expectedToken) && $authToken !== $expectedToken) {
http_response_code(403);
custom_log("Acceso no autorizado: token inválido.");
exit('Acceso no autorizado');
}
// Verificar token del bot
$botToken = $_ENV['TELEGRAM_BOT_TOKEN'] ?? '';
if (empty($botToken)) {
http_response_code(500);
custom_log("Token de bot no configurado.");
exit('Token de bot no configurado');
}
// Obtener datos de la actualización
$content = file_get_contents("php://input");
$update = json_decode($content, true);
if (!$update) {
http_response_code(400);
exit('Datos de actualización no válidos');
}
custom_log("Update recibido: " . json_encode($update, JSON_PRETTY_PRINT));
try {
$telegram = new TelegramSender($botToken, $pdo);
$translator = new Translate();
$commandLocker = new CommandLocker($pdo);
$message = $update['message'] ?? $update['channel_post'] ?? null;
$callbackQuery = $update['callback_query'] ?? null;
$userChatMode = null; // Inicializar para evitar errores
// --- INICIO DE LA NUEVA LÓGICA DE CHAT AGENT/IA ---
$chatId = $message['chat']['id'] ?? $callbackQuery['message']['chat']['id'] ?? null;
$userId = $message['from']['id'] ?? $callbackQuery['from']['id'] ?? null;
if (!$chatId) {
custom_log("No se pudo determinar el chat_id. Saliendo.");
exit();
}
$isPrivateChat = (isset($message['chat']['type']) && $message['chat']['type'] === 'private') ||
(isset($callbackQuery['message']['chat']['type']) && $callbackQuery['message']['chat']['type'] === 'private');
if ($isPrivateChat && $userId) {
custom_log("[MODO AGENTE] Chat privado detectado. UserID: $userId");
if ($callbackQuery) {
$data = $callbackQuery['data'];
if ($data === 'platicar_bot' || $data === 'usar_ia') {
$newMode = ($data === 'platicar_bot') ? 'bot' : 'ia';
$stmt = $pdo->prepare("UPDATE recipients SET chat_mode = ? WHERE platform_id = ? AND platform = 'telegram'");
$stmt->execute([$newMode, $userId]);
$responseText = $newMode === 'bot'
? "🤖 Modo cambiado a 'Platicar con bot'. Ahora puedes usar los comandos normales como /comandos."
: "🧠 Modo cambiado a 'Usar IA'. Todo lo que escribas será procesado por la IA.\n\nEscribe /agente para volver a este menú.";
$telegram->answerCallbackQuery($callbackQuery['id']);
$telegram->editMessageText($chatId, $callbackQuery['message']['message_id'], $responseText);
custom_log("[MODO AGENTE] Usuario $userId cambió al modo: $newMode");
exit('ok');
}
}
$stmt = $pdo->prepare("SELECT chat_mode FROM recipients WHERE platform_id = ? AND platform = 'telegram'");
$stmt->execute([$userId]);
$userChatMode = $stmt->fetchColumn();
if ($userChatMode === false) {
custom_log("[NUEVO USUARIO] Usuario $userId no encontrado. Iniciando proceso de bienvenida.");
// 1. Obtener la configuración del mensaje de bienvenida
$configStmt = $pdo->query("SELECT * FROM telegram_bot_messages WHERE id = 1");
$welcomeConfig = $configStmt->fetch(PDO::FETCH_ASSOC);
// Verificar si el registro de usuarios está activo
if ($welcomeConfig && $welcomeConfig['register_users']) {
// 2. Registrar al nuevo usuario
$from = $message['from'];
$userName = trim(($from['first_name'] ?? '') . ' ' . ($from['last_name'] ?? ''));
$languageCode = $from['language_code'] ?? 'es';
$insertStmt = $pdo->prepare(
"INSERT INTO recipients (platform_id, name, type, platform, language_code, chat_mode)
VALUES (?, ?, 'user', 'telegram', ?, 'agent')
ON DUPLICATE KEY UPDATE name = VALUES(name), language_code = VALUES(language_code)"
);
$insertStmt->execute([$userId, $userName, $languageCode]);
custom_log("[NUEVO USUARIO] Usuario $userId ($userName) registrado con modo 'agent'.");
// 3. Enviar el mensaje de bienvenida si está activo
if ($welcomeConfig['is_active']) {
$messageText = str_replace('{user_name}', htmlspecialchars($userName), $welcomeConfig['message_text']);
$keyboard = [
'inline_keyboard' => [
[
['text' => $welcomeConfig['button_text'], 'url' => $welcomeConfig['group_invite_link']]
]
]
];
$telegram->sendMessage($chatId, $messageText, ['reply_markup' => json_encode($keyboard)]);
custom_log("[NUEVO USUARIO] Mensaje de bienvenida enviado a $userId.");
}
} else {
custom_log("[NUEVO USUARIO] El registro de usuarios está desactivado en la configuración. No se realiza ninguna acción.");
}
// Salir después de gestionar al nuevo usuario. La interacción ha terminado.
exit('ok');
}
custom_log("[MODO AGENTE] Modo actual del usuario $userId: $userChatMode");
if (isset($message['text']) && trim($message['text']) === '/agente') {
$stmt = $pdo->prepare("UPDATE recipients SET chat_mode = 'agent' WHERE platform_id = ? AND platform = 'telegram'");
$stmt->execute([$userId]);
$userChatMode = 'agent';
custom_log("[MODO AGENTE] Usuario $userId usó /agente. Reseteando a modo 'agent'.");
}
switch ($userChatMode) {
case 'agent':
$keyboard = [
'inline_keyboard' => [
[
['text' => '🤖 Platicar con bot', 'callback_data' => 'platicar_bot'],
['text' => '🧠 Usar IA', 'callback_data' => 'usar_ia']
]
]
];
$telegram->sendMessage($chatId, "👋 Hola! ¿Cómo quieres interactuar?", ['reply_markup' => json_encode($keyboard)]);
exit('ok');
case 'ia':
custom_log("[DEBUG] Entrando en el caso 'ia'.");
$n8nWebhookUrl = $_ENV['N8N_IA_WEBHOOK_URL'] ?? null;
custom_log("[DEBUG] URL de n8n leída desde .env: " . ($n8nWebhookUrl ? $n8nWebhookUrl : 'NO ENCONTRADA'));
if ($n8nWebhookUrl && isset($message['text'])) {
$postData = [
'chat_id' => $chatId,
'user_id' => $userId,
'message' => $message['text'],
'name' => trim(($message['from']['first_name'] ?? '') . ' ' . ($message['from']['last_name'] ?? ''))
];
custom_log("[DEBUG] Preparando para enviar a n8n. Datos: " . json_encode($postData));
$ch = curl_init($n8nWebhookUrl);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($postData));
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
curl_setopt($ch, CURLOPT_TIMEOUT, 10);
$response = curl_exec($ch);
$curlError = curl_error($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($curlError) {
custom_log("[ERROR] Error en cURL al llamar a n8n: " . $curlError);
} else {
custom_log("[DEBUG] Llamada a n8n completada. Código HTTP: $httpCode. Respuesta: " . $response);
}
custom_log("[MODO AGENTE] Mensaje de $userId reenviado a n8n (modo IA).");
} else {
if (!$n8nWebhookUrl) {
custom_log("[ERROR] La variable N8N_IA_WEBHOOK_URL no está configurada en el .env.");
}
if (!isset($message['text'])) {
custom_log("[DEBUG] No se reenvió a n8n porque no era un mensaje de texto.");
}
}
exit('ok');
case 'bot':
break;
}
}
$isPrivateChatInBotMode = ($isPrivateChat && isset($userChatMode) && $userChatMode === 'bot');
if (!$isPrivateChat || $isPrivateChatInBotMode) {
custom_log("Ejecutando lógica original. Razón: " . (!$isPrivateChat ? "No es chat privado" : "Chat privado en modo BOT"));
if (isset($message['new_chat_members'])) {
foreach ($message['new_chat_members'] as $newMember) {
if (isset($newMember['is_bot']) && $newMember['is_bot']) continue;
$userId = $newMember['id'];
$userName = trim(($newMember['first_name'] ?? '') . ' ' . ($newMember['last_name'] ?? ''));
$languageCode = $newMember['language_code'] ?? 'es';
$stmt = $pdo->prepare("INSERT INTO recipients (platform_id, name, type, platform, language_code) VALUES (?, ?, 'user', 'telegram', ?) ON DUPLICATE KEY UPDATE name = VALUES(name), language_code = VALUES(language_code)");
$stmt->execute([$userId, $userName, $languageCode]);
custom_log("Nuevo miembro $userName ($userId) registrado/actualizado.");
}
exit();
}
if ($callbackQuery) {
$callbackId = $callbackQuery['id'];
$data = $callbackQuery['data'];
$message = $callbackQuery['message'];
$chatId = $message['chat']['id'];
$messageId = $message['message_id'];
custom_log("[CALLBACK] Recibido callback original: " . $data);
if (strpos($data, 'translate_manual:') === 0) {
$targetLang = substr($data, strlen('translate_manual:'));
$originalMessage = $callbackQuery['message'];
$originalText = $originalMessage['text'] ?? $originalMessage['caption'] ?? '';
if (!empty($originalText)) {
try {
$sourceLang = $translator->detectLanguage($originalText);
if ($sourceLang && $sourceLang !== $targetLang) {
$translatedText = $translator->translateHtml($originalText, $sourceLang, $targetLang);
$telegram->answerCallbackQuery($callbackId, ['text' => $translatedText, 'show_alert' => true]);
} else {
$telegram->answerCallbackQuery($callbackId, ['text' => 'El mensaje ya está en este idioma o no se pudo detectar el idioma original.', 'show_alert' => false]);
}
} catch (Exception $e) {
custom_log("[CALLBACK] Excepción en traducción manual: " . $e->getMessage());
$telegram->answerCallbackQuery($callbackId, ['text' => '⚠️ Error interno al traducir.']);
}
} else {
$telegram->answerCallbackQuery($callbackId, ['text' => 'No se encontró texto para traducir.']);
}
exit('ok');
}
if (strpos($data, 'translate:') === 0) {
$parts = explode(':', $data);
if (count($parts) === 3) {
$originalMessageId = $parts[1];
$targetLang = $parts[2];
try {
$stmt = $pdo->prepare("SELECT message_text FROM telegram_interactions WHERE telegram_message_id = ? AND chat_id = ? ORDER BY id DESC LIMIT 1");
$stmt->execute([$originalMessageId, $chatId]);
$originalContent = $stmt->fetchColumn();
if ($originalContent) {
// Detectar idioma real del contenido y aplicar fallback si coincide con el destino
$plain = strip_tags(html_entity_decode($originalContent, ENT_QUOTES | ENT_HTML5, 'UTF-8'));
$sourceLang = $translator->detectLanguage($plain) ?? 'es';
if ($sourceLang === $targetLang) {
$fallbackSrc = 'es';
if ($fallbackSrc !== $targetLang) {
$sourceLang = $fallbackSrc;
}
}
$translatedText = $translator->translateHtml($originalContent, $sourceLang, $targetLang);
if ($translatedText) {
$telegram->sendMessage($chatId, $translatedText, ['parse_mode' => 'HTML'], false, $targetLang);
// Conservar los botones para permitir traducir a otros idiomas
$telegram->answerCallbackQuery($callbackId, ['text' => '✅ Traducción enviada']);
} else {
$telegram->answerCallbackQuery($callbackId, ['text' => '⚠️ Error al traducir.']);
}
} else {
$telegram->answerCallbackQuery($callbackId, ['text' => '⚠️ No se encontró el texto original.']);
}
} catch (Exception $e) {
custom_log("[CALLBACK] Excepción al traducir: " . $e->getMessage());
$telegram->answerCallbackQuery($callbackId, ['text' => '⚠️ Error interno.']);
}
}
$telegram->answerCallbackQuery($callbackId);
exit();
}
}
if (isset($message['text'])) {
$text = trim($message['text']);
$from = $message['from'] ?? null;
$userId = $from['id'] ?? null;
$messageId = $message['message_id'] ?? null;
if (!$userId) {
custom_log("No se pudo determinar el ID de usuario para la lógica original.");
exit();
}
$isCommand = (strpos($text, '/') === 0) || (strpos($text, '#') === 0);
if ($isCommand) {
handleCommand($pdo, $telegram, $commandLocker, $translator, $text, $from, $chatId, $messageId);
} else {
handleRegularMessage($pdo, $telegram, $commandLocker, $translator, $text, $from, $chatId, $messageId);
}
}
}
http_response_code(200);
exit('ok');
} catch (Exception $e) {
custom_log("Error al procesar la actualización de Telegram: " . $e->getMessage());
http_response_code(500);
exit('Error interno del servidor');
}
function handleCommand($pdo, $telegram, $commandLocker, $translator, $text, $from, $chatId, $messageId) {
$userId = $from['id'];
$detectedLang = $translator->detectLanguage($text) ?? 'es';
if (strpos($text, '/setlang') === 0 || strpos($text, '/setlanguage') === 0) {
$parts = explode(' ', $text, 2);
if (count($parts) < 2 || strlen(trim($parts[1])) !== 2) {
$telegram->sendMessage($chatId, "❌ Formato incorrecto. Usa: /setlang es", [], true);
return;
}
$newLangCode = strtolower(trim($parts[1]));
$userName = trim(($from['first_name'] ?? '') . ' ' . ($from['last_name'] ?? ''));
$stmt = $pdo->prepare("SELECT id FROM recipients WHERE platform_id = ? AND platform = 'telegram'");
$stmt->execute([$userId]);
$userExists = $stmt->fetch();
if ($userExists) {
$stmt = $pdo->prepare("UPDATE recipients SET language_code = ?, name = ? WHERE platform_id = ? AND platform = 'telegram'");
$stmt->execute([$newLangCode, $userName, $userId]);
} else {
$stmt = $pdo->prepare("INSERT INTO recipients (platform_id, name, type, platform, language_code) VALUES (?, ?, 'user', 'telegram', ?)");
$stmt->execute([$userId, $userName, $newLangCode]);
}
$telegram->sendMessage($chatId, "✅ Tu idioma ha sido establecido a '" . strtoupper($newLangCode) . "'.", [], true, $detectedLang);
} elseif (strpos($text, '/bienvenida') === 0) {
custom_log("[COMANDO] Usuario $userId solicitó el mensaje de bienvenida.");
$configStmt = $pdo->query("SELECT * FROM telegram_bot_messages WHERE id = 1");
$welcomeConfig = $configStmt->fetch(PDO::FETCH_ASSOC);
if ($welcomeConfig && $welcomeConfig['is_active']) {
$userName = trim(($from['first_name'] ?? '') . ' ' . ($from['last_name'] ?? ''));
$messageText = str_replace('{user_name}', htmlspecialchars($userName), $welcomeConfig['message_text']);
$keyboard = [
'inline_keyboard' => [
[
['text' => $welcomeConfig['button_text'], 'url' => $welcomeConfig['group_invite_link']]
]
]
];
$telegram->sendMessage($chatId, $messageText, ['reply_markup' => json_encode($keyboard)]);
custom_log("[COMANDO] Mensaje de bienvenida enviado a $userId por solicitud.");
} else {
custom_log("[COMANDO] /bienvenida solicitado, pero el mensaje está inactivo.");
$telegram->sendMessage($chatId, " La función de bienvenida no está activa en este momento.", [], true, $detectedLang);
}
} elseif (strpos($text, '/comandos') === 0) {
$stmt = $pdo->query("SELECT telegram_command, name FROM recurrent_messages WHERE telegram_command IS NOT NULL AND telegram_command != '' ORDER BY name ASC");
$commands = $stmt->fetchAll(PDO::FETCH_ASSOC);
$response = "";
if (empty($commands)) {
$response = " No hay comandos personalizados disponibles.";
} else {
$response = "📋 <b>LISTA DE COMANDOS DISPONIBLES</b>\n\n";
foreach ($commands as $index => $cmd) {
$command = htmlspecialchars(trim($cmd['telegram_command']));
if (strpos($command, '#') !== 0) $command = '#' . $command;
$name = htmlspecialchars(trim($cmd['name']));
$response .= ($index + 1) . ". <code>" . $command . "</code> - " . $name . "\n";
}
$response .= "\n Escribe el comando para usarlo.";
}
$stmt = $pdo->prepare("SELECT language_code FROM recipients WHERE platform_id = ? AND platform = 'telegram'");
$stmt->execute([$userId]);
$userLang = $stmt->fetchColumn();
if (!$userLang || $userLang === 'es') {
$converter = new HtmlToTelegramHtmlConverter();
$convertedContent = $converter->convert($response);
$telegram->sendMessage($chatId, $convertedContent, ['parse_mode' => 'HTML'], true, 'es');
} else {
$plainText = strip_tags(str_replace(['<br>', '</p>'], "\n", $response));
$plainText = html_entity_decode($plainText, ENT_QUOTES | ENT_HTML5, 'UTF-8');
$telegram->sendMessage($chatId, $plainText, ['parse_mode' => 'HTML'], true, 'es');
}
} elseif (strpos($text, '#') === 0) {
$command = ltrim($text, '#');
if ($commandLocker->isCommandProcessing($command, $chatId, 'command')) {
$telegram->sendMessage($chatId, "El comando `#{$command}` ya está siendo procesado. Por favor, espera un momento.", [], true, 'es');
return;
}
$lockId = $commandLocker->acquireLock($command, $chatId, 'command', ['user_id' => $userId, 'original_message' => $text]);
if ($lockId === false) {
$telegram->sendMessage($chatId, "No se pudo procesar el comando. Inténtalo de nuevo.", [], true, 'es');
return;
}
try {
$stmt = $pdo->prepare("SELECT message_content FROM recurrent_messages WHERE telegram_command = ?");
$stmt->execute([$command]);
$template = $stmt->fetch(PDO::FETCH_ASSOC);
if ($template) {
$content = $template['message_content'];
$converter = new HtmlToTelegramHtmlConverter();
$content = $converter->convert($content);
$detectedLang = $translator->detectLanguage(strip_tags($content)) ?? 'es';
if ($detectedLang !== 'es') {
$translatedContent = $translator->translateHtml($content, $detectedLang, 'es');
if ($translatedContent) $content = $translatedContent;
}
usleep(500000);
$result = $telegram->sendMessage($chatId, $content, ['parse_mode' => 'HTML'], true, 'es');
// TelegramSender::sendMessage devuelve un array de partes enviadas con message_id
$sentMessageId = null;
if (is_array($result)) {
// Tomar el último message_id exitoso
for ($i = count($result) - 1; $i >= 0; $i--) {
if (isset($result[$i]['success']) && $result[$i]['success'] && isset($result[$i]['message_id'])) {
$sentMessageId = $result[$i]['message_id'];
break;
}
}
} elseif (isset($result['result']['message_id'])) {
$sentMessageId = $result['result']['message_id'];
}
if (!empty($sentMessageId)) {
$commandLocker->releaseLock($lockId, $sentMessageId);
// Añadir botones de traducción dinámicos
$langStmt = $pdo->query("SELECT language_code, language_name, flag_emoji FROM supported_languages WHERE is_active = 1");
$activeLangs = $langStmt->fetchAll(PDO::FETCH_ASSOC);
if (count($activeLangs) > 1) {
$keyboard = [];
foreach ($activeLangs as $lang) {
$keyboard[] = ['text' => ($lang['flag_emoji'] ?? '') . ' ' . $lang['language_name'], 'callback_data' => 'translate:' . $sentMessageId . ':' . $lang['language_code']];
}
// Agrupar botones en filas de a 2
$inline_keyboard = array_chunk($keyboard, 2);
$replyMarkup = json_encode(['inline_keyboard' => $inline_keyboard]);
$telegram->editMessageReplyMarkup($chatId, $sentMessageId, $replyMarkup);
}
} else {
$commandLocker->failLock($lockId, 'Error al enviar el mensaje');
$telegram->sendMessage($chatId, "Ocurrió un error al procesar el comando.", [], true, 'es');
}
} else {
$commandLocker->releaseLock($lockId);
$telegram->sendMessage($chatId, "El comando `#{$command}` no fue encontrado.", [], true, $detectedLang);
}
} catch (Exception $e) {
if (isset($lockId)) $commandLocker->failLock($lockId, 'Excepción: ' . $e->getMessage());
custom_log("Error al procesar comando #{$command}: " . $e->getMessage());
$telegram->sendMessage($chatId, "Ocurrió un error inesperado.", [], true, 'es');
}
}
}
function handleRegularMessage($pdo, $telegram, $commandLocker, $translator, $text, $from, $chatId, $messageId) {
$userId = $from['id'];
try {
// 1. Detectar el idioma del mensaje entrante
$detectedLang = $translator->detectLanguage(strip_tags($text)) ?? 'es';
// 2. Guardar la interacción original en la base de datos
try {
$stmt = $pdo->prepare("INSERT INTO telegram_interactions (chat_id, message_text, direction, language_code) VALUES (?, ?, 'in', ?)");
$stmt->execute([$chatId, $text, $detectedLang]);
} catch (PDOException $e) {
custom_log("[ERROR] No se pudo guardar el mensaje entrante en telegram_interactions: " . $e->getMessage());
// No detenemos la ejecución, la traducción es más importante.
}
// 3. 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);
if (in_array($detectedLang, $activeLangs)) {
foreach ($activeLangs as $targetLang) {
if ($detectedLang === $targetLang) {
continue; // No traducir al mismo idioma
}
$sql = "INSERT INTO translation_queue (message_id, chat_id, user_id, text_to_translate, source_lang, target_lang, platform) VALUES (?, ?, ?, ?, ?, ?, 'telegram')";
$stmt = $pdo->prepare($sql);
$stmt->execute([
$messageId,
$chatId,
$userId,
$text,
$detectedLang,
$targetLang
]);
custom_log("[QUEUE] Mensaje #$messageId encolado para traducción de '$detectedLang' a '$targetLang'.");
}
}
} catch (Exception $e) {
custom_log("[ERROR] Error en handleRegularMessage al encolar traducción: " . $e->getMessage());
}
}