305 lines
9.3 KiB
TypeScript
305 lines
9.3 KiB
TypeScript
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<string, Conversation>();
|
|
|
|
|
|
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 });
|
|
}
|
|
});
|
|
}
|