@nexustechpro/baileys 2.0.2 → 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.
Files changed (108) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +924 -1299
  3. package/WAProto/index.js +22 -18
  4. package/lib/Defaults/baileys-version.json +6 -2
  5. package/lib/Defaults/index.js +173 -172
  6. package/lib/Signal/libsignal.js +395 -292
  7. package/lib/Signal/lid-mapping.js +264 -171
  8. package/lib/Socket/Client/index.js +2 -2
  9. package/lib/Socket/Client/types.js +10 -10
  10. package/lib/Socket/Client/websocket.js +45 -310
  11. package/lib/Socket/business.js +375 -375
  12. package/lib/Socket/chats.js +916 -963
  13. package/lib/Socket/communities.js +430 -430
  14. package/lib/Socket/groups.js +342 -342
  15. package/lib/Socket/index.js +21 -22
  16. package/lib/Socket/messages-recv.js +963 -743
  17. package/lib/Socket/messages-send.js +273 -321
  18. package/lib/Socket/mex.js +50 -50
  19. package/lib/Socket/newsletter.js +148 -148
  20. package/lib/Socket/nexus-handler.js +296 -247
  21. package/lib/Socket/registration.js +50 -33
  22. package/lib/Socket/socket.js +872 -1201
  23. package/lib/Store/index.js +5 -5
  24. package/lib/Store/make-cache-manager-store.js +81 -81
  25. package/lib/Store/make-in-memory-store.js +416 -416
  26. package/lib/Store/make-ordered-dictionary.js +81 -81
  27. package/lib/Store/object-repository.js +30 -30
  28. package/lib/Types/Auth.js +1 -1
  29. package/lib/Types/Bussines.js +1 -1
  30. package/lib/Types/Call.js +1 -1
  31. package/lib/Types/Chat.js +7 -7
  32. package/lib/Types/Contact.js +1 -1
  33. package/lib/Types/Events.js +1 -1
  34. package/lib/Types/GroupMetadata.js +1 -1
  35. package/lib/Types/Label.js +24 -24
  36. package/lib/Types/LabelAssociation.js +6 -6
  37. package/lib/Types/Message.js +10 -10
  38. package/lib/Types/Newsletter.js +37 -29
  39. package/lib/Types/Product.js +1 -1
  40. package/lib/Types/Signal.js +1 -1
  41. package/lib/Types/Socket.js +2 -2
  42. package/lib/Types/State.js +55 -12
  43. package/lib/Types/USync.js +1 -1
  44. package/lib/Types/index.js +25 -25
  45. package/lib/Utils/auth-utils.js +264 -256
  46. package/lib/Utils/baileys-event-stream.js +55 -55
  47. package/lib/Utils/browser-utils.js +27 -27
  48. package/lib/Utils/business.js +228 -230
  49. package/lib/Utils/chat-utils.js +726 -764
  50. package/lib/Utils/companion-reg-client-utils.js +34 -0
  51. package/lib/Utils/crypto.js +109 -135
  52. package/lib/Utils/decode-wa-message.js +342 -314
  53. package/lib/Utils/event-buffer.js +547 -547
  54. package/lib/Utils/generics.js +295 -297
  55. package/lib/Utils/history.js +91 -83
  56. package/lib/Utils/index.js +25 -20
  57. package/lib/Utils/key-store.js +17 -0
  58. package/lib/Utils/link-preview.js +107 -98
  59. package/lib/Utils/logger.js +2 -2
  60. package/lib/Utils/lt-hash.js +47 -47
  61. package/lib/Utils/make-mutex.js +39 -39
  62. package/lib/Utils/message-retry-manager.js +148 -148
  63. package/lib/Utils/messages-media.js +579 -535
  64. package/lib/Utils/messages.js +821 -706
  65. package/lib/Utils/noise-handler.js +255 -255
  66. package/lib/Utils/pre-key-manager.js +105 -105
  67. package/lib/Utils/process-message.js +430 -412
  68. package/lib/Utils/reporting-utils.js +155 -0
  69. package/lib/Utils/signal.js +191 -159
  70. package/lib/Utils/sync-action-utils.js +33 -0
  71. package/lib/Utils/tc-token-utils.js +162 -0
  72. package/lib/Utils/use-multi-file-auth-state.js +120 -120
  73. package/lib/Utils/validate-connection.js +194 -194
  74. package/lib/WABinary/constants.js +1306 -1300
  75. package/lib/WABinary/decode.js +237 -237
  76. package/lib/WABinary/encode.js +232 -232
  77. package/lib/WABinary/generic-utils.js +252 -211
  78. package/lib/WABinary/index.js +6 -5
  79. package/lib/WABinary/jid-utils.js +279 -95
  80. package/lib/WABinary/types.js +1 -1
  81. package/lib/WAM/BinaryInfo.js +9 -9
  82. package/lib/WAM/constants.js +22852 -22852
  83. package/lib/WAM/encode.js +149 -149
  84. package/lib/WAM/index.js +3 -3
  85. package/lib/WAUSync/Protocols/USyncContactProtocol.js +28 -28
  86. package/lib/WAUSync/Protocols/USyncDeviceProtocol.js +53 -53
  87. package/lib/WAUSync/Protocols/USyncDisappearingModeProtocol.js +26 -26
  88. package/lib/WAUSync/Protocols/USyncStatusProtocol.js +37 -37
  89. package/lib/WAUSync/Protocols/UsyncBotProfileProtocol.js +50 -50
  90. package/lib/WAUSync/Protocols/UsyncLIDProtocol.js +28 -28
  91. package/lib/WAUSync/Protocols/index.js +4 -4
  92. package/lib/WAUSync/USyncQuery.js +93 -93
  93. package/lib/WAUSync/USyncUser.js +22 -22
  94. package/lib/WAUSync/index.js +3 -3
  95. package/lib/index.js +65 -66
  96. package/package.json +172 -143
  97. package/lib/Signal/Group/ciphertext-message.js +0 -12
  98. package/lib/Signal/Group/group-session-builder.js +0 -30
  99. package/lib/Signal/Group/group_cipher.js +0 -100
  100. package/lib/Signal/Group/index.js +0 -12
  101. package/lib/Signal/Group/keyhelper.js +0 -18
  102. package/lib/Signal/Group/sender-chain-key.js +0 -26
  103. package/lib/Signal/Group/sender-key-distribution-message.js +0 -63
  104. package/lib/Signal/Group/sender-key-message.js +0 -66
  105. package/lib/Signal/Group/sender-key-name.js +0 -48
  106. package/lib/Signal/Group/sender-key-record.js +0 -41
  107. package/lib/Signal/Group/sender-key-state.js +0 -84
  108. package/lib/Signal/Group/sender-message-key.js +0 -26
