@zervonex/baileys 3.0.7 → 3.0.9

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.
@@ -3,10 +3,10 @@ import { proto } from '../../WAProto/index.js'
3
3
  import {
4
4
  generateMessageIDV2, generateVerificationMetadata, waitAllPromises, extractIE, tokenizeCode, generateWAMessageFromContent, generateWAMessage,
5
5
  makeWhatsAppFlowButton, metaTyping, sendMetaComposited, PlanningStepStatus, buildSteps, replayPlanning, replayPlanningOnly, buildReasoningSteps, buildSearchSteps,
6
- mixedSteps, buildBotRichResponse, BotCapabilityType, generateTableContent, generateListContent, generateCodeBlockContent,
6
+ mixedSteps, buildBotRichResponse, BotCapabilityType, RichSubMessageType, generateTableContent, generateListContent, generateCodeBlockContent,
7
7
  generateLatexContent, captureUnifiedResponse, generateUnifiedResponseContent
8
8
  } from '../Utils/index.js'
9
- import { isJidGroup, isJidMetaAI, jidNormalizedUser } from '../WABinary/index.js'
9
+ import { isJidGroup, isJidMetaAI, jidNormalizedUser, getBizBinaryNode } from '../WABinary/index.js'
10
10
  import { BOT_RENDERING_CONFIG_METADATA } from '../Defaults/index.js'
11
11
  import { randomBytes } from 'crypto'
12
12
 
@@ -60,6 +60,18 @@ const mkProduct = d => ({ __typename: 'GenAIProductItemCardPrimitive', title: d.
60
60
  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 })
61
61
  const mkMap = ({ latitude, longitude, name = '', address = '' }) => ({ __typename: 'GenAIMapPrimitive', latitude, longitude, name, address })
62
62
 
63
+ const _mp4Duration = (buf, silent = true) => {
64
+ try { if (!Buffer.isBuffer(buf) || buf.length < 8) { if (silent) return 0; throw new Error('Invalid buffer') }; let o = 0; while (o < buf.length - 8) { const sz = buf.readUInt32BE(o); if (sz < 8 || o + sz > buf.length) { if (silent) return 0; throw new Error('Bad atom') }; if (buf.toString('ascii', o + 4, o + 8) === 'moov') { let m = o + 8; const me = o + sz; while (m < me - 8) { const cs = buf.readUInt32BE(m); if (cs < 8 || m + cs > me) break; if (buf.toString('ascii', m + 4, m + 8) === 'mvhd') { const v = buf.readUInt8(m + 8); if (v === 0) { const ts = buf.readUInt32BE(m + 20); const dur = buf.readUInt32BE(m + 24); return ts ? dur / ts : 0 }; if (v === 1) { const ts = buf.readUInt32BE(m + 32); const dur = Number(buf.readBigUInt64BE(m + 36)); return ts ? dur / ts : 0 } }; m += cs } }; o += sz }; return 0 }
65
+ catch { return 0 }
66
+ }
67
+
68
+ const _mp4Preview = async (buf, time, silent = true) => {
69
+ const fail = e => { if (!silent) throw e; return null }; if (!Buffer.isBuffer(buf) || !buf.length) return fail(new Error('Empty buffer'))
70
+ try {
71
+ const { Readable, PassThrough } = await import('stream'); const { default: ffmpeg } = await import('fluent-ffmpeg'); const src = new Readable({ read() { } }); src.push(buf); src.push(null); const out = new PassThrough(); const chunks = []; out.on('data', c => chunks.push(c)); const t = time ?? Math.max(0.5, Math.min(_mp4Duration(buf) * 0.3, 10)); await new Promise((res, rej) => { out.on('end', res); out.on('error', rej); ffmpeg(src).outputOptions([`-ss ${t}`, '-vframes 1', '-vcodec png', '-f image2pipe']).on('error', rej).pipe(out, { end: true }) }); const b = Buffer.concat(chunks); return b.length ? b.toString('base64') : fail(new Error('Empty output'))
72
+ } catch (e) { return fail(e) }
73
+ }
74
+
63
75
  const mkRichMap = ({ centerLatitude, centerLongitude, latitudeDelta, longitudeDelta, annotations = [], showInfoList = false }) => {
64
76
  if (centerLatitude == null || centerLongitude == null) throw new TypeError('addRichMap requires centerLatitude and centerLongitude')
65
77
  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 ?? '' })) } }
