@nexustechpro/baileys 2.0.5 → 2.0.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,3 +1,4 @@
1
+ import { proto } from '../../WAProto/index.js'
1
2
  import axios from 'axios'
2
3
  import crypto from 'crypto'
3
4
 
@@ -27,23 +28,24 @@ class NexusHandler {
27
28
  // ─── TYPE DETECTION ───────────────────────────────────────────────────────
28
29
  detectType(content) {
29
30
  if (content.carouselMessage || content.carousel) return 'CAROUSEL'
30
- const types = {
31
+ if (content.carouselProto) return 'CAROUSEL_PROTO'
32
+ const map = {
31
33
  requestPaymentMessage: 'PAYMENT', productMessage: 'PRODUCT',
32
34
  interactiveMessage: 'INTERACTIVE', interactive: 'INTERACTIVE',
33
- albumMessage: 'ALBUM', eventMessage: 'EVENT', pollResultMessage: 'POLL_RESULT',
34
- statusMentionMessage: 'STATUS_MENTION', orderMessage: 'ORDER',
35
- stickerPack: 'STICKER_PACK', groupStatus: 'GROUP_STATUS', carouselProto: 'CAROUSEL_PROTO'
35
+ albumMessage: 'ALBUM', eventMessage: 'EVENT',
36
+ pollResultMessage: 'POLL_RESULT', statusMentionMessage: 'STATUS_MENTION',
37
+ orderMessage: 'ORDER', stickerPack: 'STICKER_PACK', groupStatus: 'GROUP_STATUS'
36
38
  }
37
- return types[Object.keys(types).find(k => content[k])] || null
39
+ return map[Object.keys(map).find(k => content[k])] || null
38
40
  }
39
41
 
40
42
  // ─── UNIFIED PROCESSOR ────────────────────────────────────────────────────
41
43
  async processMessage(content, jid, quoted) {
42
- const messageType = this.detectType(content)
43
- if (!messageType) throw new Error('Unknown message type')
44
- const handler = this.handlers[messageType]
45
- if (!handler) throw new Error(`No handler for: ${messageType}`)
46
- return messageType === 'STICKER_PACK' ? await handler(content.stickerPack, jid, quoted) : await handler(content, jid, quoted)
44
+ const type = this.detectType(content)
45
+ if (!type) throw new Error('Unknown message type')
46
+ const handler = this.handlers[type]
47
+ if (!handler) throw new Error(`No handler for: ${type}`)
48
+ return await handler(content, jid, quoted)
47
49
  }
48
50
 
49
51
  // ─── HELPERS ──────────────────────────────────────────────────────────────
@@ -53,7 +55,7 @@ class NexusHandler {
53
55
  return await this.utils.prepareWAMessageMedia(payload, { upload: this.upload })
54
56
  }
55
57
 
56
- async genContent(jid, content, opts = {}) {
58
+ async genMsg(jid, content, opts = {}) {
57
59
  return await this.utils.generateWAMessage(jid, content, {
58
60
  ...opts,
59
61
  upload: this.upload,
@@ -63,37 +65,72 @@ class NexusHandler {
63
65
  })
64
66
  }
65
67
 
68
+ async genFromContent(jid, content, opts = {}) {
69
+ return await this.utils.generateWAMessageFromContent(jid, content, {
70
+ ...opts,
71
+ userJid: opts.userJid || this.user?.id
72
+ })
73
+ }
74
+
66
75
  async sendMsg(jid, message, opts = {}) {
67
76
  return await this.relay(jid, message, opts)
68
77
  }
69
78
 
70
79
  buildCtx(quoted, sender) {
71
- return { stanzaId: quoted?.key?.id, participant: quoted?.key?.participant || sender, quotedMessage: quoted?.message }
80
+ return {
81
+ stanzaId: quoted?.key?.id,
82
+ participant: quoted?.key?.participant || sender,
83
+ quotedMessage: quoted?.message
84
+ }
72
85
  }
73
86
 
74
87
  buildFullCtx(ctx, adReply) {
88
+ const allowed = ['title', 'body', 'mediaType', 'thumbnailUrl', 'mediaUrl', 'sourceUrl', 'showAdAttribution', 'renderLargerThumbnail', 'thumbnail']
75
89
  const final = ctx ? { mentionedJid: ctx.mentionedJid || [], forwardingScore: ctx.forwardingScore || 0, isForwarded: ctx.isForwarded || false, ...ctx } : {}
76
- if (adReply) final.externalAdReply = { title: adReply.title || '', body: adReply.body || '', mediaType: adReply.mediaType || 1, thumbnailUrl: adReply.thumbnailUrl || '', mediaUrl: adReply.mediaUrl || '', sourceUrl: adReply.sourceUrl || '', showAdAttribution: adReply.showAdAttribution || false, renderLargerThumbnail: adReply.renderLargerThumbnail || false, ...adReply }
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
+ }
77
95
  return final
78
96
  }
79
97
 
80
- genJid() { return this.utils.generateMessageIDV2().split('@')[0] + '@s.whatsapp.net' }
81
- parseTime(val, def) { return typeof val === 'string' ? parseInt(val) : val || def }
82
- delay(ms) { return new Promise(resolve => setTimeout(resolve, ms)) }
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
+
103
+ parseTime(val, def) { return typeof val === 'string' ? parseInt(val) : (val || def) }
104
+ delay(ms) { return new Promise(r => setTimeout(r, ms)) }
105
+
106
+ async downloadBuffer(urlOrBuffer) {
107
+ 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
+ }
114
+ return null
115
+ }
83
116
 
84
117
  // ─── PAYMENT ──────────────────────────────────────────────────────────────
85
118
  async handlePayment(content, jid, quoted) {
86
119
  const d = content.requestPaymentMessage
120
+ const ctx = this.buildCtx(quoted, content.sender)
87
121
  const notes = d.sticker?.stickerMessage
88
- ? { stickerMessage: { ...d.sticker.stickerMessage, contextInfo: this.buildCtx(quoted, content.sender) } }
89
- : d.note ? { extendedTextMessage: { text: d.note, contextInfo: this.buildCtx(quoted, content.sender) } } : {}
122
+ ? { stickerMessage: { ...d.sticker.stickerMessage, contextInfo: ctx } }
123
+ : d.note ? { extendedTextMessage: { text: d.note, contextInfo: ctx } } : {}
90
124
  const targetJid = jid || content.jid
91
- const msg = await this.genContent(targetJid, {
92
- requestPaymentMessage: {
93
- expiryTimestamp: d.expiry || 0, amount1000: d.amount || 0,
94
- currencyCodeIso4217: d.currency || 'IDR', requestFrom: d.from || '0@s.whatsapp.net',
95
- noteMessage: notes, background: d.background ?? { id: 'DEFAULT', placeholderArgb: 0xfff0f0f0 }
96
- }
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
+ })
97
134
  }, { quoted })
98
135
  await this.sendMsg(targetJid, msg.message, { messageId: msg.key.id })
99
136
  return msg
@@ -104,13 +141,36 @@ class NexusHandler {
104
141
  const p = content.productMessage || {}
105
142
  let prodImg = null
106
143
  if (p.thumbnail) {
107
- const imgContent = Buffer.isBuffer(p.thumbnail) ? { image: p.thumbnail } : { image: { url: p.thumbnail.url || p.thumbnail } }
108
- const res = await this.utils.generateWAMessageContent(imgContent, { upload: this.upload })
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 })
109
146
  prodImg = res?.imageMessage || res?.message?.imageMessage
110
147
  }
