diff --git a/scripts/whatsapp-bridge/bridge.native.test.mjs b/scripts/whatsapp-bridge/bridge.native.test.mjs index 559a9f9d4d..8e8f026c73 100644 --- a/scripts/whatsapp-bridge/bridge.native.test.mjs +++ b/scripts/whatsapp-bridge/bridge.native.test.mjs @@ -468,4 +468,43 @@ import { console.log(' āœ“ quoted message text survives ephemeral/view-once/document envelopes'); } +{ + // Nested envelopes (ephemeral wrapping viewOnce wrapping the payload) and + // the second envelope position: quoted text must resolve there as well. + const quotedMessage = { + ephemeralMessage: { + message: { + viewOnceMessageV2: { + message: { extendedTextMessage: { text: 'Example appointment at 11:40' } }, + }, + }, + }, + }; + const event = await extractBridgeEvent({ + msg: { + key: { id: 'reply-nested', remoteJid: '15551234567@s.whatsapp.net', fromMe: false }, + messageTimestamp: 123, + message: { + ephemeralMessage: { + message: { + extendedTextMessage: { + text: 'confirmed', + contextInfo: { stanzaId: 'original-id', participant: '15559998888@s.whatsapp.net', quotedMessage }, + }, + }, + }, + }, + }, + chatId: '15551234567@s.whatsapp.net', + senderId: '15550001111@s.whatsapp.net', + senderNumber: '15550001111', + botIds: ['15559998888@s.whatsapp.net'], + downloadMedia: async () => Buffer.from(''), + }); + assert.equal(event.quotedMessageId, 'original-id'); + assert.equal(event.hasQuotedMessage, true); + assert.equal(event.quotedText, 'Example appointment at 11:40'); + console.log(' āœ“ nested envelopes: quote text resolves through both layers'); +} + console.log('\nāœ… All WhatsApp native bridge helper tests passed.'); diff --git a/scripts/whatsapp-bridge/bridge_helpers.js b/scripts/whatsapp-bridge/bridge_helpers.js index 08834755e0..439af7665f 100644 --- a/scripts/whatsapp-bridge/bridge_helpers.js +++ b/scripts/whatsapp-bridge/bridge_helpers.js @@ -19,15 +19,26 @@ export function normalizeWhatsAppId(value) { } function unwrapMessageEnvelopes(content) { - if (content?.ephemeralMessage?.message) return content.ephemeralMessage.message; - if (content?.viewOnceMessage?.message) return content.viewOnceMessage.message; - if (content?.viewOnceMessageV2?.message) return content.viewOnceMessageV2.message; - if (content?.documentWithCaptionMessage?.message) return content.documentWithCaptionMessage.message; - return content; + let cur = content; + // Envelopes nest (ephemeral wrapping viewOnce wrapping the payload); peel + // until an inner message is reached so nested quotes resolve too. + for (let i = 0; i < 8 && cur; i++) { + const next = + cur.ephemeralMessage?.message ?? + cur.viewOnceMessage?.message ?? + cur.viewOnceMessageV2?.message ?? + cur.documentWithCaptionMessage?.message; + if (next === undefined) break; + cur = next; + } + return cur; } export function getMessageContent(msg) { const content = unwrapMessageEnvelopes(msg?.message || {}); + // A peeled envelope returns its inner message immediately: the payload + // shape (template/buttons/list/etc.) applies to unenveloped messages. + if (content !== (msg?.message || {})) return content; if (content.templateMessage?.hydratedTemplate) return content.templateMessage.hydratedTemplate; if (content.buttonsMessage) return content.buttonsMessage; if (content.listMessage) return content.listMessage;