@crysnovax/baileys 2.5.0 → 2.5.2

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.
@@ -2,7 +2,6 @@ import { Boom } from '@hapi/boom';
2
2
  import { randomBytes } from 'crypto';
3
3
  import { zip } from 'fflate';
4
4
  import { promises as fs } from 'fs';
5
- import {} from 'stream';
6
5
  import { proto } from '../../WAProto/index.js';
7
6
  import { CALL_AUDIO_PREFIX, CALL_VIDEO_PREFIX, DONATE_URL, LIBRARY_NAME, MEDIA_KEYS, URL_REGEX, WA_DEFAULT_EPHEMERAL } from '../Defaults/index.js';
8
7
  import { AssociationType, ButtonHeaderType, ButtonType, CarouselCardType, ListType, ProtocolType, WAMessageStatus, WAProto } from '../Types/index.js';
@@ -12,6 +11,7 @@ import { generateMessageIDV2, getKeyAuthor, unixTimestampSeconds } from './gener
12
11
  import { downloadContentFromMessage, encryptedStream, generateThumbnail, getAudioDuration, getAudioWaveform, getImageProcessingLibrary, getRawMediaUploadData, getStream, toBuffer } from './messages-media.js';
13
12
  import { prepareRichResponseMessage } from './rich-message-utils.js';
14
13
  import { shouldIncludeReportingToken } from './reporting-utils.js';
