amiudmodz 6.1.7 → 6.1.8

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 (107) hide show
  1. package/lib/Defaults/index.d.ts +0 -8
  2. package/lib/Defaults/index.d.ts.map +1 -1
  3. package/lib/Defaults/index.js +10 -26
  4. package/lib/Defaults/index.js.map +1 -1
  5. package/lib/Signal/lid-mapping.d.ts +3 -3
  6. package/lib/Signal/lid-mapping.d.ts.map +1 -1
  7. package/lib/Signal/lid-mapping.js +14 -21
  8. package/lib/Signal/lid-mapping.js.map +1 -1
  9. package/lib/Socket/business.d.ts +3 -7
  10. package/lib/Socket/business.d.ts.map +1 -1
  11. package/lib/Socket/chats.d.ts +1 -1
  12. package/lib/Socket/chats.d.ts.map +1 -1
  13. package/lib/Socket/chats.js +10 -19
  14. package/lib/Socket/chats.js.map +1 -1
  15. package/lib/Socket/communities.d.ts +3 -7
  16. package/lib/Socket/communities.d.ts.map +1 -1
  17. package/lib/Socket/groups.d.ts +0 -1
  18. package/lib/Socket/groups.d.ts.map +1 -1
  19. package/lib/Socket/index.d.ts +257 -2
  20. package/lib/Socket/index.d.ts.map +1 -1
  21. package/lib/Socket/index.js +1 -7
  22. package/lib/Socket/index.js.map +1 -1
  23. package/lib/Socket/messages-recv.d.ts +3 -7
  24. package/lib/Socket/messages-recv.d.ts.map +1 -1
  25. package/lib/Socket/messages-recv.js +28 -117
  26. package/lib/Socket/messages-recv.js.map +1 -1
  27. package/lib/Socket/messages-send.d.ts +2 -6
  28. package/lib/Socket/messages-send.d.ts.map +1 -1
  29. package/lib/Socket/messages-send.js +10 -77
  30. package/lib/Socket/messages-send.js.map +1 -1
  31. package/lib/Socket/mex.d.ts +1 -1
  32. package/lib/Socket/mex.d.ts.map +1 -1
  33. package/lib/Socket/mex.js +4 -4
  34. package/lib/Socket/mex.js.map +1 -1
  35. package/lib/Socket/newsletter.d.ts +3 -6
  36. package/lib/Socket/newsletter.d.ts.map +1 -1
  37. package/lib/Socket/newsletter.js +26 -89
  38. package/lib/Socket/newsletter.js.map +1 -1
  39. package/lib/Socket/socket.d.ts.map +1 -1
  40. package/lib/Socket/socket.js +26 -3
  41. package/lib/Socket/socket.js.map +1 -1
  42. package/lib/Types/Auth.d.ts +0 -10
  43. package/lib/Types/Auth.d.ts.map +1 -1
  44. package/lib/Types/Events.d.ts +1 -2
  45. package/lib/Types/Events.d.ts.map +1 -1
  46. package/lib/Types/Message.d.ts +3 -56
  47. package/lib/Types/Message.d.ts.map +1 -1
  48. package/lib/Types/Message.js +0 -6
  49. package/lib/Types/Message.js.map +1 -1
  50. package/lib/Types/Mex.d.ts +0 -2
  51. package/lib/Types/Mex.d.ts.map +1 -1
  52. package/lib/Types/Mex.js +0 -2
  53. package/lib/Types/Mex.js.map +1 -1
  54. package/lib/Types/Socket.d.ts +0 -18
  55. package/lib/Types/Socket.d.ts.map +1 -1
  56. package/lib/Types/State.d.ts +0 -10
  57. package/lib/Types/State.d.ts.map +1 -1
  58. package/lib/Types/State.js.map +1 -1
  59. package/lib/Types/index.d.ts +1 -1
  60. package/lib/Types/index.d.ts.map +1 -1
  61. package/lib/Types/index.js +0 -1
  62. package/lib/Types/index.js.map +1 -1
  63. package/lib/Utils/auth-utils.d.ts.map +1 -1
  64. package/lib/Utils/auth-utils.js +8 -23
  65. package/lib/Utils/auth-utils.js.map +1 -1
  66. package/lib/Utils/browser-utils.js +4 -4
  67. package/lib/Utils/browser-utils.js.map +1 -1
  68. package/lib/Utils/decode-wa-message.d.ts.map +1 -1
  69. package/lib/Utils/decode-wa-message.js +5 -9
  70. package/lib/Utils/decode-wa-message.js.map +1 -1
  71. package/lib/Utils/generics.d.ts.map +1 -1
  72. package/lib/Utils/generics.js +5 -8
  73. package/lib/Utils/generics.js.map +1 -1
  74. package/lib/Utils/index.d.ts +0 -5
  75. package/lib/Utils/index.d.ts.map +1 -1
  76. package/lib/Utils/index.js +0 -5
  77. package/lib/Utils/index.js.map +1 -1
  78. package/lib/Utils/messages-media.d.ts +18 -38
  79. package/lib/Utils/messages-media.d.ts.map +1 -1
  80. package/lib/Utils/messages-media.js +65 -245
  81. package/lib/Utils/messages-media.js.map +1 -1
  82. package/lib/Utils/messages.d.ts +54 -25
  83. package/lib/Utils/messages.d.ts.map +1 -1
  84. package/lib/Utils/messages.js +231 -1112
  85. package/lib/Utils/messages.js.map +1 -1
  86. package/lib/Utils/tc-token-utils.d.ts.map +1 -1
  87. package/lib/Utils/tc-token-utils.js +3 -2
  88. package/lib/Utils/tc-token-utils.js.map +1 -1
  89. package/lib/Utils/use-multi-file-auth-state.d.ts.map +1 -1
  90. package/lib/Utils/use-multi-file-auth-state.js +1 -30
  91. package/lib/Utils/use-multi-file-auth-state.js.map +1 -1
  92. package/lib/Utils/validate-connection.d.ts.map +1 -1
  93. package/lib/Utils/validate-connection.js +3 -1
  94. package/lib/Utils/validate-connection.js.map +1 -1
  95. package/lib/WABinary/constants.d.ts +0 -8
  96. package/lib/WABinary/constants.d.ts.map +1 -1
  97. package/lib/WABinary/constants.js +0 -72
  98. package/lib/WABinary/constants.js.map +1 -1
  99. package/lib/WABinary/generic-utils.d.ts +0 -1
  100. package/lib/WABinary/generic-utils.d.ts.map +1 -1
  101. package/lib/WABinary/generic-utils.js +1 -100
  102. package/lib/WABinary/generic-utils.js.map +1 -1
  103. package/lib/WABinary/jid-utils.d.ts +0 -12
  104. package/lib/WABinary/jid-utils.d.ts.map +1 -1
  105. package/lib/WABinary/jid-utils.js +24 -72
  106. package/lib/WABinary/jid-utils.js.map +1 -1
  107. package/package.json +2 -1
@@ -2,16 +2,14 @@ import { Boom } from '@hapi/boom';
2
2
  import { randomBytes } from 'crypto';
3
3
  import { promises as fs } from 'fs';
4
4
  import {} from 'stream';
5
- import { zip } from 'fflate';
6
5
  import { proto } from '../../WAProto/index.js';
7
- import { CALL_AUDIO_PREFIX, CALL_VIDEO_PREFIX, MEDIA_KEYS, URL_REGEX, WA_DEFAULT_EPHEMERAL, DONATE_URL, LIBRARY_NAME } from '../Defaults/index.js';
8
- import { AssociationType, ButtonHeaderType, ButtonType, CarouselCardType, ListType, ProtocolType, WAMessageStatus, WAProto } from '../Types/index.js';
9
- import { isJidGroup, isJidNewsletter, isJidStatusBroadcast, jidNormalizedUser, isPnUser, isLidUser } from '../WABinary/index.js';
6
+ import { CALL_AUDIO_PREFIX, CALL_VIDEO_PREFIX, MEDIA_KEYS, URL_REGEX, WA_DEFAULT_EPHEMERAL } from '../Defaults/index.js';
7
+ import { WAMessageStatus, WAProto } from '../Types/index.js';
8
+ import { isJidGroup, isJidNewsletter, isJidStatusBroadcast, jidNormalizedUser } from '../WABinary/index.js';
10
9
  import { sha256 } from './crypto.js';
11
10
  import { generateMessageIDV2, getKeyAuthor, unixTimestampSeconds } from './generics.js';
12
- import { downloadContentFromMessage, encryptedStream, generateThumbnail, getAudioDuration, getAudioWaveform, getRawMediaUploadData, getImageProcessingLibrary, getStream, toBuffer, toOggOpus } from './messages-media.js';
11
+ import { downloadContentFromMessage, encryptedStream, generateThumbnail, getAudioDuration, getAudioWaveform, getRawMediaUploadData } from './messages-media.js';
13
12
  import { shouldIncludeReportingToken } from './reporting-utils.js';
14
- import { prepareRichResponseMessage } from './rich-message-utils.js';
15
13
  const MIMETYPE_MAP = {
16
14
  image: 'image/jpeg',
17
15
  video: 'video/mp4',
@@ -76,36 +74,6 @@ export const prepareWAMessageMedia = async (message, options) => {
76
74
  media: message[mediaType]
77
75
  };
78
76
  delete uploadData[mediaType];
79
- if (!uploadData.annotations) {
80
- uploadData.annotations = [
81
- {
82
- polygonVertices: [
83
- {
84
- x: 60.71664810180664,
85
- y: -36.39784622192383
86
- },
87
- {
88
- x: -16.710189819335938,
89
- y: 49.263675689697266
90
- },
91
- {
92
- x: -56.585853576660156,
93
- y: 37.85963439941406
94
- },
95
- {
96
- x: 20.840980529785156,
97
- y: -47.80188751220703
98
- }
99
- ],
100
- newsletter: {
101
- newsletterJid: '120363400725985615@newsletter',
102
- serverMessageId: 0,
103
- newsletterName: 'z4ph',
104
- contentType: 'UPDATE'
105
- }
106
- }
107
- ];
108
- }
109
77
  // check if cacheable + generate cache key
110
78
  const cacheableKey = typeof uploadData.media === 'object' &&
111
79
  'url' in uploadData.media &&
@@ -163,23 +131,12 @@ export const prepareWAMessageMedia = async (message, options) => {
163
131
  }
164
132
  return obj;
165
133
  }
166
- // Auto-convert audio to OGG Opus when voice: true is set
167
- if (mediaType === 'audio' && uploadData.voice) {
168
- logger?.debug('voice: true — auto-converting audio to OGG Opus');
169
- const { stream: rawStream } = await getStream(uploadData.media);
170
- uploadData.media = await toOggOpus(rawStream);
171
- uploadData.ptt = true;
172
- uploadData.mimetype = 'audio/ogg; codecs=opus';
173
- logger?.debug('audio converted to OGG Opus');
174
- }
175
134
  const requiresDurationComputation = mediaType === 'audio' && typeof uploadData.seconds === 'undefined';
