@hansaka02/baileys 7.3.4 → 7.3.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 (50) hide show
  1. package/README.md +203 -247
  2. package/lib/Defaults/baileys-version.json +2 -2
  3. package/lib/Defaults/connection.js +1 -1
  4. package/lib/Defaults/constants.js +13 -1
  5. package/lib/Defaults/history.js +3 -1
  6. package/lib/Signal/Group/sender-chain-key.js +1 -14
  7. package/lib/Signal/Group/sender-key-distribution-message.js +2 -2
  8. package/lib/Signal/Group/sender-key-record.js +2 -11
  9. package/lib/Signal/Group/sender-key-state.js +11 -57
  10. package/lib/Signal/libsignal.js +200 -116
  11. package/lib/Signal/lid-mapping.js +121 -68
  12. package/lib/Socket/Client/websocket.js +9 -2
  13. package/lib/Socket/business.js +5 -1
  14. package/lib/Socket/chats.js +180 -89
  15. package/lib/Socket/community.js +169 -41
  16. package/lib/Socket/groups.js +25 -21
  17. package/lib/Socket/messages-recv.js +458 -333
  18. package/lib/Socket/messages-send.js +517 -572
  19. package/lib/Socket/mex.js +61 -0
  20. package/lib/Socket/newsletter.js +159 -252
  21. package/lib/Socket/socket.js +283 -100
  22. package/lib/Types/Newsletter.js +32 -25
  23. package/lib/Utils/auth-utils.js +189 -354
  24. package/lib/Utils/browser-utils.js +43 -0
  25. package/lib/Utils/chat-utils.js +166 -41
  26. package/lib/Utils/decode-wa-message.js +77 -35
  27. package/lib/Utils/event-buffer.js +80 -24
  28. package/lib/Utils/generics.js +28 -128
  29. package/lib/Utils/history.js +10 -8
  30. package/lib/Utils/index.js +1 -1
  31. package/lib/Utils/link-preview.js +17 -32
  32. package/lib/Utils/lt-hash.js +28 -22
  33. package/lib/Utils/make-mutex.js +26 -28
  34. package/lib/Utils/message-retry-manager.js +51 -3
  35. package/lib/Utils/messages-media.js +343 -151
  36. package/lib/Utils/messages.js +806 -792
  37. package/lib/Utils/noise-handler.js +33 -2
  38. package/lib/Utils/pre-key-manager.js +126 -0
  39. package/lib/Utils/process-message.js +115 -55
  40. package/lib/Utils/signal.js +45 -18
  41. package/lib/Utils/validate-connection.js +52 -29
  42. package/lib/WABinary/constants.js +1268 -1268
  43. package/lib/WABinary/decode.js +58 -4
  44. package/lib/WABinary/encode.js +54 -7
  45. package/lib/WABinary/jid-utils.js +58 -11
  46. package/lib/WAM/constants.js +19064 -11563
  47. package/lib/WAM/encode.js +57 -8
  48. package/lib/WAUSync/USyncQuery.js +35 -19
  49. package/package.json +9 -8
  50. package/lib/Socket/usync.js +0 -83
@@ -3,7 +3,6 @@
3
3
  Object.defineProperty(exports, "__esModule", { value: true })
4
4
 
5
5
  const { Boom } = require("@hapi/boom")
6
- const axios_1 = require("axios")
7
6
  const { randomBytes } = require("crypto")
8
7
  const { promises } = require("fs")
9
8
  const { proto } = require("../../WAProto")
