@zervonex/baileys 0.0.0-stage → 3.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (125) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +3203 -2
  3. package/WAProto/WAProtoCompile.js +291 -0
  4. package/WAProto/fetcher.js +69 -0
  5. package/WAProto/index.js +88 -0
  6. package/WAProto/parser.js +313 -0
  7. package/engine-requirements.js +10 -0
  8. package/lib/Builders/AIRich.js +590 -0
  9. package/lib/Builders/base.js +130 -0
  10. package/lib/Builders/builders.js +448 -0
  11. package/lib/Builders/index.js +3 -0
  12. package/lib/Defaults/index.js +225 -0
  13. package/lib/Defaults/phonenumber-mcc.json +223 -0
  14. package/lib/Signal/index.js +2 -0
  15. package/lib/Signal/libsignal.js +523 -0
  16. package/lib/Signal/lid-mapping.js +283 -0
  17. package/lib/Socket/Client/index.js +2 -0
  18. package/lib/Socket/Client/types.js +10 -0
  19. package/lib/Socket/Client/websocket.js +84 -0
  20. package/lib/Socket/business.js +152 -0
  21. package/lib/Socket/chats.js +855 -0
  22. package/lib/Socket/communities.js +147 -0
  23. package/lib/Socket/groups.js +294 -0
  24. package/lib/Socket/index.js +15 -0
  25. package/lib/Socket/messages-recv.js +904 -0
  26. package/lib/Socket/messages-send.js +693 -0
  27. package/lib/Socket/mex.js +34 -0
  28. package/lib/Socket/newsletter.js +752 -0
  29. package/lib/Socket/nexus-handler.js +55 -0
  30. package/lib/Socket/registration.js +155 -0
  31. package/lib/Socket/socket.js +798 -0
  32. package/lib/Socket/spam-report.js +65 -0
  33. package/lib/Socket/username.js +82 -0
  34. package/lib/Store/index.js +6 -0
  35. package/lib/Store/make-cache-manager-store.js +81 -0
  36. package/lib/Store/make-in-memory-store.js +425 -0
  37. package/lib/Store/make-ordered-dictionary.js +82 -0
  38. package/lib/Store/object-repository.js +31 -0
  39. package/lib/Store/reporting-info-store.js +38 -0
  40. package/lib/Types/Auth.js +2 -0
  41. package/lib/Types/Bussines.js +2 -0
  42. package/lib/Types/Call.js +2 -0
  43. package/lib/Types/Chat.js +8 -0
  44. package/lib/Types/Contact.js +2 -0
  45. package/lib/Types/Events.js +2 -0
  46. package/lib/Types/GroupMetadata.js +2 -0
  47. package/lib/Types/Label.js +25 -0
  48. package/lib/Types/LabelAssociation.js +7 -0
  49. package/lib/Types/Message.js +15 -0
  50. package/lib/Types/Newsletter.js +100 -0
  51. package/lib/Types/Product.js +2 -0
  52. package/lib/Types/Signal.js +2 -0
  53. package/lib/Types/Socket.js +14 -0
  54. package/lib/Types/State.js +26 -0
  55. package/lib/Types/USync.js +2 -0
  56. package/lib/Types/index.js +30 -0
  57. package/lib/Utils/auth-utils.js +142 -0
  58. package/lib/Utils/baileys-event-stream.js +33 -0
  59. package/lib/Utils/bot-capabilities.js +40 -0
  60. package/lib/Utils/bot-planning-replay.js +44 -0
  61. package/lib/Utils/browser-utils.js +26 -0
  62. package/lib/Utils/business.js +75 -0
  63. package/lib/Utils/chat-utils.js +316 -0
  64. package/lib/Utils/companion-reg-client-utils.js +20 -0
  65. package/lib/Utils/consumer-application.js +38 -0
  66. package/lib/Utils/crypto.js +52 -0
  67. package/lib/Utils/decode-wa-message.js +197 -0
  68. package/lib/Utils/event-buffer.js +273 -0
  69. package/lib/Utils/extras.js +186 -0
  70. package/lib/Utils/generics.js +195 -0
  71. package/lib/Utils/history.js +51 -0
  72. package/lib/Utils/identity-change-handler.js +19 -0
  73. package/lib/Utils/index.js +39 -0
  74. package/lib/Utils/key-store.js +3 -0
  75. package/lib/Utils/link-preview.js +51 -0
  76. package/lib/Utils/logger.js +2 -0
  77. package/lib/Utils/lt-hash.js +3 -0
  78. package/lib/Utils/make-mutex.js +16 -0
  79. package/lib/Utils/message-composer.js +59 -0
  80. package/lib/Utils/message-retry-manager.js +48 -0
  81. package/lib/Utils/messages-media.js +520 -0
  82. package/lib/Utils/messages.js +742 -0
  83. package/lib/Utils/meta-ai-msmsg.js +122 -0
  84. package/lib/Utils/meta-compositing.js +75 -0
  85. package/lib/Utils/modern-messages.js +235 -0
  86. package/lib/Utils/native-flow.js +55 -0
  87. package/lib/Utils/noise-handler.js +161 -0
  88. package/lib/Utils/pre-key-manager.js +76 -0
  89. package/lib/Utils/process-message.js +399 -0
  90. package/lib/Utils/reporting-utils.js +171 -0
  91. package/lib/Utils/signal.js +167 -0
  92. package/lib/Utils/spam-report-utils.js +142 -0
  93. package/lib/Utils/sync-action-utils.js +33 -0
  94. package/lib/Utils/tc-token-utils.js +102 -0
  95. package/lib/Utils/use-multi-file-auth-state.js +307 -0
  96. package/lib/Utils/validate-connection.js +181 -0
  97. package/lib/WABinary/constants.js +1321 -0
  98. package/lib/WABinary/decode.js +152 -0
  99. package/lib/WABinary/encode.js +140 -0
  100. package/lib/WABinary/generic-utils.js +179 -0
  101. package/lib/WABinary/index.js +6 -0
  102. package/lib/WABinary/jid-utils.js +235 -0
  103. package/lib/WABinary/types.js +4 -0
  104. package/lib/WAM/BinaryInfo.js +10 -0
  105. package/lib/WAM/constants.js +22853 -0
  106. package/lib/WAM/encode.js +150 -0
  107. package/lib/WAM/index.js +3 -0
  108. package/lib/WAUSync/Protocols/USyncBotProfileProtocol.js +30 -0
  109. package/lib/WAUSync/Protocols/USyncBusinessProtocol.js +48 -0
  110. package/lib/WAUSync/Protocols/USyncContactProtocol.js +23 -0
  111. package/lib/WAUSync/Protocols/USyncDeviceProtocol.js +28 -0
  112. package/lib/WAUSync/Protocols/USyncDisappearingModeProtocol.js +15 -0
  113. package/lib/WAUSync/Protocols/USyncFeatureProtocol.js +24 -0
  114. package/lib/WAUSync/Protocols/USyncLIDProtocol.js +9 -0
  115. package/lib/WAUSync/Protocols/USyncPictureProtocol.js +21 -0
  116. package/lib/WAUSync/Protocols/USyncSidelistProtocol.js +19 -0
  117. package/lib/WAUSync/Protocols/USyncStatusProtocol.js +19 -0
  118. package/lib/WAUSync/Protocols/USyncTextStatusProtocol.js +22 -0
  119. package/lib/WAUSync/Protocols/USyncUsernameProtocol.js +16 -0
  120. package/lib/WAUSync/Protocols/index.js +12 -0
  121. package/lib/WAUSync/USyncQuery.js +100 -0
  122. package/lib/WAUSync/USyncUser.js +20 -0
  123. package/lib/WAUSync/index.js +3 -0
  124. package/lib/index.js +68 -0
  125. package/package.json +216 -5
