@itsliaaa/baileys 0.1.29 → 0.1.31

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.
@@ -12,6 +12,7 @@ import { generateMessageIDV2, getKeyAuthor, unixTimestampSeconds } from './gener
12
12
  import { downloadContentFromMessage, encryptedStream, generateThumbnail, getAudioDuration, getAudioWaveform, getImageProcessingLibrary, getRawMediaUploadData, getStream, toBuffer } from './messages-media.js';
13
13
  import { prepareRichResponseMessage } from './rich-message-utils.js';
14
14
  import { shouldIncludeReportingToken } from './reporting-utils.js';
15
+ const CONCURRENCY_LIMIT = 10;
15
16
  const MIMETYPE_MAP = {
16
17
  image: 'image/jpeg',
17
18
  video: 'video/mp4',
@@ -82,31 +83,24 @@ const assertColor = async (color) => {
82
83
  return assertedColor;
83
84
  }
84
85
  };
86
+ // Lia@Changes 21-04-26 --- Refactor prepareWAMessageMedia function
85
87
  export const prepareWAMessageMedia = async (message, options) => {
86
88
  const logger = options.logger;
87
- let mediaType;
88
- for (const key of MEDIA_KEYS) {
89
- if (key in message) {
90
- mediaType = key;
91
- }
92
- }
89
+ const mediaType = MEDIA_KEYS.find(key => key in message);
93
90
  if (!mediaType) {
94
91
  throw new Boom('Invalid media type', { statusCode: 400 });
95
92
  }
96
- const uploadData = {
97
- ...message,
98
- media: message[mediaType]
99
- };
93
+ const uploadData = { ...message };
94
+ const mediaPayload = uploadData[mediaType];
95
+ delete uploadData[mediaType];
100
96
  if (uploadData.image || uploadData.video) {
101
97
  uploadData.annotations = mediaAnnotation;
102
98
  }
103
- delete uploadData[mediaType];
104
- // check if cacheable + generate cache key
105
- const cacheableKey = typeof uploadData.media === 'object' &&
106
- 'url' in uploadData.media &&
107
- !!uploadData.media.url &&
99
+ const cacheableKey = typeof mediaPayload === 'object' &&
100
+ 'url' in mediaPayload &&
101
+ !!mediaPayload.url &&
108
102
  !!options.mediaCache &&
109
- mediaType + ':' + uploadData.media.url;
103
+ `${mediaType}:${mediaPayload.url}`;
110
104
  if (mediaType === 'document' && !uploadData.fileName) {
111
105
  uploadData.fileName = LIBRARY_NAME;
112
106
  }
@@ -119,7 +113,7 @@ export const prepareWAMessageMedia = async (message, options) => {
119
113
  logger?.debug({ cacheableKey }, 'got media cache hit');
120
114
  const obj = proto.Message.decode(mediaBuff);
121
115
  const key = `${mediaType}Message`;
122
- Object.assign(obj[key], { ...uploadData, media: undefined });
116
+ Object.assign(obj[key], uploadData);
123
117
  return obj;
124
118
  }
125
119
  }
