@crysnovax/baileys 2.5.5 → 2.6.0

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.
@@ -5,7 +5,7 @@ import { promises as fs } from 'fs';
5
5
  import { proto } from '../../WAProto/index.js';
6
6
  import { CALL_AUDIO_PREFIX, CALL_VIDEO_PREFIX, DONATE_URL, LIBRARY_NAME, MEDIA_KEYS, URL_REGEX, WA_DEFAULT_EPHEMERAL } from '../Defaults/index.js';
7
7
  import { AssociationType, ButtonHeaderType, ButtonType, CarouselCardType, ListType, ProtocolType, WAMessageStatus, WAProto } from '../Types/index.js';
8
- import { isPnUser, isLidUser, isJidGroup, isJidNewsletter, isJidStatusBroadcast, jidNormalizedUser } from '../WABinary/index.js';
8
+ import { isLidUser, isPnUser, isJidGroup, isJidNewsletter, isJidStatusBroadcast, jidNormalizedUser } from '../WABinary/index.js';
9
9
  import { sha256 } from './crypto.js';
10
10
  import { generateMessageIDV2, getKeyAuthor, unixTimestampSeconds } from './generics.js';
11
11
  import { downloadContentFromMessage, encryptedStream, generateThumbnail, getAudioDuration, getAudioWaveform, getImageProcessingLibrary, getRawMediaUploadData, getStream, toBuffer } from './messages-media.js';
@@ -36,7 +36,7 @@ const mediaAnnotation = [
36
36
  { x: 20.840980529785156, y: -47.80188751220703 }
37
37
  ],
38
38
  newsletter: {
39
- // crysnovax@Note 03-02-26 --- You can change jid, message id, and name via .env (⁠≧⁠▽⁠≦⁠)
39
+ // Lia@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 ||
@@ -124,54 +124,18 @@ export const prepareWAMessageMedia = async (message, options) => {
124
124
  }
125
125
  }
126
126
  const isNewsletter = !!options.jid && 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
- // crysnovax@Changes 06-02-26 --- Add few support for sending media to newsletter (⁠≧⁠▽⁠≦⁠)
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,43 +232,27 @@ 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: WAProto.Message.ProtocolMessage.Type.EPHEMERAL_SETTING,
289
- ephemeralExpiration
290
- }
291
- }
292
- }
293
- };
294
- return WAProto.Message.fromObject(content);
295
- };
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
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) {
299
252
  throw new Boom('"businessOwnerJid" is missing from the content', { statusCode: 400 });
300
253
  }
301
254
  const { imageMessage } = await prepareWAMessageMedia({ image: message.image || message.product.productImage }, options);
302
- // crysnovax@Changes 01-02-26 --- Add product message default value
255
+ // Lia@Changes 01-02-26 --- Add product message default value
303
256
  const { image, ...content } = message;
304
257
  content.product = {
305
258
  currencyCode: 'IDR',
@@ -311,11 +264,12 @@ const prepareProductMessage = async (message, options) => {
311
264
  return content;
312
265
  };
313
266
  /**
314
- * crysnovax@Note 30-01-26
267
+ * Lia@Note 30-01-26
315
268
  * ---
316
269
  * Credits: Work on ensuring stickerPackMessage fields are valid by @jlucaso1 (https://github.com/jlucaso1).
317
270
  * based on https://github.com/WhiskeySockets/Baileys/pull/1561
318
271
  */
272
+ // Lia@Changes 21-04-26 --- Enhanced prepareStickerPackMessage
319
273
  const prepareStickerPackMessage = async (message, options) => {
320
274
  const { cover, stickers = [], name = '📦 Sticker Pack', publisher = 'Website: web.crysnovax.link', description = '🏷️ crysnovax/bailey' } = message;
321
275
  if (stickers.length > 60) {
@@ -327,136 +281,124 @@ const prepareStickerPackMessage = async (message, options) => {
327
281
  if (!cover) {
328
282
  throw new Boom('Sticker pack must contain a cover', { statusCode: 400 });
329
283
  }
330
- // crysnovax@Changes 01-02-26 --- Add caching for sticker pack (similiar to prepareWAMessageMedia)
331
- const cacheableKey = Array.isArray(stickers) &&
332
- stickers.length &&
333
- !!options.mediaCache &&
334
- 'sticker:' + stickers
335
- .reduce((acc, x) => {
336
- const url = typeof x.data === 'object' &&
337
- 'url' in x.data &&
338
- !!x.data.url &&
339
- x.data.url;
340
- if (url) acc.push(url);
341
- return acc;
342
- }, [])
343
- .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
+ }
344
299
  if (cacheableKey) {
345
300
  const mediaBuff = await options.mediaCache.get(cacheableKey);
346
301
  if (mediaBuff) {
347
- options.logger?.debug({ cacheableKey }, 'got media cache hit');
302
+ logger?.debug({ cacheableKey }, 'got media cache hit');
348
303
  return proto.Message.StickerPackMessage.decode(mediaBuff);
349
304
  }
350
305
  }
351
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
+ }
352
313
  const stickerPackIdValue = generateMessageIDV2();
353
314
  const stickerData = {};
354
- const stickerPromises = stickers.map(async (sticker, i) => {
355
- const { stream } = await getStream(sticker.data);
356
- const buffer = await toBuffer(stream);
357
- let webpBuffer,
358
- isAnimated = false;
359
- const isWebP = isWebPBuffer(buffer);
360
- if (isWebP) {
361
- // Already WebP - preserve original to keep exif metadata and animation
362
- webpBuffer = buffer;
363
- isAnimated = isAnimatedWebP(buffer);
364
- }
365
- else if ('sharp' in lib && lib.sharp?.default) {
366
- // Convert to WebP, preserving metadata
367
- webpBuffer = await lib
368
- .sharp
369
- .default(buffer)
370
- .resize(512, 512, { fit: 'inside' })
371
- .webp({ quality: 80 })
372
- .toBuffer();
373
- // Non-WebP inputs converted to WebP are not animated
374
- isAnimated = false;
375
- }
376
- else if ('image' in lib && lib.image?.Transformer) {
377
- webpBuffer = await new lib
378
- .image
379
- .Transformer(buffer)
380
- .resize(512, 512)
381
- .webp(80);
382
- // Non-WebP inputs converted to WebP are not animated
383
- 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));
384
355
  }
385
- else {
386
- 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.');
387
- }
388
- if (webpBuffer.length > 1024 * 1024) {
389
- throw new Boom(`Sticker at index ${i} exceeds the 1MB size limit`, { statusCode: 400 });
390
- }
391
- const hash = sha256(webpBuffer).toString('base64').replace(/\//g, '-');
392
- const fileName = hash + '.webp';
393
- stickerData[fileName] = [new Uint8Array(webpBuffer), { level: 0 }];
394
- return {
395
- fileName,
396
- mimetype: 'image/webp',
397
- isAnimated,
398
- emojis: sticker.emojis || ['✨'],
399
- accessibilityLabel: sticker.accessibilityLabel || '‎'
400
- };
401
- });
402
- const stickerMetadata = await Promise.all(stickerPromises);
403
- // Process and add cover/tray icon to the ZIP
404
- const trayIconFileName = stickerPackIdValue + '.webp';
356
+ await Promise.all(promises);
357
+ }
358
+ const trayIconFileName = `${stickerPackIdValue}.webp`;
405
359
  const { stream: coverStream } = await getStream(cover);
