From 191776af671e8a6077ac756395c56a91811301d5 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 6 Feb 2026 03:55:38 +0000 Subject: [PATCH] fix(list-message): convert nativeFlowMessage to direct listMessage for delivery The viewOnceMessage > interactiveMessage > nativeFlowMessage wrapper with single_select button was causing error 479 even with correct biz node. WhatsApp requires the message to be in direct listMessage format (legacy) paired with biz > list (type=product_list, v=2) node. This matches the Pastorini implementation which sends: - messageKeys: ['listMessage'] (NOT viewOnceMessage) - biz > list (type=product_list, v=2) The conversion extracts sections from nativeFlowMessage's single_select buttonParamsJson and creates a proper listMessage with SINGLE_SELECT type. https://claude.ai/code/session_01SJdSHiUxtwzV8bb5dedodb --- src/Socket/messages-send.ts | 55 +++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/src/Socket/messages-send.ts b/src/Socket/messages-send.ts index 3a09b9d3..3f919a65 100644 --- a/src/Socket/messages-send.ts +++ b/src/Socket/messages-send.ts @@ -829,6 +829,61 @@ export const makeMessagesSocket = (config: SocketConfig) => { useUserDevicesCache = useUserDevicesCache !== false useCachedGroupMetadata = useCachedGroupMetadata !== false && !isStatus + // Convert nativeFlowMessage with single_select to direct listMessage (legacy format) + // This is required because WhatsApp expects listMessage format with biz > list node + // The viewOnceMessage > interactiveMessage > nativeFlowMessage wrapper causes error 479 + // Reference: Pastorini sends as direct listMessage and it works on phone + web + const innerMsg = message.viewOnceMessage?.message + const nativeFlow = innerMsg?.interactiveMessage?.nativeFlowMessage + if (nativeFlow?.buttons?.length) { + const singleSelectBtn = nativeFlow.buttons.find( + (btn: any) => btn?.name === 'single_select' + ) + if (singleSelectBtn?.buttonParamsJson) { + try { + const params = JSON.parse(singleSelectBtn.buttonParamsJson) + const sections = params.sections?.map((section: any) => ({ + title: section.title, + rows: section.rows?.map((row: any) => ({ + rowId: row.id || row.rowId, + title: row.title, + description: row.description || '' + })) + })) + + if (sections?.length) { + // Build direct listMessage (legacy format that works) + const listMessage = proto.Message.ListMessage.fromObject({ + title: innerMsg?.interactiveMessage?.header?.title || '', + description: innerMsg?.interactiveMessage?.body?.text || '', + buttonText: params.title || 'Menu', + footerText: innerMsg?.interactiveMessage?.footer?.text || '', + listType: proto.Message.ListMessage.ListType.SINGLE_SELECT, + sections + }) + + // Mutate message in-place: remove viewOnceMessage, add listMessage + delete message.viewOnceMessage + message.listMessage = listMessage + // Keep messageContextInfo if it was nested + if (!message.messageContextInfo && innerMsg?.messageContextInfo) { + message.messageContextInfo = innerMsg.messageContextInfo + } + + logger.info( + { msgId, sectionsCount: sections.length, buttonText: params.title }, + '[LIST CONVERT] Converted nativeFlowMessage(single_select) to direct listMessage format' + ) + } + } catch (err) { + logger.warn( + { msgId, error: (err as Error).message }, + '[LIST CONVERT] Failed to convert nativeFlowMessage to listMessage, sending as-is' + ) + } + } + } + const participants: BinaryNode[] = [] const destinationJid = !isStatus ? finalJid : statusJid const binaryNodeContent: BinaryNode[] = []