@@ -1,9 +1,4 @@
1
- /**
2
- * @nexus/baileys - Advanced Message Handler
3
- * Handles: PAYMENT, PRODUCT, INTERACTIVE, ALBUM, EVENT, POLL_RESULT,
4
- * STATUS_MENTION, ORDER, GROUP_STATUS, CAROUSEL, CAROUSEL_PROTO, STICKER_PACK
5
- */
6
-
1
+ import { proto } from '../../WAProto/index.js'
7
2
  import axios from 'axios'
8
3
  import crypto from 'crypto'
9
4
 
@@ -14,8 +9,6 @@ class NexusHandler {
14
9
  this.upload = waUploadToServer
15
10
  this.opts = options
16
11
  this.user = options.user || null
17
-
18
- // Auto-routing map for message types
19
12
  this.handlers = {
20
13
  PAYMENT: this.handlePayment.bind(this),
21
14
  PRODUCT: this.handleProduct.bind(this),
@@ -32,53 +25,55 @@ class NexusHandler {
32
25
  }
33
26
  }
34
27
 
35
- // ========== TYPE DETECTION ==========
28
+ // ─── TYPE DETECTION ───────────────────────────────────────────────────────
36
29
  detectType(content) {
37
- const types = {
38
- requestPaymentMessage: 'PAYMENT',
39
- productMessage: 'PRODUCT',
40
- interactiveMessage: 'INTERACTIVE',
41
- interactive: 'INTERACTIVE', // Also detect 'interactive' key
42
- albumMessage: 'ALBUM',
43
- eventMessage: 'EVENT',
44
- pollResultMessage: 'POLL_RESULT',
45
- statusMentionMessage: 'STATUS_MENTION',
46
- orderMessage: 'ORDER',
47
- stickerPack: 'STICKER_PACK',
48
- groupStatus: 'GROUP_STATUS',
49
- carouselProto: 'CAROUSEL_PROTO'
50
- }
51
-
52
30
  if (content.carouselMessage || content.carousel) return 'CAROUSEL'
53
- return types[Object.keys(types).find(k => content[k])] || null
31
+ if (content.carouselProto) return 'CAROUSEL_PROTO'
32
+ const map = {
33
+ requestPaymentMessage: 'PAYMENT', productMessage: 'PRODUCT',
34
+ interactiveMessage: 'INTERACTIVE', interactive: 'INTERACTIVE',
35
+ albumMessage: 'ALBUM', eventMessage: 'EVENT',
36
+ pollResultMessage: 'POLL_RESULT', statusMentionMessage: 'STATUS_MENTION',
37
+ orderMessage: 'ORDER', stickerPack: 'STICKER_PACK', groupStatus: 'GROUP_STATUS'
38
+ }
39
+ return map[Object.keys(map).find(k => content[k])] || null
54
40
  }
55
41
 
56
- // ========== UNIFIED PROCESSOR ==========
42
+ // ─── UNIFIED PROCESSOR ────────────────────────────────────────────────────
57
43
  async processMessage(content, jid, quoted) {
58
- const messageType = this.detectType(content)
59
- if (!messageType) throw new Error('Unknown message type')
60
-
61
- const handler = this.handlers[messageType]
62
- if (!handler) throw new Error(`No handler for: ${messageType}`)
63
-
64
- return messageType === 'STICKER_PACK'
65
- ? await handler(content.stickerPack, jid, quoted)
66
- : 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)
67
49
  }
