diff --git a/api/version/media-url.php b/api/version/media-url.php index 410ecfa..1e11cd3 100644 --- a/api/version/media-url.php +++ b/api/version/media-url.php @@ -45,6 +45,55 @@ if ($providedUrl) { if (!empty($extraParts)) { $mediaUrl .= (strpos($mediaUrl, '?') === false ? '?' : '&') . implode('&', $extraParts); } + + // Intentar detectar un media id embebido en la URL (mid=... o URL hacia graph.facebook.com/v.../{id}) + $resolvedFromGraph = false; + $mid = null; + + // Buscar mid en query string (lookaside or other URLs) + if (preg_match('/[?&]mid=([^&]+)/i', $mediaUrl, $m)) { + $mid = rawurldecode($m[1]); + } + + // Buscar patrón directo hacia graph.facebook.com/.../{mediaId} + if (!$mid && preg_match('#https?://[^/]*graph\.facebook\.com(?:/v[0-9]+\.[0-9]+)?/([^/?#&]+)#i', $mediaUrl, $m)) { + // El primer segmento después de la versión suele ser el media id + $candidate = $m[1]; + // Aceptar si parece un id (numérico o alfanum corto) + if (preg_match('/^[a-zA-Z0-9_-]+$/', $candidate)) { + $mid = $candidate; + } + } + + // Si detectamos un mid, consultamos Graph API desde el servidor para obtener la URL (autenticada) + if ($mid) { + $token = getConfigFromDB('whatsapp_token', ''); + $apiUrl = rtrim(getConfigFromDB('whatsapp_api_url', 'https://graph.facebook.com/v22.0/'), '/'); + $endpoint = "{$apiUrl}/{$mid}"; + + $ch = curl_init(); + curl_setopt_array($ch, [ + CURLOPT_URL => $endpoint, + CURLOPT_RETURNTRANSFER => true, + CURLOPT_HTTPHEADER => [ 'Authorization: Bearer ' . $token, 'Accept: application/json' ], + CURLOPT_TIMEOUT => 15, + ]); + $response = curl_exec($ch); + $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); + $err = curl_error($ch); + curl_close($ch); + + if (!$err && $httpCode < 400) { + $decoded = json_decode($response, true); + $resolvedUrl = $decoded['url'] ?? ($decoded['data'][0]['url'] ?? null); + if ($resolvedUrl) { + $mediaUrl = $resolvedUrl; + $resolvedFromGraph = true; + } + } + // Si hubo error o no devolvió URL, seguimos usando la URL provista como fallback + } + } else { $token = getConfigFromDB('whatsapp_token', ''); $apiUrl = rtrim(getConfigFromDB('whatsapp_api_url', 'https://graph.facebook.com/v22.0/'), '/'); @@ -126,9 +175,25 @@ if ($local) { exit; } -// Si se solicitó JSON, devolver la URL +// Si se solicitó JSON, devolver la URL y metadata si está disponible if ($wantsJson) { - echo json_encode(['success' => true, 'url' => $mediaUrl]); + $result = ['success' => true, 'url' => $mediaUrl]; + + // Incluir metadata retornada por Graph API si existe en $decoded + if (!empty($decoded) && is_array($decoded)) { + // Campos esperados directamente + foreach (['url','mime_type','sha256','file_size','id','messaging_product'] as $k) { + if (isset($decoded[$k])) $result[$k] = $decoded[$k]; + } + // También comprobar 'data'[0] (caso de respuestas anidadas) + if (isset($decoded['data']) && is_array($decoded['data']) && isset($decoded['data'][0]) && is_array($decoded['data'][0])) { + foreach (['url','mime_type','sha256','file_size','id','messaging_product'] as $k) { + if (isset($decoded['data'][0][$k])) $result[$k] = $decoded['data'][0][$k]; + } + } + } + + echo json_encode($result); exit; } @@ -252,6 +317,52 @@ if ($download) { exit; } +// Por defecto: si la URL fue resuelta mediante Graph API, intentar proxificar +// (hacer la petición desde el servidor añadiendo Authorization si existe el token) +// Esto evita problemas cuando la URL requiere autorización y un 302 directo no funciona. +if (!$wantsJson && !empty($resolvedFromGraph) && $resolvedFromGraph) { + $ch = curl_init(); + $token = getConfigFromDB('whatsapp_token', ''); + $headers = ['User-Agent: WhatsAppMediaProxy/1.0', 'Accept: */*']; + if ($token) $headers[] = 'Authorization: Bearer ' . $token; + + curl_setopt_array($ch, [ + CURLOPT_URL => $mediaUrl, + CURLOPT_RETURNTRANSFER => true, + CURLOPT_FOLLOWLOCATION => true, + CURLOPT_TIMEOUT => 60, + CURLOPT_HTTPHEADER => $headers, + ]); + $body = curl_exec($ch); + $info = curl_getinfo($ch); + $err = curl_error($ch); + curl_close($ch); + + if ($err) { + http_response_code(502); + header('Content-Type: application/json; charset=utf-8'); + echo json_encode(['success' => false, 'error' => 'Error al descargar el archivo (proxy)', 'detail' => $err]); + exit; + } + + $remoteHttp = isset($info['http_code']) ? (int)$info['http_code'] : 0; + if ($remoteHttp >= 400) { + http_response_code(502); + header('Content-Type: application/json; charset=utf-8'); + echo json_encode(['success' => false, 'error' => 'Remote server returned error status', 'http_code' => $remoteHttp, 'url' => $mediaUrl]); + exit; + } + + $contentType = $info['content_type'] ?? null; + + header_remove('Content-Type'); + header('Cache-Control: public, max-age=3600'); + if (!empty($contentType)) header('Content-Type: ' . $contentType); + header('Content-Length: ' . strlen($body)); + echo $body; + exit; +} + // Por defecto redirigir al URL (navegador hará la descarga o mostrará la imagen) header('Cache-Control: public, max-age=3600'); header('Location: ' . $mediaUrl, true, 302); diff --git a/chat_window.php b/chat_window.php index 4e47937..beac355 100644 --- a/chat_window.php +++ b/chat_window.php @@ -619,17 +619,30 @@ if (empty($user_id)) { } let fullUrl = ''; - if (isValidMediaUrl(msg.media_url_external)) { - fullUrl = `/api/version/media-url.php?local=${encodeURIComponent(msg.media_url_external)}`; + if (isValidMediaUrl(msg.local_file)) { + fullUrl = `/api/version/media-url.php?local=${encodeURIComponent(msg.local_file)}`; } else if (isValidMediaUrl(msg.media_url_external)) { - if (msg.media_url_external.indexOf('api/get_media.php?id=') !== -1) { - const parts = msg.media_url_external.split('id='); - const mid = parts[1] ? decodeURIComponent(parts[1]) : ''; - fullUrl = `/api/version/media-url.php?id=${encodeURIComponent(mid)}`; - } else if (/^https?:\/\//i.test(msg.media_url_external)) { - fullUrl = `/api/version/media-url.php?url=${encodeURIComponent(msg.media_url_external)}`; + // Si la URL externa contiene un media id (api/get_media.php?id=... o mid=...), usar id para consultar Graph API + const external = msg.media_url_external; + let mid = ''; + + if (external.indexOf('api/get_media.php?id=') !== -1) { + const parts = external.split('id='); + mid = parts[1] ? decodeURIComponent(parts[1]) : ''; } else { - fullUrl = `/api/version/media-url.php?url=${encodeURIComponent(msg.media_url_external)}`; + // Buscar parámetro mid en la query string (ej: ?mid=12345&...) + const m = external.match(/[?&]mid=([^&]+)/i); + if (m && m[1]) { + mid = decodeURIComponent(m[1]); + } + } + + if (mid) { + fullUrl = `/api/version/media-url.php?id=${encodeURIComponent(mid)}`; + } else if (/^https?:\/\//i.test(external)) { + fullUrl = `/api/version/media-url.php?url=${encodeURIComponent(external)}`; + } else { + fullUrl = `/api/version/media-url.php?url=${encodeURIComponent(external)}`; } } else { // No usar msg.media_url como fuente principal para apertura; dejar vacío para forzar 'no disponible'