diff --git a/api/file_request_upload.php b/api/file_request_upload.php new file mode 100644 index 0000000..f7bce76 --- /dev/null +++ b/api/file_request_upload.php @@ -0,0 +1,327 @@ + false, 'error' => 'Método no permitido']); + exit; +} + +try { + $token = $_POST['token'] ?? ''; + + // Validar token + if (empty($token) || !preg_match('/^[a-zA-Z0-9]{32,64}$/', $token)) { + http_response_code(400); + echo json_encode(['success' => false, 'error' => 'Token no válido']); + exit; + } + + // Validar archivo + if (!isset($_FILES['file']) || $_FILES['file']['error'] !== UPLOAD_ERR_OK) { + $errorMsg = 'No se recibió el archivo'; + if (isset($_FILES['file'])) { + $uploadErrors = [ + UPLOAD_ERR_INI_SIZE => 'El archivo excede el tamaño máximo del servidor', + UPLOAD_ERR_FORM_SIZE => 'El archivo excede el tamaño máximo del formulario', + UPLOAD_ERR_PARTIAL => 'El archivo se subió parcialmente', + UPLOAD_ERR_NO_FILE => 'No se envió ningún archivo', + UPLOAD_ERR_NO_TMP_DIR => 'Falta la carpeta temporal del servidor', + UPLOAD_ERR_CANT_WRITE => 'No se pudo escribir el archivo en disco', + ]; + $errorMsg = $uploadErrors[$_FILES['file']['error']] ?? 'Error de subida desconocido'; + } + http_response_code(400); + echo json_encode(['success' => false, 'error' => $errorMsg]); + exit; + } + + $db = Database::getInstance(); + + // Buscar solicitud por token + $request = $db->fetch( + "SELECT fr.*, u.name as user_name, u.phone_number as user_phone + FROM file_requests fr + LEFT JOIN users u ON fr.user_id = u.id + WHERE fr.token = ?", + [$token] + ); + + if (!$request) { + http_response_code(404); + echo json_encode(['success' => false, 'error' => 'Solicitud no encontrada']); + exit; + } + + // Verificar expiración + if (strtotime($request['expires_at']) < time()) { + http_response_code(410); + echo json_encode(['success' => false, 'error' => 'El enlace ha expirado']); + exit; + } + + // Verificar estado + if ($request['status'] === 'cancelled') { + http_response_code(410); + echo json_encode(['success' => false, 'error' => 'La solicitud fue cancelada']); + exit; + } + + $file = $_FILES['file']; + $maxSize = intval($request['max_file_size']); + + // Validar tamaño + if ($file['size'] > $maxSize) { + $maxMB = round($maxSize / (1024 * 1024)); + http_response_code(413); + echo json_encode(['success' => false, 'error' => "El archivo excede el límite de {$maxMB} MB"]); + exit; + } + + // Detectar MIME type + $finfo = finfo_open(FILEINFO_MIME_TYPE); + $mimeType = finfo_file($finfo, $file['tmp_name']); + finfo_close($finfo); + + if (!$mimeType) { + $mimeType = $file['type'] ?: 'application/octet-stream'; + } + + // Clasificar tipo de media + $mediaType = 'other'; + if (strpos($mimeType, 'image/') === 0) $mediaType = 'image'; + elseif (strpos($mimeType, 'video/') === 0) $mediaType = 'video'; + elseif (strpos($mimeType, 'audio/') === 0) $mediaType = 'audio'; + elseif (strpos($mimeType, 'application/') === 0 || strpos($mimeType, 'text/') === 0) $mediaType = 'document'; + + // Validar tipo permitido + $allowedTypes = explode(',', $request['allowed_types']); + $typeAllowed = in_array($mediaType, $allowedTypes) || in_array('other', $allowedTypes); + if (!$typeAllowed && $mediaType === 'other') { + // Dar paso a "other" si document está permitido + $typeAllowed = in_array('document', $allowedTypes); + } + if (!$typeAllowed) { + http_response_code(400); + echo json_encode(['success' => false, 'error' => 'Tipo de archivo no permitido']); + exit; + } + + // Crear directorio de destino + $uploadDir = __DIR__ . '/../uploads/file_requests/' . date('Y') . '/' . date('m'); + if (!is_dir($uploadDir)) { + mkdir($uploadDir, 0755, true); + } + + // Generar nombre único + $ext = pathinfo($file['name'], PATHINFO_EXTENSION) ?: 'bin'; + $ext = strtolower(preg_replace('/[^a-zA-Z0-9]/', '', $ext)); + $storedName = 'fr_' . $request['id'] . '_' . uniqid() . '.' . $ext; + $destPath = $uploadDir . '/' . $storedName; + $relativePath = 'uploads/file_requests/' . date('Y') . '/' . date('m') . '/' . $storedName; + + // Mover archivo + if (!move_uploaded_file($file['tmp_name'], $destPath)) { + http_response_code(500); + echo json_encode(['success' => false, 'error' => 'No se pudo guardar el archivo en disco']); + exit; + } + + // Generar thumbnail para imágenes + $thumbnailPath = null; + if ($mediaType === 'image' && class_exists('GdImage') || function_exists('imagecreatefromjpeg')) { + try { + $thumbName = 'thumb_' . $storedName; + $thumbDest = $uploadDir . '/' . $thumbName; + if (createThumbnail($destPath, $thumbDest, 200, 200)) { + $thumbnailPath = 'uploads/file_requests/' . date('Y') . '/' . date('m') . '/' . $thumbName; + } + } catch (Exception $e) { + // Thumbnail falla silenciosamente + error_log('Thumbnail error: ' . $e->getMessage()); + } + } + + // Guardar en BD - tabla file_request_uploads + $uploadId = $db->insert('file_request_uploads', [ + 'request_id' => $request['id'], + 'original_filename' => $file['name'], + 'stored_filename' => $storedName, + 'file_path' => $relativePath, + 'file_size' => $file['size'], + 'mime_type' => $mimeType, + 'media_type' => $mediaType, + 'thumbnail_path' => $thumbnailPath, + ]); + + // Actualizar estado de la solicitud a "uploaded" + $db->update('file_requests', ['status' => 'uploaded'], 'id = ?', [$request['id']]); + + // Generar URL pública del archivo + $protocol = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? 'https' : 'http'; + $appUrl = $protocol . '://' . $_SERVER['HTTP_HOST']; + $fileUrl = $appUrl . '/' . $relativePath; + $thumbUrl = $thumbnailPath ? ($appUrl . '/' . $thumbnailPath) : null; + + // Insertar como mensaje en la conversación (para que el operador lo vea en el chat) + $db->insert('conversations', [ + 'user_id' => $request['user_id'], + 'direction' => 'incoming', + 'message_type' => $mediaType === 'other' ? 'document' : $mediaType, + 'content' => '📎 Archivo recibido via enlace: ' . $file['name'], + 'media_url' => $fileUrl, + 'local_file' => $relativePath, + 'local_thumb' => $thumbnailPath, + 'status' => 'received', + 'is_read' => 0, + 'filename' => $file['name'], + 'mime_type' => $mimeType, + 'created_at' => date('Y-m-d H:i:s'), + ]); + + // Crear notificación para el operador + $userName = $request['user_name'] ?: $request['phone_number']; + $db->insert('notifications', [ + 'user_id' => $request['user_id'], + 'type' => 'file_uploaded', + 'message' => "📎 {$userName} subió un archivo: {$file['name']}", + 'data' => json_encode([ + 'request_id' => $request['id'], + 'upload_id' => $uploadId, + 'filename' => $file['name'], + 'file_url' => $fileUrl, + 'media_type' => $mediaType, + 'file_size' => $file['size'], + ]), + 'is_read' => 0, + ]); + + // Escribir evento SSE para actualización en tiempo real + try { + $eventsFile = __DIR__ . '/../uploads/events_global.json'; + $events = []; + if (file_exists($eventsFile)) { + $raw = file_get_contents($eventsFile); + $events = json_decode($raw, true) ?: []; + } + $events[] = [ + 'type' => 'file_uploaded', + 'user_id' => $request['user_id'], + 'data' => [ + 'request_id' => $request['id'], + 'upload_id' => $uploadId, + 'filename' => $file['name'], + 'file_url' => $fileUrl, + 'thumb_url' => $thumbUrl, + 'media_type' => $mediaType, + 'file_size' => $file['size'], + 'user_name' => $userName, + ], + 'timestamp' => time(), + ]; + file_put_contents($eventsFile, json_encode($events)); + } catch (Exception $e) { + error_log('Error escribiendo evento SSE: ' . $e->getMessage()); + } + + echo json_encode([ + 'success' => true, + 'file_id' => $uploadId, + 'filename' => $file['name'], + 'file_url' => $fileUrl, + 'media_type' => $mediaType, + 'file_size' => $file['size'], + ]); + +} catch (Exception $e) { + $errorDetail = date('[Y-m-d H:i:s] ') . 'file_request_upload ERROR: ' . $e->getMessage() . "\n" . $e->getTraceAsString() . "\n\n"; + error_log('Error en file_request_upload: ' . $e->getMessage() . ' | Trace: ' . $e->getTraceAsString()); + @file_put_contents(__DIR__ . '/../logs/app_errors.log', $errorDetail, FILE_APPEND); + http_response_code(500); + echo json_encode(['success' => false, 'error' => 'Error interno del servidor']); +} + + +/** + * Crear thumbnail de una imagen usando GD + */ +function createThumbnail($source, $dest, $maxW = 200, $maxH = 200) { + $info = @getimagesize($source); + if (!$info) return false; + + $mime = $info['mime']; + $origW = $info[0]; + $origH = $info[1]; + + switch ($mime) { + case 'image/jpeg': $img = @imagecreatefromjpeg($source); break; + case 'image/png': $img = @imagecreatefrompng($source); break; + case 'image/gif': $img = @imagecreatefromgif($source); break; + case 'image/webp': $img = @imagecreatefromwebp($source); break; + default: return false; + } + + if (!$img) return false; + + // Calcular dimensiones + $ratio = min($maxW / $origW, $maxH / $origH); + if ($ratio >= 1) { + // La imagen ya es más pequeña que el thumb + imagedestroy($img); + return copy($source, $dest); + } + + $newW = intval($origW * $ratio); + $newH = intval($origH * $ratio); + + $thumb = imagecreatetruecolor($newW, $newH); + + // Preservar transparencia para PNG/GIF + if ($mime === 'image/png' || $mime === 'image/gif') { + imagealphablending($thumb, false); + imagesavealpha($thumb, true); + $transparent = imagecolorallocatealpha($thumb, 0, 0, 0, 127); + imagefilledrectangle($thumb, 0, 0, $newW, $newH, $transparent); + } + + imagecopyresampled($thumb, $img, 0, 0, 0, 0, $newW, $newH, $origW, $origH); + + // Guardar como JPEG (más ligero para thumbs) + $result = imagejpeg($thumb, $dest, 85); + + imagedestroy($img); + imagedestroy($thumb); + + return $result; +} diff --git a/api/get_file_requests.php b/api/get_file_requests.php new file mode 100644 index 0000000..142ce70 --- /dev/null +++ b/api/get_file_requests.php @@ -0,0 +1,94 @@ + false, 'error' => 'Método no permitido']); + exit; +} + +try { + $userId = intval($_GET['user_id'] ?? 0); + $status = trim($_GET['status'] ?? ''); + + if (!$userId) { + http_response_code(400); + echo json_encode(['success' => false, 'error' => 'Se requiere user_id']); + exit; + } + + $db = Database::getInstance(); + + // Obtener solicitudes + $sql = "SELECT fr.*, + CASE WHEN fr.expires_at < NOW() AND fr.status = 'pending' THEN 'expired' ELSE fr.status END as effective_status, + (SELECT COUNT(*) FROM file_request_uploads fru WHERE fru.request_id = fr.id) as upload_count + FROM file_requests fr + WHERE fr.user_id = ?"; + $params = [$userId]; + + if (!empty($status)) { + if ($status === 'active') { + $sql .= " AND fr.status IN ('pending','uploaded') AND fr.expires_at > NOW()"; + } else { + $sql .= " AND fr.status = ?"; + $params[] = $status; + } + } + + $sql .= " ORDER BY fr.created_at DESC LIMIT 20"; + $requests = $db->fetchAll($sql, $params); + + // Obtener uploads de las solicitudes + $uploads = []; + $requestIds = array_column($requests, 'id'); + if (!empty($requestIds)) { + $placeholders = implode(',', array_fill(0, count($requestIds), '?')); + $uploads = $db->fetchAll( + "SELECT fru.*, fr.token as request_token + FROM file_request_uploads fru + JOIN file_requests fr ON fru.request_id = fr.id + WHERE fru.request_id IN ({$placeholders}) + ORDER BY fru.created_at DESC", + $requestIds + ); + + // Agregar URLs públicas + $protocol = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? 'https' : 'http'; + $appUrl = $protocol . '://' . $_SERVER['HTTP_HOST']; + foreach ($uploads as &$upload) { + $upload['file_url'] = $appUrl . '/' . $upload['file_path']; + $upload['thumb_url'] = $upload['thumbnail_path'] + ? $appUrl . '/' . $upload['thumbnail_path'] + : null; + } + } + + echo json_encode([ + 'success' => true, + 'requests' => $requests, + 'uploads' => $uploads, + ]); + +} catch (Exception $e) { + error_log('Error en get_file_requests: ' . $e->getMessage()); + http_response_code(500); + echo json_encode(['success' => false, 'error' => 'Error interno']); +} diff --git a/api/request_large_file.php b/api/request_large_file.php new file mode 100644 index 0000000..186707a --- /dev/null +++ b/api/request_large_file.php @@ -0,0 +1,156 @@ + false, 'error' => 'Método no permitido']); + exit; +} + +try { + $input = json_decode(file_get_contents('php://input'), true); + + $userId = intval($input['user_id'] ?? 0); + $phoneNumber = trim($input['phone_number'] ?? ''); + + if (!$userId || empty($phoneNumber)) { + http_response_code(400); + echo json_encode(['success' => false, 'error' => 'Se requiere user_id y phone_number']); + exit; + } + + $db = Database::getInstance(); + + // Verificar que el usuario existe + $user = $db->fetch("SELECT id, phone_number, name FROM users WHERE id = ?", [$userId]); + if (!$user) { + http_response_code(404); + echo json_encode(['success' => false, 'error' => 'Usuario no encontrado']); + exit; + } + + // Usar el teléfono de la BD si no se proporcionó + $phone = !empty($phoneNumber) ? $phoneNumber : $user['phone_number']; + + // Verificar si hay una solicitud pendiente activa (no expirada) + $existing = $db->fetch( + "SELECT id, token, expires_at FROM file_requests + WHERE user_id = ? AND status = 'pending' AND expires_at > NOW() + ORDER BY created_at DESC LIMIT 1", + [$userId] + ); + + if ($existing) { + // Reutilizar solicitud existente — reenviar enlace + $token = $existing['token']; + $requestId = $existing['id']; + } else { + // Generar nuevo token único (48 caracteres alfanuméricos) + $token = bin2hex(random_bytes(24)); + + // Operador que solicita + $requestedBy = $_SESSION['user_id'] ?? null; + + // Crear solicitud — expira en 24 horas + $expiresAt = date('Y-m-d H:i:s', strtotime('+24 hours')); + + $requestId = $db->insert('file_requests', [ + 'token' => $token, + 'user_id' => $userId, + 'phone_number' => $phone, + 'requested_by' => $requestedBy, + 'status' => 'pending', + 'max_file_size' => 52428800, // 50MB + 'allowed_types' => 'image,document', + 'expires_at' => $expiresAt, + ]); + } + + // Construir URL de carga + $protocol = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? 'https' : 'http'; + $appUrl = $protocol . '://' . $_SERVER['HTTP_HOST']; + $uploadUrl = $appUrl . '/upload.php?token=' . $token; + + // Enviar mensaje por WhatsApp con el enlace + $whatsapp = new WhatsAppService(); + + // Mensaje amigable con el enlace + $message = "📎 *Enlace para enviar archivos grandes*\n\n" + . "Hola" . ($user['name'] ? ", " . $user['name'] : "") . ".\n\n" + . "Le enviamos este enlace seguro para que pueda subir archivos de gran tamaño (hasta 50 MB):\n\n" + . "👉 " . $uploadUrl . "\n\n" + . "✅ Puede subir imágenes o documentos.\n" + . "🔒 El enlace es seguro y exclusivo para usted.\n" + . "⏰ Válido por 24 horas.\n\n" + . "_Si tiene alguna duda, estamos aquí para ayudarle._"; + + $sendResult = $whatsapp->sendTextMessage($phone, $message, [ + 'context_type' => 'file_request', + 'file_request_id' => $requestId, + ]); + + // Guardar ID del mensaje enviado + if ($sendResult && isset($sendResult['messages'][0]['id'])) { + $messageId = $sendResult['messages'][0]['id']; + $db->update('file_requests', + ['message_sent_id' => $messageId], + 'id = ?', [$requestId] + ); + } + + // Crear notificación para el log + $db->insert('notifications', [ + 'user_id' => $userId, + 'type' => 'file_request_sent', + 'message' => 'Se envió enlace de carga de archivos a ' . ($user['name'] ?: $phone), + 'data' => json_encode([ + 'request_id' => $requestId, + 'token' => $token, + 'upload_url' => $uploadUrl, + ]), + 'is_read' => 0, + ]); + + echo json_encode([ + 'success' => true, + 'request_id' => $requestId, + 'token' => $token, + 'upload_url' => $uploadUrl, + 'expires_at' => $existing ? $existing['expires_at'] : $expiresAt, + 'message' => 'Enlace enviado al cliente por WhatsApp', + ]); + +} catch (Exception $e) { + $errorDetail = date('[Y-m-d H:i:s] ') . 'request_large_file ERROR: ' . $e->getMessage() . "\n" . $e->getTraceAsString() . "\n\n"; + error_log('Error en request_large_file: ' . $e->getMessage() . ' | Trace: ' . $e->getTraceAsString()); + @file_put_contents(__DIR__ . '/../logs/app_errors.log', $errorDetail, FILE_APPEND); + http_response_code(500); + echo json_encode(['success' => false, 'error' => 'Error interno: ' . $e->getMessage()]); +} diff --git a/api/send_media_message_debug.log b/api/send_media_message_debug.log index 6b0350f..2c59913 100644 --- a/api/send_media_message_debug.log +++ b/api/send_media_message_debug.log @@ -263,3 +263,30 @@ [2026-02-03 16:20:02] Send media by id response: {"messaging_product":"whatsapp","contacts":[{"input":"573022548060","wa_id":"573022548060"}],"messages":[{"id":"wamid.HBgMNTczMDIyNTQ4MDYwFQIAERgSMjAzNDlFQTVENzA2OEQ5QzkwAA=="}]} [2026-02-03 16:20:02] WhatsApp response (successful): {"messaging_product":"whatsapp","contacts":[{"input":"573022548060","wa_id":"573022548060"}],"messages":[{"id":"wamid.HBgMNTczMDIyNTQ4MDYwFQIAERgSMjAzNDlFQTVENzA2OEQ5QzkwAA=="}]} [2026-02-03 16:20:02] Saving to DB - content: 20260122213820_CONSOLIDADO FERTILIZACION DESDE EL 2025-01-22 HASTA EL 2026-01-22.xlsx, media_url: 876532491851564, local_file: uploads/media_69826681178109.48762271.xlsx, local_thumb: null +[2026-02-21 07:21:21] Raw input: {"recipient":"573168950803","media_url":"https://bot.u-s.app/uploads/media_6999a341951659.74588611.pdf","media_type":"document","caption":null,"filename":"14672320327.pdf","is_voice":false} +[2026-02-21 07:21:21] Input decoded: {"recipient":"573168950803","media_url":"https:\/\/bot.u-s.app\/uploads\/media_6999a341951659.74588611.pdf","media_type":"document","caption":null,"filename":"14672320327.pdf","is_voice":false} +[2026-02-21 07:21:21] is_voice: false +[2026-02-21 07:21:21] Local file: /var/www/html/api/../uploads/media_6999a341951659.74588611.pdf exists=yes size=215839 +[2026-02-21 07:21:22] Upload result: {"id":"4311430285767679"} +[2026-02-21 07:21:22] Media ID obtained: 4311430285767679 +[2026-02-21 07:21:23] Send media by id response: {"messaging_product":"whatsapp","contacts":[{"input":"573168950803","wa_id":"573168950803"}],"messages":[{"id":"wamid.HBgMNTczMTY4OTUwODAzFQIAERgSQzg1RDY4RjA0MkEyNjY2N0MxAA=="}]} +[2026-02-21 07:21:23] WhatsApp response (successful): {"messaging_product":"whatsapp","contacts":[{"input":"573168950803","wa_id":"573168950803"}],"messages":[{"id":"wamid.HBgMNTczMTY4OTUwODAzFQIAERgSQzg1RDY4RjA0MkEyNjY2N0MxAA=="}]} +[2026-02-21 07:21:23] Saving to DB - content: 14672320327.pdf, media_url: 4311430285767679, local_file: uploads/media_6999a341951659.74588611.pdf, local_thumb: null +[2026-02-21 07:21:47] Raw input: {"recipient":"573022548060","media_url":"https://bot.u-s.app/uploads/media_6999a35b752f47.93279475.png","media_type":"image","caption":null,"filename":"image.png","is_voice":false} +[2026-02-21 07:21:47] Input decoded: {"recipient":"573022548060","media_url":"https:\/\/bot.u-s.app\/uploads\/media_6999a35b752f47.93279475.png","media_type":"image","caption":null,"filename":"image.png","is_voice":false} +[2026-02-21 07:21:47] is_voice: false +[2026-02-21 07:21:47] Local file: /var/www/html/api/../uploads/media_6999a35b752f47.93279475.png exists=yes size=888005 +[2026-02-21 07:21:48] Upload result: {"id":"835705749483344"} +[2026-02-21 07:21:48] Media ID obtained: 835705749483344 +[2026-02-21 07:21:49] Send media by id response: {"messaging_product":"whatsapp","contacts":[{"input":"573022548060","wa_id":"573022548060"}],"messages":[{"id":"wamid.HBgMNTczMDIyNTQ4MDYwFQIAERgSMzJFRjFFMzFDNDVDRENDRTAyAA=="}]} +[2026-02-21 07:21:49] WhatsApp response (successful): {"messaging_product":"whatsapp","contacts":[{"input":"573022548060","wa_id":"573022548060"}],"messages":[{"id":"wamid.HBgMNTczMDIyNTQ4MDYwFQIAERgSMzJFRjFFMzFDNDVDRENDRTAyAA=="}]} +[2026-02-21 07:21:49] Saving to DB - content: image.png, media_url: 835705749483344, local_file: uploads/media_6999a35b752f47.93279475.png, local_thumb: uploads/media_6999a35b752f47.93279475.png +[2026-02-21 07:23:25] Raw input: {"recipient":"573022548060","media_url":"https://bot.u-s.app/uploads/media_6999a3bdb40265.38760593.pdf","media_type":"document","caption":null,"filename":"DOCUMENTACI‡N_SITIO_XIMENACAICEDO.pdf","is_voice":false} +[2026-02-21 07:23:25] Input decoded: {"recipient":"573022548060","media_url":"https:\/\/bot.u-s.app\/uploads\/media_6999a3bdb40265.38760593.pdf","media_type":"document","caption":null,"filename":"DOCUMENTACI\u2021N_SITIO_XIMENACAICEDO.pdf","is_voice":false} +[2026-02-21 07:23:25] is_voice: false +[2026-02-21 07:23:25] Local file: /var/www/html/api/../uploads/media_6999a3bdb40265.38760593.pdf exists=yes size=225941 +[2026-02-21 07:23:26] Upload result: {"id":"804275938612470"} +[2026-02-21 07:23:26] Media ID obtained: 804275938612470 +[2026-02-21 07:23:27] Send media by id response: {"messaging_product":"whatsapp","contacts":[{"input":"573022548060","wa_id":"573022548060"}],"messages":[{"id":"wamid.HBgMNTczMDIyNTQ4MDYwFQIAERgSOEQ4MjMwMUQ2QThCQTcwNjgzAA=="}]} +[2026-02-21 07:23:27] WhatsApp response (successful): {"messaging_product":"whatsapp","contacts":[{"input":"573022548060","wa_id":"573022548060"}],"messages":[{"id":"wamid.HBgMNTczMDIyNTQ4MDYwFQIAERgSOEQ4MjMwMUQ2QThCQTcwNjgzAA=="}]} +[2026-02-21 07:23:27] Saving to DB - content: DOCUMENTACI‡N_SITIO_XIMENACAICEDO.pdf, media_url: 804275938612470, local_file: uploads/media_6999a3bdb40265.38760593.pdf, local_thumb: null diff --git a/api/version/media-url_debug.log b/api/version/media-url_debug.log index 0bd91a7..e13441e 100644 --- a/api/version/media-url_debug.log +++ b/api/version/media-url_debug.log @@ -426,3 +426,81 @@ [2026-02-20 09:05:37] Request: GET /api/version/media-url.php?url=https%3A%2F%2Flookaside.fbsbx.com%2Fwhatsapp_business%2Fattachments%2F%3Fmid%3D2371342336638440%26source%3Dwebhook%26ext%3D1769575796%26hash%3DARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA GET:{"url":"https:\/\/lookaside.fbsbx.com\/whatsapp_business\/attachments\/?mid=2371342336638440&source=webhook&ext=1769575796&hash=ARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA"} POST:[] [2026-02-20 09:05:37] Graph API fetch failed for mid 2371342336638440 - http: 400 - err: - resp: {"error":{"message":"Unsupported get request. Object with ID '2371342336638440' does not exist, cannot be loaded due to missing permissions, or does not support this operation. Please read the Graph API documentation at https:\/\/developers.facebook.com\/docs\/graph-api","type":"GraphMethodException","code":100,"error_subcode":33,"fbtrace_id":"Ayy1xE4nFVa4ae3yKVwTCJo"}} [2026-02-20 09:05:37] Proxying Facebook media URL with Authorization to https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=2371342336638440&source=webhook&ext=1769575796&hash=ARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA +[2026-02-21 06:51:51] Request: GET /api/version/media-url.php?id=876668735219378 GET:{"id":"876668735219378"} POST:[] +[2026-02-21 06:51:53] Auto-cached media 876668735219378 → uploads/media/2026/02/m_69999c594b0e4.jpg +[2026-02-21 07:16:05] Request: GET /api/version/media-url.php?id=1217156256734654 GET:{"id":"1217156256734654"} POST:[] +[2026-02-21 07:16:05] Request: GET /api/version/media-url.php?url=https%3A%2F%2Flookaside.fbsbx.com%2Fwhatsapp_business%2Fattachments%2F%3Fmid%3D2371342336638440%26source%3Dwebhook%26ext%3D1769575796%26hash%3DARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA GET:{"url":"https:\/\/lookaside.fbsbx.com\/whatsapp_business\/attachments\/?mid=2371342336638440&source=webhook&ext=1769575796&hash=ARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA"} POST:[] +[2026-02-21 07:16:05] Auto-download failed for media 1217156256734654: Graph API fetch failed: +[2026-02-21 07:16:05] Graph API request to https://graph.facebook.com/v22.0/1217156256734654 +[2026-02-21 07:16:05] Graph API fetch failed for mid 2371342336638440 - http: 400 - err: - resp: {"error":{"message":"Unsupported get request. Object with ID '2371342336638440' does not exist, cannot be loaded due to missing permissions, or does not support this operation. Please read the Graph API documentation at https:\/\/developers.facebook.com\/docs\/graph-api","type":"GraphMethodException","code":100,"error_subcode":33,"fbtrace_id":"ANJp7KukCHb4DzuSpzES6L5"}} +[2026-02-21 07:16:05] Streaming Facebook media with Authorization: https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=2371342336638440&source=webhook&ext=1769575796&hash=ARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA +[2026-02-21 07:16:05] streamRemoteFile failed: url=https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=2371342336638440&source=webhook&ext=1769575796&hash=ARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA err= http=404 +[2026-02-21 07:20:04] Request: GET /api/version/media-url.php?id=1217156256734654 GET:{"id":"1217156256734654"} POST:[] +[2026-02-21 07:20:04] Request: GET /api/version/media-url.php?url=https%3A%2F%2Flookaside.fbsbx.com%2Fwhatsapp_business%2Fattachments%2F%3Fmid%3D2371342336638440%26source%3Dwebhook%26ext%3D1769575796%26hash%3DARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA GET:{"url":"https:\/\/lookaside.fbsbx.com\/whatsapp_business\/attachments\/?mid=2371342336638440&source=webhook&ext=1769575796&hash=ARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA"} POST:[] +[2026-02-21 07:20:04] Graph API fetch failed for mid 2371342336638440 - http: 400 - err: - resp: {"error":{"message":"Unsupported get request. Object with ID '2371342336638440' does not exist, cannot be loaded due to missing permissions, or does not support this operation. Please read the Graph API documentation at https:\/\/developers.facebook.com\/docs\/graph-api","type":"GraphMethodException","code":100,"error_subcode":33,"fbtrace_id":"A6XVbqqIuOgalslGDeKiVzm"}} +[2026-02-21 07:20:04] Streaming Facebook media with Authorization: https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=2371342336638440&source=webhook&ext=1769575796&hash=ARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA +[2026-02-21 07:20:04] Auto-download failed for media 1217156256734654: Graph API fetch failed: +[2026-02-21 07:20:04] Graph API request to https://graph.facebook.com/v22.0/1217156256734654 +[2026-02-21 07:20:04] streamRemoteFile failed: url=https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=2371342336638440&source=webhook&ext=1769575796&hash=ARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA err= http=404 +[2026-02-21 07:21:34] Request: GET /api/version/media-url.php?id=1217156256734654 GET:{"id":"1217156256734654"} POST:[] +[2026-02-21 07:21:34] Request: GET /api/version/media-url.php?url=https%3A%2F%2Flookaside.fbsbx.com%2Fwhatsapp_business%2Fattachments%2F%3Fmid%3D2371342336638440%26source%3Dwebhook%26ext%3D1769575796%26hash%3DARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA GET:{"url":"https:\/\/lookaside.fbsbx.com\/whatsapp_business\/attachments\/?mid=2371342336638440&source=webhook&ext=1769575796&hash=ARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA"} POST:[] +[2026-02-21 07:21:34] Graph API fetch failed for mid 2371342336638440 - http: 400 - err: - resp: {"error":{"message":"Unsupported get request. Object with ID '2371342336638440' does not exist, cannot be loaded due to missing permissions, or does not support this operation. Please read the Graph API documentation at https:\/\/developers.facebook.com\/docs\/graph-api","type":"GraphMethodException","code":100,"error_subcode":33,"fbtrace_id":"AGMzefWK6Jm8NVar69sH-jp"}} +[2026-02-21 07:21:34] Auto-download failed for media 1217156256734654: Graph API fetch failed: +[2026-02-21 07:21:34] Streaming Facebook media with Authorization: https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=2371342336638440&source=webhook&ext=1769575796&hash=ARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA +[2026-02-21 07:21:34] Graph API request to https://graph.facebook.com/v22.0/1217156256734654 +[2026-02-21 07:21:35] streamRemoteFile failed: url=https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=2371342336638440&source=webhook&ext=1769575796&hash=ARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA err= http=404 +[2026-02-21 07:21:49] Request: GET /api/version/media-url.php?id=1217156256734654 GET:{"id":"1217156256734654"} POST:[] +[2026-02-21 07:21:49] Request: GET /api/version/media-url.php?url=https%3A%2F%2Flookaside.fbsbx.com%2Fwhatsapp_business%2Fattachments%2F%3Fmid%3D2371342336638440%26source%3Dwebhook%26ext%3D1769575796%26hash%3DARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA GET:{"url":"https:\/\/lookaside.fbsbx.com\/whatsapp_business\/attachments\/?mid=2371342336638440&source=webhook&ext=1769575796&hash=ARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA"} POST:[] +[2026-02-21 07:21:49] Auto-download failed for media 1217156256734654: Graph API fetch failed: +[2026-02-21 07:21:49] Graph API request to https://graph.facebook.com/v22.0/1217156256734654 +[2026-02-21 07:21:49] Graph API fetch failed for mid 2371342336638440 - http: 400 - err: - resp: {"error":{"message":"Unsupported get request. Object with ID '2371342336638440' does not exist, cannot be loaded due to missing permissions, or does not support this operation. Please read the Graph API documentation at https:\/\/developers.facebook.com\/docs\/graph-api","type":"GraphMethodException","code":100,"error_subcode":33,"fbtrace_id":"A4_9s9OJ8bcOZfLlTbHf0Dv"}} +[2026-02-21 07:21:49] Streaming Facebook media with Authorization: https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=2371342336638440&source=webhook&ext=1769575796&hash=ARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA +[2026-02-21 07:21:49] streamRemoteFile failed: url=https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=2371342336638440&source=webhook&ext=1769575796&hash=ARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA err= http=404 +[2026-02-21 07:21:52] Request: GET /api/version/media-url.php?id=1217156256734654 GET:{"id":"1217156256734654"} POST:[] +[2026-02-21 07:21:52] Request: GET /api/version/media-url.php?url=https%3A%2F%2Flookaside.fbsbx.com%2Fwhatsapp_business%2Fattachments%2F%3Fmid%3D2371342336638440%26source%3Dwebhook%26ext%3D1769575796%26hash%3DARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA GET:{"url":"https:\/\/lookaside.fbsbx.com\/whatsapp_business\/attachments\/?mid=2371342336638440&source=webhook&ext=1769575796&hash=ARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA"} POST:[] +[2026-02-21 07:21:52] Graph API fetch failed for mid 2371342336638440 - http: 400 - err: - resp: {"error":{"message":"Unsupported get request. Object with ID '2371342336638440' does not exist, cannot be loaded due to missing permissions, or does not support this operation. Please read the Graph API documentation at https:\/\/developers.facebook.com\/docs\/graph-api","type":"GraphMethodException","code":100,"error_subcode":33,"fbtrace_id":"AN5tABn0BsuVQndLVmb32L3"}} +[2026-02-21 07:21:52] Streaming Facebook media with Authorization: https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=2371342336638440&source=webhook&ext=1769575796&hash=ARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA +[2026-02-21 07:21:52] Auto-download failed for media 1217156256734654: Graph API fetch failed: +[2026-02-21 07:21:52] Graph API request to https://graph.facebook.com/v22.0/1217156256734654 +[2026-02-21 07:21:52] streamRemoteFile failed: url=https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=2371342336638440&source=webhook&ext=1769575796&hash=ARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA err= http=404 +[2026-02-21 07:22:25] Request: GET /api/version/media-url.php?id=1217156256734654 GET:{"id":"1217156256734654"} POST:[] +[2026-02-21 07:22:25] Request: GET /api/version/media-url.php?url=https%3A%2F%2Flookaside.fbsbx.com%2Fwhatsapp_business%2Fattachments%2F%3Fmid%3D2371342336638440%26source%3Dwebhook%26ext%3D1769575796%26hash%3DARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA GET:{"url":"https:\/\/lookaside.fbsbx.com\/whatsapp_business\/attachments\/?mid=2371342336638440&source=webhook&ext=1769575796&hash=ARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA"} POST:[] +[2026-02-21 07:22:26] Auto-download failed for media 1217156256734654: Graph API fetch failed: +[2026-02-21 07:22:26] Graph API request to https://graph.facebook.com/v22.0/1217156256734654 +[2026-02-21 07:22:26] Graph API fetch failed for mid 2371342336638440 - http: 400 - err: - resp: {"error":{"message":"Unsupported get request. Object with ID '2371342336638440' does not exist, cannot be loaded due to missing permissions, or does not support this operation. Please read the Graph API documentation at https:\/\/developers.facebook.com\/docs\/graph-api","type":"GraphMethodException","code":100,"error_subcode":33,"fbtrace_id":"AiiaRGgmOStqG0RVrC04Gla"}} +[2026-02-21 07:22:26] Streaming Facebook media with Authorization: https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=2371342336638440&source=webhook&ext=1769575796&hash=ARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA +[2026-02-21 07:22:26] streamRemoteFile failed: url=https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=2371342336638440&source=webhook&ext=1769575796&hash=ARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA err= http=404 +[2026-02-21 07:23:27] Request: GET /api/version/media-url.php?id=1217156256734654 GET:{"id":"1217156256734654"} POST:[] +[2026-02-21 07:23:27] Request: GET /api/version/media-url.php?url=https%3A%2F%2Flookaside.fbsbx.com%2Fwhatsapp_business%2Fattachments%2F%3Fmid%3D2371342336638440%26source%3Dwebhook%26ext%3D1769575796%26hash%3DARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA GET:{"url":"https:\/\/lookaside.fbsbx.com\/whatsapp_business\/attachments\/?mid=2371342336638440&source=webhook&ext=1769575796&hash=ARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA"} POST:[] +[2026-02-21 07:23:27] Graph API fetch failed for mid 2371342336638440 - http: 400 - err: - resp: {"error":{"message":"Unsupported get request. Object with ID '2371342336638440' does not exist, cannot be loaded due to missing permissions, or does not support this operation. Please read the Graph API documentation at https:\/\/developers.facebook.com\/docs\/graph-api","type":"GraphMethodException","code":100,"error_subcode":33,"fbtrace_id":"AVFGht18YvnGrGvIZP6Oil9"}} +[2026-02-21 07:23:27] Auto-download failed for media 1217156256734654: Graph API fetch failed: +[2026-02-21 07:23:27] Streaming Facebook media with Authorization: https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=2371342336638440&source=webhook&ext=1769575796&hash=ARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA +[2026-02-21 07:23:27] Graph API request to https://graph.facebook.com/v22.0/1217156256734654 +[2026-02-21 07:23:27] streamRemoteFile failed: url=https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=2371342336638440&source=webhook&ext=1769575796&hash=ARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA err= http=404 +[2026-02-21 07:23:29] Request: GET /api/version/media-url.php?id=804275938612470&download=1 GET:{"id":"804275938612470","download":"1"} POST:[] +[2026-02-21 07:23:29] Serving local file for media ID 804275938612470: /var/www/html/uploads/media_6999a3bdb40265.38760593.pdf +[2026-02-21 07:31:16] Request: GET /api/version/media-url.php?url=https%3A%2F%2Flookaside.fbsbx.com%2Fwhatsapp_business%2Fattachments%2F%3Fmid%3D2371342336638440%26source%3Dwebhook%26ext%3D1769575796%26hash%3DARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA GET:{"url":"https:\/\/lookaside.fbsbx.com\/whatsapp_business\/attachments\/?mid=2371342336638440&source=webhook&ext=1769575796&hash=ARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA"} POST:[] +[2026-02-21 07:31:16] Request: GET /api/version/media-url.php?id=1217156256734654 GET:{"id":"1217156256734654"} POST:[] +[2026-02-21 07:31:16] Graph API fetch failed for mid 2371342336638440 - http: 400 - err: - resp: {"error":{"message":"Unsupported get request. Object with ID '2371342336638440' does not exist, cannot be loaded due to missing permissions, or does not support this operation. Please read the Graph API documentation at https:\/\/developers.facebook.com\/docs\/graph-api","type":"GraphMethodException","code":100,"error_subcode":33,"fbtrace_id":"AQwxMcsaRiezLVdkdElx2cK"}} +[2026-02-21 07:31:16] Streaming Facebook media with Authorization: https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=2371342336638440&source=webhook&ext=1769575796&hash=ARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA +[2026-02-21 07:31:16] Auto-download failed for media 1217156256734654: Graph API fetch failed: +[2026-02-21 07:31:16] Graph API request to https://graph.facebook.com/v22.0/1217156256734654 +[2026-02-21 07:31:17] streamRemoteFile failed: url=https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=2371342336638440&source=webhook&ext=1769575796&hash=ARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA err= http=404 +[2026-02-21 07:31:28] Request: GET /api/version/media-url.php?url=http%3A%2F%2Flocalhost%3A8080%2Fuploads%2Ffile_requests%2F2026%2F02%2Ffr_2_6999a5935b274.pdf&download=1 GET:{"url":"http:\/\/localhost:8080\/uploads\/file_requests\/2026\/02\/fr_2_6999a5935b274.pdf","download":"1"} POST:[] +[2026-02-21 07:31:28] Serving local file for URL http://localhost:8080/uploads/file_requests/2026/02/fr_2_6999a5935b274.pdf: /var/www/html/uploads/file_requests/2026/02/fr_2_6999a5935b274.pdf +[2026-02-21 07:31:40] Request: GET /api/version/media-url.php?id=1217156256734654 GET:{"id":"1217156256734654"} POST:[] +[2026-02-21 07:31:40] Request: GET /api/version/media-url.php?url=https%3A%2F%2Flookaside.fbsbx.com%2Fwhatsapp_business%2Fattachments%2F%3Fmid%3D2371342336638440%26source%3Dwebhook%26ext%3D1769575796%26hash%3DARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA GET:{"url":"https:\/\/lookaside.fbsbx.com\/whatsapp_business\/attachments\/?mid=2371342336638440&source=webhook&ext=1769575796&hash=ARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA"} POST:[] +[2026-02-21 07:31:40] Graph API fetch failed for mid 2371342336638440 - http: 400 - err: - resp: {"error":{"message":"Unsupported get request. Object with ID '2371342336638440' does not exist, cannot be loaded due to missing permissions, or does not support this operation. Please read the Graph API documentation at https:\/\/developers.facebook.com\/docs\/graph-api","type":"GraphMethodException","code":100,"error_subcode":33,"fbtrace_id":"AknMoavxvfmPrC4LDQE0C60"}} +[2026-02-21 07:31:40] Streaming Facebook media with Authorization: https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=2371342336638440&source=webhook&ext=1769575796&hash=ARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA +[2026-02-21 07:31:40] Auto-download failed for media 1217156256734654: Graph API fetch failed: +[2026-02-21 07:31:40] Graph API request to https://graph.facebook.com/v22.0/1217156256734654 +[2026-02-21 07:31:41] streamRemoteFile failed: url=https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=2371342336638440&source=webhook&ext=1769575796&hash=ARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA err= http=404 +[2026-02-21 07:31:44] Request: GET /api/version/media-url.php?url=http%3A%2F%2Flocalhost%3A8080%2Fuploads%2Ffile_requests%2F2026%2F02%2Ffr_2_6999a5abc6620.docx&download=1 GET:{"url":"http:\/\/localhost:8080\/uploads\/file_requests\/2026\/02\/fr_2_6999a5abc6620.docx","download":"1"} POST:[] +[2026-02-21 07:31:44] Serving local file for URL http://localhost:8080/uploads/file_requests/2026/02/fr_2_6999a5abc6620.docx: /var/www/html/uploads/file_requests/2026/02/fr_2_6999a5abc6620.docx +[2026-02-21 07:35:57] Request: GET /api/version/media-url.php?id=1217156256734654 GET:{"id":"1217156256734654"} POST:[] +[2026-02-21 07:35:57] Request: GET /api/version/media-url.php?url=https%3A%2F%2Flookaside.fbsbx.com%2Fwhatsapp_business%2Fattachments%2F%3Fmid%3D2371342336638440%26source%3Dwebhook%26ext%3D1769575796%26hash%3DARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA GET:{"url":"https:\/\/lookaside.fbsbx.com\/whatsapp_business\/attachments\/?mid=2371342336638440&source=webhook&ext=1769575796&hash=ARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA"} POST:[] +[2026-02-21 07:35:57] Graph API fetch failed for mid 2371342336638440 - http: 400 - err: - resp: {"error":{"message":"Unsupported get request. Object with ID '2371342336638440' does not exist, cannot be loaded due to missing permissions, or does not support this operation. Please read the Graph API documentation at https:\/\/developers.facebook.com\/docs\/graph-api","type":"GraphMethodException","code":100,"error_subcode":33,"fbtrace_id":"A3_VKJznLWm0yO9VOYihIll"}} +[2026-02-21 07:35:57] Streaming Facebook media with Authorization: https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=2371342336638440&source=webhook&ext=1769575796&hash=ARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA +[2026-02-21 07:35:57] Auto-download failed for media 1217156256734654: Graph API fetch failed: +[2026-02-21 07:35:57] Graph API request to https://graph.facebook.com/v22.0/1217156256734654 +[2026-02-21 07:35:57] streamRemoteFile failed: url=https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=2371342336638440&source=webhook&ext=1769575796&hash=ARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA err= http=404 diff --git a/api/webhook.php b/api/webhook.php index e8a1e2e..d560cf4 100644 --- a/api/webhook.php +++ b/api/webhook.php @@ -108,6 +108,18 @@ class WhatsAppWebhook { return; } + // Extraer nombres de contactos del payload (contacts[].profile.name) + $contactNames = []; + if (isset($value['contacts']) && is_array($value['contacts'])) { + foreach ($value['contacts'] as $contact) { + $waId = $contact['wa_id'] ?? null; + $name = $contact['profile']['name'] ?? null; + if ($waId && $name) { + $contactNames[$waId] = $name; + } + } + } + foreach ($items as $message) { // En algunos payloads la estructura key es 'from' y 'id' (mensajes), en otros puede venir distinta; normalizamos $phoneNumber = $message['from'] ?? ($message['wa_id'] ?? null); @@ -131,9 +143,29 @@ class WhatsAppWebhook { // Obtener o crear usuario $user = $this->getUserByPhone($phoneNumber); + $contactName = $contactNames[$phoneNumber] ?? null; if (!$user) { $userId = $this->createUser($phoneNumber); $user = $this->getUserById($userId); + // Guardar nombre del contacto de WhatsApp al crear usuario + if ($contactName && $userId) { + try { + $this->db->update('users', ['name' => $contactName], 'id = ? AND (name IS NULL OR name = ?)', [$userId, '']); + $user['name'] = $contactName; + } catch (Exception $e) { + error_log('[webhook] Error guardando nombre contacto: ' . $e->getMessage()); + } + } + } else { + // Actualizar nombre si el usuario no tiene uno guardado + if ($contactName && (empty($user['name']))) { + try { + $this->db->update('users', ['name' => $contactName], 'id = ? AND (name IS NULL OR name = ?)', [$user['id'], '']); + $user['name'] = $contactName; + } catch (Exception $e) { + error_log('[webhook] Error actualizando nombre contacto: ' . $e->getMessage()); + } + } } // Procesar diferentes tipos de mensaje @@ -178,40 +210,34 @@ class WhatsAppWebhook { } elseif (isset($message['image'])) { $messageText = $message['image']['caption'] ?? ''; $messageType = 'image'; - // Preferir URL directo si viene en el webhook (evita resolver media_id) - $mediaUrl = $message['image']['url'] ?? ($message['image']['id'] ?? null); + // Preferir media ID (estable) sobre URL (expira en minutos) + $mediaUrl = $message['image']['id'] ?? ($message['image']['url'] ?? null); $mimeType = $message['image']['mime_type'] ?? null; $filename = $message['image']['filename'] ?? null; - - } elseif (isset($message['audio'])) { $messageType = 'audio'; - $mediaUrl = $message['audio']['url'] ?? ($message['audio']['id'] ?? null); + $mediaUrl = $message['audio']['id'] ?? ($message['audio']['url'] ?? null); $mimeType = $message['audio']['mime_type'] ?? null; $filename = $message['audio']['filename'] ?? null; - - } elseif (isset($message['video'])) { $messageText = $message['video']['caption'] ?? ''; $messageType = 'video'; - $mediaUrl = $message['video']['url'] ?? ($message['video']['id'] ?? null); + $mediaUrl = $message['video']['id'] ?? ($message['video']['url'] ?? null); $mimeType = $message['video']['mime_type'] ?? null; $filename = $message['video']['filename'] ?? null; - - } elseif (isset($message['document'])) { $messageText = $message['document']['filename'] ?? ''; $messageType = 'document'; - $mediaUrl = $message['document']['url'] ?? ($message['document']['id'] ?? null); + $mediaUrl = $message['document']['id'] ?? ($message['document']['url'] ?? null); $mimeType = $message['document']['mime_type'] ?? null; $filename = $message['document']['filename'] ?? null; } elseif (isset($message['sticker'])) { $messageType = 'sticker'; - $mediaUrl = $message['sticker']['url'] ?? ($message['sticker']['id'] ?? null); + $mediaUrl = $message['sticker']['id'] ?? ($message['sticker']['url'] ?? null); $mimeType = $message['sticker']['mime_type'] ?? null; $filename = $message['sticker']['filename'] ?? null; diff --git a/api/webhook_optimized.php b/api/webhook_optimized.php index 4e9b40f..01c5305 100644 --- a/api/webhook_optimized.php +++ b/api/webhook_optimized.php @@ -127,7 +127,8 @@ class OptimizedWebhook { foreach ($entry['changes'] as $change) { if (isset($change['value']['messages'])) { - $this->queueMessages($change['value']['messages']); + $contacts = $change['value']['contacts'] ?? []; + $this->queueMessages($change['value']['messages'], $contacts); } if (isset($change['value']['statuses'])) { @@ -156,7 +157,17 @@ class OptimizedWebhook { /** * Encolar mensajes para procesamiento asíncrono */ - private function queueMessages($messages) { + private function queueMessages($messages, $contacts = []) { + // Extraer nombres de contactos del payload + $contactNames = []; + foreach ($contacts as $contact) { + $waId = $contact['wa_id'] ?? null; + $name = $contact['profile']['name'] ?? null; + if ($waId && $name) { + $contactNames[$waId] = $name; + } + } + foreach ($messages as $message) { try { $messageId = $message['id'] ?? null; @@ -178,9 +189,29 @@ class OptimizedWebhook { // Obtener o crear usuario (rápido, solo DB lookup) $user = $this->getUserByPhone($phoneNumber); + $contactName = $contactNames[$phoneNumber] ?? null; if (!$user) { $userId = $this->createUser($phoneNumber); $user = $this->getUserById($userId); + // Guardar nombre del contacto de WhatsApp + if ($contactName && $userId) { + try { + $this->db->update('users', ['name' => $contactName], 'id = ? AND (name IS NULL OR name = ? OR name = ?)', [$userId, '', $phoneNumber]); + $user['name'] = $contactName; + } catch (Exception $e) { + $this->logger->warning('Error guardando nombre contacto: ' . $e->getMessage()); + } + } + } else { + // Actualizar nombre si no tiene uno real (solo tiene el teléfono o vacío) + if ($contactName && (empty($user['name']) || $user['name'] === $phoneNumber)) { + try { + $this->db->update('users', ['name' => $contactName], 'id = ? AND (name IS NULL OR name = ? OR name = ?)', [$user['id'], '', $phoneNumber]); + $user['name'] = $contactName; + } catch (Exception $e) { + $this->logger->warning('Error actualizando nombre contacto: ' . $e->getMessage()); + } + } } // Extraer tipo y contenido del mensaje @@ -249,7 +280,8 @@ class OptimizedWebhook { case 'image': $text = $message['image']['caption'] ?? ''; - $mediaUrl = $message['image']['url'] ?? $message['image']['id'] ?? null; + // Preferir media ID (estable) sobre URL (expira en minutos) + $mediaUrl = $message['image']['id'] ?? $message['image']['url'] ?? null; // Encolar descarga de media if ($mediaUrl) { @@ -259,7 +291,7 @@ class OptimizedWebhook { case 'video': $text = $message['video']['caption'] ?? ''; - $mediaUrl = $message['video']['url'] ?? $message['video']['id'] ?? null; + $mediaUrl = $message['video']['id'] ?? $message['video']['url'] ?? null; if ($mediaUrl) { $this->queueMediaDownload($mediaUrl, $message['id'], 'video'); @@ -267,7 +299,7 @@ class OptimizedWebhook { break; case 'audio': - $mediaUrl = $message['audio']['url'] ?? $message['audio']['id'] ?? null; + $mediaUrl = $message['audio']['id'] ?? $message['audio']['url'] ?? null; if ($mediaUrl) { $this->queueMediaDownload($mediaUrl, $message['id'], 'audio'); @@ -276,7 +308,7 @@ class OptimizedWebhook { case 'document': $text = $message['document']['filename'] ?? ''; - $mediaUrl = $message['document']['url'] ?? $message['document']['id'] ?? null; + $mediaUrl = $message['document']['id'] ?? $message['document']['url'] ?? null; if ($mediaUrl) { $this->queueMediaDownload($mediaUrl, $message['id'], 'document'); diff --git a/classes/Database.php b/classes/Database.php index f106c52..e4a9efd 100644 --- a/classes/Database.php +++ b/classes/Database.php @@ -47,7 +47,9 @@ class Database { } return $stmt; } catch (PDOException $e) { + $errorDetail = date('[Y-m-d H:i:s] ') . "DB Query FAILED: " . $e->getMessage() . "\n SQL: " . $sql . "\n Params: " . json_encode($params) . "\n Trace: " . $e->getTraceAsString() . "\n\n"; error_log("Query failed: " . $e->getMessage() . " SQL: " . $sql . " Params: " . json_encode($params)); + @file_put_contents(__DIR__ . '/../logs/app_errors.log', $errorDetail, FILE_APPEND); throw new Exception("Error en la consulta a la base de datos: " . $e->getMessage()); } } @@ -90,8 +92,36 @@ class Database { } $fieldsStr = implode(', ', $fields); - $sql = "UPDATE {$table} SET {$fieldsStr} WHERE {$where}"; - $params = array_merge($data, $whereParams); + // Convertir WHERE params para evitar mezcla de nombrados y posicionales + $whereNamed = $where; + $namedWhereParams = []; + + if (!empty($whereParams)) { + // Detectar si es asociativo (nombrado) o secuencial (posicional) + $keys = array_keys($whereParams); + $isAssociative = ($keys !== range(0, count($whereParams) - 1)); + + if ($isAssociative) { + // WHERE ya usa :nombre — renombrar para evitar colisión con SET params + foreach ($whereParams as $key => $value) { + $newKey = '_w_' . $key; + $whereNamed = str_replace(':' . $key, ':' . $newKey, $whereNamed); + $namedWhereParams[$newKey] = $value; + } + } else { + // WHERE usa ? posicionales — convertir a nombrados + $i = 0; + foreach ($whereParams as $value) { + $paramName = '_w' . $i; + $whereNamed = preg_replace('/\?/', ':' . $paramName, $whereNamed, 1); + $namedWhereParams[$paramName] = $value; + $i++; + } + } + } + + $sql = "UPDATE {$table} SET {$fieldsStr} WHERE {$whereNamed}"; + $params = array_merge($data, $namedWhereParams); return $this->query($sql, $params); } diff --git a/conversations.php b/conversations.php index effc8b2..894b8bb 100644 --- a/conversations.php +++ b/conversations.php @@ -904,6 +904,10 @@ if (!isset($_SESSION['user_id'])) {
Tus archivos fueron enviados correctamente. El asesor los recibirá en breve.
+ +