@@ -177,8 +189,8 @@ export const prepareRichGenerationMessage = ({ text = '', mediaType = 'video', u
177
189
  const sections = []
178
190
  if (text) sections.push({ view_model: { primitive: { text: String(text), inline_entities: [], __typename: 'GenAIMarkdownTextUXPrimitive' }, __typename: 'GenAISingleLayoutViewModel' } })
179
191
  sections.push({ view_model: { primitive: buildImaginePrimitive({ mediaType, url, thumbnail, mimeType, fileLength, duration, status, estimatedMs }), __typename: 'GenAISingleLayoutViewModel', id: itemId } })
180
- const submessages = text ? [{ messageType: 2, messageText: String(text) }] : []
181
- 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 } })
192
+ const submessages = text ? [{ messageType: RichSubMessageType.AI_RICH_RESPONSE_TEXT, messageText: String(text) }] : []
193
+ const richResponseMessage = proto.AIRichResponseMessage.create({ submessages, messageType: proto.AIRichResponseMessageType.AI_RICH_RESPONSE_TYPE_STANDARD, unifiedResponse: { data: Buffer.from(JSON.stringify({ response_id: responseId, sections })) }, contextInfo: { isForwarded: true, forwardingScore: 1, forwardedAiBotMessageInfo: { botJid: DEFAULT_BOT_JID }, forwardOrigin: 4 } })
182
194
  const message = wrapToBotForwardedMessage(richResponseMessage, verificationMetadata)
183
195
  message.messageContextInfo.botMetadata.botResponseId = responseId
184
196
  return { message, responseId, itemId }
