fix: remove bot node for carousels and restore ProductCarousel

fix: remove bot node for carousels and restore ProductCarousel
This commit is contained in:
Renato Alcara
2026-01-27 00:56:40 -03:00
committed by GitHub
2 changed files with 90 additions and 29 deletions
+62 -2
View File
@@ -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
+28 -27
View File
@@ -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