68
50
 
69
- // ========== HELPERS ==========
51
+ // ─── HELPERS ──────────────────────────────────────────────────────────────
70
52
  async prepMedia(data, type) {
71
53
  if (!data) return null
72
54
  const payload = typeof data === 'object' && data.url ? { [type]: { url: data.url } } : { [type]: data }
73
55
  return await this.utils.prepareWAMessageMedia(payload, { upload: this.upload })
74
56
  }
75
57
 
76
- async genContent(jid, content, opts = {}) {
77
- return await this.utils.generateWAMessage(jid, content, { ...opts, upload: this.upload })
58
+ 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
+ })
66
+ }
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
+ })
78
73
  }
79
74
 
80
75
  async sendMsg(jid, message, opts = {}) {
81
- await this.relay(jid, message, opts)
76
+ return await this.relay(jid, message, opts)
82
77
  }
83
78
 
84
79
  buildCtx(quoted, sender) {
@@ -90,85 +85,101 @@ class NexusHandler {
90
85
  }
91
86
 
92
87
  buildFullCtx(ctx, adReply) {
88
+ const allowed = ['title', 'body', 'mediaType', 'thumbnailUrl', 'mediaUrl', 'sourceUrl', 'showAdAttribution', 'renderLargerThumbnail', 'thumbnail']
93
89
  const final = ctx ? { mentionedJid: ctx.mentionedJid || [], forwardingScore: ctx.forwardingScore || 0, isForwarded: ctx.isForwarded || false, ...ctx } : {}
94
90
  if (adReply) {
95
- final.externalAdReply = { title: adReply.title || '', body: adReply.body || '', mediaType: adReply.mediaType || 1,
96
- thumbnailUrl: adReply.thumbnailUrl || '', mediaUrl: adReply.mediaUrl || '', sourceUrl: adReply.sourceUrl || '',
97
- showAdAttribution: adReply.showAdAttribution || false, renderLargerThumbnail: adReply.renderLargerThumbnail || false, ...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 }
98
94
  }
99
95
  return final
100
96
  }
101
97
 
102
98
  genJid() {
103
- return this.utils.generateMessageID().split('@')[0] + '@s.whatsapp.net'
99
+ const id = this.utils.generateMessageIDV2?.() || this.utils.generateMessageID?.() || crypto.randomBytes(10).toString('hex')
100
+ return id.includes('@') ? id : `${id}@s.whatsapp.net`
104
101
  }
105
102
 
106
- parseTime(val, def) {
107
- return typeof val === 'string' ? parseInt(val) : val || def
108
- }
103
+ parseTime(val, def) { return typeof val === 'string' ? parseInt(val) : (val || def) }
104
+ delay(ms) { return new Promise(r => setTimeout(r, ms)) }
109
105
 
110
- delay(ms) {
111
- return new Promise(resolve => setTimeout(resolve, ms))
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
112
115
  }
113
116
 
114
- // ========== MESSAGE HANDLERS ==========
115
-
116
- async handlePayment(content, quoted) {
117
+ // ─── PAYMENT ──────────────────────────────────────────────────────────────
118
+ async handlePayment(content, jid, quoted) {
117
119
  const d = content.requestPaymentMessage
118
- const notes = d.sticker?.stickerMessage ? {
119
- stickerMessage: { ...d.sticker.stickerMessage, contextInfo: this.buildCtx(quoted, content.sender) }
120
- } : d.note ? {
121
- extendedTextMessage: { text: d.note, contextInfo: this.buildCtx(quoted, content.sender) }
122
- } : {}
123
-
124
- const msg = await this.genContent(content.jid, {
125
- requestPaymentMessage: this.utils.WAProto.proto.Message.RequestPaymentMessage.fromObject({
126
- expiryTimestamp: d.expiry || 0, amount1000: d.amount || 0, currencyCodeIso4217: d.currency || 'IDR',
127
- requestFrom: d.from || '0@s.whatsapp.net', noteMessage: notes, background: d.background ?? { id: 'DEFAULT', placeholderArgb: 0xfff0f0f0 }
120
+ 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 } } : {}
124
+ 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 }
128
133
  })
129
134
  }, { quoted })
