Commit inicial con archivos existentes
This commit is contained in:
851
telegram/TelegramSender.php
Executable file
851
telegram/TelegramSender.php
Executable file
@@ -0,0 +1,851 @@
|
||||
<?php
|
||||
|
||||
require_once __DIR__ . '/../includes/logger.php';
|
||||
require_once __DIR__ . '/../src/Translate.php';
|
||||
|
||||
class TelegramSender
|
||||
{
|
||||
private $botToken;
|
||||
private $apiUrl = 'https://api.telegram.org/bot';
|
||||
private $pdo;
|
||||
|
||||
public function __construct($botToken, $pdo)
|
||||
{
|
||||
$this->botToken = $botToken;
|
||||
$this->pdo = $pdo;
|
||||
}
|
||||
|
||||
public function sendMessage($chatId, $content, $options = [], $addTranslateButton = false, $messageLanguage = 'es', $originalFullContent = null)
|
||||
{
|
||||
try {
|
||||
custom_log("Iniciando envío de mensaje a chat $chatId");
|
||||
if (empty(trim($content))) {
|
||||
custom_log("Error: Contenido vacío para el chat $chatId");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Obtener el language_code del destinatario
|
||||
$recipientLang = 'es'; // Idioma por defecto
|
||||
try {
|
||||
$stmt = $this->pdo->prepare("SELECT language_code FROM recipients WHERE platform_id = ? AND platform = 'telegram'");
|
||||
$stmt->execute([$chatId]);
|
||||
$dbLang = $stmt->fetchColumn();
|
||||
if ($dbLang) {
|
||||
$recipientLang = $dbLang;
|
||||
}
|
||||
} catch (PDOException $e) {
|
||||
custom_log("Error al obtener language_code del destinatario: " . $e->getMessage());
|
||||
}
|
||||
custom_log("[DEBUG] Idioma del destinatario ($chatId): " . $recipientLang);
|
||||
|
||||
$parts = $this->parseContent($content);
|
||||
if (empty($parts)) {
|
||||
custom_log("No se pudo parsear el contenido para el chat $chatId");
|
||||
return false;
|
||||
}
|
||||
|
||||
$messageIds = [];
|
||||
$all_sent_successfully = true;
|
||||
$totalParts = count($parts);
|
||||
custom_log("Procesando $totalParts partes del mensaje para el chat $chatId");
|
||||
|
||||
foreach ($parts as $index => $part) {
|
||||
$isLastPart = ($index === $totalParts - 1);
|
||||
$partOptions = $isLastPart ? $options : [];
|
||||
|
||||
try {
|
||||
custom_log("Procesando parte " . ($index + 1) . "/$totalParts de tipo: " . $part['type']);
|
||||
$response = null;
|
||||
$partSuccess = false;
|
||||
|
||||
if ($part['type'] === 'text') {
|
||||
// Asegurarse de que el parámetro addTranslateButton se pase correctamente
|
||||
if ($isLastPart && $addTranslateButton) {
|
||||
$partOptions['add_translate_button'] = true;
|
||||
}
|
||||
$response = $this->sendText($chatId, $part['content'], $partOptions);
|
||||
$partSuccess = ($response && ($response['ok'] ?? false));
|
||||
} elseif ($part['type'] === 'image') {
|
||||
// Verificar la URL de la imagen antes de intentar enviarla
|
||||
$imageUrl = $part['url'] ?? '';
|
||||
custom_log("Verificando URL de imagen: " . $imageUrl);
|
||||
|
||||
if ($this->isValidImageUrl($imageUrl)) {
|
||||
// Asegurarse de que el parámetro addTranslateButton se pase correctamente
|
||||
if ($isLastPart && $addTranslateButton) {
|
||||
$partOptions['add_translate_button'] = true;
|
||||
}
|
||||
$response = $this->sendPhoto($chatId, $imageUrl, $part['caption'] ?? '', $partOptions);
|
||||
$partSuccess = ($response && ($response['ok'] ?? false));
|
||||
} else {
|
||||
$errorMsg = "URL de imagen no válida o inaccesible: " . $imageUrl;
|
||||
custom_log($errorMsg);
|
||||
|
||||
// Enviar un mensaje de error al chat
|
||||
$errorText = "⚠️ No se pudo cargar una imagen: " . basename($imageUrl) . "\n";
|
||||
if (!empty($part['caption'])) {
|
||||
$errorText .= "📝 " . $part['caption'] . "\n";
|
||||
}
|
||||
$errorText .= "🔗 " . $imageUrl;
|
||||
|
||||
$this->sendText($chatId, $errorText);
|
||||
$partSuccess = false;
|
||||
}
|
||||
}
|
||||
|
||||
if ($partSuccess) {
|
||||
if (isset($response['result']['message_id'])) {
|
||||
$messageIds[] = [
|
||||
'success' => true,
|
||||
'message_id' => $response['result']['message_id'],
|
||||
'type' => $part['type']
|
||||
];
|
||||
|
||||
}
|
||||
} else {
|
||||
$error = $response['description'] ?? 'Error desconocido al procesar la parte del mensaje';
|
||||
custom_log("Error al enviar parte " . ($index + 1) . "/$totalParts: " . json_encode($error));
|
||||
|
||||
// Agregar el error a los IDs de mensaje para referencia
|
||||
$messageIds[] = [
|
||||
'success' => false,
|
||||
'error' => $error,
|
||||
'type' => $part['type'],
|
||||
'content' => $part['type'] === 'image' ? $part['url'] : substr($part['content'] ?? '', 0, 100) . '...'
|
||||
];
|
||||
|
||||
$all_sent_successfully = false;
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
custom_log("Excepción al procesar parte: " . $e->getMessage());
|
||||
$all_sent_successfully = false;
|
||||
}
|
||||
|
||||
if (!$isLastPart) {
|
||||
usleep(500000); // 500ms de pausa
|
||||
}
|
||||
}
|
||||
|
||||
// Si hubo algún error, devolver información detallada
|
||||
if (!$all_sent_successfully) {
|
||||
$failedParts = array_filter($messageIds, function($msg) {
|
||||
return isset($msg['success']) && $msg['success'] === false;
|
||||
});
|
||||
|
||||
custom_log("No se pudieron enviar " . count($failedParts) . " partes del mensaje");
|
||||
|
||||
// Si no se pudo enviar ninguna parte, devolver falso
|
||||
if (count($failedParts) === count($messageIds)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Obtener los mensajes enviados exitosamente
|
||||
$sentMessages = array_filter($messageIds, function($msg) {
|
||||
return !empty($msg['success']) && !empty($msg['message_id']);
|
||||
});
|
||||
|
||||
// Guardar el mensaje saliente en telegram_interactions con el idioma del remitente
|
||||
// Esto se hace una vez, después de que todas las partes del mensaje han sido enviadas.
|
||||
if ($all_sent_successfully && !empty($messageIds)) {
|
||||
$lastSentMessage = end($messageIds);
|
||||
$lastMessageId = $lastSentMessage['message_id'] ?? null;
|
||||
$contentToSave = $originalFullContent ?? $content; // Use originalFullContent or the assembled content
|
||||
|
||||
if ($lastMessageId) {
|
||||
// Ensure contentToSave is not NULL
|
||||
if ($contentToSave === null) {
|
||||
$contentToSave = '';
|
||||
}
|
||||
try {
|
||||
$stmt = $this->pdo->prepare("INSERT INTO telegram_interactions (chat_id, message_text, direction, telegram_message_id, language_code) VALUES (?, ?, 'out', ?, ?)");
|
||||
$stmt->execute([$chatId, $contentToSave, $lastMessageId, $messageLanguage]);
|
||||
custom_log("[DEBUG] Mensaje completo guardado con idioma: $messageLanguage y message_id: " . $lastMessageId);
|
||||
} catch (PDOException $e) {
|
||||
custom_log("Error al guardar mensaje saliente en telegram_interactions (después del loop): " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Lógica para los botones de traducción basados en supported_languages
|
||||
if ($addTranslateButton && !empty($sentMessages)) {
|
||||
$lastMessage = end($sentMessages);
|
||||
$lastMessageId = $lastMessage['message_id'] ?? null;
|
||||
|
||||
if ($lastMessageId) {
|
||||
$messageLang = strtolower($messageLanguage);
|
||||
$buttons = [];
|
||||
try {
|
||||
$stmt = $this->pdo->query("SELECT language_code, flag_emoji FROM supported_languages WHERE is_active = 1 ORDER BY language_name ASC");
|
||||
$langs = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
foreach ($langs as $lang) {
|
||||
$code = strtolower($lang['language_code']);
|
||||
if ($code === $messageLang) continue; // no traducir al mismo idioma
|
||||
$flag = $lang['flag_emoji'] ?: '';
|
||||
$buttons[] = [
|
||||
'text' => $flag !== '' ? $flag : strtoupper($code),
|
||||
'callback_data' => 'translate:' . $lastMessageId . ':' . $code
|
||||
];
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
custom_log('[DEBUG] No se pudieron obtener idiomas activos para botones: ' . $e->getMessage());
|
||||
}
|
||||
|
||||
if (!empty($buttons)) {
|
||||
// Armar filas de hasta 6 botones por fila para no saturar
|
||||
$inline = [];
|
||||
$row = [];
|
||||
foreach ($buttons as $i => $btn) {
|
||||
$row[] = $btn;
|
||||
if (count($row) >= 6) {
|
||||
$inline[] = $row;
|
||||
$row = [];
|
||||
}
|
||||
}
|
||||
if (!empty($row)) $inline[] = $row;
|
||||
|
||||
$keyboard = ['inline_keyboard' => $inline];
|
||||
custom_log('[DEBUG] Enviando ' . count($buttons) . ' botones de traducción para mensaje ' . $lastMessageId);
|
||||
$this->editMessageReplyMarkup($chatId, $lastMessageId, json_encode($keyboard));
|
||||
} else {
|
||||
custom_log('[DEBUG] No hay idiomas activos distintos a ' . $messageLang . ' para mostrar botones.');
|
||||
}
|
||||
} else {
|
||||
custom_log('[DEBUG] No se muestra botón de traducción. Razón: Falta message ID');
|
||||
}
|
||||
}
|
||||
|
||||
// Devolver los IDs de los mensajes que se enviaron correctamente
|
||||
return $messageIds;
|
||||
} catch (Exception $e) {
|
||||
custom_log("Error crítico en sendMessage para el chat $chatId: " . $e->getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public function deleteMessage($chatId, $messageId)
|
||||
{
|
||||
$response = $this->apiRequest('deleteMessage', [
|
||||
'chat_id' => $chatId,
|
||||
'message_id' => $messageId
|
||||
]);
|
||||
if (!isset($response['ok']) || $response['ok'] !== true) {
|
||||
throw new Exception("Error de Telegram al eliminar");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public function editMessageText($chatId, $messageId, $text, $options = [])
|
||||
{
|
||||
$params = array_merge([
|
||||
'chat_id' => $chatId,
|
||||
'message_id' => $messageId,
|
||||
'text' => $text
|
||||
], $options);
|
||||
|
||||
return $this->apiRequest('editMessageText', $params);
|
||||
}
|
||||
|
||||
public function editMessageReplyMarkup($chatId, $messageId, $replyMarkup)
|
||||
{
|
||||
$params = [
|
||||
'chat_id' => $chatId,
|
||||
'message_id' => $messageId
|
||||
];
|
||||
|
||||
// Solo agregar reply_markup si no está vacío
|
||||
if (!empty($replyMarkup)) {
|
||||
$params['reply_markup'] = $replyMarkup;
|
||||
} else {
|
||||
// Para eliminar el teclado, necesitamos enviar un objeto JSON vacío
|
||||
$params['reply_markup'] = '{"inline_keyboard":[]}';
|
||||
}
|
||||
|
||||
return $this->apiRequest('editMessageReplyMarkup', $params);
|
||||
}
|
||||
|
||||
public function answerCallbackQuery($callbackQueryId, $options = [])
|
||||
{
|
||||
$params = array_merge([
|
||||
'callback_query_id' => $callbackQueryId
|
||||
], $options);
|
||||
|
||||
return $this->apiRequest('answerCallbackQuery', $params);
|
||||
}
|
||||
|
||||
private function parseContent($html)
|
||||
{
|
||||
$parts = [];
|
||||
if (empty(trim($html))) return [];
|
||||
|
||||
if (strip_tags($html) === $html) {
|
||||
return [['type' => 'text', 'content' => $html]];
|
||||
}
|
||||
|
||||
preg_match_all('/(<img[^>]+src=[\'\"]([^\'\"]+)[\'\"][^>]*>)/i', $html, $matches, PREG_OFFSET_CAPTURE | PREG_SET_ORDER);
|
||||
|
||||
if (empty($matches)) {
|
||||
return [['type' => 'text', 'content' => $html]];
|
||||
}
|
||||
|
||||
$last_pos = 0;
|
||||
foreach ($matches as $match) {
|
||||
$full_match = $match[0][0];
|
||||
$image_url = $match[2][0];
|
||||
$offset = $match[0][1];
|
||||
|
||||
$text_before = substr($html, $last_pos, $offset - $last_pos);
|
||||
if (!empty(trim($text_before))) {
|
||||
$parts[] = ['type' => 'text', 'content' => trim($text_before)];
|
||||
}
|
||||
|
||||
// Convertir rutas relativas a absolutas si es necesario
|
||||
if (!preg_match('/^https?:\/\//', $image_url)) {
|
||||
$base_url = rtrim($_ENV['APP_URL'], '/');
|
||||
$image_url = $base_url . '/' . ltrim($image_url, '/');
|
||||
custom_log("[DEBUG] URL de imagen convertida: " . $image_url);
|
||||
|
||||
// Verificar si la URL es accesible
|
||||
$headers = @get_headers($image_url);
|
||||
if ($headers && strpos($headers[0], '200') === false) {
|
||||
error_log("[ERROR] La imagen no es accesible en: " . $image_url);
|
||||
} else {
|
||||
error_log("[DEBUG] La imagen es accesible en: " . $image_url);
|
||||
}
|
||||
}
|
||||
$parts[] = ['type' => 'image', 'url' => $image_url, 'caption' => ''];
|
||||
|
||||
$last_pos = $offset + strlen($full_match);
|
||||
}
|
||||
|
||||
$remaining_text = substr($html, $last_pos);
|
||||
if (!empty(trim($remaining_text))) {
|
||||
$parts[] = ['type' => 'text', 'content' => trim($remaining_text)];
|
||||
}
|
||||
|
||||
return $parts;
|
||||
}
|
||||
|
||||
private function splitLongText($text, $maxLength = 4000)
|
||||
{
|
||||
if (mb_strlen($text) <= $maxLength) return [$text];
|
||||
$parts = [];
|
||||
$paragraphs = preg_split('/\n\s*\n/', $text);
|
||||
$currentPart = '';
|
||||
foreach ($paragraphs as $paragraph) {
|
||||
if ((mb_strlen($currentPart) + mb_strlen($paragraph) + 2) <= $maxLength) {
|
||||
$currentPart .= (empty($currentPart) ? '' : "\n\n") . $paragraph;
|
||||
} else {
|
||||
if (!empty($currentPart)) $parts[] = $currentPart;
|
||||
$currentPart = $paragraph;
|
||||
while (mb_strlen($currentPart) > $maxLength) {
|
||||
$chunk = mb_substr($currentPart, 0, $maxLength);
|
||||
$parts[] = $chunk;
|
||||
$currentPart = mb_substr($currentPart, $maxLength);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!empty($currentPart)) $parts[] = $currentPart;
|
||||
return $parts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Envía un mensaje de texto a un chat de Telegram, manejando mensajes largos y rate limiting
|
||||
*
|
||||
* @param int|string $chatId ID del chat de Telegram
|
||||
* @param string $text Texto a enviar
|
||||
* @param array $options Opciones adicionales para el mensaje
|
||||
* @param int $retryCount Número de reintentos actual (uso interno)
|
||||
* @param int $maxRetries Número máximo de reintentos
|
||||
* @return array|null Respuesta de la API o null en caso de error
|
||||
*/
|
||||
private function sendText($chatId, $text, $options = [], $retryCount = 0, $maxRetries = 3)
|
||||
{
|
||||
// Asegurarse de que $options sea un array
|
||||
if (!is_array($options)) {
|
||||
custom_log("ADVERTENCIA: Las opciones no son un array. Valor recibido: " . print_r($options, true));
|
||||
$options = [];
|
||||
}
|
||||
|
||||
// Verificar si se debe agregar el botón de traducción
|
||||
$addTranslateButton = $options['add_translate_button'] ?? false;
|
||||
unset($options['add_translate_button']); // Eliminar para evitar conflictos
|
||||
|
||||
// Configuración de depuración
|
||||
$debug = $options['debug'] ?? false;
|
||||
unset($options['debug']);
|
||||
|
||||
// Limitar la longitud de los mensajes de depuración
|
||||
$debugText = $debug ? substr($text, 0, 200) : '';
|
||||
$logPrefix = "[SEND_TEXT] [Chat: $chatId]";
|
||||
|
||||
custom_log("$logPrefix === INICIO ===");
|
||||
custom_log("$logPrefix Tamaño del texto: " . mb_strlen($text, 'UTF-8') . " caracteres");
|
||||
|
||||
if ($debug) {
|
||||
custom_log("$logPrefix Texto (primeros 200 caracteres): $debugText");
|
||||
custom_log("$logPrefix Opciones: " . json_encode($options));
|
||||
custom_log("$logPrefix Mostrar botón de traducción: " . ($addTranslateButton ? 'Sí' : 'No'));
|
||||
}
|
||||
|
||||
// Limpiar el texto de etiquetas HTML innecesarias
|
||||
$clean_text = trim(strip_tags($text));
|
||||
if (empty($clean_text)) {
|
||||
custom_log("$logPrefix ERROR: El texto está vacío después de limpiar etiquetas");
|
||||
return null;
|
||||
}
|
||||
|
||||
// Dividir el texto en partes manejables
|
||||
$textParts = $this->splitLongText($clean_text);
|
||||
$totalParts = count($textParts);
|
||||
$allResponses = [];
|
||||
$allSuccessful = true;
|
||||
|
||||
custom_log("$logPrefix Texto dividido en $totalParts partes");
|
||||
|
||||
foreach ($textParts as $index => $part) {
|
||||
$isLastPart = ($index === $totalParts - 1);
|
||||
$partNumber = $index + 1;
|
||||
|
||||
// Configurar parámetros básicos del mensaje
|
||||
$params = [
|
||||
'chat_id' => $chatId,
|
||||
'text' => $part,
|
||||
'disable_web_page_preview' => true,
|
||||
'parse_mode' => 'HTML',
|
||||
'disable_notification' => $options['disable_notification'] ?? false
|
||||
];
|
||||
|
||||
// Agregar opciones adicionales solo a la última parte del mensaje
|
||||
if ($isLastPart) {
|
||||
// Mantener solo las opciones permitidas por la API de Telegram
|
||||
$allowedOptions = [
|
||||
'reply_markup', 'reply_to_message_id', 'allow_sending_without_reply',
|
||||
'entities', 'protect_content', 'message_thread_id'
|
||||
];
|
||||
|
||||
foreach ($allowedOptions as $option) {
|
||||
if (isset($options[$option])) {
|
||||
$params[$option] = $options[$option];
|
||||
}
|
||||
}
|
||||
|
||||
if ($debug) {
|
||||
custom_log("$logPrefix [Parte $partNumber/$totalParts] Opciones aplicadas: " .
|
||||
json_encode(array_keys($params)));
|
||||
}
|
||||
}
|
||||
|
||||
// Intentar enviar la parte del mensaje con reintentos
|
||||
$attempt = 0;
|
||||
$maxAttempts = 3;
|
||||
$response = null;
|
||||
|
||||
while ($attempt < $maxAttempts) {
|
||||
$attempt++;
|
||||
custom_log("$logPrefix [Parte $partNumber/$totalParts] Intento $attempt de $maxAttempts");
|
||||
|
||||
$response = $this->apiRequest('sendMessage', $params);
|
||||
|
||||
// Verificar si la solicitud fue exitosa
|
||||
if (!empty($response) && ($response['ok'] ?? false)) {
|
||||
$allResponses[] = $response;
|
||||
|
||||
if ($debug) {
|
||||
$msgId = $response['result']['message_id'] ?? 'desconocido';
|
||||
custom_log("$logPrefix [Parte $partNumber/$totalParts] Enviado exitosamente (ID: $msgId)");
|
||||
}
|
||||
|
||||
// Pequeña pausa entre mensajes para evitar rate limiting
|
||||
if (!$isLastPart) {
|
||||
usleep(500000); // 0.5 segundos
|
||||
}
|
||||
|
||||
break; // Salir del bucle de reintentos
|
||||
} else {
|
||||
$errorCode = $response['error_code'] ?? 'desconocido';
|
||||
$errorDesc = $response['description'] ?? 'Error desconocido';
|
||||
|
||||
custom_log("$logPrefix [Parte $partNumber/$totalParts] Error $errorCode: $errorDesc");
|
||||
|
||||
// Manejar errores específicos
|
||||
if (strpos($errorDesc, 'Too Many Requests') !== false) {
|
||||
$retryAfter = $response['parameters']['retry_after'] ?? 5; // Por defecto 5 segundos
|
||||
custom_log("$logPrefix Rate limit alcanzado. Reintentando en $retryAfter segundos...");
|
||||
sleep($retryAfter);
|
||||
continue; // Reintentar
|
||||
}
|
||||
|
||||
// Para otros errores, esperar un tiempo antes de reintentar
|
||||
if ($attempt < $maxAttempts) {
|
||||
$backoffTime = pow(2, $attempt); // Retroceso exponencial
|
||||
custom_log("$logPrefix Reintentando en $backoffTime segundos...");
|
||||
sleep($backoffTime);
|
||||
} else {
|
||||
custom_log("$logPrefix Se agotaron los reintentos para esta parte del mensaje");
|
||||
$allSuccessful = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Si no se pudo enviar después de los reintentos, marcar como fallido
|
||||
if (empty($response) || !($response['ok'] ?? false)) {
|
||||
$allSuccessful = false;
|
||||
|
||||
// Si es un error crítico, detener el envío de las partes restantes
|
||||
$errorCode = $response['error_code'] ?? 0;
|
||||
$criticalErrors = [400, 403, 404]; // Errores que indican que no tiene sentido seguir intentando
|
||||
|
||||
if (in_array($errorCode, $criticalErrors)) {
|
||||
custom_log("$logPrefix Error crítico ($errorCode). Deteniendo el envío de las partes restantes.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Devolver la última respuesta exitosa o null si hubo errores
|
||||
$result = $allSuccessful ? end($allResponses) : null;
|
||||
custom_log("$logPrefix === FIN === " . ($allSuccessful ? 'ÉXITO' : 'CON ERRORES'));
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Envía una foto a un chat de Telegram con manejo de errores y reintentos
|
||||
*
|
||||
* @param int|string $chatId ID del chat de Telegram
|
||||
* @param string $photoUrl URL de la foto a enviar
|
||||
* @param string $caption Texto opcional para la foto
|
||||
* @param array $options Opciones adicionales para el mensaje
|
||||
* @param int $retryCount Número de reintentos actual (uso interno)
|
||||
* @param int $maxRetries Número máximo de reintentos
|
||||
* @return array|bool Respuesta de la API o false en caso de error
|
||||
*/
|
||||
private function sendPhoto($chatId, $photoUrl, $caption = '', $options = [], $retryCount = 0, $maxRetries = 3)
|
||||
{
|
||||
$logPrefix = "[SEND_PHOTO] [Chat: $chatId]";
|
||||
custom_log("$logPrefix === INICIO ===");
|
||||
|
||||
try {
|
||||
// Validar y normalizar la URL de la imagen
|
||||
$photoUrl = $this->normalizeAndValidateImageUrl($photoUrl, $logPrefix);
|
||||
|
||||
// Si la URL no es válida, intentar enviar un mensaje de error
|
||||
if ($photoUrl === false) {
|
||||
$fallbackMessage = $this->createPhotoErrorFallback($photoUrl, $caption, "URL de imagen no válida o inaccesible");
|
||||
return $this->sendText($chatId, $fallbackMessage, $options);
|
||||
}
|
||||
|
||||
// Configurar parámetros básicos para el envío de la foto
|
||||
$params = [
|
||||
'chat_id' => $chatId,
|
||||
'photo' => $photoUrl,
|
||||
'caption' => $caption,
|
||||
'parse_mode' => 'HTML',
|
||||
'disable_notification' => $options['disable_notification'] ?? false
|
||||
];
|
||||
|
||||
// Agregar opciones adicionales permitidas por la API de Telegram
|
||||
$allowedOptions = [
|
||||
'reply_markup', 'reply_to_message_id', 'allow_sending_without_reply',
|
||||
'protect_content', 'message_thread_id'
|
||||
];
|
||||
|
||||
foreach ($allowedOptions as $option) {
|
||||
if (isset($options[$option])) {
|
||||
$params[$option] = $options[$option];
|
||||
}
|
||||
}
|
||||
|
||||
// Realizar el envío con manejo de reintentos
|
||||
$result = $this->sendPhotoWithRetry($params, $photoUrl, $retryCount, $maxRetries, $logPrefix);
|
||||
|
||||
// Si el envío falló después de los reintentos, enviar un mensaje de error
|
||||
if ($result === false || (isset($result['ok']) && !$result['ok'])) {
|
||||
$errorDesc = $result['description'] ?? 'Error desconocido';
|
||||
custom_log("$logPrefix Error al enviar foto después de $maxRetries intentos: $errorDesc");
|
||||
|
||||
$fallbackMessage = $this->createPhotoErrorFallback($photoUrl, $caption, $errorDesc);
|
||||
return $this->sendText($chatId, $fallbackMessage, $options);
|
||||
}
|
||||
|
||||
custom_log("$logPrefix Foto enviada exitosamente");
|
||||
return $result;
|
||||
|
||||
} catch (Exception $e) {
|
||||
custom_log("$logPrefix Excepción: " . $e->getMessage() . " - URL: $photoUrl");
|
||||
|
||||
// En caso de error inesperado, intentar enviar un mensaje de texto
|
||||
$fallbackMessage = $this->createPhotoErrorFallback(
|
||||
$photoUrl,
|
||||
$caption,
|
||||
"Error inesperado: " . $e->getMessage()
|
||||
);
|
||||
|
||||
return $this->sendText($chatId, $fallbackMessage, $options);
|
||||
} finally {
|
||||
custom_log("$logPrefix === FIN ===");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Normaliza y valida una URL de imagen, convirtiendo URLs relativas a absolutas si es necesario
|
||||
*
|
||||
* @param string $url URL de la imagen a normalizar y validar
|
||||
* @param string $logPrefix Prefijo para los mensajes de log
|
||||
* @return string|false URL normalizada o false si no es válida
|
||||
*/
|
||||
private function normalizeAndValidateImageUrl($url, $logPrefix = '')
|
||||
{
|
||||
if (empty($url)) {
|
||||
custom_log("${logPrefix} URL de imagen vacía");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Si la URL es relativa, intentar convertirla a absoluta
|
||||
if (!preg_match('/^https?:\/\//i', $url)) {
|
||||
$baseUrl = rtrim($_ENV['APP_URL'], '/');
|
||||
$absoluteUrl = $baseUrl . '/' . ltrim($url, '/');
|
||||
custom_log("${logPrefix} URL relativa detectada, convirtiendo a absoluta: $absoluteUrl");
|
||||
$url = $absoluteUrl;
|
||||
}
|
||||
|
||||
// Verificar si la URL es accesible y es una imagen
|
||||
if ($this->isValidImageUrl($url)) {
|
||||
custom_log("${logPrefix} URL de imagen válida: $url");
|
||||
return $url;
|
||||
}
|
||||
|
||||
custom_log("${logPrefix} URL de imagen no válida o inaccesible: $url");
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Crea un mensaje de error estandarizado para cuando falla el envío de una foto
|
||||
*
|
||||
* @param string $photoUrl URL de la foto que falló
|
||||
* @param string $caption Texto de la foto (opcional)
|
||||
* @param string $error Descripción del error
|
||||
* @return string Mensaje de error formateado
|
||||
*/
|
||||
private function createPhotoErrorFallback($photoUrl, $caption, $error)
|
||||
{
|
||||
$message = "⚠️ *Error al procesar la imagen*\n";
|
||||
$message .= "\n🔗 *URL*: " . (strlen($photoUrl) > 100 ? substr($photo_url, 0, 100) . '...' : $photoUrl);
|
||||
|
||||
if (!empty($error)) {
|
||||
$message .= "\n\n❌ *Error*: $error";
|
||||
}
|
||||
|
||||
if (!empty($caption)) {
|
||||
$message .= "\n\n📝 *Descripción*: " .
|
||||
(mb_strlen($caption) > 200 ? mb_substr($caption, 0, 200) . '...' : $caption);
|
||||
}
|
||||
|
||||
return $message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Envía una foto a Telegram con manejo de reintentos y rate limiting
|
||||
*
|
||||
* @param array $params Parámetros para la API de Telegram
|
||||
* @param string $photoUrl URL de la foto (solo para registro)
|
||||
* @param int $retryCount Número de reintentos actual
|
||||
* @param int $maxRetries Número máximo de reintentos
|
||||
* @param string $logPrefix Prefijo para los mensajes de log
|
||||
* @return array|false Respuesta de la API o false en caso de error
|
||||
*/
|
||||
private function sendPhotoWithRetry($params, $photoUrl, $retryCount = 0, $maxRetries = 3, $logPrefix = '')
|
||||
{
|
||||
$attempt = 0;
|
||||
$lastError = null;
|
||||
|
||||
while ($attempt <= $maxRetries) {
|
||||
$attempt++;
|
||||
custom_log("${logPrefix} Intento $attempt de $maxRetries - Enviando foto...");
|
||||
|
||||
$response = $this->apiRequest('sendPhoto', $params);
|
||||
|
||||
// Si la solicitud fue exitosa, devolver la respuesta
|
||||
if (!empty($response) && ($response['ok'] ?? false)) {
|
||||
custom_log("${logPrefix} Foto enviada exitosamente en el intento $attempt");
|
||||
return $response;
|
||||
}
|
||||
|
||||
// Si hay un error, registrarlo y verificar si se debe reintentar
|
||||
$errorCode = $response['error_code'] ?? 'desconocido';
|
||||
$errorDesc = $response['description'] ?? 'Error desconocido';
|
||||
$lastError = $errorDesc;
|
||||
|
||||
custom_log("${logPrefix} Error en el intento $attempt: [$errorCode] $errorDesc");
|
||||
|
||||
// Si es un error de rate limit, esperar el tiempo indicado
|
||||
if (isset($response['parameters']['retry_after'])) {
|
||||
$retryAfter = $response['parameters']['retry_after'];
|
||||
custom_log("${logPrefix} Rate limit alcanzado. Esperando $retryAfter segundos...");
|
||||
sleep($retryAfter);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Para otros errores, esperar un tiempo antes de reintentar (backoff exponencial)
|
||||
if ($attempt < $maxRetries) {
|
||||
$backoffTime = pow(2, $attempt); // 2, 4, 8, 16... segundos
|
||||
custom_log("${logPrefix} Reintentando en $backoffTime segundos...");
|
||||
sleep($backoffTime);
|
||||
}
|
||||
}
|
||||
|
||||
// Si llegamos aquí, se agotaron los reintentos
|
||||
custom_log("${logPrefix} Se agotaron los $maxRetries intentos. Último error: $lastError");
|
||||
return $response ?? ['ok' => false, 'description' => $lastError ?? 'Error desconocido'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifica si una URL es una imagen accesible
|
||||
*/
|
||||
private function isValidImageUrl($url)
|
||||
{
|
||||
if (empty($url)) {
|
||||
custom_log("[DEBUG] isValidImageUrl: URL vacía.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!preg_match('/^https?:\/\//', $url)) {
|
||||
$baseUrl = rtrim($_ENV['APP_URL'], '/');
|
||||
$url = $baseUrl . '/' . ltrim($url, '/');
|
||||
custom_log("[DEBUG] isValidImageUrl: URL convertida a absoluta: " . $url);
|
||||
}
|
||||
|
||||
if (!filter_var($url, FILTER_VALIDATE_URL)) {
|
||||
custom_log("[DEBUG] isValidImageUrl: URL no válida después de la conversión: " . $url);
|
||||
return false;
|
||||
}
|
||||
|
||||
$ch = curl_init();
|
||||
curl_setopt($ch, CURLOPT_URL, $url);
|
||||
curl_setopt($ch, CURLOPT_NOBODY, true);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_HEADER, true);
|
||||
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
|
||||
curl_setopt($ch, CURLOPT_MAXREDIRS, 3);
|
||||
curl_setopt($ch, CURLOPT_TIMEOUT, 5);
|
||||
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
|
||||
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
|
||||
|
||||
$headers = curl_exec($ch);
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
$contentType = curl_getinfo($ch, CURLINFO_CONTENT_TYPE);
|
||||
curl_close($ch);
|
||||
|
||||
custom_log("[DEBUG] isValidImageUrl: Verificación con cURL para " . $url . " - Código HTTP: " . $httpCode . " - Content-Type: " . $contentType);
|
||||
|
||||
if ($httpCode != 200) {
|
||||
custom_log("[DEBUG] isValidImageUrl: Código de estado HTTP no es 200.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($contentType === null) {
|
||||
custom_log("[DEBUG] isValidImageUrl: Content-Type es nulo.");
|
||||
return false;
|
||||
}
|
||||
|
||||
$isImage = (bool)preg_match('/^image\/(jpe?g|png|gif|webp|bmp|svg\+?xml)/i', $contentType);
|
||||
custom_log("[DEBUG] isValidImageUrl: Es imagen (" . ($isImage ? 'Sí' : 'No') . ").");
|
||||
|
||||
return $isImage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Realiza una petición a la API de Telegram con manejo de reintentos y rate limiting
|
||||
*
|
||||
* @param string $method Método de la API de Telegram
|
||||
* @param array $params Parámetros de la petición
|
||||
* @param int $retryCount Número de reintentos actual (para uso interno)
|
||||
* @param int $maxRetries Número máximo de reintentos
|
||||
* @return array Respuesta de la API de Telegram
|
||||
*/
|
||||
private function apiRequest($method, $params, $retryCount = 0, $maxRetries = 3)
|
||||
{
|
||||
$url = $this->apiUrl . $this->botToken . '/' . $method;
|
||||
custom_log("[TELEGRAM API REQUEST] Method: {$method} | Params: " . json_encode($params));
|
||||
|
||||
// Inicializar cURL
|
||||
$ch = curl_init($url);
|
||||
|
||||
// Configurar opciones de cURL
|
||||
curl_setopt_array($ch, [
|
||||
CURLOPT_POST => 1,
|
||||
CURLOPT_POSTFIELDS => http_build_query($params),
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_SSL_VERIFYPEER => false,
|
||||
CURLOPT_CONNECTTIMEOUT => 10, // 10 segundos para conectar
|
||||
CURLOPT_TIMEOUT => 30, // 30 segundos para la ejecución total
|
||||
CURLOPT_HTTPHEADER => ['Content-Type: application/x-www-form-urlencoded'],
|
||||
]);
|
||||
|
||||
// Ejecutar la petición
|
||||
$response_body = curl_exec($ch);
|
||||
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
$error = curl_error($ch);
|
||||
curl_close($ch);
|
||||
|
||||
// Manejar errores de cURL
|
||||
if ($response_body === false) {
|
||||
custom_log("[TELEGRAM API ERROR] cURL Error: " . $error);
|
||||
|
||||
// Si hay un error de conexión, reintentar con backoff exponencial
|
||||
if ($retryCount < $maxRetries) {
|
||||
$backoffTime = pow(2, $retryCount); // Backoff exponencial (2, 4, 8 segundos...)
|
||||
custom_log("Reintentando en {" . $backoffTime . "} segundos... (Intento " . ($retryCount + 1) . " de $maxRetries)");
|
||||
sleep($backoffTime);
|
||||
return $this->apiRequest($method, $params, $retryCount + 1, $maxRetries);
|
||||
}
|
||||
|
||||
return ['ok' => false, 'description' => 'Error de conexión: ' . $error];
|
||||
}
|
||||
|
||||
// Decodificar la respuesta
|
||||
$response_array = json_decode($response_body, true);
|
||||
|
||||
// Si hay un error en la decodificación JSON
|
||||
if (json_last_error() !== JSON_ERROR_NONE) {
|
||||
custom_log("[TELEGRAM API ERROR] Error al decodificar JSON: " . json_last_error_msg());
|
||||
return ['ok' => false, 'description' => 'Error al decodificar la respuesta JSON'];
|
||||
}
|
||||
|
||||
// Registrar la respuesta
|
||||
custom_log("[TELEGRAM API RESPONSE] HTTP Code: $http_code | Body: " . $response_body);
|
||||
|
||||
// Manejar errores de rate limiting (código 429)
|
||||
if ($http_code === 429) {
|
||||
$retry_after = $response_array['parameters']['retry_after'] ?? 5; // Por defecto 5 segundos
|
||||
|
||||
if ($retryCount < $maxRetries) {
|
||||
custom_log("Rate limit alcanzado. Reintentando en {" . $retry_after . "} segundos... (Intento " . ($retryCount + 1) . " de $maxRetries)");
|
||||
sleep($retry_after);
|
||||
return $this->apiRequest($method, $params, $retryCount + 1, $maxRetries);
|
||||
}
|
||||
|
||||
custom_log("Se alcanzó el número máximo de reintentos para el método $method");
|
||||
}
|
||||
|
||||
// Manejar otros códigos de error HTTP
|
||||
if ($http_code < 200 || $http_code >= 400) {
|
||||
$error_msg = $response_array['description'] ?? 'Error desconocido';
|
||||
custom_log("[TELEGRAM API ERROR] HTTP $http_code: $error_msg");
|
||||
|
||||
// Si es un error 5xx, reintentar con backoff exponencial
|
||||
if ($http_code >= 500 && $retryCount < $maxRetries) {
|
||||
$backoffTime = pow(2, $retryCount);
|
||||
custom_log("Error del servidor. Reintentando en {" . $backoffTime . "} segundos... (Intento " . ($retryCount + 1) . " de $maxRetries)");
|
||||
sleep($backoffTime);
|
||||
return $this->apiRequest($method, $params, $retryCount + 1, $maxRetries);
|
||||
}
|
||||
|
||||
return ['ok' => false, 'description' => "Error HTTP $http_code: $error_msg"];
|
||||
}
|
||||
|
||||
return $response_array;
|
||||
}
|
||||
}
|
||||
161
telegram/actions/send_telegram_reply.php
Executable file
161
telegram/actions/send_telegram_reply.php
Executable file
@@ -0,0 +1,161 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/includes/session_check.php';
|
||||
require_once __DIR__ . '/includes/db.php';
|
||||
require_once __DIR__ . '/src/TelegramSender.php';
|
||||
require_once __DIR__ . '/src/HtmlToTelegramHtmlConverter.php';
|
||||
require_once __DIR__ . '/config/config.php';
|
||||
|
||||
header('Content-Type: application/json');
|
||||
|
||||
// Solo para administradores
|
||||
if ($_SESSION['role'] !== 'admin') {
|
||||
echo json_encode(['success' => false, 'error' => 'Acceso denegado']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$chatId = $_POST['chat_id'] ?? null;
|
||||
$message = trim($_POST['message'] ?? '');
|
||||
|
||||
if (!$chatId || !$message) {
|
||||
echo json_encode(['success' => false, 'error' => 'Faltan parámetros']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$botToken = $_ENV['TELEGRAM_BOT_TOKEN'] ?? '';
|
||||
if (empty($botToken)) {
|
||||
echo json_encode(['success' => false, 'error' => 'Token de Telegram no configurado']);
|
||||
exit;
|
||||
}
|
||||
|
||||
try {
|
||||
$telegram = new TelegramSender($botToken, $pdo);
|
||||
$converter = new HtmlToTelegramHtmlConverter();
|
||||
|
||||
// Verificar si el mensaje es un comando (comienza con #)
|
||||
if (strpos($message, '#') === 0) {
|
||||
// Extraer el comando (eliminando el # inicial y cualquier espacio en blanco)
|
||||
$command = trim(substr($message, 1));
|
||||
|
||||
// Depuración: Mostrar el comando que se está buscando
|
||||
error_log("Buscando comando: " . $command);
|
||||
|
||||
// Buscar el comando en la base de datos (insensible a mayúsculas/minúsculas)
|
||||
$sql = "SELECT id, telegram_command, message_content FROM recurrent_messages WHERE LOWER(telegram_command) = LOWER(?)";
|
||||
error_log("SQL: " . $sql . " - Parámetro: " . $command);
|
||||
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute([$command]);
|
||||
$template = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
// Depuración: Mostrar si se encontró el comando
|
||||
if ($template) {
|
||||
error_log("Comando encontrado - ID: " . $template['id'] . ", Comando: " . $template['telegram_command']);
|
||||
|
||||
// Usar el convertidor HTML para manejar correctamente el formato
|
||||
$convertedContent = $converter->convert($template['message_content']);
|
||||
|
||||
// Usar el método sendMessage de TelegramSender que ya maneja la división de mensajes e imágenes
|
||||
error_log("Enviando mensaje con comando: " . $template['telegram_command']);
|
||||
$messageIds = $telegram->sendMessage($chatId, $convertedContent, ['parse_mode' => 'HTML']);
|
||||
|
||||
if ($messageIds === false) {
|
||||
$error = "Error al enviar el mensaje a través de la API de Telegram";
|
||||
error_log($error);
|
||||
throw new Exception($error);
|
||||
}
|
||||
|
||||
// Verificar si hubo errores en el envío de las partes
|
||||
$failedParts = array_filter($messageIds, function($part) {
|
||||
return isset($part['success']) && $part['success'] === false;
|
||||
});
|
||||
|
||||
if (!empty($failedParts)) {
|
||||
$error = "Algunas partes del mensaje no se pudieron enviar. Detalles: " . json_encode($failedParts);
|
||||
error_log($error);
|
||||
throw new Exception($error);
|
||||
}
|
||||
|
||||
// Guardar tanto el comando como la respuesta en la base de datos
|
||||
$stmt = $pdo->prepare(
|
||||
"INSERT INTO telegram_interactions (chat_id, message_text, direction) VALUES (?, ?, 'out')"
|
||||
);
|
||||
|
||||
// Guardar el comando original
|
||||
$stmt->execute([$chatId, $message]);
|
||||
|
||||
// Guardar la respuesta automática (solo la primera parte para evitar llenar la base de datos)
|
||||
$firstPart = mb_substr($template['message_content'], 0, 1000) . (mb_strlen($template['message_content']) > 1000 ? '...' : '');
|
||||
$stmt->execute([$chatId, $firstPart]);
|
||||
|
||||
echo json_encode(['success' => true, 'is_command' => true]);
|
||||
exit;
|
||||
} else {
|
||||
error_log("Comando NO encontrado en la base de datos: " . $command);
|
||||
// Depuración adicional: Mostrar todos los comandos disponibles
|
||||
$allCommands = $pdo->query("SELECT id, telegram_command FROM recurrent_messages WHERE telegram_command IS NOT NULL")->fetchAll(PDO::FETCH_ASSOC);
|
||||
error_log("Comandos disponibles: " . print_r($allCommands, true));
|
||||
}
|
||||
}
|
||||
|
||||
// Si no es un comando o no se encontró el comando, enviar el mensaje normalmente
|
||||
error_log("Enviando mensaje normal");
|
||||
$messageIds = $telegram->sendMessage($chatId, $message, [], true);
|
||||
|
||||
if ($messageIds === false) {
|
||||
$error = "Error al enviar el mensaje a través de la API de Telegram";
|
||||
error_log($error);
|
||||
throw new Exception($error);
|
||||
}
|
||||
|
||||
// Verificar si hubo errores en el envío de las partes
|
||||
$failedParts = array_filter($messageIds, function($part) {
|
||||
return isset($part['success']) && $part['success'] === false;
|
||||
});
|
||||
|
||||
if (!empty($failedParts)) {
|
||||
$error = "Algunas partes del mensaje no se pudieron enviar. Detalles: " . json_encode($failedParts);
|
||||
error_log($error);
|
||||
throw new Exception($error);
|
||||
}
|
||||
|
||||
// Guardar el mensaje saliente en la base de datos
|
||||
$stmt = $pdo->prepare(
|
||||
"INSERT INTO telegram_interactions (chat_id, message_text, direction) VALUES (?, ?, 'out')"
|
||||
);
|
||||
$stmt->execute([$chatId, $message]);
|
||||
|
||||
echo json_encode(['success' => true, 'is_command' => false]);
|
||||
|
||||
} catch (Exception $e) {
|
||||
$errorMsg = 'Error en send_telegram_reply: ' . $e->getMessage();
|
||||
error_log($errorMsg);
|
||||
|
||||
// Intentar enviar un mensaje de error al chat si es posible
|
||||
try {
|
||||
if (isset($telegram) && $chatId) {
|
||||
$errorDetails = "⚠️ Error al procesar tu solicitud. Por favor, inténtalo de nuevo más tarde.";
|
||||
$telegram->sendMessage($chatId, $errorDetails);
|
||||
|
||||
// También registrar el error en la base de datos
|
||||
if (isset($pdo)) {
|
||||
$stmt = $pdo->prepare(
|
||||
"INSERT INTO telegram_interactions (chat_id, message_text, direction, error) VALUES (?, ?, 'out', 1)"
|
||||
);
|
||||
$stmt->execute([$chatId, "[ERROR] " . $e->getMessage()]);
|
||||
}
|
||||
}
|
||||
} catch (Exception $innerE) {
|
||||
error_log("Error al notificar al usuario: " . $innerE->getMessage());
|
||||
}
|
||||
|
||||
// Devolver el error al cliente
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'error' => $e->getMessage(),
|
||||
'debug' => [
|
||||
'file' => $e->getFile(),
|
||||
'line' => $e->getLine(),
|
||||
'trace' => $e->getTraceAsString()
|
||||
]
|
||||
]);
|
||||
}
|
||||
75
telegram/actions/telegram_actions.php
Executable file
75
telegram/actions/telegram_actions.php
Executable file
@@ -0,0 +1,75 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../../includes/session_check.php';
|
||||
require_once __DIR__ . '/../../includes/db.php';
|
||||
require_once __DIR__ . '/../TelegramSender.php';
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST' || !isset($_POST['action'])) {
|
||||
header('Location: ../sent_messages.php');
|
||||
exit();
|
||||
}
|
||||
|
||||
$action = $_POST['action'];
|
||||
|
||||
if ($action === 'delete_message') {
|
||||
if (!isset($_POST['sent_message_id'], $_POST['platform_message_id'], $_POST['chat_id'])) {
|
||||
header('Location: ../sent_messages.php?error=missing_data');
|
||||
exit();
|
||||
}
|
||||
|
||||
$sentMessageId = $_POST['sent_message_id'];
|
||||
$telegramMessageIdsJson = $_POST['platform_message_id'];
|
||||
$chatId = $_POST['chat_id'];
|
||||
|
||||
$telegramMessageIds = json_decode($telegramMessageIdsJson, true);
|
||||
|
||||
// If decoding fails or the result is not an array, treat it as a single ID
|
||||
if (json_last_error() !== JSON_ERROR_NONE || !is_array($telegramMessageIds)) {
|
||||
$telegramMessageIds = [$telegramMessageIdsJson];
|
||||
}
|
||||
|
||||
$telegramSender = new TelegramSender(TELEGRAM_BOT_TOKEN);
|
||||
$allDeleted = true;
|
||||
$error_messages = [];
|
||||
|
||||
try {
|
||||
foreach ($telegramMessageIds as $messageId) {
|
||||
// Skip if the ID is empty or invalid
|
||||
if (empty($messageId) || !is_numeric($messageId)) {
|
||||
error_log("Skipping invalid Telegram message ID: " . var_export($messageId, true));
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
$telegramSender->deleteMessage($chatId, $messageId);
|
||||
usleep(300000); // 300ms pause to avoid rate limits
|
||||
} catch (Exception $e) {
|
||||
$allDeleted = false;
|
||||
$error_messages[] = "Failed to delete message ID {$messageId}: " . $e->getMessage();
|
||||
error_log(end($error_messages)); // Log the last error
|
||||
}
|
||||
}
|
||||
|
||||
if ($allDeleted) {
|
||||
// If all messages were deleted successfully, remove the record from DB
|
||||
$stmt = $pdo->prepare("DELETE FROM sent_messages WHERE id = ?");
|
||||
$stmt->execute([$sentMessageId]);
|
||||
header('Location: ../sent_messages.php?success=deleted&platform=Telegram');
|
||||
} else {
|
||||
// If some deletions failed, redirect with an error message
|
||||
$errorMessage = implode('; ', $error_messages);
|
||||
header('Location: ../sent_messages.php?error=delete_failed&platform=Telegram&message=' . urlencode($errorMessage));
|
||||
}
|
||||
exit();
|
||||
|
||||
} catch (Exception $e) {
|
||||
// Catch any other unexpected errors during the process
|
||||
$errorMessage = "An unexpected error occurred: " . $e->getMessage();
|
||||
error_log($errorMessage);
|
||||
header('Location: ../sent_messages.php?error=unexpected_error&platform=Telegram&message=' . urlencode($errorMessage));
|
||||
exit();
|
||||
}
|
||||
} // Closing brace for "if ($action === 'delete_message')"
|
||||
|
||||
// Fallback redirect if action is not 'delete_message'
|
||||
header('Location: ../sent_messages.php');
|
||||
exit();
|
||||
621
telegram/admin/chat_telegram.php
Executable file
621
telegram/admin/chat_telegram.php
Executable file
@@ -0,0 +1,621 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../../includes/session_check.php';
|
||||
require_once __DIR__ . '/../../includes/db.php';
|
||||
|
||||
// Solo para administradores
|
||||
if ($_SESSION['role'] !== 'admin') {
|
||||
header('HTTP/1.0 403 Forbidden');
|
||||
die('Acceso denegado.');
|
||||
}
|
||||
|
||||
// Obtener todos los usuarios de Telegram que han interactuado o son miembros de grupos
|
||||
$stmt = $pdo->query(
|
||||
"SELECT DISTINCT r.platform_id, r.name
|
||||
FROM recipients r
|
||||
WHERE r.platform = 'telegram' AND r.type = 'user'
|
||||
ORDER BY r.name ASC"
|
||||
);
|
||||
$users = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
require_once __DIR__ . '/../../templates/header.php';
|
||||
?>
|
||||
|
||||
<style>
|
||||
.chat-container {
|
||||
display: flex;
|
||||
height: calc(100vh - 200px); /* Ajustar altura */
|
||||
}
|
||||
|
||||
/* Estilos para el selector de emojis */
|
||||
.emoji-picker {
|
||||
position: fixed;
|
||||
width: 250px;
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
background: white;
|
||||
border: 1px solid #dee2e6;
|
||||
border-radius: 8px;
|
||||
padding: 10px;
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
|
||||
z-index: 9999;
|
||||
display: none;
|
||||
flex-wrap: wrap;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.emoji-option {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
font-size: 1.5em;
|
||||
cursor: pointer;
|
||||
border-radius: 4px;
|
||||
transition: all 0.2s;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.emoji-option:hover {
|
||||
background-color: #f8f9fa;
|
||||
transform: scale(1.2);
|
||||
}
|
||||
|
||||
.emoji-option:active {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
|
||||
.input-group-prepend .btn {
|
||||
border-top-right-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
}
|
||||
|
||||
.input-group-append .btn {
|
||||
border-top-left-radius: 0;
|
||||
border-bottom-left-radius: 0;
|
||||
}
|
||||
|
||||
#message-text {
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.message-text {
|
||||
white-space: pre-wrap;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
.user-list {
|
||||
border-right: 1px solid #dee2e6;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.chat-history {
|
||||
flex-grow: 1;
|
||||
overflow-y: auto;
|
||||
padding: 1rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.message-form {
|
||||
padding: 1rem;
|
||||
border-top: 1px solid #dee2e6;
|
||||
}
|
||||
.message {
|
||||
max-width: 70%;
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: 1rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
.message.in {
|
||||
background-color: #6495ED; /* Azul claro (CornflowerBlue) */
|
||||
color: white;
|
||||
align-self: flex-start;
|
||||
}
|
||||
|
||||
.message.out {
|
||||
background-color: #6495ED; /* Azul claro (CornflowerBlue) */
|
||||
color: white;
|
||||
align-self: flex-end;
|
||||
}
|
||||
|
||||
.user-list .list-group-item.active {
|
||||
background-color: #0d6efd;
|
||||
border-color: #0d6efd;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="container-fluid">
|
||||
<h1 class="mt-4">Chat de Soporte de Telegram</h1>
|
||||
|
||||
<div class="card shadow-sm">
|
||||
<div class="card-body p-0">
|
||||
<div class="chat-container">
|
||||
<!-- Columna de Usuarios -->
|
||||
<div class="col-md-4 col-lg-3 user-list">
|
||||
<div class="list-group list-group-flush">
|
||||
<?php foreach ($users as $user): ?>
|
||||
<a href="#" class="list-group-item list-group-item-action" data-chat-id="<?= $user['platform_id'] ?>">
|
||||
<?= htmlspecialchars($user['name']) ?>
|
||||
</a>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Columna de Chat -->
|
||||
<div class="col-md-8 col-lg-9 d-flex flex-column">
|
||||
<div id="chat-history" class="chat-history">
|
||||
<div class="text-center text-muted my-auto">
|
||||
<i class="bi bi-arrow-left-circle-fill fs-1"></i>
|
||||
<p>Selecciona un usuario para ver la conversación.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div id="message-form-container" class="message-form" style="display: none;">
|
||||
<form id="message-form">
|
||||
<input type="hidden" id="chat-id-input" name="chat_id">
|
||||
<div class="input-group" style="position: relative;">
|
||||
<div class="input-group-prepend">
|
||||
<button type="button" id="emoji-trigger" class="btn btn-outline-secondary" onclick="toggleEmojiPicker()">
|
||||
<i class="bi bi-emoji-smile"></i>
|
||||
</button>
|
||||
</div>
|
||||
<input type="text" id="message-text" name="message" class="form-control" placeholder="Escribe tu respuesta..." required>
|
||||
<div class="input-group-append">
|
||||
<button type="submit" class="btn btn-primary">Enviar</button>
|
||||
</div>
|
||||
|
||||
<!-- Selector de emojis simplificado -->
|
||||
<div id="emoji-picker" style="
|
||||
display: none;
|
||||
position: absolute;
|
||||
bottom: 100%;
|
||||
left: 0;
|
||||
background: white;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 8px;
|
||||
padding: 10px;
|
||||
width: 250px;
|
||||
max-height: 200px;
|
||||
overflow-y: auto;
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
|
||||
z-index: 1000;">
|
||||
<div style="display: flex; flex-wrap: wrap; gap: 5px;">
|
||||
<span class="emoji-option" onclick="insertEmoji('😊')" style="cursor: pointer; font-size: 24px;">😊</span>
|
||||
<span class="emoji-option" onclick="insertEmoji('😂')" style="cursor: pointer; font-size: 24px;">😂</span>
|
||||
<span class="emoji-option" onclick="insertEmoji('😍')" style="cursor: pointer; font-size: 24px;">😍</span>
|
||||
<span class="emoji-option" onclick="insertEmoji('😎')" style="cursor: pointer; font-size: 24px;">😎</span>
|
||||
<span class="emoji-option" onclick="insertEmoji('😢')" style="cursor: pointer; font-size: 24px;">😢</span>
|
||||
<span class="emoji-option" onclick="insertEmoji('😡')" style="cursor: pointer; font-size: 24px;">😡</span>
|
||||
<span class="emoji-option" onclick="insertEmoji('😴')" style="cursor: pointer; font-size: 24px;">😴</span>
|
||||
<span class="emoji-option" onclick="insertEmoji('😷')" style="cursor: pointer; font-size: 24px;">😷</span>
|
||||
<span class="emoji-option" onclick="insertEmoji('🤔')" style="cursor: pointer; font-size: 24px;">🤔</span>
|
||||
<span class="emoji-option" onclick="insertEmoji('🤗')" style="cursor: pointer; font-size: 24px;">🤗</span>
|
||||
<span class="emoji-option" onclick="insertEmoji('👍')" style="cursor: pointer; font-size: 24px;">👍</span>
|
||||
<span class="emoji-option" onclick="insertEmoji('👎')" style="cursor: pointer; font-size: 24px;">👎</span>
|
||||
<span class="emoji-option" onclick="insertEmoji('👏')" style="cursor: pointer; font-size: 24px;">👏</span>
|
||||
<span class="emoji-option" onclick="insertEmoji('🙌')" style="cursor: pointer; font-size: 24px;">🙌</span>
|
||||
<span class="emoji-option" onclick="insertEmoji('🤝')" style="cursor: pointer; font-size: 24px;">🤝</span>
|
||||
<span class="emoji-option" onclick="insertEmoji('📱')" style="cursor: pointer; font-size: 24px;">📱</span>
|
||||
<span class="emoji-option" onclick="insertEmoji('💻')" style="cursor: pointer; font-size: 24px;">💻</span>
|
||||
<span class="emoji-option" onclick="insertEmoji('📷')" style="cursor: pointer; font-size: 24px;">📷</span>
|
||||
<span class="emoji-option" onclick="insertEmoji('🎮')" style="cursor: pointer; font-size: 24px;">🎮</span>
|
||||
<span class="emoji-option" onclick="insertEmoji('📚')" style="cursor: pointer; font-size: 24px;">📚</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Función para mostrar/ocultar el selector de emojis
|
||||
function toggleEmojiPicker() {
|
||||
const picker = document.getElementById('emoji-picker');
|
||||
if (picker.style.display === 'none' || !picker.style.display) {
|
||||
picker.style.display = 'block';
|
||||
} else {
|
||||
picker.style.display = 'none';
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Función para insertar un emoji en el campo de texto
|
||||
function insertEmoji(emoji) {
|
||||
const input = document.getElementById('message-text');
|
||||
const start = input.selectionStart;
|
||||
const end = input.selectionEnd;
|
||||
const text = input.value;
|
||||
input.value = text.substring(0, start) + emoji + text.substring(end);
|
||||
input.focus();
|
||||
input.setSelectionRange(start + emoji.length, start + emoji.length);
|
||||
document.getElementById('emoji-picker').style.display = 'none';
|
||||
}
|
||||
|
||||
// Cerrar el selector al hacer clic fuera de él
|
||||
document.addEventListener('click', function(e) {
|
||||
const picker = document.getElementById('emoji-picker');
|
||||
const trigger = document.getElementById('emoji-trigger');
|
||||
if (picker && trigger &&
|
||||
e.target !== picker && !picker.contains(e.target) &&
|
||||
e.target !== trigger && !trigger.contains(e.target)) {
|
||||
picker.style.display = 'none';
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php require_once __DIR__ . '/../../templates/footer.php'; ?>
|
||||
|
||||
<script>
|
||||
// Función para insertar emojis en el campo de texto
|
||||
function insertEmoji(emoji) {
|
||||
const input = document.getElementById('message-text');
|
||||
const start = input.selectionStart;
|
||||
const end = input.selectionEnd;
|
||||
const text = input.value;
|
||||
|
||||
// Insertar el emoji en la posición del cursor
|
||||
input.value = text.substring(0, start) + emoji + text.substring(end);
|
||||
|
||||
// Mover el cursor después del emoji insertado
|
||||
const newPos = start + emoji.length;
|
||||
input.selectionStart = input.selectionEnd = newPos;
|
||||
|
||||
// Enfocar el campo de texto
|
||||
input.focus();
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const userLinks = document.querySelectorAll('.user-list .list-group-item');
|
||||
const chatHistory = document.getElementById('chat-history');
|
||||
const formContainer = document.getElementById('message-form-container');
|
||||
const messageForm = document.getElementById('message-form');
|
||||
const chatIdInput = document.getElementById('chat-id-input');
|
||||
const messageTextInput = document.getElementById('message-text');
|
||||
|
||||
let activeChatId = null;
|
||||
let lastMessageId = 0;
|
||||
let refreshInterval = null;
|
||||
|
||||
userLinks.forEach(link => {
|
||||
link.addEventListener('click', function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
// Marcar usuario activo
|
||||
userLinks.forEach(l => l.classList.remove('active'));
|
||||
this.classList.add('active');
|
||||
|
||||
// Detener el intervalo anterior si existe
|
||||
if (refreshInterval) {
|
||||
clearInterval(refreshInterval);
|
||||
}
|
||||
|
||||
// Establecer el nuevo chat activo
|
||||
activeChatId = this.getAttribute('data-chat-id');
|
||||
chatIdInput.value = activeChatId;
|
||||
lastMessageId = 0; // Reiniciar el ID del último mensaje
|
||||
|
||||
// Cargar el historial y comenzar a actualizar
|
||||
loadChatHistory(activeChatId);
|
||||
|
||||
// Iniciar la actualización automática con un intervalo dinámico
|
||||
let refreshDelay = 500; // Comenzar con 0.5 segundos
|
||||
|
||||
function startRefreshTimer() {
|
||||
if (refreshInterval) clearInterval(refreshInterval);
|
||||
|
||||
refreshInterval = setInterval(() => {
|
||||
if (activeChatId && !document.hidden) {
|
||||
checkForNewMessages(activeChatId);
|
||||
}
|
||||
}, refreshDelay);
|
||||
}
|
||||
|
||||
// Iniciar el temporizador
|
||||
startRefreshTimer();
|
||||
});
|
||||
});
|
||||
|
||||
function loadChatHistory(chatId, onlyNew = false) {
|
||||
// Usar un timestamp para evitar caché del navegador
|
||||
const timestamp = new Date().getTime();
|
||||
const url = `get_chat_history.php?chat_id=${chatId}${onlyNew ? '&last_id=' + lastMessageId : ''}&_=${timestamp}`;
|
||||
|
||||
if (!onlyNew) {
|
||||
chatHistory.innerHTML = '<div class="text-center text-muted my-auto"><div class="spinner-border" role="status"><span class="visually-hidden">Cargando...</span></div></div>';
|
||||
formContainer.style.display = 'block';
|
||||
}
|
||||
|
||||
fetch(url)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
console.log('Datos recibidos:', data); // Depuración
|
||||
|
||||
if (onlyNew && (!data.history || data.history.length === 0)) {
|
||||
return; // No hay mensajes nuevos
|
||||
}
|
||||
|
||||
if (!onlyNew) {
|
||||
chatHistory.innerHTML = '';
|
||||
}
|
||||
|
||||
let hasNewMessages = false;
|
||||
|
||||
if (data.success && data.history && data.history.length > 0) {
|
||||
// Ordenar los mensajes por ID para asegurar el orden correcto
|
||||
data.history.sort((a, b) => a.id - b.id);
|
||||
|
||||
// Ordenar los mensajes por ID para asegurar el orden correcto
|
||||
data.history.sort((a, b) => a.id - b);
|
||||
|
||||
data.history.forEach(msg => {
|
||||
// Solo agregar mensajes más recientes que el último mostrado
|
||||
if (msg.id > lastMessageId || !onlyNew) {
|
||||
const messageDiv = document.createElement('div');
|
||||
messageDiv.classList.add('message', msg.direction);
|
||||
// Usar innerHTML para permitir que el enlace de traducción se añada correctamente
|
||||
messageDiv.innerHTML = `<div class="message-text">${msg.message_text.replace(/\n/g, '<br>')}</div>`;
|
||||
|
||||
// Add translate link if needed
|
||||
// For testing, always show for incoming messages
|
||||
if (msg.direction === 'in') { // msg.language_code !== 'es'
|
||||
const translateLink = document.createElement('a');
|
||||
translateLink.href = '#';
|
||||
translateLink.textContent = ' (Traducir)';
|
||||
translateLink.classList.add('translate-link');
|
||||
translateLink.setAttribute('data-message-id', msg.id);
|
||||
messageDiv.appendChild(translateLink);
|
||||
}
|
||||
|
||||
// Agregar el mensaje al historial
|
||||
chatHistory.appendChild(messageDiv);
|
||||
|
||||
// Actualizar el ID del último mensaje
|
||||
if (msg.id > lastMessageId) {
|
||||
lastMessageId = msg.id;
|
||||
}
|
||||
|
||||
hasNewMessages = true;
|
||||
}
|
||||
});
|
||||
|
||||
// Desplazarse al final solo si hay mensajes nuevos
|
||||
if (hasNewMessages) {
|
||||
chatHistory.scrollTop = chatHistory.scrollHeight;
|
||||
}
|
||||
} else if (!onlyNew) {
|
||||
chatHistory.innerHTML = '<div class="text-center text-muted my-auto">No hay mensajes en esta conversación.</div>';
|
||||
}
|
||||
|
||||
// Si no es una actualización, desplazarse al final
|
||||
if (!onlyNew) {
|
||||
chatHistory.scrollTop = chatHistory.scrollHeight;
|
||||
}
|
||||
|
||||
// Actualizar lastMessageId si viene en la respuesta
|
||||
if (data.last_id && data.last_id > lastMessageId) {
|
||||
lastMessageId = data.last_id;
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
if (!onlyNew) {
|
||||
console.error('Error al cargar el historial:', error);
|
||||
chatHistory.innerHTML = '<div class="text-center text-danger my-auto">Error al cargar el historial. Intenta recargar la página.</div>';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Función para verificar mensajes nuevos
|
||||
function checkForNewMessages(chatId) {
|
||||
if (!document.hidden && chatId) {
|
||||
// Usar fetch con headers para evitar caché
|
||||
fetch(`get_chat_history.php?chat_id=${chatId}&last_id=${lastMessageId}&_=${new Date().getTime()}`, {
|
||||
headers: {
|
||||
'Cache-Control': 'no-cache, no-store, must-revalidate',
|
||||
'Pragma': 'no-cache',
|
||||
'Expires': '0'
|
||||
}
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
console.log('Datos recibidos en checkForNewMessages:', data); // Depuración
|
||||
|
||||
if (data.success && data.history && data.history.length > 0) {
|
||||
let hasNewMessages = false;
|
||||
|
||||
// Ordenar los mensajes por ID para asegurar el orden correcto
|
||||
data.history.sort((a, b) => a.id - b);
|
||||
|
||||
data.history.forEach(msg => {
|
||||
if (msg.id > lastMessageId) {
|
||||
const messageDiv = document.createElement('div');
|
||||
messageDiv.classList.add('message', msg.direction);
|
||||
messageDiv.textContent = msg.message_text;
|
||||
|
||||
chatHistory.appendChild(messageDiv);
|
||||
|
||||
if (msg.id > lastMessageId) {
|
||||
lastMessageId = msg.id;
|
||||
}
|
||||
|
||||
hasNewMessages = true;
|
||||
}
|
||||
});
|
||||
|
||||
if (hasNewMessages) {
|
||||
chatHistory.scrollTop = chatHistory.scrollHeight;
|
||||
// Si hay mensajes nuevos, reiniciar el temporizador con el intervalo más corto
|
||||
refreshDelay = 2000;
|
||||
startRefreshTimer();
|
||||
}
|
||||
}
|
||||
|
||||
if (data.last_id && data.last_id > lastMessageId) {
|
||||
lastMessageId = data.last_id;
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error al verificar mensajes nuevos:', error);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Actualizar el chat cuando la pestaña vuelve a estar activa
|
||||
document.addEventListener('visibilitychange', function() {
|
||||
if (!document.hidden && activeChatId) {
|
||||
loadChatHistory(activeChatId);
|
||||
}
|
||||
|
||||
// Inicializar el selector de emojis
|
||||
console.log('Inicializando selector de emojis...');
|
||||
let emojiPickerVisible = false;
|
||||
const emojiTrigger = document.getElementById('emoji-trigger');
|
||||
const emojiPicker = document.getElementById('emoji-picker');
|
||||
const messageTextInput = document.getElementById('message-text');
|
||||
|
||||
console.log('Elementos del selector de emojis:', {
|
||||
emojiTrigger: emojiTrigger ? 'Encontrado' : 'No encontrado',
|
||||
emojiPicker: emojiPicker ? 'Encontrado' : 'No encontrado',
|
||||
messageTextInput: messageTextInput ? 'Encontrado' : 'No encontrado'
|
||||
});
|
||||
|
||||
// Asegurarse de que el selector esté oculto inicialmente
|
||||
if (emojiPicker) {
|
||||
emojiPicker.style.display = 'none';
|
||||
emojiPicker.style.backgroundColor = '#fff';
|
||||
emojiPicker.style.border = '2px solid red';
|
||||
emojiPicker.innerHTML = '<div style="padding: 10px;">Selector de emojis</div>' + emojiPicker.innerHTML;
|
||||
}
|
||||
|
||||
// Mostrar/ocultar selector de emojis
|
||||
if (emojiTrigger) {
|
||||
emojiTrigger.addEventListener('click', function(e) {
|
||||
console.log('Botón de emoji clickeado');
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
if (!emojiPicker) {
|
||||
console.error('El selector de emojis no se encontró en el DOM');
|
||||
return false;
|
||||
}
|
||||
|
||||
if (emojiPickerVisible) {
|
||||
console.log('Ocultando selector de emojis');
|
||||
emojiPicker.style.display = 'none';
|
||||
} else {
|
||||
console.log('Mostrando selector de emojis');
|
||||
emojiPicker.style.display = 'block';
|
||||
// Posicionar el selector debajo del botón
|
||||
const rect = emojiTrigger.getBoundingClientRect();
|
||||
emojiPicker.style.position = 'fixed';
|
||||
emojiPicker.style.top = (rect.bottom + window.scrollY) + 'px';
|
||||
emojiPicker.style.left = (rect.left + window.scrollX) + 'px';
|
||||
emojiPicker.style.zIndex = '9999';
|
||||
console.log('Posición del selector:', emojiPicker.style.top, emojiPicker.style.left);
|
||||
}
|
||||
|
||||
emojiPickerVisible = !emojiPickerVisible;
|
||||
return false;
|
||||
});
|
||||
} else {
|
||||
console.error('No se pudo encontrar el botón de emoji');
|
||||
}
|
||||
|
||||
// Cerrar el selector al hacer clic fuera
|
||||
document.addEventListener('click', function(e) {
|
||||
if (!emojiPicker || !emojiTrigger) return;
|
||||
|
||||
const isClickInside = emojiPicker.contains(e.target) || emojiTrigger.contains(e.target);
|
||||
|
||||
if (emojiPickerVisible && !isClickInside) {
|
||||
console.log('Clic fuera del selector, ocultando...');
|
||||
emojiPicker.style.display = 'none';
|
||||
emojiPickerVisible = false;
|
||||
}
|
||||
});
|
||||
|
||||
// Manejar clics en emojis
|
||||
if (emojiPicker) {
|
||||
emojiPicker.addEventListener('click', function(e) {
|
||||
console.log('Clic en el selector de emojis');
|
||||
const emojiOption = e.target.closest('.emoji-option');
|
||||
if (emojiOption) {
|
||||
const emojiChar = emojiOption.getAttribute('data-emoji');
|
||||
console.log('Emoji seleccionado:', emojiChar);
|
||||
if (emojiChar) {
|
||||
insertEmoji(emojiChar);
|
||||
emojiPicker.style.display = 'none';
|
||||
emojiPickerVisible = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Evento para enviar mensaje
|
||||
messageForm.addEventListener('submit', function(e) {
|
||||
e.preventDefault();
|
||||
const messageText = messageTextInput.value.trim();
|
||||
if (!messageText) return;
|
||||
|
||||
const formData = new FormData(this);
|
||||
|
||||
// Optimistic UI update
|
||||
const messageDiv = document.createElement('div');
|
||||
messageDiv.className = `message out`;
|
||||
messageDiv.innerHTML = `
|
||||
<div class="message-text">${messageText.replace(/\n/g, '<br>')}</div>
|
||||
<div class="message-time">${new Date().toLocaleTimeString()}</div>
|
||||
`;
|
||||
chatHistory.appendChild(messageDiv);
|
||||
chatHistory.scrollTop = chatHistory.scrollHeight;
|
||||
|
||||
messageTextInput.value = '';
|
||||
|
||||
fetch('admin_send_message.php', {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (!data.success) {
|
||||
alert('Error al enviar el mensaje: ' + data.error);
|
||||
// Opcional: eliminar el mensaje de la UI si falla
|
||||
chatHistory.removeChild(messageDiv);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error:', error);
|
||||
alert('Error de red al enviar el mensaje.');
|
||||
chatHistory.removeChild(messageDiv);
|
||||
});
|
||||
});
|
||||
|
||||
// Evento para traducir mensaje
|
||||
chatHistory.addEventListener('click', function(e) {
|
||||
if (e.target.classList.contains('translate-link')) {
|
||||
e.preventDefault();
|
||||
const messageId = e.target.getAttribute('data-message-id');
|
||||
const messageDiv = e.target.parentElement;
|
||||
|
||||
fetch(`translate_message.php?message_id=${messageId}`)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
const translatedText = document.createElement('div');
|
||||
translatedText.classList.add('translated-text');
|
||||
translatedText.textContent = data.translated_text;
|
||||
messageDiv.appendChild(translatedText);
|
||||
e.target.remove(); // Remove the translate link
|
||||
} else {
|
||||
alert('Error al traducir el mensaje: ' + data.error);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error:', error);
|
||||
alert('Error de red al traducir el mensaje.');
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
338
telegram/admin/telegram_bot_interactions.php
Executable file
338
telegram/admin/telegram_bot_interactions.php
Executable file
@@ -0,0 +1,338 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../../includes/session_check.php';
|
||||
require_once __DIR__ . '/../../includes/db.php';
|
||||
|
||||
// Crear las tablas necesarias si no existen
|
||||
try {
|
||||
$pdo->exec("
|
||||
-- Tabla para registrar interacciones con el bot
|
||||
CREATE TABLE IF NOT EXISTS telegram_bot_interactions (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
user_id BIGINT NOT NULL,
|
||||
username VARCHAR(255),
|
||||
first_name VARCHAR(255),
|
||||
last_name VARCHAR(255),
|
||||
interaction_type VARCHAR(50) NOT NULL,
|
||||
interaction_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
UNIQUE KEY unique_interaction (user_id, interaction_type)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
-- Tabla para la configuración del mensaje del bot
|
||||
CREATE TABLE IF NOT EXISTS telegram_bot_messages (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
message_text TEXT NOT NULL,
|
||||
button_text VARCHAR(50) DEFAULT '¡Únete a nuestro grupo!',
|
||||
group_invite_link VARCHAR(255) DEFAULT 'https://t.me/+Hzh0G-1-BY41ZDNj',
|
||||
is_active BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
register_users BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
-- Insertar configuración por defecto si no existe
|
||||
INSERT IGNORE INTO telegram_bot_messages
|
||||
(message_text, button_text, group_invite_link, is_active, register_users)
|
||||
VALUES (
|
||||
'¡Hola {user_name}! 👋\n\nGracias por interactuar con nuestro bot. Únete a nuestro grupo principal para mantenerte actualizado con nuestras novedades.',
|
||||
'¡Únete a nuestro grupo!',
|
||||
'https://t.me/+Hzh0G-1-BY41ZDNj',
|
||||
TRUE,
|
||||
TRUE
|
||||
);
|
||||
");
|
||||
} catch (PDOException $e) {
|
||||
die("Error al crear las tablas: " . $e->getMessage());
|
||||
}
|
||||
|
||||
// Manejar el envío del formulario
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$messageText = $_POST['message_text'] ?? '';
|
||||
$buttonText = $_POST['button_text'] ?? '¡Únete a nuestro grupo!';
|
||||
$groupInviteLink = $_POST['group_invite_link'] ?? 'https://t.me/+Hzh0G-1-BY41ZDNj';
|
||||
$isActive = isset($_POST['is_active']) ? 1 : 0;
|
||||
$registerUsers = isset($_POST['register_users']) ? 1 : 0;
|
||||
|
||||
try {
|
||||
$pdo->beginTransaction();
|
||||
|
||||
// Actualizar la configuración
|
||||
$stmt = $pdo->prepare("
|
||||
UPDATE telegram_bot_messages
|
||||
SET message_text = ?,
|
||||
button_text = ?,
|
||||
group_invite_link = ?,
|
||||
is_active = ?,
|
||||
register_users = ?
|
||||
WHERE id = 1
|
||||
");
|
||||
|
||||
$stmt->execute([
|
||||
$messageText,
|
||||
$buttonText,
|
||||
$groupInviteLink,
|
||||
$isActive,
|
||||
$registerUsers
|
||||
]);
|
||||
|
||||
$pdo->commit();
|
||||
$successMessage = "¡Configuración guardada con éxito!";
|
||||
} catch (PDOException $e) {
|
||||
$pdo->rollBack();
|
||||
$errorMessage = "Error al guardar la configuración: " . $e->getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
// Obtener la configuración actual
|
||||
try {
|
||||
$stmt = $pdo->query("SELECT * FROM telegram_bot_messages WHERE id = 1");
|
||||
$config = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$config) {
|
||||
throw new Exception("No se encontró la configuración del mensaje del bot");
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
$errorMessage = "Error al cargar la configuración: " . $e->getMessage();
|
||||
$config = [
|
||||
'message_text' => '¡Hola {user_name}! 👋\n\nGracias por interactuar con nuestro bot. Únete a nuestro grupo principal para mantenerte actualizado con nuestras novedades.',
|
||||
'button_text' => '¡Únete a nuestro grupo!',
|
||||
'group_invite_link' => 'https://t.me/+Hzh0G-1-BY41ZDNj',
|
||||
'is_active' => true,
|
||||
'register_users' => true
|
||||
];
|
||||
}
|
||||
|
||||
// Obtener estadísticas de interacciones
|
||||
try {
|
||||
$statsStmt = $pdo->query("
|
||||
SELECT
|
||||
COUNT(*) as total_interactions,
|
||||
COUNT(DISTINCT user_id) as unique_users
|
||||
FROM telegram_bot_interactions
|
||||
");
|
||||
$stats = $statsStmt->fetch(PDO::FETCH_ASSOC);
|
||||
} catch (PDOException $e) {
|
||||
$errorMessage = "Error al cargar las estadísticas: " . $e->getMessage();
|
||||
$stats = [];
|
||||
}
|
||||
|
||||
$pageTitle = 'Interacciones del Bot de Telegram';
|
||||
require_once __DIR__ . '/../../templates/header.php';
|
||||
?>
|
||||
|
||||
<div class="container-fluid">
|
||||
<h1 class="mt-4" data-translate="true">Interacciones del Bot de Telegram</h1>
|
||||
<p class="text-muted" data-translate="true">Configura el mensaje que se enviará cuando los usuarios interactúen con tu bot.</p>
|
||||
|
||||
<?php if (isset($successMessage)): ?>
|
||||
<div class="alert alert-success" data-translate="true"><?= htmlspecialchars($successMessage) ?></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (isset($errorMessage)): ?>
|
||||
<div class="alert alert-danger" data-translate="true"><?= htmlspecialchars($errorMessage) ?></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-8">
|
||||
<div class="card shadow-sm mb-4">
|
||||
<div class="card-header">
|
||||
<h5 class="mb-0" data-translate="true">Configuración del Mensaje del Bot</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form method="POST">
|
||||
<div class="mb-3">
|
||||
<label for="message_text" class="form-label" data-translate="true">Mensaje de Respuesta</label>
|
||||
<textarea class="form-control" id="message_text" name="message_text" rows="5" required><?= htmlspecialchars($config['message_text'] ?? '') ?></textarea>
|
||||
<div class="form-text" data-translate="true">
|
||||
Puedes usar los siguientes placeholders que serán reemplazados automáticamente:
|
||||
<ul class="mb-0">
|
||||
<li><code>{user_name}</code> - El nombre del usuario que interactúa con el bot.</li>
|
||||
<li><code>{group_link}</code> - El enlace al grupo principal.</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-6">
|
||||
<label for="button_text" class="form-label" data-translate="true">Texto del Botón</label>
|
||||
<input type="text" class="form-control" id="button_text" name="button_text"
|
||||
value="<?= htmlspecialchars($config['button_text'] ?? '') ?>" required>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label for="group_invite_link" class="form-label" data-translate="true">Enlace de Invitación al Grupo</label>
|
||||
<input type="url" class="form-control" id="group_invite_link" name="group_invite_link"
|
||||
value="<?= htmlspecialchars($config['group_invite_link'] ?? '') ?>" required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-check form-switch mb-3">
|
||||
<input class="form-check-input" type="checkbox" id="is_active" name="is_active"
|
||||
<?= ($config['is_active'] ?? true) ? 'checked' : '' ?>>
|
||||
<label class="form-check-label" for="is_active" data-translate="true">Activar mensaje de respuesta automática</label>
|
||||
</div>
|
||||
|
||||
<div class="form-check form-switch mb-3">
|
||||
<input class="form-check-input" type="checkbox" id="register_users" name="register_users"
|
||||
<?= ($config['register_users'] ?? true) ? 'checked' : '' ?>>
|
||||
<label class="form-check-label" for="register_users" data-translate="true">Registrar usuarios que interactúan con el bot</label>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<i class="bi bi-save-fill me-1"></i> <span data-translate="true">Guardar Cambios</span>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card shadow-sm">
|
||||
<div class="card-header">
|
||||
<h5 class="mb-0" data-translate="true">Configuración de Webhook</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="mb-4">
|
||||
<h6 data-translate="true">Webhook Unificado</h6>
|
||||
<p class="small text-muted" data-translate="true">Este único webhook maneja tanto las interacciones directas con el bot como los mensajes de bienvenida a nuevos miembros en un grupo.</p>
|
||||
<div class="input-group mb-2">
|
||||
<input type="text" class="form-control" value="<?= rtrim($_ENV['APP_URL'] ?? 'https://pruebaspons.duckdns.org', '/') ?>/telegram/webhook/telegram_bot_webhook.php?auth_token=<?= urlencode($_ENV['TELEGRAM_WEBHOOK_TOKEN'] ?? '') ?>" id="unifiedWebhookUrl" readonly>
|
||||
<button class="btn btn-outline-secondary" type="button" onclick="copyToClipboard(document.getElementById('unifiedWebhookUrl'))">
|
||||
<i class="bi bi-clipboard"></i> Copiar
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-4">
|
||||
<h6 data-translate="true">Comando de Configuración con cURL</h6>
|
||||
<p class="small text-muted" data-translate="true">Usa este comando en tu terminal para configurar el webhook en Telegram. Solo necesitas hacerlo una vez.</p>
|
||||
|
||||
<div class="mb-3">
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control form-control-sm" value="curl -F 'url=<?= rtrim($_ENV['APP_URL'] ?? 'https://pruebaspons.duckdns.org', '/') ?>/telegram/webhook/telegram_bot_webhook.php?auth_token=<?= urlencode($_ENV['TELEGRAM_WEBHOOK_TOKEN'] ?? '') ?>' https://api.telegram.org/bot<?= $_ENV['TELEGRAM_BOT_TOKEN'] ?? '' ?>/setWebhook" id="curlUnifiedCommand" readonly>
|
||||
<button class="btn btn-outline-secondary btn-sm" type="button" onclick="copyToClipboard(document.getElementById('curlUnifiedCommand'))">
|
||||
<i class="bi bi-clipboard"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="alert alert-success mt-3 p-2 small">
|
||||
<i class="bi bi-check-circle-fill me-1"></i>
|
||||
<span data-translate="true">Este es el <strong>único webhook</strong> que necesitas configurar.</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-4">
|
||||
<div class="card shadow-sm mb-4">
|
||||
<div class="card-header">
|
||||
<h5 class="mb-0" data-translate="true">Vista Previa</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="telegram-message">
|
||||
<div class="telegram-message-avatar">
|
||||
<img src="https://cdn4.telegram.org/file/vaG9VXzJ9Us3druVCEMoQv-1wwhBWO1hieJQxGZA9fY/1lZkbjXz6CQ" alt="Bot" class="rounded-circle" width="40">
|
||||
</div>
|
||||
<div class="telegram-message-content">
|
||||
<div class="telegram-message-username">Bot de Interacción</div>
|
||||
<div class="telegram-message-text">
|
||||
<?= nl2br(htmlspecialchars(str_replace(
|
||||
['{user_name}', '{group_link}'],
|
||||
['UsuarioEjemplo', $config['group_invite_link'] ?? ''],
|
||||
$config['message_text'] ?? ''
|
||||
))) ?>
|
||||
</div>
|
||||
<?php if (!empty($config['group_invite_link'])): ?>
|
||||
<div class="mt-2">
|
||||
<a href="<?= htmlspecialchars($config['group_invite_link']) ?>"
|
||||
class="btn btn-telegram"
|
||||
target="_blank">
|
||||
<?= htmlspecialchars($config['button_text'] ?? '') ?>
|
||||
</a>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php if (!empty($stats)): ?>
|
||||
<div class="card shadow-sm">
|
||||
<div class="card-header">
|
||||
<h5 class="mb-0" data-translate="true">Estadísticas</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="d-flex justify-content-between mb-2">
|
||||
<span data-translate="true">Usuarios únicos registrados:</span>
|
||||
<span class="fw-bold"><?= number_format($stats['unique_users'] ?? 0) ?></span>
|
||||
</div>
|
||||
<div class="d-flex justify-content-between">
|
||||
<span data-translate="true">Total de interacciones:</span>
|
||||
<span class="fw-bold"><?= number_format($stats['total_interactions'] ?? 0) ?></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.telegram-message {
|
||||
display: flex;
|
||||
padding: 8px 16px;
|
||||
margin-bottom: 1rem;
|
||||
background-color: #182533;
|
||||
border-radius: 8px;
|
||||
color: #e1e9f2;
|
||||
max-width: 100%;
|
||||
}
|
||||
.telegram-message-avatar {
|
||||
margin-right: 12px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.telegram-message-avatar img {
|
||||
border-radius: 50%;
|
||||
}
|
||||
.telegram-message-username {
|
||||
font-weight: 500;
|
||||
color: #6ab3f3;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
.telegram-message-text {
|
||||
margin-top: 2px;
|
||||
line-height: 1.4;
|
||||
white-space: pre-line;
|
||||
}
|
||||
.btn-telegram {
|
||||
background-color: #2AABEE;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 6px 12px;
|
||||
border-radius: 4px;
|
||||
font-size: 14px;
|
||||
text-decoration: none;
|
||||
display: inline-block;
|
||||
}
|
||||
.btn-telegram:hover {
|
||||
background-color: #229ED9;
|
||||
color: white;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
function copyToClipboard(button) {
|
||||
const input = button.closest('.input-group').querySelector('input');
|
||||
input.select();
|
||||
document.execCommand('copy');
|
||||
|
||||
// Cambiar el ícono temporalmente
|
||||
const icon = button.querySelector('i');
|
||||
const originalClass = icon.className;
|
||||
icon.className = 'bi bi-check';
|
||||
|
||||
// Restaurar después de 2 segundos
|
||||
setTimeout(() => {
|
||||
icon.className = originalClass;
|
||||
}, 2000);
|
||||
}
|
||||
</script>
|
||||
|
||||
<?php require_once __DIR__ . '/../../templates/footer.php'; ?>
|
||||
142
telegram/admin/telegram_welcome.php
Executable file
142
telegram/admin/telegram_welcome.php
Executable file
@@ -0,0 +1,142 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../../includes/session_check.php';
|
||||
require_once __DIR__ . '/../../includes/db.php';
|
||||
|
||||
// --- Lógica para el Mensaje de Bienvenida ---
|
||||
try {
|
||||
$pdo->exec("
|
||||
CREATE TABLE IF NOT EXISTS telegram_welcome_messages (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
chat_id BIGINT NOT NULL UNIQUE,
|
||||
welcome_message TEXT,
|
||||
is_active BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||||
);
|
||||
");
|
||||
} catch (PDOException $e) {
|
||||
die("Error al verificar la tabla de bienvenida: " . $e->getMessage());
|
||||
}
|
||||
|
||||
// --- Lógica para los Ajustes Generales ---
|
||||
|
||||
// Manejar el envío de formularios
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
// Guardar Mensaje de Bienvenida
|
||||
if (isset($_POST['save_welcome_message'])) {
|
||||
$welcomeMessage = $_POST['welcome_message'] ?? '';
|
||||
$isActive = isset($_POST['is_active']) ? 1 : 0;
|
||||
$chatId = '-1002578350881'; // Manteniendo el ID de chat original para esta función
|
||||
|
||||
try {
|
||||
$stmt = $pdo->prepare("
|
||||
INSERT INTO telegram_welcome_messages (chat_id, welcome_message, is_active)
|
||||
VALUES (?, ?, ?)
|
||||
ON DUPLICATE KEY UPDATE welcome_message = ?, is_active = ?;
|
||||
");
|
||||
$stmt->execute([$chatId, $welcomeMessage, $isActive, $welcomeMessage, $isActive]);
|
||||
$successMessage = "¡Configuración de bienvenida guardada con éxito!";
|
||||
} catch (PDOException $e) {
|
||||
$errorMessage = "Error al guardar la configuración de bienvenida: " . $e->getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
// Guardar Ajustes Generales (Grupo por defecto)
|
||||
if (isset($_POST['save_general_settings'])) {
|
||||
$defaultGroupId = $_POST['default_announcement_group'] ?? '';
|
||||
try {
|
||||
$stmt = $pdo->prepare("INSERT INTO settings (setting_key, setting_value) VALUES (?, ?) ON DUPLICATE KEY UPDATE setting_value = ?");
|
||||
$stmt->execute(['telegram_default_announcement_group', $defaultGroupId, $defaultGroupId]);
|
||||
$successMessage = "¡Ajustes generales guardados con éxito!";
|
||||
} catch (PDOException $e) {
|
||||
$errorMessage = "Error al guardar los ajustes generales: " . $e->getMessage();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- Obtener datos para mostrar en la página ---
|
||||
|
||||
// Configuración del Mensaje de Bienvenida
|
||||
$stmt_welcome = $pdo->prepare("SELECT * FROM telegram_welcome_messages WHERE chat_id = ?");
|
||||
$stmt_welcome->execute(['-1002578350881']);
|
||||
$welcomeConfig = $stmt_welcome->fetch(PDO::FETCH_ASSOC);
|
||||
if (!$welcomeConfig) {
|
||||
$welcomeConfig = [
|
||||
'welcome_message' => '¡Bienvenido/a {user_name} a nuestro grupo! Esperamos que disfrutes tu estancia.',
|
||||
'is_active' => true
|
||||
];
|
||||
}
|
||||
|
||||
// Grupos de Telegram para el selector
|
||||
$stmt_groups = $pdo->query("SELECT name, platform_id FROM recipients WHERE platform = 'telegram' AND type = 'channel' ORDER BY name ASC");
|
||||
$telegramGroups = $stmt_groups->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
// Configuración del grupo por defecto
|
||||
$stmt_settings = $pdo->prepare("SELECT setting_value FROM settings WHERE setting_key = ?");
|
||||
$stmt_settings->execute(['telegram_default_announcement_group']);
|
||||
$defaultGroupSetting = $stmt_settings->fetchColumn();
|
||||
|
||||
$pageTitle = 'Configuración de Telegram';
|
||||
require_once __DIR__ . '/../../templates/header.php';
|
||||
?>
|
||||
|
||||
<div class="container-fluid">
|
||||
<h1 class="mt-4" data-translate="true">Configuración de Telegram</h1>
|
||||
|
||||
<?php if (isset($successMessage)): ?>
|
||||
<div class="alert alert-success" data-translate="true"><?= htmlspecialchars($successMessage) ?></div>
|
||||
<?php endif; ?>
|
||||
<?php if (isset($errorMessage)): ?>
|
||||
<div class="alert alert-danger" data-translate="true"><?= htmlspecialchars($errorMessage) ?></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- Card para Mensaje de Bienvenida -->
|
||||
<div class="card shadow-sm mb-4">
|
||||
<div class="card-header"><h5 class="mb-0" data-translate="true">Gestionar Mensaje de Bienvenida</h5></div>
|
||||
<div class="card-body">
|
||||
<form method="POST">
|
||||
<div class="mb-3">
|
||||
<label for="welcome_message" class="form-label" data-translate="true">Mensaje de Bienvenida</label>
|
||||
<textarea class="form-control" id="welcome_message" name="welcome_message" rows="5"><?= htmlspecialchars($welcomeConfig['welcome_message']) ?></textarea>
|
||||
<div class="form-text" data-translate="true">
|
||||
Puedes usar <code>{user_name}</code> y <code>{chat_title}</code>.
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-check form-switch mb-3">
|
||||
<input class="form-check-input" type="checkbox" id="is_active" name="is_active" <?= $welcomeConfig['is_active'] ? 'checked' : '' ?>>
|
||||
<label class="form-check-label" for="is_active" data-translate="true">Activar mensaje de bienvenida</label>
|
||||
</div>
|
||||
<button type="submit" name="save_welcome_message" class="btn btn-primary">
|
||||
<i class="bi bi-save-fill me-1"></i> <span data-translate="true">Guardar Mensaje de Bienvenida</span>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Card para Ajustes Generales -->
|
||||
<div class="card shadow-sm mb-4">
|
||||
<div class="card-header"><h5 class="mb-0" data-translate="true">Ajustes de Comandos</h5></div>
|
||||
<div class="card-body">
|
||||
<form method="POST">
|
||||
<div class="mb-3">
|
||||
<label for="default_announcement_group" class="form-label" data-translate="true">Grupo de Anuncios por Defecto</label>
|
||||
<select class="form-select" id="default_announcement_group" name="default_announcement_group">
|
||||
<option value="" data-translate="true">-- No seleccionado --</option>
|
||||
<?php foreach ($telegramGroups as $group): ?>
|
||||
<option value="<?= htmlspecialchars($group['platform_id']) ?>" <?= ($defaultGroupSetting == $group['platform_id']) ? 'selected' : '' ?>>
|
||||
<?= htmlspecialchars($group['name']) ?> (ID: <?= htmlspecialchars($group['platform_id']) ?>)
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
<div class="form-text" data-translate="true">Selecciona el grupo al que se enviarán los mensajes al usar el comando "Enviar a grupo #comando" desde el chat del bot.</div>
|
||||
</div>
|
||||
<button type="submit" name="save_general_settings" class="btn btn-primary">
|
||||
<i class="bi bi-save-fill me-1"></i> <span data-translate="true">Guardar Ajustes de Comandos</span>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php
|
||||
require_once __DIR__ . '/../../templates/footer.php';
|
||||
?>
|
||||
73
telegram/converters/HtmlToTelegramHtmlConverter.php
Executable file
73
telegram/converters/HtmlToTelegramHtmlConverter.php
Executable file
@@ -0,0 +1,73 @@
|
||||
<?php
|
||||
|
||||
class HtmlToTelegramHtmlConverter
|
||||
{
|
||||
/**
|
||||
* Convierte un fragmento de HTML a un formato compatible con el parse_mode=HTML de Telegram.
|
||||
*
|
||||
* @param string $html El HTML de entrada.
|
||||
* @return string El HTML adaptado para Telegram.
|
||||
*/
|
||||
public function convert($html)
|
||||
{
|
||||
if (empty(trim($html))) {
|
||||
return '';
|
||||
}
|
||||
|
||||
// 1. Decodificar entidades HTML como a espacios reales
|
||||
$html = html_entity_decode($html, ENT_QUOTES | ENT_HTML5, 'UTF-8');
|
||||
|
||||
// 2. Reemplazar párrafos por saltos de línea para mantener la separación.
|
||||
// Quita la etiqueta de apertura y reemplaza la de cierre con un salto de línea.
|
||||
$html = str_ireplace('<p>', '', $html);
|
||||
$html = str_ireplace('</p>', "\n", $html);
|
||||
|
||||
// 3. Asegurar que <br> también se convierta en un salto de línea.
|
||||
$html = preg_replace('/<br\s*\/?>/i', "\n", $html);
|
||||
|
||||
// 4. Eliminar etiquetas no soportadas por Telegram, pero manteniendo las básicas y <img>.
|
||||
// Telegram soporta: <b>, <strong>, <i>, <em>, <u>, <ins>, <s>, <strike>, <del>, <a>, <code>, <pre>
|
||||
// Mantenemos <img> para que nuestro parser interno (TelegramSender) lo procese.
|
||||
$allowed_tags = '<b><strong><i><em><u><ins><s><strike><del><a><code><pre><img>';
|
||||
$html = strip_tags($html, $allowed_tags);
|
||||
|
||||
// 5. Limpiar múltiples saltos de línea consecutivos, pero NO los espacios en blanco.
|
||||
// Esto previene más de dos saltos de línea seguidos, que Telegram ignora.
|
||||
$html = trim($html);
|
||||
$html = preg_replace('/[\r\n]{3,}/', "\n\n", $html);
|
||||
|
||||
// 6. Corregir etiquetas que pudieran quedar abiertas (buena práctica).
|
||||
$html = $this->fixUnmatchedTags($html);
|
||||
|
||||
error_log("HTML convertido para Telegram: " . $html);
|
||||
return $html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Corrige etiquetas HTML no cerradas o mal formadas en el mensaje.
|
||||
* @param string $html El HTML a corregir.
|
||||
* @return string El HTML con las etiquetas corregidas.
|
||||
*/
|
||||
private function fixUnmatchedTags($html) {
|
||||
$tags = ['b', 'strong', 'i', 'em', 'u', 'ins', 's', 'strike', 'del', 'code', 'pre'];
|
||||
|
||||
foreach ($tags as $tag) {
|
||||
// Contar etiquetas de apertura y cierre
|
||||
$openCount = substr_count(strtolower($html), "<$tag>");
|
||||
$closeCount = substr_count(strtolower($html), "</$tag>");
|
||||
|
||||
// Si hay más etiquetas de cierre que de apertura, eliminar las sobrantes
|
||||
if ($closeCount > $openCount) {
|
||||
$diff = $closeCount - $openCount;
|
||||
$html = preg_replace("#</{$tag}>#i", '', $html, $diff);
|
||||
}
|
||||
// Si hay más etiquetas de apertura que de cierre, agregar las que faltan al final
|
||||
elseif ($openCount > $closeCount) {
|
||||
$diff = $openCount - $closeCount;
|
||||
$html .= str_repeat("</{$tag}>", $diff);
|
||||
}
|
||||
}
|
||||
|
||||
return $html;
|
||||
}
|
||||
}
|
||||
567
telegram/webhook/telegram_bot_webhook.php
Executable file
567
telegram/webhook/telegram_bot_webhook.php
Executable file
@@ -0,0 +1,567 @@
|
||||
<?php
|
||||
// --- INICIO DE DEPURACIÓN DE DATOS CRUDOS ---
|
||||
$raw_content = file_get_contents("php://input");
|
||||
file_put_contents(__DIR__ . '/../../logs/raw_telegram_update.log', date('[Y-m-d H:i:s] ') . $raw_content . "\n", FILE_APPEND);
|
||||
// --- FIN DE DEPURACIÓN DE DATOS CRUDOS ---
|
||||
|
||||
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);
|
||||
|
||||
// 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.
|
||||
}
|
||||
|
||||
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)]);
|
||||
break;
|
||||
|
||||
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 || ($isPrivateChat && $userChatMode !== 'ia')) {
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
// Determinar si hay texto para procesar, ya sea en 'text' o 'caption'
|
||||
$text = null;
|
||||
$isCaption = false;
|
||||
if (isset($message['text'])) {
|
||||
$text = trim($message['text']);
|
||||
} elseif (isset($message['caption'])) {
|
||||
$text = trim($message['caption']);
|
||||
$isCaption = true;
|
||||
}
|
||||
|
||||
if ($text !== null) {
|
||||
$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();
|
||||
}
|
||||
|
||||
// Un caption no puede ser un comando, así que solo se comprueba si no es un caption.
|
||||
$isCommand = !$isCaption && ((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());
|
||||
}
|
||||
|
||||
// 3. Obtener idiomas activos
|
||||
$langStmt = $pdo->query("SELECT language_code FROM supported_languages WHERE is_active = 1");
|
||||
$activeLangs = $langStmt->fetchAll(PDO::FETCH_COLUMN);
|
||||
|
||||
// 4. Filtrar los idiomas de destino (todos los activos menos el original)
|
||||
$targetLangs = array_filter($activeLangs, function($lang) use ($detectedLang) {
|
||||
return $lang !== $detectedLang;
|
||||
});
|
||||
|
||||
// 5. Si no hay idiomas a los que traducir, no hacer nada
|
||||
if (empty($targetLangs)) {
|
||||
custom_log("[QUEUE] No se requieren traducciones para el mensaje de Telegram #$messageId desde '$detectedLang'.");
|
||||
return;
|
||||
}
|
||||
|
||||
// 6. Unir los idiomas de destino en un solo string y encolar una única tarea
|
||||
$targetLangsStr = implode(',', $targetLangs);
|
||||
|
||||
$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,
|
||||
$targetLangsStr
|
||||
]);
|
||||
|
||||
custom_log("[QUEUE] Mensaje de Telegram #$messageId encolado para traducción de '$detectedLang' a '{$targetLangsStr}'.");
|
||||
|
||||
} catch (Exception $e) {
|
||||
custom_log("[ERROR] Error en handleRegularMessage al encolar traducción: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user