From 1589c82b0dd964ee76eb909f5682a073e5375ab4 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 6 Feb 2026 22:11:54 +0000 Subject: [PATCH] fix(carousel): bypass proto fromObject for carousel messages to fix error 479 Three changes matching Pastorini's working implementation: 1. Always set root header in generateCarouselMessage - the root interactiveMessage header must always be present with title and hasMediaAttachment:false. Previously it was undefined when no text was provided, which violates WhatsApp MD protocol requirements. 2. Return plain JS object from generateWAMessageContent for carousel - skip WAProto.Message.fromObject() which can corrupt nested carousel structures by incorrectly handling oneOf fields in deeply nested InteractiveMessage cards. protobuf encode() handles plain objects correctly during serialization. 3. Pass plain JS object directly to relayMessage in sendMessage - call relayMessage(jid, msgContent) with the plain object instead of going through proto.WebMessageInfo.fromObject() first. This matches Pastorini's approach of relayMessage(jid, plainObject, opts). https://claude.ai/code/session_018DkDxsjWzM131jy3ivWjZp --- src/Socket/messages-send.ts | 30 +++++++++++++++++++----------- src/Utils/messages.ts | 17 ++++++++++------- 2 files changed, 29 insertions(+), 18 deletions(-) diff --git a/src/Socket/messages-send.ts b/src/Socket/messages-send.ts index 484c9da2..d9d65d56 100644 --- a/src/Socket/messages-send.ts +++ b/src/Socket/messages-send.ts @@ -2020,12 +2020,16 @@ export const makeMessagesSocket = (config: SocketConfig) => { const userJid = authState.creds.me!.id - // Special path for carousel: call relayMessage directly - // This bypasses generateWAMessageFromContent (which adds contextInfo.expiration - // and calls WAProto.Message.create that may corrupt nested carousel structures) - // Matches the working implementation's approach: generateWAMessageContent → relayMessage + // Special path for carousel: call relayMessage directly with plain JS object + // This matches Pastorini's working approach: + // 1. Build message as plain JS object (not proto instance) + // 2. Pass directly to relayMessage (no WAProto.Message.fromObject/create) + // 3. protobuf encode() handles plain objects correctly during serialization + // Going through fromObject() can corrupt nested carousel InteractiveMessage oneOf fields if (typeof content === 'object' && 'nativeCarousel' in content && (content as any).nativeCarousel) { const messageId = generateMessageIDV2(sock.user?.id) + // generateWAMessageContent returns a plain JS object for carousel + // (not a proto instance) to preserve nested structure integrity const msgContent = await generateWAMessageContent(content, { logger, upload: waUploadToServer, @@ -2034,6 +2038,17 @@ export const makeMessagesSocket = (config: SocketConfig) => { jid, }) + // Pass the plain JS object directly to relayMessage + // This avoids proto.WebMessageInfo.fromObject() which would call + // Message.fromObject() on the nested message, potentially corrupting it + await relayMessage(jid, msgContent, { + messageId, + useCachedGroupMetadata: options.useCachedGroupMetadata, + additionalAttributes: {}, + statusJidList: options.statusJidList, + }) + + // Build WebMessageInfo only for event emission and return value const timestamp = unixTimestampSeconds() const fullMsg = proto.WebMessageInfo.fromObject({ key: { @@ -2048,13 +2063,6 @@ export const makeMessagesSocket = (config: SocketConfig) => { status: proto.WebMessageInfo.Status.PENDING }) as WAMessage - await relayMessage(jid, fullMsg.message!, { - messageId: fullMsg.key.id!, - useCachedGroupMetadata: options.useCachedGroupMetadata, - additionalAttributes: {}, - statusJidList: options.statusJidList, - }) - if (config.emitOwnEvents) { process.nextTick(async () => { const mutexKey = fullMsg.key.remoteJid || fullMsg.key.id || 'unknown' diff --git a/src/Utils/messages.ts b/src/Utils/messages.ts index 27507070..910992f2 100644 --- a/src/Utils/messages.ts +++ b/src/Utils/messages.ts @@ -641,8 +641,10 @@ export const generateCarouselMessage = async ( })) // Build the interactive message with carousel + // Root header MUST always be present for carousel messages (WhatsApp MD requirement) + // Missing root header causes error 479 on some devices const interactiveMessage: proto.Message.IInteractiveMessage = { - header: text ? { title: text } : undefined, + header: { title: text || '', hasMediaAttachment: false }, body: { text: text || '' }, footer: footer ? { text: footer } : undefined, carouselMessage: { @@ -1156,12 +1158,13 @@ export const generateWAMessageContent = async ( // Pass options for media processing if cards have images/videos const generated = await generateCarouselMessage(carouselOptions, options) m.viewOnceMessage = generated.viewOnceMessage - options.logger?.info('Sending carousel with viewOnceMessage wrapper + fromObject encoding') - // Return early with fromObject for proper deep conversion of nested carousel structure - // fromObject recursively converts nested plain objects to protobuf message instances - // whereas create() does shallow property copy which may not properly encode deep structures - // This matches the working implementation's encoding approach - return WAProto.Message.fromObject(m) + options.logger?.info('Sending carousel with viewOnceMessage wrapper (plain object, no proto conversion)') + // Return the plain JS object directly WITHOUT calling WAProto.Message.fromObject() + // This matches Pastorini's working approach where plain objects are passed directly + // to relayMessage. The fromObject() conversion can corrupt nested carousel structures + // by incorrectly handling oneOf fields in deeply nested InteractiveMessage cards. + // The protobuf encode() method handles plain objects correctly when serializing. + return m as WAMessageContent } // Check for nativeList else if (hasNonNullishProperty(message, 'nativeList')) {