diff --git a/CHANGELOG.md b/CHANGELOG.md index 0fb487553..960f2f3b6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,14 @@ +# 1.7.1 (2024-04-03 10:19) + +### Fixed + +* Correction when sending files with captions on Whatsapp Business +* Correction in receiving messages with response on WhatsApp Business +* Correction when sending a reaction to a message on WhatsApp Business +* Correction of receiving reactions on WhatsApp business +* Removed mandatory description of rows from sendList +* Feature to collect message type in typebot + # 1.7.0 (2024-03-11 18:23) ### Feature diff --git a/src/validate/validate.schema.ts b/src/validate/validate.schema.ts index 82bb89024..b723826a4 100644 --- a/src/validate/validate.schema.ts +++ b/src/validate/validate.schema.ts @@ -406,7 +406,7 @@ export const listMessageSchema: JSONSchema7 = { description: { type: 'string' }, rowId: { type: 'string' }, }, - required: ['title', 'description', 'rowId'], + required: ['title', 'rowId'], ...isNotEmpty('title', 'description', 'rowId'), }, }, diff --git a/src/whatsapp/dto/typebot.dto.ts b/src/whatsapp/dto/typebot.dto.ts index c6c1fbdd0..6adfcf339 100644 --- a/src/whatsapp/dto/typebot.dto.ts +++ b/src/whatsapp/dto/typebot.dto.ts @@ -10,6 +10,7 @@ export class Session { export class PrefilledVariables { remoteJid?: string; pushName?: string; + messageType?: string; additionalData?: { [key: string]: any }; } diff --git a/src/whatsapp/services/typebot.service.ts b/src/whatsapp/services/typebot.service.ts index 1aa63b99e..4d79b6607 100644 --- a/src/whatsapp/services/typebot.service.ts +++ b/src/whatsapp/services/typebot.service.ts @@ -269,19 +269,27 @@ export class TypebotService { } private getTypeMessage(msg: any) { - this.logger.verbose('get type message'); - - const types = { - conversation: msg.conversation, - extendedTextMessage: msg.extendedTextMessage?.text, - responseRowId: msg.listResponseMessage?.singleSelectReply?.selectedRowId, - }; - - this.logger.verbose('type message: ' + types); + this.logger.verbose('get type message'); + const types = { + conversation: msg.conversation, + extendedTextMessage: msg.extendedTextMessage?.text, + audioMessage: msg.audioMessage?.url, + imageMessage: msg.imageMessage?.url, + videoMessage: msg.videoMessage?.url, + documentMessage: msg.documentMessage?.fileName, + contactMessage: msg.contactMessage?.displayName, + locationMessage: msg.locationMessage?.degreesLatitude, + viewOnceMessageV2: msg.viewOnceMessageV2?.message?.imageMessage?.url || msg.viewOnceMessageV2?.message?.videoMessage?.url || msg.viewOnceMessageV2?.message?.audioMessage?.url, + listResponseMessage: msg.listResponseMessage?.singleSelectReply?.selectedRowId, + responseRowId: msg.listResponseMessage?.singleSelectReply?.selectedRowId, + }; - return types; + const messageType = Object.keys(types).find(key => types[key] !== undefined) || 'unknown'; + + this.logger.verbose('Type message: ' + JSON.stringify(types)); + return { ...types, messageType }; } - + private getMessageContent(types: any) { this.logger.verbose('get message content'); const typeKey = Object.keys(types).find((key) => types[key] !== undefined); @@ -305,6 +313,101 @@ export class TypebotService { return messageContent; } + private getAudioMessageContent(msg: any) { + this.logger.verbose('get audio message content'); + + const types = this.getTypeMessage(msg); + + const audioContent = types.audioMessage; + + this.logger.verbose('audio message URL: ' + audioContent); + + return audioContent; + } + + private getImageMessageContent(msg: any) { + this.logger.verbose('get image message content'); + + const types = this.getTypeMessage(msg); + + const imageContent = types.imageMessage; + + this.logger.verbose('image message URL: ' + imageContent); + + return imageContent; + } + + private getVideoMessageContent(msg: any) { + this.logger.verbose('get video message content'); + + const types = this.getTypeMessage(msg); + + const videoContent = types.videoMessage; + + this.logger.verbose('video message URL: ' + videoContent); + + return videoContent; + } + + private getDocumentMessageContent(msg: any) { + this.logger.verbose('get document message content'); + + const types = this.getTypeMessage(msg); + + const documentContent = types.documentMessage; + + this.logger.verbose('document message fileName: ' + documentContent); + + return documentContent; + } + + private getContactMessageContent(msg: any) { + this.logger.verbose('get contact message content'); + + const types = this.getTypeMessage(msg); + + const contactContent = types.contactMessage; + + this.logger.verbose('contact message displayName: ' + contactContent); + + return contactContent; + } + + private getLocationMessageContent(msg: any) { + this.logger.verbose('get location message content'); + + const types = this.getTypeMessage(msg); + + const locationContent = types.locationMessage; + + this.logger.verbose('location message degreesLatitude: ' + locationContent); + + return locationContent; + } + + private getViewOnceMessageV2Content(msg: any) { + this.logger.verbose('get viewOnceMessageV2 content'); + + const types = this.getTypeMessage(msg); + + const viewOnceContent = types.viewOnceMessageV2; + + this.logger.verbose('viewOnceMessageV2 URL: ' + viewOnceContent); + + return viewOnceContent; + } + + private getListResponseMessageContent(msg: any) { + this.logger.verbose('get listResponseMessage content'); + + const types = this.getTypeMessage(msg); + + const listResponseContent = types.listResponseMessage || types.responseRowId; + + this.logger.verbose('listResponseMessage selectedRowId: ' + listResponseContent); + + return listResponseContent; + } public async createNewSession(instance: InstanceDto, data: any) { if (data.remoteJid === 'status@broadcast') return; const id = Math.floor(Math.random() * 10000000000).toString(); @@ -565,6 +668,7 @@ export class TypebotService { const delay_message = findTypebot.delay_message; const unknown_message = findTypebot.unknown_message; const listening_from_me = findTypebot.listening_from_me; + const messageType = this.getTypeMessage(msg.message).messageType; const session = sessions.find((session) => session.remoteJid === remoteJid); @@ -687,6 +791,9 @@ export class TypebotService { sessions: sessions, remoteJid: remoteJid, pushName: msg.pushName, + prefilledVariables: { + messageType: messageType, + }, }); await this.sendWAMessage(instance, remoteJid, data.messages, data.input, data.clientSideActions); diff --git a/src/whatsapp/services/whatsapp.business.service.ts b/src/whatsapp/services/whatsapp.business.service.ts index 874e75dcd..2b4a32cf1 100644 --- a/src/whatsapp/services/whatsapp.business.service.ts +++ b/src/whatsapp/services/whatsapp.business.service.ts @@ -183,14 +183,28 @@ export class BusinessStartupService extends WAStartupService { const message = received.messages[0]; let content: any = message.type + 'Message'; content = { [content]: message[message.type] }; - message.context ? (content.extendedTextMessage = { contextInfo: { stanzaId: message.context.id } }) : content; + message.context ? (content = { ...content, contextInfo: { stanzaId: message.context.id } }) : content; return content; } private messageInteractiveJson(received: any) { const message = received.messages[0]; - const content: any = { conversation: message.interactive[message.interactive.type].title }; - message.context ? (content.extendedTextMessage = { contextInfo: { stanzaId: message.context.id } }) : content; + let content: any = { conversation: message.interactive[message.interactive.type].title }; + message.context ? (content = { ...content, contextInfo: { stanzaId: message.context.id } }) : content; + return content; + } + + private messageReactionJson(received: any) { + const message = received.messages[0]; + let content: any = { + reactionMessage: { + key: { + id: message.reaction.message_id, + }, + text: message.reaction.emoji, + }, + }; + message.context ? (content = { ...content, contextInfo: { stanzaId: message.context.id } }) : content; return content; } @@ -198,18 +212,20 @@ export class BusinessStartupService extends WAStartupService { let content: any; const message = received.messages[0]; if (message.from === received.metadata.phone_number_id) { - content = { extendedTextMessage: { text: message.text.body } }; - message.context ? (content.extendedTextMessage.contextInfo = { stanzaId: message.context.id }) : content; + content = { + extendedTextMessage: { text: message.text.body }, + }; + message.context ? (content = { ...content, contextInfo: { stanzaId: message.context.id } }) : content; } else { content = { conversation: message.text.body }; - message.context ? (content.extendedTextMessage = { contextInfo: { stanzaId: message.context.id } }) : content; + message.context ? (content = { ...content, contextInfo: { stanzaId: message.context.id } }) : content; } return content; } private messageContactsJson(received: any) { const message = received.messages[0]; - const content: any = {}; + let content: any = {}; const vcard = (contact: any) => { this.logger.verbose('Creating vcard'); @@ -264,7 +280,7 @@ export class BusinessStartupService extends WAStartupService { }), }; } - message.context ? (content.extendedTextMessage = { contextInfo: { stanzaId: message.context.id } }) : content; + message.context ? (content = { ...content, contextInfo: { stanzaId: message.context.id } }) : content; return content; } @@ -287,8 +303,11 @@ export class BusinessStartupService extends WAStartupService { case 'document': messageType = 'documentMessage'; break; + case 'template': + messageType = 'conversation'; + break; default: - messageType = 'imageMessage'; + messageType = 'conversation'; break; } @@ -339,6 +358,18 @@ export class BusinessStartupService extends WAStartupService { owner: this.instance.name, // source: getDevice(received.key.id), }; + } else if (received?.messages[0].reaction) { + messageRaw = { + key, + pushName, + message: { + ...this.messageReactionJson(received), + }, + messageType: 'reactionMessage', + messageTimestamp: received.messages[0].timestamp as number, + owner: this.instance.name, + // source: getDevice(received.key.id), + }; } else if (received?.messages[0].contacts) { messageRaw = { key, @@ -374,6 +405,7 @@ export class BusinessStartupService extends WAStartupService { this.logger.log(messageRaw); this.logger.verbose('Sending data to webhook in event MESSAGES_UPSERT'); + this.sendDataWebhook(Events.MESSAGES_UPSERT, messageRaw); if (this.localChatwoot.enabled) { @@ -540,30 +572,68 @@ export class BusinessStartupService extends WAStartupService { } } - private convertMessageToRaw(message: any) { + private convertMessageToRaw(message: any, content: any) { + let convertMessage: any; + if (message?.conversation) { - return message; + if (content?.context?.message_id) { + convertMessage = { + ...message, + contextInfo: { stanzaId: content.context.message_id }, + }; + return convertMessage; + } + convertMessage = message; + return convertMessage; } if (message?.mediaType === 'image') { + if (content?.context?.message_id) { + convertMessage = { + imageMessage: message, + contextInfo: { stanzaId: content.context.message_id }, + }; + return convertMessage; + } return { imageMessage: message, }; } if (message?.mediaType === 'video') { + if (content?.context?.message_id) { + convertMessage = { + videoMessage: message, + contextInfo: { stanzaId: content.context.message_id }, + }; + return convertMessage; + } return { videoMessage: message, }; } if (message?.mediaType === 'audio') { + if (content?.context?.message_id) { + convertMessage = { + audioMessage: message, + contextInfo: { stanzaId: content.context.message_id }, + }; + return convertMessage; + } return { audioMessage: message, }; } if (message?.mediaType === 'document') { + if (content?.context?.message_id) { + convertMessage = { + documentMessage: message, + contextInfo: { stanzaId: content.context.message_id }, + }; + return convertMessage; + } return { documentMessage: message, }; @@ -610,7 +680,6 @@ export class BusinessStartupService extends WAStartupService { message_id: message['reactionMessage']['key']['id'], emoji: message['reactionMessage']['text'], }, - context: { message_id: quoted.id }, }; quoted ? (content.context = { message_id: quoted.id }) : content; return await this.post(content, 'messages'); @@ -670,6 +739,7 @@ export class BusinessStartupService extends WAStartupService { [message['mediaType']]: { [message['type']]: message['id'], preview_url: linkPreview, + caption: message['caption'], }, }; quoted ? (content.context = { message_id: quoted.id }) : content; @@ -771,10 +841,17 @@ export class BusinessStartupService extends WAStartupService { } })(); + if (messageSent?.error?.message) { + this.logger.error(messageSent.error.message); + throw messageSent.error.message.toString(); + } + + console.log(content); + const messageRaw: MessageRaw = { key: { fromMe: true, id: messageSent?.messages[0]?.id, remoteJid: this.createJid(number) }, //pushName: messageSent.pushName, - message: this.convertMessageToRaw(message), + message: this.convertMessageToRaw(message, content), messageType: this.renderMessageType(content.type), messageTimestamp: (messageSent?.messages[0]?.timestamp as number) || Math.round(new Date().getTime() / 1000), owner: this.instance.name, @@ -1154,6 +1231,10 @@ export class BusinessStartupService extends WAStartupService { } } + public async deleteMessage() { + throw new BadRequestException('Method not available on WhatsApp Business API'); + } + // methods not available on WhatsApp Business API public async mediaSticker() { throw new BadRequestException('Method not available on WhatsApp Business API'); @@ -1176,9 +1257,6 @@ export class BusinessStartupService extends WAStartupService { public async archiveChat() { throw new BadRequestException('Method not available on WhatsApp Business API'); } - public async deleteMessage() { - throw new BadRequestException('Method not available on WhatsApp Business API'); - } public async fetchProfile() { throw new BadRequestException('Method not available on WhatsApp Business API'); }