fix: Use legacy listMessage format with correct biz node to fix error 479
The modern interactiveMessage format was causing error 479 (message rejection). Switched to legacy listMessage format that matches pastorini's working implementation. Changes: 1. **messages.ts**: Changed nativeList to use generateListMessageLegacy() - Creates listMessage directly (not viewOnceMessage wrapper) - Uses SINGLE_SELECT type (standard list) 2. **messages-send.ts**: Inject correct biz node for listMessage - For buttonType === 'list': <biz><list type="product_list" v="2"> - Matches pastorini's working structure - Removed checks that skipped biz node for listMessage Why this works: - Legacy listMessage + product_list biz node = accepted by WhatsApp - Modern interactiveMessage + native_flow biz node = error 479 - This matches the pastorini implementation that works on Web/iOS/Android https://claude.ai/code/session_01Vgu4xrsj8aUVCHWb4pmQPF
This commit is contained in:
+50
-52
@@ -628,11 +628,8 @@ export const makeMessagesSocket = (config: SocketConfig) => {
|
||||
} else if (message.templateMessage) {
|
||||
return 'template'
|
||||
} else if (message.listMessage) {
|
||||
// Check if it's a product list (uses PRODUCT_LIST type)
|
||||
// Product lists from WhatsApp Business catalog don't need biz node injection
|
||||
if (message.listMessage.listType === proto.Message.ListMessage.ListType.PRODUCT_LIST) {
|
||||
return undefined // No biz node needed for catalog product lists
|
||||
}
|
||||
// All listMessage types need biz node with type="product_list"
|
||||
// This includes SINGLE_SELECT and PRODUCT_LIST
|
||||
return 'list'
|
||||
} else if (message.buttonsResponseMessage) {
|
||||
return 'buttons_response'
|
||||
@@ -643,15 +640,6 @@ export const makeMessagesSocket = (config: SocketConfig) => {
|
||||
} else if (message.interactiveMessage) {
|
||||
// Check if it has nativeFlowMessage (modern format)
|
||||
if (message.interactiveMessage.nativeFlowMessage) {
|
||||
// Check if it's a list (single_select or multi_select)
|
||||
// Lists don't need biz node injection, similar to product lists
|
||||
const buttons = message.interactiveMessage.nativeFlowMessage.buttons || []
|
||||
const isListButton = buttons.some(
|
||||
(btn: any) => btn?.name === 'single_select' || btn?.name === 'multi_select'
|
||||
)
|
||||
if (isListButton) {
|
||||
return undefined // No biz node for list messages
|
||||
}
|
||||
return 'native_flow'
|
||||
}
|
||||
// Check if it's a carousel with nativeFlowMessage buttons in cards
|
||||
@@ -684,10 +672,8 @@ export const makeMessagesSocket = (config: SocketConfig) => {
|
||||
} else if (innerMessage.templateMessage) {
|
||||
return 'template'
|
||||
} else if (innerMessage.listMessage) {
|
||||
// Product lists from WhatsApp Business catalog don't need biz node injection
|
||||
if (innerMessage.listMessage.listType === proto.Message.ListMessage.ListType.PRODUCT_LIST) {
|
||||
return undefined // No biz node needed for catalog product lists
|
||||
}
|
||||
// All listMessage types need biz node with type="product_list"
|
||||
// This includes SINGLE_SELECT and PRODUCT_LIST
|
||||
return 'list'
|
||||
} else if (innerMessage.buttonsResponseMessage) {
|
||||
return 'buttons_response'
|
||||
@@ -698,15 +684,6 @@ export const makeMessagesSocket = (config: SocketConfig) => {
|
||||
} else if (innerMessage.interactiveMessage) {
|
||||
// Check if it has nativeFlowMessage (modern format)
|
||||
if (innerMessage.interactiveMessage.nativeFlowMessage) {
|
||||
// Check if it's a list (single_select or multi_select)
|
||||
// Lists don't need biz node injection, similar to product lists
|
||||
const buttons = innerMessage.interactiveMessage.nativeFlowMessage.buttons || []
|
||||
const isListButton = buttons.some(
|
||||
(btn: any) => btn?.name === 'single_select' || btn?.name === 'multi_select'
|
||||
)
|
||||
if (isListButton) {
|
||||
return undefined // No biz node for list messages
|
||||
}
|
||||
return 'native_flow'
|
||||
}
|
||||
// Check if it's a carousel with nativeFlowMessage buttons in cards
|
||||
@@ -1233,32 +1210,53 @@ export const makeMessagesSocket = (config: SocketConfig) => {
|
||||
metrics.interactiveMessagesSent.inc({ type: buttonType })
|
||||
|
||||
try {
|
||||
// Use nested structure: biz > interactive > native_flow
|
||||
// For buttons, carousels, and other interactive messages
|
||||
// NOTE: Lists now skip biz node injection (handled in getButtonType)
|
||||
const interactiveType = 'native_flow'
|
||||
;(stanza.content as BinaryNode[]).push({
|
||||
tag: 'biz',
|
||||
attrs: {},
|
||||
content: [
|
||||
{
|
||||
tag: 'interactive',
|
||||
attrs: {
|
||||
type: interactiveType,
|
||||
v: '1'
|
||||
},
|
||||
content: [
|
||||
{
|
||||
tag: interactiveType,
|
||||
attrs: {
|
||||
v: '9',
|
||||
name: 'mixed'
|
||||
}
|
||||
// For listMessage (legacy format), use direct <list> tag
|
||||
// This matches pastorini's working implementation
|
||||
if (buttonType === 'list') {
|
||||
;(stanza.content as BinaryNode[]).push({
|
||||
tag: 'biz',
|
||||
attrs: {},
|
||||
content: [
|
||||
{
|
||||
tag: 'list',
|
||||
attrs: {
|
||||
type: 'product_list',
|
||||
v: '2'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
})
|
||||
}
|
||||
]
|
||||
})
|
||||
logger.info(
|
||||
{ msgId, to: destinationJid },
|
||||
'[EXPERIMENTAL] Injected biz node for listMessage (legacy format)'
|
||||
)
|
||||
} else {
|
||||
// Use nested structure: biz > interactive > native_flow
|
||||
// For buttons, carousels, and other interactive messages
|
||||
const interactiveType = 'native_flow'
|
||||
;(stanza.content as BinaryNode[]).push({
|
||||
tag: 'biz',
|
||||
attrs: {},
|
||||
content: [
|
||||
{
|
||||
tag: 'interactive',
|
||||
attrs: {
|
||||
type: interactiveType,
|
||||
v: '1'
|
||||
},
|
||||
content: [
|
||||
{
|
||||
tag: interactiveType,
|
||||
attrs: {
|
||||
v: '9',
|
||||
name: 'mixed'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
})
|
||||
}
|
||||
|
||||
// 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
|
||||
|
||||
+12
-10
@@ -1076,16 +1076,18 @@ export const generateWAMessageContent = async (
|
||||
// Check for nativeList
|
||||
else if (hasNonNullishProperty(message, 'nativeList')) {
|
||||
const listMsg = message as any
|
||||
const listOptions: ListMessageOptions = {
|
||||
buttonText: listMsg.nativeList.buttonText,
|
||||
sections: listMsg.nativeList.sections,
|
||||
text: listMsg.text || '',
|
||||
title: listMsg.title,
|
||||
footer: listMsg.footer
|
||||
}
|
||||
const generated = generateListMessage(listOptions)
|
||||
m.viewOnceMessage = generated.viewOnceMessage
|
||||
options.logger?.info('Sending listMessage with viewOnceMessage wrapper')
|
||||
// Use LEGACY format to avoid error 479
|
||||
// Modern format (interactiveMessage) causes rejection
|
||||
// Legacy format (listMessage) works on all platforms
|
||||
const generated = generateListMessageLegacy(
|
||||
{ sections: listMsg.nativeList.sections },
|
||||
listMsg.title || listMsg.text || '',
|
||||
listMsg.text || '',
|
||||
listMsg.nativeList.buttonText,
|
||||
listMsg.footer
|
||||
)
|
||||
m.listMessage = generated.listMessage
|
||||
options.logger?.info('Sending listMessage with LEGACY format (fixes error 479)')
|
||||
}
|
||||
// Check for productList (multi-product message from catalog)
|
||||
else if (hasNonNullishProperty(message, 'productList')) {
|
||||
|
||||
Reference in New Issue
Block a user