130
-
131
- await this.sendMsg(content.jid, msg.message, { messageId: msg.key.id })
135
+ await this.sendMsg(targetJid, msg.message, { messageId: msg.key.id })
132
136
  return msg
133
137
  }
134
138
 
139
+ // ─── PRODUCT ──────────────────────────────────────────────────────────────
135
140
  async handleProduct(content, jid, quoted) {
136
141
  const p = content.productMessage || {}
137
142
  let prodImg = null
138
-
139
143
  if (p.thumbnail) {
140
- const imgContent = Buffer.isBuffer(p.thumbnail) ? { image: p.thumbnail } : { image: { url: p.thumbnail.url || p.thumbnail } }
141
- 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 })
142
146
  prodImg = res?.imageMessage || res?.message?.imageMessage
143
147
  }
144
-
145
- const product = {
146
- productId: p.productId, title: p.title || '', description: p.description || '', currencyCode: p.currencyCode || 'IDR',
147
- priceAmount1000: p.priceAmount1000, retailerId: p.retailerId, url: p.url, productImageCount: prodImg ? 1 : 0,
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,
148
157
  ...(prodImg && { productImage: prodImg })
149
- }
150
-
151
- const msg = await this.genContent(jid, {
158
+ })
159
+ const msg = await this.genFromContent(jid, {
152
160
  viewOnceMessage: {
153
161
  message: {
154
- interactiveMessage: {
155
- body: { text: p.body || '' }, footer: { text: p.footer || '' },
156
- header: { title: p.title, hasMediaAttachment: !!prodImg, productMessage: { product, businessOwnerJid: '0@s.whatsapp.net' } },
157
- nativeFlowMessage: { buttons: p.buttons || [] }
158
- }
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
+ })
159
172
  }
160
173
  }
161
174
  }, { quoted })
162
-
163
175
  await this.sendMsg(jid, msg.message, { messageId: msg.key.id })
164
176
  return msg
165
177
  }
166
178
 
179
+ // ─── INTERACTIVE ──────────────────────────────────────────────────────────
167
180
  async handleInteractive(content, jid, quoted) {
168
- // Handle both 'interactiveMessage' and 'interactive' keys
169
181
  const i = content.interactiveMessage || content.interactive || {}
170
182
  let media = null
171
-
172
183
  if (i.thumbnail) media = await this.prepMedia({ url: i.thumbnail }, 'image')
173
184
  else if (i.image) media = await this.prepMedia(i.image, 'image')
174
185
  else if (i.video) media = await this.prepMedia(i.video, 'video')
@@ -178,267 +189,303 @@ class NexusHandler {
178
189
  if (i.fileName) media.documentMessage.fileName = i.fileName
179
190
  if (i.mimetype) media.documentMessage.mimetype = i.mimetype
180
191
  }
181
-
182
- // Handle both formats: { title, footer } and { body: {text}, footer: {text} }
183
192
  const bodyText = i.body?.text || i.title || ''
184
- const footerText = i.footer?.text || i.footer || ''
185
- const interactive = { body: { text: bodyText }, footer: { text: footerText } }
186
-
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
187
196
  if (i.buttons?.length || i.nativeFlowMessage) {
188
- // Build nativeFlowMessage with proper structure
189
- const nativeFlow = i.nativeFlowMessage || {}
190
- // Only include buttons, don't add empty messageParamsJson
191
- interactive.nativeFlowMessage = {
192
- buttons: i.buttons || nativeFlow.buttons || []
193
- }
194
- // Only add messageParamsJson if explicitly provided
195
- if (nativeFlow.messageParamsJson) {
196
- interactive.nativeFlowMessage.messageParamsJson = nativeFlow.messageParamsJson
197
- }
198
- }
199
-
200
- if (media) {
201
- const headerMedia = {}
202
- if (media.imageMessage) headerMedia.imageMessage = media.imageMessage
203
- if (media.videoMessage) headerMedia.videoMessage = media.videoMessage
204
- if (media.documentMessage) headerMedia.documentMessage = media.documentMessage
205
- // Handle both plain title string and full header object
206
- const headerTitle = typeof i.header === 'string' ? i.header : i.header?.title || ''
207
- interactive.header = { title: headerTitle, hasMediaAttachment: true, ...headerMedia }
208
- } else {
209
- // Handle both plain title string and full header object
210
- const headerTitle = typeof i.header === 'string' ? i.header : i.header?.title || ''
211
- 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
+ })
212
202
  }
