@nexustechpro/baileys 2.1.2 → 2.1.6

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.
@@ -1,6 +1,67 @@
1
1
  import { proto } from '../../WAProto/index.js'
2
2
  import axios from 'axios'
3
3
  import crypto from 'crypto'
4
+ import { createRequire } from 'module'
5
+ const _require = createRequire(import.meta.url)
6
+ const Prism = _require('prismjs')
7
+ const loadPrismLanguages = _require('prismjs/components/index')
8
+
9
+ let _prismReady = false
10
+ const ensurePrism = () => { if (_prismReady) return; _prismReady = true; try { loadPrismLanguages() } catch { } }
11
+
12
+ const PRISM_TYPE_MAP = {
13
+ keyword: 1, 'keyword control': 1, builtin: 1, important: 1, bold: 1,
14
+ function: 2, 'function-definition': 2, method: 2, 'method-definition': 2,
15
+ string: 3, 'template-string': 3, 'string interpolation': 3, char: 3, regex: 3,
16
+ number: 4, constant: 4, boolean: 4, unit: 4,
17
+ comment: 5, 'block-comment': 5, prolog: 5, doctype: 5,
18
+ }
19
+
20
+ const flattenPrism = (tokens, out = []) => {
21
+ for (const t of tokens) {
22
+ if (typeof t === 'string') { if (t) out.push({ codeContent: t, highlightType: 0 }); continue }
23
+ const type = PRISM_TYPE_MAP[t.type] ?? 0
24
+ const content = Array.isArray(t.content) ? flattenPrism(t.content, []).map(x => x.codeContent).join('') : String(t.content)
25
+ if (content) out.push({ codeContent: content, highlightType: type })
26
+ }
27
+ return out
28
+ }
29
+
30
+ const tokenizeCode = (code, language = 'javascript') => {
31
+ ensurePrism()
32
+ const lang = language.toLowerCase()
33
+ const grammar = Prism.languages[lang] ?? Prism.languages.javascript
34
+ const raw = flattenPrism(Prism.tokenize(code, grammar))
35
+ const merged = []
36
+ for (const t of raw) {
37
+ const prev = merged[merged.length - 1]
38
+ if (prev && prev.highlightType === t.highlightType) prev.codeContent += t.codeContent
39
+ else merged.push({ ...t })
40
+ }
41
+ const TYPE_NAMES = ['DEFAULT', 'KEYWORD', 'METHOD', 'STR', 'NUMBER', 'COMMENT']
42
+ return { codeBlocks: merged, unified_codeBlock: merged.map(t => ({ content: t.codeContent, type: TYPE_NAMES[t.highlightType] ?? 'DEFAULT' })) }
43
+ }
44
+
45
+ const extractHyperlinks = (text) => {
46
+ const hyperlinks = []
47
+ let cleaned = text
48
+ const IE_RE = /\{\{IE_(\d+)\}\}([\s\S]*?)\{\{\/IE_\1\}\}/g
49
+ const URL_RE = /https?:\/\/[^\s\])"]+/g
50
+ let match
51
+ while ((match = IE_RE.exec(text)) !== null) { hyperlinks.push({ key: `IE_${match[1]}`, reference_id: parseInt(match[1]) + 1, text: match[2], url: '' }); cleaned = cleaned.replace(match[0], match[2]) }
52
+ if (!hyperlinks.length) { while ((match = URL_RE.exec(text)) !== null) hyperlinks.push({ key: `IE_${hyperlinks.length}`, reference_id: hyperlinks.length + 1, text: '', url: match[0] }) }
53
+ return { text: cleaned, hyperlinks }
54
+ }
55
+
56
+ const parseTableArray = (table) => {
57
+ const [title, headerStr, ...rest] = table
58
+ const splitCols = s => typeof s === 'string' ? (s.includes('|') ? s.split('|') : s.split(',')).map(x => x.trim()) : (Array.isArray(s) ? s.map(String) : [])
59
+ const headers = splitCols(headerStr)
60
+ const rows = rest.flatMap(r => typeof r === 'string' ? r.split(';;').map(splitCols) : [Array.isArray(r) ? r.map(String) : [String(r)]])
61
+ const maxLen = Math.max(headers.length, ...rows.map(r => r.length))
62
+ const pad = arr => [...arr, ...Array(maxLen - arr.length).fill('')]
63
+ return { title: String(title ?? ''), rows: [{ items: pad(headers), isHeading: true }, ...rows.map(r => ({ items: pad(r) }))], unified_rows: [{ is_header: true, cells: pad(headers) }, ...rows.map(r => ({ is_header: false, cells: pad(r) }))] }
64
+ }
4
65
 
