From d94e481866ca2b5d8f9c7dd8fd44813a146d558e Mon Sep 17 00:00:00 2001 From: Berg Pinheiro Date: Tue, 31 Mar 2026 16:49:02 -0300 Subject: [PATCH 1/2] [core] add Chatwoot support for WhatsApp status replies Detect WhatsApp status replies in incoming messages, append localized status context to Chatwoot content, and attach quoted status media when available while preserving current non-status behavior. --- src/apps/chatwoot/i18n/locales/ar-AE.yaml | 13 ++ src/apps/chatwoot/i18n/locales/bn-BD.yaml | 13 ++ src/apps/chatwoot/i18n/locales/de-DE.yaml | 13 ++ src/apps/chatwoot/i18n/locales/en-US.yaml | 13 ++ src/apps/chatwoot/i18n/locales/es-ES.yaml | 13 ++ src/apps/chatwoot/i18n/locales/fa-IR.yaml | 13 ++ src/apps/chatwoot/i18n/locales/fr-FR.yaml | 13 ++ src/apps/chatwoot/i18n/locales/he-IL.yaml | 13 ++ src/apps/chatwoot/i18n/locales/hi-IN.yaml | 13 ++ src/apps/chatwoot/i18n/locales/id-ID.yaml | 13 ++ src/apps/chatwoot/i18n/locales/it-IT.yaml | 13 ++ src/apps/chatwoot/i18n/locales/pa-PK.yaml | 13 ++ src/apps/chatwoot/i18n/locales/pt-BR.yaml | 13 ++ src/apps/chatwoot/i18n/locales/ru-RU.yaml | 13 ++ src/apps/chatwoot/i18n/locales/tr-TR.yaml | 13 ++ src/apps/chatwoot/i18n/locales/uk-UA.yaml | 13 ++ src/apps/chatwoot/i18n/locales/ur-PK.yaml | 13 ++ src/apps/chatwoot/i18n/locales/zh-CN.yaml | 13 ++ src/apps/chatwoot/i18n/templates.ts | 11 + .../messages/to/chatwoot/TextMessage.ts | 198 ++++++++++++++++-- 20 files changed, 426 insertions(+), 17 deletions(-) diff --git a/src/apps/chatwoot/i18n/locales/ar-AE.yaml b/src/apps/chatwoot/i18n/locales/ar-AE.yaml index 448ae9fa1..7867f7f57 100644 --- a/src/apps/chatwoot/i18n/locales/ar-AE.yaml +++ b/src/apps/chatwoot/i18n/locales/ar-AE.yaml @@ -487,6 +487,19 @@ whatsapp.to.chatwoot.message.pix: |- 💳 **تم إرسال PIX نسخ ولصق** {{/pixData}} +whatsapp.to.chatwoot.message.status.reply.with.text: |- + 📣 **رد على حالة واتساب** + 🏷️ **النوع:** {{type}} + 📝 **نص الحالة الأصلي:** {{quotedText}} + + {{content}} + +whatsapp.to.chatwoot.message.status.reply.without.text: |- + 📣 **رد على حالة واتساب** + 🏷️ **النوع:** {{type}} + + {{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 57f88ba63..1c30e06b0 100644 --- a/src/apps/chatwoot/i18n/locales/bn-BD.yaml +++ b/src/apps/chatwoot/i18n/locales/bn-BD.yaml @@ -487,6 +487,19 @@ whatsapp.to.chatwoot.message.pix: |- 💳 **PIX কপি এবং পেস্ট পাঠানো হয়েছে** {{/pixData}} +whatsapp.to.chatwoot.message.status.reply.with.text: |- + 📣 **WhatsApp স্ট্যাটাসের জবাব** + 🏷️ **ধরন:** {{type}} + 📝 **মূল স্ট্যাটাসের টেক্সট:** {{quotedText}} + + {{content}} + +whatsapp.to.chatwoot.message.status.reply.without.text: |- + 📣 **WhatsApp স্ট্যাটাসের জবাব** + 🏷️ **ধরন:** {{type}} + + {{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 144585ed6..83c22f350 100644 --- a/src/apps/chatwoot/i18n/locales/de-DE.yaml +++ b/src/apps/chatwoot/i18n/locales/de-DE.yaml @@ -490,6 +490,19 @@ whatsapp.to.chatwoot.message.pix: |- 💳 **PIX Kopieren und Einfügen gesendet** {{/pixData}} +whatsapp.to.chatwoot.message.status.reply.with.text: |- + 📣 **Antwort auf WhatsApp-Status** + 🏷️ **Typ:** {{type}} + 📝 **Originaler Status-Text:** {{quotedText}} + + {{content}} + +whatsapp.to.chatwoot.message.status.reply.without.text: |- + 📣 **Antwort auf WhatsApp-Status** + 🏷️ **Typ:** {{type}} + + {{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 ac0efde81..211f2125e 100644 --- a/src/apps/chatwoot/i18n/locales/en-US.yaml +++ b/src/apps/chatwoot/i18n/locales/en-US.yaml @@ -268,6 +268,19 @@ whatsapp.to.chatwoot.message.pix: |- 💳 **PIX Copy and Paste sent** {{/pixData}} +whatsapp.to.chatwoot.message.status.reply.with.text: |- + 📣 **Reply to WhatsApp Status** + 🏷️ **Type:** {{type}} + 📝 **Original status text:** {{quotedText}} + + {{content}} + +whatsapp.to.chatwoot.message.status.reply.without.text: |- + 📣 **Reply to WhatsApp Status** + 🏷️ **Type:** {{type}} + + {{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 3247cbf47..19f6b62ce 100644 --- a/src/apps/chatwoot/i18n/locales/es-ES.yaml +++ b/src/apps/chatwoot/i18n/locales/es-ES.yaml @@ -487,6 +487,19 @@ whatsapp.to.chatwoot.message.pix: |- 💳 **PIX Copiar y Pegar enviado** {{/pixData}} +whatsapp.to.chatwoot.message.status.reply.with.text: |- + 📣 **Respuesta al estado de WhatsApp** + 🏷️ **Tipo:** {{type}} + 📝 **Texto original del estado:** {{quotedText}} + + {{content}} + +whatsapp.to.chatwoot.message.status.reply.without.text: |- + 📣 **Respuesta al estado de WhatsApp** + 🏷️ **Tipo:** {{type}} + + {{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 6ea70279f..1abf58725 100644 --- a/src/apps/chatwoot/i18n/locales/fa-IR.yaml +++ b/src/apps/chatwoot/i18n/locales/fa-IR.yaml @@ -487,6 +487,19 @@ whatsapp.to.chatwoot.message.pix: |- 💳 **PIX کپی و چسباندن ارسال شد** {{/pixData}} +whatsapp.to.chatwoot.message.status.reply.with.text: |- + 📣 **پاسخ به وضعیت واتساپ** + 🏷️ **نوع:** {{type}} + 📝 **متن اصلی وضعیت:** {{quotedText}} + + {{content}} + +whatsapp.to.chatwoot.message.status.reply.without.text: |- + 📣 **پاسخ به وضعیت واتساپ** + 🏷️ **نوع:** {{type}} + + {{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 42d235ce4..e656017dd 100644 --- a/src/apps/chatwoot/i18n/locales/fr-FR.yaml +++ b/src/apps/chatwoot/i18n/locales/fr-FR.yaml @@ -487,6 +487,19 @@ whatsapp.to.chatwoot.message.pix: |- 💳 **PIX Copier et Coller envoyé** {{/pixData}} +whatsapp.to.chatwoot.message.status.reply.with.text: |- + 📣 **Réponse au statut WhatsApp** + 🏷️ **Type :** {{type}} + 📝 **Texte original du statut :** {{quotedText}} + + {{content}} + +whatsapp.to.chatwoot.message.status.reply.without.text: |- + 📣 **Réponse au statut WhatsApp** + 🏷️ **Type :** {{type}} + + {{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 f20c8aec6..cb51212f2 100644 --- a/src/apps/chatwoot/i18n/locales/he-IL.yaml +++ b/src/apps/chatwoot/i18n/locales/he-IL.yaml @@ -487,6 +487,19 @@ whatsapp.to.chatwoot.message.pix: |- 💳 **PIX העתק והדבק נשלח** {{/pixData}} +whatsapp.to.chatwoot.message.status.reply.with.text: |- + 📣 **תגובה לסטטוס ב-WhatsApp** + 🏷️ **סוג:** {{type}} + 📝 **טקסט הסטטוס המקורי:** {{quotedText}} + + {{content}} + +whatsapp.to.chatwoot.message.status.reply.without.text: |- + 📣 **תגובה לסטטוס ב-WhatsApp** + 🏷️ **סוג:** {{type}} + + {{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 fc8fe5803..8074da5f9 100644 --- a/src/apps/chatwoot/i18n/locales/hi-IN.yaml +++ b/src/apps/chatwoot/i18n/locales/hi-IN.yaml @@ -487,6 +487,19 @@ whatsapp.to.chatwoot.message.pix: |- 💳 **PIX कॉपी और पेस्ट भेजा गया** {{/pixData}} +whatsapp.to.chatwoot.message.status.reply.with.text: |- + 📣 **WhatsApp स्टेटस का जवाब** + 🏷️ **प्रकार:** {{type}} + 📝 **मूल स्टेटस टेक्स्ट:** {{quotedText}} + + {{content}} + +whatsapp.to.chatwoot.message.status.reply.without.text: |- + 📣 **WhatsApp स्टेटस का जवाब** + 🏷️ **प्रकार:** {{type}} + + {{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 05be670eb..43d0be4c6 100644 --- a/src/apps/chatwoot/i18n/locales/id-ID.yaml +++ b/src/apps/chatwoot/i18n/locales/id-ID.yaml @@ -487,6 +487,19 @@ whatsapp.to.chatwoot.message.pix: |- 💳 **PIX Salin dan Tempel terkirim** {{/pixData}} +whatsapp.to.chatwoot.message.status.reply.with.text: |- + 📣 **Balasan ke Status WhatsApp** + 🏷️ **Tipe:** {{type}} + 📝 **Teks status asli:** {{quotedText}} + + {{content}} + +whatsapp.to.chatwoot.message.status.reply.without.text: |- + 📣 **Balasan ke Status WhatsApp** + 🏷️ **Tipe:** {{type}} + + {{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 40fe3f0d4..332c5796e 100644 --- a/src/apps/chatwoot/i18n/locales/it-IT.yaml +++ b/src/apps/chatwoot/i18n/locales/it-IT.yaml @@ -268,6 +268,19 @@ whatsapp.to.chatwoot.message.pix: |- 💳 **PIX Copia e Incolla inviato** {{/pixData}} +whatsapp.to.chatwoot.message.status.reply.with.text: |- + 📣 **Risposta allo stato di WhatsApp** + 🏷️ **Tipo:** {{type}} + 📝 **Testo originale dello stato:** {{quotedText}} + + {{content}} + +whatsapp.to.chatwoot.message.status.reply.without.text: |- + 📣 **Risposta allo stato di WhatsApp** + 🏷️ **Tipo:** {{type}} + + {{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 e392454a3..1d17e51b0 100644 --- a/src/apps/chatwoot/i18n/locales/pa-PK.yaml +++ b/src/apps/chatwoot/i18n/locales/pa-PK.yaml @@ -487,6 +487,19 @@ whatsapp.to.chatwoot.message.pix: |- 💳 **PIX ਕਾਪੀ ਅਤੇ ਪੇਸਟ ਭੇਜਿਆ ਗਿਆ** {{/pixData}} +whatsapp.to.chatwoot.message.status.reply.with.text: |- + 📣 **WhatsApp ਸਟੇਟਸ ਦਾ ਜਵਾਬ** + 🏷️ **ਕਿਸਮ:** {{type}} + 📝 **ਅਸਲ ਸਟੇਟਸ ਲਿਖਤ:** {{quotedText}} + + {{content}} + +whatsapp.to.chatwoot.message.status.reply.without.text: |- + 📣 **WhatsApp ਸਟੇਟਸ ਦਾ ਜਵਾਬ** + 🏷️ **ਕਿਸਮ:** {{type}} + + {{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 e4a4f0e1e..b21294d37 100644 --- a/src/apps/chatwoot/i18n/locales/pt-BR.yaml +++ b/src/apps/chatwoot/i18n/locales/pt-BR.yaml @@ -492,6 +492,19 @@ whatsapp.to.chatwoot.message.pix: |- 💳 **PIX Copia e Cola enviado** {{/pixData}} +whatsapp.to.chatwoot.message.status.reply.with.text: |- + 📣 **Resposta a Status do WhatsApp** + 🏷️ **Tipo:** {{type}} + 📝 **Texto original do status:** {{quotedText}} + + {{content}} + +whatsapp.to.chatwoot.message.status.reply.without.text: |- + 📣 **Resposta a Status do WhatsApp** + 🏷️ **Tipo:** {{type}} + + {{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 602c19f82..f667ebd1a 100644 --- a/src/apps/chatwoot/i18n/locales/ru-RU.yaml +++ b/src/apps/chatwoot/i18n/locales/ru-RU.yaml @@ -487,6 +487,19 @@ whatsapp.to.chatwoot.message.pix: |- 💳 **PIX Копировать и вставить отправлен** {{/pixData}} +whatsapp.to.chatwoot.message.status.reply.with.text: |- + 📣 **Ответ на статус WhatsApp** + 🏷️ **Тип:** {{type}} + 📝 **Оригинальный текст статуса:** {{quotedText}} + + {{content}} + +whatsapp.to.chatwoot.message.status.reply.without.text: |- + 📣 **Ответ на статус WhatsApp** + 🏷️ **Тип:** {{type}} + + {{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 265b9d8d6..cd3b551f6 100644 --- a/src/apps/chatwoot/i18n/locales/tr-TR.yaml +++ b/src/apps/chatwoot/i18n/locales/tr-TR.yaml @@ -487,6 +487,19 @@ whatsapp.to.chatwoot.message.pix: |- 💳 **PIX Kopyala ve Yapıştır gönderildi** {{/pixData}} +whatsapp.to.chatwoot.message.status.reply.with.text: |- + 📣 **WhatsApp Durumuna Yanıt** + 🏷️ **Tür:** {{type}} + 📝 **Orijinal durum metni:** {{quotedText}} + + {{content}} + +whatsapp.to.chatwoot.message.status.reply.without.text: |- + 📣 **WhatsApp Durumuna Yanıt** + 🏷️ **Tür:** {{type}} + + {{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 626801238..ea43ef502 100644 --- a/src/apps/chatwoot/i18n/locales/uk-UA.yaml +++ b/src/apps/chatwoot/i18n/locales/uk-UA.yaml @@ -487,6 +487,19 @@ whatsapp.to.chatwoot.message.pix: |- 💳 **PIX Копіювати та Вставити надіслано** {{/pixData}} +whatsapp.to.chatwoot.message.status.reply.with.text: |- + 📣 **Відповідь на статус WhatsApp** + 🏷️ **Тип:** {{type}} + 📝 **Оригінальний текст статусу:** {{quotedText}} + + {{content}} + +whatsapp.to.chatwoot.message.status.reply.without.text: |- + 📣 **Відповідь на статус WhatsApp** + 🏷️ **Тип:** {{type}} + + {{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 a8b4a5674..7b9747160 100644 --- a/src/apps/chatwoot/i18n/locales/ur-PK.yaml +++ b/src/apps/chatwoot/i18n/locales/ur-PK.yaml @@ -487,6 +487,19 @@ whatsapp.to.chatwoot.message.pix: |- 💳 **PIX کاپی اور پیسٹ بھیج دیا گیا** {{/pixData}} +whatsapp.to.chatwoot.message.status.reply.with.text: |- + 📣 **WhatsApp اسٹیٹس کا جواب** + 🏷️ **قسم:** {{type}} + 📝 **اصل اسٹیٹس کا متن:** {{quotedText}} + + {{content}} + +whatsapp.to.chatwoot.message.status.reply.without.text: |- + 📣 **WhatsApp اسٹیٹس کا جواب** + 🏷️ **قسم:** {{type}} + + {{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 0ad7eed08..d5fd0e735 100644 --- a/src/apps/chatwoot/i18n/locales/zh-CN.yaml +++ b/src/apps/chatwoot/i18n/locales/zh-CN.yaml @@ -487,6 +487,19 @@ whatsapp.to.chatwoot.message.pix: |- 💳 **PIX 复制粘贴已发送** {{/pixData}} +whatsapp.to.chatwoot.message.status.reply.with.text: |- + 📣 **回复 WhatsApp 状态** + 🏷️ **类型:**{{type}} + 📝 **原始状态文本:**{{quotedText}} + + {{content}} + +whatsapp.to.chatwoot.message.status.reply.without.text: |- + 📣 **回复 WhatsApp 状态** + 🏷️ **类型:**{{type}} + + {{content}} + whatsapp.to.chatwoot.message.unsupported: |- ⚠️ **此收件箱不支持此消息类型。** 📱 请打开 **WhatsApp** 查看。 diff --git a/src/apps/chatwoot/i18n/templates.ts b/src/apps/chatwoot/i18n/templates.ts index d37239b53..6dd3e1e6b 100644 --- a/src/apps/chatwoot/i18n/templates.ts +++ b/src/apps/chatwoot/i18n/templates.ts @@ -54,6 +54,8 @@ 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_WITH_TEXT = 'whatsapp.to.chatwoot.message.status.reply.with.text', + WA_TO_CW_MESSAGE_STATUS_REPLY_WITHOUT_TEXT = 'whatsapp.to.chatwoot.message.status.reply.without.text', 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 +191,15 @@ export type TemplatePayloads = { message: proto.Message | null; pixData: PixTemplatePayload; }; + [TKey.WA_TO_CW_MESSAGE_STATUS_REPLY_WITH_TEXT]: { + type: string; + quotedText: string; + content: string; + }; + [TKey.WA_TO_CW_MESSAGE_STATUS_REPLY_WITHOUT_TEXT]: { + type: 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/TextMessage.ts b/src/apps/chatwoot/messages/to/chatwoot/TextMessage.ts index bf9b55f3c..f48fafc12 100644 --- a/src/apps/chatwoot/messages/to/chatwoot/TextMessage.ts +++ b/src/apps/chatwoot/messages/to/chatwoot/TextMessage.ts @@ -15,6 +15,20 @@ import { Job } from 'bullmq'; // eslint-disable-next-line @typescript-eslint/no-var-requires const mime = require('mime-types'); +type StatusReplyType = 'text' | 'image' | 'audio' | 'video' | 'unknown'; + +interface StatusReplyMediaDetails { + url: string; + mimetype?: string; + type: 'image' | 'audio' | 'video'; +} + +interface StatusReplyDetails { + statusType: StatusReplyType; + quotedText?: string; + media?: StatusReplyMediaDetails; +} + export class TextMessage implements MessageToChatWootConverter { constructor( private readonly locale: Locale, @@ -28,8 +42,12 @@ export class TextMessage implements MessageToChatWootConverter { protoMessage: proto.Message | null, ): Promise { void protoMessage; - const attachments = await this.getAttachments(payload); + const statusReplyDetails = this.getStatusReplyDetails(payload); + const attachments = await this.getAttachments(payload, statusReplyDetails); let content = this.locale.key(TKey.WA_TO_CW_MESSAGE).render({ payload }); + if (statusReplyDetails) { + content = this.wrapStatusReplyContent(content, statusReplyDetails); + } if (isEmptyString(content) && attachments.length === 0) { // No media, no content - return null so we can process it later return null; @@ -52,33 +70,179 @@ export class TextMessage implements MessageToChatWootConverter { } return { content: WhatsappToMarkdown(content), - attachments, + attachments: attachments, private: undefined, }; } - private async getAttachments(payload: WAMessage): Promise { - const hasMedia = payload.media?.url; - if (!hasMedia) { - return []; + private async getAttachments( + payload: WAMessage, + statusReplyDetails: StatusReplyDetails | null, + ): Promise { + const attachments: SendAttachment[] = []; + const media = payload.media; + if (media?.url) { + const attachment = await this.downloadAttachment(media.url, media.filename, { + fallbackBaseName: 'no-filename', + mimetype: media.mimetype, + }); + if (attachment) { + attachments.push(attachment); + } } - const media = payload.media!; - this.logger.debug(`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 statusMedia = statusReplyDetails?.media; + if (!statusMedia?.url) { + return attachments; + } + + const statusAttachment = await this.downloadAttachment( + statusMedia.url, + undefined, + { + fallbackBaseName: `status-reply-${statusMedia.type}`, + mimetype: statusMedia.mimetype, + }, + ); + if (statusAttachment) { + attachments.push(statusAttachment); } + return attachments; + } + private async downloadAttachment( + url: string, + filename: string | undefined, + options: { + fallbackBaseName: string; + mimetype?: string; + }, + ): Promise { + this.logger.debug(`Downloading media from '${url}'...`); + const buffer = await this.waha.fetch(url); + const fileContent = buffer.toString('base64'); + const resolvedFilename = this.resolveFilename( + filename, + options.fallbackBaseName, + options.mimetype, + ); const attachment: SendAttachment = { content: fileContent, - filename, + filename: resolvedFilename, encoding: 'base64', }; - this.logger.info(`Downloaded media from '${media.url}' as '${filename}'`); - return [attachment]; + this.logger.info(`Downloaded media from '${url}' as '${resolvedFilename}'`); + return attachment; + } + + private resolveFilename( + filename: string | undefined, + fallbackBaseName: string, + mimetype: string | undefined, + ): string { + if (!isEmptyString(filename)) { + return filename!; + } + const extensionFromMime = mimetype ? mime.extension(mimetype) : false; + const extension = extensionFromMime || 'bin'; + return `${fallbackBaseName}.${extension}`; + } + + private wrapStatusReplyContent( + content: string | null, + statusReplyDetails: StatusReplyDetails, + ): string { + const contentWithFallback = content ?? ''; + if (statusReplyDetails.quotedText) { + return this.locale.r(TKey.WA_TO_CW_MESSAGE_STATUS_REPLY_WITH_TEXT, { + type: statusReplyDetails.statusType, + quotedText: statusReplyDetails.quotedText, + content: contentWithFallback, + }); + } + + return this.locale.r(TKey.WA_TO_CW_MESSAGE_STATUS_REPLY_WITHOUT_TEXT, { + type: statusReplyDetails.statusType, + content: contentWithFallback, + }); + } + + private getStatusReplyDetails(payload: WAMessage): StatusReplyDetails | null { + if (!payload.replyTo) { + return null; + } + + const contextInfo = (payload as any)?._data?.Message?.extendedTextMessage + ?.contextInfo; + const remoteJid = contextInfo?.remoteJID ?? contextInfo?.remoteJid; + if (remoteJid !== 'status@broadcast') { + return null; + } + + const replyData = (payload.replyTo as any)?._data; + if (!replyData) { + return { + statusType: 'unknown', + }; + } + + const imageMessage = replyData.imageMessage; + if (imageMessage) { + return { + statusType: 'image', + media: this.getStatusReplyMediaDetails('image', imageMessage), + }; + } + + const audioMessage = replyData.audioMessage; + if (audioMessage) { + return { + statusType: 'audio', + media: this.getStatusReplyMediaDetails('audio', audioMessage), + }; + } + + const videoMessage = replyData.videoMessage; + if (videoMessage) { + return { + statusType: 'video', + media: this.getStatusReplyMediaDetails('video', videoMessage), + }; + } + + const extendedTextMessage = replyData.extendedTextMessage; + if (extendedTextMessage) { + return { + statusType: 'text', + quotedText: extendedTextMessage.text, + }; + } + + const conversation = replyData.conversation; + if (conversation) { + return { + statusType: 'text', + quotedText: conversation, + }; + } + + return { + statusType: 'unknown', + }; + } + + private getStatusReplyMediaDetails( + type: 'image' | 'audio' | 'video', + data: any, + ): StatusReplyMediaDetails | undefined { + const url = data?.URL ?? data?.url; + if (!url) { + return undefined; + } + return { + type: type, + url: url, + mimetype: data?.mimetype, + }; } } From e5aff4ecdbdaf3a3f0b1615093a3d081c39134bd Mon Sep 17 00:00:00 2001 From: Berg Pinheiro Date: Tue, 31 Mar 2026 17:07:11 -0300 Subject: [PATCH 2/2] [core] improve status reply media download for Chatwoot Resolve quoted status media through the session message API before attaching files so Chatwoot receives stable media URLs, with fallback to raw quoted metadata when needed. --- .../chatwoot/consumers/waha/message.any.ts | 8 ++- .../messages/to/chatwoot/TextMessage.ts | 50 ++++++++++++++++++- 2 files changed, 55 insertions(+), 3 deletions(-) diff --git a/src/apps/chatwoot/consumers/waha/message.any.ts b/src/apps/chatwoot/consumers/waha/message.any.ts index b2b81b235..a269c8738 100644 --- a/src/apps/chatwoot/consumers/waha/message.any.ts +++ b/src/apps/chatwoot/consumers/waha/message.any.ts @@ -86,7 +86,13 @@ export class MessageAnyHandler extends MessageBaseHandler { return msg; } - converter = new TextMessage(this.l, this.logger, this.waha, this.job); + converter = new TextMessage( + this.l, + this.logger, + this.waha, + this.job, + this.session, + ); msg = await converter.convert(payload, null); if (msg) { return msg; diff --git a/src/apps/chatwoot/messages/to/chatwoot/TextMessage.ts b/src/apps/chatwoot/messages/to/chatwoot/TextMessage.ts index f48fafc12..5a785279b 100644 --- a/src/apps/chatwoot/messages/to/chatwoot/TextMessage.ts +++ b/src/apps/chatwoot/messages/to/chatwoot/TextMessage.ts @@ -11,6 +11,7 @@ import { MessageToChatWootConverter } from '@waha/apps/chatwoot/messages/to/chat import { WhatsappToMarkdown } from '@waha/apps/chatwoot/messages/to/chatwoot/utils/markdown'; import { JobLink } from '@waha/apps/app_sdk/JobUtils'; import { Job } from 'bullmq'; +import { WAHASessionAPI } from '@waha/apps/app_sdk/waha/WAHASelf'; // eslint-disable-next-line @typescript-eslint/no-var-requires const mime = require('mime-types'); @@ -27,6 +28,7 @@ interface StatusReplyDetails { statusType: StatusReplyType; quotedText?: string; media?: StatusReplyMediaDetails; + replyMessageId?: string; } export class TextMessage implements MessageToChatWootConverter { @@ -35,6 +37,7 @@ export class TextMessage implements MessageToChatWootConverter { private readonly logger: ILogger, private readonly waha: WAHASelf, private readonly job: Job, + private readonly session: WAHASessionAPI, ) {} async convert( @@ -91,8 +94,10 @@ export class TextMessage implements MessageToChatWootConverter { } } - const statusMedia = statusReplyDetails?.media; - if (!statusMedia?.url) { + const statusMedia = + (await this.getStatusReplyMedia(payload, statusReplyDetails)) || + statusReplyDetails?.media; + if (!statusMedia || !statusMedia.url) { return attachments; } @@ -110,6 +115,39 @@ export class TextMessage implements MessageToChatWootConverter { return attachments; } + private async getStatusReplyMedia( + payload: WAMessage, + statusReplyDetails: StatusReplyDetails | null, + ): Promise { + void payload; + if (!statusReplyDetails?.replyMessageId) { + return statusReplyDetails?.media || null; + } + + try { + const quotedMessage = await this.session.getMessageById( + 'status@broadcast', + statusReplyDetails.replyMessageId, + true, + ); + const quotedMedia = quotedMessage?.media; + if (!quotedMedia?.url) { + return statusReplyDetails?.media || null; + } + return { + type: statusReplyDetails.statusType as 'image' | 'audio' | 'video', + url: quotedMedia.url, + mimetype: quotedMedia.mimetype || statusReplyDetails?.media?.mimetype, + }; + } catch (error) { + this.logger.warn( + `Failed to resolve status reply media via message id '${statusReplyDetails.replyMessageId}'`, + ); + this.logger.debug(error); + return statusReplyDetails?.media || null; + } + } + private async downloadAttachment( url: string, filename: string | undefined, @@ -180,9 +218,11 @@ export class TextMessage implements MessageToChatWootConverter { } const replyData = (payload.replyTo as any)?._data; + const replyMessageId = payload.replyTo.id; if (!replyData) { return { statusType: 'unknown', + replyMessageId: replyMessageId, }; } @@ -191,6 +231,7 @@ export class TextMessage implements MessageToChatWootConverter { return { statusType: 'image', media: this.getStatusReplyMediaDetails('image', imageMessage), + replyMessageId: replyMessageId, }; } @@ -199,6 +240,7 @@ export class TextMessage implements MessageToChatWootConverter { return { statusType: 'audio', media: this.getStatusReplyMediaDetails('audio', audioMessage), + replyMessageId: replyMessageId, }; } @@ -207,6 +249,7 @@ export class TextMessage implements MessageToChatWootConverter { return { statusType: 'video', media: this.getStatusReplyMediaDetails('video', videoMessage), + replyMessageId: replyMessageId, }; } @@ -215,6 +258,7 @@ export class TextMessage implements MessageToChatWootConverter { return { statusType: 'text', quotedText: extendedTextMessage.text, + replyMessageId: replyMessageId, }; } @@ -223,11 +267,13 @@ export class TextMessage implements MessageToChatWootConverter { return { statusType: 'text', quotedText: conversation, + replyMessageId: replyMessageId, }; } return { statusType: 'unknown', + replyMessageId: replyMessageId, }; }