@kelvdra/baileys 1.0.4 → 1.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/LICENSE +21 -0
  2. package/WAProto/index.js +65472 -137440
  3. package/lib/Defaults/index.d.ts +1 -1
  4. package/lib/Defaults/index.js +22 -3
  5. package/lib/Socket/chats.js +12 -13
  6. package/lib/Socket/groups.js +140 -7
  7. package/lib/Socket/hydra.js +44 -0
  8. package/lib/Socket/messages-recv.js +736 -324
  9. package/lib/Socket/messages-send.js +481 -110
  10. package/lib/Socket/mex.js +44 -6
  11. package/lib/Socket/newsletter.d.ts +16 -9
  12. package/lib/Socket/newsletter.js +259 -70
  13. package/lib/Types/Mex.d.ts +141 -0
  14. package/lib/Types/Mex.js +37 -0
  15. package/lib/Types/State.js +54 -1
  16. package/lib/Utils/auth-utils.js +12 -1
  17. package/lib/Utils/chat-utils.js +36 -2
  18. package/lib/Utils/companion-reg-client-utils.d.ts +17 -0
  19. package/lib/Utils/companion-reg-client-utils.js +35 -0
  20. package/lib/Utils/decode-wa-message.js +23 -4
  21. package/lib/Utils/generics.js +4 -1
  22. package/lib/Utils/identity-change-handler.d.ts +44 -0
  23. package/lib/Utils/identity-change-handler.js +50 -0
  24. package/lib/Utils/index.js +1 -1
  25. package/lib/Utils/message-retry-manager.js +25 -1
  26. package/lib/Utils/messages-media.js +162 -43
  27. package/lib/Utils/messages.d.ts +1 -1
  28. package/lib/Utils/messages.js +230 -9
  29. package/lib/Utils/offline-node-processor.d.ts +17 -0
  30. package/lib/Utils/offline-node-processor.js +40 -0
  31. package/lib/Utils/reporting-utils.d.ts +11 -0
  32. package/lib/Utils/reporting-utils.js +258 -0
  33. package/lib/Utils/signal.js +45 -1
  34. package/lib/Utils/stanza-ack.d.ts +11 -0
  35. package/lib/Utils/stanza-ack.js +38 -0
  36. package/lib/Utils/sync-action-utils.d.ts +19 -0
  37. package/lib/Utils/sync-action-utils.js +49 -0
  38. package/lib/Utils/tc-token-utils.d.ts +37 -0
  39. package/lib/Utils/tc-token-utils.js +163 -0
  40. package/lib/WAUSync/Protocols/USyncUsernameProtocol.d.ts +10 -0
  41. package/lib/WAUSync/Protocols/USyncUsernameProtocol.js +25 -0
  42. package/package.json +3 -1
