[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.

closes #1995 - Reply message to WhatsApp Status Context in Chatwoot Messages
fixes #1991 - Include WhatsApp Status Context in Chatwoot Messages
This commit is contained in:
Berg Pinheiro authored and devlikepro committed 2026-04-15 13:05:20 +07:00
1 parent 32e57cab98
commit f711fcca98
26 files changed
+542 -6

No files matched your search

@@ -26,6 +26,7 @@ import {
PollMessage,
ListMessage,
ShareContactMessage,
StatusReplyMessage,
TextMessage,
UnsupportedMessage,
AlbumMessage,
@@ -86,6 +87,18 @@ export class MessageAnyHandler extends MessageBaseHandler<WAMessage> {
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) {
@@ -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** لعرضه.
@@ -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** খুলুন।
@@ -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.
@@ -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}}}
@@ -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.
@@ -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** را باز کنید.
@@ -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.
@@ -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** כדי לצפות בזה.
@@ -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** खोलें।
@@ -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.
@@ -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}}}
@@ -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** ਖੋਲ੍ਹੋ।
@@ -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.
@@ -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**, чтобы просмотреть.
@@ -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.
@@ -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**, щоб переглянути.
@@ -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** کھولیں۔
@@ -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** 查看。
+6
View File
@@ -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;
@@ -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<ChatWootMessagePartial | null> {
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<SendAttachment | null> {
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<WAMessage | null> {
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;
}
}
}
@@ -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<SendAttachment[]> {
protected async getAttachments(
payload: WAMessage,
): Promise<SendAttachment[]> {
const hasMedia = payload.media?.url;
if (!hasMedia) {
return [];
@@ -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> = T | Promise<T>;
+126
View File
@@ -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<T extends { timestamp: number }>(
@@ -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);
+2
View File
@@ -1,3 +1,5 @@
import { EngineHelper } from './engines';
import type { QuotedMedia } from './engines';
export { EngineHelper };
export type { QuotedMedia };
+9
View File
@@ -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;
}