import { Application } from 'express'; import express from 'express'; import { WhatsAppMessage, Conversation, Msg } from '../types'; import { addMessageToConversation } from '../store/conversation'; import { mapWhatsAppMessageToMsg } from '../messageProcessor'; import { getHandler } from '../chatHandlers'; import axios from 'axios'; interface UIMessage { chatId: string; id: string; from: string; to: string; ts: number; type: 'chat'; text: string; mediaUrl: string | null; mentions: string[] | null; meta: { ack: number; hasReaction: boolean; isQuoted: boolean; }; } const conversations = new Map(); export const processIncomingMessageForConversation = ( conversation: Conversation, message: WhatsAppMessage ): void => { const mappedMsg = mapWhatsAppMessageToMsg(message); // Avoid duplicates if multiple webhook events deliver the same message if (!conversation.messages.some((m) => m.id === mappedMsg.id)) { conversation.messages.push(mappedMsg); // if (conversation.messages.length > 20) { // conversation.messages.shift(); // Keep only the last 20 messages // } } // Add new participants if necessary const sender = message.sender; if (sender && !conversation.participants.some((p) => p.id === sender.id)) { conversation.participants.push({ id: sender.id, name: sender.pushname || sender.name || '', isMe: sender.isMe, // isAdmin property is not available here, it's part of group metadata }); } // The conversation object is modified directly }; export function registerChatUIRoutes(app: Application, openWaUrl: string | undefined) { app.post('/chatUI/sendMessage', async (req: express.Request, res: express.Response) => { console.log(`[routes] POST /chatUI/sendMessage`, 'v2.0'); try { if (!req.body || !req.body.data) { throw new Error('Invalid request format'); } const uiMessage: UIMessage = req.body.data; if (!uiMessage.text || !uiMessage.chatId) { throw new Error('Missing required fields: text and chatId'); } // convertimos el mensaje de la UI a un mensaje de whatsapp const message: WhatsAppMessage = { id: uiMessage.id, body: uiMessage.text, text: uiMessage.text, type: uiMessage.type, from: uiMessage.from, to: uiMessage.to, chatId: uiMessage.chatId, timestamp: uiMessage.ts * 1000, // Convertir a milisegundos fromMe: true, viewed: false, t: uiMessage.ts * 1000, notifyName: 'User', author: null, invis: false, isNewMsg: true, star: false, kicNotified: false, recvFresh: true, isFromTemplate: false, pollInvalidated: false, isSentCagPollCreation: false, latestEditMsgKey: null, latestEditSenderTimestampMs: null, mentionedJidList: uiMessage.mentions || [], groupMentions: [], isEventCanceled: false, eventInvalidated: false, isVcardOverMmsDocument: false, labels: [], hasReaction: uiMessage.meta.hasReaction, ephemeralDuration: 0, ephemeralSettingTimestamp: 0, disappearingModeInitiator: '', disappearingModeTrigger: '', viewMode: '', productHeaderImageRejected: false, lastPlaybackProgress: 0, isDynamicReplyButtonsMsg: false, isCarouselCard: false, parentMsgId: null, callSilenceReason: null, isVideoCall: false, callDuration: null, callParticipants: null, isMdHistoryMsg: false, stickerSentTs: 0, isAvatar: false, lastUpdateFromServerTs: 0, invokedBotWid: null, bizBotType: null, botResponseTargetId: null, botPluginType: null, botPluginReferenceIndex: null, botPluginSearchProvider: null, botPluginSearchUrl: null, botPluginSearchQuery: null, botPluginMaybeParent: false, botReelPluginThumbnailCdnUrl: null, botMessageDisclaimerText: null, botMsgBodyType: null, reportingTokenInfo: null, requiresDirectConnection: false, bizContentPlaceholderType: null, hostedBizEncStateMismatch: false, senderOrRecipientAccountTypeHosted: false, placeholderCreatedWhenAccountIsHosted: false, device: 0, local: true, mId: uiMessage.id, senderId: uiMessage.from, content: uiMessage.text, isGroupMsg: false, isQuotedMsgAvailable: uiMessage.meta.isQuoted, isMedia: !!uiMessage.mediaUrl, mediaData: uiMessage.mediaUrl ? { url: uiMessage.mediaUrl } : {}, isOnline: false, sender: { id: uiMessage.from, name: 'User', shortName: 'User', pushname: 'User', type: 'user', isBusiness: false, isEnterprise: false, isSmb: false, isContactSyncCompleted: 1, disappearingModeDuration: 0, disappearingModeSettingTimestamp: 0, textStatusLastUpdateTime: 0, syncToAddressbook: false, formattedName: 'User', isMe: true, isMyContact: false, isPSA: false, isUser: true, isVerified: false, isWAContact: true, msgs: [] }, chat: { id: uiMessage.chatId, name: 'Chat', isGroup: false, participantsCount: 2, formattedTitle: 'Chat', pendingMsgs: false, t: uiMessage.ts * 1000, unreadCount: 0, unreadDividerOffset: 0, archive: false, isReadOnly: false, isLocked: false, muteExpiration: 0, isAutoMuted: false, notSpam: true, pin: 0, ephemeralDuration: 0, ephemeralSettingTimestamp: 0, disappearingModeInitiator: '', disappearingModeTrigger: '', createdLocally: false, unreadMentionsOfMe: [], unreadMentionCount: 0, hasUnreadMention: false, archiveAtMentionViewedInDrawer: false, hasChatBeenOpened: true, tcToken: {}, tcTokenTimestamp: 0, tcTokenSenderTimestamp: 0, endOfHistoryTransferType: 0, pendingInitialLoading: false, unreadEditTimestampMs: 0, celebrationAnimationLastPlayed: 0, hasRequestedWelcomeMsg: false, canSend: true, groupMetadata: {}, isOnline: false, contact: { id: uiMessage.chatId, name: 'User', shortName: 'User', pushname: 'User', type: 'user', isBusiness: false, isEnterprise: false, isSmb: false, isContactSyncCompleted: 1, disappearingModeDuration: 0, disappearingModeSettingTimestamp: 0, textStatusLastUpdateTime: 0, syncToAddressbook: false, formattedName: 'User', isMe: true, isMyContact: false, isPSA: false, isUser: true, isVerified: false, isWAContact: true, msgs: [] }, msgs: [] } }; // nosotros mismos tenemos que buscar la conversacion in la memoria, conversations. si no existe, crearla let conversation = conversations.get(uiMessage.chatId); if (!conversation) { conversation = { chatId: uiMessage.chatId, title: 'Chat', isGroup: false, unreadCount: 0, participants: [], messages: [], createdAt: Date.now() } conversations.set(uiMessage.chatId, conversation); } processIncomingMessageForConversation(conversation, message); const handler = getHandler(uiMessage.chatId); if (!handler) throw new Error('No handler configured'); let reply: string; if (typeof handler === 'string') { console.log(`🔗 Calling agent at ${handler} for conversation ${uiMessage.chatId}\n`); const agentRes = await axios.post(handler, { conversation }); reply = agentRes.data.reply || agentRes.data; } else { reply = await handler(conversation); } const replyMsg: Msg = { id: uiMessage.id, from: uiMessage.to, to: uiMessage.from, ts: uiMessage.ts, type: 'chat', text: reply, mediaUrl: undefined, mentions: [], meta: { ack: 0, hasReaction: false, isQuoted: false } }; conversation.messages.push(replyMsg); console.log('conversation', conversation); // Devolver la conversación completa /* it needs to be able to be parsed by the UI const response = await axios.post(routerUrl, {data:messagePayload}); console.log('Message sent, raw response:', response.data); // Log raw response const conversation = response.data; */ res.json(conversation); } catch (err: any) { console.error('Error processing UI message:', err.message); res.status(400).json({ error: err.message }); } }); }