@@ -129,142 +123,118 @@ export const prepareWAMessageMedia = async (message, options) => {
129
123
  const requiresWaveformProcessing = mediaType === 'audio' && uploadData.ptt === true && typeof uploadData.waveform === 'undefined';
130
124
  const requiresAudioBackground = options.backgroundColor && mediaType === 'audio' && uploadData.ptt === true;
131
125
  const requiresOriginalForSomeProcessing = requiresDurationComputation || requiresThumbnailComputation || requiresWaveformProcessing;
126
+ let mediaUrl,
127
+ directPath,
128
+ thumbnailDirectPath,
129
+ thumbnailSha256,
130
+ fileSha256,
131
+ fileLength,
132
+ mediaKey,
133
+ fileEncSha256;
132
134
  // Lia@Changes 06-02-26 --- Add few support for sending media to newsletter (⁠≧⁠▽⁠≦⁠)
133
135
  if (isNewsletter) {
134
136
  logger?.info({ key: cacheableKey }, 'Preparing raw media for newsletter');
135
- const { filePath, fileSha256, fileLength } = await getRawMediaUploadData(uploadData.media, options.mediaTypeOverride || mediaType, logger);
137
+ const rawData = await getRawMediaUploadData(mediaPayload, options.mediaTypeOverride || mediaType, logger);
138
+ fileSha256 = rawData.fileSha256;
139
+ fileLength = rawData.fileLength;
140
+ const filePath = rawData.filePath;
136
141
  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
- })(),
142
+ const [uploadResult] = await Promise.all([
143
+ options.upload(filePath, {
144
+ fileEncSha256B64: fileSha256B64,
145
+ mediaType,
146
+ timeoutMs: options.mediaUploadTimeoutMs,
147
+ newsletter: true
148
+ }),
148
149
  (async () => {
149
150
  try {
150
151
  if (requiresThumbnailComputation) {
151
152
  const { thumbnail } = await generateThumbnail(filePath, mediaType, options);
152
153
  uploadData.jpegThumbnail = thumbnail;
153
- logger?.debug('generated thumbnail');
154
154
  }
155
155
  if (requiresDurationComputation) {
156
156
  uploadData.seconds = await getAudioDuration(filePath);
157
- logger?.debug('computed audio duration');
158
157
  }
159
158
  }
160
159
  catch (error) {
161
160
  logger?.warn({ trace: error.stack }, 'failed to obtain extra info');
162
161
  }
163
162
  })()
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
- }
172
- });
173
- delete uploadData.media;
174
- const obj = proto.Message.create({
175
- // todo: add more support here
176
- [`${mediaType}Message`]: MessageTypeProto[mediaType].fromObject({
177
- url: mediaUrl,
178
- directPath,
179
- fileSha256,
180
- fileLength,
181
- thumbnailDirectPath,
182
- thumbnailSha256,
183
- ...uploadData
184
- })
185
- });
186
- if (uploadData.ptv) {
187
- obj.ptvMessage = obj.videoMessage;
188
- delete obj.videoMessage;
189
- }
190
- if (obj.stickerMessage) {
191
- obj.stickerMessage.stickerSentTs = Date.now();
192
- }
193
- if (cacheableKey) {
194
- logger?.debug({ cacheableKey }, 'set cache');
195
- await options.mediaCache.set(cacheableKey, WAProto.Message.encode(obj).finish());
196
- }
197
- return obj;
163
+ ]);
164
+ // todo: add more support here
165
+ mediaUrl = uploadResult.mediaUrl;
166
+ directPath = uploadResult.directPath;
167
+ thumbnailDirectPath = uploadResult.thumbnailDirectPath;
168
+ thumbnailSha256 = uploadResult.thumbnailSha256;
169
+ fs.unlink(filePath).catch(() => logger?.warn('failed to remove tmp file'));
198
170
  }
