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'