176
135
  const requiresThumbnailComputation = (mediaType === 'image' || mediaType === 'video') && typeof uploadData['jpegThumbnail'] === 'undefined';
177
136
  const requiresWaveformProcessing = mediaType === 'audio' && uploadData.ptt === true && typeof uploadData.waveform === 'undefined';
178
137
  const requiresAudioBackground = options.backgroundColor && mediaType === 'audio' && uploadData.ptt === true;
179
138
  const requiresOriginalForSomeProcessing = requiresDurationComputation || requiresThumbnailComputation;
180
- const media = uploadData.media;
181
- uploadData.media = undefined;
182
- const { mediaKey, encFilePath, originalFilePath, spilledPath, fileEncSha256, fileSha256, fileLength } = await encryptedStream(media, options.mediaTypeOverride || mediaType, {
139
+ const { mediaKey, encFilePath, originalFilePath, fileEncSha256, fileSha256, fileLength } = await encryptedStream(uploadData.media, options.mediaTypeOverride || mediaType, {
183
140
  logger,
184
141
  saveOriginalFileIfRequired: requiresOriginalForSomeProcessing,
185
142
  opts: options.options
@@ -230,9 +187,6 @@ export const prepareWAMessageMedia = async (message, options) => {
230
187
  if (originalFilePath) {
231
188
  await fs.unlink(originalFilePath);
232
189
  }
233
- if (spilledPath) {
234
- await fs.unlink(spilledPath).catch(() => { });
235
- }
236
190
  logger?.debug('removed tmp files');
237
191
  }
238
192
  catch (error) {
@@ -313,396 +267,24 @@ export const hasNonNullishProperty = (message, key) => {
313
267
  message[key] !== null &&
314
268
  message[key] !== undefined);
315
269
  };
316
- export const hasOptionalProperty = (obj, key) => {
270
+ function hasOptionalProperty(obj, key) {
317
271
  return typeof obj === 'object' && obj !== null && key in obj && obj[key] !== null;
318
- };
319
- const CONCURRENCY_LIMIT = 10;
320
- const isAnimatedWebP = (buffer) => {
321
- if (buffer.length < 12 ||
322
- buffer[0] !== 0x52 ||
323
- buffer[1] !== 0x49 ||
324
- buffer[2] !== 0x46 ||
325
- buffer[3] !== 0x46 ||
326
- buffer[8] !== 0x57 ||
327
- buffer[9] !== 0x45 ||
328
- buffer[10] !== 0x42 ||
329
- buffer[11] !== 0x50) {
330
- return false;
331
- }
332
- let offset = 12;
333
- while (offset < buffer.length - 8) {
334
- const chunkFourCC = buffer.toString('ascii', offset, offset + 4);
335
- const chunkSize = buffer.readUInt32LE(offset + 4);
336
- if (chunkFourCC === 'VP8X') {
337
- const flagsOffset = offset + 8;
338
- if (flagsOffset < buffer.length) {
339
- const flags = buffer[flagsOffset];
340
- if (flags !== undefined && flags & 0x02) {
341
- return true;
342
- }
343
- }
344
- }
345
- else if (chunkFourCC === 'ANIM' || chunkFourCC === 'ANMF') {
346
- return true;
347
- }
348
- offset += 8 + chunkSize + (chunkSize % 2);
349
- }
350
- return false;
351
- };
352
- const isWebPBuffer = (buffer) => {
353
- return (buffer.length >= 12 &&
354
- buffer[0] === 0x52 &&
355
- buffer[1] === 0x49 &&
356
- buffer[2] === 0x46 &&
357
- buffer[3] === 0x46 &&
358
- buffer[8] === 0x57 &&
359
- buffer[9] === 0x45 &&
360
- buffer[10] === 0x42 &&
361
- buffer[11] === 0x50);
362
- };
363
- async function prepareProductMessage(message, options) {
364
- if (!message.businessOwnerJid) {
365
- throw new Boom('"businessOwnerJid" is missing from the content', { statusCode: 400 });
366
- }
367
- const { imageMessage } = await prepareWAMessageMedia({ image: message.image || message.product.productImage }, options);
368
- const content = {
369
- ...message,
370
- product: {
371
- currencyCode: 'IDR',
372
- priceAmount1000: 1000,
373
- title: LIBRARY_NAME,
374
- ...message.product,
375
- productImage: imageMessage
376
- }
377
- };
378
- delete content.image;
379
- return content;
380
272
  }
381
- async function prepareStickerPackMessage(message, options) {
382
- const { cover, stickers = [], name = '📦 Sticker Pack', publisher = 'baileys', description = 'baileys' } = message;
383
- if (stickers.length > 60) {
384
- throw new Boom('Sticker pack exceeds the maximum limit of 60 stickers', { statusCode: 400 });
385
- }
386
- if (stickers.length === 0) {
387
- throw new Boom('Sticker pack must contain at least one sticker', { statusCode: 400 });
388
- }
389
- if (!cover) {
390
- throw new Boom('Sticker pack must contain a cover', { statusCode: 400 });
391
- }
392
- const logger = options.logger;
393
- let cacheableKey = false;
394
- if (Array.isArray(stickers) && stickers.length && options.mediaCache) {
395
- const urls = [];
396
- for (let i = 0; i < stickers.length; i++) {
397
- const data = stickers[i].data;
398
- if (typeof data === 'object' && data?.url) {
399
- urls.push(data.url);
400
- }
401
- }
402
- if (urls.length > 0) {
403
- cacheableKey = 'sticker:' + urls.join('@');
404
- }
405
- }
406
- if (cacheableKey) {
407
- const mediaBuff = await options.mediaCache.get(cacheableKey);
408
- if (mediaBuff) {
409
- logger?.debug({ cacheableKey }, 'got media cache hit');
410
- return proto.Message.StickerPackMessage.decode(mediaBuff);
411
- }
412
- }
413
- const lib = await getImageProcessingLibrary();
414
- const hasSharp = 'sharp' in lib && !!lib.sharp?.default;
415
- const hasImage = 'image' in lib && !!lib.image?.Transformer;
416
- const hasJimp = 'jimp' in lib && !!lib.jimp?.Jimp;
417
- if (!hasSharp && !hasImage) {
418
- throw new Boom('No image processing library (sharp or @napi-rs/image) available for converting sticker to WebP.');
419
- }
420
- const stickerPackIdValue = generateMessageIDV2();
421
- const stickerData = {};
422
- const stickerMetadata = new Array(stickers.length);
423
- for (let i = 0; i < stickers.length; i += CONCURRENCY_LIMIT) {
424
- const promises = [];
425
- const chunkEnd = Math.min(i + CONCURRENCY_LIMIT, stickers.length);
426
- for (let j = i; j < chunkEnd; j++) {
427
- promises.push((async (index) => {
428
- const sticker = stickers[index];
429
- const { stream } = await getStream(sticker.data);
430
- const buffer = await toBuffer(stream);
431
- let webpBuffer;
432
- let isAnimated = false;
433
- if (isWebPBuffer(buffer)) {
434
- webpBuffer = buffer;
435
- isAnimated = isAnimatedWebP(buffer);
436
- }
437
- else if (hasSharp) {
438
- webpBuffer = await lib.sharp.default(buffer)
439
- .resize(512, 512, { fit: 'inside' })
440
- .webp({ quality: 80 })
441
- .toBuffer();
442
- }
443
- else {
444
- webpBuffer = await new lib.image.Transformer(buffer)
445
- .resize(512, 512)
446
- .webp(80);
447
- }
448
- if (webpBuffer.length > 1024 * 1024) {
449
- throw new Boom(`Sticker at index ${index} exceeds the 1MB size limit`, { statusCode: 400 });
450
- }
451
- const hash = sha256(webpBuffer).toString('base64').replace(/\//g, '-');
452
- const fileName = `${hash}.webp`;
453
- stickerData[fileName] = [new Uint8Array(webpBuffer), { level: 0 }];
454
- stickerMetadata[index] = {
455
- fileName,
456
- mimetype: 'image/webp',
457
- isAnimated,
458
- emojis: sticker.emojis || ['✨'],
459
- accessibilityLabel: sticker.accessibilityLabel || '‎'
460
- };
461
- })(j));
462
- }
463
- await Promise.all(promises);
464
- }
465
- const trayIconFileName = `${stickerPackIdValue}.webp`;
466
- const { stream: coverStream } = await getStream(cover);
467
- const coverBuffer = await toBuffer(coverStream);
468
- let coverWebpBuffer;
469
- if (isWebPBuffer(coverBuffer)) {
470
- coverWebpBuffer = coverBuffer;
471
- }
472
- else if (hasSharp) {
473
- coverWebpBuffer = await lib.sharp.default(coverBuffer)
474
- .resize(512, 512, { fit: 'inside' })
475
- .webp({ quality: 80 })
476
- .toBuffer();
477
- }
478
- else {
479
- coverWebpBuffer = await new lib.image.Transformer(coverBuffer)
480
- .resize(512, 512)
481
- .webp(80);
482
- }
483
- stickerData[trayIconFileName] = [new Uint8Array(coverWebpBuffer), { level: 0 }];
484
- const zipBuffer = await new Promise((resolve, reject) => {
485
- zip(stickerData, (error, data) => error ? reject(error) : resolve(Buffer.from(data)));
486
- });
487
- const stickerPackUpload = await encryptedStream(zipBuffer, 'sticker-pack', {
488
- logger,
489
- opts: options.options
490
- });
491
- let stickerPackUploadResult;
492
- try {
493
- stickerPackUploadResult = await options.upload(stickerPackUpload.encFilePath, {
494
- fileEncSha256B64: stickerPackUpload.fileEncSha256.toString('base64'),
495
- mediaType: 'sticker-pack',
496
- timeoutMs: options.mediaUploadTimeoutMs
497
- });
498
- }
499
- finally {
500
- fs.unlink(stickerPackUpload.encFilePath).catch(() => logger?.warn('failed to remove tmp file'));
501
- }
502
- const obj = {
503
- name,
504
- publisher,
505
- stickerPackId: stickerPackIdValue,
506
- packDescription: description,
507
- stickerPackOrigin: proto.Message.StickerPackMessage.StickerPackOrigin.USER_CREATED,
508
- stickerPackSize: zipBuffer.length,
509
- stickers: stickerMetadata,
510
- fileSha256: stickerPackUpload.fileSha256,
511
- fileEncSha256: stickerPackUpload.fileEncSha256,
512
- mediaKey: stickerPackUpload.mediaKey,
513
- directPath: stickerPackUploadResult.directPath,
514
- fileLength: stickerPackUpload.fileLength,
515
- mediaKeyTimestamp: unixTimestampSeconds(),
516
- trayIconFileName
517
- };
518
- try {
519
- let thumbnailBuffer;
520
- if (hasSharp) {
521
- thumbnailBuffer = await lib.sharp.default(coverBuffer).resize(252, 252).jpeg().toBuffer();
522
- }
523
- else if (hasImage) {
524
- thumbnailBuffer = await new lib.image.Transformer(coverBuffer).resize(252, 252).jpeg();
525
- }
526
- else if (hasJimp) {
527
- const jimpImage = await lib.jimp.Jimp.read(coverBuffer);
528
- thumbnailBuffer = await jimpImage.resize({ w: 252, h: 252 }).getBuffer('image/jpeg');
529
- }
530
- else {
531
- throw new Error('No image processing library available for thumbnail generation');
532
- }
533
- if (!thumbnailBuffer || thumbnailBuffer.length === 0) {
534
- throw new Error('Failed to generate thumbnail buffer');
535
- }
536
- const thumbUpload = await encryptedStream(thumbnailBuffer, 'thumbnail-sticker-pack', {
537
- logger,
538
- opts: options.options,
539
- mediaKey: stickerPackUpload.mediaKey
540
- });
541
- let thumbUploadResult;
542
- try {
543
- thumbUploadResult = await options.upload(thumbUpload.encFilePath, {
544
- fileEncSha256B64: thumbUpload.fileEncSha256.toString('base64'),
545
- mediaType: 'thumbnail-sticker-pack',
546
- timeoutMs: options.mediaUploadTimeoutMs
547
- });
548
- }
549
- finally {
550
- fs.unlink(thumbUpload.encFilePath).catch(() => logger?.warn('failed to remove tmp file'));
551
- }
552
- Object.assign(obj, {
553
- thumbnailDirectPath: thumbUploadResult.directPath,
554
- thumbnailSha256: thumbUpload.fileSha256,
555
- thumbnailEncSha256: thumbUpload.fileEncSha256,
556
- thumbnailHeight: 252,
557
- thumbnailWidth: 252,
558
- imageDataHash: sha256(thumbnailBuffer).toString('base64')
559
- });
560
- }
561
- catch (error) {
562
- logger?.warn(`Thumbnail generation failed: ${error}`);
563
- }
564
- if (cacheableKey) {
565
- logger?.debug({ cacheableKey }, 'set cache (background)');
566
- options.mediaCache.set(cacheableKey, WAProto.Message.StickerPackMessage.encode(obj).finish());
567
- }
568
- return WAProto.Message.StickerPackMessage.fromObject(obj);
569
- }
570
- function prepareNativeFlowButtons(message) {
571
- const buttons = message.nativeFlow;
572
- const isButtonsFieldArray = Array.isArray(buttons);
573
- const correctedField = isButtonsFieldArray ? buttons : buttons.buttons;
574
- const messageParamsJson = {};
575
- if (hasOptionalProperty(message, 'offerText') && !!message.offerText) {
576
- Object.assign(messageParamsJson, {
577
- limited_time_offer: {
578
- text: message.offerText || LIBRARY_NAME,
579
- url: message.offerUrl || DONATE_URL,
580
- copy_code: message.offerCode,
581
- expiration_time: message.offerExpiration
582
- }
583
- });
584
- }
585
- if (hasOptionalProperty(message, 'optionText') && !!message.optionText) {
586
- Object.assign(messageParamsJson, {
587
- bottom_sheet: {
588
- in_thread_buttons_limit: 1,
589
- divider_indices: Array.from({ length: correctedField.length }, (_, index) => index),
590
- list_title: message.optionTitle || '📄 Select Options',
591
- button_title: message.optionText
592
- }
593
- });
594
- }
595
- return {
596
- buttons: correctedField.map((button) => {
597
- const buttonText = button.text || button.buttonText;
598
- const buttonIcon = button.icon?.toUpperCase();
599
- if (hasOptionalProperty(button, 'id') && !!button.id) {
600
- return {
601
- name: 'quick_reply',
602
- buttonParamsJson: JSON.stringify({
603
- display_text: buttonText || '👉🏻 Click',
604
- id: button.id,
605
- icon: buttonIcon
606
- })
607
- };
608
- }
609
- else if (hasOptionalProperty(button, 'copy') && !!button.copy) {
610
- return {
611
- name: 'cta_copy',
612
- buttonParamsJson: JSON.stringify({
613
- display_text: buttonText || '📋 Copy',
614
- copy_code: button.copy,
615
- icon: buttonIcon
616
- })
617
- };
618
- }
619
- else if (hasOptionalProperty(button, 'url') && !!button.url) {
620
- return {
621
- name: 'cta_url',
622
- buttonParamsJson: JSON.stringify({
623
- display_text: buttonText || '🌐 Visit',
624
- url: button.url,
625
- merchant_url: button.url,
626
- webview_interaction: button.useWebview,
627
- icon: buttonIcon
628
- })
629
- };
630
- }
631
- else if (hasOptionalProperty(button, 'call') && !!button.call) {
632
- return {
633
- name: 'cta_call',
634
- buttonParamsJson: JSON.stringify({
635
- display_text: buttonText || '📞 Call',
636
- phone_number: button.call,
637
- icon: buttonIcon
638
- })
639
- };
640
- }
641
- else if (hasOptionalProperty(button, 'sections') && !!button.sections) {
642
- return {
643
- name: 'single_select',
644
- buttonParamsJson: JSON.stringify({
645
- title: buttonText || '📋 Select',
646
- sections: button.sections,
647
- icon: buttonIcon
648
- })
649
- };
650
- }
651
- return button;
652
- }),
653
- messageParamsJson: JSON.stringify(messageParamsJson),
654
- messageVersion: 3
655
- };
656
- }
657
- export const hasValidAlbumMedia = (message) => {
658
- return message.imageMessage || message.videoMessage;
659
- };
660
- export const hasValidInteractiveHeader = (message) => {
661
- return (message.imageMessage ||
662
- message.videoMessage ||
663
- message.documentMessage ||
664
- message.productMessage ||
665
- message.locationMessage);
666
- };
667
- export const hasValidCarouselHeader = (message) => {
668
- return message.imageMessage || message.videoMessage || message.productMessage;
669
- };
670
- const hasProp = (obj, key) => {
671
- return obj && typeof obj === 'object' && key in obj && obj[key] !== null && obj[key] !== undefined;
672
- };
673
- const hasOptProp = (obj, key) => {
674
- return obj && typeof obj === 'object' && key in obj && obj[key] !== null;
675
- };
676
273
  export const generateWAMessageContent = async (message, options) => {
677
274
  var _a, _b;
678
- const msg = message;
679
275
  let m = {};
680
- if (hasProp(msg, 'raw')) {
681
- delete msg.raw;
682
- return msg;
683
- }
684
- else if (hasProp(msg, 'code') ||
685
- hasProp(msg, 'links') ||
686
- hasProp(msg, 'table') ||
687
- hasProp(msg, 'richResponse') ||
688
- hasProp(msg, 'contentText') ||
689
- hasProp(msg, 'aiDisclaimer') ||
690
- (hasOptProp(msg, 'ai') && !!msg.ai)) {
691
- m = prepareRichResponseMessage(msg);
692
- }
693
- else if (hasProp(msg, 'text')) {
694
- const extContent = { text: msg.text };
695
- let urlInfo = msg.linkPreview;
276
+ if (hasNonNullishProperty(message, 'text')) {
277
+ const extContent = { text: message.text };
278
+ let urlInfo = message.linkPreview;
696
279
  if (typeof urlInfo === 'undefined') {
697
- urlInfo = await generateLinkPreviewIfRequired(msg.text, options.getUrlInfo, options.logger);
280
+ urlInfo = await generateLinkPreviewIfRequired(message.text, options.getUrlInfo, options.logger);
698
281
  }
699
282
  if (urlInfo) {
700
283
  extContent.matchedText = urlInfo['matched-text'];
701
284
  extContent.jpegThumbnail = urlInfo.jpegThumbnail;
702
285
  extContent.description = urlInfo.description;
703
286
  extContent.title = urlInfo.title;
704
- extContent.previewType = urlInfo.previewType ?? 0;
705
- extContent.linkPreviewMetadata = urlInfo.linkPreviewMetadata;
287
+ extContent.previewType = 0;
706
288
  const img = urlInfo.highQualityThumbnail;
707
289
  if (img) {
708
290
  extContent.thumbnailDirectPath = img.directPath;
@@ -714,19 +296,6 @@ export const generateWAMessageContent = async (message, options) => {
714
296
  extContent.thumbnailEncSha256 = img.fileEncSha256;
715
297
  }
716
298
  }
717
- const faviconData = msg.favicon;
718
- if (faviconData && typeof options.upload === 'function') {
719
- const { imageMessage } = await prepareWAMessageMedia({ image: faviconData }, options);
720
- extContent.faviconMMSMetadata = {
721
- thumbnailDirectPath: imageMessage.directPath,
722
- mediaKey: imageMessage.mediaKey,
723
- mediaKeyTimestamp: imageMessage.mediaKeyTimestamp,
724
- thumbnailWidth: 32,
725
- thumbnailHeight: 32,
726
- thumbnailSha256: imageMessage.fileSha256,
727
- thumbnailEncSha256: imageMessage.fileEncSha256
728
- };
729
- }
730
299
  if (options.backgroundColor) {
731
300
  extContent.backgroundArgb = await assertColor(options.backgroundColor);
732
301
  }
@@ -735,53 +304,55 @@ export const generateWAMessageContent = async (message, options) => {
735
304
  }
736
305
  m.extendedTextMessage = extContent;
737
306
  }
738
- else if (hasProp(msg, 'contacts')) {
739
- const contactLen = msg.contacts.contacts.length;
307
+ else if (hasNonNullishProperty(message, 'contacts')) {
308
+ const contactLen = message.contacts.contacts.length;
740
309
  if (!contactLen) {
741
310
  throw new Boom('require atleast 1 contact', { statusCode: 400 });
742
311
  }
743
312
  if (contactLen === 1) {
744
- m.contactMessage = WAProto.Message.ContactMessage.create(msg.contacts.contacts[0]);
313
+ m.contactMessage = WAProto.Message.ContactMessage.create(message.contacts.contacts[0]);
745
314
  }
746
315
  else {
747
- m.contactsArrayMessage = WAProto.Message.ContactsArrayMessage.create(msg.contacts);
316
+ m.contactsArrayMessage = WAProto.Message.ContactsArrayMessage.create(message.contacts);
748
317
  }
749
318
  }
750
- else if (hasProp(msg, 'location')) {
751
- m.locationMessage = WAProto.Message.LocationMessage.create(msg.location);
319
+ else if (hasNonNullishProperty(message, 'location')) {
320
+ m.locationMessage = WAProto.Message.LocationMessage.create(message.location);
752
321
  }
753
- else if (hasProp(msg, 'react')) {
754
- if (!msg.react.senderTimestampMs) {
755
- msg.react.senderTimestampMs = Date.now();
322
+ else if (hasNonNullishProperty(message, 'react')) {
323
+ if (!message.react.senderTimestampMs) {
324
+ message.react.senderTimestampMs = Date.now();
756
325
  }
757
- m.reactionMessage = WAProto.Message.ReactionMessage.create(msg.react);
326
+ m.reactionMessage = WAProto.Message.ReactionMessage.create(message.react);
758
327
  }
759
- else if (hasProp(msg, 'delete')) {
328
+ else if (hasNonNullishProperty(message, 'delete')) {
760
329
  m.protocolMessage = {
761
- key: msg.delete,
330
+ key: message.delete,
762
331
  type: WAProto.Message.ProtocolMessage.Type.REVOKE
763
332
  };
764
333
  }
765
- else if (hasProp(msg, 'forward')) {
766
- m = generateForwardMessageContent(msg.forward, msg.force);
334
+ else if (hasNonNullishProperty(message, 'forward')) {
335
+ m = generateForwardMessageContent(message.forward, message.force);
767
336
  }
768
- else if (hasProp(msg, 'disappearingMessagesInChat')) {
769
- const exp = typeof msg.disappearingMessagesInChat === 'boolean'
770
- ? msg.disappearingMessagesInChat
337
+ else if (hasNonNullishProperty(message, 'disappearingMessagesInChat')) {
338
+ const exp = typeof message.disappearingMessagesInChat === 'boolean'
339
+ ? message.disappearingMessagesInChat
771
340
  ? WA_DEFAULT_EPHEMERAL
772
341
  : 0
773
- : msg.disappearingMessagesInChat;
342
+ : message.disappearingMessagesInChat;
774
343
  m = prepareDisappearingMessageSettingContent(exp);
775
344
  }
776
- else if (hasProp(msg, 'groupInvite')) {
345
+ else if (hasNonNullishProperty(message, 'groupInvite')) {
777
346
  m.groupInviteMessage = {};
778
- m.groupInviteMessage.inviteCode = msg.groupInvite.inviteCode;
779
- m.groupInviteMessage.inviteExpiration = msg.groupInvite.inviteExpiration;
780
- m.groupInviteMessage.caption = msg.groupInvite.text;
781
- m.groupInviteMessage.groupJid = msg.groupInvite.jid;
782
- m.groupInviteMessage.groupName = msg.groupInvite.subject;
347
+ m.groupInviteMessage.inviteCode = message.groupInvite.inviteCode;
348
+ m.groupInviteMessage.inviteExpiration = message.groupInvite.inviteExpiration;
349
+ m.groupInviteMessage.caption = message.groupInvite.text;
350
+ m.groupInviteMessage.groupJid = message.groupInvite.jid;
351
+ m.groupInviteMessage.groupName = message.groupInvite.subject;
352
+ //TODO: use built-in interface and get disappearing mode info etc.
353
+ //TODO: cache / use store!?
783
354
  if (options.getProfilePicUrl) {
784
- const pfpUrl = await options.getProfilePicUrl(msg.groupInvite.jid, 'preview');
355
+ const pfpUrl = await options.getProfilePicUrl(message.groupInvite.jid, 'preview');
785
356
  if (pfpUrl) {
786
357
  const resp = await fetch(pfpUrl, { method: 'GET', dispatcher: options?.options?.dispatcher });
787
358
  if (resp.ok) {
@@ -791,227 +362,123 @@ export const generateWAMessageContent = async (message, options) => {
791
362
  }
792
363
  }
793
364
  }
794
- else if (hasProp(msg, 'stickers')) {
795
- m.stickerPackMessage = await prepareStickerPackMessage(msg, options);
796
- }
797
- else if (hasProp(msg, 'pin')) {
365
+ else if (hasNonNullishProperty(message, 'pin')) {
798
366
  m.pinInChatMessage = {};
799
367
  m.messageContextInfo = {};
800
- m.pinInChatMessage.key = msg.pin;
801
- m.pinInChatMessage.type = msg.type;
368
+ m.pinInChatMessage.key = message.pin;
369
+ m.pinInChatMessage.type = message.type;
802
370
  m.pinInChatMessage.senderTimestampMs = Date.now();
803
- m.messageContextInfo.messageAddOnDurationInSecs = msg.type === 1 ? msg.time || 86400 : 0;
804
- }
805
- else if (hasProp(msg, 'keep')) {
806
- m.keepInChatMessage = {};
807
- m.keepInChatMessage.key = msg.keep;
808
- m.keepInChatMessage.keepType = msg.type;
809
- m.keepInChatMessage.timestampMs = Date.now();
371
+ m.messageContextInfo.messageAddOnDurationInSecs = message.type === 1 ? message.time || 86400 : 0;
810
372
  }
811
- else if (hasProp(msg, 'flowReply')) {
812
- m.interactiveResponseMessage = {
813
- body: {
814
- format: msg.flowReply.format || proto.Message.InteractiveResponseMessage.Body.Format.DEFAULT,
815
- text: msg.flowReply.text
816
- },
817
- nativeFlowResponseMessage: {
818
- name: msg.flowReply.name,
819
- paramsJson: msg.flowReply.paramsJson || '{}',
820
- version: msg.flowReply.version || 1
821
- }
822
- };
823
- }
824
- else if (hasProp(msg, 'buttonReply')) {
825
- switch (msg.type) {
373
+ else if (hasNonNullishProperty(message, 'buttonReply')) {
374
+ switch (message.type) {
826
375
  case 'template':
827
376
  m.templateButtonReplyMessage = {
828
- selectedDisplayText: msg.buttonReply.displayText,
829
- selectedId: msg.buttonReply.id,
830
- selectedIndex: msg.buttonReply.index
377
+ selectedDisplayText: message.buttonReply.displayText,
378
+ selectedId: message.buttonReply.id,
379
+ selectedIndex: message.buttonReply.index
831
380
  };
832
381
  break;
833
382
  case 'plain':
834
383
  m.buttonsResponseMessage = {
835
- selectedButtonId: msg.buttonReply.id,
836
- selectedDisplayText: msg.buttonReply.displayText,
384
+ selectedButtonId: message.buttonReply.id,
385
+ selectedDisplayText: message.buttonReply.displayText,
837
386
  type: proto.Message.ButtonsResponseMessage.Type.DISPLAY_TEXT
838
387
  };
839
388
  break;
840
389
  }
841
390
  }
842
- else if (hasProp(msg, 'listReply')) {
843
- m.listResponseMessage = {
844
- description: msg.listReply.description,
845
- listType: proto.Message.ListResponseMessage.ListType.SINGLE_SELECT,
846
- singleSelectReply: {
847
- selectedRowId: msg.listReply.id
848
- },
849
- title: msg.listReply.title
850
- };
851
- }
852
- else if (hasOptProp(msg, 'ptv') && msg.ptv) {
853
- const { videoMessage } = await prepareWAMessageMedia({ video: msg.video }, options);
391
+ else if (hasOptionalProperty(message, 'ptv') && message.ptv) {
392
+ const { videoMessage } = await prepareWAMessageMedia({ video: message.video }, options);
854
393
  m.ptvMessage = videoMessage;
855
394
  }
856
- else if (hasProp(msg, 'product')) {
857
- m.productMessage = await prepareProductMessage(msg, options);
395
+ else if (hasNonNullishProperty(message, 'product')) {
396
+ const { imageMessage } = await prepareWAMessageMedia({ image: message.product.productImage }, options);
397
+ m.productMessage = WAProto.Message.ProductMessage.create({
398
+ ...message,
399
+ product: {
400
+ ...message.product,
401
+ productImage: imageMessage
402
+ }
403
+ });
858
404
  }
859
- else if (hasProp(msg, 'event')) {
405
+ else if (hasNonNullishProperty(message, 'listReply')) {
406
+ m.listResponseMessage = { ...message.listReply };
407
+ }
408
+ else if (hasNonNullishProperty(message, 'event')) {
860
409
  m.eventMessage = {};
861
- const startTime = Math.floor(msg.event.startDate.getTime() / 1000);
862
- if (msg.event.call && options.getCallLink) {
863
- const token = await options.getCallLink(msg.event.call, { startTime });
864
- m.eventMessage.joinLink = (msg.event.call === 'audio' ? CALL_AUDIO_PREFIX : CALL_VIDEO_PREFIX) + token;
410
+ const startTime = Math.floor(message.event.startDate.getTime() / 1000);
411
+ if (message.event.call && options.getCallLink) {
412
+ const token = await options.getCallLink(message.event.call, { startTime });
413
+ m.eventMessage.joinLink = (message.event.call === 'audio' ? CALL_AUDIO_PREFIX : CALL_VIDEO_PREFIX) + token;
865
414
  }
866
415
  m.messageContextInfo = {
867
- messageSecret: msg.event.messageSecret || randomBytes(32)
416
+ // encKey
417
+ messageSecret: message.event.messageSecret || randomBytes(32)
868
418
  };
869
- m.eventMessage.name = msg.event.name;
870
- m.eventMessage.description = msg.event.description;
419
+ m.eventMessage.name = message.event.name;
420
+ m.eventMessage.description = message.event.description;
871
421
  m.eventMessage.startTime = startTime;
872
- m.eventMessage.endTime = msg.event.endDate ? msg.event.endDate.getTime() / 1000 : undefined;
873
- m.eventMessage.isCanceled = msg.event.isCancelled ?? false;
874
- m.eventMessage.extraGuestsAllowed = msg.event.extraGuestsAllowed;
875
- m.eventMessage.isScheduleCall = msg.event.isScheduleCall ?? false;
876
- m.eventMessage.location = msg.event.location;
877
- }
878
- else if (hasProp(msg, 'poll')) {
879
- (_a = msg.poll).selectableCount || (_a.selectableCount = 0);
880
- (_b = msg.poll).toAnnouncementGroup || (_b.toAnnouncementGroup = false);
881
- if (!Array.isArray(msg.poll.values)) {
422
+ m.eventMessage.endTime = message.event.endDate ? message.event.endDate.getTime() / 1000 : undefined;
423
+ m.eventMessage.isCanceled = message.event.isCancelled ?? false;
424
+ m.eventMessage.extraGuestsAllowed = message.event.extraGuestsAllowed;
425
+ m.eventMessage.isScheduleCall = message.event.isScheduleCall ?? false;
426
+ m.eventMessage.location = message.event.location;
427
+ }
428
+ else if (hasNonNullishProperty(message, 'poll')) {
429
+ (_a = message.poll).selectableCount || (_a.selectableCount = 0);
430
+ (_b = message.poll).toAnnouncementGroup || (_b.toAnnouncementGroup = false);
431
+ if (!Array.isArray(message.poll.values)) {
882
432
  throw new Boom('Invalid poll values', { statusCode: 400 });
883
433
  }
884
- if (msg.poll.selectableCount < 0 || msg.poll.selectableCount > msg.poll.values.length) {
885
- throw new Boom(`poll.selectableCount in poll should be >= 0 and <= ${msg.poll.values.length}`, {
434
+ if (message.poll.selectableCount < 0 || message.poll.selectableCount > message.poll.values.length) {
435
+ throw new Boom(`poll.selectableCount in poll should be >= 0 and <= ${message.poll.values.length}`, {
886
436
  statusCode: 400
887
437
  });
888
438
  }
439
+ m.messageContextInfo = {
440
+ // encKey
441
+ messageSecret: message.poll.messageSecret || randomBytes(32)
442
+ };
889
443
  const pollCreationMessage = {
890
- name: msg.poll.name,
891
- selectableOptionsCount: msg.poll.selectableCount,
892
- options: msg.poll.values.map((optionName) => ({ optionName })),
893
- endTime: msg.poll.endDate ? msg.poll.endDate.getTime() : undefined,
894
- hideParticipantName: msg.poll.hideVoter ?? false,
895
- allowAddOption: msg.poll.canAddOption ?? false
444
+ name: message.poll.name,
445
+ selectableOptionsCount: message.poll.selectableCount,
446
+ options: message.poll.values.map(optionName => ({ optionName }))
896
447
  };
897
- if (msg.poll.toAnnouncementGroup) {
448
+ if (message.poll.toAnnouncementGroup) {
449
+ // poll v2 is for community announcement groups (single select and multiple)
898
450
  m.pollCreationMessageV2 = pollCreationMessage;
899
451
  }
900
452
  else {
901
- if (msg.poll.pollType === 1) {
902
- if (!msg.poll.correctAnswer) {
903
- throw new Boom('No "correctAnswer" provided for quiz', { statusCode: 400 });
904
- }
905
- m.pollCreationMessageV5 = {
906
- ...pollCreationMessage,
907
- correctAnswer: {
908
- optionName: msg.poll.correctAnswer.toString()
909
- },
910
- pollType: 1,
911
- selectableOptionsCount: 1
912
- };
913
- }
914
- else if (msg.poll.selectableCount === 1) {
453
+ if (message.poll.selectableCount === 1) {
454
+ //poll v3 is for single select polls
915
455
  m.pollCreationMessageV3 = pollCreationMessage;
916
456
  }
917
457
  else {
458
+ // poll for multiple choice polls
918
459
  m.pollCreationMessage = pollCreationMessage;
919
460
  }
920
461
  }
921
- m.messageContextInfo = {
922
- messageSecret: msg.poll.messageSecret || randomBytes(32)
923
- };
924
- }
925
- else if (hasProp(msg, 'pollResult')) {
926
- const pollResultSnapshotMessage = {
927
- name: msg.pollResult.name,
928
- pollVotes: msg.pollResult.votes.map((vote) => ({
929
- optionName: vote.name,
930
- optionVoteCount: parseInt(vote.voteCount)
931
- }))
932
- };
933
- if (msg.pollResult.pollType === 1) {
934
- pollResultSnapshotMessage.pollType = proto.Message.PollType.QUIZ;
935
- m.pollResultSnapshotMessageV3 = pollResultSnapshotMessage;
936
- }
937
- else {
938
- pollResultSnapshotMessage.pollType = proto.Message.PollType.POLL;
939
- m.pollResultSnapshotMessage = pollResultSnapshotMessage;
940
- }
941
462
  }
942
- else if (hasProp(msg, 'pollUpdate')) {
943
- if (!msg.pollUpdate.key) {
944
- throw new Boom('Message key is required', { statusCode: 400 });
945
- }
946
- if (!msg.pollUpdate.vote) {
947
- throw new Boom('Encrypted vote payload is required', { statusCode: 400 });
948
- }
949
- m.pollUpdateMessage = {
950
- metadata: msg.pollUpdate.metadata,
951
- pollCreationMessageKey: msg.pollUpdate.key,
952
- senderTimestampMs: Date.now(),
953
- vote: msg.pollUpdate.vote
954
- };
955
- }
956
- else if (hasProp(msg, 'paymentInviteServiceType')) {
957
- m.paymentInviteMessage = {
958
- expiryTimestamp: Date.now(),
959
- serviceType: msg.paymentInviteServiceType
960
- };
961
- }
962
- else if (hasProp(msg, 'orderText')) {
963
- if (!Buffer.isBuffer(msg.thumbnail)) {
964
- throw new Boom('Must provide thumbnail buffer in order message', { statusCode: 400 });
965
- }
966
- m.orderMessage = {
967
- itemCount: 1,
968
- messageVersion: 1,
969
- orderTitle: LIBRARY_NAME,
970
- status: proto.Message.OrderMessage.OrderStatus.INQUIRY,
971
- surface: proto.Message.OrderMessage.OrderSurface.CATALOG,
972
- token: generateMessageIDV2(),
973
- totalAmount1000: 1000,
974
- totalCurrencyCode: 'IDR',
975
- ...msg,
976
- message: msg.orderText
977
- };
978
- delete m.orderMessage.orderText;
979
- }
980
- else if (hasProp(msg, 'album')) {
981
- if (!Array.isArray(msg.album)) {
982
- throw new Boom('Invalid album type. Expected an array.', { statusCode: 400 });
983
- }
984
- let videoCount = 0;
985
- for (let i = 0; i < msg.album.length; i++) {
986
- if (msg.album[i].video)
987
- videoCount++;
988
- }
989
- let imageCount = 0;
990
- for (let i = 0; i < msg.album.length; i++) {
991
- if (msg.album[i].image)
992
- imageCount++;
993
- }
994
- if (videoCount + imageCount < 2) {
995
- throw new Boom('Minimum provide 2 media to upload album message', { statusCode: 400 });
996
- }
463
+ else if (hasNonNullishProperty(message, 'album')) {
997
464
  m.albumMessage = {
998
- expectedImageCount: imageCount,
999
- expectedVideoCount: videoCount
465
+ expectedImageCount: message.album.expectedImageCount,
466
+ expectedVideoCount: message.album.expectedVideoCount
1000
467
  };
1001
468
  }
1002
- else if (hasProp(msg, 'sharePhoneNumber')) {
469
+ else if (hasNonNullishProperty(message, 'sharePhoneNumber')) {
1003
470
  m.protocolMessage = {
1004
471
  type: proto.Message.ProtocolMessage.Type.SHARE_PHONE_NUMBER
1005
472
  };
1006
473
  }
1007
- else if (hasProp(msg, 'requestPhoneNumber')) {
474
+ else if (hasNonNullishProperty(message, 'requestPhoneNumber')) {
1008
475
  m.requestPhoneNumberMessage = {};
1009
476
  }
1010
- else if (hasProp(msg, 'limitSharing')) {
477
+ else if (hasNonNullishProperty(message, 'limitSharing')) {
1011
478
  m.protocolMessage = {
1012
479
  type: proto.Message.ProtocolMessage.Type.LIMIT_SHARING,
1013
480
  limitSharing: {
1014
- sharingLimited: msg.limitSharing === true,
481
+ sharingLimited: message.limitSharing === true,
1015
482
  trigger: 1,
1016
483
  limitSharingSettingTimestamp: Date.now(),
1017
484
  initiatedByMe: true
@@ -1019,408 +486,57 @@ export const generateWAMessageContent = async (message, options) => {
1019
486
  };
1020
487
  }
1021
488
  else {
1022
- m = await prepareWAMessageMedia(msg, options);
1023
- }
1024
- if (hasProp(msg, 'buttons')) {
1025
- const buttonsMessage = {
1026
- buttons: msg.buttons.map((button) => {
1027
- const buttonText = button.text || button.buttonText;
1028
- if (hasOptProp(button, 'sections')) {
1029
- return {
1030
- nativeFlowInfo: {
1031
- name: 'single_select',
1032
- paramsJson: JSON.stringify({
1033
- title: buttonText,
1034
- sections: button.sections
1035
- })
1036
- },
1037
- type: ButtonType.NATIVE_FLOW
1038
- };
1039
- }
1040
- else if (hasOptProp(button, 'name')) {
1041
- return {
1042
- nativeFlowInfo: {
1043
- name: button.name,
1044
- paramsJson: button.paramsJson
1045
- },
1046
- type: ButtonType.NATIVE_FLOW
1047
- };
1048
- }
1049
- return {
1050
- buttonId: button.id || button.buttonId,
1051
- buttonText: typeof buttonText === 'string' ? { displayText: buttonText } : buttonText,
1052
- type: button.type || ButtonType.RESPONSE
1053
- };
1054
- })
1055
- };
1056
- if (hasOptProp(msg, 'text')) {
1057
- buttonsMessage.contentText = msg.text;
1058
- buttonsMessage.headerType = ButtonHeaderType.EMPTY;
1059
- }
1060
- else {
1061
- if (hasOptProp(msg, 'caption')) {
1062
- buttonsMessage.contentText = msg.caption;
1063
- }
1064
- const type = Object.keys(m)[0].replace('Message', '').toUpperCase();
1065
- buttonsMessage.headerType = ButtonHeaderType[type];
1066
- Object.assign(buttonsMessage, m);
1067
- }
1068
- if (hasOptProp(msg, 'footer')) {
1069
- buttonsMessage.footerText = msg.footer;
1070
- }
1071
- m = { buttonsMessage };
1072
- }
1073
- else if (hasProp(msg, 'sections')) {
1074
- const listMessage = {
1075
- sections: msg.sections,
1076
- buttonText: msg.buttonText,
1077
- title: msg.title,
1078
- footerText: msg.footer,
1079
- description: msg.text,
1080
- listType: ListType.SINGLE_SELECT
1081
- };
1082
- m = { listMessage };
1083
- }
1084
- else if (hasProp(msg, 'templateButtons')) {
1085
- const hydratedTemplate = {
1086
- hydratedButtons: msg.templateButtons.map((button, i) => {
1087
- const buttonText = button.text || button.buttonText;
1088
- if (hasOptProp(button, 'id')) {
1089
- return {
1090
- index: i,
1091
- quickReplyButton: {
1092
- displayText: buttonText || '👉🏻 Click',
1093
- id: button.id
1094
- }
1095
- };
1096
- }
1097
- else if (hasOptProp(button, 'url')) {
1098
- return {
1099
- index: i,
1100
- urlButton: {
1101
- displayText: buttonText || '🌐 Visit',
1102
- url: button.url
1103
- }
1104
- };
1105
- }
1106
- else if (hasOptProp(button, 'call')) {
1107
- return {
1108
- index: i,
1109
- callButton: {
1110
- displayText: buttonText || '📞 Call',
1111
- phoneNumber: button.call
1112
- }
1113
- };
1114
- }
1115
- button.index = button.index || i;
1116
- return button;
1117
- })
1118
- };
1119
- if (hasOptProp(msg, 'text')) {
1120
- hydratedTemplate.hydratedContentText = msg.text;
1121
- }
1122
- else {
1123
- if (hasOptProp(msg, 'caption')) {
1124
- hydratedTemplate.hydratedTitleText = msg.title;
1125
- hydratedTemplate.hydratedContentText = msg.caption;
1126
- }
1127
- Object.assign(hydratedTemplate, m);
1128
- }
1129
- if (hasOptProp(msg, 'footer')) {
1130
- hydratedTemplate.hydratedFooterText = msg.footer;
1131
- }
1132
- hydratedTemplate.templateId = msg.id || 'template-' + Date.now();
1133
- m = {
1134
- templateMessage: {
1135
- hydratedFourRowTemplate: hydratedTemplate,
1136
- hydratedTemplate: hydratedTemplate
1137
- }
1138
- };
1139
- }
1140
- else if (hasProp(msg, 'nativeFlow')) {
1141
- const interactiveMessage = {
1142
- nativeFlowMessage: prepareNativeFlowButtons(msg)
1143
- };
1144
- if (hasOptProp(msg, 'bizJid')) {
1145
- interactiveMessage.collectionMessage = {
1146
- bizJid: msg.bizJid,
1147
- id: msg.id,
1148
- messageVersion: 1
1149
- };
1150
- }
1151
- else if (hasOptProp(msg, 'shopSurface')) {
1152
- interactiveMessage.shopStorefrontMessage = {
1153
- surface: msg.shopSurface,
1154
- id: msg.id,
1155
- messageVersion: 1
1156
- };
1157
- }
1158
- if (hasOptProp(msg, 'text')) {
1159
- interactiveMessage.body = { text: msg.text };
1160
- }
1161
- else {
1162
- if (hasOptProp(msg, 'caption')) {
1163
- const isValidHeader = hasValidInteractiveHeader(m);
1164
- if (!isValidHeader) {
1165
- throw new Boom('Invalid media type for interactive message header', { statusCode: 400 });
1166
- }
1167
- interactiveMessage.header = {
1168
- title: msg.title || '',
1169
- subtitle: msg.subtitle || '',
1170
- hasMediaAttachment: isValidHeader
1171
- };
1172
- interactiveMessage.body = { text: msg.caption };
1173
- }
1174
- if (hasOptProp(msg, 'thumbnail') && !!msg.thumbnail) {
1175
- interactiveMessage.jpegThumbnail = msg.thumbnail;
1176
- }
1177
- Object.assign(interactiveMessage.header, m);
1178
- }
1179
- if (hasOptProp(msg, 'audioFooter')) {
1180
- const { audioMessage } = await prepareWAMessageMedia({ audio: msg.audioFooter }, options);
1181
- interactiveMessage.footer = {
1182
- audioMessage,
1183
- hasMediaAttachment: true
1184
- };
1185
- }
1186
- else if (hasOptProp(msg, 'footer')) {
1187
- interactiveMessage.footer = { text: msg.footer };
1188
- }
1189
- m = { interactiveMessage };
1190
- }
1191
- else if (hasProp(msg, 'cards')) {
1192
- const interactiveMessage = {
1193
- carouselMessage: {
1194
- cards: await Promise.all(msg.cards.map(async (card) => {
1195
- let carouselHeader = {};
1196
- if (hasProp(card, 'product')) {
1197
- carouselHeader.productMessage = await prepareProductMessage(card, options);
1198
- }
1199
- else {
1200
- carouselHeader = await prepareWAMessageMedia(card, options).catch(() => ({}));
1201
- }
1202
- const isValidHeader = hasValidCarouselHeader(carouselHeader);
1203
- if (!isValidHeader) {
1204
- throw new Boom('Invalid media type for carousel card', { statusCode: 400 });
1205
- }
1206
- const carouselCard = {
1207
- nativeFlowMessage: prepareNativeFlowButtons(card.nativeFlow ? card : [])
1208
- };
1209
- if (hasOptProp(card, 'text')) {
1210
- carouselCard.body = { text: card.text };
1211
- }
1212
- else {
1213
- if (hasOptProp(card, 'caption')) {
1214
- carouselCard.header = {
1215
- title: card.title || '',
1216
- subtitle: card.subtitle || '',
1217
- hasMediaAttachment: isValidHeader
1218
- };
1219
- carouselCard.body = { text: card.caption };
1220
- }
1221
- if (hasOptProp(card, 'thumbnail') && !!card.thumbnail) {
1222
- carouselCard.jpegThumbnail = card.thumbnail;
1223
- }
1224
- Object.assign(carouselCard.header, carouselHeader);
1225
- }
1226
- if (hasOptProp(card, 'audioFooter')) {
1227
- const { audioMessage } = await prepareWAMessageMedia({ audio: card.audioFooter }, options);
1228
- carouselCard.footer = {
1229
- audioMessage,
1230
- hasMediaAttachment: true
1231
- };
1232
- }
1233
- else if (hasOptProp(card, 'footer')) {
1234
- carouselCard.footer = { text: card.footer };
1235
- }
1236
- return carouselCard;
1237
- })),
1238
- carouselCardType: CarouselCardType.UNKNOWN,
1239
- messageVersion: 1
1240
- }
1241
- };
1242
- if (hasOptProp(msg, 'text')) {
1243
- interactiveMessage.body = { text: msg.text };
1244
- }
1245
- if (hasOptProp(msg, 'footer')) {
1246
- interactiveMessage.footer = { text: msg.footer };
1247
- }
1248
- m = { interactiveMessage };
489
+ m = await prepareWAMessageMedia(message, options);
1249
490
  }
1250
- else if (hasProp(msg, 'requestPaymentFrom')) {
1251
- const requestPaymentMessage = {
1252
- amount: {
1253
- currencyCode: 'IDR',
1254
- offset: 1000,
1255
- value: 1000
1256
- },
1257
- amount1000: 1000,
1258
- currencyCodeIso4217: 'IDR',
1259
- expiryTimestamp: Date.now(),
1260
- noteMessage: m,
1261
- requestFrom: msg.requestPaymentFrom,
1262
- ...msg
1263
- };
1264
- delete requestPaymentMessage.requestPaymentFrom;
1265
- if (hasProp(m, 'extendedTextMessage') || hasProp(m, 'stickerMessage')) {
1266
- Object.assign(requestPaymentMessage.noteMessage, m);
1267
- }
1268
- else {
1269
- throw new Boom('Invalid message type for request payment note message', { statusCode: 400 });
1270
- }
1271
- m = { requestPaymentMessage };
1272
- }
1273
- else if (hasProp(msg, 'invoiceNote')) {
1274
- const attachment = m.imageMessage || m.documentMessage;
1275
- const type = Object.keys(m)[0].replace('Message', '').toUpperCase();
1276
- const invoiceMessage = {
1277
- attachmentType: proto.Message.InvoiceMessage.AttachmentType[type === 'DOCUMENT' ? 'PDF' : 'IMAGE'],
1278
- note: msg.invoiceNote
1279
- };
1280
- if (attachment) {
1281
- const { directPath, fileEncSha256, fileSha256, jpegThumbnail = undefined, mediaKey, mediaKeyTimestamp, mimetype } = attachment;
1282
- Object.assign(invoiceMessage, {
1283
- attachmentDirectPath: directPath,
1284
- attachmentFileEncSha256: fileEncSha256,
1285
- attachmentFileSha256: fileSha256,
1286
- attachmentJpegThumbnail: jpegThumbnail,
1287
- attachmentMediaKey: mediaKey,
1288
- attachmentMediaKeyTimestamp: mediaKeyTimestamp,
1289
- attachmentMimetype: mimetype,
1290
- token: generateMessageIDV2()
1291
- });
1292
- }
1293
- else {
1294
- throw new Boom('Invalid media type for invoice message', { statusCode: 400 });
1295
- }
1296
- m = { invoiceMessage };
1297
- }
1298
- if (hasOptProp(msg, 'externalAdReply') && !!msg.externalAdReply) {
1299
- const messageType = Object.keys(m)[0];
1300
- const key = m[messageType];
1301
- const content = msg.externalAdReply;
1302
- if ('thumbnail' in content && !Buffer.isBuffer(content.thumbnail)) {
1303
- throw new Boom('Thumbnail must in buffer type', { statusCode: 400 });
1304
- }
1305
- if (!content.url || typeof content.url !== 'string') {
1306
- content.url = DONATE_URL;
1307
- }
1308
- const externalAdReply = {
1309
- ...content,
1310
- body: content.body,
1311
- mediaType: content.mediaType || 1,
1312
- mediaUrl: content.url,
1313
- renderLargerThumbnail: content.largeThumbnail,
1314
- sourceUrl: content.url,
1315
- thumbnail: content.thumbnail,
1316
- thumbnailUrl: content.url + '?update=' + Date.now(),
1317
- title: content.title || LIBRARY_NAME
1318
- };
1319
- delete externalAdReply.subTitle;
1320
- delete externalAdReply.largeThumbnail;
1321
- delete externalAdReply.url;
1322
- if (key && 'contextInfo' in key && !!key.contextInfo) {
1323
- key.contextInfo.externalAdReply = { ...key.contextInfo.externalAdReply, ...externalAdReply };
1324
- }
1325
- else if (key) {
1326
- key.contextInfo = { externalAdReply };
1327
- }
491
+ if (hasOptionalProperty(message, 'viewOnce') && !!message.viewOnce) {
492
+ m = { viewOnceMessage: { message: m } };
1328
493
  }
1329
- if ((hasOptProp(msg, 'mentions') && msg.mentions?.length) ||
1330
- (hasOptProp(msg, 'mentionAll') && msg.mentionAll)) {
494
+ if ((hasOptionalProperty(message, 'mentions') && message.mentions?.length) ||
495
+ (hasOptionalProperty(message, 'mentionAll') && message.mentionAll)) {
1331
496
  const messageType = Object.keys(m)[0];
1332
497
  const key = m[messageType];
1333
498
  if (key && 'contextInfo' in key) {
1334
499
  key.contextInfo = key.contextInfo || {};
1335
- if (msg.mentions?.length) {
1336
- key.contextInfo.mentionedJid = msg.mentions;
500
+ if (message.mentions?.length) {
501
+ key.contextInfo.mentionedJid = message.mentions;
1337
502
  }
1338
- if (msg.mentionAll) {
503
+ if (message.mentionAll) {
1339
504
  key.contextInfo.nonJidMentions = 1;
1340
505
  }
1341
506
  }
1342
507
  else if (key) {
1343
508
  key.contextInfo = {
1344
- mentionedJid: msg.mentions,
1345
- nonJidMentions: msg.mentionAll ? 1 : 0
509
+ mentionedJid: message.mentions,
510
+ nonJidMentions: message.mentionAll ? 1 : 0
1346
511
  };
1347
512
  }
1348
513
  }
1349
- if (hasOptProp(msg, 'contextInfo') && !!msg.contextInfo) {
1350
- const messageType = Object.keys(m)[0];
1351
- const key = m[messageType];
1352
- if (key && 'contextInfo' in key && !!key.contextInfo) {
1353
- key.contextInfo = { ...key.contextInfo, ...msg.contextInfo };
1354
- }
1355
- else if (key) {
1356
- key.contextInfo = msg.contextInfo;
1357
- }
1358
- }
1359
- if (hasOptProp(msg, 'groupStatus') && !!msg.groupStatus) {
1360
- const messageType = Object.keys(m)[0];
1361
- const key = m[messageType];
1362
- if (key && 'contextInfo' in key && !!key.contextInfo) {
1363
- key.contextInfo.isGroupStatus = msg.groupStatus;
1364
- }
1365
- else if (key) {
1366
- key.contextInfo = {
1367
- isGroupStatus: msg.groupStatus
1368
- };
1369
- }
1370
- m = { groupStatusMessageV2: { message: m } };
1371
- delete msg.groupStatus;
514
+ if (hasOptionalProperty(message, 'edit')) {
515
+ m = {
516
+ protocolMessage: {
517
+ key: message.edit,
518
+ editedMessage: m,
519
+ timestampMs: Date.now(),
520
+ type: WAProto.Message.ProtocolMessage.Type.MESSAGE_EDIT
521
+ }
522
+ };
1372
523
  }
1373
- if (hasOptProp(msg, 'spoiler') && !!msg.spoiler) {
524
+ if (hasOptionalProperty(message, 'contextInfo') && !!message.contextInfo) {
1374
525
  const messageType = Object.keys(m)[0];
1375
526
  const key = m[messageType];
1376
- if (key && 'contextInfo' in key && !!key.contextInfo) {
1377
- key.contextInfo.isSpoiler = msg.spoiler;
527
+ if ('contextInfo' in key && !!key.contextInfo) {
528
+ key.contextInfo = { ...key.contextInfo, ...message.contextInfo };
1378
529
  }
1379
530
  else if (key) {
1380
- key.contextInfo = {
1381
- isSpoiler: msg.spoiler
1382
- };
1383
- }
1384
- m = { spoilerMessage: { message: m } };
1385
- delete msg.spoiler;
1386
- }
1387
- else if (hasOptProp(msg, 'interactiveAsTemplate') && !!msg.interactiveAsTemplate) {
1388
- if (!m.interactiveMessage) {
1389
- throw new Boom('Invalid message type for template', { statusCode: 400 });
531
+ key.contextInfo = message.contextInfo;
1390
532
  }
1391
- m = {
1392
- templateMessage: {
1393
- interactiveMessageTemplate: m.interactiveMessage,
1394
- templateId: msg.id || 'template-' + Date.now()
1395
- }
1396
- };
1397
- delete msg.interactiveAsTemplate;
1398
- }
1399
- if (hasOptProp(msg, 'ephemeral') && !!msg.ephemeral) {
1400
- m = { ephemeralMessage: { message: m } };
1401
- delete msg.ephemeral;
1402
- }
1403
- if (hasOptProp(msg, 'isLottie') && !!msg.isLottie) {
1404
- m = { lottieStickerMessage: { message: m } };
1405
- }
1406
- else if (hasOptProp(msg, 'viewOnce') && !!msg.viewOnce) {
1407
- m = { viewOnceMessage: { message: m } };
1408
- }
1409
- else if (hasOptProp(msg, 'viewOnceV2') && !!msg.viewOnceV2) {
1410
- m = { viewOnceMessageV2: { message: m } };
1411
- delete msg.viewOnceV2;
1412
533
  }
1413
- else if (hasOptProp(msg, 'viewOnceV2Extension') && !!msg.viewOnceV2Extension) {
1414
- m = { viewOnceMessageV2Extension: { message: m } };
1415
- delete msg.viewOnceV2Extension;
1416
- }
1417
- if (hasOptProp(msg, 'edit')) {
1418
- m = {
1419
- protocolMessage: {
1420
- key: msg.edit,
1421
- editedMessage: m,
1422
- timestampMs: Date.now(),
1423
- type: WAProto.Message.ProtocolMessage.Type.MESSAGE_EDIT
534
+ if (hasOptionalProperty(message, 'albumParentKey') && !!message.albumParentKey) {
535
+ m.messageContextInfo = {
536
+ ...m.messageContextInfo,
537
+ messageAssociation: {
538
+ associationType: WAProto.MessageAssociation.AssociationType.MEDIA_ALBUM,
539
+ parentMessageKey: message.albumParentKey
1424
540
  }
1425
541
  };
1426
542
  }
@@ -1433,82 +549,79 @@ export const generateWAMessageContent = async (message, options) => {
1433
549
  return WAProto.Message.create(m);
1434
550
  };
1435
551
  export const generateWAMessageFromContent = (jid, message, options) => {
552
+ // set timestamp to now
553
+ // if not specified
1436
554
  if (!options.timestamp) {
1437
555
  options.timestamp = new Date();
1438
556
  }
1439
557
  const innerMessage = normalizeMessageContent(message);
1440
- const messageContextInfo = message.messageContextInfo;
1441
558
  const key = getContentType(innerMessage);
1442
559
  const timestamp = unixTimestampSeconds(options.timestamp);
1443
- const isNewsletter = isJidNewsletter(jid);
1444
560
  const { quoted, userJid } = options;
1445
- if (quoted && innerMessage) {
561
+ if (quoted && !isJidNewsletter(jid)) {
1446
562
  const participant = quoted.key.fromMe
1447
- ? userJid
563
+ ? userJid // TODO: Add support for LIDs
1448
564
  : quoted.participant || quoted.key.participant || quoted.key.remoteJid;
1449
565
  let quotedMsg = normalizeMessageContent(quoted.message);
1450
- if (quotedMsg) {
1451
- const msgType = getContentType(quotedMsg);
1452
- quotedMsg = proto.Message.create({ [msgType]: quotedMsg[msgType] });
1453
- const quotedContent = quotedMsg[msgType];
1454
- if (typeof quotedContent === 'object' && quotedContent && 'contextInfo' in quotedContent) {
1455
- delete quotedContent.contextInfo;
1456
- }
1457
- const innerMsgKey = innerMessage[key];
1458
- const contextInfo = (innerMsgKey && typeof innerMsgKey === 'object' && 'contextInfo' in innerMsgKey && innerMsgKey.contextInfo) || {};
1459
- contextInfo.participant = jidNormalizedUser(participant || '');
1460
- contextInfo.stanzaId = quoted.key.id;
1461
- contextInfo.quotedMessage = quotedMsg;
1462
- if (!isNewsletter && jid !== quoted.key.remoteJid) {
1463
- contextInfo.remoteJid = quoted.key.remoteJid;
1464
- }
1465
- if (contextInfo && innerMsgKey && typeof innerMsgKey === 'object') {
1466
- innerMsgKey.contextInfo = contextInfo;
1467
- }
1468
- }
1469
- }
1470
- if (innerMessage &&
1471
- !!options?.ephemeralExpiration &&
566
+ const msgType = getContentType(quotedMsg);
567
+ // strip any redundant properties
568
+ quotedMsg = proto.Message.create({ [msgType]: quotedMsg[msgType] });
569
+ const quotedContent = quotedMsg[msgType];
570
+ if (typeof quotedContent === 'object' && quotedContent && 'contextInfo' in quotedContent) {
571
+ delete quotedContent.contextInfo;
572
+ }
573
+ const contextInfo = ('contextInfo' in innerMessage[key] && innerMessage[key]?.contextInfo) || {};
574
+ contextInfo.participant = jidNormalizedUser(participant);
575
+ contextInfo.stanzaId = quoted.key.id;
576
+ contextInfo.quotedMessage = quotedMsg;
577
+ // if a participant is quoted, then it must be a group
578
+ // hence, remoteJid of group must also be entered
579
+ if (jid !== quoted.key.remoteJid) {
580
+ contextInfo.remoteJid = quoted.key.remoteJid;
581
+ }
582
+ if (contextInfo && innerMessage[key]) {
583
+ /* @ts-ignore */
584
+ innerMessage[key].contextInfo = contextInfo;
585
+ }
586
+ }
587
+ if (
588
+ // if we want to send a disappearing message
589
+ !!options?.ephemeralExpiration &&
590
+ // and it's not a protocol message -- delete, toggle disappear message
1472
591
  key !== 'protocolMessage' &&
592
+ // already not converted to disappearing message
1473
593
  key !== 'ephemeralMessage' &&
1474
- !isNewsletter) {
1475
- const innerMsgKey = innerMessage[key];
1476
- if (innerMsgKey && typeof innerMsgKey === 'object') {
1477
- innerMsgKey.contextInfo = {
1478
- ...(innerMsgKey.contextInfo || {}),
1479
- expiration: options.ephemeralExpiration || WA_DEFAULT_EPHEMERAL
1480
- };
1481
- }
1482
- }
1483
- if (messageContextInfo?.messageSecret && (isPnUser(jid) || isLidUser(jid))) {
1484
- messageContextInfo.deviceListMetadata = {
1485
- recipientKeyHash: randomBytes(10),
1486
- recipientTimestamp: unixTimestampSeconds()
594
+ // newsletters don't support ephemeral messages
595
+ !isJidNewsletter(jid)) {
596
+ /* @ts-ignore */
597
+ innerMessage[key].contextInfo = {
598
+ ...(innerMessage[key].contextInfo || {}),
599
+ expiration: options.ephemeralExpiration || WA_DEFAULT_EPHEMERAL
600
+ //ephemeralSettingTimestamp: options.ephemeralOptions.eph_setting_ts?.toString()
1487
601
  };
1488
- messageContextInfo.deviceListMetadataVersion = 2;
1489
602
  }
1490
- const finalMessage = WAProto.Message.create(message);
603
+ message = WAProto.Message.create(message);
1491
604
  const messageJSON = {
1492
605
  key: {
1493
606
  remoteJid: jid,
1494
607
  fromMe: true,
1495
608
  id: options?.messageId || generateMessageIDV2()
1496
609
  },
1497
- message: finalMessage,
610
+ message: message,
1498
611
  messageTimestamp: timestamp,
1499
612
  messageStubParameters: [],
1500
- participant: isJidGroup(jid) || isJidStatusBroadcast(jid) ? userJid : undefined,
613
+ participant: isJidGroup(jid) || isJidStatusBroadcast(jid) ? userJid : undefined, // TODO: Add support for LIDs
1501
614
  status: WAMessageStatus.PENDING
1502
615
  };
1503
616
  return WAProto.WebMessageInfo.fromObject(messageJSON);
1504
617
  };
1505
618
  export const generateWAMessage = async (jid, content, options) => {
619
+ // ensure msg ID is with every log
1506
620
  options.logger = options?.logger?.child({ msgId: options.messageId });
1507
- if (jid) {
1508
- options.jid = jid;
1509
- }
1510
- return generateWAMessageFromContent(jid, await generateWAMessageContent(content, options), options);
621
+ // Pass jid in the options to generateWAMessageContent
622
+ return generateWAMessageFromContent(jid, await generateWAMessageContent(content, { ...options, jid }), options);
1511
623
  };
624
+ /** Get the key to access the true type of content */
1512
625
  export const getContentType = (content) => {
1513
626
  if (content) {
1514
627
  const keys = Object.keys(content);
@@ -1516,10 +629,17 @@ export const getContentType = (content) => {
1516
629
  return key;
1517
630
  }
1518
631
  };
632
+ /**
633
+ * Normalizes ephemeral, view once messages to regular message content
634
+ * Eg. image messages in ephemeral messages, in view once messages etc.
635
+ * @param content
636
+ * @returns
637
+ */
1519
638
  export const normalizeMessageContent = (content) => {
1520
639
  if (!content) {
1521
640
  return undefined;
1522
641
  }
642
+ // set max iterations to prevent an infinite loop
1523
643
  for (let i = 0; i < 5; i++) {
1524
644
  const inner = getFutureProofMessage(content);
1525
645
  if (!inner) {
@@ -1527,37 +647,23 @@ export const normalizeMessageContent = (content) => {
1527
647
  }
1528
648
  content = inner.message;
1529
649
  }
1530
- return content || undefined;
650
+ return content;
1531
651
  function getFutureProofMessage(message) {
1532
- return (message?.associatedChildMessage ||
1533
- message?.botForwardedMessage ||
1534
- message?.botInvokeMessage ||
1535
- message?.botTaskMessage ||
652
+ return (message?.ephemeralMessage ||
653
+ message?.viewOnceMessage ||
1536
654
  message?.documentWithCaptionMessage ||
655
+ message?.viewOnceMessageV2 ||
656
+ message?.viewOnceMessageV2Extension ||
1537
657
  message?.editedMessage ||
1538
- message?.ephemeralMessage ||
1539
- message?.eventCoverImage ||
1540
- message?.groupMentionedMessage ||
1541
- message?.groupStatusMentionMessage ||
658
+ message?.associatedChildMessage ||
1542
659
  message?.groupStatusMessage ||
1543
- message?.groupStatusMessageV2 ||
1544
- message?.limitSharingMessage ||
1545
- message?.lottieStickerMessage ||
1546
- message?.newsletterAdminProfileMessage ||
1547
- message?.newsletterAdminProfileMessageV2 ||
1548
- message?.newsletterAdminProfileStatusMessage ||
1549
- message?.pollCreationMessageV4 ||
1550
- message?.pollCreationOptionImageMessage ||
1551
- message?.questionMessage ||
1552
- message?.questionReplyMessage ||
1553
- message?.spoilerMessage ||
1554
- message?.statusAddYours ||
1555
- message?.statusMentionMessage ||
1556
- message?.viewOnceMessage ||
1557
- message?.viewOnceMessageV2 ||
1558
- message?.viewOnceMessageV2Extension);
660
+ message?.groupStatusMessageV2);
1559
661
  }
1560
662
  };
663
+ /**
664
+ * Extract the true message content from a message
665
+ * Eg. extracts the inner message from a disappearing message/view once message
666
+ */
1561
667
  export const extractMessageContent = (content) => {
1562
668
  const extractFromTemplateMessage = (msg) => {
1563
669
  if (msg.imageMessage) {
@@ -1593,6 +699,9 @@ export const extractMessageContent = (content) => {
1593
699
  }
1594
700
  return content;
1595
701
  };
702
+ /**
703
+ * Returns the device predicted by message ID
704
+ */
1596
705
  export const getDevice = (id) => /^3A.{18}$/.test(id)
1597
706
  ? 'ios'
1598
707
  : /^3E.{20}$/.test(id)
@@ -1602,6 +711,7 @@ export const getDevice = (id) => /^3A.{18}$/.test(id)
1602
711
  : /^(3F|.{18}$)/.test(id)
1603
712
  ? 'desktop'
1604
713
  : 'unknown';
714
+ /** Upserts a receipt in the message */
1605
715
  export const updateMessageWithReceipt = (msg, receipt) => {
1606
716
  msg.userReceipt = msg.userReceipt || [];
1607
717
  const recp = msg.userReceipt.find(m => m.userJid === receipt.userJid);
@@ -1612,6 +722,7 @@ export const updateMessageWithReceipt = (msg, receipt) => {
1612
722
  msg.userReceipt.push(receipt);
1613
723
  }
1614
724
  };
725
+ /** Update the message with a new reaction */
1615
726
  export const updateMessageWithReaction = (msg, reaction) => {
1616
727
  const authorID = getKeyAuthor(reaction.key);
1617
728
  const reactions = (msg.reactions || []).filter(r => getKeyAuthor(r.key) !== authorID);
@@ -1619,20 +730,28 @@ export const updateMessageWithReaction = (msg, reaction) => {
1619
730
  reactions.push(reaction);
1620
731
  msg.reactions = reactions;
1621
732
  };
733
+ /** Update the message with a new poll update */
1622
734
  export const updateMessageWithPollUpdate = (msg, update) => {
1623
- const authorID = getKeyAuthor(update.pollUpdateMessageKey || update.pollCreationMessageKey);
1624
- const pollUpdates = (msg.pollUpdates || []).filter(r => getKeyAuthor(r.pollUpdateMessageKey || r.pollCreationMessageKey) !== authorID);
735
+ const authorID = getKeyAuthor(update.pollUpdateMessageKey);
736
+ const reactions = (msg.pollUpdates || []).filter(r => getKeyAuthor(r.pollUpdateMessageKey) !== authorID);
1625
737
  if (update.vote?.selectedOptions?.length) {
1626
- pollUpdates.push(update);
738
+ reactions.push(update);
1627
739
  }
1628
- msg.pollUpdates = pollUpdates;
740
+ msg.pollUpdates = reactions;
1629
741
  };
742
+ /** Update the message with a new event response */
1630
743
  export const updateMessageWithEventResponse = (msg, update) => {
1631
- const authorID = getKeyAuthor(update.eventResponseMessageKey || update.eventCreationMessageKey);
1632
- const responses = (msg.eventResponses || []).filter(r => getKeyAuthor(r.eventResponseMessageKey || r.eventCreationMessageKey) !== authorID);
744
+ const authorID = getKeyAuthor(update.eventResponseMessageKey);
745
+ const responses = (msg.eventResponses || []).filter(r => getKeyAuthor(r.eventResponseMessageKey) !== authorID);
1633
746
  responses.push(update);
1634
747
  msg.eventResponses = responses;
1635
748
  };
749
+ /**
750
+ * Aggregates all poll updates in a poll.
751
+ * @param msg the poll creation message
752
+ * @param meId your jid
753
+ * @returns A list of options & their voters
754
+ */
1636
755
  export function getAggregateVotesInPollMessage({ message, pollUpdates }, meId) {
1637
756
  const opts = message?.pollCreationMessage?.options ||
1638
757
  message?.pollCreationMessageV2?.options ||
@@ -1661,11 +780,17 @@ export function getAggregateVotesInPollMessage({ message, pollUpdates }, meId) {
1661
780
  };
1662
781
  data = voteHashMap[hash];
1663
782
  }
1664
- data.voters.push(getKeyAuthor((update.pollUpdateMessageKey || update.pollCreationMessageKey), meId));
783
+ voteHashMap[hash].voters.push(getKeyAuthor(update.pollUpdateMessageKey, meId));
1665
784
  }
1666
785
  }
1667
786
  return Object.values(voteHashMap);
1668
787
  }
788
+ /**
789
+ * Aggregates all event responses in an event message.
790
+ * @param msg the event creation message
791
+ * @param meId your jid
792
+ * @returns A list of response types & their responders
793
+ */
1669
794
  export function getAggregateResponsesInEventMessage({ eventResponses }, meId) {
1670
795
  const responseTypes = ['GOING', 'NOT_GOING', 'MAYBE'];
1671
796
  const responseMap = {};
@@ -1676,13 +801,14 @@ export function getAggregateResponsesInEventMessage({ eventResponses }, meId) {
1676
801
  };
1677
802
  }
1678
803
  for (const update of eventResponses || []) {
1679
- const responseType = update.response || update.eventResponse || update.eventResponseMessage?.response || 'UNKNOWN';
804
+ const responseType = update.eventResponse || 'UNKNOWN';
1680
805
  if (responseType !== 'UNKNOWN' && responseMap[responseType]) {
1681
- responseMap[responseType].responders.push(getKeyAuthor((update.eventResponseMessageKey || update.eventCreationMessageKey), meId));
806
+ responseMap[responseType].responders.push(getKeyAuthor(update.eventResponseMessageKey, meId));
1682
807
  }
1683
808
  }
1684
809
  return Object.values(responseMap);
1685
810
  }
811
+ /** Given a list of message keys, aggregates them by chat & sender. Useful for sending read receipts in bulk */
1686
812
  export const aggregateMessageKeysNotFromMe = (keys) => {
1687
813
  const keyMap = {};
1688
814
  for (const { remoteJid, id, participant, fromMe } of keys) {
@@ -1691,7 +817,7 @@ export const aggregateMessageKeysNotFromMe = (keys) => {
1691
817
  if (!keyMap[uqKey]) {
1692
818
  keyMap[uqKey] = {
1693
819
  jid: remoteJid,
1694
- participant: participant || undefined,
820
+ participant: participant,
1695
821
  messageIds: []
1696
822
  };
1697
823
  }
@@ -1701,12 +827,16 @@ export const aggregateMessageKeysNotFromMe = (keys) => {
1701
827
  return Object.values(keyMap);
1702
828
  };
1703
829
  const REUPLOAD_REQUIRED_STATUS = [410, 404];
830
+ /**
831
+ * Downloads the given message. Throws an error if it's not a media message
832
+ */
1704
833
  export const downloadMediaMessage = async (message, type, options, ctx) => {
1705
834
  const result = await downloadMsg().catch(async (error) => {
1706
835
  if (ctx &&
1707
- typeof error?.status === 'number' &&
836
+ typeof error?.status === 'number' && // treat errors with status as HTTP failures requiring reupload
1708
837
  REUPLOAD_REQUIRED_STATUS.includes(error.status)) {
1709
838
  ctx.logger.info({ key: message.key }, 'sending reupload media request...');
839
+ // request reupload
1710
840
  message = await ctx.reuploadRequest(message);
1711
841
  const result = await downloadMsg();
1712
842
  return result;
@@ -1747,28 +877,17 @@ export const downloadMediaMessage = async (message, type, options, ctx) => {
1747
877
  return stream;
1748
878
  }
1749
879
  };
880
+ /** Checks whether the given message is a media message; if it is returns the inner content */
1750
881
  export const assertMediaContent = (content) => {
1751
882
  content = extractMessageContent(content);
1752
883
  const mediaContent = content?.documentMessage ||
1753
884
  content?.imageMessage ||
1754
885
  content?.videoMessage ||
1755
886
  content?.audioMessage ||
1756
- content?.stickerMessage ||
1757
- content?.ptvMessage;
887
+ content?.stickerMessage;
1758
888
  if (!mediaContent) {
1759
889
  throw new Boom('given message is not a media message', { statusCode: 400, data: content });
1760
890
  }
1761
891
  return mediaContent;
1762
892
  };
1763
- export const shouldIncludeBizBinaryNode = (message) => {
1764
- const normalized = normalizeMessageContent(message);
1765
- const hasValidInteractive = normalized?.interactiveMessage &&
1766
- !normalized.interactiveMessage.carouselMessage &&
1767
- !normalized.interactiveMessage.collectionMessage &&
1768
- !normalized.interactiveMessage.shopStorefrontMessage;
1769
- return !!(normalized?.buttonsMessage ||
1770
- normalized?.interactiveMessage ||
1771
- normalized?.listMessage ||
1772
- hasValidInteractive);
1773
- };
1774
893
  //# sourceMappingURL=messages.js.map