199
- const { mediaKey, encFilePath, originalFilePath, fileEncSha256, fileSha256, fileLength } = await encryptedStream(uploadData.media, options.mediaTypeOverride || mediaType, {
200
- logger,
201
- saveOriginalFileIfRequired: requiresOriginalForSomeProcessing,
202
- opts: options.options
203
- });
204
- const fileEncSha256B64 = fileEncSha256.toString('base64');
205
- const [{ mediaUrl, directPath }] = await Promise.all([
206
- (async () => {
207
- const result = await options.upload(encFilePath, {
171
+ else {
172
+ const encryptedData = await encryptedStream(mediaPayload, options.mediaTypeOverride || mediaType, {
173
+ logger,
174
+ saveOriginalFileIfRequired: requiresOriginalForSomeProcessing,
175
+ opts: options.options
176
+ });
177
+ mediaKey = encryptedData.mediaKey;
178
+ fileEncSha256 = encryptedData.fileEncSha256;
179
+ fileSha256 = encryptedData.fileSha256;
180
+ fileLength = encryptedData.fileLength;
181
+ const encFilePath = encryptedData.encFilePath;
182
+ const originalFilePath = encryptedData.originalFilePath;
183
+ const fileEncSha256B64 = fileEncSha256.toString('base64');
184
+ const [uploadResult] = await Promise.all([
185
+ options.upload(encFilePath, {
208
186
  fileEncSha256B64,
209
187
  mediaType,
210
188
  timeoutMs: options.mediaUploadTimeoutMs
211
- });
212
- logger?.debug({ mediaType, cacheableKey }, 'uploaded media');
213
- return result;
214
- })(),
215
- (async () => {
216
- try {
217
- if (requiresThumbnailComputation) {
218
- const { thumbnail, originalImageDimensions } = await generateThumbnail(originalFilePath, mediaType, options);
219
- uploadData.jpegThumbnail = thumbnail;
220
- if (!uploadData.width && originalImageDimensions) {
221
- uploadData.width = originalImageDimensions.width;
222
- uploadData.height = originalImageDimensions.height;
223
- logger?.debug('set dimensions');
189
+ }),
190
+ (async () => {
191
+ try {
192
+ if (requiresThumbnailComputation) {
193
+ const { thumbnail, originalImageDimensions } = await generateThumbnail(originalFilePath, mediaType, options);
194
+ uploadData.jpegThumbnail = thumbnail;
195
+ if (!uploadData.width && originalImageDimensions) {
196
+ uploadData.width = originalImageDimensions.width;
197
+ uploadData.height = originalImageDimensions.height;
198
+ }
199
+ }
200
+ if (requiresDurationComputation) {
201
+ uploadData.seconds = await getAudioDuration(originalFilePath);
202
+ }
203
+ if (requiresWaveformProcessing) {
204
+ uploadData.waveform = await getAudioWaveform(originalFilePath, logger);
205
+ }
206
+ if (requiresAudioBackground) {
207
+ uploadData.backgroundArgb = await assertColor(options.backgroundColor);
224
208
  }
225
- logger?.debug('generated thumbnail');
226
- }
227
- if (requiresDurationComputation) {
228
- uploadData.seconds = await getAudioDuration(originalFilePath);
229
- logger?.debug('computed audio duration');
230
- }
231
- if (requiresWaveformProcessing) {
232
- uploadData.waveform = await getAudioWaveform(originalFilePath, logger);
233
- logger?.debug('processed waveform');
234
209
  }
235
- if (requiresAudioBackground) {
236
- uploadData.backgroundArgb = await assertColor(options.backgroundColor);
237
- logger?.debug('computed backgroundColor audio status');
210
+ catch (error) {
211
+ logger?.warn({ trace: error.stack }, 'failed to obtain extra info');
238
212
  }
239
- }
240
- catch (error) {
241
- logger?.warn({ trace: error.stack }, 'failed to obtain extra info');
242
- }
243
- })()
244
- ]).finally(async () => {
245
- try {
246
- await fs.unlink(encFilePath);
247
- if (originalFilePath) {
248
- await fs.unlink(originalFilePath);
249
- }
250
- logger?.debug('removed tmp files');
251
- }
252
- catch (error) {
253
- logger?.warn('failed to remove tmp file');
254
- }
255
- });
256
- delete uploadData.media;
213
+ })()
214
+ ]);
215
+ mediaUrl = uploadResult.mediaUrl;
216
+ directPath = uploadResult.directPath;
217
+ fs.unlink(encFilePath).catch(() => logger?.warn('failed to remove tmp file'));
218
+ fs.unlink(originalFilePath).catch(() => logger?.warn('failed to remove tmp file'));
219
+ }
220
+ const messagePayload = {
221
+ url: mediaUrl,
222
+ directPath,
223
+ fileSha256,
224
+ fileLength,
225
+ ...uploadData
226
+ };
227
+ if (isNewsletter) {
228
+ messagePayload.thumbnailDirectPath = thumbnailDirectPath;
229
+ messagePayload.thumbnailSha256 = thumbnailSha256;
230
+ }
231
+ else {
232
+ messagePayload.mediaKey = mediaKey;
233
+ messagePayload.fileEncSha256 = fileEncSha256;
234
+ messagePayload.mediaKeyTimestamp = unixTimestampSeconds();
235
+ }
257
236
  const obj = proto.Message.create({
258
- [`${mediaType}Message`]: MessageTypeProto[mediaType].fromObject({
259
- url: mediaUrl,
260
- directPath,
261
- mediaKey,
262
- fileEncSha256,
263
- fileSha256,
264
- fileLength,
265
- mediaKeyTimestamp: unixTimestampSeconds(),
266
- ...uploadData
267
- })
237
+ [`${mediaType}Message`]: MessageTypeProto[mediaType].fromObject(messagePayload)
268
238
  });
269
239
  if (uploadData.ptv) {
270
240
  obj.ptvMessage = obj.videoMessage;
@@ -274,8 +244,8 @@ export const prepareWAMessageMedia = async (message, options) => {
274
244
  obj.stickerMessage.stickerSentTs = Date.now();
275
245
  }
276
246
  if (cacheableKey) {
277
- logger?.debug({ cacheableKey }, 'set cache');
278
- await options.mediaCache.set(cacheableKey, WAProto.Message.encode(obj).finish());
247
+ logger?.debug({ cacheableKey }, 'set cache (background)');
248
+ options.mediaCache.set(cacheableKey, WAProto.Message.encode(obj).finish());
279
249
  }
280
250
  return obj;
281
251
  };
@@ -319,6 +289,7 @@ const prepareProductMessage = async (message, options) => {
319
289
  * Credits: Work on ensuring stickerPackMessage fields are valid by @jlucaso1 (https://github.com/jlucaso1).
320
290
  * based on https://github.com/WhiskeySockets/Baileys/pull/1561
321
291
  */
292
+ // Lia@Changes 21-04-26 --- Enhanced prepareStickerPackMessage
322
293
  const prepareStickerPackMessage = async (message, options) => {
323
294
  const { cover, stickers = [], name = '📦 Sticker Pack', publisher = 'GitHub: itsliaaa', description = '🏷️ itsliaaa/baileys' } = message;
324
295
  if (stickers.length > 60) {
@@ -330,136 +301,124 @@ const prepareStickerPackMessage = async (message, options) => {
330
301
  if (!cover) {
331
302
  throw new Boom('Sticker pack must contain a cover', { statusCode: 400 });
332
303
  }
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('@');
304
+ const logger = options.logger;
305
+ // Lia@Changes 01-02-26 --- Add caching for sticker pack
306
+ let cacheableKey = false;
307
+ if (Array.isArray(stickers) && stickers.length && options.mediaCache) {
308
+ const urls = [];
309
+ for (let i = 0; i < stickers.length; i++) {
310
+ const data = stickers[i].data;
311
+ if (typeof data === 'object' && data?.url) {
312
+ urls.push(data.url);
313
+ }
314
+ }
315
+ if (urls.length > 0) {
316
+ cacheableKey = 'sticker:' + urls.join('@');
317
+ }
318
+ }
347
319
  if (cacheableKey) {
348
320
  const mediaBuff = await options.mediaCache.get(cacheableKey);
349
321
  if (mediaBuff) {
350
- options.logger?.debug({ cacheableKey }, 'got media cache hit');
322
+ logger?.debug({ cacheableKey }, 'got media cache hit');
351
323
  return proto.Message.StickerPackMessage.decode(mediaBuff);
352
324
  }
353
325
  }
354
326
  const lib = await getImageProcessingLibrary();
327
+ const hasSharp = 'sharp' in lib && !!lib.sharp?.default;
328
+ const hasImage = 'image' in lib && !!lib.image?.Transformer;
329
+ const hasJimp = 'jimp' in lib && !!lib.jimp?.Jimp;
330
+ if (!hasSharp && !hasImage) {
331
+ throw new Boom('No image processing library (sharp or @napi-rs/image) available for converting sticker to WebP.');
332
+ }
355
333
  const stickerPackIdValue = generateMessageIDV2();
356
334
  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;
335
+ const stickerMetadata = new Array(stickers.length);
336
+ for (let i = 0; i < stickers.length; i += CONCURRENCY_LIMIT) {
337
+ const promises = [];
338
+ const chunkEnd = Math.min(i + CONCURRENCY_LIMIT, stickers.length);
339
+ for (let j = i; j < chunkEnd; j++) {
340
+ promises.push((async (index) => {
341
+ const sticker = stickers[index];
342
+ const { stream } = await getStream(sticker.data);
343
+ const buffer = await toBuffer(stream);
344
+ let webpBuffer;
345
+ let isAnimated = false;
346
+ if (isWebPBuffer(buffer)) {
347
+ webpBuffer = buffer;
348
+ isAnimated = isAnimatedWebP(buffer);
349
+ }
350
+ else if (hasSharp) {
351
+ webpBuffer = await lib.sharp.default(buffer)
352
+ .resize(512, 512, { fit: 'inside' })
353
+ .webp({ quality: 80 })
354
+ .toBuffer();
355
+ }
356
+ else {
357
+ webpBuffer = await new lib.image.Transformer(buffer)
358
+ .resize(512, 512)
359
+ .webp(80);
360
+ }
361
+ if (webpBuffer.length > 1024 * 1024) {
362
+ throw new Boom(`Sticker at index ${index} exceeds the 1MB size limit`, { statusCode: 400 });
363
+ }
364
+ const hash = sha256(webpBuffer).toString('base64').replace(/\//g, '-');
365
+ const fileName = `${hash}.webp`;
366
+ stickerData[fileName] = [new Uint8Array(webpBuffer), { level: 0 }];
367
+ stickerMetadata[index] = {
368
+ fileName,
369
+ mimetype: 'image/webp',
370
+ isAnimated,
371
+ emojis: sticker.emojis || ['✨'],
372
+ accessibilityLabel: sticker.accessibilityLabel || '‎'
373
+ };
374
+ })(j));
387
375
  }
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';
376
+ await Promise.all(promises);
377
+ }
378
+ const trayIconFileName = `${stickerPackIdValue}.webp`;
408
379
  const { stream: coverStream } = await getStream(cover);
409
380
  const coverBuffer = await toBuffer(coverStream);
410
381
  let coverWebpBuffer;
411
- const isCoverWebP = isWebPBuffer(coverBuffer);
412
- if (isCoverWebP) {
413
- // Already WebP - preserve original to keep exif metadata
382
+ if (isWebPBuffer(coverBuffer)) {
414
383
  coverWebpBuffer = coverBuffer;
415
384
  }
416
- else if ('sharp' in lib && lib.sharp?.default) {
417
- coverWebpBuffer = await lib
418
- .sharp
419
- .default(coverBuffer)
385
+ else if (hasSharp) {
386
+ coverWebpBuffer = await lib.sharp.default(coverBuffer)
420
387
  .resize(512, 512, { fit: 'inside' })
421
388
  .webp({ quality: 80 })
422
389
  .toBuffer();
423
390
  }
424
- else if ('image' in lib && lib.image?.Transformer) {
425
- coverWebpBuffer = await new lib
426
- .image
427
- .Transformer(coverBuffer)
391
+ else {
392
+ coverWebpBuffer = await new lib.image.Transformer(coverBuffer)
428
393
  .resize(512, 512)
429
394
  .webp(80);
430
395
  }
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
396
  stickerData[trayIconFileName] = [new Uint8Array(coverWebpBuffer), { level: 0 }];
436
397
  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
- });
398
+ zip(stickerData, (error, data) => error ? reject(error) : resolve(Buffer.from(data)));
444
399
  });
445
- const stickerPackSize = zipBuffer.length;
446
400
  const stickerPackUpload = await encryptedStream(zipBuffer, 'sticker-pack', {
447
- logger: options.logger,
401
+ logger,
448
402
  opts: options.options
449
403
  });
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);
404
+ let stickerPackUploadResult;
405
+ try {
406
+ stickerPackUploadResult = await options.upload(stickerPackUpload.encFilePath, {
407
+ fileEncSha256B64: stickerPackUpload.fileEncSha256.toString('base64'),
408
+ mediaType: 'sticker-pack',
409
+ timeoutMs: options.mediaUploadTimeoutMs
410
+ });
411
+ }
412
+ finally {
413
+ fs.unlink(stickerPackUpload.encFilePath).catch(() => logger?.warn('failed to remove tmp file'));
414
+ }
456
415
  const obj = {
457
- name: name,
458
- publisher: publisher,
416
+ name,
417
+ publisher,
459
418
  stickerPackId: stickerPackIdValue,
460
419
  packDescription: description,
461
420
  stickerPackOrigin: proto.Message.StickerPackMessage.StickerPackOrigin.USER_CREATED,
462
- stickerPackSize: stickerPackSize,
421
+ stickerPackSize: zipBuffer.length,
463
422
  stickers: stickerMetadata,
464
423
  fileSha256: stickerPackUpload.fileSha256,
465
424
  fileEncSha256: stickerPackUpload.fileEncSha256,
@@ -467,31 +426,19 @@ const prepareStickerPackMessage = async (message, options) => {
467
426
  directPath: stickerPackUploadResult.directPath,
468
427
  fileLength: stickerPackUpload.fileLength,
469
428
  mediaKeyTimestamp: unixTimestampSeconds(),
470
- trayIconFileName: trayIconFileName
429
+ trayIconFileName
471
430
  };
472
431
  try {
473
- // Reuse the cover buffer we already processed for thumbnail generation
474
432
  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) {
433
+ if (hasSharp) {
434
+ thumbnailBuffer = await lib.sharp.default(coverBuffer).resize(252, 252).jpeg().toBuffer();
435
+ }
436
+ else if (hasImage) {
437
+ thumbnailBuffer = await new lib.image.Transformer(coverBuffer).resize(252, 252).jpeg();
438
+ }
439
+ else if (hasJimp) {
491
440
  const jimpImage = await lib.jimp.Jimp.read(coverBuffer);
492
- thumbnailBuffer = await jimpImage
493
- .resize({ w: 252, h: 252 })
494
- .getBuffer('image/jpeg');
441
+ thumbnailBuffer = await jimpImage.resize({ w: 252, h: 252 }).getBuffer('image/jpeg');
495
442
  }
496
443
  else {
497
444
  throw new Error('No image processing library available for thumbnail generation');
@@ -500,16 +447,21 @@ const prepareStickerPackMessage = async (message, options) => {
500
447
  throw new Error('Failed to generate thumbnail buffer');
501
448
  }
502
449
  const thumbUpload = await encryptedStream(thumbnailBuffer, 'thumbnail-sticker-pack', {
503
- logger: options.logger,
450
+ logger,
504
451
  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
452
+ mediaKey: stickerPackUpload.mediaKey
511
453
  });
512
- await fs.unlink(thumbUpload.encFilePath);
454
+ let thumbUploadResult;
455
+ try {
456
+ thumbUploadResult = await options.upload(thumbUpload.encFilePath, {
457
+ fileEncSha256B64: thumbUpload.fileEncSha256.toString('base64'),
458
+ mediaType: 'thumbnail-sticker-pack',
459
+ timeoutMs: options.mediaUploadTimeoutMs
460
+ });
461
+ }
462
+ finally {
463
+ fs.unlink(thumbUpload.encFilePath).catch(() => logger?.warn('failed to remove tmp file'));
464
+ }
513
465
  Object.assign(obj, {
514
466
  thumbnailDirectPath: thumbUploadResult.directPath,
515
467
  thumbnailSha256: thumbUpload.fileSha256,
@@ -520,14 +472,13 @@ const prepareStickerPackMessage = async (message, options) => {
520
472
  });
521
473
  }
522
474
  catch (error) {
523
- options.logger?.warn?.(`Thumbnail generation failed: ${error}`);
475
+ logger?.warn(`Thumbnail generation failed: ${error}`);
524
476
  }
525
- const content = obj;
526
477
  if (cacheableKey) {
527
- options.logger?.debug({ cacheableKey }, 'set cache');
528
- await options.mediaCache.set(cacheableKey, WAProto.Message.StickerPackMessage.encode(content).finish());
478
+ logger?.debug({ cacheableKey }, 'set cache (background)');
479
+ options.mediaCache.set(cacheableKey, WAProto.Message.StickerPackMessage.encode(obj).finish());
529
480
  }
530
- return WAProto.Message.StickerPackMessage.fromObject(content);
481
+ return WAProto.Message.StickerPackMessage.fromObject(obj);
531
482
  };
532
483
  // Lia@Changes 30-01-26 --- Add native flow button helper for interactive message
533
484
  const prepareNativeFlowButtons = (message) => {
@@ -1319,6 +1270,12 @@ export const generateWAMessageContent = async (message, options) => {
1319
1270
  }
1320
1271
  m = { invoiceMessage };
1321
1272
  }
1273
+ if (shouldIncludeReportingToken(m)) {
1274
+ m.messageContextInfo = m.messageContextInfo || {};
1275
+ if (!m.messageContextInfo.messageSecret) {
1276
+ m.messageContextInfo.messageSecret = randomBytes(32);
1277
+ }
1278
+ }
1322
1279
  // Lia@Changes 31-01-26 --- Add direct externalAdReply access (no need to create contextInfo first)
1323
1280
  if (hasOptionalProperty(message, 'externalAdReply') && !!message.externalAdReply) {
1324
1281
  const messageType = Object.keys(m)[0];
@@ -1336,9 +1293,9 @@ export const generateWAMessageContent = async (message, options) => {
1336
1293
  mediaType: content.mediaType || 1,
1337
1294
  mediaUrl: content.url,
1338
1295
  renderLargerThumbnail: content.largeThumbnail,
1339
- sourceUrl: content.url + '?update=' + Date.now(),
1296
+ sourceUrl: content.url,
1340
1297
  thumbnail: content.thumbnail,
1341
- thumbnailUrl: content.url,
1298
+ thumbnailUrl: content.url + '?update=' + Date.now(),
1342
1299
  title: content.title || LIBRARY_NAME
1343
1300
  };
1344
1301
  delete externalAdReply.subTitle;
@@ -1434,12 +1391,6 @@ export const generateWAMessageContent = async (message, options) => {
1434
1391
  }
1435
1392
  }
1436
1393
  }
1437
- if (shouldIncludeReportingToken(m)) {
1438
- m.messageContextInfo = m.messageContextInfo || {};
1439
- if (!m.messageContextInfo.messageSecret) {
1440
- m.messageContextInfo.messageSecret = randomBytes(32);
1441
- }
1442
- }
1443
1394
  return proto.Message.create(m);
1444
1395
  };
1445
1396
  export const generateWAMessageFromContent = (jid, message, options) => {