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 <noreply@anthropic.com>
This commit is contained in:
@@ -642,6 +642,24 @@ export const makeMessagesSocket = (config: SocketConfig) => {
|
|||||||
if (message.interactiveMessage.nativeFlowMessage) {
|
if (message.interactiveMessage.nativeFlowMessage) {
|
||||||
return 'native_flow'
|
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'
|
return 'interactive'
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -669,6 +687,24 @@ export const makeMessagesSocket = (config: SocketConfig) => {
|
|||||||
if (innerMessage.interactiveMessage.nativeFlowMessage) {
|
if (innerMessage.interactiveMessage.nativeFlowMessage) {
|
||||||
return 'native_flow'
|
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'
|
return 'interactive'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -710,6 +746,21 @@ export const makeMessagesSocket = (config: SocketConfig) => {
|
|||||||
return {}
|
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 (
|
const relayMessage = async (
|
||||||
jid: string,
|
jid: string,
|
||||||
message: proto.IMessage,
|
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
|
// 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 = (
|
const isPrivateUserChat = (
|
||||||
isPnUser(destinationJid) ||
|
isPnUser(destinationJid) ||
|
||||||
isLidUser(destinationJid) ||
|
isLidUser(destinationJid) ||
|
||||||
destinationJid?.endsWith('@c.us')
|
destinationJid?.endsWith('@c.us')
|
||||||
) && !isJidBot(destinationJid)
|
) && !isJidBot(destinationJid)
|
||||||
|
|
||||||
if (isPrivateUserChat) {
|
const isCarousel = isCarouselMessage(message)
|
||||||
|
|
||||||
|
if (isPrivateUserChat && !isCarousel) {
|
||||||
;(stanza.content as BinaryNode[]).push({
|
;(stanza.content as BinaryNode[]).push({
|
||||||
tag: 'bot',
|
tag: 'bot',
|
||||||
attrs: { biz_bot: '1' }
|
attrs: { biz_bot: '1' }
|
||||||
@@ -1114,6 +1169,11 @@ export const makeMessagesSocket = (config: SocketConfig) => {
|
|||||||
{ msgId, to: destinationJid },
|
{ msgId, to: destinationJid },
|
||||||
'[EXPERIMENTAL] Added bot node for private chat interactive message'
|
'[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
|
// Track success and latency after message is sent
|
||||||
|
|||||||
+28
-27
@@ -637,18 +637,18 @@ export type ProductCarouselCard = {
|
|||||||
* { productId: 'iphone_15' },
|
* { productId: 'iphone_15' },
|
||||||
* { productId: 'macbook_air' },
|
* { productId: 'macbook_air' },
|
||||||
* { productId: 'apple_watch' }
|
* { productId: 'apple_watch' }
|
||||||
* ],
|
* ]
|
||||||
* body: 'Check out our featured products!'
|
* },
|
||||||
* }
|
* body: 'Check out our featured products!'
|
||||||
* })
|
* })
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
export type ProductCarouselMessageOptions = {
|
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
|
businessOwnerJid: string
|
||||||
/** Products to display (2-10 cards required) */
|
/** Products to display (2-10 cards required) */
|
||||||
products: ProductCarouselCard[]
|
products: ProductCarouselCard[]
|
||||||
/** Body text for the carousel message */
|
/** Body text for the message */
|
||||||
body?: string
|
body?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -753,6 +753,29 @@ export type AnyRegularMessageContent = (
|
|||||||
text?: string
|
text?: string
|
||||||
footer?: 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
|
* Native List Message - Interactive list with sections
|
||||||
@@ -786,28 +809,6 @@ export type AnyRegularMessageContent = (
|
|||||||
title?: string
|
title?: string
|
||||||
footer?: 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
|
* Album message - send multiple images/videos grouped together
|
||||||
|
|||||||
Reference in New Issue
Block a user