@nexustechpro/baileys 2.0.5 → 2.1.0

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.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
+ }
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.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
+ })
172
269
  }
173
270
  return album
174
271
  }
@@ -176,8 +273,33 @@ 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
+ 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
+ })
181
303
  }, { quoted })
182
304
  await this.sendMsg(jid, msg.message, { messageId: msg.key.id })
183
305
  return msg
@@ -186,8 +308,24 @@ class NexusHandler {
186
308
  // ─── POLL RESULT ──────────────────────────────────────────────────────────
187
309
  async handlePollResult(content, jid, quoted) {
188
310
  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' } } }
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
+ })
191
329
  }, { userJid: this.genJid(), quoted })
192
330
  await this.sendMsg(jid, msg.message, { messageId: msg.key.id })
193
331
  return msg
@@ -196,30 +334,54 @@ class NexusHandler {
196
334
  // ─── STATUS MENTION ───────────────────────────────────────────────────────
197
335
  async handleStMention(content, jid, quoted) {
198
336
  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 }, {
337
+ const mediaType = d.image ? 'image' : 'video'
338
+ const media = await this.prepMedia(d.image || d.video, mediaType)
339
+ const statusMsg = await this.relay('status@broadcast', { ...media }, {
201
340
  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 }] }] }]
341
+ additionalNodes: [{
342
+ tag: 'meta', attrs: {},
343
+ content: [{ tag: 'mentioned_users', attrs: {}, content: [{ tag: 'to', attrs: { jid: d.mentions }, content: undefined }] }]
344
+ }]
203
345
  })
204
- const xontols = await this.genContent(jid, {
205
- statusMentionMessage: { message: { protocolMessage: { messageId: d.mentions, type: 'STATUS_MENTION_MESSAGE' } } }
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
+ })
206
355
  }, { additionalNodes: [{ tag: 'meta', attrs: { is_status_mention: 'true' }, content: undefined }] })
207
- await this.sendMsg(jid, xontols.message, { messageId: xontols.key.id })
208
- return xontols
356
+ await this.sendMsg(jid, mentionMsg.message, { messageId: mentionMsg.key.id })
357
+ return mentionMsg
209
358
  }
210
359
 
211
360
  // ─── ORDER ────────────────────────────────────────────────────────────────
212
361
  async handleOrderMessage(content, jid, quoted) {
213
362
  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
- }
363
+ 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`
220
368
  }
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 }
369
+ 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
+ })
223
385
  }, { quoted })
224
386
  await this.sendMsg(jid, msg.message, { messageId: msg.key.id })
225
387
  return msg
@@ -228,62 +390,137 @@ class NexusHandler {
228
390
  // ─── GROUP STATUS ─────────────────────────────────────────────────────────
229
391
  async handleGroupStory(content, jid, quoted) {
230
392
  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 }] })
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
+ })
234
405
  }
235
406
 
236
407
  // ─── CAROUSEL ─────────────────────────────────────────────────────────────
237
408
  async handleCarousel(content, jid, quoted) {
238
409
  const c = content.carouselMessage || content.carousel || {}
239
410
  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 } } } }
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
+ }
242
421
  }, { quoted })
243
422
  await this.sendMsg(jid, msg.message, { messageId: msg.key.id })
244
423
  return msg
245
424
  }
246
425
 
247
426
  async buildCard(card) {
427
+ const buttons = (card.buttons || []).map(btn => ({
428
+ name: btn.name,
429
+ buttonParamsJson: JSON.stringify(btn.params || {})
430
+ }))
248
431
  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 || {}) })) } }
432
+ 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
+ }
250
457
  }
251
458
  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 || {}) })) } }
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
+ }
253
470
  }
254
471
 
255
472
  // ─── CAROUSEL PROTO ───────────────────────────────────────────────────────
256
473
  async handleCarouselProto(content, jid, quoted) {
257
474
  const c = content.carouselProto
258
- const proto = this.utils.WAProto?.proto
259
- if (!proto) throw new Error('WAProto not available')
260
475
  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 }),
476
+ header: proto.Message.InteractiveMessage.Header.create({
477
+ title: card.title?.substring(0, 60) || '',
478
+ subtitle: card.subtitle || '',
479
+ hasMediaAttachment: false
480
+ }),
262
481
  body: proto.Message.InteractiveMessage.Body.create({ text: card.bodyText || '' }),
263
482
  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 || {}) })) })
483
+ nativeFlowMessage: proto.Message.InteractiveMessage.NativeFlowMessage.create({
484
+ buttons: (card.buttons || []).map(btn => ({ name: btn.name, buttonParamsJson: JSON.stringify(btn.params || {}) }))
485
+ })
265
486
  })))
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 }) }) } }
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
+ }
268
498
  }, { quoted })
269
499
  await this.sendMsg(jid, msg.message, { messageId: msg.key.id })
270
500
  return msg
271
501
  }
272
502
 
273
503
  // ─── 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 })
504
+ async handleStickerPack(content, jid, quoted) {
505
+ 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
+ })
276
513
  if (result.isBatched) {
277
514
  const sent = []
278
515
  for (let i = 0; i < result.stickerPackMessage.length; i++) {
279
- const msg = await this.genContent(jid, { stickerPackMessage: result.stickerPackMessage[i] }, { quoted })
516
+ const msg = await this.genFromContent(jid, { stickerPackMessage: result.stickerPackMessage[i] }, { quoted })
280
517
  await this.sendMsg(jid, msg.message, { messageId: msg.key.id })
281
518
  sent.push(msg)
282
519
  if (i < result.stickerPackMessage.length - 1) await this.delay(2000)
283
520
  }
284
521
  return sent[sent.length - 1]
285
522
  }
286
- const msg = await this.genContent(jid, { stickerPackMessage: result.stickerPackMessage }, { quoted })
523
+ const msg = await this.genFromContent(jid, { stickerPackMessage: result.stickerPackMessage }, { quoted })
287
524
  await this.sendMsg(jid, msg.message, { messageId: msg.key.id })
288
525
  return msg
289
526
  }