111
- const product = { 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 }) }
112
- const msg = await this.genContent(jid, {
113
- viewOnceMessage: { message: { interactiveMessage: { body: { text: p.body || '' }, footer: { text: p.footer || '' }, header: { title: p.title, hasMediaAttachment: !!prodImg, productMessage: { product, businessOwnerJid: '0@s.whatsapp.net' } }, nativeFlowMessage: { buttons: p.buttons || [] } } } }
148
+ const product = proto.Message.ProductMessage.Product.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
+ }
114
174
  }, { quoted })
115
175
  await this.sendMsg(jid, msg.message, { messageId: msg.key.id })
116
176
  return msg
@@ -130,26 +190,29 @@ class NexusHandler {
130
190
  if (i.mimetype) media.documentMessage.mimetype = i.mimetype
131
191
  }
132
192
  const bodyText = i.body?.text || i.title || ''
133
- const footerText = i.footer?.text || i.footer || ''
134
- const interactive = { body: { text: bodyText }, footer: { text: footerText } }
193
+ const footerText = i.footer?.text || (typeof i.footer === 'string' ? i.footer : '') || ''
194
+ const headerTitle = typeof i.header === 'string' ? i.header : i.header?.title || ''
195
+ let nativeFlow = null
135
196
  if (i.buttons?.length || i.nativeFlowMessage) {
136
- const nativeFlow = i.nativeFlowMessage || {}
137
- interactive.nativeFlowMessage = { buttons: i.buttons || nativeFlow.buttons || [], messageParamsJson: nativeFlow.messageParamsJson || '' }
138
- }
139
- if (media) {
140
- const headerMedia = {}
141
- if (media.imageMessage) headerMedia.imageMessage = media.imageMessage
142
- if (media.videoMessage) headerMedia.videoMessage = media.videoMessage
143
- if (media.documentMessage) headerMedia.documentMessage = media.documentMessage
144
- const headerTitle = typeof i.header === 'string' ? i.header : i.header?.title || ''
145
- interactive.header = { title: headerTitle, hasMediaAttachment: true, ...headerMedia }
146
- } else {
147
- const headerTitle = typeof i.header === 'string' ? i.header : i.header?.title || ''
148
- interactive.header = { title: headerTitle, hasMediaAttachment: false }
197
+ const nfm = i.nativeFlowMessage || {}
198
+ nativeFlow = proto.Message.InteractiveMessage.NativeFlowMessage.create({
199
+ buttons: i.buttons || nfm.buttons || [],
200
+ messageParamsJson: nfm.messageParamsJson || ''
201
+ })
149
202
  }
