feat(messages): complete binary node wrappers for interactive messages
- Update getButtonType() to return 'native_flow' for modern nativeFlowMessage format - Add product list detection (PRODUCT_LIST type returns native_flow) - Implement getButtonArgs() with proper v:4 attributes for native_flow - Add special attribute handling for payment flows (review_and_pay, mpm, review_order) - Add bot node injection for private chats (required for interactive messages to render) - Based on Itsukichan/Baileys and baileys_helpers implementation Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -611,6 +611,13 @@ export const makeMessagesSocket = (config: SocketConfig) => {
|
||||
|
||||
// ⚠️ EXPERIMENTAL: Functions to detect and handle interactive messages
|
||||
// These features may not work and can cause account bans
|
||||
// Based on Itsukichan/Baileys and baileys_helpers implementation
|
||||
|
||||
/**
|
||||
* Detects the type of interactive message and returns the appropriate binary node tag
|
||||
* Returns 'native_flow' for modern nativeFlowMessage format (recommended)
|
||||
* Returns legacy types for older button formats
|
||||
*/
|
||||
const getButtonType = (message: proto.IMessage): string | undefined => {
|
||||
// Check direct message types (legacy formats)
|
||||
if (message.buttonsMessage) {
|
||||
@@ -618,6 +625,10 @@ 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)
|
||||
if (message.listMessage.listType === proto.Message.ListMessage.ListType.PRODUCT_LIST) {
|
||||
return 'native_flow'
|
||||
}
|
||||
return 'list'
|
||||
} else if (message.buttonsResponseMessage) {
|
||||
return 'buttons_response'
|
||||
@@ -626,12 +637,15 @@ export const makeMessagesSocket = (config: SocketConfig) => {
|
||||
} else if (message.templateButtonReplyMessage) {
|
||||
return 'template_reply'
|
||||
} else if (message.interactiveMessage) {
|
||||
// Check if it has nativeFlowMessage (modern format)
|
||||
if (message.interactiveMessage.nativeFlowMessage) {
|
||||
return 'native_flow'
|
||||
}
|
||||
return 'interactive'
|
||||
}
|
||||
|
||||
// Check inside viewOnceMessage wrapper (modern nativeFlowMessage format)
|
||||
// This is the recommended format for interactive messages
|
||||
// Check all 7 types for consistency with direct message detection
|
||||
const innerMessage = message.viewOnceMessage?.message
|
||||
if (innerMessage) {
|
||||
if (innerMessage.buttonsMessage) {
|
||||
@@ -639,6 +653,9 @@ export const makeMessagesSocket = (config: SocketConfig) => {
|
||||
} else if (innerMessage.templateMessage) {
|
||||
return 'template'
|
||||
} else if (innerMessage.listMessage) {
|
||||
if (innerMessage.listMessage.listType === proto.Message.ListMessage.ListType.PRODUCT_LIST) {
|
||||
return 'native_flow'
|
||||
}
|
||||
return 'list'
|
||||
} else if (innerMessage.buttonsResponseMessage) {
|
||||
return 'buttons_response'
|
||||
@@ -647,6 +664,10 @@ export const makeMessagesSocket = (config: SocketConfig) => {
|
||||
} else if (innerMessage.templateButtonReplyMessage) {
|
||||
return 'template_reply'
|
||||
} else if (innerMessage.interactiveMessage) {
|
||||
// Check if it has nativeFlowMessage (modern format)
|
||||
if (innerMessage.interactiveMessage.nativeFlowMessage) {
|
||||
return 'native_flow'
|
||||
}
|
||||
return 'interactive'
|
||||
}
|
||||
}
|
||||
@@ -654,9 +675,37 @@ export const makeMessagesSocket = (config: SocketConfig) => {
|
||||
return undefined
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the attributes for the interactive binary node based on message type
|
||||
* For native_flow: returns { v: '4', name: '' } or special attributes for payment flows
|
||||
*/
|
||||
const getButtonArgs = (message: proto.IMessage): BinaryNodeAttributes => {
|
||||
// Return appropriate attributes for each button type
|
||||
// This is often empty but required for the 'biz' node structure
|
||||
const buttonType = getButtonType(message)
|
||||
|
||||
// For native_flow messages, check for special button types that need specific attributes
|
||||
if (buttonType === 'native_flow') {
|
||||
const interactiveMsg = message.interactiveMessage ||
|
||||
message.viewOnceMessage?.message?.interactiveMessage
|
||||
|
||||
if (interactiveMsg?.nativeFlowMessage?.buttons?.[0]) {
|
||||
const firstButtonName = interactiveMsg.nativeFlowMessage.buttons[0].name
|
||||
|
||||
// Special button types that require specific attributes
|
||||
// Based on official WhatsApp client traffic
|
||||
if (firstButtonName === 'review_and_pay' || firstButtonName === 'payment_info') {
|
||||
return { v: '4', name: 'payment_info' }
|
||||
} else if (firstButtonName === 'mpm') {
|
||||
return { v: '4', name: 'mpm' }
|
||||
} else if (firstButtonName === 'review_order') {
|
||||
return { v: '4', name: 'order_details' }
|
||||
}
|
||||
}
|
||||
|
||||
// Default native_flow attributes
|
||||
return { v: '4', name: '' }
|
||||
}
|
||||
|
||||
// For other button types, return empty attributes
|
||||
return {}
|
||||
}
|
||||
|
||||
@@ -1047,6 +1096,18 @@ export const makeMessagesSocket = (config: SocketConfig) => {
|
||||
]
|
||||
})
|
||||
|
||||
// For private chats, add bot node (required for interactive messages to render)
|
||||
if (!isJidGroup(destinationJid)) {
|
||||
;(stanza.content as BinaryNode[]).push({
|
||||
tag: 'bot',
|
||||
attrs: { biz_bot: '1' }
|
||||
})
|
||||
logger.debug(
|
||||
{ msgId, to: destinationJid },
|
||||
'[EXPERIMENTAL] Added bot node for private chat interactive message'
|
||||
)
|
||||
}
|
||||
|
||||
// Track success and latency after message is sent
|
||||
metrics.interactiveMessagesSuccess.inc({ type: buttonType })
|
||||
metrics.interactiveMessagesLatency.observe({ type: buttonType }, Date.now() - startTime)
|
||||
|
||||
Reference in New Issue
Block a user