@@ -189,6 +201,7 @@ export class AIRich {
189
201
  #responseId = null; #dynamic = true; #signedVerificationMetadata = null
190
202
  #contextInfoExtra = {}; #embeddedScreens = []; #suggestedPrompts = null
191
203
  #botSources = null; #pttTranscript = null
204
+ #footerSections = []; #responseMeta = {}; #botMetadataExtra = {}
192
205
 
193
206
  constructor(client, opts = {}) {
194
207
  if (!client) throw new Error('Socket client is required')
@@ -210,17 +223,28 @@ export class AIRich {
210
223
  setBotJid(jid) { this.#botJid = jid; return this }
211
224
  setResponseId(id) { this.#responseId = id; this.#dynamic = false; return this }
212
225
  setContextInfo(ci) { this.#contextInfoExtra = ci; return this }
226
+ setResponseMeta(meta) { if (!meta || typeof meta !== 'object') throw new TypeError('responseMeta must be a plain object'); this.#responseMeta = { ...this.#responseMeta, ...meta }; return this }
227
+ setBotMetadata(extra) { if (!extra || typeof extra !== 'object') throw new TypeError('botMetadata must be a plain object'); this.#botMetadataExtra = { ...this.#botMetadataExtra, ...extra }; return this }
228
+ addFooterSection(section) { if (!section || typeof section !== 'object') throw new TypeError('footer section must be a plain object'); this.#footerSections.push(section); return this }
229
+ clearFooterSections() { this.#footerSections = []; return this }
213
230
 
214
231
  addText(text, opts = {}) { const { text: t, inline_entities } = extractIE(text); return this.#add(single(mkMarkdown(t, inline_entities)), null, opts) }
215
- 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) }
232
+ addCode(language, content, opts = {}) { const { codeBlocks, unified_codeBlock } = tokenizeCode(content, language); const submessage = { messageType: RichSubMessageType.AI_RICH_RESPONSE_CODE, codeMetadata: { codeLanguage: language, codeBlocks } }; return this.#add(single(mkCode(content, language, unified_codeBlock)), submessage, opts) }
216
233
  addTable(rows, title = '', opts = {}) { return this.#add(single(mkTable(rows, title)), null, opts) }
217
234
  addImage(input, opts = {}) { const resolved = this.#uploadMedia(input, 'image').then(url => single(mkImage(url))); return this.#add(resolved, null, opts) }
218
- addVideo(url, opts = {}) { const resolved = this.#uploadMedia(url, 'video').then(u => single(mkVideo(u))); return this.#add(resolved, null, opts) }
235
+ addVideo(url, { autoFill = false, mimeType = 'video/mp4', ...opts } = {}) {
236
+ const resolved = this.#uploadMedia(url, 'video').then(async finalUrl => { if (!autoFill) return single(mkVideo(finalUrl)); const buf = await fetch(finalUrl).then(r => r.arrayBuffer()).then(b => Buffer.from(b)).catch(() => null); const duration = buf ? _mp4Duration(buf) : 0; const thumbnail = buf ? await _mp4Preview(buf, null, true) : null; return single({ __typename: 'GenAIImaginePrimitive', imagine_type: 'ANIMATE', status: { status: 'READY' }, media: { url: finalUrl, mime_type: mimeType, file_length: buf?.length ?? 0, duration }, thumbnail: thumbnail ? { raw_media: thumbnail } : undefined }) }); return this.#add(resolved, null, opts)
237
+ }
219
238
  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) }
220
239
 
221
240
  addVideoGrid(videos, opts = {}) {
222
241
  if (!Array.isArray(videos) || videos.length < 2) throw new Error('addVideoGrid requires at least 2 videos')
223
- 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' } } })
242
+ const prims = videos.map((v, i) => {
243
+ const item = typeof v === 'string' ? {} : v
244
+ const url = typeof v === 'string' ? v : (v?.url ?? v?.videoUrl ?? '')
245
+ if (!url) throw new Error(`VideoGrid item ${i}: video requires a URL`)
246
+ 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' } }
247
+ })
224
248
  return this.#add(grid(prims), null, opts)
225
249
  }
226
250
 
@@ -282,15 +306,15 @@ export class AIRich {
282
306
  addVerticalAlbum(items, opts = {}) {
283
307
  if (!Array.isArray(items) || items.length < 2) throw new Error('addVerticalAlbum requires at least 2 items')
284
308
  const expiration = String(Date.now() + 30 * 24 * 60 * 60 * 1000)
285
- const primitives = items.map(item => {
309
+ const primitives = items.map((item, i) => {
286
310
  const kind = item?.type || (item?.video ? 'video' : 'image')
287
311
  if (kind === 'video') {
288
- const v = item.video || item
289
- return buildImaginePrimitive({ mediaType: 'video', url: typeof v === 'string' ? v : (v?.url || ''), mimeType: v?.mimeType || 'video/mp4', fileLength: v?.fileLength || 0, duration: v?.duration || 0, status: 'READY' })
312
+ const v = item.video || item.data || item
313
+ return buildImaginePrimitive({ mediaType: 'video', url: typeof v === 'string' ? v : (v?.url || ''), mimeType: v?.mimeType ?? v?.mime_type ?? 'video/mp4', fileLength: v?.fileLength || 0, duration: v?.duration || 0, status: 'READY' })
290
314
  }
291
315
  const img = typeof (item?.image || item) === 'string' ? { url: item?.image || item } : (item?.image || item || {})
292
- const url = img.url || ''
293
- if (!url) throw new Error('Each vertical album image item requires a URL')
316
+ const url = img?.imageHighResUrl ?? img?.highResUrl ?? img?.fullUrl ?? img?.imagePreviewUrl ?? img?.previewUrl ?? img?.url ?? ''
317
+ if (!url) throw new Error(`VerticalAlbum item ${i}: image requires a URL`)
294
318
  return { __typename: 'GenAIImagePrimitive', preview_image: { url, url_fallback: url, mime_type: img.mimeType || 'image/jpeg', expiration_timestamp_ms: expiration, width: img.width || 600, height: img.height || 400 }, full_image: { url, url_fallback: url, mime_type: img.mimeType || 'image/jpeg', expiration_timestamp_ms: expiration, width: img.width || 600, height: img.height || 400 }, dark_mode_preview_image: { url, url_fallback: url, mime_type: img.mimeType || 'image/jpeg', expiration_timestamp_ms: expiration, width: img.width || 600, height: img.height || 400 }, dark_mode_full_image: { url, url_fallback: url, mime_type: img.mimeType || 'image/jpeg', expiration_timestamp_ms: expiration, width: img.width || 600, height: img.height || 400 }, asset_query_status: 'FETCHED', display_mode: 'FULL_WIDTH', full_width: true }
295
319
  })
296
320
  return this.#add({ view_model: { primitives, display_mode: 'FULL_WIDTH', full_width: true, __typename: 'GenAIVStackLayoutViewModel' } }, null, opts)
@@ -302,6 +326,28 @@ export class AIRich {
302
326
  return this.#add(single(mkMeta(`🎤 _${transcript}_`)), null, opts)
303
327
  }
304
328
 
329
+ // ─── Node introspection & mutation ────────────────────────────────────────
330
+ hasId(id) { return typeof id === 'string' && id in this.#idIndex }
331
+ getIds() { return this.#nodes.map(n => n.id) }
332
+ peek(id) { const idx = this.#idIndex[id]; return idx !== undefined ? { ...this.#nodes[idx] } : null }
333
+ assignId(index, newId) {
334
+ if (!Number.isInteger(index) || index < 0 || index >= this.#nodes.length) throw new RangeError(`Node index ${index} is out of range`)
335
+ if (newId in this.#idIndex) throw new Error(`Id "${newId}" already exists`)
336
+ const node = this.#nodes[index]
337
+ delete this.#idIndex[node.id]
338
+ node.id = newId
339
+ this.#idIndex[newId] = index
340
+ return this
341
+ }
342
+ delete(id) {
343
+ const idx = this.#idIndex[id]
344
+ if (idx === undefined) throw new Error(`Node id "${id}" not found`)
345
+ this.#nodes.splice(idx, 1)
346
+ this.#idIndex = {}
347
+ this.#nodes.forEach((n, i) => { this.#idIndex[n.id] = i })
348
+ return this
349
+ }
350
+
305
351
  loadFrom(msg) {
306
352
  const rich = msg?.botForwardedMessage?.message?.richResponseMessage ?? msg?.richResponseMessage
307
353
  if (!rich) throw new Error('Not an AIRich message')
@@ -324,11 +370,12 @@ export class AIRich {
324
370
  const submessages = resolved.map(n => n.submessage).filter(Boolean)
325
371
  const embeddedScreens = await waitAllPromises(this.#embeddedScreens)
326
372
  const responseId = this.#dynamic ? crypto.randomUUID() : (this.#responseId ?? crypto.randomUUID())
327
- const unifiedData = JSON.stringify({ response_id: responseId, sections, ...(embeddedScreens.length ? { embedded_screens: embeddedScreens } : {}) })
373
+ const footerSections = await waitAllPromises(this.#footerSections)
374
+ const unifiedData = JSON.stringify({ response_id: responseId, ...this.#responseMeta, sections, ...(footerSections.length ? { footer_sections: footerSections } : {}), ...(embeddedScreens.length ? { embedded_screens: embeddedScreens } : {}) })
328
375
  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 } : {}) }
329
376
  const verification = this.#signedVerificationMetadata ?? generateVerificationMetadata()
330
377
  const { sources, mediaDetailsMetadataList } = buildAdditionalBotMetadata(submessages)
331
- const richResponseMessage = proto.AIRichResponseMessage.create({ messageType: 1, submessages, unifiedResponse: { data: Buffer.from(unifiedData) }, originalRecipientMetadata: { data: Buffer.from(unifiedData) }, contextInfo })
378
+ const richResponseMessage = proto.AIRichResponseMessage.create({ messageType: proto.AIRichResponseMessageType.AI_RICH_RESPONSE_TYPE_STANDARD, submessages, unifiedResponse: { data: Buffer.from(unifiedData) }, originalRecipientMetadata: { data: Buffer.from(unifiedData) }, contextInfo })
332
379
  const message = wrapToBotForwardedMessage(richResponseMessage, verification)
333
380
  const botMeta = message.messageContextInfo.botMetadata
334
381
  botMeta.botResponseId = responseId
@@ -340,6 +387,7 @@ export class AIRich {
340
387
  if (mediaDetailsMetadataList.length) botMeta.unifiedResponseMutation = { mediaDetailsMetadataList }
341
388
  if (this.#suggestedPrompts) botMeta.suggestedPromptMetadata = this.#suggestedPrompts
342
389
  if (this.#pttTranscript) botMeta.pttPromptMetadata = { transcript: this.#pttTranscript }
390
+ if (Object.keys(this.#botMetadataExtra).length) Object.assign(botMeta, this.#botMetadataExtra)
343
391
  botMeta.capabilities = { richResponseUnifiedResponse: true, richResponseEmbeddedScreens: true, richResponseInlineLinksEnabled: true, richResponseUrBloksEnabled: true, richResponseUrImagine: true, richResponseUrReasoning: true }
344
392
  return await await generateWAMessageFromContent(jid, message, { userJid: this.#client.user?.id, messageId })
345
393
  }
@@ -347,14 +395,11 @@ export class AIRich {
347
395
  async send(jid, opts = {}) {
348
396
  const { additionalNodes = [], ...buildOpts } = opts
349
397
  const msg = await this.build(jid, buildOpts)
398
+ const bizNode = getBizBinaryNode(msg.message)
350
399
  const relayFn = this.#client.relayRichMessage ?? this.#client.relayMessage
351
400
  await relayFn(jid, msg, {
352
401
  ...opts,
353
- additionalNodes: [
354
- { tag: 'bot', attrs: { biz_bot: '1' }, content: undefined },
355
- { tag: 'biz', attrs: {}, content: [{ tag: 'interactive', attrs: { type: 'native_flow', v: '1' }, content: [{ tag: 'native_flow', attrs: { v: '9', name: 'mixed' } }] }] },
356
- ...(opts.additionalNodes ?? [])
357
- ]
402
+ additionalNodes: [...(bizNode ? [bizNode] : []), ...(opts.additionalNodes ?? [])]
358
403
  })
359
404
  return msg
360
405
  }
@@ -370,31 +415,17 @@ export class AIRich {
370
415
  }
371
416
 
372
417
  export function aiRichFromObject(client, jid, data, opts = {}) {
373
- const r = new AIRich(client, { botJid: data.botJid })
374
- if (data.title) r.setTitle(data.title)
418
+ const { _r, ...sendOpts } = opts
419
+ const r = _r ?? new AIRich(client, { botJid: data.botJid })
420
+ const isChild = !!_r
421
+ if (!isChild && (data.title ?? data.disclaimerText)) r.setTitle(data.title ?? data.disclaimerText)
375
422
  if (data.parts) {
376
423
  for (const p of data.parts) {
377
- if (p.type === 'text') r.addText(p.content ?? p.text)
378
- if (p.type === 'code') r.addCode(p.language ?? '', p.content ?? p.code)
379
- if (p.type === 'table') r.addTable(p.table, p.title)
380
- if (p.type === 'image') r.addImage(p.url ?? p.image)
381
- if (p.type === 'grid') r.addGrid(p.items ?? p.images)
382
- if (p.type === 'videoGrid') r.addVideoGrid(p.items ?? p.videos)
383
- if (p.type === 'imagine') r.addImagine(p)
384
- if (p.type === 'sources') r.addSource(p.sources)
385
- if (p.type === 'divider') r.addDivider()
386
- if (p.type === 'spacer') r.addSpacer(p.spacing)
387
- if (p.type === 'tip') r.addTip(p.content ?? p.text)
388
- if (p.type === 'meta') r.addMetadata(p.content ?? p.text)
389
- if (p.type === 'foa') r.addFOAText(p.content ?? p.text)
390
- if (p.type === 'html') r.addHtml(p.html, p.trustedSources)
391
- if (p.type === 'richHtml') r.addRichHtml(p.tabs ?? p.html, p.title)
392
- if (p.type === 'copy') r.addCopyAction(p.text, p.label, p.alignment)
393
- if (p.type === 'social') r.addSocialProfile(p)
394
- if (p.type === 'imagine') r.addImagine(p)
395
- if (p.type === 'rawSections') r.addRawSections(p.sections, p.submessages)
424
+ const code = Array.isArray(p.code) ? p.code.map(c => c.codeContent ?? c.content ?? '').join('\n') : p.code
425
+ const table = Array.isArray(p.table) ? p.table.map(r => Array.isArray(r) ? r : (r.items ?? r.cells ?? [])) : p.table
426
+ aiRichFromObject(client, jid, { ...p, ...(code ? { code } : {}), ...(table ? { table } : {}) }, { ...sendOpts, _r: r })
396
427
  }
397
- return r.send(jid, opts)
428
+ return isChild ? r : r.send(jid, sendOpts)
398
429
  }
399
430
  if (data.texts) data.texts.forEach(t => r.addText(t))
400
431
  else if (data.text) r.addText(data.text)
@@ -426,7 +457,7 @@ export function aiRichFromObject(client, jid, data, opts = {}) {
426
457
  if (data.metadata ?? data.meta) r.addMetadata(data.metadata ?? data.meta)
427
458
  if (data.footer) r.addFOAText(data.footer)
428
459
  if (data.rawSections) r.addRawSections(data.rawSections, data.rawSubmessages)
429
- return r.send(jid, opts)
460
+ return isChild ? r : r.send(jid, sendOpts)
430
461
  }
431
462
 
432
463
  const stripDevice = jid => String(jid || '').replace(/:\d+$/, '')
@@ -484,7 +515,7 @@ export function attachAIRich(result, config) {
484
515
  result.captureAiRich = msg => {
485
516
  const rich = msg?.botForwardedMessage?.message?.richResponseMessage ?? msg?.richResponseMessage
486
517
  if (!rich?.unifiedResponse?.data) return null
487
- return { submessages: rich.submessages ?? [], sections: JSON.parse(Buffer.from(rich.unifiedResponse.data, 'base64').toString()), contextInfo: rich.contextInfo ?? {}, messageType: rich.messageType ?? 1 }
518
+ return { submessages: rich.submessages ?? [], sections: JSON.parse(Buffer.isBuffer(rich.unifiedResponse.data) ? rich.unifiedResponse.data.toString() : Buffer.from(rich.unifiedResponse.data, 'base64').toString()), contextInfo: rich.contextInfo ?? {}, messageType: rich.messageType ?? proto.AIRichResponseMessageType.AI_RICH_RESPONSE_TYPE_STANDARD }
488
519
  }
489
520
 
490
521
  result.relayAiRich = (jid, captured, opts = {}) => {
@@ -524,7 +555,7 @@ export function attachAIRich(result, config) {
524
555
  }
525
556
  result.sendBotPlanning = async (jid, content = {}, opts = {}) => {
526
557
  const { text = 'Execution plan', steps = [], queryPlan = true, botJid = META_AI_BOT_JID } = content
527
- 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}` : ''}` }))]
558
+ const submessages = [{ messageType: RichSubMessageType.AI_RICH_RESPONSE_TEXT, messageText: text }, ...steps.map(s => ({ messageType: RichSubMessageType.AI_RICH_RESPONSE_TEXT, messageText: typeof s === 'string' ? s : `${s.title || s.name || 'Step'}${s.body ? `: ${s.body}` : ''}` }))]
528
559
  const wrapped = buildBotRichResponse({ botJid, submessages, capabilities: [BotCapabilityType.AGENTIC_PLANNING, ...(queryPlan ? [BotCapabilityType.QUERY_PLAN] : [])] })
529
560
  const full = await await generateWAMessageFromContent(jid, wrapped, { userJid: sock.user?.id, messageId: opts.messageId || generateMessageIDV2(sock.user?.id) })
530
561
  return relayRichMessage(jid, full, opts)
@@ -534,7 +565,7 @@ export function attachAIRich(result, config) {
534
565
  if (!Array.isArray(steps) || steps.length === 0) throw new Error('sendBotPlan requires at least one step')
535
566
  const statusEnum = proto.BotProgressIndicatorMetadata.BotPlanningStepMetadata.PlanningStepStatus
536
567
  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 }))
537
- 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 } } } } }
568
+ 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: proto.AIRichResponseMessageType.AI_RICH_RESPONSE_TYPE_STANDARD, submessages: [{ messageType: RichSubMessageType.AI_RICH_RESPONSE_TEXT, messageText: text }], contextInfo: { isForwarded: true, forwardingScore: 1, forwardedAiBotMessageInfo: { botJid }, forwardOrigin: 4 } } } } }
538
569
  const full = await await generateWAMessageFromContent(jid, wrapped, { userJid: sock.user?.id, messageId: opts.messageId || generateMessageIDV2(sock.user?.id) })
539
570
  await sock.relayMessage(jid, full.message, { messageId: full.key.id })
540
571
  return full
@@ -1,6 +1,6 @@
1
1
  import crypto from 'crypto'
2
2
  import { proto } from '../../WAProto/index.js'
3
- import { generateWAMessage, generateWAMessageFromContent, getUrlInfo, prepareWAMessageMedia } from '../Utils/index.js'
3
+ import { generateWAMessage, generateWAMessageFromContent, getUrlInfo, prepareWAMessageMedia, resolveNativeFlowButtons } from '../Utils/index.js'
4
4
 
5
5
  export const getRandomHex = () => '#' + Array.from(crypto.getRandomValues(new Uint8Array(3)), b => b.toString(16).padStart(2, '0')).join('')
6
6
 
@@ -100,7 +100,7 @@ export async function handlePollResult(sock, jid, p) {
100
100
  pollResultSnapshotMessage: proto.Message.PollResultSnapshotMessage.create({
101
101
  name: p.name,
102
102
  pollVotes: (p.pollVotes ?? []).map(v => proto.Message.PollResultSnapshotMessage.PollVote.create({ optionName: v.optionName, optionVoteCount: String(v.optionVoteCount ?? 0) })),
103
- contextInfo: proto.ContextInfo.create({ isForwarded: true, forwardingScore: 1, forwardedNewsletterMessageInfo: proto.ContextInfo.ForwardedNewsletterMessageInfo.create({ newsletterName: p.newsletter?.newsletterName ?? 'Newsletter', newsletterJid: p.newsletter?.newsletterJid ?? '120363399602691477@newsletter', serverMessageId: 1000, contentType: 'UPDATE' }) })
103
+ contextInfo: proto.ContextInfo.create({ isForwarded: true, forwardingScore: 1 })
104
104
  })
105
105
  }, { userJid: sock.user?.id })
106
106
  await sock.relayMessage(jid, msg.message, { messageId: msg.key.id })
@@ -109,6 +109,16 @@ export async function handlePollResult(sock, jid, p) {
109
109
 
110
110
  export const q = quoted => quoted ? { quoted } : {}
111
111
 
112
+ export async function handleNativeFlow(sock, jid, content, opts = {}) {
113
+ const rawButtons = content.nativeFlow
114
+ const resolved = resolveNativeFlowButtons(rawButtons)
115
+ const offerParams = content.offerText ? { limited_time_offer: { text: content.offerText, url: content.offerUrl || '', copy_code: content.offerCode || '', expiration_time: content.offerExpiration ? Math.floor(content.offerExpiration / 1000) : 0 } } : {}
116
+ const messageParams = { ...offerParams }
117
+ if (content.optionText) messageParams.bottom_sheet = { in_thread_buttons_limit: 1, divider_indices: Array.from({ length: resolved.length }, (_, i) => i), list_title: content.optionTitle || content.optionText, button_title: content.optionText }
118
+ if (content.interactiveAsTemplate) return sock.sendMessage(jid, { ...content, nativeFlow: undefined, interactiveButtons: undefined, templateButtons: resolved.map(b => { const p = JSON.parse(b.buttonParamsJson ?? '{}'); return b.name === 'cta_url' ? { text: p.display_text, url: p.url } : b.name === 'cta_call' ? { text: p.display_text, call: p.phone_number } : { text: p.display_text, id: p.id } }) }, opts)
119
+ return sock.sendMessage(jid, { ...content, nativeFlow: undefined, interactiveButtons: resolved, ...(Object.keys(messageParams).length ? { messageParamsJson: JSON.stringify(messageParams) } : {}) }, opts)
120
+ }
121
+
112
122
  export const PRIMITIVE_SHORTCUTS = {
113
123
  sendText: sock => (jid, text, opts = {}) => sock.sendMessage(jid, { text }, opts),
114
124
  sendImage: sock => (jid, image, caption = '', opts = {}) => sock.sendMessage(jid, { image, caption }, opts),
@@ -126,5 +136,6 @@ export const HANDLER_SHORTCUTS = {
126
136
  sendStatusMentionMessage: (sock, config) => (jid, data) => handleStatusMention(sock, jid, norm(data), config),
127
137
  sendStatusMentions: (sock, config) => (jid, data) => handleStatusMention(sock, jid, norm(data), config),
128
138
  sendPollResultMessage: sock => (jid, data) => handlePollResult(sock, jid, norm(data)),
129
- sendGroupStatusMessage: (sock, config) => (jid, content, opts = {}) => handleGroupStatus(sock, jid, norm(content), config)
139
+ sendGroupStatusMessage: (sock, config) => (jid, content, opts = {}) => handleGroupStatus(sock, jid, norm(content), config),
140
+ sendNativeFlow: sock => (jid, content, opts = {}) => handleNativeFlow(sock, jid, norm(content), opts)
130
141
  }