diff --git a/whatsapp-router/src/chatHandlers.ts b/whatsapp-router/src/chatHandlers.ts index dbe850b..9c0f685 100644 --- a/whatsapp-router/src/chatHandlers.ts +++ b/whatsapp-router/src/chatHandlers.ts @@ -6,7 +6,7 @@ dotenv.config(); export type Handler = string | ((conv: Conversation) => Promise); export const chatHandlers: Record = { - '50498554225@c.us': process.env.CONVERSATION_AGENT_URL || 'http://conversation-layer-agent:8001', + '50498554225@c.us': process.env.CONVERSATION_AGENT_URL || 'http://localhost:8001', '120363401804322608@g.us' : 'http://planilla-agent:8012' // Add other mappings like: // '50496210031@c.us': 'http://llm-agent:8000' diff --git a/whatsapp-router/src/store/conversation.ts b/whatsapp-router/src/store/conversation.ts index c074b4d..6c2a9a5 100644 --- a/whatsapp-router/src/store/conversation.ts +++ b/whatsapp-router/src/store/conversation.ts @@ -1,84 +1,17 @@ import axios from 'axios'; import { WhatsAppMessage, Conversation, Msg, Participant } from '../types'; -/** - * In‑memory cache of conversations indexed by chatId. - */ const conversations = new Map(); +// ──────────────────────────── Helpers ───────────────────────────── - -// ─────────────────────────────────────────────────────── internal helpers ──── - -/** - * Fetches *all* messages for a chat using OpenWA and returns them as‑is. - */ async function fetchChatMessages(chatId: string, openWaUrl: string): Promise { const { data } = await axios.post(`${openWaUrl}/loadAndGetAllMessagesInChat`, { args: { chatId, includeMe: true, includeNotifications: true }, }); - return data?.response ?? data ?? []; } -/** - * Builds a complete {@link Conversation} object from raw WA messages. - */ -export async function buildConversation(chatId: string, openWaUrl: string): Promise { - const raw = await fetchChatMessages(chatId, openWaUrl); - const chatMeta = raw[0]?.chat; - - const convo: Conversation = { - chatId, - title: chatMeta?.formattedTitle ?? chatMeta?.name ?? chatId, - isGroup: Boolean(chatMeta?.isGroup), - unreadCount: chatMeta?.unreadCount ?? 0, - participants: buildParticipants(raw), - messages: raw.slice(-100).map(toMsg).sort((a, b) => a.ts - b.ts), - createdAt: Date.now(), - }; - - conversations.set(chatId, convo); - return convo; -} - -/** - * Collects unique participants from every message (and group metadata if any). - */ -function buildParticipants(messages: WhatsAppMessage[]): Participant[] { - const map = new Map(); - - for (const m of messages) { - const s: any = m.sender ?? m.chat?.contact ?? null; - if (!s) continue; - - if (!map.has(s.id)) { - map.set(s.id, { - id: s.id, - name: s.pushname || s.name || '', - isMe: Boolean(s.isMe), - isAdmin: Boolean(s.isAdmin || s.isSuperAdmin), - }); - } - } - - return [...map.values()]; -} - -function ensureParticipant(list: Participant[], sender: any): void { - if (!sender) return; - if (list.some((p) => p.id === sender.id)) return; - - list.push({ - id: sender.id, - name: sender.pushname || sender.name || '', - isMe: Boolean(sender.isMe), - }); -} - -/** - * Normalises a raw WhatsApp message to the lightweight {@link Msg} format. - */ function toMsg(m: WhatsAppMessage): Msg { const anyMsg = m as any; return { @@ -88,7 +21,7 @@ function toMsg(m: WhatsAppMessage): Msg { ts: anyMsg.timestamp ?? anyMsg.t ?? Date.now(), type: anyMsg.type ?? 'chat', text: anyMsg.text ?? anyMsg.caption ?? anyMsg.body ?? '', - mediaUrl: anyMsg.cloudUrl ?? anyMsg.clientUrl ?? undefined, + mediaUrl: anyMsg.cloudUrl ?? anyMsg.clientUrl, mentions: anyMsg.mentionedJidList ?? [], meta: { ack: anyMsg.ack ?? 0, @@ -98,7 +31,65 @@ function toMsg(m: WhatsAppMessage): Msg { }; } -// ─────────────────────────────────────────────────────────── public API ────── +function buildParticipants(messages: WhatsAppMessage[], chat?: any): Participant[] { + const map = new Map(); + + if (chat?.contact) { + const c = chat.contact; + map.set(c.id, { + id: c.id, + name: c.pushname || c.name || '', + isMe: Boolean(c.isMe), + }); + } + + if (chat?.isGroup && chat.groupMetadata?.participants) { + for (const p of chat.groupMetadata.participants as any[]) { + const c = p.contact || {}; + const id = c.id || p.id; + map.set(id, { + id, + name: c.pushname || c.name || '', + isMe: Boolean(c.isMe), + isAdmin: Boolean(p.isAdmin || p.isSuperAdmin), + }); + } + } + + for (const m of messages) { + const s = m.sender; + if (s && !map.has(s.id)) { + map.set(s.id, { + id: s.id, + name: s.pushname || s.name || '', + isMe: Boolean(s.isMe), + }); + } + } + + return [...map.values()]; +} + +// ──────────────────────────── Public API ───────────────────────────── + +export async function buildConversation(chatId: string, openWaUrl: string): Promise { + const raw = await fetchChatMessages(chatId, openWaUrl); + const chat = raw[0]?.chat; + const now = Date.now(); + + const conversation: Conversation = { + chatId, + title: chat?.formattedTitle ?? chat?.name ?? chatId, + isGroup: Boolean(chat?.isGroup), + unreadCount: chat?.unreadCount ?? 0, + participants: buildParticipants(raw, chat), + messages: raw.slice(-20).map(toMsg).sort((a, b) => a.ts - b.ts), + createdAt: conversations.get(chatId)?.createdAt || now, + }; + + conversations.set(chatId, conversation); + return conversation; +} export async function getConversation(chatId: string, openWaUrl: string): Promise { let conv = conversations.get(chatId); @@ -116,13 +107,11 @@ export function deleteConversation(chatId: string): boolean { export async function addMessageToConversation( chatId: string, - waMsg: WhatsAppMessage, - openWaUrl: string, + msg: WhatsAppMessage, + openWaUrl: string ): Promise { - console.log(`[conversationStore] Adding message to ${chatId}`); const conv = await getConversation(chatId, openWaUrl); - const mapped = mapMessage(msg); - // avoid duplicates if multiple webhook events deliver the same message + const mapped = toMsg(msg); if (!conv.messages.some((m) => m.id === mapped.id)) { conv.messages.push(mapped); if (conv.messages.length > 20) conv.messages.shift(); @@ -132,7 +121,7 @@ export async function addMessageToConversation( conv.participants.push({ id: s.id, name: s.pushname || s.name || '', - isMe: s.isMe, + isMe: Boolean(s.isMe), }); } return conv;