203
+ const headerMedia = {}
204
+ if (media?.imageMessage) headerMedia.imageMessage = media.imageMessage
205
+ if (media?.videoMessage) headerMedia.videoMessage = media.videoMessage
206
+ 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
+ })
150
213
  const ctx = this.buildFullCtx(i.contextInfo, i.externalAdReply)
151
214
  if (Object.keys(ctx).length) interactive.contextInfo = ctx
152
- const msg = await this.genContent(jid, { interactiveMessage: interactive }, { quoted })
215
+ const msg = await this.genFromContent(jid, { interactiveMessage: interactive }, { quoted })
153
216
  await this.sendMsg(jid, msg.message, { messageId: msg.key.id })
154
217
  return msg
155
218
  }
@@ -158,17 +221,51 @@ class NexusHandler {
158
221
  async handleAlbum(content, jid, quoted) {
159
222
  const arr = Array.isArray(content.albumMessage) ? content.albumMessage : []
160
223
  if (!arr.length) throw new Error('albumMessage must contain media items')
161
- const album = await this.genContent(jid, {
162
- messageContextInfo: { messageSecret: crypto.randomBytes(32) },
163
- albumMessage: { expectedImageCount: arr.filter(a => a.image).length, expectedVideoCount: arr.filter(a => a.video).length }
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
+ })
164
230
  }, { userJid: this.genJid(), quoted })
165
231
  await this.sendMsg(jid, album.message, { messageId: album.key.id })
