From 602e6d372497ab882e6126bd20dc4bbb535777fb Mon Sep 17 00:00:00 2001 From: Lizandro Guarnizo <77708265+lizandrogd@users.noreply.github.com> Date: Fri, 20 Feb 2026 11:21:06 -0500 Subject: [PATCH] up --- api/get_conversation_detail.php | 4 +- api/version/media-url.php | 1 + api/webhook.php | 1 + assets/js/chat-common.js | 11 ++- classes/MediaService.php | 4 +- conversations.php | 27 +++++- docker-compose.prod.yml | 4 +- docker/entrypoint.sh | 3 + docker/nginx/ssl-prod.conf | 2 +- migrations/20260220_media_queue_attempts.sql | 4 + scripts/media_worker.php | 99 ++++++++++++++++---- 11 files changed, 130 insertions(+), 30 deletions(-) create mode 100644 migrations/20260220_media_queue_attempts.sql diff --git a/api/get_conversation_detail.php b/api/get_conversation_detail.php index f35cf58..e99409b 100644 --- a/api/get_conversation_detail.php +++ b/api/get_conversation_detail.php @@ -77,7 +77,7 @@ try { // Formatear mensajes $conversations = array_map(function($msg) { // Build external URL if available (media id -> api/get_media proxy) - $external = (isset($msg['media_url']) && $msg['media_url']) ? (preg_match('#^https?://#i', $msg['media_url']) ? $msg['media_url'] : ("api/get_media.php?id=" . urlencode($msg['media_url']))) : null; + $external = (isset($msg['media_url']) && $msg['media_url']) ? (preg_match('#^https?://#i', $msg['media_url']) ? $msg['media_url'] : ("api/version/media-url.php?id=" . urlencode($msg['media_url']))) : null; // Fallback: if no external URL but content is JSON with link, extract it $contentStr = $msg['content'] ?? ''; @@ -99,7 +99,7 @@ try { foreach ($candidates as $cand) { if ($cand) { // if looks like id or url - $external = preg_match('#^https?://#i', $cand) ? $cand : (strpos($cand, 'http') === false ? "api/get_media.php?id=" . urlencode($cand) : $cand); + $external = preg_match('#^https?://#i', $cand) ? $cand : (strpos($cand, 'http') === false ? "api/version/media-url.php?id=" . urlencode($cand) : $cand); break; } } diff --git a/api/version/media-url.php b/api/version/media-url.php index d661bc9..c340ea3 100644 --- a/api/version/media-url.php +++ b/api/version/media-url.php @@ -140,6 +140,7 @@ if ($mediaId) { // ── Si no hay archivo local, descargar con MediaService y guardar como cache permanente ── if ($mediaId) { try { + set_time_limit(180); // Dar tiempo suficiente para fotos grandes (hasta 5MB) require_once __DIR__ . '/../../classes/MediaService.php'; $ms = new MediaService(); $subdir = date('Y/m'); diff --git a/api/webhook.php b/api/webhook.php index e4e4890..e8a1e2e 100644 --- a/api/webhook.php +++ b/api/webhook.php @@ -226,6 +226,7 @@ class WhatsAppWebhook { $localThumb = null; if (!empty($mediaUrl) && in_array($messageType, ['image', 'audio', 'video', 'document', 'sticker'])) { try { + set_time_limit(180); // Tiempo extra para descargar media pesado $mediaService = new MediaService(); $subdir = date('Y/m'); // Organizar por año/mes diff --git a/assets/js/chat-common.js b/assets/js/chat-common.js index 5310d3a..8507106 100644 --- a/assets/js/chat-common.js +++ b/assets/js/chat-common.js @@ -77,7 +77,14 @@ function renderMediaMessage(msg) { thumbUrl = `/api/version/media-url.php?url=${encodeURIComponent(external)}`; } } else { - thumbUrl = external; + // Route through media-url.php proxy (handles caching + auth) + if (external.indexOf('api/get_media.php?id=') !== -1 || external.indexOf('api/version/media-url.php?id=') !== -1) { + const parts = external.split('id='); + const mid = parts[1] ? decodeURIComponent(parts[1]) : ''; + thumbUrl = mid ? `/api/version/media-url.php?id=${encodeURIComponent(mid)}` : external; + } else { + thumbUrl = ensureProxyUrl(external) || external; + } } } else if (isValidMediaUrl(msg.media_url)) { thumbUrl = msg.media_url; @@ -90,7 +97,7 @@ function renderMediaMessage(msg) { fullUrl = localFile.startsWith('/') ? localFile : `/${localFile}`; } else if (isValidMediaUrl(mediaExternal)) { let mid = ''; - if (mediaExternal.indexOf('api/get_media.php?id=') !== -1) { + if (mediaExternal.indexOf('api/get_media.php?id=') !== -1 || mediaExternal.indexOf('api/version/media-url.php?id=') !== -1) { const parts = mediaExternal.split('id='); mid = parts[1] ? decodeURIComponent(parts[1]) : ''; } else { const m = mediaExternal.match(/[?&]mid=([^&]+)/i); if (m && m[1]) mid = decodeURIComponent(m[1]); diff --git a/classes/MediaService.php b/classes/MediaService.php index 627f8f4..2d51f38 100644 --- a/classes/MediaService.php +++ b/classes/MediaService.php @@ -116,8 +116,10 @@ class MediaService { CURLOPT_URL => $url, CURLOPT_RETURNTRANSFER => true, CURLOPT_FOLLOWLOCATION => true, - CURLOPT_TIMEOUT => 120, + CURLOPT_CONNECTTIMEOUT => 30, + CURLOPT_TIMEOUT => 180, CURLOPT_HTTPHEADER => $headers, + CURLOPT_BUFFERSIZE => 65536, ]); $content = curl_exec($ch); $err = curl_error($ch); diff --git a/conversations.php b/conversations.php index 970d031..effc8b2 100644 --- a/conversations.php +++ b/conversations.php @@ -5704,9 +5704,9 @@ if (!isset($_SESSION['user_id'])) { console.log('🔄 Usando media_url_external (URL absoluta):', full); } else if (mediaUrl && /^\d+$/.test(mediaUrl)) { // 📱 PRIORIDAD 4: Es un ID de WhatsApp (solo números) - usar URL absoluta - full = `${baseUrl}/api/get_media.php?id=${encodeURIComponent(mediaUrl)}`; + full = `${baseUrl}/api/version/media-url.php?id=${encodeURIComponent(mediaUrl)}`; thumb = full; - console.log('📱 Usando whatsapp_media_id con get_media.php:', mediaUrl); + console.log('📱 Usando whatsapp_media_id con media-url.php:', mediaUrl); } else if (mediaUrl && /^https?:\/\//i.test(mediaUrl)) { // 🌐 PRIORIDAD 5: URL externa directa (puede estar caducada) full = mediaUrl; @@ -5719,12 +5719,33 @@ if (!isset($_SESSION['user_id'])) { thumb = ''; } + // Si no hay URL de media, mostrar placeholder con botón de descarga + if (!full && mediaType !== 'text') { + const retryUrl = mediaUrl && /^\d+$/.test(mediaUrl) + ? `${baseUrl}/api/version/media-url.php?id=${encodeURIComponent(mediaUrl)}` + : (mediaUrlExternal ? `${baseUrl}/${mediaUrlExternal.replace(/^\//, '')}` : ''); + const retryBtn = retryUrl + ? ` Descargar ahora` + : ''; + const iconMap = {image: 'fa-image', video: 'fa-video', audio: 'fa-microphone', document: 'fa-file', sticker: 'fa-sticky-note'}; + const icon = iconMap[mediaType] || 'fa-file'; + return ` +
+ ${caption ? `