From 10c0fd988c90c7127eb0888d5e18e84f793538bf Mon Sep 17 00:00:00 2001 From: Juan Vargas Date: Tue, 17 Dec 2024 11:07:31 -0500 Subject: [PATCH] fix(meta): property to reply to messages --- packages/bot/src/core/coreClass.ts | 4 +- packages/bot/src/core/eventEmitterClass.ts | 2 +- .../provider-meta/__tests__/provider.test.ts | 37 ++++++++---- packages/provider-meta/src/interface/meta.ts | 22 +++---- packages/provider-meta/src/meta/provider.ts | 57 ++++++++++++------- packages/provider-meta/src/types.ts | 18 +++++- 6 files changed, 90 insertions(+), 50 deletions(-) diff --git a/packages/bot/src/core/coreClass.ts b/packages/bot/src/core/coreClass.ts index c668e8aed..8b3eb3839 100644 --- a/packages/bot/src/core/coreClass.ts +++ b/packages/bot/src/core/coreClass.ts @@ -683,8 +683,8 @@ class CoreClass

extends answer !== '__end_flow__' ) { if (answer !== '__capture_only_intended__') { - await this.provider.sendMessage(numberOrId, answer, ctxMessage) - this.emit('send_message', { ...ctxMessage, from: numberOrId, answer }) + const respMessage = await this.provider.sendMessage(numberOrId, answer, ctxMessage) + this.emit('send_message', { ...ctxMessage, from: numberOrId, answer, respMessage }) } } await this.database.save({ ...ctxMessage, from: numberOrId }) diff --git a/packages/bot/src/core/eventEmitterClass.ts b/packages/bot/src/core/eventEmitterClass.ts index 198c7d0c7..108478a2a 100644 --- a/packages/bot/src/core/eventEmitterClass.ts +++ b/packages/bot/src/core/eventEmitterClass.ts @@ -3,7 +3,7 @@ import { EventEmitter } from 'node:events' import type { TContext } from '../types' export type HostEventTypes = { - send_message: [arg1: TContext & { from: string; answer: string | string[] }] + send_message: [arg1: TContext & { from: string; answer: string | string[]; respMessage: any }] notice: [arg1: { title: string; instructions: string[] }] } diff --git a/packages/provider-meta/__tests__/provider.test.ts b/packages/provider-meta/__tests__/provider.test.ts index 3a36a8f56..f873739b3 100644 --- a/packages/provider-meta/__tests__/provider.test.ts +++ b/packages/provider-meta/__tests__/provider.test.ts @@ -161,7 +161,7 @@ describe('#MetaProvider', () => { // Arrange const fakeRecipient = '1234567890' const fakeMessage = 'Hello, World!' - metaProvider.sendMessageMeta = jest.fn() + metaProvider.sendMessageMeta = jest.fn() as never // Act await metaProvider.sendText(fakeRecipient, fakeMessage) @@ -192,7 +192,7 @@ describe('#MetaProvider', () => { long_number: '123.456', lat_number: '78.90', } - metaProvider.sendMessageMeta = jest.fn() + metaProvider.sendMessageMeta = jest.fn() as never // Act await metaProvider.sendLocation(fakeRecipient, fakeLocalization) @@ -217,7 +217,7 @@ describe('#MetaProvider', () => { // Arrange const fakeRecipient = '1234567890' const fakeText = 'Please share your location' - metaProvider.sendMessageMeta = jest.fn() + metaProvider.sendMessageMeta = jest.fn() as never // Act await metaProvider.sendLocationRequest(fakeRecipient, fakeText) @@ -250,7 +250,7 @@ describe('#MetaProvider', () => { emoji: '😄', } - metaProvider.sendMessageMeta = jest.fn() + metaProvider.sendMessageMeta = jest.fn() as never // Act await metaProvider.sendReaction(fakeRecipient, fakeReaction) @@ -275,7 +275,7 @@ describe('#MetaProvider', () => { const fakeRecipient = '1234567890' const fakePathVideo: any = 'path/to/audio.mp3' - metaProvider.sendMessageMeta = jest.fn() + metaProvider.sendMessageMeta = jest.fn() as never // Act await metaProvider.sendAudio(fakeRecipient, fakePathVideo) @@ -341,7 +341,7 @@ describe('#MetaProvider', () => { const fakeMimeType = 'application/pdf' const fakeNameOriginal = 'file.pdf' - metaProvider.sendMessageMeta = jest.fn() + metaProvider.sendMessageMeta = jest.fn() as never const formDataMock = { append: jest.fn(), @@ -380,14 +380,18 @@ describe('#MetaProvider', () => { const fakeRecipient = '1234567890' const fakeMessage = 'Hello, world!' const options = {} + const context = undefined jest.spyOn(metaProvider, 'sendText') jest.spyOn(metaProvider, 'sendButtons') jest.spyOn(metaProvider, 'sendMedia') + + metaProvider.sendMessageMeta = jest.fn() as never + // Act - await metaProvider.sendMessage(fakeRecipient, fakeMessage, options) + await metaProvider.sendMessage(fakeRecipient, fakeMessage, options, context) // Assert - expect(metaProvider.sendText).toHaveBeenCalledWith(fakeRecipient, fakeMessage) + expect(metaProvider.sendText).toHaveBeenCalledWith(fakeRecipient, fakeMessage, context) expect(metaProvider.sendButtons).not.toHaveBeenCalled() expect(metaProvider.sendMedia).not.toHaveBeenCalled() }) @@ -416,17 +420,26 @@ describe('#MetaProvider', () => { // Arrange const fakeRecipient = '1234567890' const fakeMessage = 'Here is a media file' - const fakeMedia = 'path/to/media.jpg' + const fakeMedia = 'https://example.com/video.mp4' const fakeOptions = { media: fakeMedia } + const context = undefined + + const fileDownloaded = 'path/to/downloaded/audio.mp3' + ;(utils.generalDownload as jest.MockedFunction).mockResolvedValue( + fileDownloaded + ) + jest.spyOn(mime, 'lookup').mockReturnValue('video/mp4') jest.spyOn(metaProvider, 'sendButtons') jest.spyOn(metaProvider, 'sendText') - jest.spyOn(metaProvider, 'sendMedia').mockResolvedValue() + jest.spyOn(metaProvider, 'sendMedia') //.mockResolvedValue() + + metaProvider.sendMessageMeta = jest.fn() as never // Act - await metaProvider.sendMessage(fakeRecipient, fakeMessage, fakeOptions) + await metaProvider.sendMessage(fakeRecipient, fakeMessage, fakeOptions, context) // Assert - expect(metaProvider.sendMedia).toHaveBeenCalledWith(fakeRecipient, fakeMessage, fakeMedia) + expect(metaProvider.sendMedia).toHaveBeenCalledWith(fakeRecipient, fakeMessage, fakeMedia, context) expect(metaProvider.sendText).not.toHaveBeenCalled() expect(metaProvider.sendButtons).not.toHaveBeenCalled() }) diff --git a/packages/provider-meta/src/interface/meta.ts b/packages/provider-meta/src/interface/meta.ts index 009f207f0..70dd49243 100644 --- a/packages/provider-meta/src/interface/meta.ts +++ b/packages/provider-meta/src/interface/meta.ts @@ -5,12 +5,12 @@ import type { TextMessageBody, Reaction, Localization, Message, SaveFileOptions, export interface MetaInterface { sendMessageMeta: (body: TextMessageBody) => void sendMessageToApi: (body: TextMessageBody) => Promise - sendText: (to: string, message: string) => Promise - sendImage: (to: string, mediaInput: string | null, caption: string) => Promise - sendImageUrl: (to: string, url: string, caption: string) => Promise - sendVideo: (to: string, pathVideo: string | null, caption: string) => Promise - sendVideoUrl: (to: string, url: string, caption: string) => Promise - sendMedia: (to: string, text: string, mediaInput: string) => Promise + sendText: (to: string, message: string, context: string | null) => Promise + sendImage: (to: string, mediaInput: string | null, caption: string, context: string | null) => Promise + sendImageUrl: (to: string, url: string, caption: string, context: string | null) => Promise + sendVideo: (to: string, pathVideo: string | null, caption: string, context: string | null) => Promise + sendVideoUrl: (to: string, url: string, caption: string, context: string | null) => Promise + sendMedia: (to: string, text: string, mediaInput: string, context: string | null) => Promise sendList: (to: string, list: MetaList) => Promise sendListComplete: ( to: string, @@ -42,11 +42,11 @@ export interface MetaInterface { ) => Promise sendContacts: (to: string, contact: any[]) => Promise sendCatalog: (number: any, bodyText: any, itemCatalogId: any) => Promise - sendMessage: (number: string, message: string, options?: SendOptions) => Promise + sendMessage: (number: string, message: string, options?: SendOptions, context?: string) => Promise sendReaction: (number: string, react: Reaction) => Promise - sendLocation: (to: string, localization: Localization) => Promise - sendLocationRequest: (to: string, bodyText: string) => Promise + sendLocation: (to: string, localization: Localization, context: string | null) => Promise + sendLocationRequest: (to: string, bodyText: string, context: string | null) => Promise saveFile: (ctx: Partial, options?: SaveFileOptions) => Promise - sendFile: (to: string, mediaInput: string | null, caption: string) => Promise - sendAudio: (to: string, fileOpus: string) => void + sendFile: (to: string, mediaInput: string | null, caption: string, context: string | null) => Promise + sendAudio: (to: string, fileOpus: string, context: string | null) => void } diff --git a/packages/provider-meta/src/meta/provider.ts b/packages/provider-meta/src/meta/provider.ts index 441746201..c8cc65b53 100644 --- a/packages/provider-meta/src/meta/provider.ts +++ b/packages/provider-meta/src/meta/provider.ts @@ -165,7 +165,7 @@ class MetaProvider extends ProviderClass implements MetaInterface }, ] - sendImage = async (to: string, mediaInput = null, caption: string) => { + sendImage = async (to: string, mediaInput = null, caption: string, context = null) => { to = parseMetaNumber(to) if (!mediaInput) throw new Error(`MEDIA_INPUT_NULL_: ${mediaInput}`) @@ -198,12 +198,13 @@ class MetaProvider extends ProviderClass implements MetaInterface caption, }, } + if (context) body.context = context return this.sendMessageMeta(body) } - sendImageUrl = async (to: string, url: string, caption = '') => { + sendImageUrl = async (to: string, url: string, caption = '', context = null) => { to = parseMetaNumber(to) - const body = { + const body: TextMessageBody = { messaging_product: 'whatsapp', recipient_type: 'individual', to, @@ -213,10 +214,11 @@ class MetaProvider extends ProviderClass implements MetaInterface caption, }, } + if (context) body.context = context return this.sendMessageMeta(body) } - sendVideo = async (to: string, pathVideo = null, caption: string) => { + sendVideo = async (to: string, pathVideo = null, caption: string, context = null) => { to = parseMetaNumber(to) if (!pathVideo) throw new Error(`MEDIA_INPUT_NULL_: ${pathVideo}`) @@ -239,7 +241,7 @@ class MetaProvider extends ProviderClass implements MetaInterface } ) - const body = { + const body: TextMessageBody = { messaging_product: 'whatsapp', to, type: 'video', @@ -248,12 +250,13 @@ class MetaProvider extends ProviderClass implements MetaInterface caption, }, } + if (context) body.context = context return this.sendMessageMeta(body) } - sendVideoUrl = async (to: string, url: string, caption = '') => { + sendVideoUrl = async (to: string, url: string, caption = '', context = null) => { to = parseMetaNumber(to) - const body = { + const body: TextMessageBody = { messaging_product: 'whatsapp', recipient_type: 'individual', to, @@ -263,22 +266,23 @@ class MetaProvider extends ProviderClass implements MetaInterface caption, }, } + if (context) body.context = context return this.sendMessageMeta(body) } - sendMedia = async (to: string, text = '', mediaInput: string) => { + sendMedia = async (to: string, text = '', mediaInput: string, context = null) => { to = parseMetaNumber(to) const fileDownloaded = await utils.generalDownload(mediaInput) const mimeType = mime.lookup(fileDownloaded) mediaInput = fileDownloaded - if (mimeType.includes('image')) return this.sendImage(to, mediaInput, text) - if (mimeType.includes('video')) return this.sendVideo(to, fileDownloaded, text) + if (mimeType.includes('image')) return this.sendImage(to, mediaInput, text, context) + if (mimeType.includes('video')) return this.sendVideo(to, fileDownloaded, text, context) if (mimeType.includes('audio')) { const fileOpus = await utils.convertAudio(mediaInput, 'mp3') - return this.sendAudio(to, fileOpus) + return this.sendAudio(to, fileOpus, context) } - return this.sendFile(to, mediaInput, text) + return this.sendFile(to, mediaInput, text, context) } sendList = async (to: string, list: MetaList) => { @@ -522,15 +526,15 @@ class MetaProvider extends ProviderClass implements MetaInterface return this.sendMessageMeta(body) } - sendMessage = async (to: string, message: string, options?: SendOptions): Promise => { + sendMessage = async (to: string, message: string, options?: SendOptions, context?: string): Promise => { to = parseMetaNumber(to) options = { ...options, ...options['options'] } if (options?.buttons?.length) return this.sendButtons(to, options.buttons, message) - if (options?.media) return this.sendMedia(to, message, options.media) - this.sendText(to, message) + if (options?.media) return this.sendMedia(to, message, options.media, context) + return this.sendText(to, message, context) } - sendFile = async (to: string, mediaInput = null, caption: string) => { + sendFile = async (to: string, mediaInput = null, caption: string, context = null) => { to = parseMetaNumber(to) if (!mediaInput) throw new Error(`MEDIA_INPUT_NULL_: ${mediaInput}`) @@ -556,7 +560,7 @@ class MetaProvider extends ProviderClass implements MetaInterface } ) - const body = { + const body: TextMessageBody = { messaging_product: 'whatsapp', to: to, type: 'document', @@ -566,10 +570,11 @@ class MetaProvider extends ProviderClass implements MetaInterface caption, }, } + if (context) body.context = context return this.sendMessageMeta(body) } - sendAudio = async (to: string, pathVideo = null) => { + sendAudio = async (to: string, pathVideo = null, context = null) => { to = parseMetaNumber(to) if (!pathVideo) throw new Error(`MEDIA_INPUT_NULL_: ${pathVideo}`) @@ -601,7 +606,7 @@ class MetaProvider extends ProviderClass implements MetaInterface } ) - const body = { + const body: TextMessageBody = { messaging_product: 'whatsapp', to, type: 'audio', @@ -609,6 +614,7 @@ class MetaProvider extends ProviderClass implements MetaInterface id: mediaId, }, } + if (context) body.context = context return this.sendMessageMeta(body) } @@ -684,7 +690,7 @@ class MetaProvider extends ProviderClass implements MetaInterface return this.sendMessageMeta(body) } - sendText = async (to: string, message: string) => { + sendText = async (to: string, message: string, context = null) => { to = parseMetaNumber(to) const body: TextMessageBody = { messaging_product: 'whatsapp', @@ -696,11 +702,17 @@ class MetaProvider extends ProviderClass implements MetaInterface body: message, }, } + if (context) body.context = context return this.sendMessageMeta(body) } - sendMessageMeta = (body: TextMessageBody): void => { - return this.queue.add(() => this.sendMessageToApi(body)) + sendMessageMeta = (body: TextMessageBody): Promise => { + return new Promise((resolve) => + this.queue.add(async () => { + const resp = await this.sendMessageToApi(body) + resolve(resp) + }) + ) } sendMessageToApi = async (body: TextMessageBody): Promise => { @@ -713,6 +725,7 @@ class MetaProvider extends ProviderClass implements MetaInterface Authorization: `Bearer ${this.globalVendorArgs.jwtToken}`, }, }) + response.data.payload = body return response.data } catch (error) { console.error(error.message) diff --git a/packages/provider-meta/src/types.ts b/packages/provider-meta/src/types.ts index 0402b05b3..97d994914 100644 --- a/packages/provider-meta/src/types.ts +++ b/packages/provider-meta/src/types.ts @@ -12,6 +12,17 @@ interface Video { link?: string } +export class File { + mime_type?: string + sha256?: string + id?: string + voice?: boolean + animated?: boolean + filename?: string + caption?: string + link?: string +} + interface TemplateMessage { name: string language: { @@ -143,10 +154,13 @@ export interface TextMessageBody { preview_url: boolean body: string } - image?: Image - video?: Video + image?: File + video?: File + audio?: File + document?: File interactive?: any contacts?: any[] + context?: string template?: TemplateMessage }