From 27d627b891562da5f60855bf0bc9d76649e6bba8 Mon Sep 17 00:00:00 2001 From: Sven Neumann Date: Fri, 10 Apr 2026 09:10:04 +0200 Subject: [PATCH] fix: use newsletter media types for channel media downloads MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit WhatsApp Web uses separate, unencrypted media types for newsletter/channel content (e.g. "newsletter-image" instead of "image"). The downloadMedia() method was passing the regular msg.type to downloadAndMaybeDecrypt, which caused failures for newsletter messages because: 1. Newsletter media is unencrypted, so mediaKey may be null — but the old code required both directPath and mediaKey for direct downloads. 2. Passing type "image" instead of "newsletter-image" made the download manager select encrypted download entries for unencrypted content. 3. The internal msg.downloadMedia() resolve crashes for newsletter JIDs because getMaybeChat() returns null (not in ChatCollection). Fix by using WA Web's own getMsgMediaType() to compute the correct newsletter-prefixed type, and isMediaCryptoExpectedForMediaType() to determine whether mediaKey is actually required. For unencrypted newsletter media, directPath alone is now sufficient to attempt the download. --- src/structures/Message.js | 54 +++++++++++++++++++++++++++++---------- 1 file changed, 41 insertions(+), 13 deletions(-) diff --git a/src/structures/Message.js b/src/structures/Message.js index 8ac970ede7..9e2ed770d2 100644 --- a/src/structures/Message.js +++ b/src/structures/Message.js @@ -549,20 +549,48 @@ class Message extends Base { ) { return null; } + + // Use WA Web's own type mapper — returns newsletter-prefixed types + // (e.g. "newsletter-image") for newsletter messages, regular types otherwise. + const mediaType = window + .require('WAWebMmsMediaTypes') + .getMsgMediaType(msg); + const needsCrypto = window + .require('WAWebMediaCryptoEligibilityUtils') + .isMediaCryptoExpectedForMediaType(mediaType); + + // For encrypted media: directPath + mediaKey required. + // For unencrypted media (newsletters): directPath alone is sufficient. + const canDirectDownload = needsCrypto + ? msg.directPath && msg.mediaKey + : !!msg.directPath; + + const isNonChatJid = /@(broadcast|newsletter)\b/.test( + msg.id.remote._serialized || msg.id.remote, + ); + if (msg.mediaData.mediaStage != 'RESOLVED') { - // try to resolve media - await msg.downloadMedia({ - downloadEvenIfExpensive: true, - rmrReason: 1, - }); - } + // Only call internal resolve for regular chat messages. + // For status@broadcast and @newsletter messages the internal + // resolve fails because getMaybeChat() returns null. + if (!canDirectDownload && !isNonChatJid) { + await msg.downloadMedia({ + downloadEvenIfExpensive: true, + rmrReason: 1, + }); + } - if ( - msg.mediaData.mediaStage.includes('ERROR') || - msg.mediaData.mediaStage === 'FETCHING' - ) { - // media could not be downloaded - return undefined; + // Bail if we can't attempt a direct download. + if (!canDirectDownload) { + if (isNonChatJid) return undefined; + if ( + !msg.mediaData || + msg.mediaData.mediaStage.includes('ERROR') || + msg.mediaData.mediaStage === 'FETCHING' + ) { + return undefined; + } + } } try { @@ -582,7 +610,7 @@ class Message extends Base { filehash: msg.filehash, mediaKey: msg.mediaKey, mediaKeyTimestamp: msg.mediaKeyTimestamp, - type: msg.type, + type: mediaType, signal: new AbortController().signal, downloadQpl: mockQpl, });