166
232
  for (const item of arr) {
167
- const img = await this.utils.generateWAMessage(jid, item, { upload: this.upload })
168
- img.message.messageContextInfo = { messageSecret: crypto.randomBytes(32), messageAssociation: { 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: { businessOwnerJid: jid }, dataSharingContext: { showMmDisclosure: true } }
169
- img.message.forwardedNewsletterMessageInfo = { newsletterJid: '0@newsletter', serverMessageId: 1, newsletterName: 'WhatsApp', contentType: 'UPDATE_CARD', timestamp: new Date().toISOString(), senderName: 'Nexus', priority: 'high', status: 'sent' }
170
- img.message.disappearingMode = { initiator: 3, trigger: 4, initiatorDeviceJid: jid, initiatedByExternalService: true, initiatedByUserDevice: true, initiatedBySystem: true, initiatedByServer: true, initiatedByAdmin: true, initiatedByUser: true, initiatedByApp: true, initiatedByBot: true, initiatedByMe: true }
171
- await this.sendMsg(jid, img.message, { messageId: img.key.id, quoted: { key: { ...album.key, fromMe: true, participant: this.genJid() }, message: album.message } })
233
+ 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.BusinessMessageForwardInfo.create({ businessOwnerJid: jid }),
246
+ dataSharingContext: proto.DataSharingContext.create({ showMmDisclosure: true })
247
+ })
248
+ img.message.forwardedNewsletterMessageInfo = proto.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
+ })
172
269
  }
173
270
  return album
174
271
  }