@@ -196,55 +196,63 @@ export async function getAudioDuration(buffer) {
196
196
  */
197
197
  export async function getAudioWaveform(buffer, logger) {
198
198
  try {
199
- const { PassThrough } = require('stream');
200
- const ff = require('fluent-ffmpeg');
201
-
202
- let audioData;
203
- if (Buffer.isBuffer(buffer)) {
204
- audioData = buffer;
205
- } else if (typeof buffer === 'string') {
206
- const rStream = require('fs').createReadStream(buffer);
207
- audioData = await toBuffer(rStream);
208
- } else {
209
- audioData = await toBuffer(buffer);
199
+ const ffmpegModule = await import('fluent-ffmpeg');
200
+ const ff = ffmpegModule.default || ffmpegModule;
201
+ let input;
202
+ if (Buffer.isBuffer(buffer) || typeof buffer === 'string') {
203
+ input = buffer;
204
+ }
205
+ else {
206
+ input = await toBuffer(buffer);
210
207
  }
211
-
212
208
  return await new Promise((resolve, reject) => {
213
- const inputStream = new PassThrough();
214
- inputStream.end(audioData);
215
209
  const chunks = [];
216
210
  const bars = 64;
217
-
218
- ff(inputStream)
211
+ ff(input)
219
212
  .audioChannels(1)
220
213
  .audioFrequency(16000)
221
214
  .format('s16le')
222
215
  .on('error', reject)
223
216
  .on('end', () => {
224
- const rawData = Buffer.concat(chunks);
225
- const samples = rawData.length / 2;
226
- const amplitudes = [];
227
-
228
- for (let i = 0; i < samples; i++) {
229
- amplitudes.push(Math.abs(rawData.readInt16LE(i * 2)) / 32768);
217
+ try {
218
+ const rawData = Buffer.concat(chunks);
219
+ const samples = Math.floor(rawData.length / 2);
220
+ if (!samples) {
221
+ return resolve(new Uint8Array(bars).fill(0));
222
+ }
223
+ const amplitudes = new Array(samples);
224
+ for (let i = 0; i < samples; i++) {
225
+ amplitudes[i] = Math.abs(rawData.readInt16LE(i * 2)) / 32768;
226
+ }
227
+ const blockSize = Math.max(1, Math.floor(amplitudes.length / bars));
228
+ const avg = [];
229
+ for (let i = 0; i < bars; i++) {
230
+ const start = i * blockSize;
231
+ const end = i === bars - 1 ? amplitudes.length : Math.min(start + blockSize, amplitudes.length);
232
+ const block = amplitudes.slice(start, end);
233
+ if (!block.length) {
234
+ avg.push(0);
235
+ continue;
236
+ }
237
+ avg.push(block.reduce((a, b) => a + b, 0) / block.length);
238
+ }
239
+ const max = Math.max(...avg, 0.0001);
240
+ const normalized = avg.map(v => {
241
+ const scaled = Math.round((v / max) * 100);
242
+ return Math.max(0, Math.min(100, scaled));
243
+ });
244
+ resolve(new Uint8Array(normalized));
230
245
  }
231
-
232
- const blockSize = Math.floor(amplitudes.length / bars);
233
- const avg = [];
234
- for (let i = 0; i < bars; i++) {
235
- const block = amplitudes.slice(i * blockSize, (i + 1) * blockSize);
236
- avg.push(block.reduce((a, b) => a + b, 0) / block.length);
246
+ catch (error) {
247
+ reject(error);
237
248
  }
238
-
239
- const max = Math.max(...avg);
240
- const normalized = avg.map(v => Math.floor((v / max) * 100));
241
- resolve(new Uint8Array(normalized));
242
249
  })
243
250
  .pipe()
244
251
  .on('data', chunk => chunks.push(chunk));
245
252
  });
246
- } catch (e) {
247
- logger?.debug(e);
253
+ }
254
+ catch (e) {
255
+ logger?.debug({ trace: e?.stack || e }, 'failed to generate waveform');
248
256
  }
249
257
  }
250
258
  export const toReadable = (buffer) => {
@@ -263,21 +271,21 @@ export const toBuffer = async (stream) => {
263
271
  };
264
272
  export const getStream = async (item, opts) => {
265
273
  if (Buffer.isBuffer(item)) {
266
- return { stream: toReadable(item), type: 'buffer' };
274
+ return { stream: toReadable(item), type: 'buffer' }
267
275
  }
268
276
  if ('stream' in item) {
269
- return { stream: item.stream, type: 'readable' };
270
- }
271
- const urlStr = item.url.toString();
277
+ return { stream: item.stream, type: 'readable' }
278
+ }
279
+ const urlStr = item.url.toString()
272
280
  if (urlStr.startsWith('data:')) {
273
- const buffer = Buffer.from(urlStr.split(',')[1], 'base64');
274
- return { stream: toReadable(buffer), type: 'buffer' };
281
+ const buffer = Buffer.from(urlStr.split(',')[1], 'base64')
282
+ return { stream: await toReadable(buffer), type: 'buffer' }
275
283
  }
276
284
  if (urlStr.startsWith('http://') || urlStr.startsWith('https://')) {
277
- return { stream: await getHttpStream(item.url, opts), type: 'remote' };
285
+ return { stream: await getHttpStream(item.url, opts), type: 'remote' }
278
286
  }
279
- return { stream: createReadStream(item.url), type: 'file' };
280
- };
287
+ return { stream: createReadStream(item.url), type: 'file' }
288
+ }
281
289
  /** generates a thumbnail for a given media, if required */
282
290
  export async function generateThumbnail(file, mediaType, options) {
283
291
  let thumbnail;
@@ -697,3 +705,114 @@ const MEDIA_RETRY_STATUS_MAP = {
697
705
  [proto.MediaRetryNotification.ResultType.GENERAL_ERROR]: 418
698
706
  };
699
707
  //# sourceMappingURL=messages-media.js.map
708
+
709
+ // Added from Baileys v7.0.0-rc10
710
+ export const DEF_MEDIA_HOST = 'mmg.whatsapp.net';
711
+
712
+ // Added from Baileys v7.0.0-rc10
713
+ export const uploadWithNodeHttp = async ({ url, filePath, headers, timeoutMs, agent }, redirectCount = 0) => {
714
+ if (redirectCount > 5) {
715
+ throw new Error('Too many redirects');
716
+ }
717
+ const parsedUrl = new URL(url);
718
+ const httpModule = parsedUrl.protocol === 'https:' ? await import('https') : await import('http');
719
+ // Get file size for Content-Length header (required for Node.js streaming)
720
+ const fileStats = await fs.stat(filePath);
721
+ const fileSize = fileStats.size;
722
+ return new Promise((resolve, reject) => {
723
+ const req = httpModule.request({
724
+ hostname: parsedUrl.hostname,
725
+ port: parsedUrl.port || (parsedUrl.protocol === 'https:' ? 443 : 80),
726
+ path: parsedUrl.pathname + parsedUrl.search,
727
+ method: 'POST',
728
+ headers: {
729
+ ...headers,
730
+ 'Content-Length': fileSize
731
+ },
732
+ agent,
733
+ timeout: timeoutMs
734
+ }, res => {
735
+ // Handle redirects (3xx)
736
+ if (res.statusCode && res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
737
+ res.resume(); // Consume response to free resources
738
+ const newUrl = new URL(res.headers.location, url).toString();
739
+ resolve(uploadWithNodeHttp({
740
+ url: newUrl,
741
+ filePath,
742
+ headers,
743
+ timeoutMs,
744
+ agent
745
+ }, redirectCount + 1));
746
+ return;
747
+ }
748
+ let body = '';
749
+ res.on('data', chunk => (body += chunk));
750
+ res.on('end', () => {
751
+ try {
752
+ resolve(JSON.parse(body));
753
+ }
754
+ catch {
755
+ resolve(undefined);
756
+ }
757
+ });
758
+ });
759
+ req.on('error', reject);
760
+ req.on('timeout', () => {
761
+ req.destroy();
762
+ reject(new Error('Upload timeout'));
763
+ });
764
+ const stream = createReadStream(filePath);
765
+ stream.pipe(req);
766
+ stream.on('error', err => {
767
+ req.destroy();
768
+ reject(err);
769
+ });
770
+ });
771
+ };
772
+ const uploadWithFetch = async ({ url, filePath, headers, timeoutMs, agent }) => {
773
+ // Convert Node.js Readable to Web ReadableStream
774
+ const nodeStream = createReadStream(filePath);
775
+ const webStream = Readable.toWeb(nodeStream);
776
+ const response = await fetch(url, {
777
+ dispatcher: agent,
778
+ method: 'POST',
779
+ body: webStream,
780
+ headers,
781
+ duplex: 'half',
782
+ signal: timeoutMs ? AbortSignal.timeout(timeoutMs) : undefined
783
+ });
784
+ try {
785
+ return (await response.json());
786
+ }
787
+ catch {
788
+ return undefined;
789
+ }
790
+ };
791
+ /**
792
+ * Uploads media to WhatsApp servers.
793
+ *
794
+ * ## Why we have two upload implementations:
795
+ *
796
+ * Node.js's native `fetch` (powered by undici) has a known bug where it buffers
797
+ * the entire request body in memory before sending, even when using streams.
798
+ * This causes memory issues with large files (e.g., 1GB file = 1GB+ memory usage).
799
+ * See: https://github.com/nodejs/undici/issues/4058
800
+ *
801
+ * Other runtimes (Bun, Deno, browsers) correctly stream the request body without
802
+ * buffering, so we can use the web-standard Fetch API there.
803
+ *
804
+ * ## Future considerations:
805
+ * Once the undici bug is fixed, we can simplify this to use only the Fetch API
806
+ * across all runtimes. Monitor the GitHub issue for updates.
807
+ */
808
+ const uploadMedia = async (params, logger) => {
809
+ if (isNodeRuntime()) {
810
+ logger?.debug('Using Node.js https module for upload (avoids undici buffering bug)');
811
+ return uploadWithNodeHttp(params);
812
+ }
813
+ else {
814
+ logger?.debug('Using web-standard Fetch API for upload');
815
+ return uploadWithFetch(params);
816
+ }
817
+ };
818
+
@@ -86,4 +86,4 @@ export declare const downloadMediaMessage: <Type extends "buffer" | "stream">(me
86
86
  /** Checks whether the given message is a media message; if it is returns the inner content */
87
87
  export declare const assertMediaContent: (content: proto.IMessage | null | undefined) => proto.Message.IVideoMessage | proto.Message.IImageMessage | proto.Message.IAudioMessage | proto.Message.IDocumentMessage | proto.Message.IStickerMessage;
88
88
  export {};
89
- //# sourceMappingURL=messages.d.ts.map
89
+ //# sourceMappingURL=messages.d.ts.map
@@ -8,7 +8,8 @@ import { WAMessageStatus, WAProto } from '../Types/index.js';
8
8
  import { 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
- import { downloadContentFromMessage, encryptedStream, generateThumbnail, getAudioDuration, getAudioWaveform, getRawMediaUploadData } from './messages-media.js';
11
+ import { downloadContentFromMessage, encryptedStream, generateThumbnail, getAudioDuration, getAudioWaveform, getRawMediaUploadData, getStream, toBuffer } from './messages-media.js';
12
+ import { shouldIncludeReportingToken } from './reporting-utils.js';
12
13
  const MIMETYPE_MAP = {
13
14
  image: 'image/jpeg',
14
15
  video: 'video/mp4',
@@ -99,6 +100,25 @@ export const prepareWAMessageMedia = async (message, options) => {
99
100
  if (isNewsletter) {
100
101
  logger?.info({ key: cacheableKey }, 'Preparing raw media for newsletter');
101
102
  const { filePath, fileSha256, fileLength } = await getRawMediaUploadData(uploadData.media, options.mediaTypeOverride || mediaType, logger);
103
+ try {
104
+ if (mediaType === 'audio') {
105
+ if (typeof uploadData.seconds === 'undefined') {
106
+ uploadData.seconds = await getAudioDuration(filePath);
107
+ logger?.debug('computed newsletter audio duration');
108
+ }
109
+ if (uploadData.ptt === true && typeof uploadData.waveform === 'undefined') {
110
+ uploadData.waveform = await getAudioWaveform(filePath, logger);
111
+ logger?.debug('processed newsletter waveform');
112
+ }
113
+ if (options.backgroundColor && uploadData.ptt === true && typeof uploadData.backgroundArgb === 'undefined') {
114
+ uploadData.backgroundArgb = await assertColor(options.backgroundColor);
115
+ logger?.debug('computed newsletter backgroundColor audio status');
116
+ }
117
+ }
118
+ }
119
+ catch (error) {
120
+ logger?.warn({ trace: error.stack }, 'failed to obtain newsletter media extra info');
121
+ }
102
122
  const fileSha256B64 = fileSha256.toString('base64');
103
123
  const { mediaUrl, directPath } = await options.upload(filePath, {
104
124
  fileEncSha256B64: fileSha256B64,
@@ -131,7 +151,10 @@ export const prepareWAMessageMedia = async (message, options) => {
131
151
  const requiresThumbnailComputation = (mediaType === 'image' || mediaType === 'video') && typeof uploadData['jpegThumbnail'] === 'undefined';
132
152
  const requiresWaveformProcessing = mediaType === 'audio' && uploadData.ptt === true;
133
153
  const requiresAudioBackground = options.backgroundColor && mediaType === 'audio' && uploadData.ptt === true;
134
- const requiresOriginalForSomeProcessing = requiresDurationComputation || requiresThumbnailComputation;
154
+ // waveform processing also needs the original media file.
155
+ // if the caller already provides `seconds`, the old logic skips saving the original file,
156
+ // so `getAudioWaveform(originalFilePath)` receives `undefined` and waveform generation fails.
157
+ const requiresOriginalForSomeProcessing = requiresDurationComputation || requiresThumbnailComputation || requiresWaveformProcessing;
135
158
  const { mediaKey, encFilePath, originalFilePath, fileEncSha256, fileSha256, fileLength } = await encryptedStream(uploadData.media, options.mediaTypeOverride || mediaType, {
136
159
  logger,
137
160
  saveOriginalFileIfRequired: requiresOriginalForSomeProcessing,
@@ -523,6 +546,174 @@ export const generateWAMessageContent = async (message, options) => {
523
546
 
524
547
  m.requestPaymentMessage = requestPaymentMessage
525
548
  }
549
+ else if ('stickerPack' in message) {
550
+ const { stickers, cover, name, publisher, packId, description } = message.stickerPack;
551
+ const { zip } = await import('fflate');
552
+
553
+ if (!Array.isArray(stickers) || stickers.length === 0) {
554
+ throw new Boom('Sticker pack must contain at least one sticker', { statusCode: 400 });
555
+ }
556
+ if (stickers.length > 120) {
557
+ throw new Boom('Sticker pack exceeds the maximum limit of 120 stickers', { statusCode: 400 });
558
+ }
559
+
560
+ const stickerPackId = packId || generateMessageIDV2();
561
+ const [_sharp, _jimp] = await Promise.all([
562
+ import('sharp').catch(() => null),
563
+ import('jimp').catch(() => null)
564
+ ]);
565
+ const lib = _sharp ? { sharp: _sharp } : _jimp ? { jimp: _jimp } : null;
566
+ if (!lib) {
567
+ throw new Boom('No image processing library available (install sharp or jimp)', { statusCode: 500 });
568
+ }
569
+
570
+ const isWebPBuffer = (buf) => (
571
+ buf.length >= 12 &&
572
+ buf[0] === 0x52 && buf[1] === 0x49 && buf[2] === 0x46 && buf[3] === 0x46 &&
573
+ buf[8] === 0x57 && buf[9] === 0x45 && buf[10] === 0x42 && buf[11] === 0x50
574
+ );
575
+ const isAnimatedWebP = (buf) => {
576
+ if (!isWebPBuffer(buf)) return false;
577
+ let offset = 12;
578
+ while (offset < buf.length - 8) {
579
+ const fourCC = buf.toString('ascii', offset, offset + 4);
580
+ const chunkSize = buf.readUInt32LE(offset + 4);
581
+ if (fourCC === 'VP8X') {
582
+ const flagsOffset = offset + 8;
583
+ if (flagsOffset < buf.length && (buf[flagsOffset] & 0x02)) return true;
584
+ } else if (fourCC === 'ANIM' || fourCC === 'ANMF') {
585
+ return true;
586
+ }
587
+ offset += 8 + chunkSize + (chunkSize % 2);
588
+ }
589
+ return false;
590
+ };
591
+ const imageToWebp = async (buffer) => {
592
+ if (isWebPBuffer(buffer)) {
593
+ if ('sharp' in lib && lib.sharp) {
594
+ return await lib.sharp.default(buffer)
595
+ .resize(512, 512, { fit: 'inside', withoutEnlargement: true })
596
+ .webp({ quality: 75, effort: 6 })
597
+ .toBuffer();
598
+ }
599
+ return buffer;
600
+ }
601
+ if ('sharp' in lib && lib.sharp) {
602
+ return await lib.sharp.default(buffer)
603
+ .resize(512, 512, { fit: 'inside', withoutEnlargement: true })
604
+ .webp({ quality: 75, effort: 6 })
605
+ .toBuffer();
606
+ }
607
+ throw new Boom('No image processing library (sharp) available for converting sticker to WebP', { statusCode: 500 });
608
+ };
609
+
610
+ const stickerData = {};
611
+ const stickerMetadata = [];
612
+ for (let i = 0; i < stickers.length; i++) {
613
+ const s = stickers[i];
614
+ try {
615
+ const { stream } = await getStream(s.data || s.sticker);
616
+ const buffer = await toBuffer(stream);
617
+ if (!buffer || buffer.length === 0) continue;
618
+ const animated = isAnimatedWebP(buffer);
619
+ const webpBuffer = await imageToWebp(buffer);
620
+ if (webpBuffer.length > 1024 * 1024) continue;
621
+ const hash = sha256(webpBuffer).toString('base64').replace(/\//g, '-').replace(/=/g, '');
622
+ const fileName = `${hash}.webp`;
623
+ stickerData[fileName] = [new Uint8Array(webpBuffer), { level: 6 }];
624
+ stickerMetadata.push({
625
+ fileName,
626
+ mimetype: 'image/webp',
627
+ isAnimated: s.isAnimated ?? animated,
628
+ isLottie: s.isLottie || false,
629
+ emojis: s.emojis || [],
630
+ accessibilityLabel: s.accessibilityLabel || ''
631
+ });
632
+ } catch (err) {
633
+ options.logger?.warn?.({ err }, 'failed processing sticker pack item');
634
+ }
635
+ }
636
+ if (stickerMetadata.length === 0) {
637
+ throw new Boom('No valid stickers could be processed', { statusCode: 400 });
638
+ }
639
+
640
+ const trayIconFileName = `${stickerPackId}.webp`;
641
+ const coverBuffer = await toBuffer((await getStream(cover)).stream);
642
+ const coverWebpBuffer = await imageToWebp(coverBuffer);
643
+ stickerData[trayIconFileName] = [new Uint8Array(coverWebpBuffer), { level: 6 }];
644
+
645
+ const zipBuffer = await new Promise((resolve, reject) => {
646
+ zip(stickerData, { level: 6, memLevel: 9 }, (err, data) => {
647
+ if (err) reject(err);
648
+ else resolve(Buffer.from(data));
649
+ });
650
+ });
651
+ if (zipBuffer.length > 10 * 1024 * 1024) {
652
+ throw new Boom(`Sticker pack too large: ${(zipBuffer.length / 1024 / 1024).toFixed(2)}MB`, { statusCode: 400 });
653
+ }
654
+
655
+ const stickerPackUpload = await encryptedStream(zipBuffer, 'sticker-pack', {
656
+ logger: options.logger,
657
+ opts: options.options
658
+ });
659
+ const stickerPackUploadResult = await options.upload(stickerPackUpload.encFilePath, {
660
+ fileEncSha256B64: stickerPackUpload.fileEncSha256.toString('base64'),
661
+ mediaType: 'sticker-pack',
662
+ timeoutMs: options.mediaUploadTimeoutMs || 300000
663
+ });
664
+
665
+ m.stickerPackMessage = {
666
+ name,
667
+ publisher,
668
+ stickerPackId,
669
+ packDescription: description,
670
+ stickerPackOrigin: proto.Message.StickerPackMessage.StickerPackOrigin.THIRD_PARTY,
671
+ stickerPackSize: zipBuffer.length,
672
+ stickers: stickerMetadata,
673
+ fileSha256: stickerPackUpload.fileSha256,
674
+ fileEncSha256: stickerPackUpload.fileEncSha256,
675
+ mediaKey: stickerPackUpload.mediaKey,
676
+ directPath: stickerPackUploadResult.directPath,
677
+ fileLength: stickerPackUpload.fileLength,
678
+ mediaKeyTimestamp: unixTimestampSeconds(),
679
+ trayIconFileName
680
+ };
681
+
682
+ try {
683
+ let thumbnailBuffer;
684
+ if ('sharp' in lib && lib.sharp) {
685
+ thumbnailBuffer = await lib.sharp.default(coverBuffer).resize(252, 252, { fit: 'cover' }).jpeg({ quality: 80 }).toBuffer();
686
+ } else {
687
+ const jimpImage = await (lib.jimp.Jimp || lib.jimp.default).read(coverBuffer);
688
+ thumbnailBuffer = await jimpImage.resize({ w: 252, h: 252 }).getBuffer('image/jpeg');
689
+ }
690
+ const thumbUpload = await encryptedStream(thumbnailBuffer, 'thumbnail-sticker-pack', {
691
+ logger: options.logger,
692
+ opts: options.options,
693
+ mediaKey: stickerPackUpload.mediaKey
694
+ });
695
+ const thumbUploadResult = await options.upload(thumbUpload.encFilePath, {
696
+ fileEncSha256B64: thumbUpload.fileEncSha256.toString('base64'),
697
+ mediaType: 'thumbnail-sticker-pack',
698
+ timeoutMs: options.mediaUploadTimeoutMs || 60000
699
+ });
700
+ Object.assign(m.stickerPackMessage, {
701
+ thumbnailDirectPath: thumbUploadResult.directPath,
702
+ thumbnailSha256: thumbUpload.fileSha256,
703
+ thumbnailEncSha256: thumbUpload.fileEncSha256,
704
+ thumbnailHeight: 252,
705
+ thumbnailWidth: 252,
706
+ imageDataHash: sha256(thumbnailBuffer).toString('base64')
707
+ });
708
+ } catch (err) {
709
+ options.logger?.warn?.({ err }, 'failed generating sticker pack thumbnail');
710
+ }
711
+
712
+ m.stickerPackMessage.contextInfo = {
713
+ ...(message.contextInfo || {}),
714
+ ...(message.mentions ? { mentionedJid: message.mentions } : {})
715
+ };
716
+ }
526
717
  else if ('sharePhoneNumber' in message) {
527
718
  m.protocolMessage = {
528
719
  type: proto.Message.ProtocolMessage.Type.SHARE_PHONE_NUMBER
@@ -857,7 +1048,7 @@ export const normalizeMessageContent = (content) => {
857
1048
  if (!content) {
858
1049
  return undefined;
859
1050
  }
860
- // set max iterations to prevent an infinite loop
1051
+
861
1052
  for (let i = 0; i < 5; i++) {
862
1053
  const inner = getFutureProofMessage(content);
863
1054
  if (!inner) {
@@ -867,12 +1058,29 @@ export const normalizeMessageContent = (content) => {
867
1058
  }
868
1059
  return content;
869
1060
  function getFutureProofMessage(message) {
870
- return (message?.ephemeralMessage ||
871
- message?.viewOnceMessage ||
872
- message?.documentWithCaptionMessage ||
873
- message?.viewOnceMessageV2 ||
874
- message?.viewOnceMessageV2Extension ||
875
- message?.editedMessage);
1061
+ return ((message === null || message === void 0 ? void 0 : message.ephemeralMessage)
1062
+ || (message === null || message === void 0 ? void 0 : message.viewOnceMessage)
1063
+ || (message === null || message === void 0 ? void 0 : message.documentWithCaptionMessage)
1064
+ || (message === null || message === void 0 ? void 0 : message.viewOnceMessageV2)
1065
+ || (message === null || message === void 0 ? void 0 : message.viewOnceMessageV2Extension)
1066
+ || (message === null || message === void 0 ? void 0 : message.editedMessage)
1067
+ || (message === null || message === void 0 ? void 0 : message.groupMentionedMessage)
1068
+ || (message === null || message === void 0 ? void 0 : message.botInvokeMessage)
1069
+ || (message === null || message === void 0 ? void 0 : message.lottieStickerMessage)
1070
+ || (message === null || message === void 0 ? void 0 : message.eventCoverImage)
1071
+ || (message === null || message === void 0 ? void 0 : message.statusMentionMessage)
1072
+ || (message === null || message === void 0 ? void 0 : message.pollCreationOptionImageMessage)
1073
+ || (message === null || message === void 0 ? void 0 : message.associatedChildMessage)
1074
+ || (message === null || message === void 0 ? void 0 : message.groupStatusMentionMessage)
1075
+ || (message === null || message === void 0 ? void 0 : message.pollCreationMessageV4)
1076
+ || (message === null || message === void 0 ? void 0 : message.pollCreationMessageV5)
1077
+ || (message === null || message === void 0 ? void 0 : message.statusAddYours)
1078
+ || (message === null || message === void 0 ? void 0 : message.groupStatusMessage)
1079
+ || (message === null || message === void 0 ? void 0 : message.limitSharingMessage)
1080
+ || (message === null || message === void 0 ? void 0 : message.botTaskMessage)
1081
+ || (message === null || message === void 0 ? void 0 : message.questionMessage)
1082
+ || (message === null || message === void 0 ? void 0 : message.groupStatusMessageV2)
1083
+ || (message === null || message === void 0 ? void 0 : message.botForwardedMessage));
876
1084
  }
877
1085
  };
878
1086
  /**
@@ -1140,3 +1348,16 @@ export const patchMessageForMdIfRequired = (message) => {
1140
1348
  }
1141
1349
  return message
1142
1350
  }
1351
+
1352
+ // Added from Baileys v7.0.0-rc10
1353
+ export const hasNonNullishProperty = (message, key) => {
1354
+ return (typeof message === 'object' &&
1355
+ message !== null &&
1356
+ key in message &&
1357
+ message[key] !== null &&
1358
+ message[key] !== undefined);
1359
+ };
1360
+ function hasOptionalProperty(obj, key) {
1361
+ return typeof obj === 'object' && obj !== null && key in obj && obj[key] !== null;
1362
+ }
1363
+
@@ -0,0 +1,17 @@
1
+ import type { BinaryNode } from '../WABinary/index.js';
2
+ export type MessageType = 'message' | 'call' | 'receipt' | 'notification';
3
+ export type OfflineNodeProcessorDeps = {
4
+ isWsOpen: () => boolean;
5
+ onUnexpectedError: (error: Error, msg: string) => void;
6
+ yieldToEventLoop: () => Promise<void>;
7
+ };
8
+ /**
9
+ * Creates a processor for offline stanza nodes that:
10
+ * - Queues nodes for sequential processing
11
+ * - Yields to the event loop periodically to avoid blocking
12
+ * - Catches handler errors to prevent the processing loop from crashing
13
+ */
14
+ export declare function makeOfflineNodeProcessor(nodeProcessorMap: Map<MessageType, (node: BinaryNode) => Promise<void>>, deps: OfflineNodeProcessorDeps, batchSize?: number): {
15
+ enqueue: (type: MessageType, node: BinaryNode) => void;
16
+ };
17
+ //# sourceMappingURL=offline-node-processor.d.ts.map
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Creates a processor for offline stanza nodes that:
3
+ * - Queues nodes for sequential processing
4
+ * - Yields to the event loop periodically to avoid blocking
5
+ * - Catches handler errors to prevent the processing loop from crashing
6
+ */
7
+ export function makeOfflineNodeProcessor(nodeProcessorMap, deps, batchSize = 10) {
8
+ const nodes = [];
9
+ let isProcessing = false;
10
+ const enqueue = (type, node) => {
11
+ nodes.push({ type, node });
12
+ if (isProcessing) {
13
+ return;
14
+ }
15
+ isProcessing = true;
16
+ const promise = async () => {
17
+ let processedInBatch = 0;
18
+ while (nodes.length && deps.isWsOpen()) {
19
+ const { type, node } = nodes.shift();
20
+ const nodeProcessor = nodeProcessorMap.get(type);
21
+ if (!nodeProcessor) {
22
+ deps.onUnexpectedError(new Error(`unknown offline node type: ${type}`), 'processing offline node');
23
+ continue;
24
+ }
25
+ await nodeProcessor(node).catch(err => deps.onUnexpectedError(err, `processing offline ${type}`));
26
+ processedInBatch++;
27
+ // Yield to event loop after processing a batch
28
+ // This prevents blocking the event loop for too long when there are many offline nodes
29
+ if (processedInBatch >= batchSize) {
30
+ processedInBatch = 0;
31
+ await deps.yieldToEventLoop();
32
+ }
33
+ }
34
+ isProcessing = false;
35
+ };
36
+ promise().catch(error => deps.onUnexpectedError(error, 'processing offline nodes'));
37
+ };
38
+ return { enqueue };
39
+ }
40
+ //# sourceMappingURL=offline-node-processor.js.map
@@ -0,0 +1,11 @@
1
+ import { proto } from '../../WAProto/index.js';
2
+ import type { WAMessageContent, WAMessageKey } from '../Types/index.js';
3
+ import type { BinaryNode } from '../WABinary/index.js';
4
+ export type ReportingField = {
5
+ f: number;
6
+ m?: boolean;
7
+ s?: ReportingField[];
8
+ };
9
+ export declare const shouldIncludeReportingToken: (message: proto.IMessage) => boolean;
10
+ export declare const getMessageReportingToken: (msgProtobuf: Buffer, message: WAMessageContent, key: WAMessageKey) => Promise<BinaryNode | null>;
11
+ //# sourceMappingURL=reporting-utils.d.ts.map