fix(carousel): switch to viewOnceMessage V1

fix(carousel): switch to viewOnceMessage V1
This commit is contained in:
Renato Alcara
2026-02-07 00:00:26 -03:00
committed by GitHub
2 changed files with 17 additions and 28 deletions
+3 -14
View File
@@ -1125,9 +1125,6 @@ export const makeMessagesSocket = (config: SocketConfig) => {
const { user: mePnUser } = jidDecode(meId)! const { user: mePnUser } = jidDecode(meId)!
const { user: meLidUser } = meLid ? jidDecode(meLid)! : { user: null } const { user: meLidUser } = meLid ? jidDecode(meLid)! : { user: null }
// Detect carousel to skip own devices (they can't handle carousel in DSM wrapper)
const isCarouselForDSM = isCarouselMessage(message)
for (const { user, jid } of devices) { for (const { user, jid } of devices) {
const isExactSenderDevice = jid === meId || (meLid && jid === meLid) const isExactSenderDevice = jid === meId || (meLid && jid === meLid)
if (isExactSenderDevice) { if (isExactSenderDevice) {
@@ -1139,11 +1136,6 @@ export const makeMessagesSocket = (config: SocketConfig) => {
const isMe = user === mePnUser || user === meLidUser const isMe = user === mePnUser || user === meLidUser
if (isMe) { if (isMe) {
// Skip own linked devices for carousel - they reject DSM-wrapped carousel with error 479
if (isCarouselForDSM) {
logger.debug({ jid }, '[CAROUSEL] Skipping own linked device - carousel DSM not supported')
continue
}
meRecipients.push(jid) meRecipients.push(jid)
} else { } else {
otherRecipients.push(jid) otherRecipients.push(jid)
@@ -1158,10 +1150,9 @@ export const makeMessagesSocket = (config: SocketConfig) => {
{ nodes: meNodes, shouldIncludeDeviceIdentity: s1 }, { nodes: meNodes, shouldIncludeDeviceIdentity: s1 },
{ nodes: otherNodes, shouldIncludeDeviceIdentity: s2 } { nodes: otherNodes, shouldIncludeDeviceIdentity: s2 }
] = await Promise.all([ ] = await Promise.all([
// For own devices: use DSM if available (1:1 chats only) // For own devices: use DSM (deviceSentMessage) wrapper
// Skip for carousel - linked devices can't handle carousel in DSM wrapper (error 479)
createParticipantNodes(meRecipients, meMsg || message, extraAttrs), createParticipantNodes(meRecipients, meMsg || message, extraAttrs),
createParticipantNodes(otherRecipients, message, extraAttrs, isCarouselForDSM ? undefined : meMsg) createParticipantNodes(otherRecipients, message, extraAttrs)
]) ])
participants.push(...meNodes) participants.push(...meNodes)
participants.push(...otherNodes) participants.push(...otherNodes)
@@ -1179,9 +1170,7 @@ export const makeMessagesSocket = (config: SocketConfig) => {
const isParticipantLid = isLidUser(participant!.jid) const isParticipantLid = isLidUser(participant!.jid)
const isMe = areJidsSameUser(participant!.jid, isParticipantLid ? meLid : meId) const isMe = areJidsSameUser(participant!.jid, isParticipantLid ? meLid : meId)
// Skip DSM for carousel retries to own devices - they can't handle it const usesDSM = isMe
const isCarouselRetry = isCarouselMessage(message)
const usesDSM = isMe && !isCarouselRetry
const encodedMessageToSend = usesDSM const encodedMessageToSend = usesDSM
? encodeWAMessage({ ? encodeWAMessage({
deviceSentMessage: { deviceSentMessage: {
+14 -14
View File
@@ -651,15 +651,15 @@ export const generateCarouselMessage = async (
})) }))
// Build the interactive message with carousel // Build the interactive message with carousel
// Root header MUST always be present for carousel messages (WhatsApp MD requirement) // Match ckptw's working structure: NO root header, NO messageVersion
// Missing root header causes error 479 on some devices // Root header is NOT used for carousel (only individual cards have headers)
// Previous assumption that root header was required was incorrect -
// the error 479 was caused by fromObject() corruption, not missing header
const interactiveMessage: proto.Message.IInteractiveMessage = { const interactiveMessage: proto.Message.IInteractiveMessage = {
header: { title: text || '', hasMediaAttachment: false },
body: { text: text || '' }, body: { text: text || '' },
footer: footer ? { text: footer } : undefined, footer: footer ? { text: footer } : undefined,
carouselMessage: { carouselMessage: {
cards: carouselCards, cards: carouselCards
messageVersion: 1
} }
} }
@@ -675,18 +675,18 @@ export const generateCarouselMessage = async (
hasFooter: !!c.footer?.text, hasFooter: !!c.footer?.text,
buttonsCount: c.nativeFlowMessage?.buttons?.length || 0 buttonsCount: c.nativeFlowMessage?.buttons?.length || 0
})), })),
rootHeader: !!interactiveMessage.header?.title,
rootBody: !!interactiveMessage.body?.text rootBody: !!interactiveMessage.body?.text
}, },
'[CAROUSEL] Structure summary' '[CAROUSEL] Structure summary'
) )
// Wrap in viewOnceMessageV2 (NOT V1!) for WhatsApp Web/Desktop rendering // Wrap in viewOnceMessage V1 (field 37) - used by ckptw, Vkazee, and most Baileys forks
// V2 (field 55) is the stable wrapper for interactive messages from non-Cloud API accounts // V2 (field 55) renders on mobile but NOT on WhatsApp Web/Desktop
// V1 (field 37) caused error 479 - V2 is the correct approach (confirmed by Z-API) // V1 is what WhatsApp Web expects for interactive messages
// messageContextInfo with deviceListMetadata is required for multi-device rendering // Previous error 479 with V1 was caused by fromObject() corruption, not missing header
// Now fixed: plain JS object (no fromObject), no root header, messageContextInfo included
return { return {
viewOnceMessageV2: { viewOnceMessage: {
message: { message: {
messageContextInfo: { messageContextInfo: {
deviceListMetadata: {}, deviceListMetadata: {},
@@ -1191,11 +1191,11 @@ export const generateWAMessageContent = async (
} }
// Pass options for media processing if cards have images/videos // Pass options for media processing if cards have images/videos
const generated = await generateCarouselMessage(carouselOptions, options) const generated = await generateCarouselMessage(carouselOptions, options)
m.viewOnceMessageV2 = generated.viewOnceMessageV2 m.viewOnceMessage = generated.viewOnceMessage
// messageContextInfo at the OUTER Message level too (not just inside the V2 wrapper) // messageContextInfo at the OUTER Message level too (not just inside the wrapper)
// This ensures meMsg/DSM and multi-device routing get proper device list metadata // This ensures meMsg/DSM and multi-device routing get proper device list metadata
m.messageContextInfo = { deviceListMetadata: {}, deviceListMetadataVersion: 2 } m.messageContextInfo = { deviceListMetadata: {}, deviceListMetadataVersion: 2 }
options.logger?.info('Sending carousel with viewOnceMessageV2 wrapper (plain object, no proto conversion)') options.logger?.info('Sending carousel with viewOnceMessage V1 wrapper (plain object, no proto conversion)')
// Return the plain JS object directly WITHOUT calling WAProto.Message.fromObject() // Return the plain JS object directly WITHOUT calling WAProto.Message.fromObject()
// This matches Pastorini's working approach where plain objects are passed directly // This matches Pastorini's working approach where plain objects are passed directly
// to relayMessage. The fromObject() conversion can corrupt nested carousel structures // to relayMessage. The fromObject() conversion can corrupt nested carousel structures