false, 'error' => 'Se requiere id o url']); exit; } $mediaUrl = null; if ($providedUrl) { $mediaUrl = $providedUrl; } else { $token = getConfigFromDB('whatsapp_token', ''); $apiUrl = rtrim(getConfigFromDB('whatsapp_api_url', 'https://graph.facebook.com/v22.0/'), '/'); $endpoint = "{$apiUrl}/{$mediaId}"; $ch = curl_init(); curl_setopt_array($ch, [ CURLOPT_URL => $endpoint, CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => [ 'Authorization: Bearer ' . $token ], CURLOPT_TIMEOUT => 15, ]); $response = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); $err = curl_error($ch); curl_close($ch); if ($err) { http_response_code(502); echo json_encode(['success' => false, 'error' => 'Error comunicando con Graph API', 'detail' => $err]); exit; } $decoded = json_decode($response, true); if ($httpCode >= 400 || !$decoded) { http_response_code($httpCode ?: 500); echo json_encode(['success' => false, 'error' => 'Graph API error', 'response' => $decoded]); exit; } $mediaUrl = $decoded['url'] ?? ($decoded['data'][0]['url'] ?? null); if (!$mediaUrl) { http_response_code(404); echo json_encode(['success' => false, 'error' => 'No se recibió URL del media', 'raw' => $decoded]); exit; } } // Soporte para archivos locales (local=uploads/media/...) $local = $_GET['local'] ?? null; if ($local) { $localPath = realpath(__DIR__ . '/../../' . ltrim($local, '/')); $base = realpath(__DIR__ . '/../../uploads/media'); // Prevención simple de path traversal if (!$localPath || strpos($localPath, $base) !== 0) { http_response_code(400); echo json_encode(['success' => false, 'error' => 'Ruta local inválida']); exit; } if (!file_exists($localPath)) { http_response_code(404); echo json_encode(['success' => false, 'error' => 'Archivo no encontrado']); exit; } if ($wantsJson) { // devolver URL pública directa (relative) echo json_encode(['success' => true, 'url' => '/' . ltrim($local, '/')]); exit; } if ($download) { // Forzar descarga del archivo local header('Cache-Control: public, max-age=3600'); $finfo = finfo_open(FILEINFO_MIME_TYPE); $ctype = finfo_file($finfo, $localPath); finfo_close($finfo); header('Content-Type: ' . $ctype); header('Content-Length: ' . filesize($localPath)); header('Content-Disposition: attachment; filename="' . basename($localPath) . '"'); readfile($localPath); exit; } // Redirigir al archivo local (servidor web lo servirá) header('Cache-Control: public, max-age=3600'); header('Location: /' . ltrim($local, '/'), true, 302); exit; } // Si se solicitó JSON, devolver la URL if ($wantsJson) { echo json_encode(['success' => true, 'url' => $mediaUrl]); exit; } // Si se pidió proxy (download=1), traer el contenido y retornarlo como descarga if ($download) { $ch = curl_init(); // Añadir Authorization si existe token en configuración $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, ]); $content = 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', 'detail' => $err]); exit; } // Cabeceras para descarga header_remove('Content-Type'); header('Cache-Control: public, max-age=3600'); if (!empty($info['content_type'])) header('Content-Type: ' . $info['content_type']); if (!empty($info['size_download'])) header('Content-Length: ' . $info['size_download']); $filename = basename(parse_url($mediaUrl, PHP_URL_PATH) ?: 'file'); header('Content-Disposition: attachment; filename="' . $filename . '"'); echo $content; 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); exit;