diff --git a/src/apps/chatwoot/consumers/waha/message.any.ts b/src/apps/chatwoot/consumers/waha/message.any.ts index b2b81b23..a8dfc712 100644 --- a/src/apps/chatwoot/consumers/waha/message.any.ts +++ b/src/apps/chatwoot/consumers/waha/message.any.ts @@ -26,6 +26,7 @@ import { PollMessage, ListMessage, ShareContactMessage, + StatusReplyMessage, TextMessage, UnsupportedMessage, AlbumMessage, @@ -86,6 +87,18 @@ export class MessageAnyHandler extends MessageBaseHandler { return msg; } + converter = new StatusReplyMessage( + this.l, + this.logger, + this.waha, + this.job, + this.session, + ); + msg = await converter.convert(payload, protoMessage); + if (msg) { + return msg; + } + converter = new TextMessage(this.l, this.logger, this.waha, this.job); msg = await converter.convert(payload, null); if (msg) { diff --git a/src/apps/chatwoot/i18n/locales/ar-AE.yaml b/src/apps/chatwoot/i18n/locales/ar-AE.yaml index 7ba1d363..84c42cae 100644 --- a/src/apps/chatwoot/i18n/locales/ar-AE.yaml +++ b/src/apps/chatwoot/i18n/locales/ar-AE.yaml @@ -489,6 +489,15 @@ whatsapp.to.chatwoot.message.pix: |- 💳 **تم إرسال PIX نسخ ولصق** {{/pixData}} +whatsapp.to.chatwoot.message.status.reply: |- + 📣 **رد على حالة واتساب** + 🏷️ **النوع:** {{type}} + {{#quotedText}} + 📝 **نص الحالة:** {{quotedText}} + {{/quotedText}} + + {{content}} + whatsapp.to.chatwoot.message.unsupported: |- ⚠️ **هذا النوع من الرسائل غير مدعوم في هذا الصندوق الوارد.** 📱 يرجى فتح **WhatsApp** لعرضه. diff --git a/src/apps/chatwoot/i18n/locales/bn-BD.yaml b/src/apps/chatwoot/i18n/locales/bn-BD.yaml index e88d2900..8cc78a74 100644 --- a/src/apps/chatwoot/i18n/locales/bn-BD.yaml +++ b/src/apps/chatwoot/i18n/locales/bn-BD.yaml @@ -489,6 +489,15 @@ whatsapp.to.chatwoot.message.pix: |- 💳 **PIX কপি এবং পেস্ট পাঠানো হয়েছে** {{/pixData}} +whatsapp.to.chatwoot.message.status.reply: |- + 📣 **WhatsApp স্ট্যাটাসের জবাব** + 🏷️ **ধরন:** {{type}} + {{#quotedText}} + 📝 **স্ট্যাটাসের টেক্সট:** {{quotedText}} + {{/quotedText}} + + {{content}} + whatsapp.to.chatwoot.message.unsupported: |- ⚠️ **এই ধরনের বার্তা এই ইনবক্সে সমর্থিত নয়।** 📱 এটি দেখতে অনুগ্রহ করে **WhatsApp** খুলুন। diff --git a/src/apps/chatwoot/i18n/locales/de-DE.yaml b/src/apps/chatwoot/i18n/locales/de-DE.yaml index 38feb30f..195d0d65 100644 --- a/src/apps/chatwoot/i18n/locales/de-DE.yaml +++ b/src/apps/chatwoot/i18n/locales/de-DE.yaml @@ -492,6 +492,15 @@ whatsapp.to.chatwoot.message.pix: |- 💳 **PIX Kopieren und Einfügen gesendet** {{/pixData}} +whatsapp.to.chatwoot.message.status.reply: |- + 📣 **Antwort auf WhatsApp-Status** + 🏷️ **Typ:** {{type}} + {{#quotedText}} + 📝 **Status-Text:** {{quotedText}} + {{/quotedText}} + + {{content}} + whatsapp.to.chatwoot.message.unsupported: |- ⚠️ **Dieser Nachrichtentyp wird in dieser Inbox nicht unterstützt.** 📱 Bitte öffne **WhatsApp**, um ihn anzusehen. diff --git a/src/apps/chatwoot/i18n/locales/en-US.yaml b/src/apps/chatwoot/i18n/locales/en-US.yaml index 235b33df..9a984f2d 100644 --- a/src/apps/chatwoot/i18n/locales/en-US.yaml +++ b/src/apps/chatwoot/i18n/locales/en-US.yaml @@ -270,6 +270,15 @@ whatsapp.to.chatwoot.message.pix: |- 💳 **PIX Copy and Paste sent** {{/pixData}} +whatsapp.to.chatwoot.message.status.reply: |- + 📣 **Reply to WhatsApp Status** + 🏷️ **Type:** {{type}} + {{#quotedText}} + 📝 **Status Text:** {{quotedText}} + {{/quotedText}} + + {{content}} + whatsapp.to.chatwoot.message.has.media.no.media: |- {{#content}} {{{content}}} diff --git a/src/apps/chatwoot/i18n/locales/es-ES.yaml b/src/apps/chatwoot/i18n/locales/es-ES.yaml index 59989688..f99b4936 100644 --- a/src/apps/chatwoot/i18n/locales/es-ES.yaml +++ b/src/apps/chatwoot/i18n/locales/es-ES.yaml @@ -489,6 +489,15 @@ whatsapp.to.chatwoot.message.pix: |- 💳 **PIX Copiar y Pegar enviado** {{/pixData}} +whatsapp.to.chatwoot.message.status.reply: |- + 📣 **Respuesta al estado de WhatsApp** + 🏷️ **Tipo:** {{type}} + {{#quotedText}} + 📝 **Texto del Estado:** {{quotedText}} + {{/quotedText}} + + {{content}} + whatsapp.to.chatwoot.message.unsupported: |- ⚠️ **Este tipo de mensaje no es compatible con esta bandeja de entrada.** 📱 Por favor, abre **WhatsApp** para verlo. diff --git a/src/apps/chatwoot/i18n/locales/fa-IR.yaml b/src/apps/chatwoot/i18n/locales/fa-IR.yaml index 560b3313..6d032767 100644 --- a/src/apps/chatwoot/i18n/locales/fa-IR.yaml +++ b/src/apps/chatwoot/i18n/locales/fa-IR.yaml @@ -489,6 +489,15 @@ whatsapp.to.chatwoot.message.pix: |- 💳 **PIX کپی و چسباندن ارسال شد** {{/pixData}} +whatsapp.to.chatwoot.message.status.reply: |- + 📣 **پاسخ به وضعیت واتساپ** + 🏷️ **نوع:** {{type}} + {{#quotedText}} + 📝 **متن وضعیت:** {{quotedText}} + {{/quotedText}} + + {{content}} + whatsapp.to.chatwoot.message.unsupported: |- ⚠️ **این نوع پیام در این صندوق ورودی پشتیبانی نمی‌شود.** 📱 لطفاً برای مشاهده آن **WhatsApp** را باز کنید. diff --git a/src/apps/chatwoot/i18n/locales/fr-FR.yaml b/src/apps/chatwoot/i18n/locales/fr-FR.yaml index 67916463..829f00e8 100644 --- a/src/apps/chatwoot/i18n/locales/fr-FR.yaml +++ b/src/apps/chatwoot/i18n/locales/fr-FR.yaml @@ -489,6 +489,15 @@ whatsapp.to.chatwoot.message.pix: |- 💳 **PIX Copier et Coller envoyé** {{/pixData}} +whatsapp.to.chatwoot.message.status.reply: |- + 📣 **Réponse au statut WhatsApp** + 🏷️ **Type :** {{type}} + {{#quotedText}} + 📝 **Texte du Statut :** {{quotedText}} + {{/quotedText}} + + {{content}} + whatsapp.to.chatwoot.message.unsupported: |- ⚠️ **Ce type de message n’est pas pris en charge dans cette boîte de réception.** 📱 Veuillez ouvrir **WhatsApp** pour l’afficher. diff --git a/src/apps/chatwoot/i18n/locales/he-IL.yaml b/src/apps/chatwoot/i18n/locales/he-IL.yaml index ffa1c77e..f235b619 100644 --- a/src/apps/chatwoot/i18n/locales/he-IL.yaml +++ b/src/apps/chatwoot/i18n/locales/he-IL.yaml @@ -489,6 +489,15 @@ whatsapp.to.chatwoot.message.pix: |- 💳 **PIX העתק והדבק נשלח** {{/pixData}} +whatsapp.to.chatwoot.message.status.reply: |- + 📣 **תגובה לסטטוס ב-WhatsApp** + 🏷️ **סוג:** {{type}} + {{#quotedText}} + 📝 **טקסט הסטטוס:** {{quotedText}} + {{/quotedText}} + + {{content}} + whatsapp.to.chatwoot.message.unsupported: |- ⚠️ **סוג הודעה זה אינו נתמך בתיבה זו.** 📱 אנא פתח/י את **WhatsApp** כדי לצפות בזה. diff --git a/src/apps/chatwoot/i18n/locales/hi-IN.yaml b/src/apps/chatwoot/i18n/locales/hi-IN.yaml index 9718af00..45d7c871 100644 --- a/src/apps/chatwoot/i18n/locales/hi-IN.yaml +++ b/src/apps/chatwoot/i18n/locales/hi-IN.yaml @@ -489,6 +489,15 @@ whatsapp.to.chatwoot.message.pix: |- 💳 **PIX कॉपी और पेस्ट भेजा गया** {{/pixData}} +whatsapp.to.chatwoot.message.status.reply: |- + 📣 **WhatsApp स्टेटस का जवाब** + 🏷️ **प्रकार:** {{type}} + {{#quotedText}} + 📝 **स्टेटस टेक्स्ट:** {{quotedText}} + {{/quotedText}} + + {{content}} + whatsapp.to.chatwoot.message.unsupported: |- ⚠️ **इस प्रकार का संदेश इस इनबॉक्स में समर्थित नहीं है।** 📱 इसे देखने के लिए कृपया **WhatsApp** खोलें। diff --git a/src/apps/chatwoot/i18n/locales/id-ID.yaml b/src/apps/chatwoot/i18n/locales/id-ID.yaml index 17b01b04..341b8d8f 100644 --- a/src/apps/chatwoot/i18n/locales/id-ID.yaml +++ b/src/apps/chatwoot/i18n/locales/id-ID.yaml @@ -489,6 +489,15 @@ whatsapp.to.chatwoot.message.pix: |- 💳 **PIX Salin dan Tempel terkirim** {{/pixData}} +whatsapp.to.chatwoot.message.status.reply: |- + 📣 **Balasan ke Status WhatsApp** + 🏷️ **Tipe:** {{type}} + {{#quotedText}} + 📝 **Teks Status:** {{quotedText}} + {{/quotedText}} + + {{content}} + whatsapp.to.chatwoot.message.unsupported: |- ⚠️ **Jenis pesan ini tidak didukung di kotak masuk ini.** 📱 Silakan buka **WhatsApp** untuk melihatnya. diff --git a/src/apps/chatwoot/i18n/locales/it-IT.yaml b/src/apps/chatwoot/i18n/locales/it-IT.yaml index 41a29719..4c82cbbf 100644 --- a/src/apps/chatwoot/i18n/locales/it-IT.yaml +++ b/src/apps/chatwoot/i18n/locales/it-IT.yaml @@ -270,6 +270,15 @@ whatsapp.to.chatwoot.message.pix: |- 💳 **PIX Copia e Incolla inviato** {{/pixData}} +whatsapp.to.chatwoot.message.status.reply: |- + 📣 **Risposta allo stato di WhatsApp** + 🏷️ **Tipo:** {{type}} + {{#quotedText}} + 📝 **Testo dello Stato:** {{quotedText}} + {{/quotedText}} + + {{content}} + whatsapp.to.chatwoot.message.has.media.no.media: |- {{#content}} {{{content}}} diff --git a/src/apps/chatwoot/i18n/locales/pa-PK.yaml b/src/apps/chatwoot/i18n/locales/pa-PK.yaml index 8ddc1867..829ce79a 100644 --- a/src/apps/chatwoot/i18n/locales/pa-PK.yaml +++ b/src/apps/chatwoot/i18n/locales/pa-PK.yaml @@ -489,6 +489,15 @@ whatsapp.to.chatwoot.message.pix: |- 💳 **PIX ਕਾਪੀ ਅਤੇ ਪੇਸਟ ਭੇਜਿਆ ਗਿਆ** {{/pixData}} +whatsapp.to.chatwoot.message.status.reply: |- + 📣 **WhatsApp ਸਟੇਟਸ ਦਾ ਜਵਾਬ** + 🏷️ **ਕਿਸਮ:** {{type}} + {{#quotedText}} + 📝 **ਸਟੇਟਸ ਲਿਖਤ:** {{quotedText}} + {{/quotedText}} + + {{content}} + whatsapp.to.chatwoot.message.unsupported: |- ⚠️ **ਇਹ ਸੁਨੇਹੇ ਦੀ ਕਿਸਮ ਇਸ ਇਨਬਾਕਸ ਵਿੱਚ ਸਮਰਥਿਤ ਨਹੀਂ ਹੈ।** 📱 ਇਸਨੂੰ ਦੇਖਣ ਲਈ ਕਿਰਪਾ ਕਰਕੇ **WhatsApp** ਖੋਲ੍ਹੋ। diff --git a/src/apps/chatwoot/i18n/locales/pt-BR.yaml b/src/apps/chatwoot/i18n/locales/pt-BR.yaml index 09052fc4..513efd2b 100644 --- a/src/apps/chatwoot/i18n/locales/pt-BR.yaml +++ b/src/apps/chatwoot/i18n/locales/pt-BR.yaml @@ -494,6 +494,15 @@ whatsapp.to.chatwoot.message.pix: |- 💳 **PIX Copia e Cola enviado** {{/pixData}} +whatsapp.to.chatwoot.message.status.reply: |- + 📣 **Resposta a Status do WhatsApp** + 🏷️ **Tipo:** {{type}} + {{#quotedText}} + 📝 **Texto do Status:** {{quotedText}} + {{/quotedText}} + + {{content}} + whatsapp.to.chatwoot.message.unsupported: |- ⚠️ **Esse tipo de mensagem não é suportado nesta caixa de entrada.** 📱 Por favor, abra o **WhatsApp** para visualizá-la. diff --git a/src/apps/chatwoot/i18n/locales/ru-RU.yaml b/src/apps/chatwoot/i18n/locales/ru-RU.yaml index df4acab7..1d8ea10c 100644 --- a/src/apps/chatwoot/i18n/locales/ru-RU.yaml +++ b/src/apps/chatwoot/i18n/locales/ru-RU.yaml @@ -489,6 +489,15 @@ whatsapp.to.chatwoot.message.pix: |- 💳 **PIX Копировать и вставить отправлен** {{/pixData}} +whatsapp.to.chatwoot.message.status.reply: |- + 📣 **Ответ на статус WhatsApp** + 🏷️ **Тип:** {{type}} + {{#quotedText}} + 📝 **Текст Статуса:** {{quotedText}} + {{/quotedText}} + + {{content}} + whatsapp.to.chatwoot.message.unsupported: |- ⚠️ **Этот тип сообщения не поддерживается в этом Inbox.** 📱 Пожалуйста, откройте **WhatsApp**, чтобы просмотреть. diff --git a/src/apps/chatwoot/i18n/locales/tr-TR.yaml b/src/apps/chatwoot/i18n/locales/tr-TR.yaml index e859c6b7..c1e90328 100644 --- a/src/apps/chatwoot/i18n/locales/tr-TR.yaml +++ b/src/apps/chatwoot/i18n/locales/tr-TR.yaml @@ -489,6 +489,15 @@ whatsapp.to.chatwoot.message.pix: |- 💳 **PIX Kopyala ve Yapıştır gönderildi** {{/pixData}} +whatsapp.to.chatwoot.message.status.reply: |- + 📣 **WhatsApp Durumuna Yanıt** + 🏷️ **Tür:** {{type}} + {{#quotedText}} + 📝 **Durum Metni:** {{quotedText}} + {{/quotedText}} + + {{content}} + whatsapp.to.chatwoot.message.unsupported: |- ⚠️ **Bu mesaj türü bu gelen kutusunda desteklenmiyor.** 📱 Görüntülemek için lütfen **WhatsApp’ı** açın. diff --git a/src/apps/chatwoot/i18n/locales/uk-UA.yaml b/src/apps/chatwoot/i18n/locales/uk-UA.yaml index 8f2821b1..c87607b8 100644 --- a/src/apps/chatwoot/i18n/locales/uk-UA.yaml +++ b/src/apps/chatwoot/i18n/locales/uk-UA.yaml @@ -489,6 +489,15 @@ whatsapp.to.chatwoot.message.pix: |- 💳 **PIX Копіювати та Вставити надіслано** {{/pixData}} +whatsapp.to.chatwoot.message.status.reply: |- + 📣 **Відповідь на статус WhatsApp** + 🏷️ **Тип:** {{type}} + {{#quotedText}} + 📝 **Текст Статусу:** {{quotedText}} + {{/quotedText}} + + {{content}} + whatsapp.to.chatwoot.message.unsupported: |- ⚠️ **Цей тип повідомлення не підтримується в цих вхідних.** 📱 Будь ласка, відкрийте **WhatsApp**, щоб переглянути. diff --git a/src/apps/chatwoot/i18n/locales/ur-PK.yaml b/src/apps/chatwoot/i18n/locales/ur-PK.yaml index 87289346..27c8c526 100644 --- a/src/apps/chatwoot/i18n/locales/ur-PK.yaml +++ b/src/apps/chatwoot/i18n/locales/ur-PK.yaml @@ -489,6 +489,15 @@ whatsapp.to.chatwoot.message.pix: |- 💳 **PIX کاپی اور پیسٹ بھیج دیا گیا** {{/pixData}} +whatsapp.to.chatwoot.message.status.reply: |- + 📣 **WhatsApp اسٹیٹس کا جواب** + 🏷️ **قسم:** {{type}} + {{#quotedText}} + 📝 **اسٹیٹس کا متن:** {{quotedText}} + {{/quotedText}} + + {{content}} + whatsapp.to.chatwoot.message.unsupported: |- ⚠️ **اس قسم کا پیغام اس انبکس میں معاونت یافتہ نہیں ہے۔** 📱 اسے دیکھنے کے لیے براہ کرم **WhatsApp** کھولیں۔ diff --git a/src/apps/chatwoot/i18n/locales/zh-CN.yaml b/src/apps/chatwoot/i18n/locales/zh-CN.yaml index 3bf52277..676335c7 100644 --- a/src/apps/chatwoot/i18n/locales/zh-CN.yaml +++ b/src/apps/chatwoot/i18n/locales/zh-CN.yaml @@ -489,6 +489,15 @@ whatsapp.to.chatwoot.message.pix: |- 💳 **PIX 复制粘贴已发送** {{/pixData}} +whatsapp.to.chatwoot.message.status.reply: |- + 📣 **回复 WhatsApp 状态** + 🏷️ **类型:**{{type}} + {{#quotedText}} + 📝 **状态文本:**{{quotedText}} + {{/quotedText}} + + {{content}} + whatsapp.to.chatwoot.message.unsupported: |- ⚠️ **此收件箱不支持此消息类型。** 📱 请打开 **WhatsApp** 查看。 diff --git a/src/apps/chatwoot/i18n/templates.ts b/src/apps/chatwoot/i18n/templates.ts index d37239b5..95799553 100644 --- a/src/apps/chatwoot/i18n/templates.ts +++ b/src/apps/chatwoot/i18n/templates.ts @@ -54,6 +54,7 @@ export enum TKey { WA_TO_CW_MESSAGE_POLL = 'whatsapp.to.chatwoot.message.poll', WA_TO_CW_MESSAGE_EVENT = 'whatsapp.to.chatwoot.message.event', WA_TO_CW_MESSAGE_PIX = 'whatsapp.to.chatwoot.message.pix', + WA_TO_CW_MESSAGE_STATUS_REPLY = 'whatsapp.to.chatwoot.message.status.reply', WA_TO_CW_MESSAGE_UNSUPPORTED = 'whatsapp.to.chatwoot.message.unsupported', WA_TO_CW_MESSAGE_FACEBOOK_AD = 'whatsapp.to.chatwoot.message.facebook.ad', WA_TO_CW_MESSAGE_LIST = 'whatsapp.to.chatwoot.message.list', @@ -189,6 +190,11 @@ export type TemplatePayloads = { message: proto.Message | null; pixData: PixTemplatePayload; }; + [TKey.WA_TO_CW_MESSAGE_STATUS_REPLY]: { + type: string; + quotedText?: string; + content: string; + }; [TKey.WA_TO_CW_MESSAGE_UNSUPPORTED]: { details: Link }; [TKey.WA_TO_CW_MESSAGE_FACEBOOK_AD]: { payload: WAMessage; diff --git a/src/apps/chatwoot/messages/to/chatwoot/StatusReplyMessage.ts b/src/apps/chatwoot/messages/to/chatwoot/StatusReplyMessage.ts new file mode 100644 index 00000000..bb29aff4 --- /dev/null +++ b/src/apps/chatwoot/messages/to/chatwoot/StatusReplyMessage.ts @@ -0,0 +1,215 @@ +import type { proto } from '@adiwajshing/baileys'; +import { ILogger } from '@waha/apps/app_sdk/ILogger'; +import { WAHASelf, WAHASessionAPI } from '@waha/apps/app_sdk/waha/WAHASelf'; +import { ChatWootMessagePartial } from '@waha/apps/chatwoot/consumers/waha/base'; +import { Locale } from '@waha/apps/chatwoot/i18n/locale'; +import { TKey } from '@waha/apps/chatwoot/i18n/templates'; +import { WhatsappToMarkdown } from '@waha/apps/chatwoot/messages/to/chatwoot/utils/markdown'; +import { EngineHelper, QuotedMedia } from '@waha/apps/chatwoot/waha'; +import { Jid } from '@waha/core/engines/const'; +import { WAMessage } from '@waha/structures/responses.dto'; +import { Job } from 'bullmq'; +import { TextMessage } from './TextMessage'; +import { isEmptyString } from './utils/text'; +import { SendAttachment } from '@waha/apps/chatwoot/client/types'; +import esm from '@waha/vendor/esm'; + +// eslint-disable-next-line @typescript-eslint/no-var-requires +const mime = require('mime-types'); + +type StatusReplyType = 'text' | 'image' | 'audio' | 'video' | 'unknown'; + +interface StatusQuotedMessageInfo { + data?: any; + id: string; + type: StatusReplyType; + text?: string; +} + +export class StatusReplyMessage extends TextMessage { + constructor( + locale: Locale, + logger: ILogger, + waha: WAHASelf, + job: Job, + private readonly session: WAHASessionAPI, + ) { + super(locale, logger, waha, job); + } + + async convert( + payload: WAMessage, + protoMessage: proto.Message | null, + ): Promise { + if (!payload.replyTo?.id) { + return null; + } + if (!EngineHelper.IsReplyToStatus(payload, protoMessage)) { + return null; + } + + let attachments: SendAttachment[] = []; + const quoted = this.getStatusQuotedMessageInfo(payload); + const wamessage = await this.fetchStatusMessage(quoted.id); + if (wamessage?.media?.url) { + const extension = mime.extension(wamessage.media.mimetype); + wamessage.media.filename = `status-message.${extension}`; + const quotedAttachments = await super.getAttachments(wamessage); + attachments = [...attachments, ...quotedAttachments]; + } else { + const replyData = payload.replyTo._data; + const quotedMedia = EngineHelper.ExtractQuotedMedia(replyData); + if (quotedMedia) { + const attachment = + await this.downloadQuotedMediaAttachment(quotedMedia); + if (attachment) { + attachments.push(attachment); + } + } + } + + // Act as regular text message, but we'll adjust some properties + const msg = await super.convert(payload, protoMessage); + // First goes Quoted attachments, then what sender sent + msg.attachments = [...attachments, ...msg.attachments]; + + // Wrap content with additional info + const quotedText = quoted.text ?? wamessage?.body ?? payload.replyTo?.body; + if ( + isEmptyString(quotedText) && + isEmptyString(msg.content) && + attachments.length === 0 + ) { + // Nothing to send. + // Must check msg.content before wrapping it in status reply + return null; + } + + msg.content = this.locale.r(TKey.WA_TO_CW_MESSAGE_STATUS_REPLY, { + type: quoted.type, + quotedText: WhatsappToMarkdown(quotedText), + content: msg.content, + }); + return msg; + } + + private getStatusQuotedMessageInfo( + payload: WAMessage, + ): StatusQuotedMessageInfo { + const replyData = payload.replyTo._data; + const imageMessage = + replyData?.imageMessage || this.getWebjsMediaData(replyData, 'image'); + if (imageMessage) { + return { + data: replyData, + id: payload.replyTo.id, + type: 'image', + text: imageMessage.caption ?? payload.replyTo.body ?? replyData?.body, + }; + } + + const videoMessage = + replyData?.videoMessage || this.getWebjsMediaData(replyData, 'video'); + if (videoMessage) { + return { + data: replyData, + id: payload.replyTo.id, + type: 'video', + text: videoMessage.caption ?? payload.replyTo.body ?? replyData?.body, + }; + } + + const audioMessage = + replyData?.audioMessage || this.getWebjsMediaData(replyData, 'audio'); + if (audioMessage) { + return { + data: replyData, + id: payload.replyTo.id, + type: 'audio', + text: audioMessage.caption ?? payload.replyTo.body ?? replyData?.body, + }; + } + + const quotedText = + replyData?.extendedTextMessage?.text ?? + replyData?.conversation ?? + payload.replyTo.body ?? + replyData?.body; + return { + data: replyData, + id: payload.replyTo.id, + type: quotedText ? 'text' : 'unknown', + text: quotedText, + }; + } + + private getWebjsMediaData( + replyData: any, + expectedKind: 'image' | 'video' | 'audio', + ): any | null { + if (!replyData) { + return null; + } + const kind = replyData.kind ?? replyData.type; + if (kind !== expectedKind) { + return null; + } + return replyData; + } + + private async downloadQuotedMediaAttachment( + quotedMedia: QuotedMedia, + ): Promise { + try { + const decrypt = !!quotedMedia.mediaKey; + const mediaKeyBuffer = decrypt + ? Buffer.from(quotedMedia.mediaKey, 'base64') + : undefined; + const stream = await esm.b.downloadContentFromMessage( + { + mediaKey: mediaKeyBuffer, + directPath: quotedMedia.directPath, + url: quotedMedia.url, + }, + quotedMedia.mediaType as any, + {}, + decrypt, + ); + const chunks: Buffer[] = []; + for await (const chunk of stream) { + chunks.push(chunk); + } + const buffer = Buffer.concat(chunks); + const extension = mime.extension(quotedMedia.mimetype); + const filename = `status-message.${extension}`; + return { + content: buffer.toString('base64'), + filename: filename, + encoding: 'base64', + }; + } catch (error) { + this.logger.warn('Failed to download quoted status media directly'); + this.logger.debug(error); + return null; + } + } + + private async fetchStatusMessage( + replyMessageId: string, + ): Promise { + try { + const quotedMessage = await this.session.getMessageById( + Jid.BROADCAST, + replyMessageId, + true, + ); + return quotedMessage || null; + } catch (error) { + this.logger.warn( + `Failed to fetch original status message '${replyMessageId}'`, + ); + this.logger.debug(error); + return null; + } + } +} diff --git a/src/apps/chatwoot/messages/to/chatwoot/TextMessage.ts b/src/apps/chatwoot/messages/to/chatwoot/TextMessage.ts index bf9b55f3..b5dcb67c 100644 --- a/src/apps/chatwoot/messages/to/chatwoot/TextMessage.ts +++ b/src/apps/chatwoot/messages/to/chatwoot/TextMessage.ts @@ -17,10 +17,10 @@ const mime = require('mime-types'); export class TextMessage implements MessageToChatWootConverter { constructor( - private readonly locale: Locale, - private readonly logger: ILogger, - private readonly waha: WAHASelf, - private readonly job: Job, + protected readonly locale: Locale, + protected readonly logger: ILogger, + protected readonly waha: WAHASelf, + protected readonly job: Job, ) {} async convert( @@ -52,12 +52,14 @@ export class TextMessage implements MessageToChatWootConverter { } return { content: WhatsappToMarkdown(content), - attachments, + attachments: attachments, private: undefined, }; } - private async getAttachments(payload: WAMessage): Promise { + protected async getAttachments( + payload: WAMessage, + ): Promise { const hasMedia = payload.media?.url; if (!hasMedia) { return []; diff --git a/src/apps/chatwoot/messages/to/chatwoot/index.ts b/src/apps/chatwoot/messages/to/chatwoot/index.ts index 6e2436d7..ae9d1398 100644 --- a/src/apps/chatwoot/messages/to/chatwoot/index.ts +++ b/src/apps/chatwoot/messages/to/chatwoot/index.ts @@ -13,6 +13,7 @@ export { EventMessage } from './EventMessage'; export { PixMessage } from './PixMessage'; export { ListMessage } from './ListMessage'; export { AlbumMessage } from './AlbumMessage'; +export { StatusReplyMessage } from './StatusReplyMessage'; export type Awaitable = T | Promise; diff --git a/src/apps/chatwoot/waha/engines.ts b/src/apps/chatwoot/waha/engines.ts index 15029772..8aabd1bd 100644 --- a/src/apps/chatwoot/waha/engines.ts +++ b/src/apps/chatwoot/waha/engines.ts @@ -1,18 +1,34 @@ import * as lodash from 'lodash'; +import type { proto } from '@adiwajshing/baileys'; import { WhatsAppMessage } from '@waha/apps/chatwoot/storage'; import { WAHAEngine } from '@waha/structures/enums.dto'; import { getEngineName } from '@waha/version'; import { Message as MessageInstance } from 'whatsapp-web.js/src/structures'; +import { Jid } from '@waha/core/engines/const'; import { isLidUser, isPnUser, toCusFormat } from '@waha/core/utils/jids'; import { parseMessageIdSerialized } from '@waha/core/utils/ids'; import { WAMessage } from '@waha/structures/responses.dto'; import { CallData } from '@waha/structures/calls.dto'; +import { getContextInfo } from '@waha/core/utils/pwa'; + +export interface QuotedMedia { + url?: string; + directPath: string; + mediaKey?: string; + mimetype: string; + mediaType: string; +} interface IEngineHelper { ChatID(message: WAMessage | any): string; CallChatID(call: CallData | any): string; + IsReplyToStatus( + message: WAMessage, + protoMessage: proto.Message | null, + ): boolean; + WhatsAppMessageKeys(message: any): WhatsAppMessage; IterateMessages( @@ -24,6 +40,8 @@ interface IEngineHelper { FilterChatIdsForMessages(chats: string[]): string[]; SupportsAllChatForMessage(): boolean; + + ExtractQuotedMedia(replyData: any): QuotedMedia | null; } class NOWEBHelper implements IEngineHelper { @@ -35,6 +53,14 @@ class NOWEBHelper implements IEngineHelper { return call.from; } + IsReplyToStatus( + message: WAMessage, + protoMessage: proto.Message | null, + ): boolean { + void message; + return getContextInfo(protoMessage)?.remoteJid === Jid.BROADCAST; + } + WhatsAppMessageKeys(message: any): WhatsAppMessage { const timestamp = parseInt(message.messageTimestamp) * 1000; return { @@ -63,6 +89,10 @@ class NOWEBHelper implements IEngineHelper { SupportsAllChatForMessage(): boolean { return true; } + + ExtractQuotedMedia(replyData: any): QuotedMedia | null { + return extractProtoQuotedMedia(replyData); + } } class GOWSHelper implements IEngineHelper { @@ -74,6 +104,14 @@ class GOWSHelper implements IEngineHelper { return call._data?.CallCreator || call.from; } + IsReplyToStatus( + message: WAMessage, + protoMessage: proto.Message | null, + ): boolean { + void message; + return getContextInfo(protoMessage)?.remoteJid === Jid.BROADCAST; + } + /** * Parse API response and get the data * API Response depends on engine right now @@ -107,6 +145,10 @@ class GOWSHelper implements IEngineHelper { ContactIsMy(contact) { return true; } + + ExtractQuotedMedia(replyData: any): QuotedMedia | null { + return extractProtoQuotedMedia(replyData, true); + } } class WEBJSHelper implements IEngineHelper { @@ -118,6 +160,14 @@ class WEBJSHelper implements IEngineHelper { return call.from; } + IsReplyToStatus( + message: WAMessage, + protoMessage: proto.Message | null, + ): boolean { + void protoMessage; + return message._data?.quotedRemoteJid === Jid.BROADCAST; + } + /** * Parse API response and get the data for WEBJS engine */ @@ -161,6 +211,10 @@ class WEBJSHelper implements IEngineHelper { ContactIsMy(contact) { return contact.isMyContact; } + + ExtractQuotedMedia(replyData: any): QuotedMedia | null { + return extractBrowserQuotedMedia(replyData); + } } class WPPHelper implements IEngineHelper { @@ -172,6 +226,14 @@ class WPPHelper implements IEngineHelper { return call.from; } + IsReplyToStatus( + message: WAMessage, + protoMessage: proto.Message | null, + ): boolean { + void protoMessage; + return message._data?.quotedRemoteJid === Jid.BROADCAST; + } + /** * Parse API response and get the data for WPP engine. * WPP returns message.id as a composite string "fromMe_chatId_msgId". @@ -217,6 +279,10 @@ class WPPHelper implements IEngineHelper { ContactIsMy(contact) { return contact.isMyContact; } + + ExtractQuotedMedia(replyData: any): QuotedMedia | null { + return extractBrowserQuotedMedia(replyData); + } } // Choose the right EngineHelper based on getEngineName() function @@ -241,6 +307,66 @@ switch (getEngineName()) { export const EngineHelper = engineHelper; +// +// Quoted media extraction helpers +// + +function extractProtoQuotedMedia( + replyData: any, + capitalUrl = false, +): QuotedMedia | null { + if (!replyData) { + return null; + } + const mediaTypes: Array<[string, string]> = [ + ['imageMessage', 'image'], + ['videoMessage', 'video'], + ['audioMessage', 'audio'], + ]; + for (const [key, mediaType] of mediaTypes) { + const msg = replyData[key]; + if (!msg) { + continue; + } + const url: string | undefined = capitalUrl ? msg.URL ?? msg.url : msg.url; + const directPath: string = msg.directPath; + const mimetype: string = msg.mimetype; + if (!directPath || !mimetype) { + continue; + } + return { + url: url, + directPath: directPath, + mediaKey: msg.mediaKey, + mimetype: mimetype, + mediaType: mediaType, + }; + } + return null; +} + +function extractBrowserQuotedMedia(replyData: any): QuotedMedia | null { + if (!replyData) { + return null; + } + const kind: string = replyData.kind ?? replyData.type; + if (kind !== 'image' && kind !== 'video' && kind !== 'audio') { + return null; + } + const directPath: string = replyData.directPath; + const mimetype: string = replyData.mimetype; + if (!directPath || !mimetype) { + return null; + } + return { + url: replyData.deprecatedMms3Url, + directPath: directPath, + mediaKey: replyData.mediaKey, + mimetype: mimetype, + mediaType: kind, + }; +} + function preferPnChats(chats: string[]): string[] { const unique = lodash.uniq(chats ?? []); const hasPn = unique.some(isPnUser); diff --git a/src/apps/chatwoot/waha/index.ts b/src/apps/chatwoot/waha/index.ts index 44f6be02..7ad032fe 100644 --- a/src/apps/chatwoot/waha/index.ts +++ b/src/apps/chatwoot/waha/index.ts @@ -1,3 +1,5 @@ import { EngineHelper } from './engines'; +import type { QuotedMedia } from './engines'; export { EngineHelper }; +export type { QuotedMedia }; diff --git a/src/core/utils/pwa.ts b/src/core/utils/pwa.ts index 4c7f08b1..94d09f0a 100644 --- a/src/core/utils/pwa.ts +++ b/src/core/utils/pwa.ts @@ -3,6 +3,7 @@ */ import { normalizeMessageContent, proto } from '@adiwajshing/baileys'; +import esm from '@waha/vendor/esm'; export function IsEditedMessage(message: proto.IMessage): boolean { message = normalizeMessageContent(message); @@ -37,3 +38,11 @@ export function IsHistorySyncNotification(message: proto.IMessage): boolean { } return true; } + +export function getContextInfo( + protoMessage: proto.Message | null, +): proto.IContextInfo | null { + const type = esm.b.getContentType(protoMessage); + const message = protoMessage[type] as any; + return message?.contextInfo; +}