@@ -176,8 +273,37 @@ class NexusHandler {
176
273
  // ─── EVENT ────────────────────────────────────────────────────────────────
177
274
  async handleEvent(content, jid, quoted) {
178
275
  const e = content.eventMessage
179
- const msg = await this.genContent(jid, {
180
- viewOnceMessage: { message: { messageContextInfo: { 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: { contextInfo: { mentionedJid: [jid], participant: jid, remoteJid: 'status@broadcast', forwardedNewsletterMessageInfo: { 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 } } }
276
+ const msg = await this.genFromContent(jid, {
277
+ viewOnceMessage: {
278
+ message: {
279
+ messageContextInfo: proto.MessageContextInfo.create({
280
+ deviceListMetadata: {},
281
+ deviceListMetadataVersion: 2,
282
+ messageSecret: crypto.randomBytes(32),
283
+ supportPayload: JSON.stringify({ version: 2, is_ai_message: true, should_show_system_message: true, ticket_id: crypto.randomBytes(16).toString('hex') })
284
+ }),
285
+ eventMessage: proto.Message.EventMessage.create({
286
+ contextInfo: proto.ContextInfo.create({
287
+ mentionedJid: [jid],
288
+ participant: jid,
289
+ remoteJid: 'status@broadcast',
290
+ forwardedNewsletterMessageInfo: proto.ForwardedNewsletterMessageInfo.create({
291
+ newsletterName: 'Nexus Events',
292
+ newsletterJid: '120363422827915475@newsletter',
293
+ serverMessageId: 1
294
+ })
295
+ }),
296
+ isCanceled: e.isCanceled || false,
297
+ name: e.name,
298
+ description: e.description,
299
+ location: e.location || { degreesLatitude: 0, degreesLongitude: 0, name: 'Location' },
300
+ joinLink: e.joinLink || '',
301
+ startTime: this.parseTime(e.startTime, Date.now()),
302
+ endTime: this.parseTime(e.endTime, Date.now() + 3600000),
303
+ extraGuestsAllowed: e.extraGuestsAllowed !== false
304
+ })
305
+ }
306
+ }
181
307
  }, { quoted })
182
308
  await this.sendMsg(jid, msg.message, { messageId: msg.key.id })
183
309
  return msg
@@ -186,8 +312,24 @@ class NexusHandler {
186
312
  // ─── POLL RESULT ──────────────────────────────────────────────────────────
187
313
  async handlePollResult(content, jid, quoted) {
188
314
  const p = content.pollResultMessage
189
- const msg = await this.genContent(jid, {
190
- pollResultSnapshotMessage: { name: p.name, pollVotes: (p.pollVotes || []).map(v => ({ optionName: v.optionName, optionVoteCount: typeof v.optionVoteCount === 'number' ? v.optionVoteCount.toString() : v.optionVoteCount })), contextInfo: { isForwarded: true, forwardingScore: 1, forwardedNewsletterMessageInfo: { newsletterName: p.newsletter?.newsletterName || 'Newsletter', newsletterJid: p.newsletter?.newsletterJid || '120363399602691477@newsletter', serverMessageId: 1000, contentType: 'UPDATE' } } }
315
+ const msg = await this.genFromContent(jid, {
316
+ pollResultSnapshotMessage: proto.Message.PollResultSnapshotMessage.create({
317
+ name: p.name,
318
+ pollVotes: (p.pollVotes || []).map(v => proto.Message.PollResultSnapshotMessage.PollVote.create({
319
+ optionName: v.optionName,
320
+ optionVoteCount: typeof v.optionVoteCount === 'number' ? v.optionVoteCount.toString() : v.optionVoteCount
321
+ })),
322
+ contextInfo: proto.ContextInfo.create({
323
+ isForwarded: true,
324
+ forwardingScore: 1,
325
+ forwardedNewsletterMessageInfo: proto.ForwardedNewsletterMessageInfo.create({
326
+ newsletterName: p.newsletter?.newsletterName || 'Newsletter',
327
+ newsletterJid: p.newsletter?.newsletterJid || '120363399602691477@newsletter',
328
+ serverMessageId: 1000,
329
+ contentType: 'UPDATE'
330
+ })
331
+ })
332
+ })
191
333
  }, { userJid: this.genJid(), quoted })
192
334
  await this.sendMsg(jid, msg.message, { messageId: msg.key.id })
193
335
  return msg
@@ -196,30 +338,48 @@ class NexusHandler {
196
338
  // ─── STATUS MENTION ───────────────────────────────────────────────────────
197
339
  async handleStMention(content, jid, quoted) {
198
340
  const d = content.statusMentionMessage
199
- const media = await this.prepMedia(d.image || d.video, d.image ? 'image' : 'video')
200
- await this.relay('status@broadcast', { ...media }, {
341
+ const mediaType = d.image ? 'image' : 'video'
342
+ const media = await this.prepMedia(d.image || d.video, mediaType)
343
+ const statusMsg = await this.relay('status@broadcast', { ...media }, {
201
344
  statusJidList: [d.mentions, this.user?.id].filter(Boolean),
202
- additionalNodes: [{ tag: 'meta', attrs: {}, content: [{ tag: 'mentioned_users', attrs: {}, content: [{ tag: 'to', attrs: { jid: d.mentions }, content: undefined }] }] }]
345
+ additionalNodes: [{
346
+ tag: 'meta', attrs: {},
347
+ content: [{ tag: 'mentioned_users', attrs: {}, content: [{ tag: 'to', attrs: { jid: d.mentions }, content: undefined }] }]
348
+ }]
203
349
  })
204
- const xontols = await this.genContent(jid, {
205
- statusMentionMessage: { message: { protocolMessage: { messageId: d.mentions, type: 'STATUS_MENTION_MESSAGE' } } }
350
+ const mentionMsg = await this.genFromContent(jid, {
351
+ statusMentionMessage: proto.Message.StatusMentionMessage.create({
352
+ message: {
353
+ protocolMessage: proto.Message.ProtocolMessage.create({
354
+ messageId: statusMsg?.key?.id || d.mentions,
355
+ type: proto.Message.ProtocolMessage.Type.STATUS_MENTION_MESSAGE
356
+ })
357
+ }
358
+ })
206
359
  }, { additionalNodes: [{ tag: 'meta', attrs: { is_status_mention: 'true' }, content: undefined }] })
207
- await this.sendMsg(jid, xontols.message, { messageId: xontols.key.id })
208
- return xontols
360
+ await this.sendMsg(jid, mentionMsg.message, { messageId: mentionMsg.key.id })
361
+ return mentionMsg
209
362
  }
210
363
 
211
364
  // ─── ORDER ────────────────────────────────────────────────────────────────
212
365
  async handleOrderMessage(content, jid, quoted) {
213
366
  const o = content.orderMessage
214
- let thumb = null
215
- if (o.thumbnail) {
216
- if (Buffer.isBuffer(o.thumbnail)) thumb = o.thumbnail
217
- else if (typeof o.thumbnail === 'string') {
218
- try { const res = await axios.get(o.thumbnail, { responseType: 'arraybuffer' }); thumb = Buffer.from(res.data) } catch (err) { this.opts.logger?.warn('Failed to download order thumbnail') }
219
- }
220
- }
221
- const msg = await this.genContent(jid, {
222
- orderMessage: { orderId: '7NEXUS25022008', thumbnail: thumb, itemCount: o.itemCount || 0, status: 'ACCEPTED', surface: 'CATALOG', message: o.message, orderTitle: o.orderTitle, sellerJid: '0@whatsapp.net', token: 'NEXUS_EXAMPLE_TOKEN', totalAmount1000: o.totalAmount1000 || 0, totalCurrencyCode: o.totalCurrencyCode || 'IDR', messageVersion: 2 }
367
+ const thumb = await this.downloadBuffer(o.thumbnail)
368
+ const msg = await this.genFromContent(jid, {
369
+ orderMessage: proto.Message.OrderMessage.create({
370
+ orderId: '7NEXUS25022008',
371
+ thumbnail: thumb,
372
+ itemCount: o.itemCount || 0,
373
+ status: proto.Message.OrderMessage.OrderStatus.ACCEPTED,
374
+ surface: proto.Message.OrderMessage.OrderSurface.CATALOG,
375
+ message: o.message,
376
+ orderTitle: o.orderTitle,
377
+ sellerJid: '0@whatsapp.net',
378
+ token: 'NEXUS_EXAMPLE_TOKEN',
379
+ totalAmount1000: o.totalAmount1000 || 0,
380
+ totalCurrencyCode: o.totalCurrencyCode || 'IDR',
381
+ messageVersion: 2
382
+ })
223
383
  }, { quoted })
224
384
  await this.sendMsg(jid, msg.message, { messageId: msg.key.id })
225
385
  return msg
@@ -228,62 +388,137 @@ class NexusHandler {
228
388
  // ─── GROUP STATUS ─────────────────────────────────────────────────────────
229
389
  async handleGroupStory(content, jid, quoted) {
230
390
  const s = content.groupStatus
231
- const mediaContent = await this.utils.generateWAMessageContent(s, { upload: this.upload, getUrlInfo: this.opts.getUrlInfo, logger: this.opts.logger })
232
- const msg = await this.utils.generateWAMessageFromContent(jid, { groupStatusMessageV2: { message: mediaContent } }, { userJid: jid })
233
- return await this.sendMsg(jid, msg.message, { messageId: msg.key.id, additionalNodes: [{ tag: 'meta', attrs: { is_group_status: 'true' }, content: undefined }] })
391
+ const mediaContent = await this.utils.generateWAMessageContent(s, {
392
+ upload: this.upload,
393
+ getUrlInfo: this.opts.getUrlInfo,
394
+ logger: this.opts.logger
395
+ })
396
+ const msg = await this.genFromContent(jid, {
397
+ groupStatusMessageV2: proto.Message.GroupStatusMessageV2.create({ message: mediaContent })
398
+ }, { userJid: jid })
399
+ return await this.sendMsg(jid, msg.message, {
400
+ messageId: msg.key.id,
401
+ additionalNodes: [{ tag: 'meta', attrs: { is_group_status: 'true' }, content: undefined }]
402
+ })
234
403
  }
235
404
 
236
405
  // ─── CAROUSEL ─────────────────────────────────────────────────────────────
237
406
  async handleCarousel(content, jid, quoted) {
238
407
  const c = content.carouselMessage || content.carousel || {}
239
408
  const cards = await Promise.all((c.cards || []).map(card => this.buildCard(card)))
240
- const msg = await this.genContent(jid, {
241
- viewOnceMessage: { message: { interactiveMessage: { body: { text: c.caption || c.body || '' }, footer: { text: c.footer || '' }, carouselMessage: { cards, messageVersion: 1 } } } }
409
+ const msg = await this.genFromContent(jid, {
410
+ viewOnceMessage: {
411
+ message: {
412
+ interactiveMessage: proto.Message.InteractiveMessage.create({
413
+ body: proto.Message.InteractiveMessage.Body.create({ text: c.caption || c.body || '' }),
414
+ footer: proto.Message.InteractiveMessage.Footer.create({ text: c.footer || '' }),
415
+ carouselMessage: proto.Message.InteractiveMessage.CarouselMessage.create({ cards, messageVersion: 1 })
416
+ })
417
+ }
418
+ }
242
419
  }, { quoted })
243
420
  await this.sendMsg(jid, msg.message, { messageId: msg.key.id })
244
421
  return msg
245
422
  }
246
423
 
247
424
  async buildCard(card) {
425
+ const buttons = (card.buttons || []).map(btn => ({
426
+ name: btn.name,
427
+ buttonParamsJson: JSON.stringify(btn.params || {})
428
+ }))
248
429
  if (card.productTitle) {
249
- return { header: { title: card.headerTitle || '', subtitle: card.headerSubtitle || '', productMessage: { product: { productImage: (await this.prepMedia({ url: card.imageUrl }, 'image')).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' }, hasMediaAttachment: false }, body: { text: card.bodyText || '' }, footer: { text: card.footerText || '' }, nativeFlowMessage: { buttons: (card.buttons || []).map(btn => ({ name: btn.name, buttonParamsJson: JSON.stringify(btn.params || {}) })) } }
430
+ const imgMedia = await this.prepMedia({ url: card.imageUrl }, 'image')
431
+ return {
432
+ header: proto.Message.InteractiveMessage.Header.create({
433
+ title: card.headerTitle || '',
434
+ subtitle: card.headerSubtitle || '',
435
+ hasMediaAttachment: false,
436
+ productMessage: proto.Message.ProductMessage.create({
437
+ product: proto.Message.ProductMessage.Product.create({
438
+ productImage: imgMedia?.imageMessage,
439
+ productId: card.productId || '123456',
440
+ title: card.productTitle,
441
+ description: card.productDescription || '',
442
+ currencyCode: card.currencyCode || 'IDR',
443
+ priceAmount1000: card.priceAmount1000 || '100000',
444
+ retailerId: card.retailerId || 'Retailer',
445
+ url: card.url || '',
446
+ productImageCount: 1
447
+ }),
448
+ businessOwnerJid: card.businessOwnerJid || '0@s.whatsapp.net'
449
+ })
450
+ }),
451
+ body: proto.Message.InteractiveMessage.Body.create({ text: card.bodyText || '' }),
452
+ footer: proto.Message.InteractiveMessage.Footer.create({ text: card.footerText || '' }),
453
+ nativeFlowMessage: proto.Message.InteractiveMessage.NativeFlowMessage.create({ buttons })
454
+ }
250
455
  }
251
456
  const imgMedia = card.imageUrl ? await this.prepMedia({ url: card.imageUrl }, 'image') : {}
252
- return { header: { title: card.headerTitle || '', subtitle: card.headerSubtitle || '', hasMediaAttachment: !!card.imageUrl, ...imgMedia }, body: { text: card.bodyText || '' }, footer: { text: card.footerText || '' }, nativeFlowMessage: { buttons: (card.buttons || []).map(btn => ({ name: btn.name, buttonParamsJson: JSON.stringify(btn.params || {}) })) } }
457
+ return {
458
+ header: proto.Message.InteractiveMessage.Header.create({
459
+ title: card.headerTitle || '',
460
+ subtitle: card.headerSubtitle || '',
461
+ hasMediaAttachment: !!card.imageUrl,
462
+ ...imgMedia
463
+ }),
464
+ body: proto.Message.InteractiveMessage.Body.create({ text: card.bodyText || '' }),
465
+ footer: proto.Message.InteractiveMessage.Footer.create({ text: card.footerText || '' }),
466
+ nativeFlowMessage: proto.Message.InteractiveMessage.NativeFlowMessage.create({ buttons })
467
+ }
253
468
  }
254
469
 
255
470
  // ─── CAROUSEL PROTO ───────────────────────────────────────────────────────
256
471
  async handleCarouselProto(content, jid, quoted) {
257
472
  const c = content.carouselProto
258
- const proto = this.utils.WAProto?.proto
259
- if (!proto) throw new Error('WAProto not available')
260
473
  const cards = await Promise.all((c.cards || []).map(async card => ({
261
- header: proto.Message.InteractiveMessage.Header.create({ title: card.title?.substring(0, 60) || '', subtitle: card.subtitle || '', hasMediaAttachment: false }),
474
+ header: proto.Message.InteractiveMessage.Header.create({
475
+ title: card.title?.substring(0, 60) || '',
476
+ subtitle: card.subtitle || '',
477
+ hasMediaAttachment: false
478
+ }),
262
479
  body: proto.Message.InteractiveMessage.Body.create({ text: card.bodyText || '' }),
263
480
  footer: proto.Message.InteractiveMessage.Footer.create({ text: card.footerText || '' }),
264
- nativeFlowMessage: proto.Message.InteractiveMessage.NativeFlowMessage.create({ buttons: (card.buttons || []).map(btn => ({ name: btn.name, buttonParamsJson: JSON.stringify(btn.params || {}) })) })
481
+ nativeFlowMessage: proto.Message.InteractiveMessage.NativeFlowMessage.create({
482
+ buttons: (card.buttons || []).map(btn => ({ name: btn.name, buttonParamsJson: JSON.stringify(btn.params || {}) }))
483
+ })
265
484
  })))
266
- const msg = await this.genContent(jid, {
267
- viewOnceMessage: { message: { messageContextInfo: { 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 }) }) } }
485
+ const msg = await this.genFromContent(jid, {
486
+ viewOnceMessage: {
487
+ message: {
488
+ messageContextInfo: proto.MessageContextInfo.create({ deviceListMetadata: {}, deviceListMetadataVersion: 2 }),
489
+ interactiveMessage: proto.Message.InteractiveMessage.create({
490
+ body: proto.Message.InteractiveMessage.Body.create({ text: c.body || '' }),
491
+ footer: proto.Message.InteractiveMessage.Footer.create({ text: c.footer || '' }),
492
+ carouselMessage: proto.Message.InteractiveMessage.CarouselMessage.create({ cards })
493
+ })
494
+ }
495
+ }
268
496
  }, { quoted })
269
497
  await this.sendMsg(jid, msg.message, { messageId: msg.key.id })
270
498
  return msg
271
499
  }
272
500
 
273
501
  // ─── STICKER PACK ─────────────────────────────────────────────────────────
274
- async handleStickerPack(stickerPack, jid, quoted) {
275
- 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 })
502
+ async handleStickerPack(content, jid, quoted) {
503
+ const stickerPack = content.stickerPack
504
+ const result = await this.utils.prepareStickerPackMessage(stickerPack, {
505
+ logger: this.opts?.logger,
506
+ upload: this.upload,
507
+ mediaCache: this.opts?.mediaCache,
508
+ options: this.opts,
509
+ mediaUploadTimeoutMs: this.opts?.mediaUploadTimeoutMs
510
+ })
276
511
  if (result.isBatched) {
277
512
  const sent = []
278
513
  for (let i = 0; i < result.stickerPackMessage.length; i++) {
279
- const msg = await this.genContent(jid, { stickerPackMessage: result.stickerPackMessage[i] }, { quoted })
514
+ const msg = await this.genFromContent(jid, { stickerPackMessage: result.stickerPackMessage[i] }, { quoted })
280
515
  await this.sendMsg(jid, msg.message, { messageId: msg.key.id })
281
516
  sent.push(msg)
282
517
  if (i < result.stickerPackMessage.length - 1) await this.delay(2000)
283
518
  }
284
519
  return sent[sent.length - 1]
285
520
  }
286
- const msg = await this.genContent(jid, { stickerPackMessage: result.stickerPackMessage }, { quoted })
521
+ const msg = await this.genFromContent(jid, { stickerPackMessage: result.stickerPackMessage }, { quoted })
287
522
  await this.sendMsg(jid, msg.message, { messageId: msg.key.id })
288
523
  return msg
289
524
  }