up
This commit is contained in:
@@ -0,0 +1,90 @@
|
||||
<?php
|
||||
/**
|
||||
* API - Ver estado de la cola de media (media_queue)
|
||||
* GET /api/media_queue_status.php - Lista la cola
|
||||
* GET /api/media_queue_status.php?action=retry&id=123 - Reintenta un item
|
||||
* GET /api/media_queue_status.php?action=stats - Resumen rápido
|
||||
*/
|
||||
require_once __DIR__ . '/../config/config.php';
|
||||
|
||||
error_reporting(E_ALL);
|
||||
ini_set('display_errors', 0);
|
||||
|
||||
requireAuthentication();
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
$db = Database::getInstance();
|
||||
$action = $_GET['action'] ?? 'list';
|
||||
|
||||
try {
|
||||
switch ($action) {
|
||||
case 'stats':
|
||||
$stats = $db->fetchAll("SELECT status, COUNT(*) as total FROM media_queue GROUP BY status");
|
||||
$result = ['pending' => 0, 'processing' => 0, 'done' => 0, 'failed' => 0, 'permanently_failed' => 0];
|
||||
foreach ($stats as $s) {
|
||||
$result[$s['status']] = intval($s['total']);
|
||||
}
|
||||
$result['total'] = array_sum($result);
|
||||
|
||||
// Últimos 5 errores
|
||||
$lastErrors = $db->fetchAll("SELECT id, media_id, media_url, status, attempts, result, created_at FROM media_queue WHERE status IN ('failed','permanently_failed') ORDER BY created_at DESC LIMIT 5");
|
||||
|
||||
echo json_encode(['success' => true, 'stats' => $result, 'last_errors' => $lastErrors], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
|
||||
break;
|
||||
|
||||
case 'retry':
|
||||
$id = intval($_GET['id'] ?? 0);
|
||||
if (!$id) {
|
||||
echo json_encode(['success' => false, 'error' => 'Se requiere id']);
|
||||
exit;
|
||||
}
|
||||
$db->query("UPDATE media_queue SET status = 'pending', attempts = 0, result = NULL WHERE id = :id", ['id' => $id]);
|
||||
echo json_encode(['success' => true, 'message' => "Item #{$id} marcado para reintento"]);
|
||||
break;
|
||||
|
||||
case 'retry_all':
|
||||
$db->query("UPDATE media_queue SET status = 'pending', attempts = 0, result = NULL WHERE status IN ('failed', 'permanently_failed')");
|
||||
$affected = $db->fetch("SELECT ROW_COUNT() as cnt");
|
||||
echo json_encode(['success' => true, 'message' => "Todos los fallidos marcados para reintento"]);
|
||||
break;
|
||||
|
||||
case 'list':
|
||||
default:
|
||||
$status = $_GET['status'] ?? null;
|
||||
$limit = min(intval($_GET['limit'] ?? 50), 200);
|
||||
|
||||
$where = '';
|
||||
$params = [];
|
||||
if ($status) {
|
||||
$where = ' WHERE status = :status';
|
||||
$params['status'] = $status;
|
||||
}
|
||||
|
||||
$rows = $db->fetchAll(
|
||||
"SELECT mq.*, c.message_type, c.filename as conv_filename, c.content as conv_content
|
||||
FROM media_queue mq
|
||||
LEFT JOIN conversations c ON c.id = mq.conversation_id
|
||||
{$where}
|
||||
ORDER BY mq.created_at DESC
|
||||
LIMIT {$limit}",
|
||||
$params
|
||||
);
|
||||
|
||||
// Stats rápidas
|
||||
$statsRaw = $db->fetchAll("SELECT status, COUNT(*) as total FROM media_queue GROUP BY status");
|
||||
$stats = [];
|
||||
foreach ($statsRaw as $s) $stats[$s['status']] = intval($s['total']);
|
||||
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'stats' => $stats,
|
||||
'count' => count($rows),
|
||||
'items' => $rows
|
||||
], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
|
||||
break;
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
http_response_code(500);
|
||||
echo json_encode(['success' => false, 'error' => $e->getMessage()]);
|
||||
}
|
||||
@@ -189,17 +189,33 @@ try {
|
||||
|
||||
if ($isLocalUrl) {
|
||||
error_log("send_media_message.php - URL local detectada, subiendo archivo a WhatsApp primero");
|
||||
set_time_limit(300); // Tiempo extra para archivos grandes
|
||||
|
||||
// Obtener la ruta local del archivo
|
||||
$parsedUrl = parse_url($mediaUrl);
|
||||
$relativePath = $parsedUrl['path'];
|
||||
|
||||
// Construir ruta absoluta
|
||||
$uploadDir = __DIR__ . '/../uploads/';
|
||||
$filename_from_url = basename($relativePath);
|
||||
$localFilePath = $uploadDir . $filename_from_url;
|
||||
// Construir ruta absoluta usando el path completo (no solo basename)
|
||||
$docRoot = __DIR__ . '/..';
|
||||
$localFilePath = $docRoot . $relativePath;
|
||||
|
||||
error_log("send_media_message.php - Ruta local del archivo: " . $localFilePath);
|
||||
// Si no existe, intentar con solo el basename en uploads/
|
||||
if (!file_exists($localFilePath)) {
|
||||
$uploadDir = __DIR__ . '/../uploads/';
|
||||
$localFilePath = $uploadDir . basename($relativePath);
|
||||
}
|
||||
|
||||
// Último intento: buscar recursivamente en uploads/
|
||||
if (!file_exists($localFilePath)) {
|
||||
$searchName = basename($relativePath);
|
||||
$found = glob(__DIR__ . '/../uploads/**/' . $searchName);
|
||||
if (!empty($found)) {
|
||||
$localFilePath = $found[0];
|
||||
}
|
||||
}
|
||||
|
||||
error_log("send_media_message.php - Ruta local del archivo: " . $localFilePath . " - existe: " . (file_exists($localFilePath) ? 'SI' : 'NO') . " - size: " . (file_exists($localFilePath) ? filesize($localFilePath) : 'N/A'));
|
||||
smm_log("Local file: {$localFilePath} exists=" . (file_exists($localFilePath) ? 'yes' : 'no') . " size=" . (file_exists($localFilePath) ? filesize($localFilePath) : 'N/A'));
|
||||
|
||||
if (!file_exists($localFilePath)) {
|
||||
throw new Exception('Archivo no encontrado en el servidor: ' . $localFilePath);
|
||||
|
||||
+160
-202
@@ -55,6 +55,123 @@ $log_write_failed = false;
|
||||
$resolvedFromGraph = false;
|
||||
$decoded = null;
|
||||
|
||||
/**
|
||||
* Stream a remote file directly to the browser without buffering in memory.
|
||||
* Solves timeout and memory issues with large videos/PDFs.
|
||||
*/
|
||||
function streamRemoteFile($url, $token = '', $forceDownload = false, $suggestedFilename = null) {
|
||||
set_time_limit(300);
|
||||
while (ob_get_level()) @ob_end_clean();
|
||||
|
||||
$curlHeaders = ['User-Agent: WhatsAppMediaProxy/1.0', 'Accept: */*'];
|
||||
if ($token) $curlHeaders[] = 'Authorization: Bearer ' . $token;
|
||||
|
||||
$respHeaders = [];
|
||||
$browserHeadersSent = false;
|
||||
$isError = false;
|
||||
$errorBody = '';
|
||||
|
||||
$ch = curl_init();
|
||||
curl_setopt_array($ch, [
|
||||
CURLOPT_URL => $url,
|
||||
CURLOPT_FOLLOWLOCATION => true,
|
||||
CURLOPT_TIMEOUT => 300,
|
||||
CURLOPT_CONNECTTIMEOUT => 30,
|
||||
CURLOPT_BUFFERSIZE => 65536,
|
||||
CURLOPT_HTTPHEADER => $curlHeaders,
|
||||
CURLOPT_HEADERFUNCTION => function($ch, $headerLine) use (&$respHeaders) {
|
||||
$len = strlen($headerLine);
|
||||
$parts = explode(':', $headerLine, 2);
|
||||
if (count($parts) == 2) {
|
||||
$respHeaders[strtolower(trim($parts[0]))] = trim($parts[1]);
|
||||
}
|
||||
return $len;
|
||||
},
|
||||
CURLOPT_WRITEFUNCTION => function($ch, $data) use (&$browserHeadersSent, &$respHeaders, &$isError, &$errorBody, $forceDownload, $suggestedFilename) {
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
|
||||
// Buffer error responses
|
||||
if ($httpCode >= 400) {
|
||||
$isError = true;
|
||||
$errorBody .= $data;
|
||||
return strlen($data);
|
||||
}
|
||||
|
||||
// Detect JSON error responses
|
||||
if (!$browserHeadersSent) {
|
||||
$ct = $respHeaders['content-type'] ?? curl_getinfo($ch, CURLINFO_CONTENT_TYPE) ?? '';
|
||||
if (stripos($ct, 'application/json') !== false) {
|
||||
$isError = true;
|
||||
$errorBody .= $data;
|
||||
return strlen($data);
|
||||
}
|
||||
}
|
||||
|
||||
// Send browser headers on first data chunk
|
||||
if (!$browserHeadersSent) {
|
||||
$browserHeadersSent = true;
|
||||
$contentType = $respHeaders['content-type'] ?? curl_getinfo($ch, CURLINFO_CONTENT_TYPE) ?? 'application/octet-stream';
|
||||
$contentLength = $respHeaders['content-length'] ?? null;
|
||||
|
||||
header_remove('Content-Type');
|
||||
header('Cache-Control: public, max-age=86400');
|
||||
header('Content-Type: ' . $contentType);
|
||||
if ($contentLength) header('Content-Length: ' . $contentLength);
|
||||
|
||||
if ($forceDownload) {
|
||||
$filename = $suggestedFilename;
|
||||
if (!$filename) {
|
||||
$cd = $respHeaders['content-disposition'] ?? null;
|
||||
if ($cd) {
|
||||
if (preg_match("/filename\\*=(?:[^']*'')?(.+)$/i", $cd, $m)) {
|
||||
$filename = rawurldecode(trim($m[1], " \"'\r\n"));
|
||||
} elseif (preg_match('/filename="?([^";]+)"?/i', $cd, $m)) {
|
||||
$filename = trim($m[1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!$filename) {
|
||||
$path = parse_url(curl_getinfo($ch, CURLINFO_EFFECTIVE_URL) ?: '', PHP_URL_PATH) ?: '';
|
||||
$filename = basename($path) ?: 'download';
|
||||
if (strpos($filename, '.') === false) {
|
||||
$mimeMap = ['application/pdf'=>'.pdf','image/jpeg'=>'.jpg','image/png'=>'.png','video/mp4'=>'.mp4','audio/mpeg'=>'.mp3','audio/ogg'=>'.ogg'];
|
||||
$baseCt = strtolower(trim(explode(';', $contentType)[0]));
|
||||
if (isset($mimeMap[$baseCt])) $filename .= $mimeMap[$baseCt];
|
||||
}
|
||||
}
|
||||
header('Content-Disposition: attachment; filename="' . $filename . '"');
|
||||
}
|
||||
}
|
||||
|
||||
echo $data;
|
||||
flush();
|
||||
return strlen($data);
|
||||
},
|
||||
]);
|
||||
|
||||
curl_exec($ch);
|
||||
$err = curl_error($ch);
|
||||
$finalHttpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
curl_close($ch);
|
||||
|
||||
if ($err || $isError) {
|
||||
if (!$browserHeadersSent) {
|
||||
http_response_code(502);
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
if ($isError && $errorBody) {
|
||||
$decoded = json_decode($errorBody, true);
|
||||
echo json_encode(['success' => false, 'error' => 'Remote server error', 'http_code' => $finalHttpCode, 'remote' => $decoded]);
|
||||
} else {
|
||||
echo json_encode(['success' => false, 'error' => 'Error streaming file', 'detail' => $err]);
|
||||
}
|
||||
}
|
||||
media_log("streamRemoteFile failed: url={$url} err={$err} http={$finalHttpCode}");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Log de petición inicial
|
||||
media_log("Request: " . ($_SERVER['REQUEST_METHOD'] ?? 'GET') . " " . ($_SERVER['REQUEST_URI'] ?? '') . " GET:" . json_encode($_GET) . " POST:" . json_encode($_POST));
|
||||
|
||||
@@ -102,6 +219,40 @@ if (!$mediaId && !$providedUrl) {
|
||||
}
|
||||
|
||||
// ── PRIORIDAD: Buscar archivo local en BD antes de consultar Graph API ──
|
||||
// También buscar por URL completa (para videos/docs enviados como URL lookaside)
|
||||
if ($providedUrl) {
|
||||
try {
|
||||
$db = Database::getInstance();
|
||||
$row = $db->fetch(
|
||||
"SELECT local_file, local_thumb, mime_type, filename FROM conversations WHERE media_url = :url AND local_file IS NOT NULL AND local_file != '' ORDER BY id DESC LIMIT 1",
|
||||
['url' => $providedUrl]
|
||||
);
|
||||
if ($row && !empty($row['local_file'])) {
|
||||
$localPath = realpath(__DIR__ . '/../../' . ltrim($row['local_file'], '/'));
|
||||
$base = realpath(__DIR__ . '/../../uploads');
|
||||
if ($localPath && strpos($localPath, $base) === 0 && file_exists($localPath)) {
|
||||
media_log("Serving local file for URL {$providedUrl}: {$localPath}");
|
||||
$finfo = finfo_open(FILEINFO_MIME_TYPE);
|
||||
$ctype = finfo_file($finfo, $localPath) ?: ($row['mime_type'] ?? 'application/octet-stream');
|
||||
finfo_close($finfo);
|
||||
|
||||
header_remove('Content-Type');
|
||||
header('Cache-Control: public, max-age=86400');
|
||||
header('Content-Type: ' . $ctype);
|
||||
header('Content-Length: ' . filesize($localPath));
|
||||
if ($download) {
|
||||
$fname = $row['filename'] ?: basename($localPath);
|
||||
header('Content-Disposition: attachment; filename="' . $fname . '"');
|
||||
}
|
||||
readfile($localPath);
|
||||
exit;
|
||||
}
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
media_log("Error buscando local_file por URL: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
if ($mediaId) {
|
||||
try {
|
||||
$db = Database::getInstance();
|
||||
@@ -538,169 +689,19 @@ if ($download) {
|
||||
exit;
|
||||
}
|
||||
|
||||
// Si no es local, hacer curl normal (incluye Authorization si aplica)
|
||||
$ch = curl_init();
|
||||
// Añadir Authorization si existe token en configuración
|
||||
// Streaming proxy para descargas (soporta archivos grandes sin buffering en memoria)
|
||||
$token = getConfigFromDB('whatsapp_token', '');
|
||||
$headers = ['User-Agent: WhatsAppMediaProxy/1.0', 'Accept: */*'];
|
||||
if ($token) $headers[] = 'Authorization: Bearer ' . $token;
|
||||
|
||||
// Pedimos cabeceras + body para poder extraer Content-Disposition
|
||||
curl_setopt_array($ch, [
|
||||
CURLOPT_URL => $mediaUrl,
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_FOLLOWLOCATION => true,
|
||||
CURLOPT_TIMEOUT => 60,
|
||||
CURLOPT_HTTPHEADER => $headers,
|
||||
CURLOPT_HEADER => true,
|
||||
]);
|
||||
$response = 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;
|
||||
}
|
||||
|
||||
// Si el servidor remoto devolvió un código HTTP de error, devolverlo al cliente
|
||||
$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;
|
||||
}
|
||||
|
||||
$headerSize = isset($info['header_size']) ? (int)$info['header_size'] : 0;
|
||||
$headersText = $headerSize ? substr($response, 0, $headerSize) : '';
|
||||
$body = $headerSize ? substr($response, $headerSize) : $response;
|
||||
|
||||
// Parsear cabeceras simples
|
||||
$parsed = [];
|
||||
if ($headersText) {
|
||||
$lines = preg_split('/\r?\n/', $headersText);
|
||||
foreach ($lines as $line) {
|
||||
if (strpos($line, ':') !== false) {
|
||||
list($n, $v) = explode(':', $line, 2);
|
||||
$parsed[strtolower(trim($n))] = trim($v);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$contentType = $parsed['content-type'] ?? ($info['content_type'] ?? null);
|
||||
|
||||
// Si la respuesta es JSON, retornar el error en JSON (evitar descargar JSON como archivo)
|
||||
if ($contentType && stripos($contentType, 'application/json') !== false) {
|
||||
$decoded = json_decode($body, true);
|
||||
http_response_code($decoded && isset($decoded['error']) ? 502 : 500);
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
echo json_encode(['success' => false, 'error' => 'Remote server returned JSON instead of file', 'remote' => $decoded]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Determinar filename desde Content-Disposition o URL como fallback
|
||||
$filename = null;
|
||||
$cd = $parsed['content-disposition'] ?? null;
|
||||
if ($cd) {
|
||||
// Usar comillas dobles para el regex que contiene apóstrofes
|
||||
if (preg_match("/filename\\*=(?:[^']*'')?(.+)$/i", $cd, $m)) {
|
||||
$filename = trim($m[1], " \"'\r\n");
|
||||
$filename = rawurldecode($filename);
|
||||
} elseif (preg_match('/filename="?([^";]+)"?/i', $cd, $m)) {
|
||||
$filename = trim($m[1]);
|
||||
}
|
||||
}
|
||||
|
||||
if (!$filename) {
|
||||
$path = parse_url($mediaUrl, PHP_URL_PATH) ?: '';
|
||||
$basename = basename($path);
|
||||
if ($basename && $basename !== '/') {
|
||||
$filename = $basename;
|
||||
}
|
||||
}
|
||||
|
||||
// Si aún no hay filename, intentar deducir extensión desde content-type
|
||||
if (!$filename) {
|
||||
$ext = '';
|
||||
$mimeMap = [
|
||||
'application/pdf' => '.pdf',
|
||||
'text/csv' => '.csv',
|
||||
'image/jpeg' => '.jpg',
|
||||
'image/png' => '.png',
|
||||
'application/zip' => '.zip',
|
||||
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' => '.xlsx',
|
||||
'application/vnd.ms-excel' => '.xls',
|
||||
];
|
||||
if ($contentType && isset($mimeMap[strtolower($contentType)])) {
|
||||
$ext = $mimeMap[strtolower($contentType)];
|
||||
}
|
||||
$filename = 'download' . $ext;
|
||||
}
|
||||
|
||||
if ($body === null || $body === '') {
|
||||
http_response_code(502);
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
echo json_encode(['success' => false, 'error' => 'Empty response body from remote']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Enviar body como descarga
|
||||
header_remove('Content-Type');
|
||||
header('Cache-Control: public, max-age=3600');
|
||||
if (!empty($contentType)) header('Content-Type: ' . $contentType);
|
||||
header('Content-Length: ' . strlen($body));
|
||||
header('Content-Disposition: attachment; filename="' . $filename . '"');
|
||||
echo $body;
|
||||
media_log("Streaming download for: {$mediaUrl}");
|
||||
streamRemoteFile($mediaUrl, $token, true);
|
||||
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.
|
||||
// Por defecto: si la URL fue resuelta mediante Graph API, streaming proxy
|
||||
// (evita buffering en memoria para archivos grandes como videos)
|
||||
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;
|
||||
media_log("Streaming Graph-resolved media: {$mediaUrl}");
|
||||
streamRemoteFile($mediaUrl, $token);
|
||||
exit;
|
||||
}
|
||||
|
||||
@@ -714,51 +715,8 @@ if ($token && ($host === 'lookaside.fbsbx.com' || stripos($host, 'graph.facebook
|
||||
}
|
||||
|
||||
if ($shouldProxy) {
|
||||
media_log("Proxying Facebook media URL with Authorization to {$mediaUrl}");
|
||||
$ch = curl_init();
|
||||
$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,
|
||||
CURLOPT_HEADER => true,
|
||||
]);
|
||||
$response = 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;
|
||||
}
|
||||
|
||||
$headerSize = isset($info['header_size']) ? (int)$info['header_size'] : 0;
|
||||
$headersText = $headerSize ? substr($response, 0, $headerSize) : '';
|
||||
$body = $headerSize ? substr($response, $headerSize) : $response;
|
||||
|
||||
// Parsear content-type desde headers devueltos
|
||||
$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;
|
||||
media_log("Streaming Facebook media with Authorization: {$mediaUrl}");
|
||||
streamRemoteFile($mediaUrl, $token);
|
||||
exit;
|
||||
}
|
||||
|
||||
|
||||
@@ -446,7 +446,8 @@ class WhatsAppService
|
||||
CURLOPT_HTTPHEADER => [
|
||||
'Authorization: Bearer ' . $this->token
|
||||
],
|
||||
CURLOPT_TIMEOUT => 120
|
||||
CURLOPT_TIMEOUT => 300,
|
||||
CURLOPT_CONNECTTIMEOUT => 30
|
||||
]);
|
||||
|
||||
$response = curl_exec($ch);
|
||||
|
||||
Reference in New Issue
Block a user