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