213
-
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
+ })
214
213
  const ctx = this.buildFullCtx(i.contextInfo, i.externalAdReply)
215
214
  if (Object.keys(ctx).length) interactive.contextInfo = ctx
216
-
217
- // Return interactiveMessage directly without wrapping for native flows
218
- // This matches fadzzz404's approach which works correctly
219
- const messageContent = { interactiveMessage: interactive }
220
-
221
- const msg = await this.genContent(jid, messageContent, { quoted })
215
+ const msg = await this.genFromContent(jid, { interactiveMessage: interactive }, { quoted })
222
216
  await this.sendMsg(jid, msg.message, { messageId: msg.key.id })
223
217
  return msg
224
218
  }
225
219
 
220
+ // ─── ALBUM ────────────────────────────────────────────────────────────────
226
221
  async handleAlbum(content, jid, quoted) {
227
222
  const arr = Array.isArray(content.albumMessage) ? content.albumMessage : []
228
223
  if (!arr.length) throw new Error('albumMessage must contain media items')
229
-
230
- const album = await this.genContent(jid, {
231
- messageContextInfo: { messageSecret: crypto.randomBytes(32) },
232
- 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
+ })
233
230
  }, { userJid: this.genJid(), quoted })
234
-
235
231
  await this.sendMsg(jid, album.message, { messageId: album.key.id })
236
-
237
232
  for (const item of arr) {
238
- const img = await this.utils.generateWAMessage(jid, item, { upload: this.upload })
239
-
240
- img.message.messageContextInfo = {
241
- messageSecret: crypto.randomBytes(32), messageAssociation: { associationType: 1, parentMessageKey: album.key },
242
- participant: '0@s.whatsapp.net', remoteJid: 'status@broadcast', forwardingScore: 99999, isForwarded: true,
243
- mentionedJid: [jid], starred: true, labels: ['Y', 'Important'], isHighlighted: true,
244
- businessMessageForwardInfo: { businessOwnerJid: jid }, dataSharingContext: { showMmDisclosure: true }
245
- }
246
-
247
- img.message.forwardedNewsletterMessageInfo = {
248
- newsletterJid: '0@newsletter', serverMessageId: 1, newsletterName: 'WhatsApp', contentType: 'UPDATE_CARD',
249
- timestamp: new Date().toISOString(), senderName: 'Nexus', priority: 'high', status: 'sent'
250
- }
251
-
252
- img.message.disappearingMode = {
253
- initiator: 3, trigger: 4, initiatorDeviceJid: jid, initiatedByExternalService: true, initiatedByUserDevice: true,
254
- initiatedBySystem: true, initiatedByServer: true, initiatedByAdmin: true, initiatedByUser: true, initiatedByApp: true,
255
- initiatedByBot: true, initiatedByMe: true
256
- }
257
-
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
+ })
258
265
  await this.sendMsg(jid, img.message, {
259
266
  messageId: img.key.id,
260
267
  quoted: { key: { ...album.key, fromMe: true, participant: this.genJid() }, message: album.message }
261
268
  })
262
269
  }
263
-
264
270
  return album
265
271
  }
266
272
 
