diff --git a/backend/app/Controllers/WhatsAppController.php b/backend/app/Controllers/WhatsAppController.php index 2623a60..7d05e2e 100644 --- a/backend/app/Controllers/WhatsAppController.php +++ b/backend/app/Controllers/WhatsAppController.php @@ -137,6 +137,48 @@ class WhatsAppController extends BaseController return; } + // Handle message received events + if ($body['state'] === 'message_received') { + if (empty($body['message'])) { + $response->status(400)->json(['error' => 'Missing message payload']); + return; + } + + $msgData = $body['message']; + + // 1. Find or create the contact in the CRM + $contact = \App\Models\Contact::findByPhone($session['company_id'], $msgData['phone']); + if (!$contact) { + // Determine a fallback name + $contactName = !empty($msgData['name']) ? $msgData['name'] : 'WA-' . substr($msgData['phone'], -4); + \App\Models\Contact::createSecure([ + 'company_id' => $session['company_id'], + 'name' => $contactName, + 'phone' => $msgData['phone'], + 'notes' => 'Auto-created via incoming WhatsApp message' + ]); + } + + // 2. Log the incoming message in history log + \App\Models\MessageLog::logMessage([ + 'company_id' => $session['company_id'], + 'session_id' => $session['id'], + 'contact_phone' => $msgData['phone'], + 'direction' => 'inbound', + 'message_type' => 'text', + 'message_body' => $msgData['body'], + 'whatsapp_message_id' => $msgData['id'], + 'status' => 'read' + ]); + + // 3. Placeholder for Phase 5 Gemini AI auto-reply + $this->triggerAutoReply($session, $msgData); + + $response->json(['status' => 'success', 'message' => 'Incoming message logged']); + return; + } + + // Handle connection state sync $updateData = [ 'status' => $body['state'] // 'waiting_qr', 'connected', 'disconnected' ]; @@ -156,4 +198,12 @@ class WhatsAppController extends BaseController $response->json(['status' => 'success']); } + + /** + * Placeholder to trigger Gemini AI Auto-Replies or Keyword rules (Phase 5) + */ + private function triggerAutoReply(array $session, array $msgData) + { + // To be implemented in Phase 5 + } } diff --git a/whatsapp-gateway/baileys-client.js b/whatsapp-gateway/baileys-client.js index 74fe0a1..8ec098a 100644 --- a/whatsapp-gateway/baileys-client.js +++ b/whatsapp-gateway/baileys-client.js @@ -74,6 +74,46 @@ async function startSession(session_key, webhook_url) { sock.ev.on('creds.update', saveCreds); + // Listen for incoming messages + sock.ev.on('messages.upsert', async (m) => { + if (m.type !== 'notify') return; + + for (const msg of m.messages) { + // Ignore messages sent by ourselves + if (msg.key.fromMe) continue; + + const remoteJid = msg.key.remoteJid; + // Only process direct messages from individuals (ignore groups/broadcasts) + if (!remoteJid || !remoteJid.endsWith('@s.whatsapp.net')) continue; + + // Extract text body + const body = msg.message?.conversation || + msg.message?.extendedTextMessage?.text || + msg.message?.imageMessage?.caption || + msg.message?.videoMessage?.caption || ''; + + // For now, only process messages that have text content + if (!body) continue; + + const senderPhone = remoteJid.split('@')[0]; + const senderName = msg.pushName || ''; + + console.log(`[Message] Received from ${senderPhone}: ${body}`); + + await sendWebhook(webhook_url, { + session_key, + state: 'message_received', + message: { + id: msg.key.id, + phone: senderPhone, + name: senderName, + body: body, + timestamp: msg.messageTimestamp + } + }); + } + }); + sock.ev.on('connection.update', async (update) => { const { connection, lastDisconnect, qr } = update;