@crysnovax/baileys 2.5.6 → 2.6.1

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,16 +2,16 @@ 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';
9
- import { isPnUser, isLidUser, isJidGroup, isJidNewsletter, isJidStatusBroadcast, jidNormalizedUser } from '../WABinary/index.js';
8
+ import { isLidUser, isPnUser, isJidGroup, isJidNewsletter, isJidStatusBroadcast, jidNormalizedUser } from '../WABinary/index.js';
10
9
  import { sha256 } from './crypto.js';
11
10
  import { generateMessageIDV2, getKeyAuthor, unixTimestampSeconds } from './generics.js';
12
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',
@@ -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,55 +123,19 @@ export const prepareWAMessageMedia = async (message, options) => {
123
123
  return obj;
124
124
  }
125
125
  }
126
- const isNewsletter = isJidNewsletter(options.jid);
127
- const requiresDurationComputation = mediaType === 'audio' && typeof uploadData.seconds === 'undefined';
128
- const requiresThumbnailComputation = (mediaType === 'image' || mediaType === 'video') && typeof uploadData.jpegThumbnail === 'undefined';
129
- const requiresWaveformProcessing = mediaType === 'audio' && uploadData.ptt === true && typeof uploadData.waveform === 'undefined';
130
- const requiresAudioBackground = options.backgroundColor && mediaType === 'audio' && uploadData.ptt === true;
131
- const requiresOriginalForSomeProcessing = requiresDurationComputation || requiresThumbnailComputation || requiresWaveformProcessing;
132
- // Lia@Changes 06-02-26 --- Add few support for sending media to newsletter (⁠≧⁠▽⁠≦⁠)
126
+ const isNewsletter = !!options.jid && isJidNewsletter(options.jid);
133
127
  if (isNewsletter) {
134
128
  logger?.info({ key: cacheableKey }, 'Preparing raw media for newsletter');
135
129
  const { filePath, fileSha256, fileLength } = await getRawMediaUploadData(uploadData.media, options.mediaTypeOverride || mediaType, logger);
136
130
  const fileSha256B64 = fileSha256.toString('base64');
137
- const [{ mediaUrl, directPath, thumbnailDirectPath, thumbnailSha256 }] = await Promise.all([
138
- (async () => {
139
- const result = options.upload(filePath, {
140
- fileEncSha256B64: fileSha256B64,
141
- mediaType,
142
- timeoutMs: options.mediaUploadTimeoutMs,
143
- newsletter: isNewsletter
144
- });
145
- logger?.debug({ mediaType, cacheableKey }, 'uploaded media');
146
- return result;
147
- })(),
148
- (async () => {
149
- try {
150
- if (requiresThumbnailComputation) {
151
- const { thumbnail } = await generateThumbnail(filePath, mediaType, options);
152
- uploadData.jpegThumbnail = thumbnail;
153
- logger?.debug('generated thumbnail');
154
- }
155
- if (requiresDurationComputation) {
156
- uploadData.seconds = await getAudioDuration(filePath);
157
- logger?.debug('computed audio duration');
158
- }
159
- }
160
- catch (error) {
161
- logger?.warn({ trace: error.stack }, 'failed to obtain extra info');
162
- }
163
- })()
164
- ]).finally(async () => {
165
- try {
166
- await fs.unlink(filePath);
167
- logger?.debug('removed tmp files');
168
- }
169
- catch (error) {
170
- logger?.warn('failed to remove tmp file');
171
- }
131
+ const { mediaUrl, directPath, thumbnailDirectPath, thumbnailSha256 } = await options.upload(filePath, {
132
+ fileEncSha256B64: fileSha256B64,
133
+ mediaType: mediaType,
134
+ timeoutMs: options.mediaUploadTimeoutMs,
135
+ newsletter: isNewsletter
172
136
  });
173
- delete uploadData.media;
174
- const obj = proto.Message.create({
137
+ await fs.unlink(filePath);
138
+ const obj = WAProto.Message.fromObject({
175
139
  // todo: add more support here
176
140
  [`${mediaType}Message`]: MessageTypeProto[mediaType].fromObject({
177
141
  url: mediaUrl,
@@ -180,7 +144,8 @@ export const prepareWAMessageMedia = async (message, options) => {
180
144
  fileLength,
181
145
  thumbnailDirectPath,
182
146
  thumbnailSha256,
183
- ...uploadData
147
+ ...uploadData,
148
+ media: undefined
184
149
  })
185
150
  });
186
151
  if (uploadData.ptv) {
@@ -196,6 +161,11 @@ export const prepareWAMessageMedia = async (message, options) => {
196
161
  }
197
162
  return obj;
198
163
  }
164
+ const requiresDurationComputation = mediaType === 'audio' && typeof uploadData.seconds === 'undefined';
165
+ const requiresThumbnailComputation = (mediaType === 'image' || mediaType === 'video') && typeof uploadData['jpegThumbnail'] === 'undefined';
166
+ const requiresWaveformProcessing = mediaType === 'audio' && uploadData.ptt === true && typeof uploadData.waveform === 'undefined';
167
+ const requiresAudioBackground = options.backgroundColor && mediaType === 'audio' && uploadData.ptt === true;
168
+ const requiresOriginalForSomeProcessing = requiresDurationComputation || requiresThumbnailComputation;
199
169
  const { mediaKey, encFilePath, originalFilePath, fileEncSha256, fileSha256, fileLength } = await encryptedStream(uploadData.media, options.mediaTypeOverride || mediaType, {
200
170
  logger,
201
171
  saveOriginalFileIfRequired: requiresOriginalForSomeProcessing,
@@ -253,8 +223,7 @@ export const prepareWAMessageMedia = async (message, options) => {
253
223
  logger?.warn('failed to remove tmp file');
254
224
  }
255
225
  });
256
- delete uploadData.media;
257
- const obj = proto.Message.create({
226
+ const obj = WAProto.Message.fromObject({
258
227
  [`${mediaType}Message`]: MessageTypeProto[mediaType].fromObject({
259
228
  url: mediaUrl,
260
229
  directPath,
@@ -263,36 +232,20 @@ export const prepareWAMessageMedia = async (message, options) => {
263
232
  fileSha256,
264
233
  fileLength,
265
234
  mediaKeyTimestamp: unixTimestampSeconds(),
266
- ...uploadData
235
+ ...uploadData,
236
+ media: undefined
267
237
  })
268
238
  });
269
239
  if (uploadData.ptv) {
270
240
  obj.ptvMessage = obj.videoMessage;
271
241
  delete obj.videoMessage;
272
242
  }
273
- if (obj.stickerMessage) {
274
- obj.stickerMessage.stickerSentTs = Date.now();
275
- }
276
243
  if (cacheableKey) {
277
244
  logger?.debug({ cacheableKey }, 'set cache');
278
245
  await options.mediaCache.set(cacheableKey, WAProto.Message.encode(obj).finish());
279
246
  }
280
247
  return obj;
281
248
  };
282
- export const prepareDisappearingMessageSettingContent = (ephemeralExpiration) => {
283
- ephemeralExpiration = ephemeralExpiration || 0;
284
- const content = {
285
- ephemeralMessage: {
286
- message: {
287
- protocolMessage: {
288
- type: ProtocolType.EPHEMERAL_SETTING,
289
- ephemeralExpiration
290
- }
291
- }
292
- }
293
- };
294
- return content;
295
- };
296
249
  // Lia@Changes 31-01-26 --- Extract product message into a standalone function so it can also be reused as the header for interactive messages
297
250
  const prepareProductMessage = async (message, options) => {
298
251
  if (!message.businessOwnerJid) {
@@ -300,17 +253,14 @@ const prepareProductMessage = async (message, options) => {
300
253
  }
301
254
  const { imageMessage } = await prepareWAMessageMedia({ image: message.image || message.product.productImage }, options);
302
255
  // 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
- }
256
+ const { image, ...content } = message;
257
+ content.product = {
258
+ currencyCode: 'IDR',
259
+ priceAmount1000: 1000,
260
+ title: LIBRARY_NAME,
261
+ ...message.product,
262
+ productImage: imageMessage
312
263
  };
313
- delete content.image;
314
264
  return content;
315
265
  };
316
266
  /**
@@ -319,6 +269,7 @@ const prepareProductMessage = async (message, options) => {
319
269
  * Credits: Work on ensuring stickerPackMessage fields are valid by @jlucaso1 (https://github.com/jlucaso1).
320
270
  * based on https://github.com/WhiskeySockets/Baileys/pull/1561
321
271
  */
272
+ // Lia@Changes 21-04-26 --- Enhanced prepareStickerPackMessage
322
273
  const prepareStickerPackMessage = async (message, options) => {
323
274
  const { cover, stickers = [], name = '📦 Sticker Pack', publisher = 'Website: web.crysnovax.link', description = '🏷️ crysnovax/bailey' } = message;
324
275
  if (stickers.length > 60) {
@@ -330,136 +281,124 @@ const prepareStickerPackMessage = async (message, options) => {
330
281
  if (!cover) {
331
282
  throw new Boom('Sticker pack must contain a cover', { statusCode: 400 });
332
283
  }
333
- // Lia@Changes 01-02-26 --- Add caching for sticker pack (similiar to prepareWAMessageMedia)
334
- const cacheableKey = Array.isArray(stickers) &&
335
- stickers.length &&
336
- !!options.mediaCache &&
337
- 'sticker:' + stickers
338
- .reduce((acc, x) => {
339
- const url = typeof x.data === 'object' &&
340
- 'url' in x.data &&
341
- !!x.data.url &&
342
- x.data.url;
343
- if (url) acc.push(url);
344
- return acc;
345
- }, [])
346
- .join('@');
284
+ const logger = options.logger;
285
+ // Lia@Changes 01-02-26 --- Add caching for sticker pack
286
+ let cacheableKey = false;
287
+ if (Array.isArray(stickers) && stickers.length && options.mediaCache) {
288
+ const urls = [];
289
+ for (let i = 0; i < stickers.length; i++) {
290
+ const data = stickers[i].data;
291
+ if (typeof data === 'object' && data?.url) {
292
+ urls.push(data.url);
293
+ }
294
+ }
295
+ if (urls.length > 0) {
296
+ cacheableKey = 'sticker:' + urls.join('@');
297
+ }
298
+ }
347
299
  if (cacheableKey) {
348
300
  const mediaBuff = await options.mediaCache.get(cacheableKey);
349
301
  if (mediaBuff) {
350
- options.logger?.debug({ cacheableKey }, 'got media cache hit');
302
+ logger?.debug({ cacheableKey }, 'got media cache hit');
351
303
  return proto.Message.StickerPackMessage.decode(mediaBuff);
352
304
  }
353
305
  }
354
306
  const lib = await getImageProcessingLibrary();
307
+ const hasSharp = 'sharp' in lib && !!lib.sharp?.default;
308
+ const hasImage = 'image' in lib && !!lib.image?.Transformer;
309
+ const hasJimp = 'jimp' in lib && !!lib.jimp?.Jimp;
310
+ if (!hasSharp && !hasImage) {
311
+ throw new Boom('No image processing library (sharp or @napi-rs/image) available for converting sticker to WebP.');
312
+ }
355
313
  const stickerPackIdValue = generateMessageIDV2();
356
314
  const stickerData = {};
357
- const stickerPromises = stickers.map(async (sticker, i) => {
358
- const { stream } = await getStream(sticker.data);
359
- const buffer = await toBuffer(stream);
360
- let webpBuffer,
361
- isAnimated = false;
362
- const isWebP = isWebPBuffer(buffer);
363
- if (isWebP) {
364
- // Already WebP - preserve original to keep exif metadata and animation
365
- webpBuffer = buffer;
366
- isAnimated = isAnimatedWebP(buffer);
367
- }
368
- else if ('sharp' in lib && lib.sharp?.default) {
369
- // Convert to WebP, preserving metadata
370
- webpBuffer = await lib
371
- .sharp
372
- .default(buffer)
373
- .resize(512, 512, { fit: 'inside' })
374
- .webp({ quality: 80 })
375
- .toBuffer();
376
- // Non-WebP inputs converted to WebP are not animated
377
- isAnimated = false;
378
- }
379
- else if ('image' in lib && lib.image?.Transformer) {
380
- webpBuffer = await new lib
381
- .image
382
- .Transformer(buffer)
383
- .resize(512, 512)
384
- .webp(80);
385
- // Non-WebP inputs converted to WebP are not animated
386
- isAnimated = false;
315
+ const stickerMetadata = new Array(stickers.length);
316
+ for (let i = 0; i < stickers.length; i += CONCURRENCY_LIMIT) {
317
+ const promises = [];
318
+ const chunkEnd = Math.min(i + CONCURRENCY_LIMIT, stickers.length);
319
+ for (let j = i; j < chunkEnd; j++) {
320
+ promises.push((async (index) => {
321
+ const sticker = stickers[index];
322
+ const { stream } = await getStream(sticker.data);
323
+ const buffer = await toBuffer(stream);
324
+ let webpBuffer;
325
+ let isAnimated = false;
326
+ if (isWebPBuffer(buffer)) {
327
+ webpBuffer = buffer;
328
+ isAnimated = isAnimatedWebP(buffer);
329
+ }
330
+ else if (hasSharp) {
331
+ webpBuffer = await lib.sharp.default(buffer)
332
+ .resize(512, 512, { fit: 'inside' })
333
+ .webp({ quality: 80 })
334
+ .toBuffer();
335
+ }
336
+ else {
337
+ webpBuffer = await new lib.image.Transformer(buffer)
338
+ .resize(512, 512)
339
+ .webp(80);
340
+ }
341
+ if (webpBuffer.length > 1024 * 1024) {
342
+ throw new Boom(`Sticker at index ${index} exceeds the 1MB size limit`, { statusCode: 400 });
343
+ }
344
+ const hash = sha256(webpBuffer).toString('base64').replace(/\//g, '-');
345
+ const fileName = `${hash}.webp`;
346
+ stickerData[fileName] = [new Uint8Array(webpBuffer), { level: 0 }];
347
+ stickerMetadata[index] = {
348
+ fileName,
349
+ mimetype: 'image/webp',
350
+ isAnimated,
351
+ emojis: sticker.emojis || ['✨'],
352
+ accessibilityLabel: sticker.accessibilityLabel || '‎'
353
+ };
354
+ })(j));
387
355
  }
388
- else {
389
- throw new Boom('No image processing library (sharp or @napi-rs/image) available for converting sticker to WebP. Either install sharp or @napi-rs/image or provide stickers in WebP format.');
390
- }
391
- if (webpBuffer.length > 1024 * 1024) {
392
- throw new Boom(`Sticker at index ${i} exceeds the 1MB size limit`, { statusCode: 400 });
393
- }
394
- const hash = sha256(webpBuffer).toString('base64').replace(/\//g, '-');
395
- const fileName = hash + '.webp';
396
- stickerData[fileName] = [new Uint8Array(webpBuffer), { level: 0 }];
397
- return {
398
- fileName,
399
- mimetype: 'image/webp',
400
- isAnimated,
401
- emojis: sticker.emojis || ['✨'],
402
- accessibilityLabel: sticker.accessibilityLabel || '‎'
403
- };
404
- });
405
- const stickerMetadata = await Promise.all(stickerPromises);
406
- // Process and add cover/tray icon to the ZIP
407
- const trayIconFileName = stickerPackIdValue + '.webp';
356
+ await Promise.all(promises);
357
+ }
358
+ const trayIconFileName = `${stickerPackIdValue}.webp`;
408
359
  const { stream: coverStream } = await getStream(cover);
409
360
  const coverBuffer = await toBuffer(coverStream);
410
361
  let coverWebpBuffer;
411
- const isCoverWebP = isWebPBuffer(coverBuffer);
412
- if (isCoverWebP) {
413
- // Already WebP - preserve original to keep exif metadata
362
+ if (isWebPBuffer(coverBuffer)) {
414
363
  coverWebpBuffer = coverBuffer;
415
364
  }
416
- else if ('sharp' in lib && lib.sharp?.default) {
417
- coverWebpBuffer = await lib
418
- .sharp
419
- .default(coverBuffer)
365
+ else if (hasSharp) {
366
+ coverWebpBuffer = await lib.sharp.default(coverBuffer)
420
367
  .resize(512, 512, { fit: 'inside' })
421
368
  .webp({ quality: 80 })
422
369
  .toBuffer();
423
370
  }
424
- else if ('image' in lib && lib.image?.Transformer) {
425
- coverWebpBuffer = await new lib
426
- .image
427
- .Transformer(coverBuffer)
371
+ else {
372
+ coverWebpBuffer = await new lib.image.Transformer(coverBuffer)
428
373
  .resize(512, 512)
429
374
  .webp(80);
430
375
  }
431
- else {
432
- throw new Boom('No image processing library (sharp or @napi-rs/image) available for converting cover to WebP. Either install sharp or @napi-rs/image or provide cover in WebP format.');
433
- }
434
- // Add cover to ZIP data
435
376
  stickerData[trayIconFileName] = [new Uint8Array(coverWebpBuffer), { level: 0 }];
436
377
  const zipBuffer = await new Promise((resolve, reject) => {
437
- zip(stickerData, (error, data) => {
438
- if (error) {
439
- reject(error);
440
- } else {
441
- resolve(Buffer.from(data));
442
- }
443
- });
378
+ zip(stickerData, (error, data) => error ? reject(error) : resolve(Buffer.from(data)));
444
379
  });
445
- const stickerPackSize = zipBuffer.length;
446
380
  const stickerPackUpload = await encryptedStream(zipBuffer, 'sticker-pack', {
447
- logger: options.logger,
381
+ logger,
448
382
  opts: options.options
449
383
  });
450
- const stickerPackUploadResult = await options.upload(stickerPackUpload.encFilePath, {
451
- fileEncSha256B64: stickerPackUpload.fileEncSha256.toString('base64'),
452
- mediaType: 'sticker-pack',
453
- timeoutMs: options.mediaUploadTimeoutMs
454
- });
455
- await fs.unlink(stickerPackUpload.encFilePath);
384
+ let stickerPackUploadResult;
385
+ try {
386
+ stickerPackUploadResult = await options.upload(stickerPackUpload.encFilePath, {
387
+ fileEncSha256B64: stickerPackUpload.fileEncSha256.toString('base64'),
388
+ mediaType: 'sticker-pack',
389
+ timeoutMs: options.mediaUploadTimeoutMs
390
+ });
391
+ }
392
+ finally {
393
+ fs.unlink(stickerPackUpload.encFilePath).catch(() => logger?.warn('failed to remove tmp file'));
394
+ }
456
395
  const obj = {
457
- name: name,
458
- publisher: publisher,
396
+ name,
397
+ publisher,
459
398
  stickerPackId: stickerPackIdValue,
460
399
  packDescription: description,
461
400
  stickerPackOrigin: proto.Message.StickerPackMessage.StickerPackOrigin.USER_CREATED,
462
- stickerPackSize: stickerPackSize,
401
+ stickerPackSize: zipBuffer.length,
463
402
  stickers: stickerMetadata,
464
403
  fileSha256: stickerPackUpload.fileSha256,
465
404
  fileEncSha256: stickerPackUpload.fileEncSha256,
@@ -467,31 +406,19 @@ const prepareStickerPackMessage = async (message, options) => {
467
406
  directPath: stickerPackUploadResult.directPath,
468
407
  fileLength: stickerPackUpload.fileLength,
469
408
  mediaKeyTimestamp: unixTimestampSeconds(),
470
- trayIconFileName: trayIconFileName
409
+ trayIconFileName
471
410
  };
472
411
  try {
473
- // Reuse the cover buffer we already processed for thumbnail generation
474
412
  let thumbnailBuffer;
475
- if ('sharp' in lib && lib.sharp?.default) {
476
- thumbnailBuffer = await lib
477
- .sharp
478
- .default(coverBuffer)
479
- .resize(252, 252)
480
- .jpeg()
481
- .toBuffer();
482
- }
483
- if ('image' in lib && lib.image?.Transformer) {
484
- thumbnailBuffer = await new lib
485
- .image
486
- .Transformer(coverBuffer)
487
- .resize(252, 252)
488
- .jpeg();
489
- }
490
- else if ('jimp' in lib && lib.jimp?.Jimp) {
413
+ if (hasSharp) {
414
+ thumbnailBuffer = await lib.sharp.default(coverBuffer).resize(252, 252).jpeg().toBuffer();
415
+ }
416
+ else if (hasImage) {
417
+ thumbnailBuffer = await new lib.image.Transformer(coverBuffer).resize(252, 252).jpeg();
418
+ }
419
+ else if (hasJimp) {
491
420
  const jimpImage = await lib.jimp.Jimp.read(coverBuffer);
492
- thumbnailBuffer = await jimpImage
493
- .resize({ w: 252, h: 252 })
494
- .getBuffer('image/jpeg');
421
+ thumbnailBuffer = await jimpImage.resize({ w: 252, h: 252 }).getBuffer('image/jpeg');
495
422
  }
496
423
  else {
497
424
  throw new Error('No image processing library available for thumbnail generation');
@@ -500,16 +427,21 @@ const prepareStickerPackMessage = async (message, options) => {
500
427
  throw new Error('Failed to generate thumbnail buffer');
501
428
  }
502
429
  const thumbUpload = await encryptedStream(thumbnailBuffer, 'thumbnail-sticker-pack', {
503
- logger: options.logger,
430
+ logger,
504
431
  opts: options.options,
505
- mediaKey: stickerPackUpload.mediaKey // Use same mediaKey as the sticker pack ZIP
506
- });
507
- const thumbUploadResult = await options.upload(thumbUpload.encFilePath, {
508
- fileEncSha256B64: thumbUpload.fileEncSha256.toString('base64'),
509
- mediaType: 'thumbnail-sticker-pack',
510
- timeoutMs: options.mediaUploadTimeoutMs
432
+ mediaKey: stickerPackUpload.mediaKey
511
433
  });
512
- await fs.unlink(thumbUpload.encFilePath);
434
+ let thumbUploadResult;
435
+ try {
436
+ thumbUploadResult = await options.upload(thumbUpload.encFilePath, {
437
+ fileEncSha256B64: thumbUpload.fileEncSha256.toString('base64'),
438
+ mediaType: 'thumbnail-sticker-pack',
439
+ timeoutMs: options.mediaUploadTimeoutMs
440
+ });
441
+ }
442
+ finally {
443
+ fs.unlink(thumbUpload.encFilePath).catch(() => logger?.warn('failed to remove tmp file'));
444
+ }
513
445
  Object.assign(obj, {
514
446
  thumbnailDirectPath: thumbUploadResult.directPath,
515
447
  thumbnailSha256: thumbUpload.fileSha256,
@@ -520,18 +452,17 @@ const prepareStickerPackMessage = async (message, options) => {
520
452
  });
521
453
  }
522
454
  catch (error) {
523
- options.logger?.warn?.(`Thumbnail generation failed: ${error}`);
455
+ logger?.warn(`Thumbnail generation failed: ${error}`);
524
456
  }
525
- const content = obj;
526
457
  if (cacheableKey) {
527
- options.logger?.debug({ cacheableKey }, 'set cache');
528
- await options.mediaCache.set(cacheableKey, WAProto.Message.StickerPackMessage.encode(content).finish());
458
+ logger?.debug({ cacheableKey }, 'set cache (background)');
459
+ options.mediaCache.set(cacheableKey, WAProto.Message.StickerPackMessage.encode(obj).finish());
529
460
  }
530
- return WAProto.Message.StickerPackMessage.fromObject(content);
461
+ return WAProto.Message.StickerPackMessage.fromObject(obj);
531
462
  };
532
463
  // Lia@Changes 30-01-26 --- Add native flow button helper for interactive message
533
464
  const prepareNativeFlowButtons = (message) => {
534
- const buttons = message.nativeFlow
465
+ const buttons = message.nativeFlow;
535
466
  const isButtonsFieldArray = Array.isArray(buttons);
536
467
  const correctedField = isButtonsFieldArray ? buttons : buttons.buttons;
537
468
  const messageParamsJson = {};
@@ -550,10 +481,7 @@ const prepareNativeFlowButtons = (message) => {
550
481
  Object.assign(messageParamsJson, {
551
482
  bottom_sheet: {
552
483
  in_thread_buttons_limit: 1,
553
- divider_indices: Array.from(
554
- { length: correctedField.length },
555
- (_, index) => index
556
- ),
484
+ divider_indices: Array.from({ length: correctedField.length }, (_, index) => index),
557
485
  list_title: message.optionTitle || '📄 Select Options',
558
486
  button_title: message.optionText
559
487
  }
@@ -590,6 +518,7 @@ const prepareNativeFlowButtons = (message) => {
590
518
  display_text: buttonText || '🌐 Visit',
591
519
  url: button.url,
592
520
  merchant_url: button.url,
521
+ webview_interaction: button.useWebview,
593
522
  icon: buttonIcon
594
523
  })
595
524
  };
@@ -617,17 +546,30 @@ const prepareNativeFlowButtons = (message) => {
617
546
  }
618
547
  return button;
619
548
  }),
620
- messageParamsJson: JSON.stringify(messageParamsJson),
621
- messageVersion: 1
549
+ messageParamsJson: JSON.stringify(messageParamsJson)
622
550
  };
623
551
  };
552
+ export const prepareDisappearingMessageSettingContent = (ephemeralExpiration) => {
553
+ ephemeralExpiration = ephemeralExpiration || 0;
554
+ const content = {
555
+ ephemeralMessage: {
556
+ message: {
557
+ protocolMessage: {
558
+ type: WAProto.Message.ProtocolMessage.Type.EPHEMERAL_SETTING,
559
+ ephemeralExpiration
560
+ }
561
+ }
562
+ }
563
+ };
564
+ return WAProto.Message.fromObject(content);
565
+ };
624
566
  /**
625
567
  * Generate forwarded message content like WA does
626
568
  * @param message the message to forward
627
569
  * @param options.forceForward will show the message as forwarded even if it is from you
628
570
  */
629
571
  export const generateForwardMessageContent = (message, forceForward) => {
630
- let content = message.message || message;
572
+ let content = message.message;
631
573
  if (!content) {
632
574
  throw new Boom('no content in message', { statusCode: 400 });
633
575
  }
@@ -643,24 +585,12 @@ export const generateForwardMessageContent = (message, forceForward) => {
643
585
  key = 'extendedTextMessage';
644
586
  }
645
587
  const key_ = content?.[key];
646
- const contextInfo = {};
647
588
  if (score > 0) {
648
- contextInfo.forwardingScore = score;
649
- contextInfo.isForwarded = true;
650
- }
651
- // when forwarding a newsletter/channel message, add the newsletter context
652
- // so the server knows where to find the original media
653
- const remoteJid = message.key?.remoteJid;
654
- if (remoteJid && isJidNewsletter(remoteJid)) {
655
- contextInfo.forwardedNewsletterMessageInfo = {
656
- newsletterJid: remoteJid,
657
- serverMessageId: message.key?.server_id ? parseInt(message.key.server_id) : null,
658
- newsletterName: null
659
- };
660
- // strip messageContextInfo (contains messageSecret etc.) as WA Web does
661
- delete content.messageContextInfo;
589
+ key_.contextInfo = { forwardingScore: score, isForwarded: true };
590
+ }
591
+ else {
592
+ key_.contextInfo = {};
662
593
  }
663
- key_.contextInfo = contextInfo;
664
594
  return content;
665
595
  };
666
596
  export const hasNonNullishProperty = (message, key) => {
@@ -677,21 +607,21 @@ export const hasOptionalProperty = (obj, key) => {
677
607
  };
678
608
  // Lia@Changes 06-02-26 --- Validate album message media to avoid bug 👀
679
609
  export const hasValidAlbumMedia = (message) => {
680
- return message.imageMessage ||
681
- message.videoMessage;
610
+ return !!(message.imageMessage ||
611
+ message.videoMessage);
682
612
  };
683
613
  export const hasValidInteractiveHeader = (message) => {
684
- return message.imageMessage ||
614
+ return !!(message.imageMessage ||
685
615
  message.videoMessage ||
686
616
  message.documentMessage ||
687
617
  message.productMessage ||
688
- message.locationMessage;
618
+ message.locationMessage);
689
619
  };
690
620
  // Lia@Changes 30-01-26 --- Validate carousel cards header to avoid bug 👀
691
621
  export const hasValidCarouselHeader = (message) => {
692
- return message.imageMessage ||
622
+ return !!(message.imageMessage ||
693
623
  message.videoMessage ||
694
- message.productMessage;
624
+ message.productMessage);
695
625
  };
696
626
  export const generateWAMessageContent = async (message, options) => {
697
627
  var _a, _b;
@@ -701,10 +631,9 @@ export const generateWAMessageContent = async (message, options) => {
701
631
  delete message.raw;
702
632
  return message;
703
633
  }
704
- // Lia@Changes 09-04-26 --- Add support for code block, latex, reels carousel, table with richResponseMessage
634
+ // Lia@Changes 09-04-26 --- Add support for code block and table with richResponseMessage
705
635
  else if (hasNonNullishProperty(message, 'code') ||
706
- hasNonNullishProperty(message, 'expressions') ||
707
- hasNonNullishProperty(message, 'items') ||
636
+ hasNonNullishProperty(message, 'links') ||
708
637
  hasNonNullishProperty(message, 'table') ||
709
638
  hasNonNullishProperty(message, 'richResponse')) {
710
639
  m = prepareRichResponseMessage(message);
@@ -720,7 +649,8 @@ export const generateWAMessageContent = async (message, options) => {
720
649
  extContent.jpegThumbnail = urlInfo.jpegThumbnail;
721
650
  extContent.description = urlInfo.description;
722
651
  extContent.title = urlInfo.title;
723
- extContent.previewType = 0;
652
+ extContent.previewType = urlInfo.previewType ?? 0;
653
+ extContent.linkPreviewMetadata = urlInfo.linkPreviewMetadata;
724
654
  const img = urlInfo.highQualityThumbnail;
725
655
  if (img) {
726
656
  extContent.thumbnailDirectPath = img.directPath;
@@ -732,6 +662,21 @@ export const generateWAMessageContent = async (message, options) => {
732
662
  extContent.thumbnailEncSha256 = img.fileEncSha256;
733
663
  }
734
664
  }
665
+ const faviconData = message.favicon;
666
+ if (faviconData && typeof options.upload === 'function') {
667
+ const { imageMessage } = await prepareWAMessageMedia({
668
+ image: faviconData
669
+ }, options);
670
+ extContent.faviconMMSMetadata = {
671
+ thumbnailDirectPath: imageMessage.directPath,
672
+ mediaKey: imageMessage.mediaKey,
673
+ mediaKeyTimestamp: imageMessage.mediaKeyTimestamp,
674
+ thumbnailWidth: 32,
675
+ thumbnailHeight: 32,
676
+ thumbnailSha256: imageMessage.fileSha256,
677
+ thumbnailEncSha256: imageMessage.fileEncSha256
678
+ };
679
+ }
735
680
  if (options.backgroundColor) {
736
681
  extContent.backgroundArgb = await assertColor(options.backgroundColor);
737
682
  }
@@ -746,25 +691,25 @@ export const generateWAMessageContent = async (message, options) => {
746
691
  throw new Boom('require atleast 1 contact', { statusCode: 400 });
747
692
  }
748
693
  if (contactLen === 1) {
749
- m.contactMessage = message.contacts.contacts[0];
694
+ m.contactMessage = WAProto.Message.ContactMessage.create(message.contacts.contacts[0]);
750
695
  }
751
696
  else {
752
- m.contactsArrayMessage = message.contacts;
697
+ m.contactsArrayMessage = WAProto.Message.ContactsArrayMessage.create(message.contacts);
753
698
  }
754
699
  }
755
700
  else if (hasNonNullishProperty(message, 'location')) {
756
- m.locationMessage = message.location;
701
+ m.locationMessage = WAProto.Message.LocationMessage.create(message.location);
757
702
  }
758
703
  else if (hasNonNullishProperty(message, 'react')) {
759
704
  if (!message.react.senderTimestampMs) {
760
705
  message.react.senderTimestampMs = Date.now();
761
706
  }
762
- m.reactionMessage = message.react;
707
+ m.reactionMessage = WAProto.Message.ReactionMessage.create(message.react);
763
708
  }
764
709
  else if (hasNonNullishProperty(message, 'delete')) {
765
710
  m.protocolMessage = {
766
711
  key: message.delete,
767
- type: ProtocolType.REVOKE
712
+ type: WAProto.Message.ProtocolMessage.Type.REVOKE
768
713
  };
769
714
  }
770
715
  else if (hasNonNullishProperty(message, 'forward')) {
@@ -894,14 +839,13 @@ export const generateWAMessageContent = async (message, options) => {
894
839
  statusCode: 400
895
840
  });
896
841
  }
897
- m.messageContextInfo = {
898
- // encKey
899
- messageSecret: message.poll.messageSecret || randomBytes(32)
900
- };
901
842
  const pollCreationMessage = {
902
843
  name: message.poll.name,
903
844
  selectableOptionsCount: message.poll.selectableCount,
904
- options: message.poll.values.map(optionName => ({ optionName }))
845
+ options: message.poll.values.map(optionName => ({ optionName })),
846
+ endTime: message.poll.endDate ? message.poll.endDate.getTime() : undefined,
847
+ hideParticipantName: message.poll.hideVoter ?? false,
848
+ allowAddOption: message.poll.canAddOption ?? false
905
849
  };
906
850
  if (message.poll.toAnnouncementGroup) {
907
851
  // poll v2 is for community announcement groups (single select and multiple)
@@ -909,7 +853,7 @@ export const generateWAMessageContent = async (message, options) => {
909
853
  }
910
854
  else {
911
855
  // Lia@Changes 08-02-26 --- Add quiz message support
912
- if (message.poll.pollType == 1) {
856
+ if (message.poll.pollType === 1) {
913
857
  if (!message.poll.correctAnswer) {
914
858
  throw new Boom('No "correctAnswer" provided for quiz', { statusCode: 400 });
915
859
  }
@@ -919,7 +863,7 @@ export const generateWAMessageContent = async (message, options) => {
919
863
  correctAnswer: {
920
864
  optionName: message.poll.correctAnswer.toString()
921
865
  },
922
- pollType: message.poll.pollType,
866
+ pollType: 1,
923
867
  selectableOptionsCount: 1
924
868
  };
925
869
  }
@@ -932,6 +876,10 @@ export const generateWAMessageContent = async (message, options) => {
932
876
  m.pollCreationMessage = pollCreationMessage;
933
877
  }
934
878
  }
879
+ m.messageContextInfo = {
880
+ // encKey
881
+ messageSecret: message.poll.messageSecret || randomBytes(32)
882
+ };
935
883
  }
936
884
  // Lia@Changes 08-02-26 --- Add poll result snapshot message
937
885
  else if (hasNonNullishProperty(message, 'pollResult')) {
@@ -942,7 +890,7 @@ export const generateWAMessageContent = async (message, options) => {
942
890
  optionVoteCount: parseInt(vote.voteCount)
943
891
  }))
944
892
  };
945
- if (message.pollResult.pollType == 1) {
893
+ if (message.pollResult.pollType === 1) {
946
894
  pollResultSnapshotMessage.pollType = proto.Message.PollType.QUIZ;
947
895
  m.pollResultSnapshotMessageV3 = pollResultSnapshotMessage;
948
896
  }
@@ -966,25 +914,6 @@ export const generateWAMessageContent = async (message, options) => {
966
914
  vote: message.pollUpdate.vote
967
915
  };
968
916
  }
969
- else if (hasNonNullishProperty(message, 'sharePhoneNumber')) {
970
- m.protocolMessage = {
971
- type: ProtocolType.SHARE_PHONE_NUMBER
972
- };
973
- }
974
- else if (hasNonNullishProperty(message, 'requestPhoneNumber')) {
975
- m.requestPhoneNumberMessage = {};
976
- }
977
- else if (hasNonNullishProperty(message, 'limitSharing')) {
978
- m.protocolMessage = {
979
- type: ProtocolType.LIMIT_SHARING,
980
- limitSharing: {
981
- sharingLimited: message.limitSharing === true,
982
- trigger: 1,
983
- limitSharingSettingTimestamp: Date.now(),
984
- initiatedByMe: true
985
- }
986
- };
987
- }
988
917
  // Lia@Changes 01-02-26 --- Add payment invite message
989
918
  else if (hasNonNullishProperty(message, 'paymentInviteServiceType')) {
990
919
  m.paymentInviteMessage = {
@@ -1018,12 +947,16 @@ export const generateWAMessageContent = async (message, options) => {
1018
947
  }
1019
948
  let videoCount = 0;
1020
949
  for (let i = 0; i < message.album.length; i++) {
1021
- if (message.album[i].video) videoCount++;
1022
- };
950
+ if (message.album[i].video)
951
+ videoCount++;
952
+ }
953
+ ;
1023
954
  let imageCount = 0;
1024
955
  for (let i = 0; i < message.album.length; i++) {
1025
- if (message.album[i].image) imageCount++;
1026
- };
956
+ if (message.album[i].image)
957
+ imageCount++;
958
+ }
959
+ ;
1027
960
  if ((videoCount + imageCount) < 2) {
1028
961
  throw new Boom('Minimum provide 2 media to upload album message', { statusCode: 400 });
1029
962
  }
@@ -1032,6 +965,25 @@ export const generateWAMessageContent = async (message, options) => {
1032
965
  expectedVideoCount: videoCount
1033
966
  };
1034
967
  }
968
+ else if (hasNonNullishProperty(message, 'sharePhoneNumber')) {
969
+ m.protocolMessage = {
970
+ type: proto.Message.ProtocolMessage.Type.SHARE_PHONE_NUMBER
971
+ };
972
+ }
973
+ else if (hasNonNullishProperty(message, 'requestPhoneNumber')) {
974
+ m.requestPhoneNumberMessage = {};
975
+ }
976
+ else if (hasNonNullishProperty(message, 'limitSharing')) {
977
+ m.protocolMessage = {
978
+ type: proto.Message.ProtocolMessage.Type.LIMIT_SHARING,
979
+ limitSharing: {
980
+ sharingLimited: message.limitSharing === true,
981
+ trigger: 1,
982
+ limitSharingSettingTimestamp: Date.now(),
983
+ initiatedByMe: true
984
+ }
985
+ };
986
+ }
1035
987
  else {
1036
988
  m = await prepareWAMessageMedia(message, options);
1037
989
  }
@@ -1044,11 +996,11 @@ export const generateWAMessageContent = async (message, options) => {
1044
996
  if (hasOptionalProperty(button, 'sections')) {
1045
997
  return {
1046
998
  nativeFlowInfo: {
1047
- name: 'single_select',
1048
- paramsJson: JSON.stringify({
1049
- title: buttonText,
1050
- sections: button.sections
1051
- })
999
+ name: 'single_select',
1000
+ paramsJson: JSON.stringify({
1001
+ title: buttonText,
1002
+ sections: button.sections
1003
+ })
1052
1004
  },
1053
1005
  type: ButtonType.NATIVE_FLOW
1054
1006
  };
@@ -1056,8 +1008,8 @@ export const generateWAMessageContent = async (message, options) => {
1056
1008
  else if (hasOptionalProperty(button, 'name')) {
1057
1009
  return {
1058
1010
  nativeFlowInfo: {
1059
- name: button.name,
1060
- paramsJson: button.paramsJson
1011
+ name: button.name,
1012
+ paramsJson: button.paramsJson
1061
1013
  },
1062
1014
  type: ButtonType.NATIVE_FLOW
1063
1015
  };
@@ -1140,7 +1092,8 @@ export const generateWAMessageContent = async (message, options) => {
1140
1092
  if (hasOptionalProperty(message, 'caption')) {
1141
1093
  hydratedTemplate.hydratedTitleText = message.title;
1142
1094
  hydratedTemplate.hydratedContentText = message.caption;
1143
- };
1095
+ }
1096
+ ;
1144
1097
  Object.assign(hydratedTemplate, m);
1145
1098
  }
1146
1099
  if (hasOptionalProperty(message, 'footer')) {
@@ -1152,7 +1105,7 @@ export const generateWAMessageContent = async (message, options) => {
1152
1105
  hydratedFourRowTemplate: hydratedTemplate,
1153
1106
  hydratedTemplate: hydratedTemplate
1154
1107
  }
1155
- }
1108
+ };
1156
1109
  }
1157
1110
  else if (hasNonNullishProperty(message, 'nativeFlow')) {
1158
1111
  const interactiveMessage = {
@@ -1177,9 +1130,9 @@ export const generateWAMessageContent = async (message, options) => {
1177
1130
  }
1178
1131
  else {
1179
1132
  if (hasOptionalProperty(message, 'caption')) {
1180
- const isValidHeader = hasValidInteractiveHeader(m)
1133
+ const isValidHeader = hasValidInteractiveHeader(m);
1181
1134
  if (!isValidHeader) {
1182
- throw new Boom('Invalid media type for interactive message header', { statusCode: 400 });
1135
+ throw new Boom('Invalid media type for interactive message header', { statusCode: 400 });
1183
1136
  }
1184
1137
  interactiveMessage.header = {
1185
1138
  title: message.title || '',
@@ -1194,11 +1147,11 @@ export const generateWAMessageContent = async (message, options) => {
1194
1147
  Object.assign(interactiveMessage.header, m);
1195
1148
  }
1196
1149
  if (hasOptionalProperty(message, 'audioFooter')) {
1197
- const parseFooter = await prepareWAMessageMedia({
1150
+ const { audioMessage } = await prepareWAMessageMedia({
1198
1151
  audio: message.audioFooter
1199
1152
  }, options);
1200
1153
  interactiveMessage.footer = {
1201
- audioMessage: parseFooter.audioMessage,
1154
+ audioMessage,
1202
1155
  hasMediaAttachment: true
1203
1156
  };
1204
1157
  }
@@ -1210,17 +1163,17 @@ export const generateWAMessageContent = async (message, options) => {
1210
1163
  else if (hasNonNullishProperty(message, 'cards')) {
1211
1164
  const interactiveMessage = {
1212
1165
  carouselMessage: {
1213
- cards: await Promise.all(message.cards.map(async card => {
1166
+ cards: await Promise.all(message.cards.map(async (card) => {
1214
1167
  let carouselHeader = {};
1215
1168
  if (hasNonNullishProperty(card, 'product')) {
1216
1169
  carouselHeader.productMessage = await prepareProductMessage(card, options);
1217
1170
  }
1218
1171
  else {
1219
- carouselHeader = await prepareWAMessageMedia(card, options).catch(() => ({ }));
1172
+ carouselHeader = await prepareWAMessageMedia(card, options).catch(() => ({}));
1220
1173
  }
1221
- const isValidHeader = hasValidCarouselHeader(carouselHeader)
1174
+ const isValidHeader = hasValidCarouselHeader(carouselHeader);
1222
1175
  if (!isValidHeader) {
1223
- throw new Boom('Invalid media type for carousel card', { statusCode: 400 });
1176
+ throw new Boom('Invalid media type for carousel card', { statusCode: 400 });
1224
1177
  }
1225
1178
  const carouselCard = {
1226
1179
  nativeFlowMessage: prepareNativeFlowButtons(card.nativeFlow ? card : [])
@@ -1243,18 +1196,18 @@ export const generateWAMessageContent = async (message, options) => {
1243
1196
  Object.assign(carouselCard.header, carouselHeader);
1244
1197
  }
1245
1198
  if (hasOptionalProperty(card, 'audioFooter')) {
1246
- const parseFooter = await prepareWAMessageMedia({
1199
+ const { audioMessage } = await prepareWAMessageMedia({
1247
1200
  audio: card.audioFooter
1248
1201
  }, options);
1249
1202
  carouselCard.footer = {
1250
- audioMessage: parseFooter.audioMessage,
1203
+ audioMessage,
1251
1204
  hasMediaAttachment: true
1252
1205
  };
1253
1206
  }
1254
1207
  else if (hasOptionalProperty(card, 'footer')) {
1255
1208
  carouselCard.footer = { text: card.footer };
1256
1209
  }
1257
- return carouselCard
1210
+ return carouselCard;
1258
1211
  })),
1259
1212
  carouselCardType: CarouselCardType.UNKNOWN,
1260
1213
  messageVersion: 1
@@ -1272,9 +1225,9 @@ export const generateWAMessageContent = async (message, options) => {
1272
1225
  else if (hasNonNullishProperty(message, 'requestPaymentFrom')) {
1273
1226
  const requestPaymentMessage = {
1274
1227
  amount: {
1275
- currencyCode: 'IDR',
1276
- offset: 1000,
1277
- value: 1000
1228
+ currencyCode: 'IDR',
1229
+ offset: 1000,
1230
+ value: 1000
1278
1231
  },
1279
1232
  amount1000: 1000,
1280
1233
  currencyCodeIso4217: 'IDR',
@@ -1335,9 +1288,9 @@ export const generateWAMessageContent = async (message, options) => {
1335
1288
  mediaType: content.mediaType || 1,
1336
1289
  mediaUrl: content.url,
1337
1290
  renderLargerThumbnail: content.largeThumbnail,
1338
- sourceUrl: content.url + '?update=' + Date.now(),
1291
+ sourceUrl: content.url,
1339
1292
  thumbnail: content.thumbnail,
1340
- thumbnailUrl: content.url,
1293
+ thumbnailUrl: content.url + '?update=' + Date.now(),
1341
1294
  title: content.title || LIBRARY_NAME
1342
1295
  };
1343
1296
  delete externalAdReply.subTitle;
@@ -1354,18 +1307,21 @@ export const generateWAMessageContent = async (message, options) => {
1354
1307
  (hasOptionalProperty(message, 'mentionAll') && message.mentionAll)) {
1355
1308
  const messageType = Object.keys(m)[0];
1356
1309
  const key = m[messageType];
1357
- if ('contextInfo' in key && !!key.contextInfo) {
1358
- key.contextInfo.mentionedJid = message.mentions || [];
1310
+ if (key && 'contextInfo' in key) {
1311
+ key.contextInfo = key.contextInfo || {};
1312
+ if (message.mentions?.length) {
1313
+ key.contextInfo.mentionedJid = message.mentions;
1314
+ }
1315
+ if (message.mentionAll) {
1316
+ key.contextInfo.nonJidMentions = 1;
1317
+ }
1359
1318
  }
1360
1319
  else if (key) {
1361
1320
  key.contextInfo = {
1362
- mentionedJid: message.mentions || []
1321
+ mentionedJid: message.mentions,
1322
+ nonJidMentions: message.mentionAll ? 1 : 0
1363
1323
  };
1364
1324
  }
1365
- if (message.mentionAll) {
1366
- key.contextInfo.mentionedJid = [];
1367
- key.contextInfo.nonJidMentions = 1;
1368
- }
1369
1325
  }
1370
1326
  if (hasOptionalProperty(message, 'contextInfo') && !!message.contextInfo) {
1371
1327
  const messageType = Object.keys(m)[0];
@@ -1387,11 +1343,26 @@ export const generateWAMessageContent = async (message, options) => {
1387
1343
  else if (key) {
1388
1344
  key.contextInfo = {
1389
1345
  isGroupStatus: message.groupStatus
1390
- }
1346
+ };
1391
1347
  }
1392
1348
  m = { groupStatusMessageV2: { message: m } };
1393
1349
  delete message.groupStatus;
1394
1350
  }
1351
+ // Lia@Changes 06-05-26 --- Add "spoiler" boolean to set contextInfo.isSpoiler and wrap message into spoilerMessage
1352
+ if (hasOptionalProperty(message, 'spoiler') && !!message.spoiler) {
1353
+ const messageType = Object.keys(m)[0];
1354
+ const key = m[messageType];
1355
+ if ('contextInfo' in key && !!key.contextInfo) {
1356
+ key.contextInfo.isSpoiler = message.spoiler;
1357
+ }
1358
+ else if (key) {
1359
+ key.contextInfo = {
1360
+ isSpoiler: message.spoiler
1361
+ };
1362
+ }
1363
+ m = { spoilerMessage: { message: m } };
1364
+ delete message.spoiler;
1365
+ }
1395
1366
  // Lia@Changes 02-02-26 --- Add "interactiveAsTemplate" boolean to wrap interactiveMessage into templateMessage
1396
1367
  else if (hasOptionalProperty(message, 'interactiveAsTemplate') && !!message.interactiveAsTemplate) {
1397
1368
  if (!m.interactiveMessage) {
@@ -1410,6 +1381,10 @@ export const generateWAMessageContent = async (message, options) => {
1410
1381
  m = { ephemeralMessage: { message: m } };
1411
1382
  delete message.ephemeral;
1412
1383
  }
1384
+ // Lia@Changes 16-05-26 --- Add wrap message into lottieStickerMessage by isLottie boolean
1385
+ if (hasOptionalProperty(message, 'isLottie') && !!message.isLottie) {
1386
+ m = { lottieStickerMessage: { message: m } };
1387
+ }
1413
1388
  else if (hasOptionalProperty(message, 'viewOnce') && !!message.viewOnce) {
1414
1389
  m = { viewOnceMessage: { message: m } };
1415
1390
  }
@@ -1429,9 +1404,9 @@ export const generateWAMessageContent = async (message, options) => {
1429
1404
  key: message.edit,
1430
1405
  editedMessage: m,
1431
1406
  timestampMs: Date.now(),
1432
- type: ProtocolType.MESSAGE_EDIT
1407
+ type: WAProto.Message.ProtocolMessage.Type.MESSAGE_EDIT
1433
1408
  }
1434
- }
1409
+ };
1435
1410
  }
1436
1411
  if (shouldIncludeReportingToken(m)) {
1437
1412
  m.messageContextInfo = m.messageContextInfo || {};
@@ -1439,16 +1414,16 @@ export const generateWAMessageContent = async (message, options) => {
1439
1414
  m.messageContextInfo.messageSecret = randomBytes(32);
1440
1415
  }
1441
1416
  }
1442
- return proto.Message.create(m);
1417
+ return WAProto.Message.create(m);
1443
1418
  };
1444
1419
  export const generateWAMessageFromContent = (jid, message, options) => {
1445
1420
  // set timestamp to now
1446
1421
  // if not specified
1447
1422
  if (!options.timestamp) {
1448
- options.timestamp = Date.now();
1423
+ options.timestamp = new Date();
1449
1424
  }
1450
- const messageContextInfo = message.messageContextInfo
1451
1425
  const innerMessage = normalizeMessageContent(message);
1426
+ const messageContextInfo = message.messageContextInfo;
1452
1427
  const key = getContentType(innerMessage);
1453
1428
  const timestamp = unixTimestampSeconds(options.timestamp);
1454
1429
  const isNewsletter = isJidNewsletter(jid);
@@ -1501,9 +1476,9 @@ export const generateWAMessageFromContent = (jid, message, options) => {
1501
1476
  recipientKeyHash: randomBytes(10),
1502
1477
  recipientTimestamp: unixTimestampSeconds()
1503
1478
  };
1504
- messageContextInfo.deviceListMetadataVersion = 2
1479
+ messageContextInfo.deviceListMetadataVersion = 2;
1505
1480
  }
1506
- message = proto.Message.create(message);
1481
+ message = WAProto.Message.create(message);
1507
1482
  const messageJSON = {
1508
1483
  key: {
1509
1484
  remoteJid: jid,
@@ -1518,11 +1493,11 @@ export const generateWAMessageFromContent = (jid, message, options) => {
1518
1493
  };
1519
1494
  return WAProto.WebMessageInfo.fromObject(messageJSON);
1520
1495
  };
1521
- export const generateWAMessage = async (jid, content, options = {}) => {
1496
+ export const generateWAMessage = async (jid, content, options) => {
1522
1497
  // ensure msg ID is with every log
1523
1498
  options.logger = options?.logger?.child({ msgId: options.messageId });
1524
1499
  // Pass jid in the options to generateWAMessageContent
1525
- if (jid && typeof options === 'object') {
1500
+ if (jid) {
1526
1501
  options.jid = jid;
1527
1502
  }
1528
1503
  return generateWAMessageFromContent(jid, await generateWAMessageContent(content, options), options);
@@ -1556,8 +1531,7 @@ export const normalizeMessageContent = (content) => {
1556
1531
  return content;
1557
1532
  // Lia@Changes 03-02-26 --- Add all futureProofMessage into getFutureProofMessage()
1558
1533
  function getFutureProofMessage(message) {
1559
- return (
1560
- message?.associatedChildMessage ||
1534
+ return (message?.associatedChildMessage ||
1561
1535
  message?.botForwardedMessage ||
1562
1536
  message?.botInvokeMessage ||
1563
1537
  message?.botTaskMessage ||
@@ -1571,16 +1545,19 @@ export const normalizeMessageContent = (content) => {
1571
1545
  message?.groupStatusMessageV2 ||
1572
1546
  message?.limitSharingMessage ||
1573
1547
  message?.lottieStickerMessage ||
1548
+ message?.newsletterAdminProfileMessage ||
1549
+ message?.newsletterAdminProfileMessageV2 ||
1550
+ message?.newsletterAdminProfileStatusMessage ||
1574
1551
  message?.pollCreationMessageV4 ||
1575
1552
  message?.pollCreationOptionImageMessage ||
1576
1553
  message?.questionMessage ||
1577
1554
  message?.questionReplyMessage ||
1555
+ message?.spoilerMessage ||
1578
1556
  message?.statusAddYours ||
1579
1557
  message?.statusMentionMessage ||
1580
1558
  message?.viewOnceMessage ||
1581
1559
  message?.viewOnceMessageV2 ||
1582
- message?.viewOnceMessageV2Extension
1583
- );
1560
+ message?.viewOnceMessageV2Extension);
1584
1561
  }
1585
1562
  };
1586
1563
  /**
@@ -1819,8 +1796,7 @@ export const assertMediaContent = (content) => {
1819
1796
  */
1820
1797
  const isAnimatedWebP = (buffer) => {
1821
1798
  // WebP must start with RIFF....WEBP
1822
- if (
1823
- buffer.length < 12 ||
1799
+ if (buffer.length < 12 ||
1824
1800
  buffer[0] !== 0x52 ||
1825
1801
  buffer[1] !== 0x49 ||
1826
1802
  buffer[2] !== 0x46 ||
@@ -1828,10 +1804,10 @@ const isAnimatedWebP = (buffer) => {
1828
1804
  buffer[8] !== 0x57 ||
1829
1805
  buffer[9] !== 0x45 ||
1830
1806
  buffer[10] !== 0x42 ||
1831
- buffer[11] !== 0x50
1832
- ) {
1807
+ buffer[11] !== 0x50) {
1833
1808
  return false;
1834
- };
1809
+ }
1810
+ ;
1835
1811
  // Parse chunks starting after RIFF header (12 bytes)
1836
1812
  let offset = 12;
1837
1813
  while (offset < buffer.length - 8) {
@@ -1844,23 +1820,27 @@ const isAnimatedWebP = (buffer) => {
1844
1820
  const flags = buffer[flagsOffset];
1845
1821
  if (flags & 0x02) {
1846
1822
  return true;
1847
- };
1848
- };
1849
- } else if (chunkFourCC === 'ANIM' || chunkFourCC === 'ANMF') {
1823
+ }
1824
+ ;
1825
+ }
1826
+ ;
1827
+ }
1828
+ else if (chunkFourCC === 'ANIM' || chunkFourCC === 'ANMF') {
1850
1829
  // ANIM or ANMF chunks indicate animation
1851
1830
  return true;
1852
- };
1831
+ }
1832
+ ;
1853
1833
  // Move to next chunk (chunk size + 8 bytes header, padded to even)
1854
1834
  offset += 8 + chunkSize + (chunkSize % 2);
1855
- };
1835
+ }
1836
+ ;
1856
1837
  return false;
1857
1838
  };
1858
1839
  /**
1859
1840
  * Checks if a buffer is a WebP file
1860
1841
  */
1861
1842
  const isWebPBuffer = (buffer) => {
1862
- return (
1863
- buffer.length >= 12 &&
1843
+ return (buffer.length >= 12 &&
1864
1844
  buffer[0] === 0x52 &&
1865
1845
  buffer[1] === 0x49 &&
1866
1846
  buffer[2] === 0x46 &&
@@ -1868,8 +1848,7 @@ const isWebPBuffer = (buffer) => {
1868
1848
  buffer[8] === 0x57 &&
1869
1849
  buffer[9] === 0x45 &&
1870
1850
  buffer[10] === 0x42 &&
1871
- buffer[11] === 0x50
1872
- );
1851
+ buffer[11] === 0x50);
1873
1852
  };
1874
1853
  /**
1875
1854
  * Lia@Changes 30-01-26
@@ -1878,14 +1857,8 @@ const isWebPBuffer = (buffer) => {
1878
1857
  * A Biz Binary Node is added only for interactive messages
1879
1858
  * such as buttons or other supported interactive types.
1880
1859
  */
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
- };
1860
+ export const shouldIncludeBizBinaryNode = (message) => !!(message.buttonsMessage ||
1861
+ message.listMessage ||
1862
+ message.templateMessage ||
1863
+ (message.interactiveMessage &&
1864
+ message.interactiveMessage.nativeFlowMessage));