273
+ // ─── EVENT ────────────────────────────────────────────────────────────────
267
274
  async handleEvent(content, jid, quoted) {
268
275
  const e = content.eventMessage
269
-
270
- const msg = await this.genContent(jid, {
276
+ const msg = await this.genFromContent(jid, {
271
277
  viewOnceMessage: {
272
278
  message: {
273
- messageContextInfo: {
274
- deviceListMetadata: {}, deviceListMetadataVersion: 2, messageSecret: crypto.randomBytes(32),
279
+ messageContextInfo: proto.MessageContextInfo.create({
280
+ deviceListMetadata: {},
281
+ deviceListMetadataVersion: 2,
282
+ messageSecret: crypto.randomBytes(32),
275
283
  supportPayload: JSON.stringify({ version: 2, is_ai_message: true, should_show_system_message: true, ticket_id: crypto.randomBytes(16).toString('hex') })
276
- },
277
- eventMessage: {
278
- contextInfo: {
279
- mentionedJid: [jid], participant: jid, remoteJid: 'status@broadcast',
280
- forwardedNewsletterMessageInfo: { newsletterName: 'Nexus Events', newsletterJid: '120363422827915475@newsletter', serverMessageId: 1 }
281
- },
282
- isCanceled: e.isCanceled || false, name: e.name, description: e.description,
283
- location: e.location || { degreesLatitude: 0, degreesLongitude: 0, name: 'Location' }, joinLink: e.joinLink || '',
284
- startTime: this.parseTime(e.startTime, Date.now()), endTime: this.parseTime(e.endTime, Date.now() + 3600000),
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),
285
303
  extraGuestsAllowed: e.extraGuestsAllowed !== false
286
- }
304
+ })
287
305
  }
288
306
  }
289
307
  }, { quoted })
290
-
291
308
  await this.sendMsg(jid, msg.message, { messageId: msg.key.id })
292
309
  return msg
293
310
  }
294
311
 
312
+ // ─── POLL RESULT ──────────────────────────────────────────────────────────
295
313
  async handlePollResult(content, jid, quoted) {
296
314
  const p = content.pollResultMessage
297
-
298
- const msg = await this.genContent(jid, {
299
- pollResultSnapshotMessage: {
315
+ const msg = await this.genFromContent(jid, {
316
+ pollResultSnapshotMessage: proto.Message.PollResultSnapshotMessage.create({
300
317
  name: p.name,
301
- pollVotes: (p.pollVotes || []).map(v => ({
318
+ pollVotes: (p.pollVotes || []).map(v => proto.Message.PollResultSnapshotMessage.PollVote.create({
302
319
  optionName: v.optionName,
303
320
  optionVoteCount: typeof v.optionVoteCount === 'number' ? v.optionVoteCount.toString() : v.optionVoteCount
304
321
  })),
305
- contextInfo: {
306
- isForwarded: true, forwardingScore: 1,
307
- forwardedNewsletterMessageInfo: {
322
+ contextInfo: proto.ContextInfo.create({
323
+ isForwarded: true,
324
+ forwardingScore: 1,
325
+ forwardedNewsletterMessageInfo: proto.ForwardedNewsletterMessageInfo.create({
308
326
  newsletterName: p.newsletter?.newsletterName || 'Newsletter',
309
327
  newsletterJid: p.newsletter?.newsletterJid || '120363399602691477@newsletter',
310
- serverMessageId: 1000, contentType: 'UPDATE'
311
- }
312
- }
313
- }
328
+ serverMessageId: 1000,
329
+ contentType: 'UPDATE'
330
+ })
331
+ })
332
+ })
314
333
  }, { userJid: this.genJid(), quoted })
315
-
316
334
  await this.sendMsg(jid, msg.message, { messageId: msg.key.id })
317
335
  return msg
318
336
  }
319
337
 
338
+ // ─── STATUS MENTION ───────────────────────────────────────────────────────
320
339
  async handleStMention(content, jid, quoted) {
321
340
  const d = content.statusMentionMessage
322
- const media = await this.prepMedia(d.image || d.video, d.image ? 'image' : 'video')
323
-
324
- const msg = await this.relay('status@broadcast', { ...media }, {
325
- statusJidList: [d.mentions, this.user?.id],
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 }, {
344
+ statusJidList: [d.mentions, this.user?.id].filter(Boolean),
326
345
  additionalNodes: [{
327
346
  tag: 'meta', attrs: {},
328
347
  content: [{ tag: 'mentioned_users', attrs: {}, content: [{ tag: 'to', attrs: { jid: d.mentions }, content: undefined }] }]
329
348
  }]
330
349
  })
331
-
332
- const xontols = await this.genContent(jid, {
333
- statusMentionMessage: { message: { protocolMessage: { messageId: msg.key, type: 'STATUS_MENTION_MESSAGE' } } }
334
- }, { additionalNodes: [{ tag: 'meta', attrs: { is_status_mention: true }, content: undefined }] })
335
-
336
- await this.sendMsg(jid, xontols.message, { messageId: xontols.key.id })
337
- return xontols
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
+ })
359
+ }, { additionalNodes: [{ tag: 'meta', attrs: { is_status_mention: 'true' }, content: undefined }] })
360
+ await this.sendMsg(jid, mentionMsg.message, { messageId: mentionMsg.key.id })
361
+ return mentionMsg
338
362
  }
339
363
 
364
+ // ─── ORDER ────────────────────────────────────────────────────────────────
340
365
  async handleOrderMessage(content, jid, quoted) {
341
366
  const o = content.orderMessage
342
- let thumb = null
343
-
344
- if (o.thumbnail) {
345
- if (Buffer.isBuffer(o.thumbnail)) {
346
- thumb = o.thumbnail
347
- } else if (typeof o.thumbnail === 'string') {
348
- try {
349
- const res = await axios.get(o.thumbnail, { responseType: 'arraybuffer' })
350
- thumb = Buffer.from(res.data)
351
- } catch (err) {
352
- console.error('Failed to download thumbnail:', err)
353
- }
354
- }
355
- }
356
-
357
- const msg = await this.genContent(jid, {
358
- orderMessage: {
359
- orderId: '7NEXUS25022008', thumbnail: thumb, itemCount: o.itemCount || 0, status: 'ACCEPTED', surface: 'CATALOG',
360
- message: o.message, orderTitle: o.orderTitle, sellerJid: '0@whatsapp.net', token: 'NEXUS_EXAMPLE_TOKEN',
361
- totalAmount1000: o.totalAmount1000 || 0, totalCurrencyCode: o.totalCurrencyCode || 'IDR', messageVersion: 2
362
- }
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
+ })
363
383
  }, { quoted })
364
-
365
384
  await this.sendMsg(jid, msg.message, { messageId: msg.key.id })
366
385
  return msg
367
386
  }
