From a162caeff2a364cb4c656b1268f51453ed72e205 Mon Sep 17 00:00:00 2001 From: Renato Alcara Date: Tue, 27 Jan 2026 00:48:55 -0300 Subject: [PATCH] fix: remove bot node for carousels and restore ProductCarousel - Fix carousel detection in getButtonType to return native_flow - Add isCarouselMessage helper function - Skip bot node injection for carousel messages (fixes error 479) - Restore ProductCarouselCard and ProductCarouselMessageOptions types - Restore generateProductCarouselMessage function - Add productCarousel handler in generateWAMessageContent The bot node with biz_bot='1' was causing error 479 for media carousels because carousels are regular interactive messages, not bot messages. Co-Authored-By: Claude Opus 4.5 --- src/Socket/messages-send.ts | 64 +++++++++++++++++++++++++++++++++++-- src/Types/Message.ts | 55 +++++++++++++++---------------- 2 files changed, 90 insertions(+), 29 deletions(-) diff --git a/src/Socket/messages-send.ts b/src/Socket/messages-send.ts index b06eefb5..942d0608 100644 --- a/src/Socket/messages-send.ts +++ b/src/Socket/messages-send.ts @@ -642,6 +642,24 @@ export const makeMessagesSocket = (config: SocketConfig) => { if (message.interactiveMessage.nativeFlowMessage) { return 'native_flow' } + // Check if it's a carousel with nativeFlowMessage buttons in cards + if (message.interactiveMessage.carouselMessage?.cards?.length) { + const hasNativeFlowButtons = message.interactiveMessage.carouselMessage.cards.some( + (card: any) => card?.nativeFlowMessage?.buttons?.length + ) + if (hasNativeFlowButtons) { + return 'native_flow' + } + } + // Check if it's a collection/product carousel + if (message.interactiveMessage.carouselMessage?.cards?.length) { + const hasCollectionCards = message.interactiveMessage.carouselMessage.cards.some( + (card: any) => card?.collectionMessage + ) + if (hasCollectionCards) { + return 'native_flow' + } + } return 'interactive' } @@ -669,6 +687,24 @@ export const makeMessagesSocket = (config: SocketConfig) => { if (innerMessage.interactiveMessage.nativeFlowMessage) { return 'native_flow' } + // Check if it's a carousel with nativeFlowMessage buttons in cards + if (innerMessage.interactiveMessage.carouselMessage?.cards?.length) { + const hasNativeFlowButtons = innerMessage.interactiveMessage.carouselMessage.cards.some( + (card: any) => card?.nativeFlowMessage?.buttons?.length + ) + if (hasNativeFlowButtons) { + return 'native_flow' + } + } + // Check if it's a collection/product carousel + if (innerMessage.interactiveMessage.carouselMessage?.cards?.length) { + const hasCollectionCards = innerMessage.interactiveMessage.carouselMessage.cards.some( + (card: any) => card?.collectionMessage + ) + if (hasCollectionCards) { + return 'native_flow' + } + } return 'interactive' } } @@ -710,6 +746,21 @@ export const makeMessagesSocket = (config: SocketConfig) => { return {} } + /** + * Checks if the message is a carousel (media carousel or product carousel) + * Carousels should NOT have the bot node injected as they are not bot messages + */ + const isCarouselMessage = (message: proto.IMessage): boolean => { + const interactiveMsg = message.interactiveMessage || + message.viewOnceMessage?.message?.interactiveMessage + + if (interactiveMsg?.carouselMessage?.cards?.length) { + return true + } + + return false + } + const relayMessage = async ( jid: string, message: proto.IMessage, @@ -1097,15 +1148,19 @@ export const makeMessagesSocket = (config: SocketConfig) => { ] }) - // For private 1:1 chats, add bot node (required for interactive messages to render) + // For private 1:1 chats, add bot node (required for some interactive messages to render) // Only inject for actual user JIDs, not broadcasts, newsletters, or Meta AI bots + // IMPORTANT: Carousels (media carousel and product carousel) should NOT have bot node + // as they are regular interactive messages, not bot messages const isPrivateUserChat = ( isPnUser(destinationJid) || isLidUser(destinationJid) || destinationJid?.endsWith('@c.us') ) && !isJidBot(destinationJid) - if (isPrivateUserChat) { + const isCarousel = isCarouselMessage(message) + + if (isPrivateUserChat && !isCarousel) { ;(stanza.content as BinaryNode[]).push({ tag: 'bot', attrs: { biz_bot: '1' } @@ -1114,6 +1169,11 @@ export const makeMessagesSocket = (config: SocketConfig) => { { msgId, to: destinationJid }, '[EXPERIMENTAL] Added bot node for private chat interactive message' ) + } else if (isCarousel) { + logger.debug( + { msgId, to: destinationJid, buttonType }, + '[EXPERIMENTAL] Skipping bot node for carousel message' + ) } // Track success and latency after message is sent diff --git a/src/Types/Message.ts b/src/Types/Message.ts index 74f44a55..539a4b5c 100644 --- a/src/Types/Message.ts +++ b/src/Types/Message.ts @@ -637,18 +637,18 @@ export type ProductCarouselCard = { * { productId: 'iphone_15' }, * { productId: 'macbook_air' }, * { productId: 'apple_watch' } - * ], - * body: 'Check out our featured products!' - * } + * ] + * }, + * body: 'Check out our featured products!' * }) * ``` */ export type ProductCarouselMessageOptions = { - /** JID of the business owner (catalog owner) - e.g., '5511999999999@s.whatsapp.net' */ + /** JID of the business owner (who owns the catalog) */ businessOwnerJid: string /** Products to display (2-10 cards required) */ products: ProductCarouselCard[] - /** Body text for the carousel message */ + /** Body text for the message */ body?: string } @@ -753,6 +753,29 @@ export type AnyRegularMessageContent = ( text?: string footer?: string } + | { + /** + * Product Carousel Message - Swipeable product cards from WhatsApp Business catalog + * Requires: WhatsApp Business account with configured catalog + * + * @example + * ```typescript + * await sock.sendMessage(jid, { + * productCarousel: { + * businessOwnerJid: '5511999999999@s.whatsapp.net', + * products: [ + * { productId: 'produto_001' }, + * { productId: 'produto_002' }, + * { productId: 'produto_003' } + * ] + * }, + * body: 'Confira nossos produtos em destaque!' + * }) + * ``` + */ + productCarousel: ProductCarouselMessageOptions + body?: string + } | { /** * Native List Message - Interactive list with sections @@ -786,28 +809,6 @@ export type AnyRegularMessageContent = ( title?: string footer?: string } - | { - /** - * Product Carousel Message - Swipeable product cards from WhatsApp Business catalog - * Requires: WhatsApp Business account with configured catalog - * - * @example - * ```typescript - * await sock.sendMessage(jid, { - * productCarousel: { - * businessOwnerJid: '5511999999999@s.whatsapp.net', - * products: [ - * { productId: 'produto_001' }, - * { productId: 'produto_002' }, - * { productId: 'produto_003' } - * ], - * body: 'Confira nossos produtos em destaque!' - * } - * }) - * ``` - */ - productCarousel: ProductCarouselMessageOptions - } | { /** * Album message - send multiple images/videos grouped together