5
66
  class NexusHandler {
6
67
  constructor(utils, waUploadToServer, relayMessageFn, options = {}) {
@@ -21,12 +82,14 @@ class NexusHandler {
21
82
  STICKER_PACK: this.handleStickerPack.bind(this),
22
83
  GROUP_STATUS: this.handleGroupStory.bind(this),
23
84
  CAROUSEL: this.handleCarousel.bind(this),
24
- CAROUSEL_PROTO: this.handleCarouselProto.bind(this)
85
+ CAROUSEL_PROTO: this.handleCarouselProto.bind(this),
86
+ AI_RICH: this.handleAiRich.bind(this)
25
87
  }
26
88
  }
27
89
 
28
90
  // ─── TYPE DETECTION ───────────────────────────────────────────────────────
29
91
  detectType(content) {
92
+ if (content.aiRich || content.airich || content.richResponse || content.richResponseMessage || content.AIRich) return 'AI_RICH'
30
93
  if (content.carouselMessage || content.carousel) return 'CAROUSEL'
31
94
  if (content.carouselProto) return 'CAROUSEL_PROTO'
32
95
  const map = {
@@ -56,82 +119,162 @@ class NexusHandler {
56
119
  }
57
120
 
58
121
  async genMsg(jid, content, opts = {}) {
59
- return await this.utils.generateWAMessage(jid, content, {
60
- ...opts,
61
- upload: this.upload,
62
- userJid: opts.userJid || this.user?.id,
63
- getUrlInfo: opts.getUrlInfo || this.opts.getUrlInfo,
64
- logger: opts.logger || this.opts.logger
65
- })
122
+ return await this.utils.generateWAMessage(jid, content, { ...opts, upload: this.upload, userJid: opts.userJid || this.user?.id, getUrlInfo: opts.getUrlInfo || this.opts.getUrlInfo, logger: opts.logger || this.opts.logger })
66
123
  }
67
124
 
68
125
  async genFromContent(jid, content, opts = {}) {
69
- return await this.utils.generateWAMessageFromContent(jid, content, {
70
- ...opts,
71
- userJid: opts.userJid || this.user?.id
72
- })
126
+ return await this.utils.generateWAMessageFromContent(jid, content, { ...opts, userJid: opts.userJid || this.user?.id })
73
127
  }
74
128
 
75
- async sendMsg(jid, message, opts = {}) {
76
- return await this.relay(jid, message, opts)
77
- }
129
+ async sendMsg(jid, message, opts = {}) { return await this.relay(jid, message, opts) }
78
130
 
79
- buildCtx(quoted, sender) {
80
- return {
81
- stanzaId: quoted?.key?.id,
82
- participant: quoted?.key?.participant || sender,
83
- quotedMessage: quoted?.message
84
- }
85
- }
131
+ buildCtx(quoted, sender) { return { stanzaId: quoted?.key?.id, participant: quoted?.key?.participant || sender, quotedMessage: quoted?.message } }
86
132
 
87
133
  buildFullCtx(ctx, adReply) {
88
134
  const allowed = ['title', 'body', 'mediaType', 'thumbnailUrl', 'mediaUrl', 'sourceUrl', 'showAdAttribution', 'renderLargerThumbnail', 'thumbnail']
89
135
  const final = ctx ? { mentionedJid: ctx.mentionedJid || [], forwardingScore: ctx.forwardingScore || 0, isForwarded: ctx.isForwarded || false, ...ctx } : {}
90
- if (adReply) {
91
- final.externalAdReply = {}
92
- for (const k of allowed) if (adReply[k] !== undefined) final.externalAdReply[k] = adReply[k]
93
- final.externalAdReply = { mediaType: 1, showAdAttribution: false, renderLargerThumbnail: false, ...final.externalAdReply }
94
- }
136
+ if (adReply) { final.externalAdReply = {}; for (const k of allowed) if (adReply[k] !== undefined) final.externalAdReply[k] = adReply[k]; final.externalAdReply = { mediaType: 1, showAdAttribution: false, renderLargerThumbnail: false, ...final.externalAdReply } }
95
137
  return final
96
138
  }
97
139
 
98
- genJid() {
99
- const id = this.utils.generateMessageIDV2?.() || this.utils.generateMessageID?.() || crypto.randomBytes(10).toString('hex')
100
- return id.includes('@') ? id : `${id}@s.whatsapp.net`
101
- }
102
-
140
+ genJid() { const id = this.utils.generateMessageIDV2?.() || this.utils.generateMessageID?.() || crypto.randomBytes(10).toString('hex'); return id.includes('@') ? id : `${id}@s.whatsapp.net` }
103
141
  parseTime(val, def) { return typeof val === 'string' ? parseInt(val) : (val || def) }
104
142
  delay(ms) { return new Promise(r => setTimeout(r, ms)) }
143
+ genMsgId() { return this.utils.generateMessageIDV2?.() || crypto.randomBytes(10).toString('hex').toUpperCase() }
105
144
 
106
145
  async downloadBuffer(urlOrBuffer) {
107
146
  if (Buffer.isBuffer(urlOrBuffer)) return urlOrBuffer
108
- if (typeof urlOrBuffer === 'string') {
109
- try {
110
- const res = await axios.get(urlOrBuffer, { responseType: 'arraybuffer' })
111
- return Buffer.from(res.data)
112
- } catch { this.opts.logger?.warn('Failed to download buffer from URL') }
113
- }
147
+ if (typeof urlOrBuffer === 'string') { try { const res = await axios.get(urlOrBuffer, { responseType: 'arraybuffer' }); return Buffer.from(res.data) } catch { this.opts.logger?.warn('Failed to download buffer from URL') } }
114
148
  return null
115
149
  }
116
150
 
151
+ // ─── AI RICH MESSAGES ─────────────────────────────────────────────────────
152
+ _buildAiRichPayload(data = {}) {
153
+ const submessages = [], sections = [], richResponseSources = []
154
+
155
+ const addText = (text = '') => {
156
+ const str = String(text ?? '')
157
+ const { text: cleaned, hyperlinks } = extractHyperlinks(str)
158
+ submessages.push({ messageType: 2, messageText: cleaned })
159
+ sections.push({ view_model: { primitive: hyperlinks.length ? { text: cleaned, inline_entities: hyperlinks.map(h => ({ key: h.key, metadata: h.text?.trim() ? { display_name: h.text, is_trusted: true, url: h.url, __typename: 'GenAIInlineLinkItem' } : { reference_id: h.reference_id, reference_url: h.url, reference_title: h.url, reference_display_name: h.url, sources: [], __typename: 'GenAISearchCitationItem' } })), __typename: 'GenAIMarkdownTextUXPrimitive' } : { text: str, __typename: 'GenAIMarkdownTextUXPrimitive' }, __typename: 'GenAISingleLayoutViewModel' } })
160
+ }
161
+
162
+ const addCode = (language = 'javascript', code = '') => {
163
+ const { codeBlocks, unified_codeBlock } = tokenizeCode(String(code ?? ''), language)
164
+ submessages.push({ messageType: 5, codeMetadata: { codeLanguage: language, codeBlocks } })
165
+ sections.push({ view_model: { primitive: { language, code_blocks: unified_codeBlock, __typename: 'GenAICodeUXPrimitive' }, __typename: 'GenAISingleLayoutViewModel' } })
166
+ }
167
+
168
+ const addTable = (table = []) => {
169
+ const isRaw = Array.isArray(table[0])
170
+ let meta
171
+ if (isRaw) {
172
+ const headers = table[0].map(String), rows = table.slice(1).map(r => r.map(String))
173
+ const maxLen = Math.max(headers.length, ...rows.map(r => r.length))
174
+ const pad = arr => [...arr, ...Array(maxLen - arr.length).fill('')]
175
+ meta = { title: '', rows: [{ items: pad(headers), isHeading: true }, ...rows.map(r => ({ items: pad(r) }))], unified_rows: [{ is_header: true, cells: pad(headers) }, ...rows.map(r => ({ is_header: false, cells: pad(r) }))] }
176
+ } else { meta = parseTableArray(table) }
177
+ submessages.push({ messageType: 4, tableMetadata: { title: meta.title, rows: meta.rows } })
178
+ sections.push({ view_model: { primitive: { rows: meta.unified_rows, ...(meta.title ? { title: meta.title } : {}), __typename: 'GenATableUXPrimitive' }, __typename: 'GenAISingleLayoutViewModel' } })
179
+ }
180
+
181
+ const addImages = (images = []) => {
182
+ const list = (Array.isArray(images) ? images : [images]).filter(Boolean).map(item => { const url = typeof item === 'string' ? item : item.url || item.imageUrl || item.imagePreviewUrl; return { imagePreviewUrl: url, imageHighResUrl: item?.imageHighResUrl || item?.highResUrl || url, sourceUrl: item?.sourceUrl || data.sourceUrl || 'https://google.com' } }).filter(x => x.imagePreviewUrl)
183
+ if (!list.length) return
184
+ submessages.push({ messageType: 1, gridImageMetadata: { gridImageUrl: { imagePreviewUrl: list[0].imagePreviewUrl }, imageUrls: list } })
185
+ list.forEach(({ imagePreviewUrl }) => sections.push({ view_model: { primitive: { media: { url: imagePreviewUrl, mime_type: 'image/jpeg' }, imagine_type: 3, status: { status: 'READY' }, __typename: 'GenAIImaginePrimitive' }, __typename: 'GenAISingleLayoutViewModel' } }))
186
+ }
187
+
188
+ const addSources = (sources = []) => {
189
+ if (!Array.isArray(sources) || !sources.length) return
190
+ sections.push({ view_model: { primitive: { sources: sources.map(s => { const arr = Array.isArray(s) ? s : null; return { source_type: 'THIRD_PARTY', source_display_name: arr ? arr[2] : s.displayName || s.title || s.sourceTitle || 'Source', source_subtitle: s.subtitle || 'AI', source_url: arr ? arr[1] : s.url || s.sourceUrl || '', favicon: { url: arr ? arr[0] : s.profileIconUrl || s.faviconUrl || s.thumbnailUrl || '', mime_type: 'image/jpeg', width: 16, height: 16 } } }), search_engine: data.searchEngine || 'MAME', __typename: 'GenAISearchResultPrimitive' }, __typename: 'GenAISingleLayoutViewModel' } })
191
+ }
192
+
193
+ const addReels = (reels = []) => {
194
+ if (!Array.isArray(reels) || !reels.length) return
195
+ submessages.push({ messageType: 9, contentItemsMetadata: { contentType: 1, itemsMetadata: reels.map(r => ({ reelItem: { title: r.title || r.creator || '', profileIconUrl: r.profileIconUrl || r.avatar_url || '', thumbnailUrl: r.thumbnailUrl || r.thumbnail_url || '', videoUrl: r.videoUrl || r.reels_url || r.url || '' } })) } })
196
+ reels.forEach((r, idx) => richResponseSources.push({ provider: r.provider || 'UNKNOWN', thumbnailCDNURL: r.thumbnailUrl || '', sourceProviderURL: r.videoUrl || r.url || '', sourceQuery: '', faviconCDNURL: r.profileIconUrl || '', citationNumber: idx + 1, sourceTitle: r.title || r.creator || `Reel ${idx + 1}` }))
197
+ sections.push({ view_model: { primitives: reels.map(r => ({ reels_url: r.videoUrl || r.url || '', thumbnail_url: r.thumbnailUrl || r.thumbnail_url || '', creator: r.title || r.creator || '', avatar_url: r.profileIconUrl || r.avatar_url || '', reels_title: r.reels_title || r.title || '', likes_count: r.likes_count || 0, shares_count: r.shares_count || 0, view_count: r.view_count || 0, reel_source: r.reel_source || 'IG', is_verified: !!(r.is_verified ?? r.isVerified), __typename: 'GenAIReelPrimitive' })), __typename: 'GenAIHScrollLayoutViewModel' } })
198
+ }
199
+
200
+ const addLatex = (expressions = [], text = '') => {
201
+ submessages.push({ messageType: 8, latexMetadata: { text, expressions: expressions.map(e => ({ latexExpression: e.expression || e.latexExpression || '', url: e.url || '', width: e.width || 0, height: e.height || 0, ...(e.fontHeight !== undefined ? { fontHeight: e.fontHeight } : {}), ...(e.imageTopPadding !== undefined ? { imageTopPadding: e.imageTopPadding } : {}), ...(e.imageLeadingPadding !== undefined ? { imageLeadingPadding: e.imageLeadingPadding } : {}), ...(e.imageBottomPadding !== undefined ? { imageBottomPadding: e.imageBottomPadding } : {}), ...(e.imageTrailingPadding !== undefined ? { imageTrailingPadding: e.imageTrailingPadding } : {}) })) } })
202
+ }
203
+
204
+ // process in declaration order for predictable output
205
+ if (data.header) addText(data.header)
206
+ if (data.text) addText(data.text)
207
+ if (Array.isArray(data.texts)) data.texts.forEach(t => addText(t))
208
+ if (data.code) { typeof data.code === 'string' ? addCode(data.language || 'javascript', data.code) : addCode(data.code.language || data.language || 'javascript', data.code.content || data.code.code || '') }
209
+ if (Array.isArray(data.codes)) data.codes.forEach(c => typeof c === 'string' ? addCode(data.language || 'javascript', c) : addCode(c.language || data.language || 'javascript', c.content || c.code || ''))
210
+ if (data.table) { Array.isArray(data.table[0]) ? addTable(data.table) : Array.isArray(data.table) ? addTable(data.table) : null }
211
+ if (data.headers && data.rows) addTable([[...(data.title ? [data.title] : ['']), data.headers, ...data.rows].flat(0)])
212
+ if (data.image || data.images || data.gridImage) addImages(data.images || data.gridImage || data.image)
213
+ if (data.sources) addSources(data.sources)
214
+ if (data.reels || data.reel) addReels(data.reels || data.reel)
215
+ if (data.latex) addLatex(Array.isArray(data.latex) ? data.latex : [data.latex], data.latexText || '')
216
+ if (Array.isArray(data.parts)) {
217
+ for (const p of data.parts) {
218
+ if (p.type === 'text') addText(p.content)
219
+ else if (p.type === 'code') addCode(p.language || 'javascript', p.content)
220
+ else if (p.type === 'table') { if (Array.isArray(p.table)) addTable(p.table); else if (p.headers && p.rows) addTable([p.title || '', p.headers, ...p.rows]) }
221
+ else if (p.type === 'images') addImages(p.images || p.image)
222
+ else if (p.type === 'sources') addSources(p.sources)
223
+ else if (p.type === 'reels') addReels(p.reels)
224
+ else if (p.type === 'latex') addLatex(Array.isArray(p.expressions) ? p.expressions : [p.expressions], p.text || '')
225
+ }
226
+ }
227
+ if (data.footer) addText(data.footer)
228
+
229
+ const forwarded = data.forwarded !== false
230
+ const includesUnifiedResponse = data.includesUnifiedResponse !== false
231
+ const botJid = data.botJid || '259786046210223@bot'
232
+ const allSources = data.richResponseSources || richResponseSources
233
+ const ctxInfo = forwarded ? { forwardingScore: data.forwardingScore || 2, isForwarded: true, forwardedAiBotMessageInfo: { botJid }, forwardOrigin: data.forwardOrigin || 4, botMessageSharingInfo: { botEntryPointOrigin: 1, forwardScore: data.forwardingScore || 2 }, mentionedJid: [], groupMentions: [], ...(data.quoted?.key ? { stanzaId: data.quoted.key.id, participant: data.quoted.key.participant ?? data.quoted.sender ?? data.quoted.key.remoteJid, quotedMessage: data.quoted.message } : {}), ...(data.contextInfo || {}) } : (data.contextInfo || {})
234
+
235
+ return {
236
+ messageContextInfo: {
237
+ deviceListMetadata: { senderKeyIndexes: [], recipientKeyIndexes: [], recipientKeyHash: '', recipientTimestamp: Math.floor(Date.now() / 1000) },
238
+ deviceListMetadataVersion: 2,
239
+ messageSecret: crypto.randomBytes(32),
240
+ botMetadata: { messageDisclaimerText: data.disclaimerText || data.messageDisclaimerText || '', pluginMetadata: {}, richResponseSourcesMetadata: { sources: allSources } }
241
+ },
242
+ botForwardedMessage: { message: { richResponseMessage: { messageType: data.messageType || 1, submessages, unifiedResponse: { data: includesUnifiedResponse ? Buffer.from(JSON.stringify({ response_id: data.responseId || crypto.randomUUID(), sections })).toString('base64') : '' }, contextInfo: ctxInfo } } }
243
+ }
244
+ }
245
+
246
+ async handleAiRich(content, jid, quoted) {
247
+ const raw = content.aiRich || content.airich || content.richResponse || content.richResponseMessage || content.AIRich || content
248
+ const data = { ...raw, quoted: raw.quoted || quoted }
249
+ const message = this._buildAiRichPayload(data)
250
+ const messageId = raw.messageId || this.genMsgId()
251
+ await this.sendMsg(jid, message, { messageId })
252
+ return { message, messageId }
253
+ }
254
+
255
+ captureAiRich(msg) {
256
+ const rich = msg?.botForwardedMessage?.message?.richResponseMessage
257
+ if (!rich?.unifiedResponse?.data) return null
258
+ return { submessages: rich.submessages || [], sections: JSON.parse(Buffer.from(rich.unifiedResponse.data, 'base64').toString()), contextInfo: rich.contextInfo || {}, messageType: rich.messageType || 1 }
259
+ }
260
+
261
+ async relayAiRich(jid, captured, quoted) {
262
+ const data = { responseId: captured.sections?.response_id, forwarded: true, quoted, includesUnifiedResponse: true, messageType: captured.messageType }
263
+ const message = this._buildAiRichPayload(data)
264
+ message.botForwardedMessage.message.richResponseMessage.submessages = captured.submessages
265
+ message.botForwardedMessage.message.richResponseMessage.unifiedResponse.data = Buffer.from(JSON.stringify(captured.sections)).toString('base64')
266
+ const messageId = this.genMsgId()
267
+ await this.sendMsg(jid, message, { messageId })
268
+ return { message, messageId }
269
+ }
270
+
117
271
  // ─── PAYMENT ──────────────────────────────────────────────────────────────
118
272
  async handlePayment(content, jid, quoted) {
119
273
  const d = content.requestPaymentMessage
120
274
  const ctx = this.buildCtx(quoted, content.sender)
121
- const notes = d.sticker?.stickerMessage
122
- ? { stickerMessage: { ...d.sticker.stickerMessage, contextInfo: ctx } }
123
- : d.note ? { extendedTextMessage: { text: d.note, contextInfo: ctx } } : {}
275
+ const notes = d.sticker?.stickerMessage ? { stickerMessage: { ...d.sticker.stickerMessage, contextInfo: ctx } } : d.note ? { extendedTextMessage: { text: d.note, contextInfo: ctx } } : {}
124
276
  const targetJid = jid || content.jid
125
- const msg = await this.genFromContent(targetJid, {
126
- requestPaymentMessage: proto.Message.RequestPaymentMessage.fromObject({
127
- expiryTimestamp: d.expiry || 0,
128
- amount1000: d.amount || 0,
129
- currencyCodeIso4217: d.currency || 'IDR',
130
- requestFrom: d.from || '0@s.whatsapp.net',
131
- noteMessage: notes,
132
- background: d.background ?? { id: 'DEFAULT', placeholderArgb: 0xfff0f0f0 }
133
- })
134
- }, { quoted })
277
+ const msg = await this.genFromContent(targetJid, { requestPaymentMessage: proto.Message.RequestPaymentMessage.fromObject({ expiryTimestamp: d.expiry || 0, amount1000: d.amount || 0, currencyCodeIso4217: d.currency || 'IDR', requestFrom: d.from || '0@s.whatsapp.net', noteMessage: notes, background: d.background ?? { id: 'DEFAULT', placeholderArgb: 0xfff0f0f0 } }) }, { quoted })
135
278
  await this.sendMsg(targetJid, msg.message, { messageId: msg.key.id })
136
279
  return msg
137
280
  }
@@ -140,38 +283,9 @@ class NexusHandler {
140
283
  async handleProduct(content, jid, quoted) {
141
284
  const p = content.productMessage || {}
142
285
  let prodImg = null
143
- if (p.thumbnail) {
144
- const src = Buffer.isBuffer(p.thumbnail) ? { image: p.thumbnail } : { image: { url: p.thumbnail.url || p.thumbnail } }
145
- const res = await this.utils.generateWAMessageContent(src, { upload: this.upload })
146
- prodImg = res?.imageMessage || res?.message?.imageMessage
147
- }
148
- const product = proto.Message.ProductMessage.ProductSnapshot.create({
149
- productId: p.productId,
150
- title: p.title || '',
151
- description: p.description || '',
152
- currencyCode: p.currencyCode || 'IDR',
153
- priceAmount1000: p.priceAmount1000,
154
- retailerId: p.retailerId,
155
- url: p.url,
156
- productImageCount: prodImg ? 1 : 0,
157
- ...(prodImg && { productImage: prodImg })
158
- })
159
- const msg = await this.genFromContent(jid, {
160
- viewOnceMessage: {
161
- message: {
162
- interactiveMessage: proto.Message.InteractiveMessage.create({
163
- body: proto.Message.InteractiveMessage.Body.create({ text: p.body || '' }),
164
- footer: proto.Message.InteractiveMessage.Footer.create({ text: p.footer || '' }),
165
- header: proto.Message.InteractiveMessage.Header.create({
166
- title: p.title || '',
167
- hasMediaAttachment: !!prodImg,
168
- productMessage: proto.Message.ProductMessage.create({ product, businessOwnerJid: '0@s.whatsapp.net' })
169
- }),
170
- nativeFlowMessage: proto.Message.InteractiveMessage.NativeFlowMessage.create({ buttons: p.buttons || [] })
171
- })
172
- }
173
- }
174
- }, { quoted })
286
+ if (p.thumbnail) { const src = Buffer.isBuffer(p.thumbnail) ? { image: p.thumbnail } : { image: { url: p.thumbnail.url || p.thumbnail } }; const res = await this.utils.generateWAMessageContent(src, { upload: this.upload }); prodImg = res?.imageMessage || res?.message?.imageMessage }
287
+ const product = proto.Message.ProductMessage.ProductSnapshot.create({ productId: p.productId, title: p.title || '', description: p.description || '', currencyCode: p.currencyCode || 'IDR', priceAmount1000: p.priceAmount1000, retailerId: p.retailerId, url: p.url, productImageCount: prodImg ? 1 : 0, ...(prodImg && { productImage: prodImg }) })
288
+ const msg = await this.genFromContent(jid, { viewOnceMessage: { message: { interactiveMessage: proto.Message.InteractiveMessage.create({ body: proto.Message.InteractiveMessage.Body.create({ text: p.body || '' }), footer: proto.Message.InteractiveMessage.Footer.create({ text: p.footer || '' }), header: proto.Message.InteractiveMessage.Header.create({ title: p.title || '', hasMediaAttachment: !!prodImg, productMessage: proto.Message.ProductMessage.create({ product, businessOwnerJid: '0@s.whatsapp.net' }) }), nativeFlowMessage: proto.Message.InteractiveMessage.NativeFlowMessage.create({ buttons: p.buttons || [] }) }) } } }, { quoted })
175
289
  await this.sendMsg(jid, msg.message, { messageId: msg.key.id })
176
290
  return msg
177
291
  }
@@ -183,33 +297,17 @@ class NexusHandler {
183
297
  if (i.thumbnail) media = await this.prepMedia({ url: i.thumbnail }, 'image')
184
298
  else if (i.image) media = await this.prepMedia(i.image, 'image')
185
299
  else if (i.video) media = await this.prepMedia(i.video, 'video')
186
- else if (i.document) {
187
- media = await this.prepMedia(i.document, 'document')
188
- if (i.jpegThumbnail) media.documentMessage.jpegThumbnail = typeof i.jpegThumbnail === 'object' && i.jpegThumbnail.url ? { url: i.jpegThumbnail.url } : i.jpegThumbnail
189
- if (i.fileName) media.documentMessage.fileName = i.fileName
190
- if (i.mimetype) media.documentMessage.mimetype = i.mimetype
191
- }
300
+ else if (i.document) { media = await this.prepMedia(i.document, 'document'); if (i.jpegThumbnail) media.documentMessage.jpegThumbnail = typeof i.jpegThumbnail === 'object' && i.jpegThumbnail.url ? { url: i.jpegThumbnail.url } : i.jpegThumbnail; if (i.fileName) media.documentMessage.fileName = i.fileName; if (i.mimetype) media.documentMessage.mimetype = i.mimetype }
192
301
  const bodyText = i.body?.text || i.title || ''
193
302
  const footerText = i.footer?.text || (typeof i.footer === 'string' ? i.footer : '') || ''
194
303
  const headerTitle = typeof i.header === 'string' ? i.header : i.header?.title || ''
195
304
  let nativeFlow = null
196
- if (i.buttons?.length || i.nativeFlowMessage) {
197
- const nfm = i.nativeFlowMessage || {}
198
- nativeFlow = proto.Message.InteractiveMessage.NativeFlowMessage.create({
199
- buttons: i.buttons || nfm.buttons || [],
200
- messageParamsJson: nfm.messageParamsJson || ''
201
- })
202
- }
305
+ if (i.buttons?.length || i.nativeFlowMessage) { const nfm = i.nativeFlowMessage || {}; nativeFlow = proto.Message.InteractiveMessage.NativeFlowMessage.create({ buttons: i.buttons || nfm.buttons || [], messageParamsJson: nfm.messageParamsJson || '' }) }
203
306
  const headerMedia = {}
204
307
  if (media?.imageMessage) headerMedia.imageMessage = media.imageMessage
205
308
  if (media?.videoMessage) headerMedia.videoMessage = media.videoMessage
206
309
  if (media?.documentMessage) headerMedia.documentMessage = media.documentMessage
207
- const interactive = proto.Message.InteractiveMessage.create({
208
- body: proto.Message.InteractiveMessage.Body.create({ text: bodyText }),
209
- footer: proto.Message.InteractiveMessage.Footer.create({ text: footerText }),
210
- header: proto.Message.InteractiveMessage.Header.create({ title: headerTitle, hasMediaAttachment: !!media, ...headerMedia }),
211
- ...(nativeFlow && { nativeFlowMessage: nativeFlow })
212
- })
310
+ const interactive = proto.Message.InteractiveMessage.create({ body: proto.Message.InteractiveMessage.Body.create({ text: bodyText }), footer: proto.Message.InteractiveMessage.Footer.create({ text: footerText }), header: proto.Message.InteractiveMessage.Header.create({ title: headerTitle, hasMediaAttachment: !!media, ...headerMedia }), ...(nativeFlow && { nativeFlowMessage: nativeFlow }) })
213
311
  const ctx = this.buildFullCtx(i.contextInfo, i.externalAdReply)
214
312
  if (Object.keys(ctx).length) interactive.contextInfo = ctx
215
313
  const msg = await this.genFromContent(jid, { interactiveMessage: interactive }, { quoted })
@@ -221,51 +319,14 @@ class NexusHandler {
221
319
  async handleAlbum(content, jid, quoted) {
222
320
  const arr = Array.isArray(content.albumMessage) ? content.albumMessage : []
223
321
  if (!arr.length) throw new Error('albumMessage must contain media items')
224
- const album = await this.genFromContent(jid, {
225
- messageContextInfo: proto.MessageContextInfo.create({ messageSecret: crypto.randomBytes(32) }),
226
- albumMessage: proto.Message.AlbumMessage.create({
227
- expectedImageCount: arr.filter(a => a.image).length,
228
- expectedVideoCount: arr.filter(a => a.video).length
229
- })
230
- }, { userJid: this.genJid(), quoted })
322
+ const album = await this.genFromContent(jid, { messageContextInfo: proto.MessageContextInfo.create({ messageSecret: crypto.randomBytes(32) }), albumMessage: proto.Message.AlbumMessage.create({ expectedImageCount: arr.filter(a => a.image).length, expectedVideoCount: arr.filter(a => a.video).length }) }, { userJid: this.genJid(), quoted })
231
323
  await this.sendMsg(jid, album.message, { messageId: album.key.id })
232
324
  for (const item of arr) {
233
325
  const img = await this.genMsg(jid, item, {})
234
- img.message.messageContextInfo = proto.MessageContextInfo.create({
235
- messageSecret: crypto.randomBytes(32),
236
- messageAssociation: proto.MessageAssociation.create({ associationType: 1, parentMessageKey: album.key }),
237
- participant: '0@s.whatsapp.net',
238
- remoteJid: 'status@broadcast',
239
- forwardingScore: 99999,
240
- isForwarded: true,
241
- mentionedJid: [jid],
242
- starred: true,
243
- labels: ['Y', 'Important'],
244
- isHighlighted: true,
245
- businessMessageForwardInfo: proto.ContextInfo.BusinessMessageForwardInfo.create({ businessOwnerJid: jid }),
246
- dataSharingContext: proto.ContextInfo.DataSharingContext.create({ showMmDisclosure: true })
247
- })
248
- img.message.forwardedNewsletterMessageInfo = proto.ContextInfo.ForwardedNewsletterMessageInfo.create({
249
- newsletterJid: '0@newsletter',
250
- serverMessageId: 1,
251
- newsletterName: 'WhatsApp',
252
- contentType: 'UPDATE_CARD',
253
- timestamp: new Date().toISOString(),
254
- senderName: 'Nexus',
255
- priority: 'high',
256
- status: 'sent'
257
- })
258
- img.message.disappearingMode = proto.DisappearingMode.create({
259
- initiator: 3, trigger: 4, initiatorDeviceJid: jid,
260
- initiatedByExternalService: true, initiatedByUserDevice: true,
261
- initiatedBySystem: true, initiatedByServer: true,
262
- initiatedByAdmin: true, initiatedByUser: true,
263
- initiatedByApp: true, initiatedByBot: true, initiatedByMe: true
264
- })
265
- await this.sendMsg(jid, img.message, {
266
- messageId: img.key.id,
267
- quoted: { key: { ...album.key, fromMe: true, participant: this.genJid() }, message: album.message }
268
- })
326
+ img.message.messageContextInfo = proto.MessageContextInfo.create({ messageSecret: crypto.randomBytes(32), messageAssociation: proto.MessageAssociation.create({ associationType: 1, parentMessageKey: album.key }), participant: '0@s.whatsapp.net', remoteJid: 'status@broadcast', forwardingScore: 99999, isForwarded: true, mentionedJid: [jid], starred: true, labels: ['Y', 'Important'], isHighlighted: true, businessMessageForwardInfo: proto.ContextInfo.BusinessMessageForwardInfo.create({ businessOwnerJid: jid }), dataSharingContext: proto.ContextInfo.DataSharingContext.create({ showMmDisclosure: true }) })
327
+ img.message.forwardedNewsletterMessageInfo = proto.ContextInfo.ForwardedNewsletterMessageInfo.create({ newsletterJid: '0@newsletter', serverMessageId: 1, newsletterName: 'WhatsApp', contentType: 'UPDATE_CARD', timestamp: new Date().toISOString(), senderName: 'Nexus', priority: 'high', status: 'sent' })
328
+ img.message.disappearingMode = proto.DisappearingMode.create({ initiator: 3, trigger: 4, initiatorDeviceJid: jid, initiatedByExternalService: true, initiatedByUserDevice: true, initiatedBySystem: true, initiatedByServer: true, initiatedByAdmin: true, initiatedByUser: true, initiatedByApp: true, initiatedByBot: true, initiatedByMe: true })
329
+ await this.sendMsg(jid, img.message, { messageId: img.key.id, quoted: { key: { ...album.key, fromMe: true, participant: this.genJid() }, message: album.message } })
269
330
  }
270
331
  return album
271
332
  }
@@ -273,34 +334,7 @@ class NexusHandler {
273
334
  // ─── EVENT ────────────────────────────────────────────────────────────────
274
335
  async handleEvent(content, jid, quoted) {
275
336
  const e = content.eventMessage
276
- const msg = await this.genFromContent(jid, {
277
- messageContextInfo: proto.MessageContextInfo.create({
278
- deviceListMetadata: {},
279
- deviceListMetadataVersion: 2,
280
- messageSecret: crypto.randomBytes(32),
281
- supportPayload: JSON.stringify({ version: 2, is_ai_message: true, should_show_system_message: true, ticket_id: crypto.randomBytes(16).toString('hex') })
282
- }),
283
- eventMessage: proto.Message.EventMessage.create({
284
- contextInfo: proto.ContextInfo.create({
285
- mentionedJid: [jid],
286
- participant: jid,
287
- remoteJid: 'status@broadcast',
288
- forwardedNewsletterMessageInfo: proto.ContextInfo.ForwardedNewsletterMessageInfo.create({
289
- newsletterName: 'Nexus Events',
290
- newsletterJid: '120363422827915475@newsletter',
291
- serverMessageId: 1
292
- })
293
- }),
294
- isCanceled: e.isCanceled || false,
295
- name: e.name,
296
- description: e.description,
297
- location: e.location || { degreesLatitude: 0, degreesLongitude: 0, name: 'Location' },
298
- joinLink: e.joinLink || '',
299
- startTime: this.parseTime(e.startTime, Date.now()),
300
- endTime: this.parseTime(e.endTime, Date.now() + 3600000),
301
- extraGuestsAllowed: e.extraGuestsAllowed !== false
302
- })
303
- }, { quoted })
337
+ const msg = await this.genFromContent(jid, { messageContextInfo: proto.MessageContextInfo.create({ deviceListMetadata: {}, deviceListMetadataVersion: 2, messageSecret: crypto.randomBytes(32), supportPayload: JSON.stringify({ version: 2, is_ai_message: true, should_show_system_message: true, ticket_id: crypto.randomBytes(16).toString('hex') }) }), eventMessage: proto.Message.EventMessage.create({ contextInfo: proto.ContextInfo.create({ mentionedJid: [jid], participant: jid, remoteJid: 'status@broadcast', forwardedNewsletterMessageInfo: proto.ContextInfo.ForwardedNewsletterMessageInfo.create({ newsletterName: 'Nexus Events', newsletterJid: '120363422827915475@newsletter', serverMessageId: 1 }) }), isCanceled: e.isCanceled || false, name: e.name, description: e.description, location: e.location || { degreesLatitude: 0, degreesLongitude: 0, name: 'Location' }, joinLink: e.joinLink || '', startTime: this.parseTime(e.startTime, Date.now()), endTime: this.parseTime(e.endTime, Date.now() + 3600000), extraGuestsAllowed: e.extraGuestsAllowed !== false }) }, { quoted })
304
338
  await this.sendMsg(jid, msg.message, { messageId: msg.key.id })
305
339
  return msg
306
340
  }
@@ -308,25 +342,7 @@ class NexusHandler {
308
342
  // ─── POLL RESULT ──────────────────────────────────────────────────────────
309
343
  async handlePollResult(content, jid, quoted) {
310
344
  const p = content.pollResultMessage
311
- const msg = await this.genFromContent(jid, {
312
- pollResultSnapshotMessage: proto.Message.PollResultSnapshotMessage.create({
313
- name: p.name,
314
- pollVotes: (p.pollVotes || []).map(v => proto.Message.PollResultSnapshotMessage.PollVote.create({
315
- optionName: v.optionName,
316
- optionVoteCount: typeof v.optionVoteCount === 'number' ? v.optionVoteCount.toString() : v.optionVoteCount
317
- })),
318
- contextInfo: proto.ContextInfo.create({
319
- isForwarded: true,
320
- forwardingScore: 1,
321
- forwardedNewsletterMessageInfo: proto.ContextInfo.ForwardedNewsletterMessageInfo.create({
322
- newsletterName: p.newsletter?.newsletterName || 'Newsletter',
323
- newsletterJid: p.newsletter?.newsletterJid || '120363399602691477@newsletter',
324
- serverMessageId: 1000,
325
- contentType: 'UPDATE'
326
- })
327
- })
328
- })
329
- }, { userJid: this.genJid(), quoted })
345
+ const msg = await this.genFromContent(jid, { pollResultSnapshotMessage: proto.Message.PollResultSnapshotMessage.create({ name: p.name, pollVotes: (p.pollVotes || []).map(v => proto.Message.PollResultSnapshotMessage.PollVote.create({ optionName: v.optionName, optionVoteCount: typeof v.optionVoteCount === 'number' ? v.optionVoteCount.toString() : v.optionVoteCount })), 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' }) }) }) }, { userJid: this.genJid(), quoted })
330
346
  await this.sendMsg(jid, msg.message, { messageId: msg.key.id })
331
347
  return msg
332
348
  }
@@ -336,23 +352,8 @@ class NexusHandler {
336
352
  const d = content.statusMentionMessage
337
353
  const mediaType = d.image ? 'image' : 'video'
338
354
  const media = await this.prepMedia(d.image || d.video, mediaType)
339
- const statusMsg = await this.relay('status@broadcast', { ...media }, {
340
- statusJidList: [d.mentions, this.user?.id].filter(Boolean),
341
- additionalNodes: [{
342
- tag: 'meta', attrs: {},
343
- content: [{ tag: 'mentioned_users', attrs: {}, content: [{ tag: 'to', attrs: { jid: d.mentions }, content: undefined }] }]
344
- }]
345
- })
346
- const mentionMsg = await this.genFromContent(jid, {
347
- statusMentionMessage: proto.Message.StatusMentionMessage.create({
348
- message: {
349
- protocolMessage: proto.Message.ProtocolMessage.create({
350
- messageId: statusMsg?.key?.id || d.mentions,
351
- type: proto.Message.ProtocolMessage.Type.STATUS_MENTION_MESSAGE
352
- })
353
- }
354
- })
355
- }, { additionalNodes: [{ tag: 'meta', attrs: { is_status_mention: 'true' }, content: undefined }] })
355
+ const statusMsg = await this.relay('status@broadcast', { ...media }, { statusJidList: [d.mentions, this.user?.id].filter(Boolean), additionalNodes: [{ tag: 'meta', attrs: {}, content: [{ tag: 'mentioned_users', attrs: {}, content: [{ tag: 'to', attrs: { jid: d.mentions }, content: undefined }] }] }] })
356
+ const mentionMsg = await this.genFromContent(jid, { statusMentionMessage: proto.Message.StatusMentionMessage.create({ message: { protocolMessage: proto.Message.ProtocolMessage.create({ messageId: statusMsg?.key?.id || d.mentions, type: proto.Message.ProtocolMessage.Type.STATUS_MENTION_MESSAGE }) } }) }, { additionalNodes: [{ tag: 'meta', attrs: { is_status_mention: 'true' }, content: undefined }] })
356
357
  await this.sendMsg(jid, mentionMsg.message, { messageId: mentionMsg.key.id })
357
358
  return mentionMsg
358
359
  }
@@ -361,28 +362,9 @@ class NexusHandler {
361
362
  async handleOrderMessage(content, jid, quoted) {
362
363
  const o = content.orderMessage
363
364
  const thumb = await this.downloadBuffer(o.thumbnail)
364
- const cleanJid = (id) => {
365
- if (!id) return null
366
- const [user] = id.split(':')
367
- return user.includes('@') ? user : `${user}@s.whatsapp.net`
368
- }
365
+ const cleanJid = id => { if (!id) return null; const [user] = id.split(':'); return user.includes('@') ? user : `${user}@s.whatsapp.net` }
369
366
  const seller = cleanJid(o.sellerJid) || cleanJid(this.user?.id) || cleanJid(jid) || '0@s.whatsapp.net'
370
- const msg = await this.genFromContent(jid, {
371
- orderMessage: proto.Message.OrderMessage.create({
372
- orderId: o.orderId || '7NEXUS25022008',
373
- thumbnail: thumb,
374
- itemCount: o.itemCount || 0,
375
- status: 2, // ACCEPTED
376
- surface: 1, // CATALOG
377
- message: o.message,
378
- orderTitle: o.orderTitle,
379
- sellerJid: seller,
380
- token: o.token || 'NEXUS_EXAMPLE_TOKEN',
381
- totalAmount1000: o.totalAmount1000 || 0,
382
- totalCurrencyCode: o.totalCurrencyCode || 'IDR',
383
- messageVersion: 2
384
- })
385
- }, { quoted })
367
+ const msg = await this.genFromContent(jid, { orderMessage: proto.Message.OrderMessage.create({ orderId: o.orderId || '7NEXUS25022008', thumbnail: thumb, itemCount: o.itemCount || 0, status: 2, surface: 1, message: o.message, orderTitle: o.orderTitle, sellerJid: seller, token: o.token || 'NEXUS_EXAMPLE_TOKEN', totalAmount1000: o.totalAmount1000 || 0, totalCurrencyCode: o.totalCurrencyCode || 'IDR', messageVersion: 2 }) }, { quoted })
386
368
  await this.sendMsg(jid, msg.message, { messageId: msg.key.id })
387
369
  return msg
388
370
  }
@@ -390,112 +372,35 @@ class NexusHandler {
390
372
  // ─── GROUP STATUS ─────────────────────────────────────────────────────────
391
373
  async handleGroupStory(content, jid, quoted) {
392
374
  const s = content.groupStatus
393
- const mediaContent = await this.utils.generateWAMessageContent(s, {
394
- upload: this.upload,
395
- getUrlInfo: this.opts.getUrlInfo,
396
- logger: this.opts.logger
397
- })
398
- const msg = await this.genFromContent(jid, {
399
- groupStatusMessageV2: proto.Message.FutureProofMessage.create({ message: proto.Message.fromObject(mediaContent) })
400
- }, { userJid: jid })
401
- return await this.sendMsg(jid, msg.message, {
402
- messageId: msg.key.id,
403
- additionalNodes: [{ tag: 'meta', attrs: { is_group_status: 'true' }, content: undefined }]
404
- })
375
+ const mediaContent = await this.utils.generateWAMessageContent(s, { upload: this.upload, getUrlInfo: this.opts.getUrlInfo, logger: this.opts.logger })
376
+ const msg = await this.genFromContent(jid, { groupStatusMessageV2: proto.Message.FutureProofMessage.create({ message: proto.Message.fromObject(mediaContent) }) }, { userJid: jid })
377
+ return await this.sendMsg(jid, msg.message, { messageId: msg.key.id, additionalNodes: [{ tag: 'meta', attrs: { is_group_status: 'true' }, content: undefined }] })
405
378
  }
406
379
 
407
380
  // ─── CAROUSEL ─────────────────────────────────────────────────────────────
408
381
  async handleCarousel(content, jid, quoted) {
409
382
  const c = content.carouselMessage || content.carousel || {}
410
383
  const cards = await Promise.all((c.cards || []).map(card => this.buildCard(card)))
411
- const msg = await this.genFromContent(jid, {
412
- viewOnceMessage: {
413
- message: {
414
- interactiveMessage: proto.Message.InteractiveMessage.create({
415
- body: proto.Message.InteractiveMessage.Body.create({ text: c.caption || c.body || '' }),
416
- footer: proto.Message.InteractiveMessage.Footer.create({ text: c.footer || '' }),
417
- carouselMessage: proto.Message.InteractiveMessage.CarouselMessage.create({ cards, messageVersion: 1 })
418
- })
419
- }
420
- }
421
- }, { quoted })
384
+ const msg = await this.genFromContent(jid, { viewOnceMessage: { message: { interactiveMessage: proto.Message.InteractiveMessage.create({ body: proto.Message.InteractiveMessage.Body.create({ text: c.caption || c.body || '' }), footer: proto.Message.InteractiveMessage.Footer.create({ text: c.footer || '' }), carouselMessage: proto.Message.InteractiveMessage.CarouselMessage.create({ cards, messageVersion: 1 }) }) } } }, { quoted })
422
385
  await this.sendMsg(jid, msg.message, { messageId: msg.key.id })
423
386
  return msg
424
387
  }
425
388
 
426
389
  async buildCard(card) {
427
- const buttons = (card.buttons || []).map(btn => ({
428
- name: btn.name,
429
- buttonParamsJson: JSON.stringify(btn.params || {})
430
- }))
390
+ const buttons = (card.buttons || []).map(btn => ({ name: btn.name, buttonParamsJson: JSON.stringify(btn.params || {}) }))
431
391
  if (card.productTitle) {
432
392
  const imgMedia = await this.prepMedia({ url: card.imageUrl }, 'image')
433
- return {
434
- header: proto.Message.InteractiveMessage.Header.create({
435
- title: card.headerTitle || '',
436
- subtitle: card.headerSubtitle || '',
437
- hasMediaAttachment: false,
438
- productMessage: proto.Message.ProductMessage.create({
439
- product: proto.Message.ProductMessage.ProductSnapshot.create({
440
- productImage: imgMedia?.imageMessage,
441
- productId: card.productId || '123456',
442
- title: card.productTitle,
443
- description: card.productDescription || '',
444
- currencyCode: card.currencyCode || 'IDR',
445
- priceAmount1000: card.priceAmount1000 || '100000',
446
- retailerId: card.retailerId || 'Retailer',
447
- url: card.url || '',
448
- productImageCount: 1
449
- }),
450
- businessOwnerJid: card.businessOwnerJid || '0@s.whatsapp.net'
451
- })
452
- }),
453
- body: proto.Message.InteractiveMessage.Body.create({ text: card.bodyText || '' }),
454
- footer: proto.Message.InteractiveMessage.Footer.create({ text: card.footerText || '' }),
455
- nativeFlowMessage: proto.Message.InteractiveMessage.NativeFlowMessage.create({ buttons })
456
- }
393
+ return { header: proto.Message.InteractiveMessage.Header.create({ title: card.headerTitle || '', subtitle: card.headerSubtitle || '', hasMediaAttachment: false, productMessage: proto.Message.ProductMessage.create({ product: proto.Message.ProductMessage.ProductSnapshot.create({ productImage: imgMedia?.imageMessage, productId: card.productId || '123456', title: card.productTitle, description: card.productDescription || '', currencyCode: card.currencyCode || 'IDR', priceAmount1000: card.priceAmount1000 || '100000', retailerId: card.retailerId || 'Retailer', url: card.url || '', productImageCount: 1 }), businessOwnerJid: card.businessOwnerJid || '0@s.whatsapp.net' }) }), body: proto.Message.InteractiveMessage.Body.create({ text: card.bodyText || '' }), footer: proto.Message.InteractiveMessage.Footer.create({ text: card.footerText || '' }), nativeFlowMessage: proto.Message.InteractiveMessage.NativeFlowMessage.create({ buttons }) }
457
394
  }
458
395
  const imgMedia = card.imageUrl ? await this.prepMedia({ url: card.imageUrl }, 'image') : {}
459
- return {
460
- header: proto.Message.InteractiveMessage.Header.create({
461
- title: card.headerTitle || '',
462
- subtitle: card.headerSubtitle || '',
463
- hasMediaAttachment: !!card.imageUrl,
464
- ...imgMedia
465
- }),
466
- body: proto.Message.InteractiveMessage.Body.create({ text: card.bodyText || '' }),
467
- footer: proto.Message.InteractiveMessage.Footer.create({ text: card.footerText || '' }),
468
- nativeFlowMessage: proto.Message.InteractiveMessage.NativeFlowMessage.create({ buttons })
469
- }
396
+ return { header: proto.Message.InteractiveMessage.Header.create({ title: card.headerTitle || '', subtitle: card.headerSubtitle || '', hasMediaAttachment: !!card.imageUrl, ...imgMedia }), body: proto.Message.InteractiveMessage.Body.create({ text: card.bodyText || '' }), footer: proto.Message.InteractiveMessage.Footer.create({ text: card.footerText || '' }), nativeFlowMessage: proto.Message.InteractiveMessage.NativeFlowMessage.create({ buttons }) }
470
397
  }
471
398
 
472
399
  // ─── CAROUSEL PROTO ───────────────────────────────────────────────────────
473
400
  async handleCarouselProto(content, jid, quoted) {
474
401
  const c = content.carouselProto
475
- const cards = await Promise.all((c.cards || []).map(async card => ({
476
- header: proto.Message.InteractiveMessage.Header.create({
477
- title: card.title?.substring(0, 60) || '',
478
- subtitle: card.subtitle || '',
479
- hasMediaAttachment: false
480
- }),
481
- body: proto.Message.InteractiveMessage.Body.create({ text: card.bodyText || '' }),
482
- footer: proto.Message.InteractiveMessage.Footer.create({ text: card.footerText || '' }),
483
- nativeFlowMessage: proto.Message.InteractiveMessage.NativeFlowMessage.create({
484
- buttons: (card.buttons || []).map(btn => ({ name: btn.name, buttonParamsJson: JSON.stringify(btn.params || {}) }))
485
- })
486
- })))
487
- const msg = await this.genFromContent(jid, {
488
- viewOnceMessage: {
489
- message: {
490
- messageContextInfo: proto.MessageContextInfo.create({ deviceListMetadata: {}, deviceListMetadataVersion: 2 }),
491
- interactiveMessage: proto.Message.InteractiveMessage.create({
492
- body: proto.Message.InteractiveMessage.Body.create({ text: c.body || '' }),
493
- footer: proto.Message.InteractiveMessage.Footer.create({ text: c.footer || '' }),
494
- carouselMessage: proto.Message.InteractiveMessage.CarouselMessage.create({ cards, messageVersion: 1 })
495
- })
496
- }
497
- }
498
- }, { quoted })
402
+ const cards = await Promise.all((c.cards || []).map(async card => ({ header: proto.Message.InteractiveMessage.Header.create({ title: card.title?.substring(0, 60) || '', subtitle: card.subtitle || '', hasMediaAttachment: false }), body: proto.Message.InteractiveMessage.Body.create({ text: card.bodyText || '' }), footer: proto.Message.InteractiveMessage.Footer.create({ text: card.footerText || '' }), nativeFlowMessage: proto.Message.InteractiveMessage.NativeFlowMessage.create({ buttons: (card.buttons || []).map(btn => ({ name: btn.name, buttonParamsJson: JSON.stringify(btn.params || {}) })) }) })))
403
+ const msg = await this.genFromContent(jid, { viewOnceMessage: { message: { messageContextInfo: proto.MessageContextInfo.create({ deviceListMetadata: {}, deviceListMetadataVersion: 2 }), interactiveMessage: proto.Message.InteractiveMessage.create({ body: proto.Message.InteractiveMessage.Body.create({ text: c.body || '' }), footer: proto.Message.InteractiveMessage.Footer.create({ text: c.footer || '' }), carouselMessage: proto.Message.InteractiveMessage.CarouselMessage.create({ cards, messageVersion: 1 }) }) } } }, { quoted })
499
404
  await this.sendMsg(jid, msg.message, { messageId: msg.key.id })
500
405
  return msg
501
406
  }
@@ -503,21 +408,10 @@ class NexusHandler {
503
408
  // ─── STICKER PACK ─────────────────────────────────────────────────────────
504
409
  async handleStickerPack(content, jid, quoted) {
505
410
  const stickerPack = content.stickerPack
506
- const result = await this.utils.prepareStickerPackMessage(stickerPack, {
507
- logger: this.opts?.logger,
508
- upload: this.upload,
509
- mediaCache: this.opts?.mediaCache,
510
- options: this.opts,
511
- mediaUploadTimeoutMs: this.opts?.mediaUploadTimeoutMs
512
- })
411
+ const result = await this.utils.prepareStickerPackMessage(stickerPack, { logger: this.opts?.logger, upload: this.upload, mediaCache: this.opts?.mediaCache, options: this.opts, mediaUploadTimeoutMs: this.opts?.mediaUploadTimeoutMs })
513
412
  if (result.isBatched) {
514
413
  const sent = []
515
- for (let i = 0; i < result.stickerPackMessage.length; i++) {
516
- const msg = await this.genFromContent(jid, { stickerPackMessage: result.stickerPackMessage[i] }, { quoted })
517
- await this.sendMsg(jid, msg.message, { messageId: msg.key.id })
518
- sent.push(msg)
519
- if (i < result.stickerPackMessage.length - 1) await this.delay(2000)
520
- }
414
+ for (let i = 0; i < result.stickerPackMessage.length; i++) { const msg = await this.genFromContent(jid, { stickerPackMessage: result.stickerPackMessage[i] }, { quoted }); await this.sendMsg(jid, msg.message, { messageId: msg.key.id }); sent.push(msg); if (i < result.stickerPackMessage.length - 1) await this.delay(2000) }
521
415
  return sent[sent.length - 1]
522
416
  }
523
417
  const msg = await this.genFromContent(jid, { stickerPackMessage: result.stickerPackMessage }, { quoted })