368
387
 
388
+ // ─── GROUP STATUS ─────────────────────────────────────────────────────────
369
389
  async handleGroupStory(content, jid, quoted) {
370
390
  const s = content.groupStatus
371
- const msgContent = s.message || await this.utils.generateWAMessageContent(s, { upload: this.upload })
372
- const msg = { message: { groupStatusMessageV2: { message: msgContent.message || msgContent } } }
373
- return await this.relay(jid, msg.message, { messageId: this.utils.generateMessageID() })
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
+ })
374
403
  }
375
404
 
405
+ // ─── CAROUSEL ─────────────────────────────────────────────────────────────
376
406
  async handleCarousel(content, jid, quoted) {
377
407
  const c = content.carouselMessage || content.carousel || {}
378
408
  const cards = await Promise.all((c.cards || []).map(card => this.buildCard(card)))
379
-
380
- const msg = await this.genContent(jid, {
409
+ const msg = await this.genFromContent(jid, {
381
410
  viewOnceMessage: {
382
411
  message: {
383
- interactiveMessage: {
384
- body: { text: c.caption || c.body || '' }, footer: { text: c.footer || '' },
385
- carouselMessage: { cards, messageVersion: 1 }
386
- }
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
+ })
387
417
  }
388
418
  }
389
419
  }, { quoted })
390
-
391
420
  await this.sendMsg(jid, msg.message, { messageId: msg.key.id })
392
421
  return msg
393
422
  }
394
423
 
395
424
  async buildCard(card) {
425
+ const buttons = (card.buttons || []).map(btn => ({
426
+ name: btn.name,
427
+ buttonParamsJson: JSON.stringify(btn.params || {})
428
+ }))
396
429
  if (card.productTitle) {
430
+ const imgMedia = await this.prepMedia({ url: card.imageUrl }, 'image')
397
431
  return {
398
- header: {
399
- title: card.headerTitle || '', subtitle: card.headerSubtitle || '',
400
- productMessage: {
401
- product: {
402
- productImage: (await this.prepMedia({ url: card.imageUrl }, 'image')).imageMessage,
403
- productId: card.productId || '123456', title: card.productTitle, description: card.productDescription || '',
404
- currencyCode: card.currencyCode || 'IDR', priceAmount1000: card.priceAmount1000 || '100000',
405
- retailerId: card.retailerId || 'Retailer', url: card.url || '', productImageCount: 1
406
- },
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
+ }),
407
448
  businessOwnerJid: card.businessOwnerJid || '0@s.whatsapp.net'
408
- },
409
- hasMediaAttachment: false
410
- },
411
- body: { text: card.bodyText || '' }, footer: { text: card.footerText || '' },
412
- nativeFlowMessage: { buttons: (card.buttons || []).map(btn => ({ name: btn.name, buttonParamsJson: JSON.stringify(btn.params || {}) })) }
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 })
413
454
  }
