diff --git a/src/apps/chatwoot/consumers/waha/message.any.ts b/src/apps/chatwoot/consumers/waha/message.any.ts index 0537ad22c..c21343fc1 100644 --- a/src/apps/chatwoot/consumers/waha/message.any.ts +++ b/src/apps/chatwoot/consumers/waha/message.any.ts @@ -75,6 +75,12 @@ class MessageAnyHandler extends MessageBaseHandler { payload: WAMessage, ): Promise { const protoMessage = this.getProtoMessage(payload); + + // Check for Facebook Ad first - but let it use the normal flow + if (this.isFacebookAd(payload, protoMessage)) { + return this.getFacebookAdMessage(payload, protoMessage); + } + let msg = await this.getTextMessage(payload, protoMessage); if (msg) { return msg; @@ -184,6 +190,92 @@ class MessageAnyHandler extends MessageBaseHandler { }; } + private isFacebookAd(payload: WAMessage, protoMessage: proto.Message | null): boolean { + // Check GOWS engine structure (_data with PascalCase) + const gowsExtendedText = payload._data?.Message?.extendedTextMessage; + if (gowsExtendedText?.contextInfo?.externalAdReply) { + return true; + } + + // Check other engines structure (protoMessage with camelCase) + if (protoMessage?.extendedTextMessage?.contextInfo?.externalAdReply) { + return true; + } + + return false; + } + + private extractAdReply(payload: WAMessage, protoMessage: proto.Message | null): any { + // First try: GOWS engine structure (_data with PascalCase) + const gowsExtendedText = payload._data?.Message?.extendedTextMessage; + if (gowsExtendedText?.contextInfo?.externalAdReply) { + return gowsExtendedText.contextInfo.externalAdReply; + } + + // Second try: Other engines structure (protoMessage with camelCase) + if (protoMessage?.extendedTextMessage?.contextInfo?.externalAdReply) { + return protoMessage.extendedTextMessage.contextInfo.externalAdReply; + } + + return null; + } + + private extractAdData(adReply: any): any { + return { + title: adReply.title || '', + body: adReply.body || '', + thumbnailURL: adReply.thumbnailURL || adReply.thumbnailUrl || '', + originalImageURL: adReply.originalImageURL || adReply.originalImageUrl || '', + sourceURL: adReply.sourceURL || adReply.sourceUrl || '', + sourceID: adReply.sourceID || adReply.sourceId || '', + }; + } + + private async getFacebookAdMessage( + payload: WAMessage, + protoMessage: proto.Message | null, + ): Promise { + const adReply = this.extractAdReply(payload, protoMessage); + const adData = this.extractAdData(adReply); + + // Create caption using the Facebook Ad template (reuse existing media caption system) + const content = this.l + .key(TKey.WA_TO_CW_MESSAGE_FACEBOOK_AD) + .render({ + payload: payload, + adData: adData + }); + + // Download Facebook Ad image and create attachment (reuse existing media flow) + const attachments: SendAttachment[] = []; + const imageURL = adData.originalImageURL || adData.thumbnailURL; + + if (imageURL) { + try { + this.logger.info(`Downloading Facebook Ad image from '${imageURL}'...`); + const buffer = await this.waha.fetch(imageURL); + const fileContent = buffer.toString('base64'); + + const attachment: SendAttachment = { + content: fileContent, + filename: 'facebook-ad-image.jpg', + encoding: 'base64', + }; + attachments.push(attachment); + this.logger.info(`Downloaded Facebook Ad image from '${imageURL}'`); + } catch (error) { + this.logger.error(`Failed to download Facebook Ad image: ${error.message}`); + } + } + + // Return as image with caption (same pattern as regular media messages) + return { + content: WhatsappToMarkdown(content), // This becomes the caption + attachments: attachments, // This becomes the image attachment + private: undefined, + }; + } + private getProtoMessage(payload: WAMessage): proto.Message | null { // GOWS if (payload._data.Message) { @@ -207,32 +299,32 @@ class MessageAnyHandler extends MessageBaseHandler { } async getAttachments(payload: WAMessage): Promise { - // - // WAHA - // + const attachments: SendAttachment[] = []; + + // Handle regular media attachments const hasMedia = payload.media?.url; - if (!hasMedia) { - return []; + if (hasMedia) { + const media = payload.media; + this.logger.info(`Downloading media from '${media.url}'...`); + const buffer = await this.waha.fetch(media.url); + const fileContent = buffer.toString('base64'); + let filename = media.filename; + if (!filename) { + const extension = mime.extension(media.mimetype); + filename = `no-filename.${extension}`; + } + + const attachment: SendAttachment = { + content: fileContent, + filename: filename, + encoding: 'base64', + }; + attachments.push(attachment); + this.logger.info(`Downloaded media from '${media.url}' as '${filename}'`); } + // Facebook Ad images are handled directly in getFacebookAdMessage method + // This method only handles regular media attachments - const attachments: SendAttachment[] = []; - const media = payload.media; - this.logger.info(`Downloading media from '${media.url}'...`); - const buffer = await this.waha.fetch(media.url); - const fileContent = buffer.toString('base64'); - let filename = media.filename; - if (!filename) { - const extension = mime.extension(media.mimetype); - filename = `no-filename.${extension}`; - } - - const attachment: SendAttachment = { - content: fileContent, - filename: filename, - encoding: 'base64', - }; - attachments.push(attachment); - this.logger.info(`Downloaded media from '${media.url}' as '${filename}'`); return attachments; } } diff --git a/src/apps/chatwoot/i18n/locales/en-US.yaml b/src/apps/chatwoot/i18n/locales/en-US.yaml index d81296535..34d999c23 100644 --- a/src/apps/chatwoot/i18n/locales/en-US.yaml +++ b/src/apps/chatwoot/i18n/locales/en-US.yaml @@ -114,6 +114,21 @@ whatsapp.to.chatwoot.message.unsupported: |- Details: [{{{details.text}}}]({{{details.url}}}) +whatsapp.to.chatwoot.message.facebook.ad: |- + 📢 **FACEBOOK AD** + + **Title:** {{{adData.title}}} + + **Description:** + {{{adData.body}}} + + **Ad Link:** [View on Facebook]({{{adData.sourceURL}}}) + **Ad ID:** {{{adData.sourceID}}} + + --- + 💬 **CUSTOMER MESSAGE:** + {{{payload.body}}} + app.connected.message: |- 🔗 **CONNECTED: WhatsApp Session to ChatWoot Inbox** diff --git a/src/apps/chatwoot/i18n/locales/pt-BR.yaml b/src/apps/chatwoot/i18n/locales/pt-BR.yaml index 665986d99..eaab6c4b2 100644 --- a/src/apps/chatwoot/i18n/locales/pt-BR.yaml +++ b/src/apps/chatwoot/i18n/locales/pt-BR.yaml @@ -237,3 +237,18 @@ whatsapp.to.chatwoot.message.unsupported: |- 📱 Por favor, abra o **WhatsApp** para visualizá-la. Detalhes: [{{{details.text}}}]({{{details.url}}}) + +whatsapp.to.chatwoot.message.facebook.ad: |- + 📢 **ANÚNCIO DO FACEBOOK** + + **Título:** {{{adData.title}}} + + **Descrição:** + {{{adData.body}}} + + **Link do Anúncio:** [Ver no Facebook]({{{adData.sourceURL}}}) + **ID do Anúncio:** {{{adData.sourceID}}} + + --- + 💬 **MENSAGEM DO CLIENTE:** + {{{payload.body}}} \ No newline at end of file diff --git a/src/apps/chatwoot/i18n/templates.ts b/src/apps/chatwoot/i18n/templates.ts index 10982bc18..39026f855 100644 --- a/src/apps/chatwoot/i18n/templates.ts +++ b/src/apps/chatwoot/i18n/templates.ts @@ -52,6 +52,7 @@ export enum TKey { WA_TO_CW_MESSAGE_CONTACTS = 'whatsapp.to.chatwoot.message.contacts', WA_TO_CW_MESSAGE_LOCATION = 'whatsapp.to.chatwoot.message.location', WA_TO_CW_MESSAGE_UNSUPPORTED = 'whatsapp.to.chatwoot.message.unsupported', + WA_TO_CW_MESSAGE_FACEBOOK_AD = 'whatsapp.to.chatwoot.message.facebook.ad', // // App Inbox @@ -132,6 +133,17 @@ export type TemplatePayloads = { [TKey.WA_TO_CW_MESSAGE_CONTACTS]: { contacts: SimpleVCardInfo[] }; [TKey.WA_TO_CW_MESSAGE_LOCATION]: { payload: any; message: proto.Message }; [TKey.WA_TO_CW_MESSAGE_UNSUPPORTED]: { details: Link }; + [TKey.WA_TO_CW_MESSAGE_FACEBOOK_AD]: { + payload: WAMessage; + adData: { + title: string; + body: string; + thumbnailURL: string; + originalImageURL: string; + sourceURL: string; + sourceID: string; + }; + }; [TKey.JOB_SCHEDULED_ERROR_HEADER]: void; [TKey.JOB_REPORT_ERROR]: { header: string;