@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.
- package/LICENSE +20 -18
- package/README.md +215 -23
- package/WAProto/WAProtoCompile.js +20 -7
- package/lib/Builders/AIRich.js +76 -45
- package/lib/Builders/base.js +14 -3
- package/lib/Builders/builders.js +140 -48
- package/lib/Socket/spam-report.js +104 -25
- package/lib/Socket/zervonex-handler.js +9 -14
- package/lib/Store/make-cache-manager-store.js +2 -3
- package/lib/Store/make-in-memory-store.js +1 -0
- package/lib/Utils/bot-capabilities.js +1 -0
- package/lib/Utils/crypto.js +2 -1
- package/lib/Utils/extras.js +1 -1
- package/lib/Utils/generics.js +1 -1
- package/lib/Utils/message-composer.js +8 -5
- package/lib/Utils/messages.js +9 -3
- package/lib/Utils/native-flow.js +26 -0
- package/lib/Utils/spam-report-utils.js +100 -69
- package/lib/Utils/use-multi-file-auth-state.js +95 -50
- package/lib/index.js +1 -1
- package/package.json +1 -1
package/lib/Builders/AIRich.js
CHANGED
|
@@ -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:
|
|
181
|
-
const richResponseMessage = proto.AIRichResponseMessage.create({ submessages, messageType:
|
|
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:
|
|
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,
|
|
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(
|
|
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
|
|
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
|
|
293
|
-
if (!url) throw new Error(
|
|
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
|
|
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:
|
|
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
|
|
374
|
-
|
|
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
|
-
|
|
378
|
-
|
|
379
|
-
|
|
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,
|
|
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,
|
|
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 ??
|
|
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:
|
|
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:
|
|
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
|
package/lib/Builders/base.js
CHANGED
|
@@ -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
|
|
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
|
}
|