406
360
  const coverBuffer = await toBuffer(coverStream);
407
361
  let coverWebpBuffer;
408
- const isCoverWebP = isWebPBuffer(coverBuffer);
409
- if (isCoverWebP) {
410
- // Already WebP - preserve original to keep exif metadata
362
+ if (isWebPBuffer(coverBuffer)) {
411
363
  coverWebpBuffer = coverBuffer;
412
364
  }
413
- else if ('sharp' in lib && lib.sharp?.default) {
414
- coverWebpBuffer = await lib
415
- .sharp
416
- .default(coverBuffer)
365
+ else if (hasSharp) {
366
+ coverWebpBuffer = await lib.sharp.default(coverBuffer)
417
367
  .resize(512, 512, { fit: 'inside' })
418
368
  .webp({ quality: 80 })
419
369
  .toBuffer();
420
370
  }
421
- else if ('image' in lib && lib.image?.Transformer) {
422
- coverWebpBuffer = await new lib
423
- .image
424
- .Transformer(coverBuffer)
371
+ else {
372
+ coverWebpBuffer = await new lib.image.Transformer(coverBuffer)
425
373
  .resize(512, 512)
426
374
  .webp(80);
427
375
  }
428
- else {
429
- 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.');
430
- }
431
- // Add cover to ZIP data
432
376
  stickerData[trayIconFileName] = [new Uint8Array(coverWebpBuffer), { level: 0 }];
433
377
  const zipBuffer = await new Promise((resolve, reject) => {
434
- zip(stickerData, (error, data) => {
435
- if (error) {
436
- reject(error);
437
- } else {
438
- resolve(Buffer.from(data));
439
- }
440
- });
378
+ zip(stickerData, (error, data) => error ? reject(error) : resolve(Buffer.from(data)));
441
379
  });
442
- const stickerPackSize = zipBuffer.length;
443
380
  const stickerPackUpload = await encryptedStream(zipBuffer, 'sticker-pack', {
444
- logger: options.logger,
381
+ logger,
445
382
  opts: options.options
446
383
  });
447
- const stickerPackUploadResult = await options.upload(stickerPackUpload.encFilePath, {
448
- fileEncSha256B64: stickerPackUpload.fileEncSha256.toString('base64'),
449
- mediaType: 'sticker-pack',
450
- timeoutMs: options.mediaUploadTimeoutMs
451
- });
452
- 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
+ }
453
395
  const obj = {
454
- name: name,
455
- publisher: publisher,
396
+ name,
397
+ publisher,
456
398
  stickerPackId: stickerPackIdValue,
457
399
  packDescription: description,
458
400
  stickerPackOrigin: proto.Message.StickerPackMessage.StickerPackOrigin.USER_CREATED,
459
- stickerPackSize: stickerPackSize,
401
+ stickerPackSize: zipBuffer.length,
460
402
  stickers: stickerMetadata,
461
403
  fileSha256: stickerPackUpload.fileSha256,
462
404
  fileEncSha256: stickerPackUpload.fileEncSha256,
@@ -464,31 +406,19 @@ const prepareStickerPackMessage = async (message, options) => {
464
406
  directPath: stickerPackUploadResult.directPath,
465
407
  fileLength: stickerPackUpload.fileLength,
466
408
  mediaKeyTimestamp: unixTimestampSeconds(),
467
- trayIconFileName: trayIconFileName
409
+ trayIconFileName
468
410
  };
469
411
  try {
470
- // Reuse the cover buffer we already processed for thumbnail generation
471
412
  let thumbnailBuffer;
472
- if ('sharp' in lib && lib.sharp?.default) {
473
- thumbnailBuffer = await lib
474
- .sharp
475
- .default(coverBuffer)
476
- .resize(252, 252)
477
- .jpeg()
478
- .toBuffer();
479
- }
480
- if ('image' in lib && lib.image?.Transformer) {
481
- thumbnailBuffer = await new lib
482
- .image
483
- .Transformer(coverBuffer)
484
- .resize(252, 252)
485
- .jpeg();
486
- }
487
- 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) {
488
420
  const jimpImage = await lib.jimp.Jimp.read(coverBuffer);
489
- thumbnailBuffer = await jimpImage
490
- .resize({ w: 252, h: 252 })
491
- .getBuffer('image/jpeg');
421
+ thumbnailBuffer = await jimpImage.resize({ w: 252, h: 252 }).getBuffer('image/jpeg');
492
422
  }
493
423
  else {
494
424
  throw new Error('No image processing library available for thumbnail generation');
@@ -497,16 +427,21 @@ const prepareStickerPackMessage = async (message, options) => {
497
427
  throw new Error('Failed to generate thumbnail buffer');
498
428
  }
499
429
  const thumbUpload = await encryptedStream(thumbnailBuffer, 'thumbnail-sticker-pack', {
500
- logger: options.logger,
430
+ logger,
501
431
  opts: options.options,
502
- mediaKey: stickerPackUpload.mediaKey // Use same mediaKey as the sticker pack ZIP
503
- });
504
- const thumbUploadResult = await options.upload(thumbUpload.encFilePath, {
505
- fileEncSha256B64: thumbUpload.fileEncSha256.toString('base64'),
506
- mediaType: 'thumbnail-sticker-pack',
507
- timeoutMs: options.mediaUploadTimeoutMs
432
+ mediaKey: stickerPackUpload.mediaKey
508
433
  });
509
- 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
+ }
510
445
  Object.assign(obj, {
511
446
  thumbnailDirectPath: thumbUploadResult.directPath,
512
447
  thumbnailSha256: thumbUpload.fileSha256,
@@ -517,27 +452,26 @@ const prepareStickerPackMessage = async (message, options) => {
517
452
  });
518
453
  }
519
454
  catch (error) {
520
- options.logger?.warn?.(`Thumbnail generation failed: ${error}`);
455
+ logger?.warn(`Thumbnail generation failed: ${error}`);
521
456
  }
522
- const content = obj;
523
457
  if (cacheableKey) {
524
- options.logger?.debug({ cacheableKey }, 'set cache');
525
- 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());
526
460
  }
527
- return WAProto.Message.StickerPackMessage.fromObject(content);
461
+ return WAProto.Message.StickerPackMessage.fromObject(obj);
528
462
  };
529
- // crysnovax@Changes 30-01-26 --- Add native flow button helper for interactive message
463
+ // Lia@Changes 30-01-26 --- Add native flow button helper for interactive message
530
464
  const prepareNativeFlowButtons = (message) => {
531
465
  const buttons = message.nativeFlow;
532
466
  const isButtonsFieldArray = Array.isArray(buttons);
533
467
  const correctedField = isButtonsFieldArray ? buttons : buttons.buttons;
534
468
  const messageParamsJson = {};
535
- // crysnovax@Changes 31-01-26 --- Add offer and options inside interactive message
469
+ // Lia@Changes 31-01-26 --- Add offer and options inside interactive message
536
470
  if (hasOptionalProperty(message, 'offerText') && !!message.offerText) {
537
471
  Object.assign(messageParamsJson, {
538
472
  limited_time_offer: {
539
473
  text: message.offerText || LIBRARY_NAME,
540
- url: message.offerUrl || DONATE_URL, // crysnovax@Note 02-02-26 --- Apologies if this feels cheeky, just a fallback
474
+ url: message.offerUrl || DONATE_URL, // Lia@Note 02-02-26 --- Apologies if this feels cheeky, just a fallback
541
475
  copy_code: message.offerCode,
542
476
  expiration_time: message.offerExpiration
543
477
  }
@@ -547,10 +481,7 @@ const prepareNativeFlowButtons = (message) => {
547
481
  Object.assign(messageParamsJson, {
548
482
  bottom_sheet: {
549
483
  in_thread_buttons_limit: 1,
550
- divider_indices: Array.from(
551
- { length: correctedField.length },
552
- (_, index) => index
553
- ),
484
+ divider_indices: Array.from({ length: correctedField.length }, (_, index) => index),
554
485
  list_title: message.optionTitle || '📄 Select Options',
555
486
  button_title: message.optionText
556
487
  }
@@ -602,7 +533,7 @@ const prepareNativeFlowButtons = (message) => {
602
533
  })
603
534
  };
604
535
  }
605
- // crysnovax@Changes 12-03-26 --- Add "single_select" shortcut \⁠(⁠°⁠o⁠°⁠)⁠/
536
+ // Lia@Changes 12-03-26 --- Add "single_select" shortcut \⁠(⁠°⁠o⁠°⁠)⁠/
606
537
  else if (hasOptionalProperty(button, 'sections') && !!button.sections) {
607
538
  return {
608
539
  name: 'single_select',
@@ -615,10 +546,23 @@ const prepareNativeFlowButtons = (message) => {
615
546
  }
616
547
  return button;
617
548
  }),
618
- messageParamsJson: JSON.stringify(messageParamsJson),
619
- messageVersion: 1
549
+ messageParamsJson: JSON.stringify(messageParamsJson)
620
550
  };
621
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
+ };
622
566
  /**
623
567
  * Generate forwarded message content like WA does
624
568
  * @param message the message to forward
@@ -641,24 +585,12 @@ export const generateForwardMessageContent = (message, forceForward) => {
641
585
  key = 'extendedTextMessage';
642
586
  }
643
587
  const key_ = content?.[key];
644
- const contextInfo = {};
645
588
  if (score > 0) {
646
- contextInfo.forwardingScore = score;
647
- contextInfo.isForwarded = true;
648
- }
649
- // when forwarding a newsletter/channel message, add the newsletter context
650
- // so the server knows where to find the original media
651
- const remoteJid = message.key?.remoteJid;
652
- if (remoteJid && isJidNewsletter(remoteJid)) {
653
- contextInfo.forwardedNewsletterMessageInfo = {
654
- newsletterJid: remoteJid,
655
- serverMessageId: message.key?.server_id ? parseInt(message.key.server_id) : null,
656
- newsletterName: null
657
- };
658
- // strip messageContextInfo (contains messageSecret etc.) as WA Web does
659
- delete content.messageContextInfo;
589
+ key_.contextInfo = { forwardingScore: score, isForwarded: true };
590
+ }
591
+ else {
592
+ key_.contextInfo = {};
660
593
  }
661
- key_.contextInfo = contextInfo;
662
594
  return content;
663
595
  };
664
596
  export const hasNonNullishProperty = (message, key) => {
@@ -673,7 +605,7 @@ export const hasOptionalProperty = (obj, key) => {
673
605
  key in obj &&
674
606
  obj[key] != null;
675
607
  };
676
- // crysnovax@Changes 06-02-26 --- Validate album message media to avoid bug 👀
608
+ // Lia@Changes 06-02-26 --- Validate album message media to avoid bug 👀
677
609
  export const hasValidAlbumMedia = (message) => {
678
610
  return !!(message.imageMessage ||
679
611
  message.videoMessage);
@@ -685,7 +617,7 @@ export const hasValidInteractiveHeader = (message) => {
685
617
  message.productMessage ||
686
618
  message.locationMessage);
687
619
  };
688
- // crysnovax@Changes 30-01-26 --- Validate carousel cards header to avoid bug 👀
620
+ // Lia@Changes 30-01-26 --- Validate carousel cards header to avoid bug 👀
689
621
  export const hasValidCarouselHeader = (message) => {
690
622
  return !!(message.imageMessage ||
691
623
  message.videoMessage ||
@@ -694,12 +626,12 @@ export const hasValidCarouselHeader = (message) => {
694
626
  export const generateWAMessageContent = async (message, options) => {
695
627
  var _a, _b;
696
628
  let m = {};
697
- // crysnovax@Changes 30-01-26 --- Add "raw" boolean to send raw messages directly via generateWAMessage()
629
+ // Lia@Changes 30-01-26 --- Add "raw" boolean to send raw messages directly via generateWAMessage()
698
630
  if (hasNonNullishProperty(message, 'raw')) {
699
631
  delete message.raw;
700
632
  return message;
701
633
  }
702
- // crysnovax@Changes 09-04-26 --- Add support for code block and table with richResponseMessage
634
+ // Lia@Changes 09-04-26 --- Add support for code block and table with richResponseMessage
703
635
  else if (hasNonNullishProperty(message, 'code') ||
704
636
  hasNonNullishProperty(message, 'links') ||
705
637
  hasNonNullishProperty(message, 'table') ||
@@ -920,18 +852,18 @@ export const generateWAMessageContent = async (message, options) => {
920
852
  m.pollCreationMessageV2 = pollCreationMessage;
921
853
  }
922
854
  else {
923
- // crysnovax@Changes 08-02-26 --- Add quiz message support
855
+ // Lia@Changes 08-02-26 --- Add quiz message support
924
856
  if (message.poll.pollType === 1) {
925
857
  if (!message.poll.correctAnswer) {
926
858
  throw new Boom('No "correctAnswer" provided for quiz', { statusCode: 400 });
927
859
  }
928
860
  m.pollCreationMessageV5 = {
929
- // crysnovax@Note 08-02-26 --- quiz for newsletter only
861
+ // Lia@Note 08-02-26 --- quiz for newsletter only
930
862
  ...pollCreationMessage,
931
863
  correctAnswer: {
932
864
  optionName: message.poll.correctAnswer.toString()
933
865
  },
934
- pollType: message.poll.pollType,
866
+ pollType: 1,
935
867
  selectableOptionsCount: 1
936
868
  };
937
869
  }
@@ -949,7 +881,7 @@ export const generateWAMessageContent = async (message, options) => {
949
881
  messageSecret: message.poll.messageSecret || randomBytes(32)
950
882
  };
951
883
  }
952
- // crysnovax@Changes 08-02-26 --- Add poll result snapshot message
884
+ // Lia@Changes 08-02-26 --- Add poll result snapshot message
953
885
  else if (hasNonNullishProperty(message, 'pollResult')) {
954
886
  const pollResultSnapshotMessage = {
955
887
  name: message.pollResult.name,
@@ -967,7 +899,7 @@ export const generateWAMessageContent = async (message, options) => {
967
899
  m.pollResultSnapshotMessage = pollResultSnapshotMessage;
968
900
  }
969
901
  }
970
- // crysnovax@Changes 08-02-26 --- Add poll update message
902
+ // Lia@Changes 08-02-26 --- Add poll update message
971
903
  else if (hasNonNullishProperty(message, 'pollUpdate')) {
972
904
  if (!message.pollUpdate.key) {
973
905
  throw new Boom('Message key is required', { statusCode: 400 });
@@ -982,33 +914,14 @@ export const generateWAMessageContent = async (message, options) => {
982
914
  vote: message.pollUpdate.vote
983
915
  };
984
916
  }
985
- else if (hasNonNullishProperty(message, 'sharePhoneNumber')) {
986
- m.protocolMessage = {
987
- type: proto.Message.ProtocolMessage.Type.SHARE_PHONE_NUMBER
988
- };
989
- }
990
- else if (hasNonNullishProperty(message, 'requestPhoneNumber')) {
991
- m.requestPhoneNumberMessage = {};
992
- }
993
- else if (hasNonNullishProperty(message, 'limitSharing')) {
994
- m.protocolMessage = {
995
- type: proto.Message.ProtocolMessage.Type.LIMIT_SHARING,
996
- limitSharing: {
997
- sharingLimited: message.limitSharing === true,
998
- trigger: 1,
999
- limitSharingSettingTimestamp: Date.now(),
1000
- initiatedByMe: true
1001
- }
1002
- };
1003
- }
1004
- // crysnovax@Changes 01-02-26 --- Add payment invite message
917
+ // Lia@Changes 01-02-26 --- Add payment invite message
1005
918
  else if (hasNonNullishProperty(message, 'paymentInviteServiceType')) {
1006
919
  m.paymentInviteMessage = {
1007
920
  expiryTimestamp: Date.now(),
1008
921
  serviceType: message.paymentInviteServiceType
1009
922
  };
1010
923
  }
1011
- // crysnovax@Changes 01-02-26 --- Add order message
924
+ // Lia@Changes 01-02-26 --- Add order message
1012
925
  else if (hasNonNullishProperty(message, 'orderText')) {
1013
926
  if (!Buffer.isBuffer(message.thumbnail)) {
1014
927
  throw new Boom('Must provide thumbnail buffer in order message', { statusCode: 400 });
@@ -1027,7 +940,7 @@ export const generateWAMessageContent = async (message, options) => {
1027
940
  };
1028
941
  delete m.orderMessage.orderText;
1029
942
  }
1030
- // crysnovax@Changes 31-01-26 --- Add support for album messages
943
+ // Lia@Changes 31-01-26 --- Add support for album messages
1031
944
  else if (hasNonNullishProperty(message, 'album')) {
1032
945
  if (!Array.isArray(message.album)) {
1033
946
  throw new Boom('Invalid album type. Expected an array.', { statusCode: 400 });
@@ -1037,11 +950,13 @@ export const generateWAMessageContent = async (message, options) => {
1037
950
  if (message.album[i].video)
1038
951
  videoCount++;
1039
952
  }
953
+ ;
1040
954
  let imageCount = 0;
1041
955
  for (let i = 0; i < message.album.length; i++) {
1042
956
  if (message.album[i].image)
1043
957
  imageCount++;
1044
958
  }
959
+ ;
1045
960
  if ((videoCount + imageCount) < 2) {
1046
961
  throw new Boom('Minimum provide 2 media to upload album message', { statusCode: 400 });
1047
962
  }
@@ -1050,23 +965,42 @@ export const generateWAMessageContent = async (message, options) => {
1050
965
  expectedVideoCount: videoCount
1051
966
  };
1052
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
+ }
1053
987
  else {
1054
988
  m = await prepareWAMessageMedia(message, options);
1055
989
  }
1056
- // crysnovax@Changes 30-01-26 --- Add interactive messages (buttonsMessage, listMessage, interactiveMessage, templateMessage, and carouselMessage)
990
+ // Lia@Changes 30-01-26 --- Add interactive messages (buttonsMessage, listMessage, interactiveMessage, templateMessage, and carouselMessage)
1057
991
  if (hasNonNullishProperty(message, 'buttons')) {
1058
992
  const buttonsMessage = {
1059
993
  buttons: message.buttons.map(button => {
1060
- // crysnovax@Changes 12-03-26 --- Add "single_select" shortcut!
994
+ // Lia@Changes 12-03-26 --- Add "single_select" shortcut!
1061
995
  const buttonText = button.text || button.buttonText;
1062
996
  if (hasOptionalProperty(button, 'sections')) {
1063
997
  return {
1064
998
  nativeFlowInfo: {
1065
- name: 'single_select',
1066
- paramsJson: JSON.stringify({
1067
- title: buttonText,
1068
- sections: button.sections
1069
- })
999
+ name: 'single_select',
1000
+ paramsJson: JSON.stringify({
1001
+ title: buttonText,
1002
+ sections: button.sections
1003
+ })
1070
1004
  },
1071
1005
  type: ButtonType.NATIVE_FLOW
1072
1006
  };
@@ -1074,8 +1008,8 @@ export const generateWAMessageContent = async (message, options) => {
1074
1008
  else if (hasOptionalProperty(button, 'name')) {
1075
1009
  return {
1076
1010
  nativeFlowInfo: {
1077
- name: button.name,
1078
- paramsJson: button.paramsJson
1011
+ name: button.name,
1012
+ paramsJson: button.paramsJson
1079
1013
  },
1080
1014
  type: ButtonType.NATIVE_FLOW
1081
1015
  };
@@ -1115,7 +1049,7 @@ export const generateWAMessageContent = async (message, options) => {
1115
1049
  };
1116
1050
  m = { listMessage };
1117
1051
  }
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 ಥ⁠‿⁠ಥ)
1052
+ // 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 ಥ⁠‿⁠ಥ)
1119
1053
  else if (hasNonNullishProperty(message, 'templateButtons')) {
1120
1054
  const hydratedTemplate = {
1121
1055
  hydratedButtons: message.templateButtons.map((button, i) => {
@@ -1158,19 +1092,20 @@ export const generateWAMessageContent = async (message, options) => {
1158
1092
  if (hasOptionalProperty(message, 'caption')) {
1159
1093
  hydratedTemplate.hydratedTitleText = message.title;
1160
1094
  hydratedTemplate.hydratedContentText = message.caption;
1161
- };
1095
+ }
1096
+ ;
1162
1097
  Object.assign(hydratedTemplate, m);
1163
1098
  }
1164
1099
  if (hasOptionalProperty(message, 'footer')) {
1165
1100
  hydratedTemplate.hydratedFooterText = message.footer;
1166
1101
  }
1167
- hydratedTemplate.templateId = message.id || 'template-' + Date.now(); // crysnovax@Note 04-02-26 --- Minimal templateId to satisfy WhatsApp (⁠ ⁠ꈍ⁠ᴗ⁠ꈍ⁠)
1102
+ hydratedTemplate.templateId = message.id || 'template-' + Date.now(); // Lia@Note 04-02-26 --- Minimal templateId to satisfy WhatsApp (⁠ ⁠ꈍ⁠ᴗ⁠ꈍ⁠)
1168
1103
  m = {
1169
1104
  templateMessage: {
1170
1105
  hydratedFourRowTemplate: hydratedTemplate,
1171
1106
  hydratedTemplate: hydratedTemplate
1172
1107
  }
1173
- }
1108
+ };
1174
1109
  }
1175
1110
  else if (hasNonNullishProperty(message, 'nativeFlow')) {
1176
1111
  const interactiveMessage = {
@@ -1195,9 +1130,9 @@ export const generateWAMessageContent = async (message, options) => {
1195
1130
  }
1196
1131
  else {
1197
1132
  if (hasOptionalProperty(message, 'caption')) {
1198
- const isValidHeader = hasValidInteractiveHeader(m)
1133
+ const isValidHeader = hasValidInteractiveHeader(m);
1199
1134
  if (!isValidHeader) {
1200
- 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 });
1201
1136
  }
1202
1137
  interactiveMessage.header = {
1203
1138
  title: message.title || '',
@@ -1212,11 +1147,11 @@ export const generateWAMessageContent = async (message, options) => {
1212
1147
  Object.assign(interactiveMessage.header, m);
1213
1148
  }
1214
1149
  if (hasOptionalProperty(message, 'audioFooter')) {
1215
- const parseFooter = await prepareWAMessageMedia({
1150
+ const { audioMessage } = await prepareWAMessageMedia({
1216
1151
  audio: message.audioFooter
1217
1152
  }, options);
1218
1153
  interactiveMessage.footer = {
1219
- audioMessage: parseFooter.audioMessage,
1154
+ audioMessage,
1220
1155
  hasMediaAttachment: true
1221
1156
  };
1222
1157
  }
@@ -1228,13 +1163,13 @@ export const generateWAMessageContent = async (message, options) => {
1228
1163
  else if (hasNonNullishProperty(message, 'cards')) {
1229
1164
  const interactiveMessage = {
1230
1165
  carouselMessage: {
1231
- cards: await Promise.all(message.cards.map(async card => {
1166
+ cards: await Promise.all(message.cards.map(async (card) => {
1232
1167
  let carouselHeader = {};
1233
1168
  if (hasNonNullishProperty(card, 'product')) {
1234
1169
  carouselHeader.productMessage = await prepareProductMessage(card, options);
1235
1170
  }
1236
1171
  else {
1237
- carouselHeader = await prepareWAMessageMedia(card, options).catch(() => ({ }));
1172
+ carouselHeader = await prepareWAMessageMedia(card, options).catch(() => ({}));
1238
1173
  }
1239
1174
  const isValidHeader = hasValidCarouselHeader(carouselHeader);
1240
1175
  if (!isValidHeader) {
@@ -1272,7 +1207,7 @@ export const generateWAMessageContent = async (message, options) => {
1272
1207
  else if (hasOptionalProperty(card, 'footer')) {
1273
1208
  carouselCard.footer = { text: card.footer };
1274
1209
  }
1275
- return carouselCard
1210
+ return carouselCard;
1276
1211
  })),
1277
1212
  carouselCardType: CarouselCardType.UNKNOWN,
1278
1213
  messageVersion: 1
@@ -1286,13 +1221,13 @@ export const generateWAMessageContent = async (message, options) => {
1286
1221
  }
1287
1222
  m = { interactiveMessage };
1288
1223
  }
1289
- // crysnovax@Changes 01-02-26 --- Add request payment message
1224
+ // Lia@Changes 01-02-26 --- Add request payment message
1290
1225
  else if (hasNonNullishProperty(message, 'requestPaymentFrom')) {
1291
1226
  const requestPaymentMessage = {
1292
1227
  amount: {
1293
- currencyCode: 'IDR',
1294
- offset: 1000,
1295
- value: 1000
1228
+ currencyCode: 'IDR',
1229
+ offset: 1000,
1230
+ value: 1000
1296
1231
  },
1297
1232
  amount1000: 1000,
1298
1233
  currencyCodeIso4217: 'IDR',
@@ -1310,7 +1245,7 @@ export const generateWAMessageContent = async (message, options) => {
1310
1245
  }
1311
1246
  m = { requestPaymentMessage };
1312
1247
  }
1313
- // crysnovax@Changes 01-02-26 --- Add invoice message
1248
+ // Lia@Changes 01-02-26 --- Add invoice message
1314
1249
  else if (hasNonNullishProperty(message, 'invoiceNote')) {
1315
1250
  const attachment = m.imageMessage || m.documentMessage;
1316
1251
  const type = Object.keys(m)[0].replace('Message', '').toUpperCase();
@@ -1336,7 +1271,7 @@ export const generateWAMessageContent = async (message, options) => {
1336
1271
  }
1337
1272
  m = { invoiceMessage };
1338
1273
  }
1339
- // crysnovax@Changes 31-01-26 --- Add direct externalAdReply access (no need to create contextInfo first)
1274
+ // Lia@Changes 31-01-26 --- Add direct externalAdReply access (no need to create contextInfo first)
1340
1275
  if (hasOptionalProperty(message, 'externalAdReply') && !!message.externalAdReply) {
1341
1276
  const messageType = Object.keys(m)[0];
1342
1277
  const key = m[messageType];
@@ -1345,7 +1280,7 @@ export const generateWAMessageContent = async (message, options) => {
1345
1280
  throw new Boom('Thumbnail must in buffer type', { statusCode: 400 });
1346
1281
  }
1347
1282
  if (!content.url || typeof content.url !== 'string') {
1348
- content.url = DONATE_URL; // crysnovax@Note 02-02-26 --- Apologies if this feels cheeky, just a fallback
1283
+ content.url = DONATE_URL; // Lia@Note 02-02-26 --- Apologies if this feels cheeky, just a fallback
1349
1284
  }
1350
1285
  const externalAdReply = {
1351
1286
  ...content,
@@ -1398,7 +1333,7 @@ export const generateWAMessageContent = async (message, options) => {
1398
1333
  key.contextInfo = message.contextInfo;
1399
1334
  }
1400
1335
  }
1401
- // crysnovax@Changes 31-01-26 --- Add "groupStatus" boolean to set contextInfo.isGroupStatus and wrap message into groupStatusMessageV2
1336
+ // Lia@Changes 31-01-26 --- Add "groupStatus" boolean to set contextInfo.isGroupStatus and wrap message into groupStatusMessageV2
1402
1337
  if (hasOptionalProperty(message, 'groupStatus') && !!message.groupStatus) {
1403
1338
  const messageType = Object.keys(m)[0];
1404
1339
  const key = m[messageType];
@@ -1408,12 +1343,12 @@ export const generateWAMessageContent = async (message, options) => {
1408
1343
  else if (key) {
1409
1344
  key.contextInfo = {
1410
1345
  isGroupStatus: message.groupStatus
1411
- }
1346
+ };
1412
1347
  }
1413
1348
  m = { groupStatusMessageV2: { message: m } };
1414
1349
  delete message.groupStatus;
1415
1350
  }
1416
- // crysnovax@Changes 06-05-26 --- Add "spoiler" boolean to set contextInfo.isSpoiler and wrap message into spoilerMessage
1351
+ // Lia@Changes 06-05-26 --- Add "spoiler" boolean to set contextInfo.isSpoiler and wrap message into spoilerMessage
1417
1352
  if (hasOptionalProperty(message, 'spoiler') && !!message.spoiler) {
1418
1353
  const messageType = Object.keys(m)[0];
1419
1354
  const key = m[messageType];
@@ -1428,33 +1363,37 @@ export const generateWAMessageContent = async (message, options) => {
1428
1363
  m = { spoilerMessage: { message: m } };
1429
1364
  delete message.spoiler;
1430
1365
  }
1431
- // crysnovax@Changes 02-02-26 --- Add "interactiveAsTemplate" boolean to wrap interactiveMessage into templateMessage
1366
+ // Lia@Changes 02-02-26 --- Add "interactiveAsTemplate" boolean to wrap interactiveMessage into templateMessage
1432
1367
  else if (hasOptionalProperty(message, 'interactiveAsTemplate') && !!message.interactiveAsTemplate) {
1433
1368
  if (!m.interactiveMessage) {
1434
- throw new Boom('Invalid message type for template', { statusCode: 400 }); // crysnovax@Note 02-02-26 --- To avoid bug 👀
1369
+ throw new Boom('Invalid message type for template', { statusCode: 400 }); // Lia@Note 02-02-26 --- To avoid bug 👀
1435
1370
  }
1436
1371
  m = {
1437
1372
  templateMessage: {
1438
1373
  interactiveMessageTemplate: m.interactiveMessage,
1439
- templateId: message.id || 'template-' + Date.now() // crysnovax@Note 04-02-26 --- Minimal templateId to satisfy WhatsApp (⁠ ⁠ꈍ⁠ᴗ⁠ꈍ⁠)
1374
+ templateId: message.id || 'template-' + Date.now() // Lia@Note 04-02-26 --- Minimal templateId to satisfy WhatsApp (⁠ ⁠ꈍ⁠ᴗ⁠ꈍ⁠)
1440
1375
  }
1441
1376
  };
1442
1377
  delete message.interactiveAsTemplate;
1443
1378
  }
1444
- // crysnovax@Changes 30-01-26 --- Add "ephemeral" boolean to wrap message into ephemeralMessage like "viewOnce"
1379
+ // Lia@Changes 30-01-26 --- Add "ephemeral" boolean to wrap message into ephemeralMessage like "viewOnce"
1445
1380
  if (hasOptionalProperty(message, 'ephemeral') && !!message.ephemeral) {
1446
1381
  m = { ephemeralMessage: { message: m } };
1447
1382
  delete message.ephemeral;
1448
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
+ }
1449
1388
  else if (hasOptionalProperty(message, 'viewOnce') && !!message.viewOnce) {
1450
1389
  m = { viewOnceMessage: { message: m } };
1451
1390
  }
1452
- // crysnovax@Changes 03-02-26 --- Add "viewOnceV2" boolean to wrap message into viewOnceMessageV2 like "viewOnce"
1391
+ // Lia@Changes 03-02-26 --- Add "viewOnceV2" boolean to wrap message into viewOnceMessageV2 like "viewOnce"
1453
1392
  else if (hasOptionalProperty(message, 'viewOnceV2') && !!message.viewOnceV2) {
1454
1393
  m = { viewOnceMessageV2: { message: m } };
1455
1394
  delete message.viewOnceV2;
1456
1395
  }
1457
- // crysnovax@Changes 03-02-26 --- Add "viewOnceV2Extension" boolean to wrap message into viewOnceMessageV2Extension like "viewOnce"
1396
+ // Lia@Changes 03-02-26 --- Add "viewOnceV2Extension" boolean to wrap message into viewOnceMessageV2Extension like "viewOnce"
1458
1397
  else if (hasOptionalProperty(message, 'viewOnceV2Extension') && !!message.viewOnceV2Extension) {
1459
1398
  m = { viewOnceMessageV2Extension: { message: m } };
1460
1399
  delete message.viewOnceV2Extension;
@@ -1467,7 +1406,7 @@ export const generateWAMessageContent = async (message, options) => {
1467
1406
  timestampMs: Date.now(),
1468
1407
  type: WAProto.Message.ProtocolMessage.Type.MESSAGE_EDIT
1469
1408
  }
1470
- }
1409
+ };
1471
1410
  }
1472
1411
  if (shouldIncludeReportingToken(m)) {
1473
1412
  m.messageContextInfo = m.messageContextInfo || {};
@@ -1483,8 +1422,8 @@ export const generateWAMessageFromContent = (jid, message, options) => {
1483
1422
  if (!options.timestamp) {
1484
1423
  options.timestamp = new Date();
1485
1424
  }
1486
- const messageContextInfo = message.messageContextInfo
1487
1425
  const innerMessage = normalizeMessageContent(message);
1426
+ const messageContextInfo = message.messageContextInfo;
1488
1427
  const key = getContentType(innerMessage);
1489
1428
  const timestamp = unixTimestampSeconds(options.timestamp);
1490
1429
  const isNewsletter = isJidNewsletter(jid);
@@ -1531,13 +1470,13 @@ export const generateWAMessageFromContent = (jid, message, options) => {
1531
1470
  //ephemeralSettingTimestamp: options.ephemeralOptions.eph_setting_ts?.toString()
1532
1471
  };
1533
1472
  }
1534
- // crysnovax@Changes 30-01-26 --- Add deviceListMetadata inside messageContextInfo for private chat
1473
+ // Lia@Changes 30-01-26 --- Add deviceListMetadata inside messageContextInfo for private chat
1535
1474
  if (messageContextInfo?.messageSecret && (isPnUser(jid) || isLidUser(jid))) {
1536
1475
  messageContextInfo.deviceListMetadata = {
1537
1476
  recipientKeyHash: randomBytes(10),
1538
1477
  recipientTimestamp: unixTimestampSeconds()
1539
1478
  };
1540
- messageContextInfo.deviceListMetadataVersion = 2
1479
+ messageContextInfo.deviceListMetadataVersion = 2;
1541
1480
  }
1542
1481
  message = WAProto.Message.create(message);
1543
1482
  const messageJSON = {
@@ -1590,10 +1529,9 @@ export const normalizeMessageContent = (content) => {
1590
1529
  content = inner.message;
1591
1530
  }
1592
1531
  return content;
1593
- // crysnovax@Changes 03-02-26 --- Add all futureProofMessage into getFutureProofMessage()
1532
+ // Lia@Changes 03-02-26 --- Add all futureProofMessage into getFutureProofMessage()
1594
1533
  function getFutureProofMessage(message) {
1595
- return (
1596
- message?.associatedChildMessage ||
1534
+ return (message?.associatedChildMessage ||
1597
1535
  message?.botForwardedMessage ||
1598
1536
  message?.botInvokeMessage ||
1599
1537
  message?.botTaskMessage ||
@@ -1619,8 +1557,7 @@ export const normalizeMessageContent = (content) => {
1619
1557
  message?.statusMentionMessage ||
1620
1558
  message?.viewOnceMessage ||
1621
1559
  message?.viewOnceMessageV2 ||
1622
- message?.viewOnceMessageV2Extension
1623
- );
1560
+ message?.viewOnceMessageV2Extension);
1624
1561
  }
1625
1562
  };
1626
1563
  /**
@@ -1859,8 +1796,7 @@ export const assertMediaContent = (content) => {
1859
1796
  */
1860
1797
  const isAnimatedWebP = (buffer) => {
1861
1798
  // WebP must start with RIFF....WEBP
1862
- if (
1863
- buffer.length < 12 ||
1799
+ if (buffer.length < 12 ||
1864
1800
  buffer[0] !== 0x52 ||
1865
1801
  buffer[1] !== 0x49 ||
1866
1802
  buffer[2] !== 0x46 ||
@@ -1868,10 +1804,10 @@ const isAnimatedWebP = (buffer) => {
1868
1804
  buffer[8] !== 0x57 ||
1869
1805
  buffer[9] !== 0x45 ||
1870
1806
  buffer[10] !== 0x42 ||
1871
- buffer[11] !== 0x50
1872
- ) {
1807
+ buffer[11] !== 0x50) {
1873
1808
  return false;
1874
- };
1809
+ }
1810
+ ;
1875
1811
  // Parse chunks starting after RIFF header (12 bytes)
1876
1812
  let offset = 12;
1877
1813
  while (offset < buffer.length - 8) {
@@ -1884,23 +1820,27 @@ const isAnimatedWebP = (buffer) => {
1884
1820
  const flags = buffer[flagsOffset];
1885
1821
  if (flags & 0x02) {
1886
1822
  return true;
1887
- };
1888
- };
1889
- } else if (chunkFourCC === 'ANIM' || chunkFourCC === 'ANMF') {
1823
+ }
1824
+ ;
1825
+ }
1826
+ ;
1827
+ }
1828
+ else if (chunkFourCC === 'ANIM' || chunkFourCC === 'ANMF') {
1890
1829
  // ANIM or ANMF chunks indicate animation
1891
1830
  return true;
1892
- };
1831
+ }
1832
+ ;
1893
1833
  // Move to next chunk (chunk size + 8 bytes header, padded to even)
1894
1834
  offset += 8 + chunkSize + (chunkSize % 2);
1895
- };
1835
+ }
1836
+ ;
1896
1837
  return false;
1897
1838
  };
1898
1839
  /**
1899
1840
  * Checks if a buffer is a WebP file
1900
1841
  */
1901
1842
  const isWebPBuffer = (buffer) => {
1902
- return (
1903
- buffer.length >= 12 &&
1843
+ return (buffer.length >= 12 &&
1904
1844
  buffer[0] === 0x52 &&
1905
1845
  buffer[1] === 0x49 &&
1906
1846
  buffer[2] === 0x46 &&
@@ -1908,11 +1848,10 @@ const isWebPBuffer = (buffer) => {
1908
1848
  buffer[8] === 0x57 &&
1909
1849
  buffer[9] === 0x45 &&
1910
1850
  buffer[10] === 0x42 &&
1911
- buffer[11] === 0x50
1912
- );
1851
+ buffer[11] === 0x50);
1913
1852
  };
1914
1853
  /**
1915
- * crysnovax@Changes 30-01-26
1854
+ * Lia@Changes 30-01-26
1916
1855
  * ---
1917
1856
  * Determines whether a message should include a Biz Binary Node.
1918
1857
  * A Biz Binary Node is added only for interactive messages