14
+ const CONCURRENCY_LIMIT = 15;
15
15
  const MIMETYPE_MAP = {
16
16
  image: 'image/jpeg',
17
17
  video: 'video/mp4',
@@ -36,7 +36,7 @@ const mediaAnnotation = [
36
36
  { x: 20.840980529785156, y: -47.80188751220703 }
37
37
  ],
38
38
  newsletter: {
39
- // Lia@Note 03-02-26 --- You can change jid, message id, and name via .env (⁠≧⁠▽⁠≦⁠)
39
+ // crysnovax@Note 03-02-26 --- You can change jid, message id, and name via .env (⁠≧⁠▽⁠≦⁠)
40
40
  newsletterJid: process.env.NEWSLETTER_ID ||
41
41
  '120363402922206865@newsletter',
42
42
  serverMessageId: process.env.NEWSLETTER_MESSAGE_ID ||
@@ -106,9 +106,9 @@ export const prepareWAMessageMedia = async (message, options) => {
106
106
  'url' in uploadData.media &&
107
107
  !!uploadData.media.url &&
108
108
  !!options.mediaCache &&
109
- mediaType + ':' + uploadData.media.url;
109
+ mediaType + ':' + uploadData.media.url.toString();
110
110
  if (mediaType === 'document' && !uploadData.fileName) {
111
- uploadData.fileName = LIBRARY_NAME;
111
+ uploadData.fileName = 'file';
112
112
  }
113
113
  if (!uploadData.mimetype) {
114
114
  uploadData.mimetype = MIMETYPE_MAP[mediaType];
@@ -123,13 +123,13 @@ export const prepareWAMessageMedia = async (message, options) => {
123
123
  return obj;
124
124
  }
125
125
  }
126
- const isNewsletter = isJidNewsletter(options.jid);
126
+ const isNewsletter = !!options.jid && isJidNewsletter(options.jid);
127
127
  const requiresDurationComputation = mediaType === 'audio' && typeof uploadData.seconds === 'undefined';
128
128
  const requiresThumbnailComputation = (mediaType === 'image' || mediaType === 'video') && typeof uploadData.jpegThumbnail === 'undefined';
129
129
  const requiresWaveformProcessing = mediaType === 'audio' && uploadData.ptt === true && typeof uploadData.waveform === 'undefined';
130
130
  const requiresAudioBackground = options.backgroundColor && mediaType === 'audio' && uploadData.ptt === true;
131
131
  const requiresOriginalForSomeProcessing = requiresDurationComputation || requiresThumbnailComputation || requiresWaveformProcessing;
132
- // Lia@Changes 06-02-26 --- Add few support for sending media to newsletter (⁠≧⁠▽⁠≦⁠)
132
+ // crysnovax@Changes 06-02-26 --- Add few support for sending media to newsletter (⁠≧⁠▽⁠≦⁠)
133
133
  if (isNewsletter) {
134
134
  logger?.info({ key: cacheableKey }, 'Preparing raw media for newsletter');
135
135
  const { filePath, fileSha256, fileLength } = await getRawMediaUploadData(uploadData.media, options.mediaTypeOverride || mediaType, logger);
@@ -285,36 +285,33 @@ export const prepareDisappearingMessageSettingContent = (ephemeralExpiration) =>
285
285
  ephemeralMessage: {
286
286
  message: {
287
287
  protocolMessage: {
288
- type: ProtocolType.EPHEMERAL_SETTING,
288
+ type: WAProto.Message.ProtocolMessage.Type.EPHEMERAL_SETTING,
289
289
  ephemeralExpiration
290
290
  }
291
291
  }
292
292
  }
293
293
  };
294
- return content;
294
+ return WAProto.Message.fromObject(content);
295
295
  };
296
- // Lia@Changes 31-01-26 --- Extract product message into a standalone function so it can also be reused as the header for interactive messages
296
+ // crysnovax@Changes 31-01-26 --- Extract product message into a standalone function so it can also be reused as the header for interactive messages
297
297
  const prepareProductMessage = async (message, options) => {
298
298
  if (!message.businessOwnerJid) {
299
299
  throw new Boom('"businessOwnerJid" is missing from the content', { statusCode: 400 });
300
300
  }
301
301
  const { imageMessage } = await prepareWAMessageMedia({ image: message.image || message.product.productImage }, options);
302
- // Lia@Changes 01-02-26 --- Add product message default value
303
- const content = {
304
- ...message,
305
- product: {
306
- currencyCode: 'IDR',
307
- priceAmount1000: 1000,
308
- title: LIBRARY_NAME,
309
- ...message.product,
310
- productImage: imageMessage
311
- }
302
+ // crysnovax@Changes 01-02-26 --- Add product message default value
303
+ const { image, ...content } = message;
304
+ content.product = {
305
+ currencyCode: 'IDR',
306
+ priceAmount1000: 1000,
307
+ title: LIBRARY_NAME,
308
+ ...message.product,
309
+ productImage: imageMessage
312
310
  };
313
- delete content.image;
314
311
  return content;
315
312
  };
316
313
  /**
317
- * Lia@Note 30-01-26
314
+ * crysnovax@Note 30-01-26
318
315
  * ---
319
316
  * Credits: Work on ensuring stickerPackMessage fields are valid by @jlucaso1 (https://github.com/jlucaso1).
320
317
  * based on https://github.com/WhiskeySockets/Baileys/pull/1561
@@ -330,7 +327,7 @@ const prepareStickerPackMessage = async (message, options) => {
330
327
  if (!cover) {
331
328
  throw new Boom('Sticker pack must contain a cover', { statusCode: 400 });
332
329
  }
333
- // Lia@Changes 01-02-26 --- Add caching for sticker pack (similiar to prepareWAMessageMedia)
330
+ // crysnovax@Changes 01-02-26 --- Add caching for sticker pack (similiar to prepareWAMessageMedia)
334
331
  const cacheableKey = Array.isArray(stickers) &&
335
332
  stickers.length &&
336
333
  !!options.mediaCache &&
@@ -529,18 +526,18 @@ const prepareStickerPackMessage = async (message, options) => {
529
526
  }
530
527
  return WAProto.Message.StickerPackMessage.fromObject(content);
531
528
  };
532
- // Lia@Changes 30-01-26 --- Add native flow button helper for interactive message
529
+ // crysnovax@Changes 30-01-26 --- Add native flow button helper for interactive message
533
530
  const prepareNativeFlowButtons = (message) => {
534
- const buttons = message.nativeFlow
531
+ const buttons = message.nativeFlow;
535
532
  const isButtonsFieldArray = Array.isArray(buttons);
536
533
  const correctedField = isButtonsFieldArray ? buttons : buttons.buttons;
537
534
  const messageParamsJson = {};
538
- // Lia@Changes 31-01-26 --- Add offer and options inside interactive message
535
+ // crysnovax@Changes 31-01-26 --- Add offer and options inside interactive message
539
536
  if (hasOptionalProperty(message, 'offerText') && !!message.offerText) {
540
537
  Object.assign(messageParamsJson, {
541
538
  limited_time_offer: {
542
539
  text: message.offerText || LIBRARY_NAME,
543
- url: message.offerUrl || DONATE_URL, // Lia@Note 02-02-26 --- Apologies if this feels cheeky, just a fallback
540
+ url: message.offerUrl || DONATE_URL, // crysnovax@Note 02-02-26 --- Apologies if this feels cheeky, just a fallback
544
541
  copy_code: message.offerCode,
545
542
  expiration_time: message.offerExpiration
546
543
  }
@@ -590,6 +587,7 @@ const prepareNativeFlowButtons = (message) => {
590
587
  display_text: buttonText || '🌐 Visit',
591
588
  url: button.url,
592
589
  merchant_url: button.url,
590
+ webview_interaction: button.useWebview,
593
591
  icon: buttonIcon
594
592
  })
595
593
  };
@@ -604,7 +602,7 @@ const prepareNativeFlowButtons = (message) => {
604
602
  })
605
603
  };
606
604
  }
607
- // Lia@Changes 12-03-26 --- Add "single_select" shortcut \⁠(⁠°⁠o⁠°⁠)⁠/
605
+ // crysnovax@Changes 12-03-26 --- Add "single_select" shortcut \⁠(⁠°⁠o⁠°⁠)⁠/
608
606
  else if (hasOptionalProperty(button, 'sections') && !!button.sections) {
609
607
  return {
610
608
  name: 'single_select',
@@ -627,7 +625,7 @@ const prepareNativeFlowButtons = (message) => {
627
625
  * @param options.forceForward will show the message as forwarded even if it is from you
628
626
  */
629
627
  export const generateForwardMessageContent = (message, forceForward) => {
630
- let content = message.message || message;
628
+ let content = message.message;
631
629
  if (!content) {
632
630
  throw new Boom('no content in message', { statusCode: 400 });
633
631
  }
@@ -675,36 +673,35 @@ export const hasOptionalProperty = (obj, key) => {
675
673
  key in obj &&
676
674
  obj[key] != null;
677
675
  };
678
- // Lia@Changes 06-02-26 --- Validate album message media to avoid bug 👀
676
+ // crysnovax@Changes 06-02-26 --- Validate album message media to avoid bug 👀
679
677
  export const hasValidAlbumMedia = (message) => {
680
- return message.imageMessage ||
681
- message.videoMessage;
678
+ return !!(message.imageMessage ||
679
+ message.videoMessage);
682
680
  };
683
681
  export const hasValidInteractiveHeader = (message) => {
684
- return message.imageMessage ||
682
+ return !!(message.imageMessage ||
685
683
  message.videoMessage ||
686
684
  message.documentMessage ||
687
685
  message.productMessage ||
688
- message.locationMessage;
686
+ message.locationMessage);
689
687
  };
690
- // Lia@Changes 30-01-26 --- Validate carousel cards header to avoid bug 👀
688
+ // crysnovax@Changes 30-01-26 --- Validate carousel cards header to avoid bug 👀
691
689
  export const hasValidCarouselHeader = (message) => {
692
- return message.imageMessage ||
690
+ return !!(message.imageMessage ||
693
691
  message.videoMessage ||
694
- message.productMessage;
692
+ message.productMessage);
695
693
  };
696
694
  export const generateWAMessageContent = async (message, options) => {
697
695
  var _a, _b;
698
696
  let m = {};
699
- // Lia@Changes 30-01-26 --- Add "raw" boolean to send raw messages directly via generateWAMessage()
697
+ // crysnovax@Changes 30-01-26 --- Add "raw" boolean to send raw messages directly via generateWAMessage()
700
698
  if (hasNonNullishProperty(message, 'raw')) {
701
699
  delete message.raw;
702
700
  return message;
703
701
  }
704
- // Lia@Changes 09-04-26 --- Add support for code block, latex, reels carousel, table with richResponseMessage
702
+ // crysnovax@Changes 09-04-26 --- Add support for code block and table with richResponseMessage
705
703
  else if (hasNonNullishProperty(message, 'code') ||
706
- hasNonNullishProperty(message, 'expressions') ||
707
- hasNonNullishProperty(message, 'items') ||
704
+ hasNonNullishProperty(message, 'links') ||
708
705
  hasNonNullishProperty(message, 'table') ||
709
706
  hasNonNullishProperty(message, 'richResponse')) {
710
707
  m = prepareRichResponseMessage(message);
@@ -720,7 +717,8 @@ export const generateWAMessageContent = async (message, options) => {
720
717
  extContent.jpegThumbnail = urlInfo.jpegThumbnail;
721
718
  extContent.description = urlInfo.description;
722
719
  extContent.title = urlInfo.title;
723
- extContent.previewType = 0;
720
+ extContent.previewType = urlInfo.previewType ?? 0;
721
+ extContent.linkPreviewMetadata = urlInfo.linkPreviewMetadata;
724
722
  const img = urlInfo.highQualityThumbnail;
725
723
  if (img) {
726
724
  extContent.thumbnailDirectPath = img.directPath;
@@ -732,6 +730,21 @@ export const generateWAMessageContent = async (message, options) => {
732
730
  extContent.thumbnailEncSha256 = img.fileEncSha256;
733
731
  }
734
732
  }
733
+ const faviconData = message.favicon;
734
+ if (faviconData && typeof options.upload === 'function') {
735
+ const { imageMessage } = await prepareWAMessageMedia({
736
+ image: faviconData
737
+ }, options);
738
+ extContent.faviconMMSMetadata = {
739
+ thumbnailDirectPath: imageMessage.directPath,
740
+ mediaKey: imageMessage.mediaKey,
741
+ mediaKeyTimestamp: imageMessage.mediaKeyTimestamp,
742
+ thumbnailWidth: 32,
743
+ thumbnailHeight: 32,
744
+ thumbnailSha256: imageMessage.fileSha256,
745
+ thumbnailEncSha256: imageMessage.fileEncSha256
746
+ };
747
+ }
735
748
  if (options.backgroundColor) {
736
749
  extContent.backgroundArgb = await assertColor(options.backgroundColor);
737
750
  }
@@ -746,25 +759,25 @@ export const generateWAMessageContent = async (message, options) => {
746
759
  throw new Boom('require atleast 1 contact', { statusCode: 400 });
747
760
  }
748
761
  if (contactLen === 1) {
749
- m.contactMessage = message.contacts.contacts[0];
762
+ m.contactMessage = WAProto.Message.ContactMessage.create(message.contacts.contacts[0]);
750
763
  }
751
764
  else {
752
- m.contactsArrayMessage = message.contacts;
765
+ m.contactsArrayMessage = WAProto.Message.ContactsArrayMessage.create(message.contacts);
753
766
  }
754
767
  }
755
768
  else if (hasNonNullishProperty(message, 'location')) {
756
- m.locationMessage = message.location;
769
+ m.locationMessage = WAProto.Message.LocationMessage.create(message.location);
757
770
  }
758
771
  else if (hasNonNullishProperty(message, 'react')) {
759
772
  if (!message.react.senderTimestampMs) {
760
773
  message.react.senderTimestampMs = Date.now();
761
774
  }
762
- m.reactionMessage = message.react;
775
+ m.reactionMessage = WAProto.Message.ReactionMessage.create(message.react);
763
776
  }
764
777
  else if (hasNonNullishProperty(message, 'delete')) {
765
778
  m.protocolMessage = {
766
779
  key: message.delete,
767
- type: ProtocolType.REVOKE
780
+ type: WAProto.Message.ProtocolMessage.Type.REVOKE
768
781
  };
769
782
  }
770
783
  else if (hasNonNullishProperty(message, 'forward')) {
@@ -894,27 +907,26 @@ export const generateWAMessageContent = async (message, options) => {
894
907
  statusCode: 400
895
908
  });
896
909
  }
897
- m.messageContextInfo = {
898
- // encKey
899
- messageSecret: message.poll.messageSecret || randomBytes(32)
900
- };
901
910
  const pollCreationMessage = {
902
911
  name: message.poll.name,
903
912
  selectableOptionsCount: message.poll.selectableCount,
904
- options: message.poll.values.map(optionName => ({ optionName }))
913
+ options: message.poll.values.map(optionName => ({ optionName })),
914
+ endTime: message.poll.endDate ? message.poll.endDate.getTime() : undefined,
915
+ hideParticipantName: message.poll.hideVoter ?? false,
916
+ allowAddOption: message.poll.canAddOption ?? false
905
917
  };
906
918
  if (message.poll.toAnnouncementGroup) {
907
919
  // poll v2 is for community announcement groups (single select and multiple)
908
920
  m.pollCreationMessageV2 = pollCreationMessage;
909
921
  }
910
922
  else {
911
- // Lia@Changes 08-02-26 --- Add quiz message support
912
- if (message.poll.pollType == 1) {
923
+ // crysnovax@Changes 08-02-26 --- Add quiz message support
924
+ if (message.poll.pollType === 1) {
913
925
  if (!message.poll.correctAnswer) {
914
926
  throw new Boom('No "correctAnswer" provided for quiz', { statusCode: 400 });
915
927
  }
916
928
  m.pollCreationMessageV5 = {
917
- // Lia@Note 08-02-26 --- quiz for newsletter only
929
+ // crysnovax@Note 08-02-26 --- quiz for newsletter only
918
930
  ...pollCreationMessage,
919
931
  correctAnswer: {
920
932
  optionName: message.poll.correctAnswer.toString()
@@ -932,8 +944,12 @@ export const generateWAMessageContent = async (message, options) => {
932
944
  m.pollCreationMessage = pollCreationMessage;
933
945
  }
934
946
  }
947
+ m.messageContextInfo = {
948
+ // encKey
949
+ messageSecret: message.poll.messageSecret || randomBytes(32)
950
+ };
935
951
  }
936
- // Lia@Changes 08-02-26 --- Add poll result snapshot message
952
+ // crysnovax@Changes 08-02-26 --- Add poll result snapshot message
937
953
  else if (hasNonNullishProperty(message, 'pollResult')) {
938
954
  const pollResultSnapshotMessage = {
939
955
  name: message.pollResult.name,
@@ -942,7 +958,7 @@ export const generateWAMessageContent = async (message, options) => {
942
958
  optionVoteCount: parseInt(vote.voteCount)
943
959
  }))
944
960
  };
945
- if (message.pollResult.pollType == 1) {
961
+ if (message.pollResult.pollType === 1) {
946
962
  pollResultSnapshotMessage.pollType = proto.Message.PollType.QUIZ;
947
963
  m.pollResultSnapshotMessageV3 = pollResultSnapshotMessage;
948
964
  }
@@ -951,7 +967,7 @@ export const generateWAMessageContent = async (message, options) => {
951
967
  m.pollResultSnapshotMessage = pollResultSnapshotMessage;
952
968
  }
953
969
  }
954
- // Lia@Changes 08-02-26 --- Add poll update message
970
+ // crysnovax@Changes 08-02-26 --- Add poll update message
955
971
  else if (hasNonNullishProperty(message, 'pollUpdate')) {
956
972
  if (!message.pollUpdate.key) {
957
973
  throw new Boom('Message key is required', { statusCode: 400 });
@@ -968,7 +984,7 @@ export const generateWAMessageContent = async (message, options) => {
968
984
  }
969
985
  else if (hasNonNullishProperty(message, 'sharePhoneNumber')) {
970
986
  m.protocolMessage = {
971
- type: ProtocolType.SHARE_PHONE_NUMBER
987
+ type: proto.Message.ProtocolMessage.Type.SHARE_PHONE_NUMBER
972
988
  };
973
989
  }
974
990
  else if (hasNonNullishProperty(message, 'requestPhoneNumber')) {
@@ -976,7 +992,7 @@ export const generateWAMessageContent = async (message, options) => {
976
992
  }
977
993
  else if (hasNonNullishProperty(message, 'limitSharing')) {
978
994
  m.protocolMessage = {
979
- type: ProtocolType.LIMIT_SHARING,
995
+ type: proto.Message.ProtocolMessage.Type.LIMIT_SHARING,
980
996
  limitSharing: {
981
997
  sharingLimited: message.limitSharing === true,
982
998
  trigger: 1,
@@ -985,14 +1001,14 @@ export const generateWAMessageContent = async (message, options) => {
985
1001
  }
986
1002
  };
987
1003
  }
988
- // Lia@Changes 01-02-26 --- Add payment invite message
1004
+ // crysnovax@Changes 01-02-26 --- Add payment invite message
989
1005
  else if (hasNonNullishProperty(message, 'paymentInviteServiceType')) {
990
1006
  m.paymentInviteMessage = {
991
1007
  expiryTimestamp: Date.now(),
992
1008
  serviceType: message.paymentInviteServiceType
993
1009
  };
994
1010
  }
995
- // Lia@Changes 01-02-26 --- Add order message
1011
+ // crysnovax@Changes 01-02-26 --- Add order message
996
1012
  else if (hasNonNullishProperty(message, 'orderText')) {
997
1013
  if (!Buffer.isBuffer(message.thumbnail)) {
998
1014
  throw new Boom('Must provide thumbnail buffer in order message', { statusCode: 400 });
@@ -1011,19 +1027,21 @@ export const generateWAMessageContent = async (message, options) => {
1011
1027
  };
1012
1028
  delete m.orderMessage.orderText;
1013
1029
  }
1014
- // Lia@Changes 31-01-26 --- Add support for album messages
1030
+ // crysnovax@Changes 31-01-26 --- Add support for album messages
1015
1031
  else if (hasNonNullishProperty(message, 'album')) {
1016
1032
  if (!Array.isArray(message.album)) {
1017
1033
  throw new Boom('Invalid album type. Expected an array.', { statusCode: 400 });
1018
1034
  }
1019
1035
  let videoCount = 0;
1020
1036
  for (let i = 0; i < message.album.length; i++) {
1021
- if (message.album[i].video) videoCount++;
1022
- };
1037
+ if (message.album[i].video)
1038
+ videoCount++;
1039
+ }
1023
1040
  let imageCount = 0;
1024
1041
  for (let i = 0; i < message.album.length; i++) {
1025
- if (message.album[i].image) imageCount++;
1026
- };
1042
+ if (message.album[i].image)
1043
+ imageCount++;
1044
+ }
1027
1045
  if ((videoCount + imageCount) < 2) {
1028
1046
  throw new Boom('Minimum provide 2 media to upload album message', { statusCode: 400 });
1029
1047
  }
@@ -1035,11 +1053,11 @@ export const generateWAMessageContent = async (message, options) => {
1035
1053
  else {
1036
1054
  m = await prepareWAMessageMedia(message, options);
1037
1055
  }
1038
- // Lia@Changes 30-01-26 --- Add interactive messages (buttonsMessage, listMessage, interactiveMessage, templateMessage, and carouselMessage)
1056
+ // crysnovax@Changes 30-01-26 --- Add interactive messages (buttonsMessage, listMessage, interactiveMessage, templateMessage, and carouselMessage)
1039
1057
  if (hasNonNullishProperty(message, 'buttons')) {
1040
1058
  const buttonsMessage = {
1041
1059
  buttons: message.buttons.map(button => {
1042
- // Lia@Changes 12-03-26 --- Add "single_select" shortcut!
1060
+ // crysnovax@Changes 12-03-26 --- Add "single_select" shortcut!
1043
1061
  const buttonText = button.text || button.buttonText;
1044
1062
  if (hasOptionalProperty(button, 'sections')) {
1045
1063
  return {
@@ -1097,7 +1115,7 @@ export const generateWAMessageContent = async (message, options) => {
1097
1115
  };
1098
1116
  m = { listMessage };
1099
1117
  }
1100
- // Lia@Note 03-02-26 --- This message type is shown on WhatsApp Web/Desktop and iOS (I guess 。⁠◕⁠‿⁠◕⁠。). On Android, it only appears in newsletter (so far ಥ⁠‿⁠ಥ)
1118
+ // crysnovax@Note 03-02-26 --- This message type is shown on WhatsApp Web/Desktop and iOS (I guess 。⁠◕⁠‿⁠◕⁠。). On Android, it only appears in newsletter (so far ಥ⁠‿⁠ಥ)
1101
1119
  else if (hasNonNullishProperty(message, 'templateButtons')) {
1102
1120
  const hydratedTemplate = {
1103
1121
  hydratedButtons: message.templateButtons.map((button, i) => {
@@ -1146,7 +1164,7 @@ export const generateWAMessageContent = async (message, options) => {
1146
1164
  if (hasOptionalProperty(message, 'footer')) {
1147
1165
  hydratedTemplate.hydratedFooterText = message.footer;
1148
1166
  }
1149
- hydratedTemplate.templateId = message.id || 'template-' + Date.now(); // Lia@Note 04-02-26 --- Minimal templateId to satisfy WhatsApp (⁠ ⁠ꈍ⁠ᴗ⁠ꈍ⁠)
1167
+ hydratedTemplate.templateId = message.id || 'template-' + Date.now(); // crysnovax@Note 04-02-26 --- Minimal templateId to satisfy WhatsApp (⁠ ⁠ꈍ⁠ᴗ⁠ꈍ⁠)
1150
1168
  m = {
1151
1169
  templateMessage: {
1152
1170
  hydratedFourRowTemplate: hydratedTemplate,
@@ -1218,9 +1236,9 @@ export const generateWAMessageContent = async (message, options) => {
1218
1236
  else {
1219
1237
  carouselHeader = await prepareWAMessageMedia(card, options).catch(() => ({ }));
1220
1238
  }
1221
- const isValidHeader = hasValidCarouselHeader(carouselHeader)
1239
+ const isValidHeader = hasValidCarouselHeader(carouselHeader);
1222
1240
  if (!isValidHeader) {
1223
- throw new Boom('Invalid media type for carousel card', { statusCode: 400 });
1241
+ throw new Boom('Invalid media type for carousel card', { statusCode: 400 });
1224
1242
  }
1225
1243
  const carouselCard = {
1226
1244
  nativeFlowMessage: prepareNativeFlowButtons(card.nativeFlow ? card : [])
@@ -1243,11 +1261,11 @@ export const generateWAMessageContent = async (message, options) => {
1243
1261
  Object.assign(carouselCard.header, carouselHeader);
1244
1262
  }
1245
1263
  if (hasOptionalProperty(card, 'audioFooter')) {
1246
- const parseFooter = await prepareWAMessageMedia({
1264
+ const { audioMessage } = await prepareWAMessageMedia({
1247
1265
  audio: card.audioFooter
1248
1266
  }, options);
1249
1267
  carouselCard.footer = {
1250
- audioMessage: parseFooter.audioMessage,
1268
+ audioMessage,
1251
1269
  hasMediaAttachment: true
1252
1270
  };
1253
1271
  }
@@ -1268,7 +1286,7 @@ export const generateWAMessageContent = async (message, options) => {
1268
1286
  }
1269
1287
  m = { interactiveMessage };
1270
1288
  }
1271
- // Lia@Changes 01-02-26 --- Add request payment message
1289
+ // crysnovax@Changes 01-02-26 --- Add request payment message
1272
1290
  else if (hasNonNullishProperty(message, 'requestPaymentFrom')) {
1273
1291
  const requestPaymentMessage = {
1274
1292
  amount: {
@@ -1292,7 +1310,7 @@ export const generateWAMessageContent = async (message, options) => {
1292
1310
  }
1293
1311
  m = { requestPaymentMessage };
1294
1312
  }
1295
- // Lia@Changes 01-02-26 --- Add invoice message
1313
+ // crysnovax@Changes 01-02-26 --- Add invoice message
1296
1314
  else if (hasNonNullishProperty(message, 'invoiceNote')) {
1297
1315
  const attachment = m.imageMessage || m.documentMessage;
1298
1316
  const type = Object.keys(m)[0].replace('Message', '').toUpperCase();
@@ -1318,7 +1336,7 @@ export const generateWAMessageContent = async (message, options) => {
1318
1336
  }
1319
1337
  m = { invoiceMessage };
1320
1338
  }
1321
- // Lia@Changes 31-01-26 --- Add direct externalAdReply access (no need to create contextInfo first)
1339
+ // crysnovax@Changes 31-01-26 --- Add direct externalAdReply access (no need to create contextInfo first)
1322
1340
  if (hasOptionalProperty(message, 'externalAdReply') && !!message.externalAdReply) {
1323
1341
  const messageType = Object.keys(m)[0];
1324
1342
  const key = m[messageType];
@@ -1327,7 +1345,7 @@ export const generateWAMessageContent = async (message, options) => {
1327
1345
  throw new Boom('Thumbnail must in buffer type', { statusCode: 400 });
1328
1346
  }
1329
1347
  if (!content.url || typeof content.url !== 'string') {
1330
- content.url = DONATE_URL; // Lia@Note 02-02-26 --- Apologies if this feels cheeky, just a fallback
1348
+ content.url = DONATE_URL; // crysnovax@Note 02-02-26 --- Apologies if this feels cheeky, just a fallback
1331
1349
  }
1332
1350
  const externalAdReply = {
1333
1351
  ...content,
@@ -1335,9 +1353,9 @@ export const generateWAMessageContent = async (message, options) => {
1335
1353
  mediaType: content.mediaType || 1,
1336
1354
  mediaUrl: content.url,
1337
1355
  renderLargerThumbnail: content.largeThumbnail,
1338
- sourceUrl: content.url + '?update=' + Date.now(),
1356
+ sourceUrl: content.url,
1339
1357
  thumbnail: content.thumbnail,
1340
- thumbnailUrl: content.url,
1358
+ thumbnailUrl: content.url + '?update=' + Date.now(),
1341
1359
  title: content.title || LIBRARY_NAME
1342
1360
  };
1343
1361
  delete externalAdReply.subTitle;
@@ -1354,18 +1372,21 @@ export const generateWAMessageContent = async (message, options) => {
1354
1372
  (hasOptionalProperty(message, 'mentionAll') && message.mentionAll)) {
1355
1373
  const messageType = Object.keys(m)[0];
1356
1374
  const key = m[messageType];
1357
- if ('contextInfo' in key && !!key.contextInfo) {
1358
- key.contextInfo.mentionedJid = message.mentions || [];
1375
+ if (key && 'contextInfo' in key) {
1376
+ key.contextInfo = key.contextInfo || {};
1377
+ if (message.mentions?.length) {
1378
+ key.contextInfo.mentionedJid = message.mentions;
1379
+ }
1380
+ if (message.mentionAll) {
1381
+ key.contextInfo.nonJidMentions = 1;
1382
+ }
1359
1383
  }
1360
1384
  else if (key) {
1361
1385
  key.contextInfo = {
1362
- mentionedJid: message.mentions || []
1386
+ mentionedJid: message.mentions,
1387
+ nonJidMentions: message.mentionAll ? 1 : 0
1363
1388
  };
1364
1389
  }
1365
- if (message.mentionAll) {
1366
- key.contextInfo.mentionedJid = [];
1367
- key.contextInfo.nonJidMentions = 1;
1368
- }
1369
1390
  }
1370
1391
  if (hasOptionalProperty(message, 'contextInfo') && !!message.contextInfo) {
1371
1392
  const messageType = Object.keys(m)[0];
@@ -1377,7 +1398,7 @@ export const generateWAMessageContent = async (message, options) => {
1377
1398
  key.contextInfo = message.contextInfo;
1378
1399
  }
1379
1400
  }
1380
- // Lia@Changes 31-01-26 --- Add "groupStatus" boolean to set contextInfo.isGroupStatus and wrap message into groupStatusMessageV2
1401
+ // crysnovax@Changes 31-01-26 --- Add "groupStatus" boolean to set contextInfo.isGroupStatus and wrap message into groupStatusMessageV2
1381
1402
  if (hasOptionalProperty(message, 'groupStatus') && !!message.groupStatus) {
1382
1403
  const messageType = Object.keys(m)[0];
1383
1404
  const key = m[messageType];
@@ -1392,20 +1413,35 @@ export const generateWAMessageContent = async (message, options) => {
1392
1413
  m = { groupStatusMessageV2: { message: m } };
1393
1414
  delete message.groupStatus;
1394
1415
  }
1395
- // Lia@Changes 02-02-26 --- Add "interactiveAsTemplate" boolean to wrap interactiveMessage into templateMessage
1416
+ // crysnovax@Changes 06-05-26 --- Add "spoiler" boolean to set contextInfo.isSpoiler and wrap message into spoilerMessage
1417
+ if (hasOptionalProperty(message, 'spoiler') && !!message.spoiler) {
1418
+ const messageType = Object.keys(m)[0];
1419
+ const key = m[messageType];
1420
+ if ('contextInfo' in key && !!key.contextInfo) {
1421
+ key.contextInfo.isSpoiler = message.spoiler;
1422
+ }
1423
+ else if (key) {
1424
+ key.contextInfo = {
1425
+ isSpoiler: message.spoiler
1426
+ };
1427
+ }
1428
+ m = { spoilerMessage: { message: m } };
1429
+ delete message.spoiler;
1430
+ }
1431
+ // crysnovax@Changes 02-02-26 --- Add "interactiveAsTemplate" boolean to wrap interactiveMessage into templateMessage
1396
1432
  else if (hasOptionalProperty(message, 'interactiveAsTemplate') && !!message.interactiveAsTemplate) {
1397
1433
  if (!m.interactiveMessage) {
1398
- throw new Boom('Invalid message type for template', { statusCode: 400 }); // Lia@Note 02-02-26 --- To avoid bug 👀
1434
+ throw new Boom('Invalid message type for template', { statusCode: 400 }); // crysnovax@Note 02-02-26 --- To avoid bug 👀
1399
1435
  }
1400
1436
  m = {
1401
1437
  templateMessage: {
1402
1438
  interactiveMessageTemplate: m.interactiveMessage,
1403
- templateId: message.id || 'template-' + Date.now() // Lia@Note 04-02-26 --- Minimal templateId to satisfy WhatsApp (⁠ ⁠ꈍ⁠ᴗ⁠ꈍ⁠)
1439
+ templateId: message.id || 'template-' + Date.now() // crysnovax@Note 04-02-26 --- Minimal templateId to satisfy WhatsApp (⁠ ⁠ꈍ⁠ᴗ⁠ꈍ⁠)
1404
1440
  }
1405
1441
  };
1406
1442
  delete message.interactiveAsTemplate;
1407
1443
  }
1408
- // Lia@Changes 30-01-26 --- Add "ephemeral" boolean to wrap message into ephemeralMessage like "viewOnce"
1444
+ // crysnovax@Changes 30-01-26 --- Add "ephemeral" boolean to wrap message into ephemeralMessage like "viewOnce"
1409
1445
  if (hasOptionalProperty(message, 'ephemeral') && !!message.ephemeral) {
1410
1446
  m = { ephemeralMessage: { message: m } };
1411
1447
  delete message.ephemeral;
@@ -1413,12 +1449,12 @@ export const generateWAMessageContent = async (message, options) => {
1413
1449
  else if (hasOptionalProperty(message, 'viewOnce') && !!message.viewOnce) {
1414
1450
  m = { viewOnceMessage: { message: m } };
1415
1451
  }
1416
- // Lia@Changes 03-02-26 --- Add "viewOnceV2" boolean to wrap message into viewOnceMessageV2 like "viewOnce"
1452
+ // crysnovax@Changes 03-02-26 --- Add "viewOnceV2" boolean to wrap message into viewOnceMessageV2 like "viewOnce"
1417
1453
  else if (hasOptionalProperty(message, 'viewOnceV2') && !!message.viewOnceV2) {
1418
1454
  m = { viewOnceMessageV2: { message: m } };
1419
1455
  delete message.viewOnceV2;
1420
1456
  }
1421
- // Lia@Changes 03-02-26 --- Add "viewOnceV2Extension" boolean to wrap message into viewOnceMessageV2Extension like "viewOnce"
1457
+ // crysnovax@Changes 03-02-26 --- Add "viewOnceV2Extension" boolean to wrap message into viewOnceMessageV2Extension like "viewOnce"
1422
1458
  else if (hasOptionalProperty(message, 'viewOnceV2Extension') && !!message.viewOnceV2Extension) {
1423
1459
  m = { viewOnceMessageV2Extension: { message: m } };
1424
1460
  delete message.viewOnceV2Extension;
@@ -1429,7 +1465,7 @@ export const generateWAMessageContent = async (message, options) => {
1429
1465
  key: message.edit,
1430
1466
  editedMessage: m,
1431
1467
  timestampMs: Date.now(),
1432
- type: ProtocolType.MESSAGE_EDIT
1468
+ type: WAProto.Message.ProtocolMessage.Type.MESSAGE_EDIT
1433
1469
  }
1434
1470
  }
1435
1471
  }
@@ -1439,13 +1475,13 @@ export const generateWAMessageContent = async (message, options) => {
1439
1475
  m.messageContextInfo.messageSecret = randomBytes(32);
1440
1476
  }
1441
1477
  }
1442
- return proto.Message.create(m);
1478
+ return WAProto.Message.create(m);
1443
1479
  };
1444
1480
  export const generateWAMessageFromContent = (jid, message, options) => {
1445
1481
  // set timestamp to now
1446
1482
  // if not specified
1447
1483
  if (!options.timestamp) {
1448
- options.timestamp = Date.now();
1484
+ options.timestamp = new Date();
1449
1485
  }
1450
1486
  const messageContextInfo = message.messageContextInfo
1451
1487
  const innerMessage = normalizeMessageContent(message);
@@ -1495,7 +1531,7 @@ export const generateWAMessageFromContent = (jid, message, options) => {
1495
1531
  //ephemeralSettingTimestamp: options.ephemeralOptions.eph_setting_ts?.toString()
1496
1532
  };
1497
1533
  }
1498
- // Lia@Changes 30-01-26 --- Add deviceListMetadata inside messageContextInfo for private chat
1534
+ // crysnovax@Changes 30-01-26 --- Add deviceListMetadata inside messageContextInfo for private chat
1499
1535
  if (messageContextInfo?.messageSecret && (isPnUser(jid) || isLidUser(jid))) {
1500
1536
  messageContextInfo.deviceListMetadata = {
1501
1537
  recipientKeyHash: randomBytes(10),
@@ -1503,7 +1539,7 @@ export const generateWAMessageFromContent = (jid, message, options) => {
1503
1539
  };
1504
1540
  messageContextInfo.deviceListMetadataVersion = 2
1505
1541
  }
1506
- message = proto.Message.create(message);
1542
+ message = WAProto.Message.create(message);
1507
1543
  const messageJSON = {
1508
1544
  key: {
1509
1545
  remoteJid: jid,
@@ -1518,11 +1554,11 @@ export const generateWAMessageFromContent = (jid, message, options) => {
1518
1554
  };
1519
1555
  return WAProto.WebMessageInfo.fromObject(messageJSON);
1520
1556
  };
1521
- export const generateWAMessage = async (jid, content, options = {}) => {
1557
+ export const generateWAMessage = async (jid, content, options) => {
1522
1558
  // ensure msg ID is with every log
1523
1559
  options.logger = options?.logger?.child({ msgId: options.messageId });
1524
1560
  // Pass jid in the options to generateWAMessageContent
1525
- if (jid && typeof options === 'object') {
1561
+ if (jid) {
1526
1562
  options.jid = jid;
1527
1563
  }
1528
1564
  return generateWAMessageFromContent(jid, await generateWAMessageContent(content, options), options);
@@ -1554,7 +1590,7 @@ export const normalizeMessageContent = (content) => {
1554
1590
  content = inner.message;
1555
1591
  }
1556
1592
  return content;
1557
- // Lia@Changes 03-02-26 --- Add all futureProofMessage into getFutureProofMessage()
1593
+ // crysnovax@Changes 03-02-26 --- Add all futureProofMessage into getFutureProofMessage()
1558
1594
  function getFutureProofMessage(message) {
1559
1595
  return (
1560
1596
  message?.associatedChildMessage ||
@@ -1571,10 +1607,14 @@ export const normalizeMessageContent = (content) => {
1571
1607
  message?.groupStatusMessageV2 ||
1572
1608
  message?.limitSharingMessage ||
1573
1609
  message?.lottieStickerMessage ||
1610
+ message?.newsletterAdminProfileMessage ||
1611
+ message?.newsletterAdminProfileMessageV2 ||
1612
+ message?.newsletterAdminProfileStatusMessage ||
1574
1613
  message?.pollCreationMessageV4 ||
1575
1614
  message?.pollCreationOptionImageMessage ||
1576
1615
  message?.questionMessage ||
1577
1616
  message?.questionReplyMessage ||
1617
+ message?.spoilerMessage ||
1578
1618
  message?.statusAddYours ||
1579
1619
  message?.statusMentionMessage ||
1580
1620
  message?.viewOnceMessage ||
@@ -1872,20 +1912,14 @@ const isWebPBuffer = (buffer) => {
1872
1912
  );
1873
1913
  };
1874
1914
  /**
1875
- * Lia@Changes 30-01-26
1915
+ * crysnovax@Changes 30-01-26
1876
1916
  * ---
1877
1917
  * Determines whether a message should include a Biz Binary Node.
1878
1918
  * A Biz Binary Node is added only for interactive messages
1879
1919
  * such as buttons or other supported interactive types.
1880
1920
  */
1881
- export const shouldIncludeBizBinaryNode = (message) => {
1882
- const hasValidInteractive =
1883
- message.interactiveMessage &&
1884
- !message.interactiveMessage.carouselMessage &&
1885
- !message.interactiveMessage.collectionMessage &&
1886
- !message.interactiveMessage.shopStorefrontMessage;
1887
- return (message.buttonsMessage ||
1888
- message.interactiveMessage ||
1889
- message.listMessage ||
1890
- hasValidInteractive);
1891
- };
1921
+ export const shouldIncludeBizBinaryNode = (message) => !!(message.buttonsMessage ||
1922
+ message.listMessage ||
1923
+ message.templateMessage ||
1924
+ (message.interactiveMessage &&
1925
+ message.interactiveMessage.nativeFlowMessage));