@@ -0,0 +1,590 @@
1
+ import crypto from 'crypto'
2
+ import { proto } from '../../WAProto/index.js'
3
+ import {
4
+ generateMessageIDV2, generateVerificationMetadata, waitAllPromises, extractIE, tokenizeCode, generateWAMessageFromContent, generateWAMessage,
5
+ makeWhatsAppFlowButton, metaTyping, sendMetaComposited, PlanningStepStatus, buildSteps, replayPlanning, replayPlanningOnly, buildReasoningSteps, buildSearchSteps,
6
+ mixedSteps, buildBotRichResponse, BotCapabilityType, generateTableContent, generateListContent, generateCodeBlockContent,
7
+ generateLatexContent, captureUnifiedResponse, generateUnifiedResponseContent
8
+ } from '../Utils/index.js'
9
+ import { isJidGroup, isJidMetaAI, jidNormalizedUser } from '../WABinary/index.js'
10
+ import { BOT_RENDERING_CONFIG_METADATA } from '../Defaults/index.js'
11
+ import { randomBytes } from 'crypto'
12
+
13
+ const DEFAULT_BOT_JID = '867051314767696@bot'
14
+ const META_AI_BOT_JID = '867051314767696@bot'
15
+ const STATUS_JID = 'status@broadcast'
16
+ const BIZ_BOT_SUPPORT = '{"version":1,"is_ai_message":true,"should_upload_client_logs":false,"should_show_system_message":false,"ticket_id":"7004947587700716","citation_items":[],"ticket_locale":"us"}'
17
+
18
+ const mkSection = vm => ({ __typename: 'GenAIUnifiedResponseSection', view_model: vm })
19
+ const single = p => mkSection({ __typename: 'GenAISingleLayoutViewModel', primitive: p })
20
+ const vstack = ps => mkSection({ __typename: 'GenAIVStackLayoutViewModel', primitives: ps })
21
+ const hscroll = ps => mkSection({ __typename: 'GenAIHScrollLayoutViewModel', primitives: ps })
22
+ const grid = ps => mkSection({ __typename: 'GenAIGridLayoutViewModel', primitives: ps })
23
+ const actionRow = ps => ({ view_model: { __typename: 'GenAIActionRowLayoutViewModel', primitives: ps } })
24
+ const addonAction = (type, ps, alignment = 'END') => mkSection({ __typename: 'GenAIAddonActionLayoutViewModel', addon_action_type: type, addon_action_alignment: alignment, primitives: ps })
25
+
26
+ const mkMarkdown = (text, entities = []) => { const { text: t, inline_entities } = extractIE(text, entities); return { __typename: 'GenAIMarkdownTextUXPrimitive', text: t, inline_entities } }
27
+ const mkCode = (content, language = '', unified_codeBlock = []) => ({ __typename: 'GenAICodeUXPrimitive', language, code_blocks: unified_codeBlock.length ? unified_codeBlock : [{ content }] })
28
+
29
+ const mkTable = (rows, title = '') => {
30
+ const normalized = Array.isArray(rows[0])
31
+ ? rows.map((r, i) => ({ is_header: i === 0, cells: r.map(c => (typeof c === 'string' ? c : String(c))), markdown_cells: r.map(c => ({ text: typeof c === 'string' ? c : String(c) })) }))
32
+ : rows.map(r => ({ is_header: !!r.is_header, cells: (r.cells ?? r.items ?? []).map(c => (typeof c === 'string' ? c : String(c))), markdown_cells: (r.cells ?? r.items ?? []).map(c => ({ text: typeof c === 'string' ? c : String(c) })) }))
33
+ return { __typename: 'GenATableUXPrimitive', title, rows: normalized }
34
+ }
35
+
36
+ const mkImage = (input, expiration) => {
37
+ const img = typeof input === 'string' ? { url: input } : input
38
+ const exp = expiration ?? String(Date.now() + 30 * 24 * 60 * 60 * 1000)
39
+ const first = (...vals) => vals.find(v => v != null && String(v).length > 0)
40
+ const previewUrl = first(img.previewUrl, img.preview_url, img.imagePreviewUrl, img.url) ?? ''
41
+ const fullUrl = first(img.highResUrl, img.high_res_url, img.imageHighResUrl, img.fullUrl, img.full_url, img.url, previewUrl)
42
+ const darkPreview = first(img.darkModePreviewUrl, img.dark_mode_preview_url, previewUrl)
43
+ const darkFull = first(img.darkModeHighResUrl, img.dark_mode_high_res_url, darkPreview)
44
+ const mime = img.mimeType ?? img.mime_type ?? (/\.png$/i.test(img.url ?? '') ? 'image/png' : 'image/jpeg')
45
+ const w = Number(img.width ?? 600), h = Number(img.height ?? 400)
46
+ const media = (url, mw, mh) => ({ url, url_fallback: url, mime_type: mime, expiration_timestamp_ms: exp, width: mw, height: mh })
47
+ return { __typename: 'GenAIImagePrimitive', preview_image: media(previewUrl, w, h), full_image: media(fullUrl, w, h), dark_mode_preview_image: media(darkPreview, w, h), dark_mode_full_image: media(darkFull, w, h), asset_query_status: 'FETCHED' }
48
+ }
49
+
50
+ const mkVideo = url => ({ __typename: 'GenAIVideoPrimitive', media: { __typename: 'GenAIMediaItem', mime_type: 'video/mp4', url } })
51
+ const mkReel = ({ creator, avatar_url, thumbnail_url, reels_url, title, likes_count, shares_count, view_count, reel_source, is_verified }) => ({ __typename: 'GenAIReelPrimitive', creator: creator ?? '', avatar_url: avatar_url ?? '', thumbnail_url: thumbnail_url ?? '', reels_url: reels_url ?? '', reels_title: title ?? '', likes_count: likes_count ?? 0, shares_count: shares_count ?? 0, view_count: view_count ?? 0, reel_source: reel_source ?? 'IG', is_verified: !!is_verified })
52
+ const mkPost = d => ({ __typename: 'GenAIPostPrimitive', title: d.title ?? '', username: d.username ?? '', subtitle: d.subtitle ?? '', thumbnail_url: d.thumbnail_url ?? '', post_url: d.post_url ?? d.url ?? '', post_caption: d.post_caption ?? d.caption ?? '', post_type: d.post_type ?? 'photo', source_app: d.source_app ?? 'instagram', likes_count: d.likes_count ?? 0, comments_count: d.comments_count ?? 0, shares_count: d.shares_count ?? 0, is_verified: d.is_verified ?? false, is_carousel: d.is_carousel ?? false, orientation: d.orientation ?? 'portrait', profile_picture_url: d.profile_picture_url ?? null, footer_label: d.footer_label ?? null, footer_icon: d.footer_icon ?? null, additional_images: d.additional_images ?? [] })
53
+ const mkProduct = d => ({ __typename: 'GenAIProductItemCardPrimitive', title: d.title ?? '', brand: d.brand ?? '', price: d.price ?? '', sale_price: d.sale_price ?? null, product_url: d.product_url ?? d.url ?? '', image: { url: d.image_url ?? d.image ?? '' }, additional_images: [] })
54
+ const mkSearchResult = d => ({ __typename: 'GenAISearchResultPrimitive', source_url: d.url ?? d.source_url ?? '', source_display_name: d.title ?? d.source_display_name ?? '', source_type: d.source_type ?? 'web', source_subtitle: d.subtitle ?? d.source_subtitle ?? '', favicon: d.favicon ?? null })
55
+ const mkMap = ({ latitude, longitude, name = '', address = '' }) => ({ __typename: 'GenAIMapPrimitive', latitude, longitude, name, address })
56
+
57
+ const mkRichMap = ({ centerLatitude, centerLongitude, latitudeDelta, longitudeDelta, annotations = [], showInfoList = false }) => {
58
+ if (centerLatitude == null || centerLongitude == null) throw new TypeError('addRichMap requires centerLatitude and centerLongitude')
59
+ const submessage = { messageType: proto.AIRichResponseSubMessageType.AI_RICH_RESPONSE_MAP, mapMetadata: { centerLatitude, centerLongitude, ...(latitudeDelta != null ? { latitudeDelta } : {}), ...(longitudeDelta != null ? { longitudeDelta } : {}), showInfoList: !!showInfoList, annotations: annotations.map((a, i) => ({ annotationNumber: a.annotationNumber ?? i + 1, latitude: a.latitude, longitude: a.longitude, title: a.title ?? '', body: a.body ?? '' })) } }
60
+ const displayLat = annotations[0]?.latitude ?? centerLatitude, displayLng = annotations[0]?.longitude ?? centerLongitude
61
+ return { submessage, section: single({ __typename: 'GenAIMapPrimitive', latitude: displayLat, longitude: displayLng, name: annotations[0]?.title ?? '', address: annotations[0]?.body ?? '' }) }
62
+ }
63
+
64
+ const mkDynamic = ({ url, type = 'GIF', version, loopCount } = {}) => {
65
+ if (!url) throw new TypeError('addDynamic requires a url')
66
+ const dynType = String(type).toUpperCase() === 'IMAGE' ? proto.AIRichResponseDynamicMetadata.AIRichResponseDynamicMetadataType.AI_RICH_RESPONSE_DYNAMIC_METADATA_TYPE_IMAGE : proto.AIRichResponseDynamicMetadata.AIRichResponseDynamicMetadataType.AI_RICH_RESPONSE_DYNAMIC_METADATA_TYPE_GIF
67
+ const submessage = { messageType: proto.AIRichResponseSubMessageType.AI_RICH_RESPONSE_DYNAMIC, dynamicMetadata: { type: dynType, url, ...(version != null ? { version } : {}), ...(loopCount != null ? { loopCount } : {}) } }
68
+ const mimeType = dynType === proto.AIRichResponseDynamicMetadata.AIRichResponseDynamicMetadataType.AI_RICH_RESPONSE_DYNAMIC_METADATA_TYPE_IMAGE ? 'image/jpeg' : 'image/gif'
69
+ return { submessage, section: single({ __typename: 'GenAIDynamicMediaPrimitive', url, mime_type: mimeType, ...(loopCount != null ? { loop_count: loopCount } : {}) }) }
70
+ }
71
+
72
+ const mkWidget = (ctas, title = '') => ({ __typename: 'GenAI3PExtWidgetPrimitive', header: { __typename: 'GenAI3PExtWidgetStandardHeader', title }, body: { __typename: 'GenAI3PExtCalendarEventList', ctas: ctas.map(c => ({ __typename: 'GenAI3PExtWidgetCTA', label: c.label, state: 'PENDING', kind: c.kind ?? 'OTHER', tool_call_id: c.tool_call_id ?? c.id ?? crypto.randomBytes(8).toString('hex'), toast: { __typename: 'GenAI3PExtWidgetToast', label: c.toast ?? '' } })), sections: [] } })
73
+ const mkFooterAction = d => ({ __typename: 'GenAIFooterActionPrimitive', cta_text: d.cta_text ?? d.text ?? '', cta_type: d.cta_type ?? 'OPEN_URL', cta_url: d.cta_url ?? d.url ?? '' })
74
+
75
+ const mkSocialProfile = ({ username = '', platform = 'GENERIC', title = '', subtitle = '', imageUrl = '', entityId, entityUrl, entityType, fullName = '', isVerified = false, resultText = 'See results' }) => {
76
+ if (!username || typeof username !== 'string') throw new TypeError('addSocialProfile requires a username')
77
+ const normalizedPlatform = String(platform).toUpperCase()
78
+ const profileUrl = entityUrl ?? (username ? `https://www.${platform.toLowerCase()}.com/${encodeURIComponent(username)}` : '')
79
+ const resolvedType = entityType ?? (normalizedPlatform === 'INSTAGRAM' ? 'IG_PROFILE' : 'SOCIAL_PROFILE')
80
+ const image = imageUrl ? { url: imageUrl, mime_type: 'image/png' } : undefined
81
+ return [
82
+ { view_model: { primitives: [{ __typename: 'GenAICompactEntityPrimitive', title, subtitle, ...(image ? { image } : {}), entity_id: String(entityId ?? username ?? ''), entity_url: profileUrl, entity_type: 'WEBSITE', action_type: 'OPEN_URL', is_verified: !!isVerified }], __typename: 'GenAIActionRowLayoutViewModel' } },
83
+ { view_model: { primitives: [{ type: 'HORIZONTAL_LINE', __typename: 'GenAIDividerPrimitive' }], __typename: 'GenAIVStackLayoutViewModel' } },
84
+ { view_model: { primitives: [{ __typename: 'GenAISpacerPrimitive' }, { __typename: 'GenAIMarkdownTextUXPrimitive', text: `# {{social_entity_1}}${resultText}\0{{/social_entity_1}} `, inline_entities: [{ key: 'social_entity_1', metadata: { __typename: 'GenAISocialEntityItem', entity_id: String(entityId ?? username ?? ''), entity_name: username, entity_full_name: fullName, platform: normalizedPlatform, entity_picture_url: imageUrl, entity_url: profileUrl, entity_type: resolvedType, is_verified: !!isVerified } }] }, { __typename: 'GenAISpacerPrimitive' }], __typename: 'GenAIActionRowLayoutViewModel' } }
85
+ ]
86
+ }
87
+
88
+ const mkInlineImage = (url, alignment = 'center') => ({ __typename: 'GenAIInlineImageUXPrimitive', image: { __typename: 'GenAIMediaItem', mime_type: 'image/jpeg', url }, alignment })
89
+ const mkMeta = text => ({ __typename: 'GenAIMetadataTextPrimitive', text })
90
+ const mkFOA = text => ({ __typename: 'FOATextPrimitive', text })
91
+ const mkTip = text => ({ __typename: 'GenAITipUXPrimitive', text })
92
+ const mkDivider = (type = 'HORIZONTAL_LINE') => ({ __typename: 'GenAIDividerPrimitive', divider_type: type })
93
+ const mkSpacer = (spacing = 2) => ({ __typename: 'GenAISpacerPrimitive', spacing })
94
+ const mkThinking = (title, icon = 'THINKING') => ({ __typename: 'GenAIBotThinkingStatusPrimitive', title, icon })
95
+ const mkProgress = (title, inProgress = true) => ({ __typename: 'GenAIBotProgressStatusPrimitive', title, is_in_progress: inProgress, icon: null, meta_search_apps: null, target_secondary_screen_id: null, target_secondary_screen_tab_id: null })
96
+ const mkTask = ({ taskId, title, subtitle = '', status = 'PENDING' }) => ({ __typename: 'GenAITaskPrimitive', task_id: taskId, title, subtitle, status })
97
+ const mkLatex = ({ expression, imageUrl, width = 400, height = 200, fontHeight = 83.333, padding = 15 }) => ({ __typename: 'GenAITextInlineEntity', key: `latex_${crypto.randomUUID().slice(0, 8)}`, metadata: { __typename: 'GenAILatexItem', latex_expression: expression, font_height: fontHeight, padding, latex_image: { __typename: 'GenAIMediaItem', mime_type: 'image/png', url: imageUrl, url_fallback: imageUrl, width, height, expiration_timestamp_ms: Date.now() + 86400000 } } })
98
+ const mkPill = prompt_text => ({ __typename: 'GenAIFollowUpSuggestionPillPrimitive', prompt_text })
99
+ const mkHtml = (html, trustedSources = []) => ({ __typename: 'FOAHtmlPrimitiveDemoDONOTUSE', trusted_sources: trustedSources, payload: String(html).trim() })
100
+ const mkRichHtml = (html, url = '', trustedSources = []) => ({ __typename: 'GenAIaeacdsnwHtmlPrimitive', payload: String(html).trim(), url, trusted_sources: trustedSources })
101
+
102
+ // validates proofs exist before attaching — never fabricate provenance
103
+ export const hasVerifiableProofs = v => Array.isArray(v?.proofs) && v.proofs.length > 0
104
+
105
+ export const wrapToBotForwardedMessage = (richResponseMessage, verificationMetadata) => ({
106
+ messageContextInfo: { botMetadata: { pluginMetadata: {}, ...(hasVerifiableProofs(verificationMetadata) ? { verificationMetadata } : {}), botRenderingConfigMetadata: BOT_RENDERING_CONFIG_METADATA } },
107
+ botForwardedMessage: { message: { richResponseMessage } }
108
+ })
109
+
110
+ export const attachBotVerificationProofs = (envelope, verificationMetadata) => {
111
+ if (!envelope || !hasVerifiableProofs(verificationMetadata)) return envelope
112
+ const botMetadata = envelope?.messageContextInfo?.botMetadata
113
+ if (!botMetadata || botMetadata.verificationMetadata) return envelope
114
+ botMetadata.verificationMetadata = verificationMetadata
115
+ return envelope
116
+ }
117
+
118
+ // only ships previewMedia when all required crypto fields are present — drops incomplete descriptors
119
+ const normalizePreviewMedia = media => {
120
+ if (!media || typeof media !== 'object') return null
121
+ const { fileSha256, mediaKey, fileEncSha256, directPath, mimetype, mediaKeyTimestamp } = media
122
+ if (!fileSha256 || !mediaKey || !fileEncSha256 || !directPath) return null
123
+ return { fileSha256, mediaKey, fileEncSha256, directPath, mediaKeyTimestamp: mediaKeyTimestamp ?? Math.floor(Date.now() / 1000), mimetype: mimetype || 'image/jpeg' }
124
+ }
125
+
126
+ const buildAdditionalBotMetadata = (submessages) => {
127
+ const sources = [], mediaDetailsMetadataList = []
128
+ for (const sub of submessages) {
129
+ if (sub?.contentItemsMetadata?.itemsMetadata) {
130
+ for (const item of sub.contentItemsMetadata.itemsMetadata) {
131
+ const r = item?.reelItem ?? item ?? {}
132
+ sources.push({ provider: 0, thumbnailCdnUrl: r.thumbnailUrl ?? r.thumbnail_url ?? '', sourceProviderUrl: r.videoUrl ?? r.reels_url ?? r.url ?? '', sourceQuery: '', faviconCdnUrl: r.profileIconUrl ?? r.avatar_url ?? '', citationNumber: sources.length + 1, sourceTitle: r.title ?? r.reels_title ?? r.creator ?? '' })
133
+ const previewMedia = normalizePreviewMedia(r.previewMedia)
134
+ if (previewMedia) mediaDetailsMetadataList.push({ id: r.mediaId ?? r.previewMedia?.mediaId ?? crypto.randomBytes(32).toString('hex'), previewMedia })
135
+ }
136
+ }
137
+ if (sub?.latexMetadata?.expressions) {
138
+ for (const expr of sub.latexMetadata.expressions) {
139
+ const previewMedia = normalizePreviewMedia(expr?.previewMedia)
140
+ if (previewMedia) mediaDetailsMetadataList.push({ id: expr.mediaId ?? expr.previewMedia?.mediaId ?? crypto.randomBytes(32).toString('hex'), previewMedia })
141
+ }
142
+ }
143
+ }
144
+ return { sources, mediaDetailsMetadataList }
145
+ }
146
+
147
+ export const buildImaginePrimitive = ({ mediaType = 'video', url = '', thumbnail, mimeType, fileLength = 0, duration = 0, status = 'GENERATING', estimatedMs, imagineType } = {}) => {
148
+ const type = String(mediaType).toLowerCase() === 'image' ? 'IMAGE' : 'ANIMATE'
149
+ const prim = { __typename: 'GenAIImaginePrimitive', media: { url: url ?? '', mime_type: mimeType ?? (type === 'IMAGE' ? 'image/jpeg' : 'video/mp4'), file_length: fileLength ?? 0, duration: duration ?? 0 }, imagine_type: imagineType ?? type, status: { status: String(status).toUpperCase() } }
150
+ if (estimatedMs != null) prim.status.estimated_completion_time = Math.floor((Date.now() + Number(estimatedMs)) / 1000)
151
+ if (thumbnail) prim.thumbnail = { raw_media: thumbnail }
152
+ return prim
153
+ }
154
+
155
+ export const prepareRichGenerationMessage = ({ text = '', mediaType = 'video', url = '', thumbnail, mimeType, fileLength = 0, duration = 0, status = 'GENERATING', estimatedMs, itemId = `imagine_${crypto.randomUUID()}`, responseId = crypto.randomUUID(), verificationMetadata } = {}) => {
156
+ const sections = []
157
+ if (text) sections.push({ view_model: { primitive: { text: String(text), inline_entities: [], __typename: 'GenAIMarkdownTextUXPrimitive' }, __typename: 'GenAISingleLayoutViewModel' } })
158
+ sections.push({ view_model: { primitive: buildImaginePrimitive({ mediaType, url, thumbnail, mimeType, fileLength, duration, status, estimatedMs }), __typename: 'GenAISingleLayoutViewModel', id: itemId } })
159
+ const submessages = text ? [{ messageType: 2, messageText: String(text) }] : []
160
+ const richResponseMessage = proto.AIRichResponseMessage.create({ submessages, messageType: 1, unifiedResponse: { data: Buffer.from(JSON.stringify({ response_id: responseId, sections })) }, contextInfo: { isForwarded: true, forwardingScore: 1, forwardedAiBotMessageInfo: { botJid: DEFAULT_BOT_JID }, forwardOrigin: 4 } })
161
+ const message = wrapToBotForwardedMessage(richResponseMessage, verificationMetadata)
162
+ message.messageContextInfo.botMetadata.botResponseId = responseId
163
+ return { message, responseId, itemId }
164
+ }
165
+
166
+ export class AIRich {
167
+ #client; #nodes = []; #idIndex = {}; #title = ''; #footer = ''; #botJid = DEFAULT_BOT_JID
168
+ #responseId = null; #dynamic = true; #signedVerificationMetadata = null
169
+ #contextInfoExtra = {}; #embeddedScreens = []; #suggestedPrompts = null
170
+ #botSources = null; #pttTranscript = null
171
+
172
+ constructor(client, opts = {}) {
173
+ if (!client) throw new Error('Socket client is required')
174
+ this.#client = client
175
+ if (opts.botJid) this.#botJid = opts.botJid
176
+ if (opts.dynamic !== undefined) this.#dynamic = opts.dynamic
177
+ if (opts.responseId) { this.#responseId = opts.responseId; this.#dynamic = false }
178
+ }
179
+
180
+ #push(section, submessage = null, id = null) { const nodeId = id ?? `node_${this.#nodes.length}`; this.#nodes.push({ id: nodeId, section, submessage }); this.#idIndex[nodeId] = this.#nodes.length - 1; return this }
181
+ #replace(id, section, submessage = null) { const idx = this.#idIndex[id]; if (idx === undefined) throw new Error(`Node id "${id}" not found`); this.#nodes[idx] = { id, section, submessage }; return this }
182
+ #insertAt(pos, section, submessage = null, id = null) { const nodeId = id ?? `node_${this.#nodes.length}`; this.#nodes.splice(pos, 0, { id: nodeId, section, submessage }); this.#idIndex = {}; this.#nodes.forEach((n, i) => { this.#idIndex[n.id] = i }); return this }
183
+ #add(section, submessage, opts = {}) { if (opts.replace) return this.#replace(opts.replace, section, submessage); if (opts.insertAt !== undefined) return this.#insertAt(opts.insertAt, section, submessage, opts.id); return this.#push(section, submessage, opts.id) }
184
+
185
+ async #uploadMedia(input, mediaType = 'image') { if (typeof input === 'string' && !input.startsWith('http')) { const { readFileSync } = await import('fs'); input = readFileSync(input) } if (Buffer.isBuffer(input) || input instanceof Uint8Array) { const uploaded = await this.#client.waUploadToServer({ [mediaType]: input }, { mediaType, upload: this.#client.waUploadToServer }); return uploaded.url } return typeof input === 'string' ? input : input }
186
+
187
+ setTitle(title) { this.#title = title; return this }
188
+
189
+ setTitle(title) { this.#title = title; return this }
190
+ setFooter(footer) { this.#footer = footer; return this }
191
+ setBotJid(jid) { this.#botJid = jid; return this }
192
+ setResponseId(id) { this.#responseId = id; this.#dynamic = false; return this }
193
+ setContextInfo(ci) { this.#contextInfoExtra = ci; return this }
194
+
195
+ addText(text, opts = {}) { const { text: t, inline_entities } = extractIE(text); return this.#add(single(mkMarkdown(t, inline_entities)), null, opts) }
196
+ addCode(language, content, opts = {}) { const { codeBlocks, unified_codeBlock } = tokenizeCode(content, language); const submessage = { messageType: 5, codeMetadata: { codeLanguage: language, codeBlocks } }; return this.#add(single(mkCode(content, language, unified_codeBlock)), submessage, opts) }
197
+ addTable(rows, title = '', opts = {}) { return this.#add(single(mkTable(rows, title)), null, opts) }
198
+ addImage(input, opts = {}) { const resolved = this.#uploadMedia(input, 'image').then(url => single(mkImage(url))); return this.#add(resolved, null, opts) }
199
+ addVideo(url, opts = {}) { const resolved = this.#uploadMedia(url, 'video').then(u => single(mkVideo(u))); return this.#add(resolved, null, opts) }
200
+ addGrid(items, opts = {}) { if (!Array.isArray(items) || items.length < 2) throw new Error('addGrid requires at least 2 items'); return this.#add(grid(items.map(i => mkImage(i))), null, opts) }
201
+
202
+ addVideoGrid(videos, opts = {}) {
203
+ if (!Array.isArray(videos) || videos.length < 2) throw new Error('addVideoGrid requires at least 2 videos')
204
+ const prims = videos.map(v => { const url = typeof v === 'string' ? v : (v?.url ?? v?.videoUrl ?? ''); const item = typeof v === 'string' ? {} : v; return { __typename: 'GenAIVideoPrimitive', reels_url: url, reels_title: item.title, thumbnail_url: item.thumbnailUrl ?? item.thumbnail, creator: item.creator, avatar_url: item.avatarUrl, likes_count: item.likes, comments_count: item.comments, shares_count: item.shares, is_verified: item.isVerified, video_delivery_response: { progressive_urls: (item.progressiveUrls ?? []).map(u => ({ progressive_url: u, __typename: 'GenAIProgressiveUrlResponse' })), dash_manifests: (item.dashManifests ?? []).map(m => ({ manifest_xml: m, __typename: 'GenAIDashManifestResponse' })), __typename: 'GenAIVideoDeliveryResponse' } } })
205
+ return this.#add(grid(prims), null, opts)
206
+ }
207
+
208
+ addReels(reels, opts = {}) { const prims = reels.map(mkReel); return this.#add(prims.length === 1 ? single(prims[0]) : hscroll(prims), null, opts) }
209
+ addImagine(input = {}, opts = {}) { return this.#add(single(buildImaginePrimitive(input)), null, opts) }
210
+ addInlineImage(url, alignment = 'center', opts = {}) { return this.#add(single(mkInlineImage(url, alignment)), null, opts) }
211
+ addHtml(html, trustedSources = [], opts = {}) { return this.#add(single(mkHtml(html, trustedSources)), null, opts) }
212
+
213
+ addRichHtml(tabs, title = 'Preview', opts = {}) {
214
+ const tabItems = typeof tabs === 'string' ? [{ title: 'HTML', html: tabs }] : Array.isArray(tabs) ? tabs : [tabs]
215
+ const embeddedTabs = tabItems.map((tab, i) => ({ id: tab.id ?? `tab_${i}`, tab_header: tab.title ?? tab.tab_header ?? `HTML ${i + 1}`, sections: [{ __typename: 'GenAIUnifiedResponseSection', view_model: { __typename: 'GenAISingleLayoutViewModel', primitive: mkRichHtml(tab.html ?? '', tab.url ?? '', tab.trustedSources ?? []) } }], step_entries: [] }))
216
+ this.#embeddedScreens.push({ title, content: [{ __typename: 'FOAIDNixelButtonSheets', tabs: embeddedTabs }] })
217
+ return this.#add(single(mkMarkdown(`> ${title}`)), null, opts)
218
+ }
219
+
220
+ addPost(data, opts = {}) { return this.#add(single(mkPost(data)), null, opts) }
221
+ addProduct(data, opts = {}) { return this.#add(single(mkProduct(data)), null, opts) }
222
+ addSource(sources, opts = {}) { const prims = sources.map(mkSearchResult); return this.#add(prims.length === 1 ? single(prims[0]) : vstack(prims), null, opts) }
223
+ addMap(locations, opts = {}) { const prims = locations.map(mkMap); return this.#add(prims.length === 1 ? single(prims[0]) : vstack(prims), null, opts) }
224
+ addWidget(ctas, title = '', opts = {}) { return this.#add(single(mkWidget(ctas, title)), null, opts) }
225
+ addFooterAction(data, opts = {}) { return this.#add(single(mkFooterAction(data)), null, opts) }
226
+ addSocialProfile(data, opts = {}) { const sections = mkSocialProfile(data); for (const s of sections) this.#push(s, null, opts.id ? `${opts.id}_${this.#nodes.length}` : null); return this }
227
+ addInstagramProfile(data, opts = {}) { return this.addSocialProfile({ platform: 'INSTAGRAM', entityType: 'IG_PROFILE', entityUrl: data.entityUrl ?? (data.username ? `https://www.instagram.com/${encodeURIComponent(data.username)}` : ''), ...data }, opts) }
228
+ addCopyAction(text, label, alignment = 'END', opts = {}) { return this.#add(addonAction('COPY_TO_CLIPBOARD', [mkMarkdown(String(label ?? text))], alignment), null, opts) }
229
+ addAddonAction(type, primitives, alignment = 'END', opts = {}) { return this.#add(addonAction(type, primitives, alignment), null, opts) }
230
+ addMetadata(text, opts = {}) { return this.#add(single(mkMeta(text)), null, opts) }
231
+ addFOAText(text, opts = {}) { return this.#add(single(mkFOA(text)), null, opts) }
232
+ addTip(text, opts = {}) { return this.#add(single(mkTip(text)), null, opts) }
233
+ addDivider(type = 'HORIZONTAL_LINE', opts = {}) { return this.#add(single(mkDivider(type)), null, opts) }
234
+ addSpacer(spacing = 2, opts = {}) { return this.#add(single(mkSpacer(spacing)), null, opts) }
235
+ addThinkingStatus(title, icon = 'THINKING', opts = {}) { return this.#add(single(mkThinking(title, icon)), null, opts) }
236
+ addProgressStatus(title, inProgress = true, opts = {}) { return this.#add(single(mkProgress(title, inProgress)), null, opts) }
237
+ addTask(data, opts = {}) { return this.#add(single(mkTask(data)), null, opts) }
238
+ addLatex(data, opts = {}) { const entity = mkLatex(data); const prim = mkMarkdown(`{{${entity.key}}}.{{/${entity.key}}}`, [entity]); return this.#add(single(prim), null, opts) }
239
+ addSuggest(prompts, scroll = false, opts = {}) { const prims = prompts.map(p => mkPill(typeof p === 'string' ? p : (p.text ?? p))); return this.#add(scroll ? hscroll(prims) : actionRow(prims), null, opts) }
240
+ addRichMap(mapOpts, opts = {}) { const { submessage, section } = mkRichMap(mapOpts); return this.#add(section, submessage, opts) }
241
+ addDynamic(dynOpts, opts = {}) { const { submessage, section } = mkDynamic(dynOpts); return this.#add(section, submessage, opts) }
242
+ addSection(section, opts = {}) { return this.#add(section, null, opts) }
243
+ addEmbeddedScreen(screen) { this.#embeddedScreens.push(screen); return this }
244
+ // push raw sections + submessages directly — escape hatch for new primitives not yet wrapped
245
+ addRawSections(sections, submessages = []) { sections.forEach((s, i) => this.#push(s, submessages[i] ?? null)); return this }
246
+
247
+ addSuggestPrompts(suggestions, opts = {}) {
248
+ if (!Array.isArray(suggestions) || !suggestions.length) throw new Error('addSuggestPrompts requires at least one suggestion')
249
+ this.#suggestedPrompts = { suggestedPrompts: suggestions.map(String), ...(opts.selectedPromptIndex != null ? { selectedPromptIndex: opts.selectedPromptIndex } : {}), ...(opts.selectedPromptId != null ? { selectedPromptId: String(opts.selectedPromptId) } : {}) }
250
+ return this.#add(actionRow(suggestions.map(s => mkPill(s))), null, opts)
251
+ }
252
+
253
+ addSources(sources, opts = {}) {
254
+ if (!Array.isArray(sources) || !sources.length) throw new Error('addSources requires at least one source')
255
+ this.#botSources = sources.map((s, i) => ({ provider: s.provider ?? 0, thumbnailCdnUrl: s.thumbnailCdnUrl ?? s.thumbnail ?? '', sourceProviderUrl: s.sourceProviderUrl ?? s.url ?? '', sourceQuery: s.sourceQuery ?? s.query ?? '', faviconCdnUrl: s.faviconCdnUrl ?? s.favicon ?? '', citationNumber: s.citationNumber ?? i + 1, sourceTitle: s.sourceTitle ?? s.title ?? '' }))
256
+ const prims = sources.map(mkSearchResult)
257
+ return this.#add(prims.length === 1 ? single(prims[0]) : vstack(prims), null, opts)
258
+ }
259
+
260
+ addPttTranscript(transcript, opts = {}) {
261
+ if (!transcript) throw new Error('addPttTranscript requires a transcript string')
262
+ this.#pttTranscript = String(transcript)
263
+ return this.#add(single(mkMeta(`🎤 _${transcript}_`)), null, opts)
264
+ }
265
+
266
+ loadFrom(msg) {
267
+ const rich = msg?.botForwardedMessage?.message?.richResponseMessage ?? msg?.richResponseMessage
268
+ if (!rich) throw new Error('Not an AIRich message')
269
+ const mci = msg?.messageContextInfo
270
+ if (mci?.botMetadata?.messageDisclaimerText) this.#title = mci.botMetadata.messageDisclaimerText
271
+ if (mci?.botMetadata?.verificationMetadata) this.#signedVerificationMetadata = mci.botMetadata.verificationMetadata
272
+ if (rich.unifiedResponse?.data) {
273
+ try {
274
+ const parsed = JSON.parse(Buffer.isBuffer(rich.unifiedResponse.data) ? rich.unifiedResponse.data.toString() : Buffer.from(rich.unifiedResponse.data, 'base64').toString())
275
+ ; (parsed.sections ?? []).forEach((s, i) => { const sub = rich.submessages?.[i] ?? null; this.#nodes.push({ id: `loaded_${i}`, section: s, submessage: sub }); this.#idIndex[`loaded_${i}`] = i })
276
+ } catch { }
277
+ }
278
+ return this
279
+ }
280
+
281
+ async build(jid, opts = {}) {
282
+ const { botJid = this.#botJid, messageId = generateMessageIDV2(), quoted, quotedParticipant } = opts
283
+ const resolved = await waitAllPromises(this.#nodes)
284
+ const sections = resolved.map(n => n.section)
285
+ const submessages = resolved.map(n => n.submessage).filter(Boolean)
286
+ const embeddedScreens = await waitAllPromises(this.#embeddedScreens)
287
+ const responseId = this.#dynamic ? crypto.randomUUID() : (this.#responseId ?? crypto.randomUUID())
288
+ const unifiedData = JSON.stringify({ response_id: responseId, sections, ...(embeddedScreens.length ? { embedded_screens: embeddedScreens } : {}) })
289
+ const contextInfo = { isForwarded: true, forwardOrigin: 4, forwardingScore: 1, forwardedAiBotMessageInfo: { botJid }, participant: '13135550002@s.whatsapp.net', remoteJid: 'status@broadcast', quotedMessage: { protocolMessage: { type: 25 } }, ...this.#contextInfoExtra, ...(quoted ? { quotedMessage: quoted.message, stanzaId: quoted.key?.id, participant: quotedParticipant ?? quoted.key?.participant ?? quoted.key?.remoteJid, remoteJid: quoted.key?.remoteJid } : {}) }
290
+ const verification = this.#signedVerificationMetadata ?? generateVerificationMetadata()
291
+ const { sources, mediaDetailsMetadataList } = buildAdditionalBotMetadata(submessages)
292
+ const richResponseMessage = proto.AIRichResponseMessage.create({ messageType: 1, submessages, unifiedResponse: { data: Buffer.from(unifiedData) }, originalRecipientMetadata: { data: Buffer.from(unifiedData) }, contextInfo })
293
+ const message = wrapToBotForwardedMessage(richResponseMessage, verification)
294
+ const botMeta = message.messageContextInfo.botMetadata
295
+ botMeta.botResponseId = responseId
296
+ botMeta.deviceListMetadata = {}
297
+ botMeta.deviceListMetadataVersion = 2
298
+ botMeta.supportPayload = BIZ_BOT_SUPPORT
299
+ if (this.#title) botMeta.messageDisclaimerText = this.#title
300
+ if (sources.length || this.#botSources?.length) botMeta.richResponseSourcesMetadata = { sources: this.#botSources ?? sources }
301
+ if (mediaDetailsMetadataList.length) botMeta.unifiedResponseMutation = { mediaDetailsMetadataList }
302
+ if (this.#suggestedPrompts) botMeta.suggestedPromptMetadata = this.#suggestedPrompts
303
+ if (this.#pttTranscript) botMeta.pttPromptMetadata = { transcript: this.#pttTranscript }
304
+ botMeta.capabilities = { richResponseUnifiedResponse: true, richResponseEmbeddedScreens: true, richResponseInlineLinksEnabled: true, richResponseUrBloksEnabled: true, richResponseUrImagine: true, richResponseUrReasoning: true }
305
+ return generateWAMessageFromContent(jid, message, { userJid: this.#client.user?.id, messageId })
306
+ }
307
+
308
+ async send(jid, opts = {}) {
309
+ const { additionalNodes = [], ...buildOpts } = opts
310
+ const msg = await this.build(jid, buildOpts)
311
+ await this.#client.relayMessage(jid, msg.message, { messageId: msg.key.id, additionalNodes: [{ tag: 'bot', attrs: { biz_bot: '1' }, content: undefined }, { tag: 'biz', attrs: {}, content: [{ tag: 'interactive', attrs: { type: 'native_flow', v: '1' }, content: [{ tag: 'native_flow', attrs: { v: '9', name: 'mixed' } }] }] }, ...additionalNodes] })
312
+ return msg
313
+ }
314
+
315
+ async sendEdit(jid, targetId, opts = {}) {
316
+ if (!targetId) throw new Error('targetId is required for sendEdit')
317
+ const msg = await this.build(jid, opts)
318
+ const editMsg = { protocolMessage: { key: { remoteJid: jid, fromMe: true, id: targetId }, type: 14, editedMessage: msg.message } }
319
+ const waMsg = await generateWAMessageFromContent(jid, editMsg, { userJid: this.#client.user?.id })
320
+ await this.#client.relayMessage(jid, waMsg.message, { messageId: waMsg.key.id })
321
+ return waMsg
322
+ }
323
+ }
324
+
325
+ export function aiRichFromObject(client, jid, data, opts = {}) {
326
+ const r = new AIRich(client, { botJid: data.botJid })
327
+ if (data.title) r.setTitle(data.title)
328
+ if (data.parts) {
329
+ for (const p of data.parts) {
330
+ if (p.type === 'text') r.addText(p.content ?? p.text)
331
+ if (p.type === 'code') r.addCode(p.language ?? '', p.content ?? p.code)
332
+ if (p.type === 'table') r.addTable(p.table, p.title)
333
+ if (p.type === 'image') r.addImage(p.url ?? p.image)
334
+ if (p.type === 'grid') r.addGrid(p.items ?? p.images)
335
+ if (p.type === 'videoGrid') r.addVideoGrid(p.items ?? p.videos)
336
+ if (p.type === 'imagine') r.addImagine(p)
337
+ if (p.type === 'sources') r.addSource(p.sources)
338
+ if (p.type === 'divider') r.addDivider()
339
+ if (p.type === 'spacer') r.addSpacer(p.spacing)
340
+ if (p.type === 'tip') r.addTip(p.content ?? p.text)
341
+ if (p.type === 'meta') r.addMetadata(p.content ?? p.text)
342
+ if (p.type === 'foa') r.addFOAText(p.content ?? p.text)
343
+ if (p.type === 'html') r.addHtml(p.html, p.trustedSources)
344
+ if (p.type === 'richHtml') r.addRichHtml(p.tabs ?? p.html, p.title)
345
+ if (p.type === 'copy') r.addCopyAction(p.text, p.label, p.alignment)
346
+ if (p.type === 'social') r.addSocialProfile(p)
347
+ if (p.type === 'imagine') r.addImagine(p)
348
+ if (p.type === 'rawSections') r.addRawSections(p.sections, p.submessages)
349
+ }
350
+ return r.send(jid, opts)
351
+ }
352
+ if (data.texts) data.texts.forEach(t => r.addText(t))
353
+ else if (data.text) r.addText(data.text)
354
+ if (data.codes) data.codes.forEach(c => r.addCode(c.language ?? data.language ?? '', c.code ?? c.content))
355
+ else if (data.code) { const c = typeof data.code === 'string' ? data.code : (data.code.code ?? data.code.content); const lang = typeof data.code === 'string' ? (data.language ?? '') : (data.code.language ?? data.language ?? ''); r.addCode(lang, c) }
356
+ if (data.table) r.addTable(data.table, data.tableTitle)
357
+ else if (data.headers && data.rows) r.addTable([data.headers, ...data.rows], data.tableTitle)
358
+ if (data.images) data.images.forEach(img => r.addImage(img.url ?? img))
359
+ if (data.image) r.addImage(data.image.url ?? data.image)
360
+ if (data.grid) r.addGrid(data.grid)
361
+ if (data.videoGrid) r.addVideoGrid(data.videoGrid)
362
+ if (data.reels) r.addReels(data.reels)
363
+ if (data.imagine) r.addImagine(typeof data.imagine === 'object' ? data.imagine : { url: data.imagine })
364
+ if (data.sources) r.addSource(data.sources)
365
+ if (data.richMapSources) r.addSources(data.richMapSources)
366
+ else if (data.botSources) r.addSources(data.botSources)
367
+ if (data.richMap) r.addRichMap(data.richMap)
368
+ if (data.dynamic) r.addDynamic(data.dynamic)
369
+ if (data.suggestPrompts) r.addSuggestPrompts(Array.isArray(data.suggestPrompts) ? data.suggestPrompts : data.suggestPrompts.suggestions, data.suggestPrompts)
370
+ if (data.pttTranscript) r.addPttTranscript(data.pttTranscript)
371
+ if (data.instagramProfile) r.addInstagramProfile(data.instagramProfile)
372
+ if (data.latex) { if (data.latexText) r.addText(data.latexText); (Array.isArray(data.latex) ? data.latex : [data.latex]).forEach(l => r.addLatex({ expression: l.expression ?? l.latexExpression ?? '', imageUrl: l.url ?? l.imageUrl ?? '', width: l.width ?? 400, height: l.height ?? 200 })) }
373
+ if (data.html) r.addHtml(data.html, data.trustedSources)
374
+ if (data.richHtml) r.addRichHtml(data.richHtml, data.richHtmlTitle)
375
+ if (data.copyText) r.addCopyAction(data.copyText, data.copyLabel, data.copyAlignment)
376
+ if (data.social) r.addSocialProfile(data.social)
377
+ if (data.suggest) r.addSuggest(data.suggest)
378
+ if (data.tip) r.addTip(data.tip)
379
+ if (data.metadata ?? data.meta) r.addMetadata(data.metadata ?? data.meta)
380
+ if (data.footer) r.addFOAText(data.footer)
381
+ if (data.rawSections) r.addRawSections(data.rawSections, data.rawSubmessages)
382
+ return r.send(jid, opts)
383
+ }
384
+
385
+ const stripDevice = jid => String(jid || '').replace(/:\d+$/, '')
386
+
387
+ const collectMetaAIBotJids = async (sock, jid, botUser) => {
388
+ const candidates = new Set([botUser])
389
+ if (!isJidGroup(jid)) return candidates
390
+ const botNumber = jidNormalizedUser(botUser).split('@')[0]
391
+ try {
392
+ const meta = await sock.groupMetadata(jid)
393
+ for (const p of meta.participants || []) {
394
+ const idNum = jidNormalizedUser(p.id).split('@')[0]
395
+ const pnNum = p.phoneNumber ? jidNormalizedUser(p.phoneNumber).split('@')[0] : undefined
396
+ if (pnNum === botNumber || idNum === botNumber || isJidMetaAI(p.id)) candidates.add(p.id)
397
+ }
398
+ } catch { }
399
+ return candidates
400
+ }
401
+
402
+ const isMetaAIResponse = (msg, chatJid, promptId, botJids) => {
403
+ if (!msg?.key || msg.key.fromMe || msg.key.remoteJid !== chatJid) return false
404
+ const sender = stripDevice(msg.key.participant || msg.key.remoteJid)
405
+ if (isJidMetaAI(sender)) return true
406
+ for (const b of botJids) if (jidNormalizedUser(stripDevice(b)) === jidNormalizedUser(sender)) return true
407
+ if (promptId) { const inner = msg.message ? Object.values(msg.message)[0] : undefined; const stanzaId = inner?.contextInfo?.stanzaId || msg.message?.contextInfo?.stanzaId; if (stanzaId === promptId) return true }
408
+ return false
409
+ }
410
+
411
+ const q = quoted => quoted ? { quoted } : {}
412
+
413
+ // ─── attachAIRich ─────────────────────────────────────────────────────────────
414
+ // Attaches all AIRich, meta, bot, and relay methods to result, then returns the
415
+ // fuzzy-proxy-wrapped result. This is the only export nexus-handler.js needs.
416
+ export function attachAIRich(result, config) {
417
+ const sock = result
418
+
419
+ const relayRichMessage = async (jid, fullMsg, options = {}) => {
420
+ const relayOpts = { messageId: fullMsg.key.id, useCachedGroupMetadata: options.useCachedGroupMetadata, statusJidList: options.statusJidList, ...(options.relayOptions || {}) }
421
+ await sock.relayMessage(jid, fullMsg.message, relayOpts)
422
+ const hasRich = !!fullMsg.message?.botForwardedMessage?.message?.richResponseMessage || !!fullMsg.message?.interactiveMessage?.bloksWidget
423
+ if (options.renderRichResponse !== false && (hasRich || options.forceRichEdit)) {
424
+ const editContent = proto.Message.fromObject({ botForwardedMessage: { message: { protocolMessage: { key: { remoteJid: jid, fromMe: true, id: fullMsg.key.id }, type: proto.Message.ProtocolMessage.Type.MESSAGE_EDIT, editedMessage: fullMsg.message } } } })
425
+ const editMsg = generateWAMessageFromContent(jid, editContent, { userJid: sock.user?.id, messageId: generateMessageIDV2(sock.user?.id || jid) })
426
+ await sock.relayMessage(jid, editMsg.message, { ...relayOpts, messageId: editMsg.key.id })
427
+ }
428
+ return fullMsg
429
+ }
430
+
431
+ result.relayRichMessage = relayRichMessage
432
+ result.airich = opts => new AIRich(result, opts)
433
+
434
+ result.captureAiRich = msg => {
435
+ const rich = msg?.botForwardedMessage?.message?.richResponseMessage ?? msg?.richResponseMessage
436
+ if (!rich?.unifiedResponse?.data) return null
437
+ return { submessages: rich.submessages ?? [], sections: JSON.parse(Buffer.from(rich.unifiedResponse.data, 'base64').toString()), contextInfo: rich.contextInfo ?? {}, messageType: rich.messageType ?? 1 }
438
+ }
439
+
440
+ result.relayAiRich = (jid, captured, opts = {}) => {
441
+ const r = new AIRich(result)
442
+ if (captured.sections?.response_id) r.setResponseId(captured.sections.response_id)
443
+ return r.loadFrom({ botForwardedMessage: { message: { richResponseMessage: { submessages: captured.submessages, unifiedResponse: { data: Buffer.from(JSON.stringify(captured.sections)) }, contextInfo: captured.contextInfo, messageType: captured.messageType } } } }).send(jid, opts)
444
+ }
445
+
446
+ result.sendRichFromObject = (jid, data, opts = {}) => aiRichFromObject(result, jid, data, opts)
447
+ result.sendRichMessage = (jid, data, quoted, opts = {}) => result.sendMessage(jid, { aiRich: data }, { ...q(quoted), ...opts })
448
+ result.sendCodeBlock = (jid, code, quoted, opts = {}) => result.sendMessage(jid, { aiRich: { code, language: opts.language ?? 'javascript', title: opts.title } }, q(quoted))
449
+ result.sendCodeBlockV2 = (jid, code, quoted, opts = {}) => result.sendMessage(jid, { aiRich: { text: opts.text, code, language: opts.language ?? '', title: opts.title } }, q(quoted))
450
+ result.sendTable = (jid, title, headers, rows, quoted, opts = {}) => result.sendMessage(jid, { aiRich: { title, table: [headers, ...rows] } }, q(quoted))
451
+ result.sendTableV2 = (jid, tableArray, quoted, opts = {}) => result.sendMessage(jid, { aiRich: { text: opts.text, title: opts.headerText, table: tableArray } }, q(quoted))
452
+ result.sendList = (jid, title, items, quoted, opts = {}) => result.sendMessage(jid, { aiRich: { title, table: [['Key', 'Value'], ...items] } }, q(quoted))
453
+ result.sendLink = (jid, text, links, quoted, opts = {}) => result.sendMessage(jid, { aiRich: { title: opts.headerText, text, sources: links.map(url => ({ url, title: url, subtitle: new URL(url).hostname })) } }, q(quoted))
454
+ result.sendLinkV2 = (jid, text, links, quoted, opts = {}) => result.sendMessage(jid, { aiRich: { title: opts.headerText, text, sources: links } }, q(quoted))
455
+ result.sendLatex = (jid, expressions, quoted, opts = {}) => result.sendMessage(jid, { aiRich: { text: opts.text, title: opts.headerText, latex: expressions } }, q(quoted))
456
+ result.sendRichTable = async (jid, title, headers, rows, quoted, opts = {}) => { const { message, messageId } = generateTableContent(title, headers, rows, quoted, opts); return relayRichMessage(jid, generateWAMessageFromContent(jid, message, { userJid: sock.user?.id, messageId: opts.messageId || messageId }), opts) }
457
+ result.sendRichList = async (jid, title, items, quoted, opts = {}) => { const { message, messageId } = generateListContent(title, items, quoted, opts); return relayRichMessage(jid, generateWAMessageFromContent(jid, message, { userJid: sock.user?.id, messageId: opts.messageId || messageId }), opts) }
458
+ result.sendRichCode = async (jid, code, quoted, opts = {}) => { const { message, messageId } = generateCodeBlockContent(code, quoted, opts); return relayRichMessage(jid, generateWAMessageFromContent(jid, message, { userJid: sock.user?.id, messageId: opts.messageId || messageId }), opts) }
459
+ result.sendRichLatex = async (jid, quoted, opts = {}) => { const { message, messageId } = generateLatexContent(quoted, { expressions: [], ...opts }); return relayRichMessage(jid, generateWAMessageFromContent(jid, message, { userJid: sock.user?.id, messageId: opts.messageId || messageId }), opts) }
460
+ result.sendRichLatexImage = async (jid, opts = {}) => {
461
+ const { text, expressions = [], headerText, footer } = opts
462
+ const rendered = expressions.map(expr => { const latex = expr.latexExpression || expr.expression || expr; const url = `https://latex.codecogs.com/png.latex?${encodeURIComponent(latex).replace(/'/g, '%27')}`; return { latexExpression: latex, url, width: expr.width || 400, height: expr.height || 100 } })
463
+ const { message, messageId } = generateLatexContent(null, { text, expressions: rendered, headerText, footer })
464
+ return relayRichMessage(jid, generateWAMessageFromContent(jid, message, { userJid: sock.user?.id, messageId: opts.messageId || messageId }), opts)
465
+ }
466
+ result.captureAndResendRichResponse = async (jid, metaAiMsg, quoted, opts = {}) => {
467
+ const captured = captureUnifiedResponse(metaAiMsg)
468
+ if (!captured) throw new Error('captureAndResendRichResponse: no unifiedResponse data in message')
469
+ const { message, messageId } = generateUnifiedResponseContent(quoted, captured)
470
+ return relayRichMessage(jid, generateWAMessageFromContent(jid, message, { userJid: sock.user?.id, messageId: opts.messageId || messageId }), opts)
471
+ }
472
+ result.sendBotPlanning = async (jid, content = {}, opts = {}) => {
473
+ const { text = 'Execution plan', steps = [], queryPlan = true, botJid = META_AI_BOT_JID } = content
474
+ const submessages = [{ messageType: proto.AIRichResponseSubMessageType.AI_RICH_RESPONSE_TEXT, messageText: text }, ...steps.map(s => ({ messageType: proto.AIRichResponseSubMessageType.AI_RICH_RESPONSE_TEXT, messageText: typeof s === 'string' ? s : `${s.title || s.name || 'Step'}${s.body ? `: ${s.body}` : ''}` }))]
475
+ const wrapped = buildBotRichResponse({ botJid, submessages, capabilities: [BotCapabilityType.AGENTIC_PLANNING, ...(queryPlan ? [BotCapabilityType.QUERY_PLAN] : [])] })
476
+ const full = await generateWAMessageFromContent(jid, wrapped, { userJid: sock.user?.id, messageId: opts.messageId || generateMessageIDV2(sock.user?.id) })
477
+ return relayRichMessage(jid, full, opts)
478
+ }
479
+ result.sendBotPlan = async (jid, content = {}, opts = {}) => {
480
+ const { text = 'Execution plan', title = 'Execution plan', steps = [], estimatedCompletionTime, botJid = META_AI_BOT_JID } = content
481
+ if (!Array.isArray(steps) || steps.length === 0) throw new Error('sendBotPlan requires at least one step')
482
+ const statusEnum = proto.BotProgressIndicatorMetadata.BotPlanningStepMetadata.PlanningStepStatus
483
+ const normalizedSteps = steps.map((s, i) => ({ statusTitle: String(s.title || s.name || `Step ${i + 1}`), statusBody: String(s.body || s.description || ''), status: s.status === 'executing' ? statusEnum.EXECUTING : (s.status === 'finished' || s.status === 'completed') ? statusEnum.FINISHED : statusEnum.PLANNED, isReasoning: s.isReasoning === true, isEnhancedSearch: s.isEnhancedSearch === true }))
484
+ const wrapped = { messageContextInfo: { botMetadata: { capabilityMetadata: { capabilities: [proto.BotCapabilityMetadata.BotCapabilityType.AGENTIC_PLANNING, proto.BotCapabilityMetadata.BotCapabilityType.QUERY_PLAN] }, progressIndicatorMetadata: { progressDescription: title, stepsMetadata: normalizedSteps, ...(estimatedCompletionTime ? { estimatedCompletionTime } : {}) } } }, botForwardedMessage: { message: { richResponseMessage: { messageType: 1, submessages: [{ messageType: 2, messageText: text }], contextInfo: { isForwarded: true, forwardingScore: 1, forwardedAiBotMessageInfo: { botJid }, forwardOrigin: 4 } } } } }
485
+ const full = await generateWAMessageFromContent(jid, wrapped, { userJid: sock.user?.id, messageId: opts.messageId || generateMessageIDV2(sock.user?.id) })
486
+ await sock.relayMessage(jid, full.message, { messageId: full.key.id })
487
+ return full
488
+ }
489
+ result.replayPlanning = (jid, steps, finalContent, opts) => replayPlanning(result, jid, steps, finalContent, opts)
490
+ result.replayPlanningOnly = (jid, steps, opts) => replayPlanningOnly(result, jid, steps, opts)
491
+ result.metaTyping = (jid, opts) => metaTyping(result, jid, opts)
492
+ result.sendMetaComposited = (jid, content, opts) => sendMetaComposited(result, jid, content, opts)
493
+ result.buildReasoningSteps = buildReasoningSteps
494
+ result.buildSearchSteps = buildSearchSteps
495
+ result.mixedSteps = mixedSteps
496
+ result.buildSteps = buildSteps
497
+ result.PlanningStepStatus = PlanningStepStatus
498
+ result.sendMetaAI = async (a, b, c = {}) => {
499
+ let text, opts = c, yourJid
500
+ if (typeof b === 'string') { text = b; yourJid = a } else { text = a; opts = b || {}; yourJid = opts.yourJid || '' }
501
+ const jid = opts.jid || META_AI_BOT_JID
502
+ const threadId = opts.threadId || generateMessageIDV2(yourJid)
503
+ const now = Date.now()
504
+ const senderKeyHash = opts.senderKeyHash || randomBytes(8).toString('base64')
505
+ const message = {
506
+ extendedTextMessage: proto.Message.ExtendedTextMessage.fromObject({ text, previewType: 'NONE', contextInfo: proto.ContextInfo.fromObject({ botMessageSharingInfo: { botEntryPointOrigin: 'FAVICON', forwardScore: 0 } }), inviteLinkGroupTypeV2: 'DEFAULT' }),
507
+ messageContextInfo: proto.MessageContextInfo.fromObject({ deviceListMetadata: { senderKeyHash, senderTimestamp: opts.senderTimestamp || String(Math.floor(now / 1000)) }, deviceListMetadataVersion: 2, messageSecret: opts.messageSecret || randomBytes(32), botMetadata: { botModeSelectionMetadata: { overrideMode: [0] }, botThreadInfo: { serverInfo: { title: text.substring(0, 50) }, clientInfo: { type: 'DEFAULT' } }, botRenderingConfigMetadata: { bloksVersioningId: '1eb86e6f4117d052e6bab62fe758a2e2af43747b85c5c1a886c8262bac462ea4', pixelDensity: 2.625 }, ...(opts.conversationContext?.length ? { aiConversationContext: opts.conversationContext } : {}) }, threadId: [{ threadType: 'AI_THREAD', threadKey: { remoteJid: '0002@s.whatsapp.net', fromMe: true, id: threadId } }] })
508
+ }
509
+ const msgId = generateMessageIDV2(yourJid)
510
+ await sock.relayMessage(jid, message, { messageId: msgId, ...(opts.quoted ? { quoted: opts.quoted } : {}) })
511
+ return msgId
512
+ }
513
+ result.aiPrompt = async (jid, prompt, options = {}) => {
514
+ const { timeout = 60_000, onPartial, botUser = META_AI_BOT_JID, mentions = [], ...sendOpts } = options
515
+ if (!jid || typeof prompt !== 'string' || !prompt.trim()) throw new Error('aiPrompt requires a chat JID and a non-empty prompt')
516
+ const botJids = await collectMetaAIBotJids(result, jid, botUser)
517
+ const allMentions = [...new Set([...mentions, ...botJids])]
518
+ return new Promise((resolve, reject) => {
519
+ let promptId, settled = false
520
+ const cleanup = () => { clearTimeout(timer); sock.ev.off('messages.upsert', onUpsert); if (onPartial) sock.ev.off('messages.update', onUpdate) }
521
+ const settle = (fn, val) => { if (settled) return; settled = true; cleanup(); fn(val) }
522
+ const timer = setTimeout(() => settle(reject, new Error(`aiPrompt timed out after ${timeout}ms in ${jid}`)), timeout)
523
+ const onUpsert = ({ messages }) => { for (const msg of messages) if (isMetaAIResponse(msg, jid, promptId, botJids)) { settle(resolve, msg); return } }
524
+ const onUpdate = ({ updates }) => { if (!onPartial) return; for (const u of updates) { if (!u.key || u.key.fromMe || u.key.remoteJid !== jid) continue; const sender = jidNormalizedUser(stripDevice(u.key.participant || u.key.remoteJid)); const isBot = isJidMetaAI(sender) || [...botJids].some(b => jidNormalizedUser(stripDevice(b)) === sender); if (isBot && u.message) onPartial(u.message, u.key) } }
525
+ sock.ev.on('messages.upsert', onUpsert)
526
+ if (onPartial) sock.ev.on('messages.update', onUpdate)
527
+ ; (async () => {
528
+ try {
529
+ const content = { text: prompt, ...(isJidGroup(jid) ? { mentions: allMentions } : {}), ...sendOpts }
530
+ const full = await generateWAMessage(jid, content, { userJid: sock.user?.id, messageId: sendOpts.messageId || generateMessageIDV2(sock.user?.id) })
531
+ await sock.relayMessage(jid, full.message, { messageId: full.key.id })
532
+ if (config.emitOwnEvents) process.nextTick(() => sock.ev.emit('messages.upsert', { messages: [full], type: 'append' }))
533
+ promptId = full.key.id
534
+ } catch (err) { settle(reject, err) }
535
+ })()
536
+ })
537
+ }
538
+ result.sendAsMimic = async (jid, content, mimicJid, options = {}) => {
539
+ if (!options.admin && !options.mimicPermission) throw new Error('sendAsMimic requires admin: true')
540
+ const msgId = generateMessageIDV2(sock.user?.id)
541
+ const full = await generateWAMessageFromContent(jid, content, { userJid: sock.user?.id, ...options })
542
+ full.key = { remoteJid: jid, fromMe: false, id: msgId, participant: mimicJid }
543
+ await sock.relayMessage(jid, full.message, { messageId: msgId, ...(options.additionalAttributes ? { additionalAttributes: options.additionalAttributes } : {}) })
544
+ return full
545
+ }
546
+ result.sendStatus = async (content, options = {}) => {
547
+ const provided = options.statusJidList || content?.statusJidList || sock.statusJidList || []
548
+ if (!provided.length) throw new Error('sendStatus requires statusJidList')
549
+ return result.sendMessage(STATUS_JID, { ...content, status: true }, { ...options, broadcast: true, statusJidList: provided })
550
+ }
551
+ result.sendBotToolResult = async (jid, content = {}, opts = {}) => {
552
+ const { text = 'Tool result', toolCallId = `tool-${Date.now()}`, resolutionData, resolutionDataSerialized, botJid = META_AI_BOT_JID } = content
553
+ const wrapped = buildBotRichResponse({ text, botJid, capabilities: [BotCapabilityType.AGENTIC_PLANNING, BotCapabilityType.QUERY_PLAN], botMetadata: { resolvedToolCallMetadata: { toolCallId, resolutionDataSerialized: resolutionDataSerialized ?? JSON.stringify(resolutionData ?? {}) } } })
554
+ const full = await generateWAMessageFromContent(jid, wrapped, { userJid: sock.user?.id, messageId: opts.messageId || generateMessageIDV2(sock.user?.id) })
555
+ return relayRichMessage(jid, full, opts)
556
+ }
557
+ result.sendWhatsAppFlow = async (jid, flow, options = {}) => {
558
+ const { text, footer, image, caption, ...flowOpts } = flow || {}
559
+ const btn = makeWhatsAppFlowButton(flowOpts)
560
+ const content = image ? { image, caption: caption || text || flowOpts.cta } : { text: text || flowOpts.cta || '' }
561
+ return result.sendMessage(jid, { ...content, ...(footer ? { footer } : {}), nativeFlow: [btn] }, options)
562
+ }
563
+ result.sendRichMap = (jid, mapOpts, options = {}) => { const r = new AIRich(result); r.addRichMap(mapOpts); return r.send(jid, options) }
564
+ result.sendDynamic = (jid, dynOpts, options = {}) => { const r = new AIRich(result); r.addDynamic(dynOpts); return r.send(jid, options) }
565
+ result.sendBotPromptSuggestions = (jid, content = {}, options = {}) => { const { text, suggestions = [] } = content; const r = new AIRich(result); if (text) r.addText(text); r.addSuggestPrompts(suggestions, content); return r.send(jid, options) }
566
+ result.sendBotSources = (jid, content = {}, options = {}) => { const { text, sources = [] } = content; const r = new AIRich(result); if (text) r.addText(text); r.addSources(sources); return r.send(jid, options) }
567
+ result.sendBotPttTranscript = (jid, content = {}, options = {}) => { const { text, transcript } = content; const r = new AIRich(result); if (text) r.addText(text); r.addPttTranscript(transcript); return r.send(jid, options) }
568
+ result.sendInstagramProfile = (jid, data, options = {}) => { const r = new AIRich(result); r.addInstagramProfile(data); return r.send(jid, options) }
569
+ result.sendRichGeneration = async (jid, opts = {}) => { const { message, responseId, itemId } = prepareRichGenerationMessage(opts); const full = await generateWAMessageFromContent(jid, message, { userJid: sock.user?.id, messageId: opts.messageId || generateMessageIDV2(sock.user?.id) }); await sock.relayMessage(jid, full.message, { messageId: full.key.id }); return { msg: full, responseId, itemId } }
570
+ result.sendUnifiedResponse = (jid, sections, submessages = [], options = {}) => { const r = new AIRich(result); r.addRawSections(sections, submessages); return r.send(jid, options) }
571
+ result.sendRichButtonGrid = async (jid, grid, options = {}) => {
572
+ const { text, footer, cards = [] } = grid || {}
573
+ if (!Array.isArray(cards) || cards.length === 0) throw new Error('sendRichButtonGrid expects at least one card')
574
+ return result.sendMessage(jid, { ...(text ? { text } : {}), ...(footer ? { footer } : {}), cards }, options)
575
+ }
576
+
577
+ const normalize = s => s.toLowerCase().replace(/[^a-z]/g, '')
578
+ const knownKeys = Object.keys(result).filter(k => typeof result[k] === 'function')
579
+ const normalizedMap = new Map(knownKeys.map(k => [normalize(k), k]))
580
+
581
+ return new Proxy(result, {
582
+ get(target, prop, receiver) {
583
+ if (prop in target) return Reflect.get(target, prop, receiver)
584
+ if (typeof prop !== 'string') return undefined
585
+ const match = normalizedMap.get(normalize(prop))
586
+ if (match) { target.logger?.warn?.(`[NexusHandler] Unknown method "${prop}" — did you mean "${match}"? Calling it.`); return target[match] }
587
+ return undefined
588
+ }
589
+ })
590
+ }