414
455
  }
415
-
416
456
  const imgMedia = card.imageUrl ? await this.prepMedia({ url: card.imageUrl }, 'image') : {}
417
457
  return {
418
- header: { title: card.headerTitle || '', subtitle: card.headerSubtitle || '', hasMediaAttachment: !!card.imageUrl, ...imgMedia },
419
- body: { text: card.bodyText || '' }, footer: { text: card.footerText || '' },
420
- nativeFlowMessage: { buttons: (card.buttons || []).map(btn => ({ name: btn.name, buttonParamsJson: JSON.stringify(btn.params || {}) })) }
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 })
421
467
  }
422
468
  }
423
469
 
470
+ // ─── CAROUSEL PROTO ───────────────────────────────────────────────────────
424
471
  async handleCarouselProto(content, jid, quoted) {
425
472
  const c = content.carouselProto
426
- const proto = this.utils.WAProto?.proto
427
- if (!proto) throw new Error('WAProto not available')
428
-
429
473
  const cards = await Promise.all((c.cards || []).map(async card => ({
430
- 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
+ }),
431
479
  body: proto.Message.InteractiveMessage.Body.create({ text: card.bodyText || '' }),
432
480
  footer: proto.Message.InteractiveMessage.Footer.create({ text: card.footerText || '' }),
433
481
  nativeFlowMessage: proto.Message.InteractiveMessage.NativeFlowMessage.create({
434
482
  buttons: (card.buttons || []).map(btn => ({ name: btn.name, buttonParamsJson: JSON.stringify(btn.params || {}) }))
435
483
  })
436
484
  })))
437
-
438
- const msg = await this.genContent(jid, {
485
+ const msg = await this.genFromContent(jid, {
439
486
  viewOnceMessage: {
440
487
  message: {
441
- messageContextInfo: { deviceListMetadata: {}, deviceListMetadataVersion: 2 },
488
+ messageContextInfo: proto.MessageContextInfo.create({ deviceListMetadata: {}, deviceListMetadataVersion: 2 }),
442
489
  interactiveMessage: proto.Message.InteractiveMessage.create({
443
490
  body: proto.Message.InteractiveMessage.Body.create({ text: c.body || '' }),
444
491
  footer: proto.Message.InteractiveMessage.Footer.create({ text: c.footer || '' }),
@@ -447,29 +494,31 @@ class NexusHandler {
447
494
  }
448
495
  }
449
496
  }, { quoted })
450
-
451
497
  await this.sendMsg(jid, msg.message, { messageId: msg.key.id })
452
498
  return msg
453
499
  }
454
500
 
455
- async handleStickerPack(stickerPack, jid, quoted) {
501
+ // ─── STICKER PACK ─────────────────────────────────────────────────────────
502
+ async handleStickerPack(content, jid, quoted) {
503
+ const stickerPack = content.stickerPack
456
504
  const result = await this.utils.prepareStickerPackMessage(stickerPack, {
457
- logger: this.opts?.logger, upload: this.upload, mediaCache: this.opts?.mediaCache,
458
- options: this.opts, mediaUploadTimeoutMs: this.opts?.mediaUploadTimeoutMs
505
+ logger: this.opts?.logger,
506
+ upload: this.upload,
507
+ mediaCache: this.opts?.mediaCache,
508
+ options: this.opts,
509
+ mediaUploadTimeoutMs: this.opts?.mediaUploadTimeoutMs
459
510
  })
460
-
461
511
  if (result.isBatched) {
462
512
  const sent = []
463
513
  for (let i = 0; i < result.stickerPackMessage.length; i++) {
464
- const msg = await this.genContent(jid, { stickerPackMessage: result.stickerPackMessage[i] }, { quoted })
514
+ const msg = await this.genFromContent(jid, { stickerPackMessage: result.stickerPackMessage[i] }, { quoted })
465
515
  await this.sendMsg(jid, msg.message, { messageId: msg.key.id })
466
516
  sent.push(msg)
467
517
  if (i < result.stickerPackMessage.length - 1) await this.delay(2000)
468
518
  }
469
519
  return sent[sent.length - 1]
470
520
  }
471
-
472
- const msg = await this.genContent(jid, { stickerPackMessage: result.stickerPackMessage }, { quoted })
521
+ const msg = await this.genFromContent(jid, { stickerPackMessage: result.stickerPackMessage }, { quoted })
473
522
  await this.sendMsg(jid, msg.message, { messageId: msg.key.id })
474
523
  return msg
475
524
  }