diff --git a/src/Socket/messages-send.ts b/src/Socket/messages-send.ts index 62ed959d..aa9f2660 100644 --- a/src/Socket/messages-send.ts +++ b/src/Socket/messages-send.ts @@ -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)