@@ -97,65 +96,70 @@ const assertColor = async (color) => {
97
96
 
98
97
  const prepareWAMessageMedia = async (message, options) => {
99
98
  const logger = options.logger
99
+
100
100
  let mediaType
101
-
101
+
102
102
  for (const key of MEDIA_KEYS) {
103
103
  if (key in message) {
104
104
  mediaType = key
105
105
  }
106
106
  }
107
-
107
+
108
108
  if (!mediaType) {
109
- throw new Boom('Invalid media type', { statusCode: 400 });
109
+ throw new Boom('Invalid media type', { statusCode: 400 })
110
110
  }
111
-
111
+
112
112
  const uploadData = {
113
113
  ...message,
114
114
  media: message[mediaType]
115
115
  }
116
-
116
+
117
117
  delete uploadData[mediaType]
118
-
118
+
119
119
  // check if cacheable + generate cache key
120
120
  const cacheableKey = typeof uploadData.media === 'object' &&
121
121
  'url' in uploadData.media &&
122
122
  !!uploadData.media.url &&
123
123
  !!options.mediaCache &&
124
124
  mediaType + ':' + uploadData.media.url.toString()
125
-
125
+
126
126
  if (mediaType === 'document' && !uploadData.fileName) {
127
127
  uploadData.fileName = 'file'
128
128
  }
129
-
129
+
130
130
  if (!uploadData.mimetype) {
131
131
  uploadData.mimetype = MIMETYPE_MAP[mediaType]
132
132
  }
133
-
133
+
134
134
  if (cacheableKey) {
135
135
  const mediaBuff = await options.mediaCache.get(cacheableKey)
136
+
136
137
  if (mediaBuff) {
137
138
  logger?.debug({ cacheableKey }, 'got media cache hit')
138
- const obj = WAProto.Message.decode(mediaBuff)
139
+
140
+ const obj = proto.Message.decode(mediaBuff)
139
141
  const key = `${mediaType}Message`
142
+
140
143
  Object.assign(obj[key], { ...uploadData, media: undefined })
144
+
141
145
  return obj
142
146
  }
143
147
  }
144
-
148
+
145
149
  const isNewsletter = !!options.jid && isJidNewsletter(options.jid)
146
-
150
+
147
151
  if (isNewsletter) {
148
152
  logger?.info({ key: cacheableKey }, 'Preparing raw media for newsletter')
149
153
  const { filePath, fileSha256, fileLength } = await getRawMediaUploadData(uploadData.media, options.mediaTypeOverride || mediaType, logger)
150
- const fileSha256B64 = fileSha256.toString('base64')
154
+ const fileSha256B64 = fileSha256.toString('base64');
151
155
  const { mediaUrl, directPath } = await options.upload(filePath, {
152
156
  fileEncSha256B64: fileSha256B64,
153
157
  mediaType: mediaType,
154
158
  timeoutMs: options.mediaUploadTimeoutMs
155
159
  })
156
-
160
+
157
161
  await promises.unlink(filePath)
158
-
162
+
159
163
  const obj = WAProto.Message.fromObject({
160
164
  // todo: add more support here
161
165
  [`${mediaType}Message`]: MessageTypeProto[mediaType].fromObject({
@@ -167,34 +171,35 @@ const prepareWAMessageMedia = async (message, options) => {
167
171
  media: undefined
168
172
  })
169
173
  })
170
-
174
+
171
175
  if (uploadData.ptv) {
172
176
  obj.ptvMessage = obj.videoMessage
173
177
  delete obj.videoMessage
174
178
  }
175
-
179
+
180
+ if (obj.stickerMessage) {
181
+ obj.stickerMessage.stickerSentTs = Date.now()
182
+ }
183
+
176
184
  if (cacheableKey) {
177
185
  logger?.debug({ cacheableKey }, 'set cache');
178
186
  await options.mediaCache.set(cacheableKey, WAProto.Message.encode(obj).finish())
179
187
  }
180
-
188
+
181
189
  return obj
182
190
  }
183
-
191
+
184
192
  const requiresDurationComputation = mediaType === 'audio' && typeof uploadData.seconds === 'undefined'
185
193
  const requiresThumbnailComputation = (mediaType === 'image' || mediaType === 'video') && typeof uploadData['jpegThumbnail'] === 'undefined'
186
194
  const requiresWaveformProcessing = mediaType === 'audio' && uploadData.ptt === true
187
195
  const requiresAudioBackground = options.backgroundColor && mediaType === 'audio' && uploadData.ptt === true
188
196
  const requiresOriginalForSomeProcessing = requiresDurationComputation || requiresThumbnailComputation
189
-
190
197
  const { mediaKey, encFilePath, originalFilePath, fileEncSha256, fileSha256, fileLength } = await encryptedStream(uploadData.media, options.mediaTypeOverride || mediaType, {
191
198
  logger,
192
199
  saveOriginalFileIfRequired: requiresOriginalForSomeProcessing,
193
200
  opts: options.options
194
201
  })
195
-
196
202
  const fileEncSha256B64 = fileEncSha256.toString('base64')
197
-
198
203
  const [{ mediaUrl, directPath }] = await Promise.all([
199
204
  (async () => {
200
205
  const result = await options.upload(encFilePath, {
@@ -205,32 +210,31 @@ const prepareWAMessageMedia = async (message, options) => {
205
210
  logger?.debug({ mediaType, cacheableKey }, 'uploaded media')
206
211
  return result
207
212
  })(),
208
-
209
213
  (async () => {
210
214
  try {
211
215
  if (requiresThumbnailComputation) {
212
216
  const { thumbnail, originalImageDimensions } = await generateThumbnail(originalFilePath, mediaType, options)
217
+
213
218
  uploadData.jpegThumbnail = thumbnail
219
+
214
220
  if (!uploadData.width && originalImageDimensions) {
215
221
  uploadData.width = originalImageDimensions.width
216
222
  uploadData.height = originalImageDimensions.height
217
-
218
223
  logger?.debug('set dimensions')
219
224
  }
220
-
221
- logger?.debug('generated thumbnail');
225
+ logger?.debug('generated thumbnail')
222
226
  }
223
-
227
+
224
228
  if (requiresDurationComputation) {
225
229
  uploadData.seconds = await getAudioDuration(originalFilePath)
226
230
  logger?.debug('computed audio duration')
227
231
  }
228
-
232
+
229
233
  if (requiresWaveformProcessing) {
230
234
  uploadData.waveform = await getAudioWaveform(originalFilePath, logger)
231
235
  logger?.debug('processed waveform')
232
236
  }
233
-
237
+
234
238
  if (requiresAudioBackground) {
235
239
  uploadData.backgroundArgb = await assertColor(options.backgroundColor)
236
240
  logger?.debug('computed backgroundColor audio status')
@@ -243,18 +247,18 @@ const prepareWAMessageMedia = async (message, options) => {
243
247
  ]).finally(async () => {
244
248
  try {
245
249
  await promises.unlink(encFilePath)
246
-
250
+
247
251
  if (originalFilePath) {
248
252
  await promises.unlink(originalFilePath)
249
253
  }
250
-
254
+
251
255
  logger?.debug('removed tmp files')
252
256
  }
253
257
  catch (error) {
254
258
  logger?.warn('failed to remove tmp file')
255
259
  }
256
260
  })
257
-
261
+
258
262
  const obj = WAProto.Message.fromObject({
259
263
  [`${mediaType}Message`]: MessageTypeProto[mediaType].fromObject({
260
264
  url: mediaUrl,
@@ -268,17 +272,17 @@ const prepareWAMessageMedia = async (message, options) => {
268
272
  media: undefined
269
273
  })
270
274
  })
271
-
275
+
272
276
  if (uploadData.ptv) {
273
277
  obj.ptvMessage = obj.videoMessage
274
278
  delete obj.videoMessage
275
279
  }
276
-
280
+
277
281
  if (cacheableKey) {
278
- logger?.debug({ cacheableKey }, 'set cache');
282
+ logger?.debug({ cacheableKey }, 'set cache')
279
283
  await options.mediaCache.set(cacheableKey, WAProto.Message.encode(obj).finish())
280
284
  }
281
-
285
+
282
286
  return obj
283
287
  }
284
288
 
@@ -292,7 +296,7 @@ const prepareAlbumMessageContent = async (jid, albums, options) => {
292
296
  }
293
297
 
294
298
  const validCount = albums.filter(m => ('image' in m) || ('video' in m)).length
295
-
299
+
296
300
  if (validCount === 0) {
297
301
  throw new Error("albums contains no valid media. Use 'image' or 'video' keys.")
298
302
  }
@@ -411,879 +415,916 @@ const generateForwardMessageContent = (message, forceForward) => {
411
415
  return content
412
416
  }
413
417
 
414
- const generateWAMessageContent = async (message, options) => {
415
- let m = {}
416
- if ('text' in message) {
417
- const extContent = { text: message.text }
418
- let urlInfo = message.linkPreview
419
-
420
- if (typeof urlInfo === 'undefined') {
421
- urlInfo = await generateLinkPreviewIfRequired(message.text, options.getUrlInfo, options.logger)
422
- }
423
-
424
- if (urlInfo) {
425
- extContent.canonicalUrl = urlInfo['canonical-url']
426
- extContent.matchedText = urlInfo['matched-text']
427
- extContent.jpegThumbnail = urlInfo.jpegThumbnail
428
- extContent.description = urlInfo.description
429
- extContent.title = urlInfo.title
430
- extContent.previewType = 0
431
- const img = urlInfo.highQualityThumbnail
432
-
433
- if (img) {
434
- extContent.thumbnailDirectPath = img.directPath
435
- extContent.mediaKey = img.mediaKey
436
- extContent.mediaKeyTimestamp = img.mediaKeyTimestamp
437
- extContent.thumbnailWidth = img.width
438
- extContent.thumbnailHeight = img.height
439
- extContent.thumbnailSha256 = img.fileSha256
440
- extContent.thumbnailEncSha256 = img.fileEncSha256
441
- }
442
- }
443
-
444
- if (options.backgroundColor) {
445
- extContent.backgroundArgb = await assertColor(options.backgroundColor)
446
- }
447
-
448
- if (options.textColor) {
449
- extContent.textArgb = await assertColor(options.textColor)
450
- }
418
+ const hasNonNullishProperty = (message, key) => {
419
+ return (
420
+ typeof message === 'object' &&
421
+ message !== null &&
422
+ key in message &&
423
+ message[key] !== null &&
424
+ message[key] !== undefined
425
+ )
426
+ }
451
427
 
452
- if (options.font) {
453
- extContent.font = options.font
454
- }
428
+ function hasOptionalProperty(obj, key) {
429
+ return (
430
+ typeof obj === 'object' &&
431
+ obj !== null &&
432
+ key in obj &&
433
+ obj[key] !== null
434
+ )
435
+ }
455
436
 
456
- extContent.contextInfo = {
457
- ...(message.contextInfo || {}),
458
- ...(message.mentions ? { mentionedJid: message.mentions } : {})
459
- }
437
+ const generateWAMessageContent = async (message, options) => {
438
+ const hasMedia = [
439
+ "image",
440
+ "video",
441
+ "audio",
442
+ "sticker",
443
+ "document",
444
+ ].some((key) => hasNonNullishProperty(message, key))
460
445
 
461
- m.extendedTextMessage = extContent
462
- }
446
+ let m = {}
463
447
 
464
- else if ('contacts' in message) {
448
+ if (hasNonNullishProperty(message, "contacts")) {
465
449
  const contactLen = message.contacts.contacts.length
466
450
 
467
451
  let contactMessage
468
452
 
469
453
  if (!contactLen) {
470
- throw new Boom('require atleast 1 contact', { statusCode: 400 })
454
+ throw new Boom("require atleast 1 contact", { statusCode: 400 })
471
455
  }
472
456
 
473
457
  if (contactLen === 1) {
474
458
  contactMessage = {
475
- contactMessage: WAProto.Message.ContactMessage.fromObject(message.contacts.contacts[0])
459
+ contactMessage: WAProto.Message.ContactMessage.fromObject(
460
+ message.contacts.contacts[0]
461
+ ),
476
462
  }
477
- }
478
-
479
- else {
463
+ } else {
480
464
  contactMessage = {
481
- contactsArrayMessage: WAProto.Message.ContactsArrayMessage.fromObject(message.contacts)
465
+ contactsArrayMessage: WAProto.Message.ContactsArrayMessage.fromObject(
466
+ message.contacts
467
+ ),
482
468
  }
483
469
  }
484
470
 
485
- const [type] = Object.keys(contactMessage)
486
-
487
- contactMessage[type].contextInfo = {
488
- ...(message.contextInfo || {}),
489
- ...(message.mentions ? { mentionedJid: message.mentions } : {})
490
- }
491
-
492
471
  m = contactMessage
493
- }
472
+ } else if (hasNonNullishProperty(message, "contacts")) {
473
+ const contactLen = message.contacts.contacts.length
494
474
 
495
- else if ('location' in message) {
496
- let locationMessage
475
+ let contactMessage
497
476
 
498
- if (message.live) {
499
- locationMessage = {
500
- liveLocationMessage: WAProto.Message.LiveLocationMessage.fromObject(message.location)
501
- }
477
+ if (!contactLen) {
478
+ throw new Boom("require atleast 1 contact", { statusCode: 400 })
502
479
  }
503
480
 
504
- else {
505
- locationMessage = {
506
- locationMessage: WAProto.Message.LocationMessage.fromObject(message.location)
481
+ if (contactLen === 1) {
482
+ contactMessage = {
483
+ contactMessage: WAProto.Message.ContactMessage.fromObject(
484
+ message.contacts.contacts[0]
485
+ ),
486
+ }
487
+ } else {
488
+ contactMessage = {
489
+ contactsArrayMessage: WAProto.Message.ContactsArrayMessage.fromObject(
490
+ message.contacts
491
+ ),
507
492
  }
508
493
  }
509
494
 
510
- const [type] = Object.keys(locationMessage)
511
-
512
- locationMessage[type].contextInfo = {
513
- ...(message.contextInfo || {}),
514
- ...(message.mentions ? { mentionedJid: message.mentions } : {})
495
+ m = contactMessage
496
+ } else if (hasNonNullishProperty(message, "location")) {
497
+ let locationMessage
498
+
499
+ if (message.live) {
500
+ locationMessage = {
501
+ liveLocationMessage: WAProto.Message.LiveLocationMessage.fromObject(
502
+ message.location
503
+ ),
504
+ }
505
+ } else {
506
+ locationMessage = {
507
+ locationMessage: WAProto.Message.LocationMessage.fromObject(
508
+ message.location
509
+ ),
510
+ }
515
511
  }
516
512
 
517
513
  m = locationMessage
518
- }
519
-
520
- else if ('react' in message) {
514
+ } else if (hasNonNullishProperty(message, "react")) {
521
515
  if (!message.react.senderTimestampMs) {
522
516
  message.react.senderTimestampMs = Date.now()
523
517
  }
524
518
 
525
- m.reactionMessage = WAProto.Message.ReactionMessage.fromObject(message.react)
526
- }
527
-
528
- else if ('delete' in message) {
519
+ m.reactionMessage = WAProto.Message.ReactionMessage.fromObject(
520
+ message.react
521
+ )
522
+ } else if (hasNonNullishProperty(message, "delete")) {
529
523
  m.protocolMessage = {
530
524
  key: message.delete,
531
- type: WAProto.Message.ProtocolMessage.Type.REVOKE
525
+ type: WAProto.Message.ProtocolMessage.Type.REVOKE,
532
526
  }
533
- }
534
-
535
- else if ('forward' in message) {
536
- const mess = generateForwardMessageContent(message.forward, message.force)
537
- const [type] = Object.keys(mess)
538
-
539
- mess[type].contextInfo = {
540
- ...(message.contextInfo || {}),
541
- ...(message.mentions ? { mentionedJid: message.mentions } : {})
527
+ } else if (hasNonNullishProperty(message, "sharePhoneNumber")) {
528
+ m.protocolMessage = {
529
+ type: WAProto.Message.ProtocolMessage.Type.SHARE_PHONE_NUMBER,
542
530
  }
543
-
544
- m = mess
545
- }
546
-
547
- else if ('disappearingMessagesInChat' in message) {
548
- const exp = typeof message.disappearingMessagesInChat === 'boolean' ?
549
- (message.disappearingMessagesInChat ? WA_DEFAULT_EPHEMERAL : 0) :
550
- message.disappearingMessagesInChat
531
+ } else if (hasNonNullishProperty(message, "requestPhoneNumber")) {
532
+ m.requestPhoneNumberMessage = {}
533
+ } else if (hasNonNullishProperty(message, "forward")) {
534
+ m = generateForwardMessageContent(message.forward, message.force)
535
+ } else if (hasNonNullishProperty(message, "disappearingMessagesInChat")) {
536
+ const exp =
537
+ typeof message.disappearingMessagesInChat === "boolean"
538
+ ? message.disappearingMessagesInChat
539
+ ? WA_DEFAULT_EPHEMERAL
540
+ : 0
541
+ : message.disappearingMessagesInChat
551
542
  m = prepareDisappearingMessageSettingContent(exp)
552
- }
553
-
554
- else if ('groupInvite' in message) {
555
- m.groupInviteMessage = {}
543
+ } else if (hasNonNullishProperty(message, "groupInvite")) {
544
+ m.groupInviteMessage = {}
556
545
 
557
546
  m.groupInviteMessage.inviteCode = message.groupInvite.code
558
547
  m.groupInviteMessage.inviteExpiration = message.groupInvite.expiration
559
548
  m.groupInviteMessage.caption = message.groupInvite.caption
560
549
  m.groupInviteMessage.groupJid = message.groupInvite.jid
561
- m.groupInviteMessage.groupName = message.groupInvite.name
562
- m.groupInviteMessage.contextInfo = message.contextInfo
550
+ m.groupInviteMessage.groupName = message.groupInvite.name
551
+ m.groupInviteMessage.contextInfo = message.contextInfo
563
552
 
564
553
  if (options.getProfilePicUrl) {
565
- const pfpUrl = await options.getProfilePicUrl(message.groupInvite.jid)
566
- const { thumbnail } = await generateThumbnail(pfpUrl, 'image')
554
+ const pfpUrl = await options.getProfilePicUrl(
555
+ message.groupInvite.jid
556
+ )
557
+ const { thumbnail } = await generateThumbnail(pfpUrl, "image")
567
558
  m.groupInviteMessage.jpegThumbnail = thumbnail
568
559
  }
569
-
570
- m.groupInviteMessage.contextInfo = {
571
- ...(message.contextInfo || {}),
572
- ...(message.mentions ? { mentionedJid: message.mentions } : {})
573
- }
574
- }
575
-
576
- else if ('adminInvite' in message) {
577
- m.newsletterAdminInviteMessage = {}
560
+ } else if (hasNonNullishProperty(message, "adminInvite")) {
561
+ m.newsletterAdminInviteMessage = {}
578
562
 
579
563
  m.newsletterAdminInviteMessage.newsletterJid = message.adminInvite.jid
580
- m.newsletterAdminInviteMessage.newsletterName= message.adminInvite.name
564
+ m.newsletterAdminInviteMessage.newsletterName =
565
+ message.adminInvite.name
581
566
  m.newsletterAdminInviteMessage.caption = message.adminInvite.caption
582
- m.newsletterAdminInviteMessage.inviteExpiration = message.adminInvite.expiration
583
- m.newsletterAdminInviteMessage.contextInfo = message.contextInfo
567
+ m.newsletterAdminInviteMessage.inviteExpiration =
568
+ message.adminInvite.expiration
569
+ m.newsletterAdminInviteMessage.contextInfo = message.contextInfo
584
570
 
585
571
  if (options.getProfilePicUrl) {
586
- const pfpUrl = await options.getProfilePicUrl(message.adminInvite.jid)
587
- const { thumbnail } = await generateThumbnail(pfpUrl, 'image')
572
+ const pfpUrl = await options.getProfilePicUrl(
573
+ message.adminInvite.jid
574
+ )
575
+ const { thumbnail } = await generateThumbnail(pfpUrl, "image")
588
576
  m.newsletterAdminInviteMessage.jpegThumbnail = thumbnail
589
577
  }
590
-
591
- m.newsletterAdminInviteMessage.contextInfo = {
592
- ...(message.contextInfo || {}),
593
- ...(message.mentions ? { mentionedJid: message.mentions } : {})
594
- }
595
- }
596
-
597
- else if ('pin' in message) {
598
- m.pinInChatMessage = {}
599
- m.messageContextInfo = {}
600
-
601
- m.pinInChatMessage.key = message.pin.key
602
- m.pinInChatMessage.type = message.pin?.type || 1
603
- m.pinInChatMessage.senderTimestampMs = message.pin?.time || Date.now()
604
- m.messageContextInfo.messageAddOnDurationInSecs = message.pin.type === 1 ? message.pin.time || 86400 : 0
605
- m.messageContextInfo.messageAddOnExpiryType = proto.MessageContextInfo.MessageAddonExpiryType.STATIC
606
- }
607
-
608
- else if ('keep' in message) {
578
+ } else if (hasNonNullishProperty(message, "keep")) {
609
579
  m.keepInChatMessage = {}
610
580
 
611
581
  m.keepInChatMessage.key = message.keep.key
612
582
  m.keepInChatMessage.keepType = message.keep?.type || 1
613
583
  m.keepInChatMessage.timestampMs = message.keep?.time || Date.now()
614
- }
615
-
616
- else if ('call' in message) {
584
+ } else if (hasNonNullishProperty(message, "call")) {
617
585
  m.scheduledCallCreationMessage = {}
618
586
 
619
- m.scheduledCallCreationMessage.scheduledTimestampMs = message.call?.time || Date.now()
587
+ m.scheduledCallCreationMessage.scheduledTimestampMs =
588
+ message.call?.time || Date.now()
620
589
  m.scheduledCallCreationMessage.callType = message.call?.type || 1
621
- m.scheduledCallCreationMessage.title = message.call?.name || 'Call Creation'
590
+ m.scheduledCallCreationMessage.title =
591
+ message.call?.name || "Call Creation"
592
+ } else if (hasNonNullishProperty(message, "paymentInvite")) {
593
+ m.messageContextInfo = {}
594
+ m.paymentInviteMessage = {}
622
595
 
623
- m.scheduledCallCreationMessage.contextInfo = {
624
- ...(message.contextInfo || {}),
625
- ...(message.mentions ? { mentionedJid: message.mentions } : {})
596
+ m.paymentInviteMessage.expiryTimestamp =
597
+ message.paymentInvite?.expiry || 0
598
+ m.paymentInviteMessage.serviceType = message.paymentInvite?.type || 2
599
+ } else if (hasNonNullishProperty(message, "ptv")) {
600
+ const { videoMessage } = await prepareWAMessageMedia(
601
+ { video: message.video },
602
+ options
603
+ )
604
+
605
+ m.ptvMessage = videoMessage
606
+ } else if (hasNonNullishProperty(message, "order")) {
607
+ m.orderMessage = WAProto.Message.OrderMessage.fromObject(message.order)
608
+ } else if (hasNonNullishProperty(message, "product")) {
609
+ const { imageMessage } = await prepareWAMessageMedia(
610
+ { image: message.product.productImage },
611
+ options
612
+ )
613
+
614
+ m.productMessage = WAProto.Message.ProductMessage.fromObject({
615
+ ...message,
616
+ product: {
617
+ ...message.product,
618
+ productImage: imageMessage,
619
+ },
620
+ })
621
+ } else if (hasNonNullishProperty(message, "album")) {
622
+ const imageMessages = message.album.filter((item) => "image" in item)
623
+ const videoMessages = message.album.filter((item) => "video" in item)
624
+
625
+ m.albumMessage = WAProto.Message.AlbumMessage.fromObject({
626
+ expectedImageCount: imageMessages.length,
627
+ expectedVideoCount: videoMessages.length,
628
+ })
629
+ } else if (hasNonNullishProperty(message, "event")) {
630
+ m.eventMessage = WAProto.Message.EventMessage.fromObject(message.event)
631
+
632
+ if (!message.event.startTime) {
633
+ m.eventMessage.startTime = unixTimestampSeconds() + 86400
634
+ }
635
+
636
+ if (options.getCallLink && message.event.call) {
637
+ const link = await options.getCallLink(
638
+ message.event.call,
639
+ m.eventMessage.startTime
640
+ )
641
+ m.eventMessage.joinLink = link
642
+ }
643
+ } else if (hasNonNullishProperty(message, "pollResult")) {
644
+ if (!Array.isArray(message.pollResult.values)) {
645
+ throw new Boom("Invalid pollResult values", { statusCode: 400 })
626
646
  }
627
- }
628
647
 
629
- else if ('paymentInvite' in message) {
630
- m.messageContextInfo = {}
631
- m.paymentInviteMessage = {}
648
+ const pollResultSnapshotMessage = {
649
+ name: message.pollResult.name,
650
+ pollVotes: message.pollResult.values.map(
651
+ ([optionName, optionVoteCount]) => ({
652
+ optionName,
653
+ optionVoteCount,
654
+ })
655
+ ),
656
+ }
632
657
 
633
- m.paymentInviteMessage.expiryTimestamp = message.paymentInvite?.expiry || 0
634
- m.paymentInviteMessage.serviceType = message.paymentInvite?.type || 2
658
+ m.pollResultSnapshotMessage = pollResultSnapshotMessage
659
+ } else if (hasNonNullishProperty(message, "poll")) {
660
+ if (!Array.isArray(message.poll.values)) {
661
+ throw new Boom("Invalid poll values", { statusCode: 400 })
662
+ }
663
+
664
+ if (
665
+ message.poll.selectableCount < 0 ||
666
+ message.poll.selectableCount > message.poll.values.length
667
+ ) {
668
+ throw new Boom(
669
+ `poll.selectableCount in poll should be >= 0 and <= ${message.poll.values.length}`,
670
+ { statusCode: 400 }
671
+ )
672
+ }
635
673
 
636
- m.paymentInviteMessage.contextInfo = {
637
- ...(message.contextInfo || {}),
638
- ...(message.mentions ? { mentionedJid: message.mentions } : {})
674
+ const pollCreationMessage = {
675
+ name: message.poll.name,
676
+ selectableOptionsCount: message.poll?.selectableCount || 0,
677
+ options: message.poll.values.map((optionName) => ({ optionName })),
639
678
  }
640
- }
641
679
 
642
- else if ('buttonReply' in message) {
680
+ if (message.poll?.toAnnouncementGroup) {
681
+ m.pollCreationMessageV2 = pollCreationMessage
682
+ } else {
683
+ if (message.poll.selectableCount > 0) {
684
+ m.pollCreationMessageV3 = pollCreationMessage
685
+ } else {
686
+ m.pollCreationMessage = pollCreationMessage
687
+ }
688
+ }
689
+ } else if (hasNonNullishProperty(message, "payment")) {
690
+ const requestPaymentMessage = {
691
+ amount: {
692
+ currencyCode: message.payment?.currency || "IDR",
693
+ offset: message.payment?.offset || 0,
694
+ value: message.payment?.amount || 999999999,
695
+ },
696
+ expiryTimestamp: message.payment?.expiry || 0,
697
+ amount1000: message.payment?.amount || 999999999 * 1000,
698
+ currencyCodeIso4217: message.payment?.currency || "IDR",
699
+ requestFrom: message.payment?.from || "0@s.whatsapp.net",
700
+ noteMessage: {
701
+ extendedTextMessage: {
702
+ text: message.payment?.note || "Notes",
703
+ },
704
+ },
705
+ background: {
706
+ placeholderArgb:
707
+ message.payment?.image?.placeholderArgb || 4278190080,
708
+ textArgb: message.payment?.image?.textArgb || 4294967295,
709
+ subtextArgb: message.payment?.image?.subtextArgb || 4294967295,
710
+ type: 1,
711
+ },
712
+ }
713
+
714
+ m.requestPaymentMessage = requestPaymentMessage
715
+ } else if (hasNonNullishProperty(message, "stickerPack")) {
716
+ const {
717
+ stickers,
718
+ cover,
719
+ name,
720
+ publisher,
721
+ packId,
722
+ description,
723
+ } = message.stickerPack
724
+
725
+ const { zip } = require("fflate")
726
+
727
+ const stickerData = {}
728
+ const stickerPromises = stickers.map(async (s, i) => {
729
+ const { stream } = await getStream(s.sticker)
730
+ const buffer = await toBuffer(stream)
731
+ const hash = sha256(buffer).toString("base64url")
732
+ const fileName = `${i.toString().padStart(2, "0")}_${hash}.webp`
733
+
734
+ stickerData[fileName] = [new Uint8Array(buffer), { level: 0 }]
735
+
736
+ return {
737
+ fileName,
738
+ mimetype: "image/webp",
739
+ isAnimated: s.isAnimated || false,
740
+ isLottie: s.isLottie || false,
741
+ emojis: s.emojis || [],
742
+ accessibilityLabel: s.accessibilityLabel || "",
743
+ }
744
+ })
745
+
746
+ const stickerMetadata = await Promise.all(stickerPromises)
747
+
748
+ const zipBuffer = await new Promise((resolve, reject) => {
749
+ zip(stickerData, (err, data) => {
750
+ if (err) {
751
+ reject(err)
752
+ } else {
753
+ resolve(Buffer.from(data))
754
+ }
755
+ })
756
+ })
757
+
758
+ const coverBuffer = await toBuffer((await getStream(cover)).stream)
759
+
760
+ const [stickerPackUpload, coverUpload] = await Promise.all([
761
+ encryptedStream(zipBuffer, "sticker-pack", {
762
+ logger: options.logger,
763
+ opts: options.options,
764
+ }),
765
+ prepareWAMessageMedia(
766
+ { image: coverBuffer },
767
+ { ...options, mediaTypeOverride: "image" }
768
+ ),
769
+ ])
770
+
771
+ const stickerPackUploadResult = await options.upload(
772
+ stickerPackUpload.encFilePath,
773
+ {
774
+ fileEncSha256B64: stickerPackUpload.fileEncSha256.toString(
775
+ "base64"
776
+ ),
777
+ mediaType: "sticker-pack",
778
+ timeoutMs: options.mediaUploadTimeoutMs,
779
+ }
780
+ )
781
+
782
+ const coverImage = coverUpload.imageMessage
783
+ const imageDataHash = sha256(coverBuffer).toString("base64")
784
+ const stickerPackId = packId || generateMessageID()
785
+
786
+ m.stickerPackMessage = {
787
+ name,
788
+ publisher,
789
+ stickerPackId,
790
+ packDescription: description,
791
+ stickerPackOrigin:
792
+ proto.Message.StickerPackMessage.StickerPackOrigin.THIRD_PARTY,
793
+ stickerPackSize: stickerPackUpload.fileLength,
794
+ stickers: stickerMetadata,
795
+ fileSha256: stickerPackUpload.fileSha256,
796
+ fileEncSha256: stickerPackUpload.fileEncSha256,
797
+ mediaKey: stickerPackUpload.mediaKey,
798
+ directPath: stickerPackUploadResult.directPath,
799
+ fileLength: stickerPackUpload.fileLength,
800
+ mediaKeyTimestamp: unixTimestampSeconds(),
801
+ trayIconFileName: `${stickerPackId}.png`,
802
+ imageDataHash,
803
+ thumbnailDirectPath: coverImage.directPath,
804
+ thumbnailFileSha256: coverImage.fileSha256,
805
+ thumbnailFileEncSha256: coverImage.fileEncSha256,
806
+ thumbnailHeight: coverImage.height,
807
+ thumbnailWidth: coverImage.width,
808
+ }
809
+ } else if (hasNonNullishProperty(message, "buttonReply")) {
643
810
  switch (message.type) {
644
- case 'list':
811
+ case "list":
645
812
  m.listResponseMessage = {
646
813
  title: message.buttonReply.title,
647
814
  description: message.buttonReply.description,
648
815
  singleSelectReply: {
649
- selectedRowId: message.buttonReply.rowId
650
- },
651
- lisType: proto.Message.ListResponseMessage.ListType.SINGLE_SELECT
816
+ selectedRowId: message.buttonReply.rowId,
817
+ },
818
+ lisType:
819
+ proto.Message.ListResponseMessage.ListType
820
+ .SINGLE_SELECT,
652
821
  }
653
822
  break
654
- case 'template':
823
+ case "template":
655
824
  m.templateButtonReplyMessage = {
656
825
  selectedDisplayText: message.buttonReply.displayText,
657
826
  selectedId: message.buttonReply.id,
658
- selectedIndex: message.buttonReply.index
827
+ selectedIndex: message.buttonReply.index,
659
828
  }
660
829
  break
661
- case 'plain':
830
+ case "plain":
662
831
  m.buttonsResponseMessage = {
663
832
  selectedButtonId: message.buttonReply.id,
664
833
  selectedDisplayText: message.buttonReply.displayText,
665
- type: proto.Message.ButtonsResponseMessage.Type.DISPLAY_TEXT
834
+ type:
835
+ proto.Message.ButtonsResponseMessage.Type.DISPLAY_TEXT,
666
836
  }
667
837
  break
668
- case 'interactive':
838
+ case "interactive":
669
839
  m.interactiveResponseMessage = {
670
840
  body: {
671
- text: message.buttonReply.displayText,
672
- format: proto.Message.InteractiveResponseMessage.Body.Format.EXTENSIONS_1
673
- },
841
+ text: message.buttonReply.displayText,
842
+ format:
843
+ proto.Message.InteractiveResponseMessage.Body.Format
844
+ .EXTENSIONS_1,
845
+ },
674
846
  nativeFlowResponseMessage: {
675
- name: message.buttonReply.nativeFlows.name,
676
- paramsJson: message.buttonReply.nativeFlows.paramsJson,
677
- version: message.buttonReply.nativeFlows.version
678
- }
847
+ name: message.buttonReply.nativeFlows.name,
848
+ paramsJson: message.buttonReply.nativeFlows.paramsJson,
849
+ version: message.buttonReply.nativeFlows.version,
850
+ },
679
851
  }
680
- break
852
+ break
853
+ }
854
+ } else if (hasNonNullishProperty(message, "sections")) {
855
+ m.listMessage = {
856
+ title: message.title,
857
+ buttonText: message.buttonText,
858
+ footerText: message.footer,
859
+ description: message.text,
860
+ sections: message.sections,
861
+ listType: proto.Message.ListMessage.ListType.SINGLE_SELECT,
862
+ }
863
+ } else if (hasNonNullishProperty(message, "productList")) {
864
+ const thumbnail = message.thumbnail
865
+ ? await generateThumbnail(message.thumbnail, "image")
866
+ : null
867
+
868
+ m.listMessage = {
869
+ title: message.title,
870
+ buttonText: message.buttonText,
871
+ footerText: message.footer,
872
+ description: message.text,
873
+ productListInfo: {
874
+ productSections: message.productList,
875
+ headerImage: {
876
+ productId: message.productList[0].products[0].productId,
877
+ jpegThumbnail: thumbnail?.thumbnail || null,
878
+ },
879
+ businessOwnerJid: message.businessOwnerJid,
880
+ },
881
+ listType: proto.Message.ListMessage.ListType.PRODUCT_LIST,
882
+ }
883
+ } else if (hasNonNullishProperty(message, "buttons")) {
884
+ const buttonsMessage = {
885
+ buttons: message.buttons.map((b) => ({
886
+ ...b,
887
+ type: proto.Message.ButtonsMessage.Button.Type.RESPONSE,
888
+ })),
681
889
  }
682
- }
683
-
684
- else if ('ptv' in message && message.ptv) {
685
- const { videoMessage } = await prepareWAMessageMedia({ video: message.video }, options)
686
-
687
- m.ptvMessage = videoMessage
688
- }
689
890
 
690
- else if ('album' in message) {
691
- const imageMessages = message.album.filter(item => 'image' in item)
692
- const videoMessages = message.album.filter(item => 'video' in item)
891
+ if (hasNonNullishProperty(message, "text")) {
892
+ buttonsMessage.contentText = message.text
893
+ buttonsMessage.headerType =
894
+ proto.Message.ButtonsMessage.HeaderType.EMPTY
895
+ } else {
896
+ if (hasNonNullishProperty(message, "caption")) {
897
+ buttonsMessage.contentText = message.caption
898
+ }
693
899
 
694
- m.albumMessage = WAProto.Message.AlbumMessage.fromObject({
695
- expectedImageCount: imageMessages.length,
696
- expectedVideoCount: videoMessages.length
697
- })
698
- }
900
+ const type = Object.keys(m)[0].replace("Message", "").toUpperCase()
699
901
 
700
- else if ('order' in message) {
701
- m.orderMessage = WAProto.Message.OrderMessage.fromObject(message.order)
902
+ buttonsMessage.headerType =
903
+ proto.Message.ButtonsMessage.HeaderType[type]
702
904
 
703
- m.orderMessage.contextInfo = {
704
- ...(message.contextInfo || {}),
705
- ...(message.mentions ? { mentionedJid: message.mentions } : {})
905
+ Object.assign(buttonsMessage, m)
706
906
  }
707
- }
708
-
709
- else if ('event' in message) {
710
- m.eventMessage = WAProto.Message.EventMessage.fromObject(message.event)
711
-
712
- if (!message.event.startTime) {
713
- m.eventMessage.startTime = unixTimestampSeconds() + 86400
714
- }
715
-
716
- if (options.getCallLink && message.event.call) {
717
- const link = await options.getCallLink(message.event.call, m.eventMessage.startTime)
718
- m.eventMessage.joinLink = link
719
- }
720
907
 
908
+ if (hasNonNullishProperty(message, "title")) {
909
+ buttonsMessage.text = message.title
910
+ buttonsMessage.headerType =
911
+ proto.Message.ButtonsMessage.HeaderType.TEXT
912
+ }
721
913
 
722
- m.eventMessage.contextInfo = {
723
- ...(message.contextInfo || {}),
724
- ...(message.mentions ? { mentionedJid: message.mentions } : {})
914
+ if (hasNonNullishProperty(message, "footer")) {
915
+ buttonsMessage.footerText = message.footer
725
916
  }
726
- }
727
917
 
728
- else if ('product' in message) {
729
- const { imageMessage } = await prepareWAMessageMedia({ image: message.product.productImage }, options)
918
+ m = { buttonsMessage }
919
+ } else if (hasNonNullishProperty(message, "templateButtons")) {
920
+ const hydratedTemplate = {
921
+ hydratedButtons: message.templateButtons,
922
+ }
730
923
 
731
- m.productMessage = WAProto.Message.ProductMessage.fromObject({
732
- ...message,
733
- product: {
734
- ...message.product,
735
- productImage: imageMessage,
924
+ if (hasNonNullishProperty(message, "text")) {
925
+ hydratedTemplate.hydratedContentText = message.text
926
+ } else {
927
+ if (hasNonNullishProperty(message, "caption")) {
928
+ hydratedTemplate.hydratedContentText = message.caption
736
929
  }
737
- })
738
930
 
739
- m.productMessage.contextInfo = {
740
- ...(message.contextInfo || {}),
741
- ...(message.mentions ? { mentionedJid: message.mentions } : {})
931
+ Object.assign(msg, m)
742
932
  }
743
- }
744
933
 
745
- else if ('pollResult' in message) {
746
- if (!Array.isArray(message.pollResult.values)) {
747
- throw new Boom('Invalid pollResult values', { statusCode: 400 })
934
+ if (hasNonNullishProperty(message, "footer")) {
935
+ hydratedTemplate.hydratedFooterText = message.footer
748
936
  }
749
937
 
750
- const pollResultSnapshotMessage = {
751
- name: message.pollResult.name,
752
- pollVotes: message.pollResult.values.map(([optionName, optionVoteCount]) => ({
753
- optionName,
754
- optionVoteCount
755
- }))
756
- }
757
-
758
- pollResultSnapshotMessage.contextInfo = {
759
- ...(message.contextInfo || {}),
760
- ...(message.mentions ? { mentionedJid: message.mentions } : {})
938
+ m = { templateMessage: { hydratedTemplate } }
939
+ } else if (hasNonNullishProperty(message, "interactiveButtons")) {
940
+ const interactiveMessage = {
941
+ nativeFlowMessage: {
942
+ buttons: message.interactiveButtons,
943
+ },
761
944
  }
762
945
 
763
- m.pollResultSnapshotMessage = pollResultSnapshotMessage
764
- }
765
-
766
- else if ('poll' in message) {
767
- if (!Array.isArray(message.poll.values)) {
768
- throw new Boom('Invalid poll values', { statusCode: 400 })
946
+ if (hasNonNullishProperty(message, "text")) {
947
+ interactiveMessage.body = {
948
+ text: message.text,
949
+ }
769
950
  }
770
951
 
771
- if (message.poll.selectableCount < 0
772
- || message.poll.selectableCount > message.poll.values.length) {
773
- throw new Boom(`poll.selectableCount in poll should be >= 0 and <= ${message.poll.values.length}`, { statusCode: 400 })
774
- }
952
+ if (hasNonNullishProperty(message, "title")) {
953
+ interactiveMessage.header = {
954
+ title: message.title,
955
+ subtitle: null,
956
+ hasMediaAttachment: false,
957
+ }
775
958
 
776
- const pollCreationMessage = {
777
- name: message.poll.name,
778
- selectableOptionsCount: message.poll?.selectableCount || 0,
779
- options: message.poll.values.map(optionName => ({ optionName })),
780
- }
959
+ if (hasNonNullishProperty(message, "subtitle")) {
960
+ interactiveMessage.header.subtitle = message.subtitle
961
+ }
962
+ } else {
963
+ if (hasNonNullishProperty(message, "caption")) {
964
+ interactiveMessage.body = {
965
+ text: message.caption,
966
+ }
781
967
 
782
- pollCreationMessage.contextInfo = {
783
- ...(message.contextInfo || {}),
784
- ...(message.mentions ? { mentionedJid: message.mentions } : {})
785
- }
968
+ interactiveMessage.header = {
969
+ title: null,
970
+ subtitle: null,
971
+ hasMediaAttachment: false,
972
+ ...Object.assign(interactiveMessage, m),
973
+ }
786
974
 
787
- if(message.poll?.toAnnouncementGroup) {
788
- m.pollCreationMessageV2 = pollCreationMessage
789
- } else {
790
- if(message.poll.selectableCount > 0) {
791
- m.pollCreationMessageV3 = pollCreationMessage
792
- } else {
793
- m.pollCreationMessage = pollCreationMessage
794
- }
975
+ if (hasNonNullishProperty(message, "title")) {
976
+ interactiveMessage.header.title = message.title
795
977
  }
796
- }
797
-
798
- else if ('payment' in message) {
799
- const requestPaymentMessage = {
800
- amount: {
801
- currencyCode: message.payment?.currency || 'IDR',
802
- offset: message.payment?.offset || 0,
803
- value: message.payment?.amount || 999999999
804
- },
805
- expiryTimestamp: message.payment?.expiry || 0,
806
- amount1000: message.payment?.amount || 999999999 * 1000,
807
- currencyCodeIso4217: message.payment?.currency || 'IDR',
808
- requestFrom: message.payment?.from || '0@s.whatsapp.net',
809
- noteMessage: {
810
- extendedTextMessage: {
811
- text: message.payment?.note || 'Notes'
812
- }
813
- },
814
- background: {
815
- placeholderArgb: message.payment?.image?.placeholderArgb || 4278190080,
816
- textArgb: message.payment?.image?.textArgb || 4294967295,
817
- subtextArgb: message.payment?.image?.subtextArgb || 4294967295,
818
- type: 1
819
- }
820
- }
821
978
 
822
- requestPaymentMessage.noteMessage.extendedTextMessage.contextInfo = {
823
- ...(message.contextInfo || {}),
824
- ...(message.mentions ? { mentionedJid: message.mentions } : {})
825
- }
979
+ if (hasNonNullishProperty(message, "subtitle")) {
980
+ interactiveMessage.header.subtitle = message.subtitle
981
+ }
826
982
 
827
- m.requestPaymentMessage = requestPaymentMessage
828
- }
829
-
830
- else if ('stickerPack' in message) {
831
- const { stickers, cover, name, publisher, packId, description } = message.stickerPack
832
-
833
- const { zip } = require('fflate')
834
-
835
- const stickerData = {}
836
- const stickerPromises = stickers.map(async (s, i) => {
837
- const { stream } = await getStream(s.sticker)
838
- const buffer = await toBuffer(stream)
839
- const hash = sha256(buffer).toString('base64url')
840
- const fileName = `${i.toString().padStart(2, '0')}_${hash}.webp`
841
- stickerData[fileName] = [new Uint8Array(buffer), { level: 0 }]
842
-
843
- return {
844
- fileName,
845
- mimetype: 'image/webp',
846
- isAnimated: s.isAnimated || false,
847
- isLottie: s.isLottie || false,
848
- emojis: s.emojis || [],
849
- accessibilityLabel: s.accessibilityLabel || ''
850
- }
851
- })
852
-
853
- const stickerMetadata = await Promise.all(stickerPromises)
854
-
855
- const zipBuffer = await new Promise((resolve, reject) => {
856
- zip(stickerData, (err, data) => {
857
- if (err) {
858
- reject(err)
859
- } else {
860
- resolve(Buffer.from(data))
861
- }
862
- })
863
- })
864
-
865
- const coverBuffer = await toBuffer((await getStream(cover)).stream)
866
-
867
- const [stickerPackUpload, coverUpload] = await Promise.all([
868
- encryptedStream(zipBuffer, 'sticker-pack', { logger: options.logger, opts: options.options }),
869
- prepareWAMessageMedia({ image: coverBuffer }, { ...options, mediaTypeOverride: 'image' })
870
- ])
871
-
872
- const stickerPackUploadResult = await options.upload(stickerPackUpload.encFilePath, {
873
- fileEncSha256B64: stickerPackUpload.fileEncSha256.toString('base64'),
874
- mediaType: 'sticker-pack',
875
- timeoutMs: options.mediaUploadTimeoutMs
876
- })
877
-
878
-
879
- const coverImage = coverUpload.imageMessage
880
- const imageDataHash = sha256(coverBuffer).toString('base64')
881
- const stickerPackId = packId || generateMessageID()
882
-
883
- m.stickerPackMessage = {
884
- name,
885
- publisher,
886
- stickerPackId,
887
- packDescription: description,
888
- stickerPackOrigin: proto.Message.StickerPackMessage.StickerPackOrigin.THIRD_PARTY,
889
- stickerPackSize: stickerPackUpload.fileLength,
890
- stickers: stickerMetadata,
891
- fileSha256: stickerPackUpload.fileSha256,
892
- fileEncSha256: stickerPackUpload.fileEncSha256,
893
- mediaKey: stickerPackUpload.mediaKey,
894
- directPath: stickerPackUploadResult.directPath,
895
- fileLength: stickerPackUpload.fileLength,
896
- mediaKeyTimestamp: unixTimestampSeconds(),
897
- trayIconFileName: `${stickerPackId}.png`,
898
- imageDataHash,
899
- thumbnailDirectPath: coverImage.directPath,
900
- thumbnailFileSha256: coverImage.fileSha256,
901
- thumbnailFileEncSha256: coverImage.fileEncSha256,
902
- thumbnailHeight: coverImage.height,
903
- thumbnailWidth: coverImage.width
904
- }
905
-
906
- m.stickerPackMessage.contextInfo = {
907
- ...(message.contextInfo || {}),
908
- ...(message.mentions ? { mentionedJid: message.mentions } : {})
909
- }
910
- }
911
-
912
- else if ('sharePhoneNumber' in message) {
913
- m.protocolMessage = {
914
- type: WAProto.Message.ProtocolMessage.Type.SHARE_PHONE_NUMBER
983
+ if (hasNonNullishProperty(message, "hasMediaAttachment")) {
984
+ interactiveMessage.header.hasMediaAttachment = Boolean(
985
+ message.hasMediaAttachment
986
+ )
987
+ }
988
+ }
915
989
  }
916
- }
917
990
 
918
- else if ('requestPhoneNumber' in message) {
919
- m.requestPhoneNumberMessage = {}
920
- }
991
+ if (hasNonNullishProperty(message, "footer")) {
992
+ interactiveMessage.footer = {
993
+ text: message.footer,
994
+ }
995
+ }
921
996
 
922
- else {
923
- const mess = await prepareWAMessageMedia(message, options)
924
- const [type] = Object.keys(mess)
997
+ m = { interactiveMessage }
998
+ } else if (hasNonNullishProperty(message, "shop")) {
999
+ const interactiveMessage = {
1000
+ shopStorefrontMessage: {
1001
+ surface: message.shop.surface,
1002
+ id: message.shop.id,
1003
+ },
1004
+ }
925
1005
 
926
- mess[type].contextInfo = {
927
- ...(message.contextInfo || {}),
928
- ...(message.mentions ? { mentionedJid: message.mentions } : {})
1006
+ if (hasNonNullishProperty(message, "text")) {
1007
+ interactiveMessage.body = {
1008
+ text: message.text,
1009
+ }
929
1010
  }
930
1011
 
931
- m = mess
932
- }
1012
+ if (hasNonNullishProperty(message, "title")) {
1013
+ interactiveMessage.header = {
1014
+ title: message.title,
1015
+ subtitle: null,
1016
+ hasMediaAttachment: false,
1017
+ }
933
1018
 
934
- if ('sections' in message && !!message.sections) {
935
- const listMessage = {
936
- title: message.title,
937
- buttonText: message.buttonText,
938
- footerText: message.footer,
939
- description: message.text,
940
- sections: message.sections,
941
- listType: proto.Message.ListMessage.ListType.SINGLE_SELECT
942
- }
1019
+ if (hasNonNullishProperty(message, "subtitle")) {
1020
+ interactiveMessage.header.subtitle = message.subtitle
1021
+ }
1022
+ } else {
1023
+ if (hasNonNullishProperty(message, "caption")) {
1024
+ interactiveMessage.body = {
1025
+ text: message.caption,
1026
+ }
943
1027
 
944
- listMessage.contextInfo = {
945
- ...(message.contextInfo || {}),
946
- ...(message.mentions ? { mentionedJid: message.mentions } : {})
947
- }
1028
+ interactiveMessage.header = {
1029
+ title: null,
1030
+ subtitle: null,
1031
+ hasMediaAttachment: false,
1032
+ ...Object.assign(interactiveMessage, m),
1033
+ }
948
1034
 
949
- m = { listMessage }
950
- }
1035
+ if (hasNonNullishProperty(message, "title")) {
1036
+ interactiveMessage.header.title = message.title
1037
+ }
951
1038
 
952
- else if ('productList' in message && !!message.productList) {
953
- const thumbnail = message.thumbnail ? await generateThumbnail(message.thumbnail, 'image') : null
1039
+ if (hasNonNullishProperty(message, "subtitle")) {
1040
+ interactiveMessage.header.subtitle = message.subtitle
1041
+ }
954
1042
 
955
- const listMessage = {
956
- title: message.title,
957
- buttonText: message.buttonText,
958
- footerText: message.footer,
959
- description: message.text,
960
- productListInfo: {
961
- productSections: message.productList,
962
- headerImage: {
963
- productId: message.productList[0].products[0].productId,
964
- jpegThumbnail: thumbnail?.thumbnail || null
965
- },
966
- businessOwnerJid: message.businessOwnerJid
967
- },
968
- listType: proto.Message.ListMessage.ListType.PRODUCT_LIST
1043
+ if (hasNonNullishProperty(message, "hasMediaAttachment")) {
1044
+ interactiveMessage.header.hasMediaAttachment = Boolean(
1045
+ message.hasMediaAttachment
1046
+ )
1047
+ }
1048
+ }
969
1049
  }
970
1050
 
971
- listMessage.contextInfo = {
972
- ...(message.contextInfo || {}),
973
- ...(message.mentions ? { mentionedJid: message.mentions } : {})
1051
+ if (hasNonNullishProperty(message, "footer")) {
1052
+ interactiveMessage.footer = {
1053
+ text: message.footer,
1054
+ }
974
1055
  }
975
1056
 
976
- m = { listMessage }
977
- }
978
-
979
- else if ('buttons' in message && !!message.buttons) {
980
- const buttonsMessage = {
981
- buttons: message.buttons.map(b => ({ ...b, type: proto.Message.ButtonsMessage.Button.Type.RESPONSE }))
1057
+ m = { interactiveMessage }
1058
+ } else if (hasNonNullishProperty(message, "collection")) {
1059
+ const interactiveMessage = {
1060
+ collectionMessage: {
1061
+ bizJid: message.collection.bizJid,
1062
+ id: message.collection.id,
1063
+ messageVersion: message?.collection?.version,
1064
+ },
982
1065
  }
983
1066
 
984
- if ('text' in message) {
985
- buttonsMessage.contentText = message.text
986
- buttonsMessage.headerType = proto.Message.ButtonsMessage.HeaderType.EMPTY
1067
+ if (hasNonNullishProperty(message, "text")) {
1068
+ interactiveMessage.body = {
1069
+ text: message.text,
1070
+ }
987
1071
  }
988
1072
 
989
- else {
990
- if ('caption' in message) {
991
- buttonsMessage.contentText = message.caption
1073
+ if (hasNonNullishProperty(message, "title")) {
1074
+ interactiveMessage.header = {
1075
+ title: message.title,
1076
+ subtitle: null,
1077
+ hasMediaAttachment: false,
992
1078
  }
993
1079
 
994
- const type = Object.keys(m)[0].replace('Message', '').toUpperCase()
1080
+ if (hasNonNullishProperty(message, "subtitle")) {
1081
+ interactiveMessage.header.subtitle = message.subtitle
1082
+ }
1083
+ } else {
1084
+ if (hasNonNullishProperty(message, "caption")) {
1085
+ interactiveMessage.body = {
1086
+ text: message.caption,
1087
+ }
995
1088
 
996
- buttonsMessage.headerType = proto.Message.ButtonsMessage.HeaderType[type]
1089
+ interactiveMessage.header = {
1090
+ title: null,
1091
+ subtitle: null,
1092
+ hasMediaAttachment: false,
1093
+ ...Object.assign(interactiveMessage, m),
1094
+ }
997
1095
 
998
- Object.assign(buttonsMessage, m)
999
- }
1096
+ if (hasNonNullishProperty(message, "title")) {
1097
+ interactiveMessage.header.title = message.title
1098
+ }
1000
1099
 
1001
- if ('footer' in message && !!message.footer) {
1002
- buttonsMessage.footerText = message.footer
1003
- }
1100
+ if (hasNonNullishProperty(message, "subtitle")) {
1101
+ interactiveMessage.header.subtitle = message.subtitle
1102
+ }
1004
1103
 
1005
- if ('title' in message && !!message.title) {
1006
- buttonsMessage.text = message.title
1007
- buttonsMessage.headerType = proto.Message.ButtonsMessage.HeaderType.TEXT
1104
+ if (hasNonNullishProperty(message, "hasMediaAttachment")) {
1105
+ interactiveMessage.header.hasMediaAttachment = Boolean(
1106
+ message.hasMediaAttachment
1107
+ )
1108
+ }
1109
+ }
1008
1110
  }
1009
1111
 
1010
- buttonsMessage.contextInfo = {
1011
- ...(message.contextInfo || {}),
1012
- ...(message.mentions ? { mentionedJid: message.mentions } : {})
1112
+ if (hasNonNullishProperty(message, "footer")) {
1113
+ interactiveMessage.footer = {
1114
+ text: message.footer,
1115
+ }
1013
1116
  }
1014
1117
 
1015
- m = { buttonsMessage }
1016
- }
1118
+ m = { interactiveMessage }
1119
+ } else if (hasNonNullishProperty(message, "cards")) {
1120
+ const slides = await Promise.all(
1121
+ message.cards.map(async (slide) => {
1122
+ const {
1123
+ image,
1124
+ video,
1125
+ product,
1126
+ title,
1127
+ body,
1128
+ footer,
1129
+ buttons,
1130
+ } = slide
1017
1131
 
1018
- else if ('templateButtons' in message && !!message.templateButtons) {
1019
- const hydratedTemplate = {
1020
- hydratedButtons: message.templateButtons
1021
- }
1132
+ let header
1022
1133
 
1023
- if ('text' in message) {
1024
- hydratedTemplate.hydratedContentText = message.text
1025
- }
1134
+ if (product) {
1135
+ const { imageMessage } = await prepareWAMessageMedia(
1136
+ { image: product.productImage, ...options },
1137
+ options
1138
+ )
1139
+ header = {
1140
+ productMessage: {
1141
+ product: {
1142
+ ...product,
1143
+ productImage: imageMessage,
1144
+ },
1145
+ ...slide,
1146
+ },
1147
+ }
1148
+ } else if (image) {
1149
+ header = await prepareWAMessageMedia(
1150
+ { image: image, ...options },
1151
+ options
1152
+ )
1153
+ } else if (video) {
1154
+ header = await prepareWAMessageMedia(
1155
+ { video: video, ...options },
1156
+ options
1157
+ )
1158
+ }
1026
1159
 
1027
- else {
1028
- if ('caption' in message) {
1029
- hydratedTemplate.hydratedContentText = message.caption
1030
- }
1160
+ const msg = {
1161
+ header: {
1162
+ title,
1163
+ hasMediaAttachment: true,
1164
+ ...header,
1165
+ },
1166
+ body: {
1167
+ text: body,
1168
+ },
1169
+ footer: {
1170
+ text: footer,
1171
+ },
1172
+ nativeFlowMessage: {
1173
+ buttons,
1174
+ },
1175
+ }
1031
1176
 
1032
- Object.assign(msg, m)
1033
- }
1177
+ return msg
1178
+ })
1179
+ )
1034
1180
 
1035
- if ('footer' in message && !!message.footer) {
1036
- hydratedTemplate.hydratedFooterText = message.footer
1181
+ const interactiveMessage = {
1182
+ carouselMessage: {
1183
+ cards: slides,
1184
+ },
1037
1185
  }
1038
1186
 
1039
- hydratedTemplate.contextInfo = {
1040
- ...(message.contextInfo || {}),
1041
- ...(message.mentions ? { mentionedJid: message.mentions } : {})
1187
+ if (hasNonNullishProperty(message, "text")) {
1188
+ interactiveMessage.body = {
1189
+ text: message.text,
1190
+ }
1042
1191
  }
1043
1192
 
1044
- m = { templateMessage: { hydratedTemplate }}
1045
- }
1046
-
1047
- else if ('interactiveButtons' in message && !!message.interactiveButtons) {
1048
- const interactiveMessage = {
1049
- nativeFlowMessage: {
1050
- buttons: message.interactiveButtons
1051
- }
1052
- }
1053
-
1054
- if ('text' in message) {
1055
- interactiveMessage.body = {
1056
- text: message.text
1057
- },
1058
- interactiveMessage.header = {
1059
- title: message.title,
1060
- subtitle: message.subtitle,
1061
- hasMediaAttachment: false
1062
- }
1063
- }
1064
-
1065
- else {
1066
- if ('caption' in message) {
1067
- interactiveMessage.body = {
1068
- text: message.caption
1069
- }
1070
-
1071
- interactiveMessage.header = {
1072
- title: message.title,
1073
- subtitle: message.subtitle,
1074
- hasMediaAttachment: message.hasMediaAttachment ? message.hasMediaAttachment : false,
1075
- ...Object.assign(interactiveMessage, m)
1076
- }
1077
- }
1078
- }
1079
-
1080
- if ('footer' in message && !!message.footer) {
1081
- interactiveMessage.footer = {
1082
- text: message.footer
1083
- }
1084
- }
1085
-
1086
- interactiveMessage.contextInfo = {
1087
- ...(message.contextInfo || {}),
1088
- ...(message.mentions ? { mentionedJid: message.mentions } : {})
1089
- }
1090
-
1091
- m = { interactiveMessage }
1092
- }
1093
-
1094
- else if ('shop' in message && !!message.shop) {
1095
- const interactiveMessage = {
1096
- shopStorefrontMessage: {
1097
- surface: message.shop.surface,
1098
- id: message.shop.id
1099
- }
1100
- }
1101
-
1102
- if ('text' in message) {
1103
- interactiveMessage.body = {
1104
- text: message.text
1105
- },
1106
- interactiveMessage.header = {
1107
- title: message.title,
1108
- subtitle: message.subtitle,
1109
- hasMediaAttachment: false
1110
- }
1111
- }
1112
-
1113
- else {
1114
- if ('caption' in message) {
1115
- interactiveMessage.body = {
1116
- text: message.caption
1117
- }
1118
-
1119
- interactiveMessage.header = {
1120
- title: message.title,
1121
- subtitle: message.subtitle,
1122
- hasMediaAttachment: message.hasMediaAttachment ? message.hasMediaAttachment : false,
1123
- ...Object.assign(interactiveMessage, m)
1193
+ if (hasNonNullishProperty(message, "title")) {
1194
+ interactiveMessage.header = {
1195
+ title: message.title,
1196
+ subtitle: null,
1197
+ hasMediaAttachment: false,
1124
1198
  }
1125
- }
1126
- }
1127
-
1128
- if ('footer' in message && !!message.footer) {
1129
- interactiveMessage.footer = {
1130
- text: message.footer
1131
- }
1132
- }
1133
-
1134
- interactiveMessage.contextInfo = {
1135
- ...(message.contextInfo || {}),
1136
- ...(message.mentions ? { mentionedJid: message.mentions } : {})
1137
- }
1138
-
1139
- m = { interactiveMessage }
1140
- }
1141
-
1142
- else if ('collection' in message && !!message.collection) {
1143
- const interactiveMessage = {
1144
- collectionMessage: {
1145
- bizJid: message.collection.bizJid,
1146
- id: message.collection.id,
1147
- messageVersion: message?.collection?.version
1148
- }
1149
- }
1150
-
1151
- if ('text' in message) {
1152
- interactiveMessage.body = {
1153
- text: message.text
1154
- },
1155
- interactiveMessage.header = {
1156
- title: message.title,
1157
- subtitle: message.subtitle,
1158
- hasMediaAttachment: false
1159
- }
1160
- }
1161
-
1162
- else {
1163
- if ('caption' in message) {
1164
- interactiveMessage.body = {
1165
- text: message.caption
1166
- }
1167
- interactiveMessage.header = {
1168
- title: message.title,
1169
- subtitle: message.subtitle,
1170
- hasMediaAttachment: message.hasMediaAttachment ? message.hasMediaAttachment : false,
1171
- ...Object.assign(interactiveMessage, m)
1199
+
1200
+ if (hasNonNullishProperty(message, "subtitle")) {
1201
+ interactiveMessage.header.subtitle = message.subtitle
1172
1202
  }
1173
- }
1174
- }
1203
+ } else {
1204
+ if (hasNonNullishProperty(message, "caption")) {
1205
+ interactiveMessage.body = {
1206
+ text: message.caption,
1207
+ }
1175
1208
 
1176
- if ('footer' in message && !message.footer) {
1177
- interactiveMessage.footer = {
1178
- text: message.footer
1179
- }
1180
- }
1209
+ interactiveMessage.header = {
1210
+ title: null,
1211
+ subtitle: null,
1212
+ hasMediaAttachment: false,
1213
+ ...Object.assign(interactiveMessage, m),
1214
+ }
1181
1215
 
1182
- interactiveMessage.contextInfo = {
1183
- ...(message.contextInfo || {}),
1184
- ...(message.mentions ? { mentionedJid: message.mentions } : {})
1185
- }
1216
+ if (hasNonNullishProperty(message, "title")) {
1217
+ interactiveMessage.header.title = message.title
1218
+ }
1186
1219
 
1187
- m = { interactiveMessage }
1188
- }
1220
+ if (hasNonNullishProperty(message, "subtitle")) {
1221
+ interactiveMessage.header.subtitle = message.subtitle
1222
+ }
1189
1223
 
1190
- else if ('cards' in message && !!message.cards) {
1191
- const slides = await Promise.all(message.cards.map(async (slide) => {
1192
- const { image, video, product, title, body, footer, buttons } = slide
1193
- let header
1194
-
1195
- if (product) {
1196
- const { imageMessage } = await prepareWAMessageMedia({ image: product.productImage, ...options }, options)
1197
- header = {
1198
- productMessage: {
1199
- product: {
1200
- ...product,
1201
- productImage: imageMessage,
1202
- },
1203
- ...slide
1204
- }
1224
+ if (hasNonNullishProperty(message, "hasMediaAttachment")) {
1225
+ interactiveMessage.header.hasMediaAttachment = Boolean(
1226
+ message.hasMediaAttachment
1227
+ )
1205
1228
  }
1206
1229
  }
1230
+ }
1207
1231
 
1208
- else if (image) {
1209
- header = await prepareWAMessageMedia({ image: image, ...options }, options)
1232
+ if (hasNonNullishProperty(message, "footer")) {
1233
+ interactiveMessage.footer = {
1234
+ text: message.footer,
1210
1235
  }
1236
+ }
1211
1237
 
1212
- else if (video) {
1213
- header = await prepareWAMessageMedia({ video: video, ...options }, options)
1214
- }
1238
+ m = { interactiveMessage }
1239
+ } else if (hasNonNullishProperty(message, "text")) {
1240
+ const extContent = { text: message.text }
1215
1241
 
1216
- const msg = {
1217
- header: {
1218
- title,
1219
- hasMediaAttachment: true,
1220
- ...header
1221
- },
1222
- body: {
1223
- text: body
1224
- },
1225
- footer: {
1226
- text: footer
1227
- },
1228
- nativeFlowMessage: {
1229
- buttons,
1230
- }
1231
- }
1242
+ let urlInfo = message.linkPreview
1232
1243
 
1233
- return msg
1234
- }))
1244
+ if (typeof urlInfo === "undefined") {
1245
+ urlInfo = await generateLinkPreviewIfRequired(
1246
+ message.text,
1247
+ options.getUrlInfo,
1248
+ options.logger
1249
+ )
1250
+ }
1235
1251
 
1236
- const interactiveMessage = {
1237
- carouselMessage: {
1238
- cards: slides
1252
+ if (urlInfo) {
1253
+ extContent.canonicalUrl = urlInfo["canonical-url"]
1254
+ extContent.matchedText = urlInfo["matched-text"]
1255
+ extContent.jpegThumbnail = urlInfo.jpegThumbnail
1256
+ extContent.description = urlInfo.description
1257
+ extContent.title = urlInfo.title
1258
+ extContent.previewType = 0
1259
+
1260
+ const img = urlInfo.highQualityThumbnail
1261
+
1262
+ if (img) {
1263
+ extContent.thumbnailDirectPath = img.directPath
1264
+ extContent.mediaKey = img.mediaKey
1265
+ extContent.mediaKeyTimestamp = img.mediaKeyTimestamp
1266
+ extContent.thumbnailWidth = img.width
1267
+ extContent.thumbnailHeight = img.height
1268
+ extContent.thumbnailSha256 = img.fileSha256
1269
+ extContent.thumbnailEncSha256 = img.fileEncSha256
1239
1270
  }
1240
1271
  }
1241
1272
 
1242
- if ('text' in message) {
1243
- interactiveMessage.body = {
1244
- text: message.text
1245
- },
1246
- interactiveMessage.header = {
1247
- title: message.title,
1248
- subtitle: message.subtitle,
1249
- hasMediaAttachment: false
1250
- }
1273
+ if (options.backgroundColor) {
1274
+ extContent.backgroundArgb = await assertColor(
1275
+ options.backgroundColor
1276
+ )
1251
1277
  }
1252
1278
 
1253
- if ('footer' in message && !!message.footer) {
1254
- interactiveMessage.footer = {
1255
- text: message.footer
1256
- }
1279
+ if (options.textColor) {
1280
+ extContent.textArgb = await assertColor(options.textColor)
1257
1281
  }
1258
1282
 
1259
- interactiveMessage.contextInfo = {
1260
- ...(message.contextInfo || {}),
1261
- ...(message.mentions ? { mentionedJid: message.mentions } : {})
1283
+ if (options.font) {
1284
+ extContent.font = options.font
1262
1285
  }
1263
1286
 
1264
- m = { interactiveMessage }
1287
+ m.extendedTextMessage = extContent
1288
+ } else if (hasMedia) {
1289
+ m = await prepareWAMessageMedia(message, options)
1290
+ }
1291
+
1292
+ if (hasOptionalProperty(message, "ephemeral")) {
1293
+ m = { ephemeralMessage: { message: m } }
1265
1294
  }
1266
1295
 
1267
- if ('ephemeral' in message && !!message.ephemeral) {
1268
- m = { ephemeralMessage: { message: m } }
1269
- }
1296
+ if (hasOptionalProperty(message, "mentions") && message.mentions?.length) {
1297
+ const messageType = Object.keys(m)[0]
1298
+ const key = m[messageType]
1270
1299
 
1271
- if ('viewOnce' in message && !!message.viewOnce) {
1272
- m = { viewOnceMessageV2: { message: m } }
1300
+ if ("contextInfo" in key && !!key.contextInfo) {
1301
+ key.contextInfo.mentionedJid = message.mentions
1302
+ } else if (key) {
1303
+ key.contextInfo = {
1304
+ mentionedJid: message.mentions,
1305
+ }
1306
+ }
1273
1307
  }
1274
1308
 
1275
- if ('viewOnceExt' in message && !!message.viewOnceExt) {
1276
- m = { viewOnceMessageV2Extension: { message: m } }
1309
+ if (hasOptionalProperty(message, "contextInfo") && !!message.contextInfo) {
1310
+ const messageType = Object.keys(m)[0]
1311
+ const key = m[messageType]
1312
+
1313
+ if ("contextInfo" in key && !!key.contextInfo) {
1314
+ key.contextInfo = { ...key.contextInfo, ...message.contextInfo }
1315
+ } else if (key) {
1316
+ key.contextInfo = message.contextInfo
1317
+ }
1277
1318
  }
1278
1319
 
1279
- if ('edit' in message) {
1320
+ if (hasOptionalProperty(message, "edit")) {
1280
1321
  m = {
1281
1322
  protocolMessage: {
1282
1323
  key: message.edit,
1283
1324
  editedMessage: m,
1284
1325
  timestampMs: Date.now(),
1285
- type: WAProto.Message.ProtocolMessage.Type.MESSAGE_EDIT
1286
- }
1326
+ type: WAProto.Message.ProtocolMessage.Type.MESSAGE_EDIT,
1327
+ },
1287
1328
  }
1288
1329
  }
1289
1330
 
@@ -1401,7 +1442,7 @@ const generateWAMessage = async (jid, content, options) => {
1401
1442
  const getContentType = (content) => {
1402
1443
  if (content) {
1403
1444
  const keys = Object.keys(content)
1404
- const key = keys.find(k => (k === 'conversation' || k.endsWith('Message') || k.endsWith('V2') || k.endsWith('V3') || k.endsWith('V4') || k.endsWith('V5')) && k !== 'senderKeyDistributionMessage' && k !== 'messageContextInfo')
1445
+ const key = keys.find(k => (k === 'conversation' || k.endsWith('Message') || k.endsWith('V2') || k.endsWith('V3') || k.endsWith('V4')) && k !== 'senderKeyDistributionMessage' && k !== 'messageContextInfo')
1405
1446
 
1406
1447
  return key
1407
1448
  }
@@ -1448,7 +1489,6 @@ const normalizeMessageContent = (content) => {
1448
1489
  || (message?.statusMentionMessage)
1449
1490
  || (message?.groupStatusMessageV2)
1450
1491
  || (message?.pollCreationMessageV4)
1451
- || (message?.pollCreationMessageV5)
1452
1492
  || (message?.associatedChildMessage)
1453
1493
  || (message?.groupMentionedMessage)
1454
1494
  || (message?.groupStatusMentionMessage)
@@ -1575,8 +1615,8 @@ const updateMessageWithPollUpdate = (msg, update) => {
1575
1615
  const updateMessageWithEventResponse = (msg, update) => {
1576
1616
  const authorID = getKeyAuthor(update.eventResponseMessageKey)
1577
1617
  const responses = (msg.eventResponses || [])
1578
- .filter(r => getKeyAuthor(r.eventResponseMessageKey) !== authorID)
1579
-
1618
+ .filter(r => getKeyAuthor(r.eventResponseMessageKey) !== authorID)
1619
+
1580
1620
  responses.push(update)
1581
1621
  msg.eventResponses = responses
1582
1622
  }
@@ -1640,18 +1680,18 @@ function getAggregateResponsesInEventMessage({ eventResponses }, meLid) {
1640
1680
  const responseMap = {}
1641
1681
 
1642
1682
  for (const type of responseTypes) {
1643
- responseMap[type] = {
1644
- response: type,
1645
- responders: []
1646
- }
1683
+ responseMap[type] = {
1684
+ response: type,
1685
+ responders: []
1686
+ }
1647
1687
  }
1648
-
1688
+
1649
1689
  for (const update of eventResponses) {
1650
- const { response } = update.response || 0
1651
- const responseType = proto.Message.EventResponseMessage.EventResponseType[response]
1652
- if (responseType !== 'UNKNOWN' && responseMap[responseType]) {
1653
- responseMap[responseType].responders.push(getKeyAuthor(update.eventResponseMessageKey, meLid))
1654
- }
1690
+ const { response } = update.response || 0
1691
+ const responseType = proto.Message.EventResponseMessage.EventResponseType[response]
1692
+ if (responseType !== 'UNKNOWN' && responseMap[responseType]) {
1693
+ responseMap[responseType].responders.push(getKeyAuthor(update.eventResponseMessageKey, meLid))
1694
+ }
1655
1695
  }
1656
1696
 
1657
1697
  return Object.values(responseMap)
@@ -1687,40 +1727,42 @@ const REUPLOAD_REQUIRED_STATUS = [410, 404]
1687
1727
  */
1688
1728
  const downloadMediaMessage = async (message, type, options, ctx) => {
1689
1729
  const result = await downloadMsg().catch(async (error) => {
1690
- if (ctx && axios_1.default.isAxiosError(error) && // check if the message requires a reupload
1691
- REUPLOAD_REQUIRED_STATUS.includes(error.response?.status)) {
1730
+ if (ctx &&
1731
+ typeof error?.status === 'number' && // treat errors with status as HTTP failures requiring reupload
1732
+ REUPLOAD_REQUIRED_STATUS.includes(error.status)) {
1692
1733
  ctx.logger.info({ key: message.key }, 'sending reupload media request...')
1693
-
1734
+
1694
1735
  // request reupload
1695
1736
  message = await ctx.reuploadRequest(message)
1696
-
1737
+
1697
1738
  const result = await downloadMsg()
1698
-
1699
1739
  return result
1700
1740
  }
1701
-
1741
+
1702
1742
  throw error
1703
1743
  })
1704
-
1744
+
1705
1745
  return result
1706
-
1746
+
1707
1747
  async function downloadMsg() {
1708
1748
  const mContent = extractMessageContent(message.message)
1709
-
1749
+
1710
1750
  if (!mContent) {
1711
1751
  throw new Boom('No message present', { statusCode: 400, data: message })
1712
1752
  }
1713
-
1753
+
1714
1754
  const contentType = getContentType(mContent)
1755
+
1715
1756
  let mediaType = contentType?.replace('Message', '')
1757
+
1716
1758
  const media = contentType === 'productMessage' ? mContent[contentType]?.product?.productImage : mContent[contentType]
1717
-
1759
+
1718
1760
  if (!media || typeof media !== 'object' || (!('url' in media) && !('thumbnailDirectPath' in media))) {
1719
1761
  throw new Boom(`"${contentType}" message is not a media message`)
1720
1762
  }
1721
-
1763
+
1722
1764
  let download
1723
-
1765
+
1724
1766
  if ('thumbnailDirectPath' in media && !('url' in media)) {
1725
1767
  download = {
1726
1768
  directPath: media.thumbnailDirectPath,
@@ -1728,68 +1770,40 @@ const downloadMediaMessage = async (message, type, options, ctx) => {
1728
1770
  }
1729
1771
  mediaType = 'thumbnail-link'
1730
1772
  }
1731
-
1732
1773
  else {
1733
1774
  download = media
1734
1775
  }
1735
-
1776
+
1736
1777
  const stream = await downloadContentFromMessage(download, mediaType, options)
1737
-
1778
+
1738
1779
  if (type === 'buffer') {
1739
1780
  const bufferArray = []
1740
-
1741
1781
  for await (const chunk of stream) {
1742
- bufferArray.push(chunk)
1782
+ bufferArray.push(chunk);
1743
1783
  }
1744
-
1745
- return Buffer.concat(bufferArray)
1784
+ return Buffer.concat(bufferArray);
1746
1785
  }
1747
-
1748
1786
  return stream
1749
1787
  }
1750
1788
  }
1751
1789
 
1752
- /** Checks whether the given message is a media message if it is returns the inner content */
1790
+ /** Checks whether the given message is a media message; if it is returns the inner content */
1753
1791
  const assertMediaContent = (content) => {
1754
1792
  content = extractMessageContent(content)
1755
-
1756
- const mediaContent = content?.documentMessage
1757
- || content?.imageMessage
1758
- || content?.videoMessage
1759
- || content?.audioMessage
1760
- || content?.stickerMessage
1761
-
1793
+
1794
+ const mediaContent = content?.documentMessage ||
1795
+ content?.imageMessage ||
1796
+ content?.videoMessage ||
1797
+ content?.audioMessage ||
1798
+ content?.stickerMessage
1799
+
1762
1800
  if (!mediaContent) {
1763
- throw new Boom('given message is not a media message', { statusCode: 400, data: content })
1801
+ throw new Boom('given message is not a media message', { statusCode: 400, data: content });
1764
1802
  }
1803
+
1765
1804
  return mediaContent
1766
1805
  }
1767
1806
 
1768
- /**
1769
- * this is an experimental patch to make buttons work
1770
- * Don't know how it works, but it does for now
1771
- */
1772
- const patchMessageForMdIfRequired = (message) => {
1773
- if (message?.buttonsMessage ||
1774
- message?.templateMessage ||
1775
- message?.listMessage ||
1776
- message?.interactiveMessage?.nativeFlowMesaage
1777
- ) {
1778
- message = {
1779
- viewOnceMessageV2Extension: {
1780
- message: {
1781
- messageContextInfo: {
1782
- deviceListMetadataVersion: 2,
1783
- deviceListMetadata: {}
1784
- },
1785
- ...message
1786
- }
1787
- }
1788
- }
1789
- }
1790
- return message
1791
- }
1792
-
1793
1807
  module.exports = {
1794
1808
  extractUrlFromText,
1795
1809
  generateLinkPreviewIfRequired,
@@ -1801,6 +1815,7 @@ module.exports = {
1801
1815
  generateWAMessageFromContent,
1802
1816
  generateWAMessage,
1803
1817
  getContentType,
1818
+ hasNonNullishProperty,
1804
1819
  normalizeMessageContent,
1805
1820
  extractMessageContent,
1806
1821
  getDevice,
@@ -1812,6 +1827,5 @@ module.exports = {
1812
1827
  getAggregateResponsesInEventMessage,
1813
1828
  aggregateMessageKeysNotFromMe,
1814
1829
  downloadMediaMessage,
1815
- assertMediaContent,
1816
- patchMessageForMdIfRequired
1830
+ assertMediaContent
1817
1831
  }