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
This commit is contained in:
Claude
2026-02-06 22:11:54 +00:00
parent 8cd62b964d
commit 1589c82b0d
2 changed files with 29 additions and 18 deletions
+19 -11
View File
@@ -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'
+10 -7
View File
@@ -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')) {