refactor: clean interactive message logs
refactor: clean interactive message logs
This commit is contained in:
@@ -85,10 +85,7 @@ export const DEFAULT_CONNECTION_CONFIG: SocketConfig = {
|
|||||||
// Enable automatic recovery of Click-to-WhatsApp ads messages
|
// Enable automatic recovery of Click-to-WhatsApp ads messages
|
||||||
// These arrive as "placeholder messages" and need to be requested from the phone
|
// These arrive as "placeholder messages" and need to be requested from the phone
|
||||||
enableCTWARecovery: true,
|
enableCTWARecovery: true,
|
||||||
// ⚠️ EXPERIMENTAL: Enable interactive messages (buttons, lists, templates, carousel)
|
// Enable interactive messages (buttons, lists, templates, carousel)
|
||||||
// WARNING: These features may not work and can cause account BANS
|
|
||||||
// Use ONLY for testing with DISPOSABLE accounts in DEV environment
|
|
||||||
// Default: true (for dev/testing) - set to false in production
|
|
||||||
enableInteractiveMessages: true,
|
enableInteractiveMessages: true,
|
||||||
options: {},
|
options: {},
|
||||||
appStateMacVerification: {
|
appStateMacVerification: {
|
||||||
|
|||||||
+24
-43
@@ -622,9 +622,7 @@ export const makeMessagesSocket = (config: SocketConfig) => {
|
|||||||
return { nodes, shouldIncludeDeviceIdentity }
|
return { nodes, shouldIncludeDeviceIdentity }
|
||||||
}
|
}
|
||||||
|
|
||||||
// ⚠️ EXPERIMENTAL: Functions to detect and handle interactive messages
|
// Interactive message detection and binary node injection
|
||||||
// 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
|
* Detects the type of interactive message and returns the appropriate binary node tag
|
||||||
@@ -1247,8 +1245,7 @@ export const makeMessagesSocket = (config: SocketConfig) => {
|
|||||||
content: binaryNodeContent
|
content: binaryNodeContent
|
||||||
}
|
}
|
||||||
|
|
||||||
// ⚠️ EXPERIMENTAL: Inject 'biz' node for interactive messages
|
// Inject 'biz' node for interactive messages
|
||||||
// This may not work and can cause account bans
|
|
||||||
const buttonType = getButtonType(message)
|
const buttonType = getButtonType(message)
|
||||||
const isCatalog = isCatalogMessage(message)
|
const isCatalog = isCatalogMessage(message)
|
||||||
const isCarousel = isCarouselMessage(message)
|
const isCarousel = isCarouselMessage(message)
|
||||||
@@ -1281,29 +1278,20 @@ export const makeMessagesSocket = (config: SocketConfig) => {
|
|||||||
|
|
||||||
const isListDetected = isListNativeFlow(message)
|
const isListDetected = isListNativeFlow(message)
|
||||||
|
|
||||||
// Log full button details including buttonParamsJson for debugging
|
|
||||||
const buttonDetails = nativeFlowButtons.map((b: any) => ({
|
|
||||||
name: b?.name,
|
|
||||||
paramsJson: b?.buttonParamsJson ? JSON.parse(b.buttonParamsJson) : null
|
|
||||||
}))
|
|
||||||
|
|
||||||
logger.info(
|
logger.info(
|
||||||
{
|
{
|
||||||
msgId,
|
msgId,
|
||||||
buttonType,
|
buttonType,
|
||||||
|
to: destinationJid,
|
||||||
hasListMessage: !!listMsg,
|
hasListMessage: !!listMsg,
|
||||||
hasInteractiveMessage: !!interactiveMsg,
|
hasInteractiveMessage: !!interactiveMsg,
|
||||||
hasNativeFlow: !!interactiveMsg?.nativeFlowMessage,
|
hasNativeFlow: !!interactiveMsg?.nativeFlowMessage,
|
||||||
nativeFlowButtonNames: nativeFlowButtons.map((b: any) => b?.name),
|
nativeFlowButtonNames: nativeFlowButtons.map((b: any) => b?.name),
|
||||||
buttonDetails: JSON.stringify(buttonDetails, null, 2),
|
isListDetected,
|
||||||
isListDetected
|
isCatalog,
|
||||||
|
isCarousel
|
||||||
},
|
},
|
||||||
'[DEBUG] Interactive message structure'
|
'[Interactive] Preparing biz node'
|
||||||
)
|
|
||||||
|
|
||||||
logger.warn(
|
|
||||||
{ msgId, buttonType, to: destinationJid, enableInteractiveMessages, isCatalog },
|
|
||||||
'[EXPERIMENTAL] Injecting biz node for interactive message - may cause ban'
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Track that we're sending an interactive message
|
// Track that we're sending an interactive message
|
||||||
@@ -1333,10 +1321,7 @@ export const makeMessagesSocket = (config: SocketConfig) => {
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
logger.info(
|
logger.info({ msgId, to: destinationJid }, '[BIZ NODE] Injected biz > list (product_list, v=2)')
|
||||||
{ msgId, to: destinationJid },
|
|
||||||
'[EXPERIMENTAL] Injected biz node for listMessage (legacy format)'
|
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
const SPECIAL_FLOW_NAMES: Record<string, string> = {
|
const SPECIAL_FLOW_NAMES: Record<string, string> = {
|
||||||
review_and_pay: 'payment_info',
|
review_and_pay: 'payment_info',
|
||||||
@@ -1349,7 +1334,7 @@ export const makeMessagesSocket = (config: SocketConfig) => {
|
|||||||
|
|
||||||
logger.info(
|
logger.info(
|
||||||
{ msgId, buttonNames: allButtonNames, hasCTA, hasQuickReply, isCTAOnly, nativeFlowName },
|
{ msgId, buttonNames: allButtonNames, hasCTA, hasQuickReply, isCTAOnly, nativeFlowName },
|
||||||
'[EXPERIMENTAL] Determined native_flow name based on button types'
|
'[BIZ NODE] Injected biz > interactive(native_flow, v=1) > native_flow(v=9, name=' + nativeFlowName + ')'
|
||||||
)
|
)
|
||||||
|
|
||||||
const interactiveType = 'native_flow'
|
const interactiveType = 'native_flow'
|
||||||
@@ -1389,38 +1374,26 @@ export const makeMessagesSocket = (config: SocketConfig) => {
|
|||||||
tag: 'bot',
|
tag: 'bot',
|
||||||
attrs: { biz_bot: '1' }
|
attrs: { biz_bot: '1' }
|
||||||
})
|
})
|
||||||
logger.debug(
|
logger.info({ msgId, to: destinationJid }, '[BOT NODE] Added bot node (biz_bot=1)')
|
||||||
{ msgId, to: destinationJid },
|
|
||||||
'[EXPERIMENTAL] Added bot node for private chat interactive message'
|
|
||||||
)
|
|
||||||
} else if (isNativeFlowButtons) {
|
} else if (isNativeFlowButtons) {
|
||||||
logger.debug(
|
logger.debug({ msgId, to: destinationJid }, '[BOT NODE] Skipped — native_flow (Web compatibility)')
|
||||||
{ msgId, to: destinationJid, buttonNames: allButtonNames },
|
|
||||||
'[EXPERIMENTAL] Skipping bot node for native_flow buttons (Web/Desktop compatibility)'
|
|
||||||
)
|
|
||||||
} else if (isCarousel) {
|
} else if (isCarousel) {
|
||||||
logger.debug(
|
logger.debug({ msgId, to: destinationJid }, '[BOT NODE] Skipped — carousel message')
|
||||||
{ msgId, to: destinationJid, buttonType },
|
|
||||||
'[EXPERIMENTAL] Skipping bot node for carousel message'
|
|
||||||
)
|
|
||||||
} else if (isCatalog) {
|
} else if (isCatalog) {
|
||||||
logger.debug(
|
logger.debug({ msgId, to: destinationJid }, '[BOT NODE] Skipped — catalog message')
|
||||||
{ msgId, to: destinationJid, buttonType },
|
|
||||||
'[EXPERIMENTAL] Skipping bot node for catalog message (with biz node)'
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Track success and latency after message is sent
|
// Track success and latency after message is sent
|
||||||
metrics.interactiveMessagesSuccess.inc({ type: buttonType })
|
metrics.interactiveMessagesSuccess.inc({ type: buttonType })
|
||||||
metrics.interactiveMessagesLatency.observe({ type: buttonType }, Date.now() - startTime)
|
metrics.interactiveMessagesLatency.observe({ type: buttonType }, Date.now() - startTime)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error({ error, msgId, buttonType }, '[EXPERIMENTAL] Failed to inject biz node for interactive message')
|
logger.error({ error, msgId, buttonType }, '[BIZ NODE] Failed to inject biz node')
|
||||||
metrics.interactiveMessagesFailures.inc({ type: buttonType, reason: 'injection_failed' })
|
metrics.interactiveMessagesFailures.inc({ type: buttonType, reason: 'injection_failed' })
|
||||||
}
|
}
|
||||||
} else if (buttonType && !enableInteractiveMessages) {
|
} else if (buttonType && !enableInteractiveMessages) {
|
||||||
logger.warn(
|
logger.warn(
|
||||||
{ msgId, buttonType },
|
{ msgId, buttonType },
|
||||||
'[EXPERIMENTAL] Interactive message detected but feature disabled (enableInteractiveMessages=false)'
|
'[Interactive] Message detected but feature disabled (enableInteractiveMessages=false)'
|
||||||
)
|
)
|
||||||
metrics.interactiveMessagesFailures.inc({ type: buttonType, reason: 'feature_disabled' })
|
metrics.interactiveMessagesFailures.inc({ type: buttonType, reason: 'feature_disabled' })
|
||||||
}
|
}
|
||||||
@@ -1499,7 +1472,9 @@ export const makeMessagesSocket = (config: SocketConfig) => {
|
|||||||
// Opportunistic cleanup: remove expired token from store
|
// Opportunistic cleanup: remove expired token from store
|
||||||
try {
|
try {
|
||||||
await authState.keys.set({ tctoken: { [tcTokenJid]: null } })
|
await authState.keys.set({ tctoken: { [tcTokenJid]: null } })
|
||||||
} catch { /* ignore cleanup errors */ }
|
} catch {
|
||||||
|
/* ignore cleanup errors */
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If tctoken is missing for a 1:1 send, fire-and-forget fetch so the
|
// If tctoken is missing for a 1:1 send, fire-and-forget fetch so the
|
||||||
@@ -1544,6 +1519,12 @@ export const makeMessagesSocket = (config: SocketConfig) => {
|
|||||||
;(stanza.content as BinaryNode[]).push(...deferredNodes)
|
;(stanza.content as BinaryNode[]).push(...deferredNodes)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Log stanza structure for interactive messages
|
||||||
|
if (buttonType || isCarousel) {
|
||||||
|
const contentTags = Array.isArray(stanza.content) ? stanza.content.map((n: BinaryNode) => n.tag) : []
|
||||||
|
logger.info({ msgId, to: destinationJid, contentTags }, '[STANZA] Content tags: ' + JSON.stringify(contentTags))
|
||||||
|
}
|
||||||
|
|
||||||
logger.debug({ msgId }, `sending message to ${participants.length} devices`)
|
logger.debug({ msgId }, `sending message to ${participants.length} devices`)
|
||||||
|
|
||||||
// ======= PROTOBUF ROUNDTRIP TEST: Verify encoding preserves carousel =======
|
// ======= PROTOBUF ROUNDTRIP TEST: Verify encoding preserves carousel =======
|
||||||
|
|||||||
@@ -281,9 +281,7 @@ export type WASendableProduct = Omit<proto.Message.ProductMessage.IProductSnapsh
|
|||||||
productImage: WAMediaUpload
|
productImage: WAMediaUpload
|
||||||
}
|
}
|
||||||
|
|
||||||
// ⚠️ EXPERIMENTAL: Interactive message types
|
// Interactive message types
|
||||||
// These features may not work and can cause account bans
|
|
||||||
// Use only for testing with disposable accounts
|
|
||||||
|
|
||||||
export type ButtonInfo = {
|
export type ButtonInfo = {
|
||||||
buttonId: string
|
buttonId: string
|
||||||
|
|||||||
+3
-18
@@ -135,24 +135,9 @@ export type SocketConfig = {
|
|||||||
enableCTWARecovery: boolean
|
enableCTWARecovery: boolean
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ⚠️ EXPERIMENTAL: Enable interactive messages (buttons, lists, templates, carousel).
|
* Enable interactive messages (buttons, lists, templates, carousel).
|
||||||
*
|
* When true, injects biz/bot binary nodes required by WhatsApp.
|
||||||
* **WARNING**: These features MAY NOT WORK and can cause ACCOUNT BANS.
|
* @default true
|
||||||
*
|
|
||||||
* WhatsApp actively blocks non-business accounts from sending interactive messages.
|
|
||||||
* Using this feature can result in:
|
|
||||||
* - Messages not being delivered
|
|
||||||
* - Temporary account restrictions
|
|
||||||
* - Permanent account bans
|
|
||||||
*
|
|
||||||
* Use ONLY for:
|
|
||||||
* - Testing in DEV environment
|
|
||||||
* - DISPOSABLE test accounts
|
|
||||||
* - Experimental research
|
|
||||||
*
|
|
||||||
* @default true (for dev/testing)
|
|
||||||
* @see https://github.com/WhiskeySockets/Baileys/issues/56
|
|
||||||
* @see https://github.com/WhiskeySockets/Baileys/issues/25
|
|
||||||
*/
|
*/
|
||||||
enableInteractiveMessages: boolean
|
enableInteractiveMessages: boolean
|
||||||
|
|
||||||
|
|||||||
@@ -1287,8 +1287,7 @@ export const generateWAMessageContent = async (
|
|||||||
m.viewOnceMessage = generated.viewOnceMessage
|
m.viewOnceMessage = generated.viewOnceMessage
|
||||||
options.logger?.info('Sending productCarouselMessage with viewOnceMessage wrapper')
|
options.logger?.info('Sending productCarouselMessage with viewOnceMessage wrapper')
|
||||||
}
|
}
|
||||||
// ⚠️ EXPERIMENTAL: Check for interactive messages FIRST (buttons, lists, templates)
|
// Interactive messages: buttons, lists, templates
|
||||||
// These use the older API which may not work reliably
|
|
||||||
else if (hasNonNullishProperty(message, 'text') && hasNonNullishProperty(message, 'buttons')) {
|
else if (hasNonNullishProperty(message, 'text') && hasNonNullishProperty(message, 'buttons')) {
|
||||||
// Process buttons for text messages
|
// Process buttons for text messages
|
||||||
const buttonsMessage: proto.Message.IButtonsMessage = {
|
const buttonsMessage: proto.Message.IButtonsMessage = {
|
||||||
@@ -1304,7 +1303,7 @@ export const generateWAMessageContent = async (
|
|||||||
}))
|
}))
|
||||||
|
|
||||||
m.buttonsMessage = buttonsMessage
|
m.buttonsMessage = buttonsMessage
|
||||||
options.logger?.warn('[EXPERIMENTAL] Sending buttonsMessage - this may not work and can cause bans')
|
options.logger?.info('[Interactive] Sending buttonsMessage (legacy format)')
|
||||||
} else if (hasNonNullishProperty(message, 'text') && hasNonNullishProperty(message, 'templateButtons')) {
|
} else if (hasNonNullishProperty(message, 'text') && hasNonNullishProperty(message, 'templateButtons')) {
|
||||||
// Process templateButtons
|
// Process templateButtons
|
||||||
const hydratedButtons = ((message as any).templateButtons as any[]).map((btn: any) => {
|
const hydratedButtons = ((message as any).templateButtons as any[]).map((btn: any) => {
|
||||||
@@ -1328,7 +1327,7 @@ export const generateWAMessageContent = async (
|
|||||||
}
|
}
|
||||||
|
|
||||||
m.templateMessage = templateMessage
|
m.templateMessage = templateMessage
|
||||||
options.logger?.warn('[EXPERIMENTAL] Sending templateMessage - this may not work and can cause bans')
|
options.logger?.info('[Interactive] Sending templateMessage')
|
||||||
} else if (hasNonNullishProperty(message, 'sections')) {
|
} else if (hasNonNullishProperty(message, 'sections')) {
|
||||||
// Process list messages - validate limits
|
// Process list messages - validate limits
|
||||||
validateListSections((message as any).sections, (message as any).buttonText)
|
validateListSections((message as any).sections, (message as any).buttonText)
|
||||||
@@ -1350,7 +1349,7 @@ export const generateWAMessageContent = async (
|
|||||||
}))
|
}))
|
||||||
|
|
||||||
m.listMessage = listMessage
|
m.listMessage = listMessage
|
||||||
options.logger?.warn('[EXPERIMENTAL] Sending listMessage - this may not work and can cause bans')
|
options.logger?.info({ sections: listMessage.sections?.length || 0 }, '[Interactive] Sending listMessage (sections: ' + (listMessage.sections?.length || 0) + ')')
|
||||||
} else if (hasNonNullishProperty(message, 'carousel')) {
|
} else if (hasNonNullishProperty(message, 'carousel')) {
|
||||||
// Process carousel/interactive messages with viewOnceMessage wrapper
|
// Process carousel/interactive messages with viewOnceMessage wrapper
|
||||||
const carousel = (message as any).carousel
|
const carousel = (message as any).carousel
|
||||||
@@ -1530,7 +1529,7 @@ export const generateWAMessageContent = async (
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// ⚠️ EXPERIMENTAL: Process buttons with media (image/video)
|
// Process buttons with media (image/video)
|
||||||
else if (
|
else if (
|
||||||
(hasNonNullishProperty(message, 'image') || hasNonNullishProperty(message, 'video')) &&
|
(hasNonNullishProperty(message, 'image') || hasNonNullishProperty(message, 'video')) &&
|
||||||
hasNonNullishProperty(message, 'buttons')
|
hasNonNullishProperty(message, 'buttons')
|
||||||
@@ -1583,7 +1582,7 @@ export const generateWAMessageContent = async (
|
|||||||
}))
|
}))
|
||||||
|
|
||||||
m.buttonsMessage = buttonsMessage
|
m.buttonsMessage = buttonsMessage
|
||||||
options.logger?.warn('[EXPERIMENTAL] Sending buttonsMessage with media - this may not work and can cause bans')
|
options.logger?.info('[Interactive] Sending buttonsMessage with media')
|
||||||
} else if (hasOptionalProperty(message, 'ptv') && message.ptv) {
|
} else if (hasOptionalProperty(message, 'ptv') && message.ptv) {
|
||||||
const { videoMessage } = await prepareWAMessageMedia({ video: message.video }, options)
|
const { videoMessage } = await prepareWAMessageMedia({ video: message.video }, options)
|
||||||
m.ptvMessage = videoMessage
|
m.ptvMessage = videoMessage
|
||||||
|
|||||||
@@ -1444,7 +1444,7 @@ export const metrics = {
|
|||||||
new Counter('ctwa_recovery_failures_total', 'Total CTWA recovery failures', ['reason'])
|
new Counter('ctwa_recovery_failures_total', 'Total CTWA recovery failures', ['reason'])
|
||||||
),
|
),
|
||||||
|
|
||||||
// ========== Interactive Messages Metrics (EXPERIMENTAL) ==========
|
// ========== Interactive Messages Metrics ==========
|
||||||
interactiveMessagesSent: baileysMetrics.register(
|
interactiveMessagesSent: baileysMetrics.register(
|
||||||
new Counter(
|
new Counter(
|
||||||
'interactive_messages_sent_total',
|
'interactive_messages_sent_total',
|
||||||
|
|||||||
Reference in New Issue
Block a user