feat: Implement Discord translation feature with ephemeral messages and refined content filtering
This commit introduces a comprehensive message translation system for the Discord bot, featuring: - Interactive language selection buttons for each translatable message. - Ephemeral translation responses visible only to the requesting user. - Robust filtering to prevent translation buttons for messages consisting solely of emojis, stickers, GIFs, or other non-translatable content. - Preservation of non-textual elements (images, video thumbnails, stickers) alongside translated text in embeds. - Full compatibility with DiscordPHP v7, addressing various API usage and error handling specifics (e.g., then , correct handling of null message properties, intent). Additionally, this commit resolves an incompatibility introduced in the shared class, ensuring that the Telegram bot's translation functionality remains fully operational by correctly parsing return values across both platforms. The following files were modified: - : Main Discord bot logic for message handling, button generation, and interaction processing. - : Adjusted to return full API response including confidence score. - : Updated environment variable loading and added new constants for service URLs and tokens. - : Updated constructor to include for absolute URL generation. - : Adjusted all calls to to correctly extract language codes from the new array return format, resolving Telegram bot's translation issues.
This commit is contained in:
@@ -7,45 +7,37 @@ require_once __DIR__ . '/../vendor/autoload.php';
|
|||||||
// Verificar si estamos en un contenedor Docker
|
// Verificar si estamos en un contenedor Docker
|
||||||
$is_docker = (getenv('DOCKER_CONTAINER') === '1') || ($_SERVER['DOCKER_CONTAINER'] ?? null) === '1';
|
$is_docker = (getenv('DOCKER_CONTAINER') === '1') || ($_SERVER['DOCKER_CONTAINER'] ?? null) === '1';
|
||||||
|
|
||||||
if ($is_docker) {
|
// Entorno: cargar archivo .env según APP_ENVIRONMENT
|
||||||
// Docker: cargar archivo .env creado por el entrypoint
|
$env = getenv('APP_ENVIRONMENT') ?: ($_SERVER['APP_ENVIRONMENT'] ?? 'pruebas');
|
||||||
$dotenv = null;
|
|
||||||
if (file_exists(dirname(__DIR__) . '/.env')) {
|
if ($env === 'reod') {
|
||||||
$dotenv = Dotenv\Dotenv::createImmutable(dirname(__DIR__));
|
|
||||||
try {
|
|
||||||
$dotenv->load();
|
|
||||||
} catch (Exception $e) {
|
|
||||||
die('Error al cargar el archivo de entorno en Docker: ' . $e->getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Entorno local: cargar archivo .env según APP_ENVIRONMENT
|
|
||||||
$env = getenv('APP_ENVIRONMENT') ?: ($_SERVER['APP_ENVIRONMENT'] ?? 'pruebas');
|
|
||||||
$envFile = '.env';
|
$envFile = '.env';
|
||||||
if ($env) {
|
} else {
|
||||||
$envFile = '.env.' . $env;
|
$envFile = '.env.' . $env;
|
||||||
}
|
|
||||||
|
|
||||||
$dotenv = null;
|
|
||||||
if (file_exists(dirname(__DIR__) . '/' . $envFile)) {
|
|
||||||
$dotenv = Dotenv\Dotenv::createImmutable(dirname(__DIR__), $envFile);
|
|
||||||
} elseif (file_exists(dirname(__DIR__) . '/.env')) {
|
|
||||||
$dotenv = Dotenv\Dotenv::createImmutable(dirname(__DIR__));
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($dotenv) {
|
|
||||||
try {
|
|
||||||
$dotenv->load();
|
|
||||||
} catch (Exception $e) {
|
|
||||||
die('Error al cargar el archivo de entorno: ' . $e->getMessage());
|
|
||||||
}
|
|
||||||
$dotenv->required([
|
|
||||||
'DB_HOST', 'DB_NAME', 'DB_USER', 'DB_PASS',
|
|
||||||
'JWT_SECRET', 'APP_URL'
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$dotenv = null;
|
||||||
|
if (file_exists(dirname(__DIR__) . '/' . $envFile)) {
|
||||||
|
$dotenv = Dotenv\Dotenv::createImmutable(dirname(__DIR__), $envFile);
|
||||||
|
} elseif (file_exists(dirname(__DIR__) . '/.env')) {
|
||||||
|
$dotenv = Dotenv\Dotenv::createImmutable(dirname(__DIR__));
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($dotenv) {
|
||||||
|
try {
|
||||||
|
$dotenv->load();
|
||||||
|
} catch (Exception $e) {
|
||||||
|
die('Error al cargar el archivo de entorno: ' . $e->getMessage());
|
||||||
|
}
|
||||||
|
// ... el resto de la configuración ...
|
||||||
|
// Aquí es donde definimos las variables obligatorias
|
||||||
|
$dotenv->required([
|
||||||
|
'DB_HOST', 'DB_NAME', 'DB_USER', 'DB_PASS',
|
||||||
|
'JWT_SECRET', 'APP_URL'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// Environment Configuration
|
// Environment Configuration
|
||||||
define('ENVIRONMENT', $_ENV['APP_ENV'] ?? $_SERVER['APP_ENV'] ?? 'production');
|
define('ENVIRONMENT', $_ENV['APP_ENV'] ?? $_SERVER['APP_ENV'] ?? 'production');
|
||||||
|
|
||||||
@@ -54,7 +46,9 @@ $is_cli = (php_sapi_name() === 'cli' || defined('STDIN'));
|
|||||||
|
|
||||||
// Helper function to get env vars
|
// Helper function to get env vars
|
||||||
function getEnvVar($name, $default = null) {
|
function getEnvVar($name, $default = null) {
|
||||||
return $_ENV[$name] ?? $_SERVER[$name] ?? getenv($name) ?? $default;
|
// Priorizamos getenv() para las variables cargadas por Dotenv,
|
||||||
|
// ya que sabemos que funciona y $_ENV puede estar corrupto o contener Array en algunos entornos Docker.
|
||||||
|
return getenv($name) ?: ($_ENV[$name] ?? $_SERVER[$name] ?? $default);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Configurar la URL base y el protocolo
|
// Configurar la URL base y el protocolo
|
||||||
|
|||||||
215
discord_bot.php
215
discord_bot.php
@@ -8,7 +8,7 @@ require_once __DIR__ . '/includes/db.php';
|
|||||||
require_once __DIR__ . '/includes/logger.php';
|
require_once __DIR__ . '/includes/logger.php';
|
||||||
require_once __DIR__ . '/discord/DiscordSender.php';
|
require_once __DIR__ . '/discord/DiscordSender.php';
|
||||||
require_once __DIR__ . '/discord/converters/HtmlToDiscordMarkdownConverter.php';
|
require_once __DIR__ . '/discord/converters/HtmlToDiscordMarkdownConverter.php';
|
||||||
require_once __DIR__ . '/includes/Translate.php';
|
require_once __DIR__ . '/src/Translate.php';
|
||||||
|
|
||||||
// Importar clases necesarias
|
// Importar clases necesarias
|
||||||
use Discord\Discord;
|
use Discord\Discord;
|
||||||
@@ -38,7 +38,7 @@ if (!defined('DISCORD_BOT_TOKEN') || empty(DISCORD_BOT_TOKEN)) {
|
|||||||
try {
|
try {
|
||||||
$discord = new Discord([
|
$discord = new Discord([
|
||||||
'token' => DISCORD_BOT_TOKEN,
|
'token' => DISCORD_BOT_TOKEN,
|
||||||
'intents' => Intents::GUILDS | Intents::GUILD_MESSAGES | Intents::DIRECT_MESSAGES | Intents::GUILD_MEMBERS | Intents::GUILD_MESSAGE_REACTIONS,
|
'intents' => Intents::GUILDS | Intents::GUILD_MESSAGES | Intents::DIRECT_MESSAGES | Intents::GUILD_MEMBERS | Intents::GUILD_MESSAGE_REACTIONS | Intents::MESSAGE_CONTENT,
|
||||||
'logger' => $logger
|
'logger' => $logger
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@@ -96,7 +96,7 @@ try {
|
|||||||
|
|
||||||
if (!empty($originalContent)) {
|
if (!empty($originalContent)) {
|
||||||
try {
|
try {
|
||||||
$translator = new Translate();
|
$translator = new Translate(LIBRETRANSLATE_URL);
|
||||||
$sourceLang = $translator->detectLanguage($originalContent) ?? 'es';
|
$sourceLang = $translator->detectLanguage($originalContent) ?? 'es';
|
||||||
if ($sourceLang === $targetLang) {
|
if ($sourceLang === $targetLang) {
|
||||||
// Fallback: muchas plantillas están en ES; intenta forzar ES como origen
|
// Fallback: muchas plantillas están en ES; intenta forzar ES como origen
|
||||||
@@ -143,6 +143,111 @@ try {
|
|||||||
return; // Salir después de manejar la interacción
|
return; // Salir después de manejar la interacción
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MANEJAR TRADUCCIÓN A IDIOMA ESPECÍFICO (desde botones de idiomas)
|
||||||
|
if (strpos($customId, 'translate_to_lang:') === 0) {
|
||||||
|
// Extraer el ID del mensaje original y el idioma destino del custom_id
|
||||||
|
$parts = explode(':', $customId);
|
||||||
|
if (count($parts) < 3) {
|
||||||
|
$interaction->respondWithMessage(MessageBuilder::new()->setContent("❌ Error: Formato de botón de traducción inválido."), true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$originalMessageId = $parts[1];
|
||||||
|
$targetLang = $parts[2];
|
||||||
|
|
||||||
|
// 1. Deferir la respuesta de forma efímera para que el usuario sepa que se está procesando
|
||||||
|
$interaction->acknowledge(true)->done(function() use ($originalMessageId, $targetLang, $interaction, $discord, $pdo, $logger, $userId) {
|
||||||
|
// 2. Obtener el mensaje original usando su ID
|
||||||
|
$interaction->channel->messages->fetch($originalMessageId)->done(function ($originalMessage) use ($originalMessageId, $interaction, $discord, $pdo, $logger, $userId, $targetLang) {
|
||||||
|
$originalContent = (string) ($originalMessage->content ?? '');
|
||||||
|
|
||||||
|
$emojiMap = [];
|
||||||
|
preg_match_all('/<a?:(\w+):(\d+)>/', $originalContent, $matches, PREG_OFFSET_CAPTURE);
|
||||||
|
foreach ($matches[0] as $match) {
|
||||||
|
$emojiMap[$match[1]] = $match[0];
|
||||||
|
}
|
||||||
|
$contentForTranslation = preg_replace('/<a?:(\w+):(\d+)>/', '', $originalContent);
|
||||||
|
$contentForTranslation = trim($contentForTranslation);
|
||||||
|
|
||||||
|
if (empty($contentForTranslation)) {
|
||||||
|
$interaction->editOriginalResponse(MessageBuilder::new()->setContent("⚠️ El mensaje no contiene texto traducible."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$translator = new Translate(LIBRETRANSLATE_URL);
|
||||||
|
$detectionResultForSource = $translator->detectLanguage($contentForTranslation);
|
||||||
|
$sourceLang = $detectionResultForSource[0]['language'] ?? 'es';
|
||||||
|
|
||||||
|
if ($sourceLang === $targetLang) {
|
||||||
|
$interaction->editOriginalResponse(MessageBuilder::new()->setContent("⚠️ El mensaje ya está en el idioma seleccionado ({$targetLang})."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$translatedText = $translator->translateText($contentForTranslation, $sourceLang, $targetLang);
|
||||||
|
|
||||||
|
if (!empty($translatedText)) {
|
||||||
|
foreach ($emojiMap as $emojiTag) {
|
||||||
|
$translatedText .= ' ' . $emojiTag;
|
||||||
|
}
|
||||||
|
$translatedText = trim($translatedText);
|
||||||
|
|
||||||
|
$stmt = $pdo->prepare("SELECT flag_emoji, language_name FROM supported_languages WHERE language_code = ? AND is_active = 1");
|
||||||
|
$stmt->execute([$targetLang]);
|
||||||
|
$langInfo = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
$flag = $langInfo['flag_emoji'] ?? '🏳️';
|
||||||
|
$langName = $langInfo['language_name'] ?? strtoupper($targetLang);
|
||||||
|
|
||||||
|
$embed = new Embed($discord);
|
||||||
|
$embed->setTitle("{$flag} Traducción a {$langName}");
|
||||||
|
$embed->setDescription($translatedText);
|
||||||
|
$embed->setColor("#5865F2");
|
||||||
|
$embed->setFooter("Traducido de {$sourceLang} • Solo tú puedes ver esto");
|
||||||
|
|
||||||
|
$imageUrl = null;
|
||||||
|
if ($originalMessage->attachments !== null && count($originalMessage->attachments) > 0) {
|
||||||
|
$firstAttachment = $originalMessage->attachments->first();
|
||||||
|
if ($firstAttachment && isset($firstAttachment->url)) {
|
||||||
|
$contentType = $firstAttachment->content_type ?? '';
|
||||||
|
if (strpos($contentType, 'image/') === 0 || strpos($contentType, 'video/') === 0) {
|
||||||
|
$imageUrl = $firstAttachment->url;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$imageUrl && $originalMessage->stickers !== null && count($originalMessage->stickers) > 0) {
|
||||||
|
$firstSticker = $originalMessage->stickers->first();
|
||||||
|
if ($firstSticker && isset($firstSticker->image_url)) {
|
||||||
|
$imageUrl = $firstSticker->image_url;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($imageUrl) {
|
||||||
|
$embed->setImage($imageUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Editar la respuesta original con el resultado
|
||||||
|
$builder = MessageBuilder::new()->addEmbed($embed);
|
||||||
|
$interaction->sendFollowUpMessage($builder, true);
|
||||||
|
|
||||||
|
$logger->info("[TRANSLATION_BUTTON] Usuario {$userId} tradujo mensaje #{$originalMessageId} de {$sourceLang} a {$targetLang}");
|
||||||
|
} else {
|
||||||
|
$interaction->editOriginalResponse(MessageBuilder::new()->setContent("⚠️ No se pudo traducir el mensaje."));
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
$logger->error("[TRANSLATION_BUTTON] Error al traducir", ['error' => $e->getMessage(), 'trace' => $e->getTraceAsString()]);
|
||||||
|
$interaction->editOriginalResponse(MessageBuilder::new()->setContent("❌ Error al traducir: " . $e->getMessage()));
|
||||||
|
}
|
||||||
|
}, function ($error) use ($interaction, $logger) {
|
||||||
|
$logger->error("[TRANSLATION_BUTTON] Error al obtener mensaje original", ['error' => $error->getMessage()]);
|
||||||
|
$interaction->editOriginalResponse(MessageBuilder::new()->setContent("❌ No se pudo obtener el mensaje original."));
|
||||||
|
});
|
||||||
|
}, function ($error) use ($logger, $interaction) {
|
||||||
|
$logger->error("[TRANSLATION_BUTTON] Error al reconocer (defer) la interacción.", ['error' => $error->getMessage()]);
|
||||||
|
// If even deferring fails, we can't edit the response.
|
||||||
|
});
|
||||||
|
return; // Finalizar para no procesar otros ifs
|
||||||
|
}
|
||||||
|
|
||||||
// Traducción de plantillas completas (comandos #)
|
// Traducción de plantillas completas (comandos #)
|
||||||
if (strpos($customId, 'translate_template:') === 0) {
|
if (strpos($customId, 'translate_template:') === 0) {
|
||||||
$payload = substr($customId, strlen('translate_template:'));
|
$payload = substr($customId, strlen('translate_template:'));
|
||||||
@@ -183,7 +288,7 @@ try {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$translator = new Translate();
|
$translator = new Translate(LIBRETRANSLATE_URL);
|
||||||
$sourceLang = $translator->detectLanguage($fullText) ?? 'es';
|
$sourceLang = $translator->detectLanguage($fullText) ?? 'es';
|
||||||
if ($sourceLang === $targetLang) {
|
if ($sourceLang === $targetLang) {
|
||||||
// Fallback: fuerza ES como origen si coincide con el destino (común en nuestras plantillas)
|
// Fallback: fuerza ES como origen si coincide con el destino (común en nuestras plantillas)
|
||||||
@@ -499,37 +604,89 @@ function handleDiscordCommand(Message $message, PDO $pdo, Logger $logger)
|
|||||||
function handleDiscordTranslation(Message $message, PDO $pdo, Logger $logger)
|
function handleDiscordTranslation(Message $message, PDO $pdo, Logger $logger)
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$translator = new Translate();
|
$translator = new Translate(LIBRETRANSLATE_URL); // Instanciar al inicio
|
||||||
|
|
||||||
$text = $message->content;
|
$messageContentOriginal = trim($message->content);
|
||||||
$detectedLang = $translator->detectLanguage(strip_tags($text)) ?? 'es';
|
|
||||||
|
// Determine if there is translatable text content based on LibreTranslate confidence
|
||||||
|
$hasTranslatableText = false;
|
||||||
|
$contentForDetection = ''; // Initialize outside if block for broader scope
|
||||||
|
|
||||||
// Obtener idiomas activos y encolar traducciones
|
if (!empty($messageContentOriginal)) { // Only try to detect if message has any content
|
||||||
$langStmt = $pdo->query("SELECT language_code FROM supported_languages WHERE is_active = 1");
|
// Prepare content for language detection: remove custom Discord emojis
|
||||||
$activeLangs = $langStmt->fetchAll(PDO::FETCH_COLUMN);
|
$contentForDetection = preg_replace('/<a?:(\w+):(\d+)>/u', '', $messageContentOriginal);
|
||||||
|
// Removed: $contentForDetection = strip_tags($contentForDetection); // This was the culprit
|
||||||
|
$contentForDetection = trim($contentForDetection);
|
||||||
|
|
||||||
if (in_array($detectedLang, $activeLangs)) {
|
if (!empty($contentForDetection)) { // Only if there's something left to detect
|
||||||
foreach ($activeLangs as $targetLang) {
|
$detectionResult = $translator->detectLanguage($contentForDetection);
|
||||||
if ($detectedLang === $targetLang) {
|
$confidence = $detectionResult[0]['confidence'] ?? 0.0;
|
||||||
continue; // No traducir al mismo idioma
|
if ($confidence > 0.0) {
|
||||||
|
$hasTranslatableText = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
$sql = "INSERT INTO translation_queue (platform, message_id, chat_id, user_id, text_to_translate, source_lang, target_lang) VALUES (?, ?, ?, ?, ?, ?, ?)";
|
|
||||||
$stmt = $pdo->prepare($sql);
|
|
||||||
$stmt->execute([
|
|
||||||
'discord',
|
|
||||||
$message->id,
|
|
||||||
$message->channel_id,
|
|
||||||
$message->author->id,
|
|
||||||
$text,
|
|
||||||
$detectedLang,
|
|
||||||
$targetLang
|
|
||||||
]);
|
|
||||||
|
|
||||||
$logger->info("[QUEUE] Mensaje de Discord #{$message->id} encolado para traducción de '$detectedLang' a '$targetLang'.");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If no translatable text is found, do not send buttons.
|
||||||
|
if (!$hasTranslatableText) {
|
||||||
|
$logger->info("[TRANSLATION_BUTTONS] Mensaje de Discord #{$message->id} sin contenido de texto traducible, no se envían botones de traducción.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. Detectar idioma original (using the content prepared for detection, which we know has text)
|
||||||
|
$finalDetectionResult = $translator->detectLanguage($contentForDetection); // This will be the full array or null
|
||||||
|
$detectedLang = $finalDetectionResult[0]['language'] ?? 'es'; // Correctly extract language
|
||||||
|
|
||||||
|
// 2. Obtener todos los idiomas activos con información completa
|
||||||
|
$langStmt = $pdo->query("SELECT language_code, language_name, flag_emoji FROM supported_languages WHERE is_active = 1 ORDER BY language_name ASC");
|
||||||
|
$activeLangs = $langStmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
// 3. Filtrar los idiomas de destino (todos los activos menos el original)
|
||||||
|
$targetLangs = array_filter($activeLangs, function($lang) use ($detectedLang) {
|
||||||
|
return $lang['language_code'] !== $detectedLang;
|
||||||
|
});
|
||||||
|
|
||||||
|
// 4. Si no hay idiomas a los que traducir, no hacer nada
|
||||||
|
if (empty($targetLangs)) {
|
||||||
|
$logger->info("[TRANSLATION_BUTTONS] No se requieren botones de traducción para el mensaje de Discord #{$message->id} desde '$detectedLang'.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. Crear botones de traducción para cada idioma destino
|
||||||
|
$components = [];
|
||||||
|
$actionRow = ActionRow::new();
|
||||||
|
$buttonCount = 0;
|
||||||
|
|
||||||
|
foreach ($targetLangs as $lang) {
|
||||||
|
$button = Button::new(Button::STYLE_SECONDARY, 'translate_to_lang:' . $message->id . ':' . $lang['language_code'])
|
||||||
|
->setLabel($lang['language_name']);
|
||||||
|
if (!empty($lang['flag_emoji'])) {
|
||||||
|
$button->setEmoji($lang['flag_emoji']);
|
||||||
|
}
|
||||||
|
$actionRow->addComponent($button);
|
||||||
|
$buttonCount++;
|
||||||
|
|
||||||
|
if ($buttonCount % 5 === 0) {
|
||||||
|
$components[] = $actionRow;
|
||||||
|
$actionRow = ActionRow::new();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($buttonCount % 5 !== 0) {
|
||||||
|
$components[] = $actionRow;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 6. Enviar mensaje del bot con botones como respuesta al mensaje original
|
||||||
|
$builder = MessageBuilder::new()
|
||||||
|
->setContent('🌐 Select a language to translate to:')
|
||||||
|
->setComponents($components);
|
||||||
|
|
||||||
|
$message->reply($builder)->done(function () use ($logger, $message, $detectedLang) {
|
||||||
|
$logger->info("[TRANSLATION_BUTTONS] Botones de traducción enviados para mensaje #{$message->id} (idioma detectado: $detectedLang)");
|
||||||
|
}, function ($error) use ($logger, $message) {
|
||||||
|
$logger->error("[TRANSLATION_BUTTONS] Error al enviar botones para mensaje #{$message->id}", ['error' => $error->getMessage()]);
|
||||||
|
});
|
||||||
|
|
||||||
} catch (Throwable $e) {
|
} catch (Throwable $e) {
|
||||||
$logger->error("[Error Encolando Traducción Discord]", ['error' => $e->getMessage(), 'trace' => $e->getTraceAsString()]);
|
$logger->error("[TRANSLATION_BUTTONS] Error al procesar mensaje para botones de traducción", ['error' => $e->getMessage(), 'trace' => $e->getTraceAsString()]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,21 +4,22 @@ class Translate
|
|||||||
{
|
{
|
||||||
private $apiUrl;
|
private $apiUrl;
|
||||||
|
|
||||||
public function __construct()
|
public function __construct(string $apiUrl)
|
||||||
{
|
{
|
||||||
$this->apiUrl = rtrim($_ENV['LIBRETRANSLATE_URL'], '/');
|
$this->apiUrl = rtrim($apiUrl, '/');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function detectLanguage($text)
|
public function detectLanguage($text)
|
||||||
{
|
{
|
||||||
if (empty(trim($text))) {
|
if (empty(trim($text))) {
|
||||||
return null;
|
return null; // Or return an empty array indicating no detection
|
||||||
}
|
}
|
||||||
|
|
||||||
$response = $this->request('/detect', ['q' => $text]);
|
$response = $this->request('/detect', ['q' => $text]);
|
||||||
|
|
||||||
|
// Return the full response array if detection was successful
|
||||||
if (isset($response[0]['language'])) {
|
if (isset($response[0]['language'])) {
|
||||||
return $response[0]['language'];
|
return $response; // Return the array, e.g., [{"confidence":90.0,"language":"es"}]
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -8,11 +8,13 @@ class TelegramSender
|
|||||||
private $botToken;
|
private $botToken;
|
||||||
private $apiUrl = 'https://api.telegram.org/bot';
|
private $apiUrl = 'https://api.telegram.org/bot';
|
||||||
private $pdo;
|
private $pdo;
|
||||||
|
private $baseUrl; // Nueva propiedad para almacenar BOT_BASE_URL
|
||||||
|
|
||||||
public function __construct($botToken, $pdo)
|
public function __construct($botToken, $pdo, string $baseUrl)
|
||||||
{
|
{
|
||||||
$this->botToken = $botToken;
|
$this->botToken = $botToken;
|
||||||
$this->pdo = $pdo;
|
$this->pdo = $pdo;
|
||||||
|
$this->baseUrl = rtrim($baseUrl, '/'); // Asegurarse de que no tenga una barra al final
|
||||||
}
|
}
|
||||||
|
|
||||||
public function sendMessage($chatId, $content, $options = [], $addTranslateButton = false, $messageLanguage = 'es', $originalFullContent = null)
|
public function sendMessage($chatId, $content, $options = [], $addTranslateButton = false, $messageLanguage = 'es', $originalFullContent = null)
|
||||||
@@ -302,7 +304,7 @@ class TelegramSender
|
|||||||
|
|
||||||
// Convertir rutas relativas a absolutas si es necesario
|
// Convertir rutas relativas a absolutas si es necesario
|
||||||
if (!preg_match('/^https?:\/\//', $image_url)) {
|
if (!preg_match('/^https?:\/\//', $image_url)) {
|
||||||
$base_url = rtrim($_ENV['APP_URL'], '/');
|
$base_url = $this->baseUrl;
|
||||||
$image_url = $base_url . '/' . ltrim($image_url, '/');
|
$image_url = $base_url . '/' . ltrim($image_url, '/');
|
||||||
custom_log("[DEBUG] URL de imagen convertida: " . $image_url);
|
custom_log("[DEBUG] URL de imagen convertida: " . $image_url);
|
||||||
|
|
||||||
@@ -605,7 +607,7 @@ class TelegramSender
|
|||||||
|
|
||||||
// Si la URL es relativa, intentar convertirla a absoluta
|
// Si la URL es relativa, intentar convertirla a absoluta
|
||||||
if (!preg_match('/^https?:\/\//i', $url)) {
|
if (!preg_match('/^https?:\/\//i', $url)) {
|
||||||
$baseUrl = rtrim($_ENV['APP_URL'], '/');
|
$baseUrl = $this->baseUrl;
|
||||||
$absoluteUrl = $baseUrl . '/' . ltrim($url, '/');
|
$absoluteUrl = $baseUrl . '/' . ltrim($url, '/');
|
||||||
custom_log("${logPrefix} URL relativa detectada, convirtiendo a absoluta: $absoluteUrl");
|
custom_log("${logPrefix} URL relativa detectada, convirtiendo a absoluta: $absoluteUrl");
|
||||||
$url = $absoluteUrl;
|
$url = $absoluteUrl;
|
||||||
@@ -712,7 +714,7 @@ class TelegramSender
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!preg_match('/^https?:\/\//', $url)) {
|
if (!preg_match('/^https?:\/\//', $url)) {
|
||||||
$baseUrl = rtrim($_ENV['APP_URL'], '/');
|
$baseUrl = $this->baseUrl;
|
||||||
$url = $baseUrl . '/' . ltrim($url, '/');
|
$url = $baseUrl . '/' . ltrim($url, '/');
|
||||||
custom_log("[DEBUG] isValidImageUrl: URL convertida a absoluta: " . $url);
|
custom_log("[DEBUG] isValidImageUrl: URL convertida a absoluta: " . $url);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ ini_set('log_errors', 1);
|
|||||||
|
|
||||||
// Verificación de autenticación
|
// Verificación de autenticación
|
||||||
$authToken = $_GET['auth_token'] ?? '';
|
$authToken = $_GET['auth_token'] ?? '';
|
||||||
$expectedToken = $_ENV['TELEGRAM_WEBHOOK_TOKEN'] ?? '';
|
$expectedToken = TELEGRAM_WEBHOOK_TOKEN;
|
||||||
if (!empty($expectedToken) && $authToken !== $expectedToken) {
|
if (!empty($expectedToken) && $authToken !== $expectedToken) {
|
||||||
http_response_code(403);
|
http_response_code(403);
|
||||||
custom_log("Acceso no autorizado: token inválido.");
|
custom_log("Acceso no autorizado: token inválido.");
|
||||||
@@ -30,7 +30,7 @@ if (!empty($expectedToken) && $authToken !== $expectedToken) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Verificar token del bot
|
// Verificar token del bot
|
||||||
$botToken = $_ENV['TELEGRAM_BOT_TOKEN'] ?? '';
|
$botToken = TELEGRAM_BOT_TOKEN;
|
||||||
if (empty($botToken)) {
|
if (empty($botToken)) {
|
||||||
http_response_code(500);
|
http_response_code(500);
|
||||||
custom_log("Token de bot no configurado.");
|
custom_log("Token de bot no configurado.");
|
||||||
@@ -49,8 +49,8 @@ if (!$update) {
|
|||||||
custom_log("Update recibido: " . json_encode($update, JSON_PRETTY_PRINT));
|
custom_log("Update recibido: " . json_encode($update, JSON_PRETTY_PRINT));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$telegram = new TelegramSender($botToken, $pdo);
|
$telegram = new TelegramSender(TELEGRAM_BOT_TOKEN, $pdo, BOT_BASE_URL);
|
||||||
$translator = new Translate();
|
$translator = new Translate(LIBRETRANSLATE_URL);
|
||||||
$commandLocker = new CommandLocker($pdo);
|
$commandLocker = new CommandLocker($pdo);
|
||||||
|
|
||||||
$message = $update['message'] ?? $update['channel_post'] ?? null;
|
$message = $update['message'] ?? $update['channel_post'] ?? null;
|
||||||
@@ -248,7 +248,8 @@ try {
|
|||||||
|
|
||||||
if (!empty($originalText)) {
|
if (!empty($originalText)) {
|
||||||
try {
|
try {
|
||||||
$sourceLang = $translator->detectLanguage($originalText);
|
$detectionResult = $translator->detectLanguage($originalText);
|
||||||
|
$sourceLang = $detectionResult[0]['language'] ?? null;
|
||||||
if ($sourceLang && $sourceLang !== $targetLang) {
|
if ($sourceLang && $sourceLang !== $targetLang) {
|
||||||
$translatedText = $translator->translateHtml($originalText, $sourceLang, $targetLang);
|
$translatedText = $translator->translateHtml($originalText, $sourceLang, $targetLang);
|
||||||
$telegram->answerCallbackQuery($callbackId, ['text' => $translatedText, 'show_alert' => true]);
|
$telegram->answerCallbackQuery($callbackId, ['text' => $translatedText, 'show_alert' => true]);
|
||||||
@@ -279,7 +280,8 @@ try {
|
|||||||
if ($originalContent) {
|
if ($originalContent) {
|
||||||
// Detectar idioma real del contenido y aplicar fallback si coincide con el destino
|
// 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'));
|
$plain = strip_tags(html_entity_decode($originalContent, ENT_QUOTES | ENT_HTML5, 'UTF-8'));
|
||||||
$sourceLang = $translator->detectLanguage($plain) ?? 'es';
|
$detectionResult = $translator->detectLanguage($plain);
|
||||||
|
$sourceLang = $detectionResult[0]['language'] ?? 'es';
|
||||||
if ($sourceLang === $targetLang) {
|
if ($sourceLang === $targetLang) {
|
||||||
$fallbackSrc = 'es';
|
$fallbackSrc = 'es';
|
||||||
if ($fallbackSrc !== $targetLang) {
|
if ($fallbackSrc !== $targetLang) {
|
||||||
@@ -351,7 +353,8 @@ try {
|
|||||||
|
|
||||||
function handleCommand($pdo, $telegram, $commandLocker, $translator, $text, $from, $chatId, $messageId) {
|
function handleCommand($pdo, $telegram, $commandLocker, $translator, $text, $from, $chatId, $messageId) {
|
||||||
$userId = $from['id'];
|
$userId = $from['id'];
|
||||||
$detectedLang = $translator->detectLanguage($text) ?? 'es';
|
$detectionResult = $translator->detectLanguage($text);
|
||||||
|
$detectedLang = $detectionResult[0]['language'] ?? 'es';
|
||||||
|
|
||||||
if (strpos($text, '/setlang') === 0 || strpos($text, '/setlanguage') === 0) {
|
if (strpos($text, '/setlang') === 0 || strpos($text, '/setlanguage') === 0) {
|
||||||
$parts = explode(' ', $text, 2);
|
$parts = explode(' ', $text, 2);
|
||||||
@@ -459,7 +462,8 @@ function handleCommand($pdo, $telegram, $commandLocker, $translator, $text, $fro
|
|||||||
$converter = new HtmlToTelegramHtmlConverter();
|
$converter = new HtmlToTelegramHtmlConverter();
|
||||||
$content = $converter->convert($content);
|
$content = $converter->convert($content);
|
||||||
|
|
||||||
$detectedLang = $translator->detectLanguage(strip_tags($content)) ?? 'es';
|
$detectionResult = $translator->detectLanguage(strip_tags($content));
|
||||||
|
$detectedLang = $detectionResult[0]['language'] ?? 'es';
|
||||||
if ($detectedLang !== 'es') {
|
if ($detectedLang !== 'es') {
|
||||||
$translatedContent = $translator->translateHtml($content, $detectedLang, 'es');
|
$translatedContent = $translator->translateHtml($content, $detectedLang, 'es');
|
||||||
if ($translatedContent) $content = $translatedContent;
|
if ($translatedContent) $content = $translatedContent;
|
||||||
@@ -520,7 +524,8 @@ function handleRegularMessage($pdo, $telegram, $commandLocker, $translator, $tex
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
// 1. Detectar el idioma del mensaje entrante
|
// 1. Detectar el idioma del mensaje entrante
|
||||||
$detectedLang = $translator->detectLanguage(strip_tags($text)) ?? 'es';
|
$detectionResult = $translator->detectLanguage(strip_tags($text));
|
||||||
|
$detectedLang = $detectionResult[0]['language'] ?? 'es';
|
||||||
|
|
||||||
// 2. Guardar la interacción original en la base de datos
|
// 2. Guardar la interacción original en la base de datos
|
||||||
try {
|
try {
|
||||||
|
|||||||
Reference in New Issue
Block a user