amiudmodz 5.0.8 → 5.1.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.
@@ -103,43 +103,52 @@ const extractVideoThumb = async (path, destPath, time, size) => new Promise((res
103
103
  });
104
104
  const extractImageThumb = async (bufferOrFilePath, width = 32) => {
105
105
  var _a, _b;
106
+ let tmpPath;
106
107
  if (bufferOrFilePath instanceof stream_1.Readable) {
107
- bufferOrFilePath = await (0, exports.toBuffer)(bufferOrFilePath);
108
- }
109
- const lib = await getImageProcessingLibrary();
110
- if ('sharp' in lib && typeof ((_a = lib.sharp) === null || _a === void 0 ? void 0 : _a.default) === 'function') {
111
- const img = lib.sharp.default(bufferOrFilePath);
112
- const dimensions = await img.metadata();
113
- const buffer = await img
114
- .resize(width)
115
- .jpeg({ quality: 50 })
116
- .toBuffer();
117
- return {
118
- buffer,
119
- original: {
120
- width: dimensions.width,
121
- height: dimensions.height,
122
- },
123
- };
124
- }
125
- else if ('jimp' in lib && typeof ((_b = lib.jimp) === null || _b === void 0 ? void 0 : _b.read) === 'function') {
126
- const { read, MIME_JPEG, RESIZE_BILINEAR, AUTO } = lib.jimp;
127
- const jimp = await read(bufferOrFilePath);
128
- const dimensions = {
129
- width: jimp.getWidth(),
130
- height: jimp.getHeight()
131
- };
132
- const buffer = await jimp
133
- .quality(50)
134
- .resize(width, AUTO, RESIZE_BILINEAR)
135
- .getBufferAsync(MIME_JPEG);
136
- return {
137
- buffer,
138
- original: dimensions
139
- };
108
+ // Spill stream to disk instead of buffering to RAM
109
+ tmpPath = await spillToDisk(bufferOrFilePath);
110
+ bufferOrFilePath = tmpPath;
140
111
  }
141
- else {
142
- throw new boom_1.Boom('No image processing library available');
112
+ try {
113
+ const lib = await getImageProcessingLibrary();
114
+ if ('sharp' in lib && typeof ((_a = lib.sharp) === null || _a === void 0 ? void 0 : _a.default) === 'function') {
115
+ const img = lib.sharp.default(bufferOrFilePath);
116
+ const dimensions = await img.metadata();
117
+ const buffer = await img
118
+ .resize(width)
119
+ .jpeg({ quality: 50 })
120
+ .toBuffer();
121
+ return {
122
+ buffer,
123
+ original: {
124
+ width: dimensions.width,
125
+ height: dimensions.height,
126
+ },
127
+ };
128
+ }
129
+ else if ('jimp' in lib && typeof ((_b = lib.jimp) === null || _b === void 0 ? void 0 : _b.read) === 'function') {
130
+ const { read, MIME_JPEG, RESIZE_BILINEAR, AUTO } = lib.jimp;
131
+ const jimp = await read(bufferOrFilePath);
132
+ const dimensions = {
133
+ width: jimp.getWidth(),
134
+ height: jimp.getHeight()
135
+ };
136
+ const buffer = await jimp
137
+ .quality(50)
138
+ .resize(width, AUTO, RESIZE_BILINEAR)
139
+ .getBufferAsync(MIME_JPEG);
140
+ return {
141
+ buffer,
142
+ original: dimensions
143
+ };
144
+ }
145
+ else {
146
+ throw new boom_1.Boom('No image processing library available');
147
+ }
148
+ } finally {
149
+ if (tmpPath) {
150
+ await fs_1.promises.unlink(tmpPath).catch(() => {});
151
+ }
143
152
  }
144
153
  };
145
154
  exports.extractImageThumb = extractImageThumb;
@@ -151,6 +160,7 @@ exports.encodeBase64EncodedStringForUpload = encodeBase64EncodedStringForUpload;
151
160
  const generateProfilePicture = async (mediaUpload) => {
152
161
  var _a, _b;
153
162
  let bufferOrFilePath;
163
+ let tmpPath;
154
164
  if (Buffer.isBuffer(mediaUpload)) {
155
165
  bufferOrFilePath = mediaUpload;
156
166
  }
@@ -158,34 +168,41 @@ const generateProfilePicture = async (mediaUpload) => {
158
168
  bufferOrFilePath = mediaUpload.url.toString();
159
169
  }
160
170
  else {
161
- bufferOrFilePath = await (0, exports.toBuffer)(mediaUpload.stream);
162
- }
163
- const lib = await getImageProcessingLibrary();
164
- let img;
165
- if ('sharp' in lib && typeof ((_a = lib.sharp) === null || _a === void 0 ? void 0 : _a.default) === 'function') {
166
- img = lib.sharp.default(bufferOrFilePath)
167
- .resize(640, 640)
168
- .jpeg({
169
- quality: 50,
170
- })
171
- .toBuffer();
172
- }
173
- else if ('jimp' in lib && typeof ((_b = lib.jimp) === null || _b === void 0 ? void 0 : _b.read) === 'function') {
174
- const { read, MIME_JPEG, RESIZE_BILINEAR } = lib.jimp;
175
- const jimp = await read(bufferOrFilePath);
176
- const min = Math.min(jimp.getWidth(), jimp.getHeight());
177
- const cropped = jimp.crop(0, 0, min, min);
178
- img = cropped
179
- .quality(50)
180
- .resize(640, 640, RESIZE_BILINEAR)
181
- .getBufferAsync(MIME_JPEG);
171
+ tmpPath = await spillToDisk(mediaUpload.stream);
172
+ bufferOrFilePath = tmpPath;
182
173
  }
183
- else {
184
- throw new boom_1.Boom('No image processing library available');
174
+ try {
175
+ const lib = await getImageProcessingLibrary();
176
+ let img;
177
+ if ('sharp' in lib && typeof ((_a = lib.sharp) === null || _a === void 0 ? void 0 : _a.default) === 'function') {
178
+ img = lib.sharp.default(bufferOrFilePath)
179
+ .resize(640, 640)
180
+ .jpeg({
181
+ quality: 50,
182
+ })
183
+ .toBuffer();
184
+ }
185
+ else if ('jimp' in lib && typeof ((_b = lib.jimp) === null || _b === void 0 ? void 0 : _b.read) === 'function') {
186
+ const { read, MIME_JPEG, RESIZE_BILINEAR } = lib.jimp;
187
+ const jimp = await read(bufferOrFilePath);
188
+ const min = Math.min(jimp.getWidth(), jimp.getHeight());
189
+ const cropped = jimp.crop(0, 0, min, min);
190
+ img = cropped
191
+ .quality(50)
192
+ .resize(640, 640, RESIZE_BILINEAR)
193
+ .getBufferAsync(MIME_JPEG);
194
+ }
195
+ else {
196
+ throw new boom_1.Boom('No image processing library available');
197
+ }
198
+ return {
199
+ img: await img,
200
+ };
201
+ } finally {
202
+ if (tmpPath) {
203
+ await fs_1.promises.unlink(tmpPath).catch(() => {});
204
+ }
185
205
  }
186
- return {
187
- img: await img,
188
- };
189
206
  };
190
207
  exports.generateProfilePicture = generateProfilePicture;
191
208
  /** gets the SHA256 of the given media message */
@@ -239,19 +256,14 @@ async function getAudioWaveform(buffer, logger) {
239
256
  const { PassThrough } = require('stream');
240
257
  const ff = require('fluent-ffmpeg');
241
258
 
242
- let audioData;
259
+ const inputStream = new PassThrough();
243
260
  if (Buffer.isBuffer(buffer)) {
244
- audioData = buffer;
261
+ inputStream.end(buffer);
245
262
  } else if (typeof buffer === 'string') {
246
- const rStream = require('fs').createReadStream(buffer);
247
- audioData = await exports.toBuffer(rStream);
263
+ (0, fs_1.createReadStream)(buffer).pipe(inputStream);
248
264
  } else {
249
- audioData = await exports.toBuffer(buffer);
265
+ buffer.pipe(inputStream);
250
266
  }
251
-
252
- return await new Promise((resolve, reject) => {
253
- const inputStream = new PassThrough();
254
- inputStream.end(audioData);
255
267
  const chunks = [];
256
268
  const bars = 64;
257
269
 
@@ -280,7 +292,7 @@ async function getAudioWaveform(buffer, logger) {
280
292
  const normalized = avg.map(v => Math.floor((v / max) * 100));
281
293
  resolve(new Uint8Array(normalized));
282
294
  })
283
- .pipe()
295
+ .pipe(new PassThrough(), { end: true })
284
296
  .on('data', chunk => chunks.push(chunk));
285
297
  });
286
298
  } catch (e) {
@@ -343,9 +355,31 @@ const toBuffer = async (stream) => {
343
355
  return Buffer.concat(chunks);
344
356
  };
345
357
  exports.toBuffer = toBuffer;
358
+ /**
359
+ * Spills a Buffer or Readable stream to a temp file on disk so it is not
360
+ * held entirely in RAM. Returns the temp-file path so the caller can clean
361
+ * it up when done.
362
+ */
363
+ const spillToDisk = async (source) => {
364
+ const tmpPath = (0, path_1.join)(getTmpFilesDirectory(), `spill-${(0, generics_1.generateMessageID)()}`);
365
+ const writeStream = (0, fs_1.createWriteStream)(tmpPath);
366
+ await new Promise((resolve, reject) => {
367
+ writeStream.on('error', reject);
368
+ writeStream.on('finish', resolve);
369
+ if (Buffer.isBuffer(source)) {
370
+ writeStream.end(source);
371
+ } else {
372
+ source.pipe(writeStream);
373
+ source.on('error', reject);
374
+ }
375
+ });
376
+ return tmpPath;
377
+ };
346
378
  const getStream = async (item, opts) => {
347
379
  if (Buffer.isBuffer(item)) {
348
- return { stream: (0, exports.toReadable)(item), type: 'buffer' };
380
+ // Write the Buffer to a temp file so it can be GC'd from RAM
381
+ const tmpPath = await spillToDisk(item);
382
+ return { stream: (0, fs_1.createReadStream)(tmpPath), type: 'file', spilledPath: tmpPath };
349
383
  }
350
384
  if ('stream' in item) {
351
385
  return { stream: item.stream, type: 'readable' };
@@ -479,15 +513,48 @@ const prepareStream = async (media, mediaType, { logger, saveOriginalFileIfRequi
479
513
  };
480
514
  exports.prepareStream = prepareStream;
481
515
  const encryptedStream = async (media, mediaType, { logger, saveOriginalFileIfRequired, opts, isPtt, forceOpus } = {}) => {
482
- const { stream, type } = await (0, exports.getStream)(media, opts);
516
+ const { stream, type, spilledPath } = await (0, exports.getStream)(media, opts);
483
517
  let finalStream = stream;
484
518
  let opusConverted = false;
519
+ let opusSpillPath;
485
520
  if (mediaType === 'audio' && (isPtt === true || forceOpus === true)) {
486
521
  try {
487
- const buffer = await (0, exports.toBuffer)(stream);
488
- const opusBuffer = await exports.convertToOpusBuffer(buffer, logger);
489
- finalStream = (0, exports.toReadable)(opusBuffer);
490
- opusConverted = true;
522
+ // Write audio to temp file, convert via ffmpeg, write result to another temp file
523
+ const audioTmpPath = await spillToDisk(stream);
524
+ try {
525
+ const ff = require('fluent-ffmpeg');
526
+ opusSpillPath = (0, path_1.join)(getTmpFilesDirectory(), `opus-${(0, generics_1.generateMessageID)()}`);
527
+ const opusWriteStream = (0, fs_1.createWriteStream)(opusSpillPath);
528
+
529
+ await new Promise((resolve, reject) => {
530
+ ff(audioTmpPath)
531
+ .noVideo()
532
+ .audioCodec('libopus')
533
+ .format('ogg')
534
+ .audioBitrate('48k')
535
+ .audioChannels(1)
536
+ .audioFrequency(48000)
537
+ .outputOptions([
538
+ '-vn',
539
+ '-b:a 64k',
540
+ '-ac 2',
541
+ '-ar 48000',
542
+ '-map_metadata', '-1',
543
+ '-application', 'voip'
544
+ ])
545
+ .on('error', (err) => {
546
+ opusWriteStream.destroy();
547
+ reject(err);
548
+ })
549
+ .on('end', resolve)
550
+ .pipe(opusWriteStream);
551
+ });
552
+
553
+ finalStream = (0, fs_1.createReadStream)(opusSpillPath);
554
+ opusConverted = true;
555
+ } finally {
556
+ await fs_1.promises.unlink(audioTmpPath).catch(() => {});
557
+ }
491
558
  }
492
559
  catch (error) {
493
560
  const { stream: newStream } = await (0, exports.getStream)(media, opts);
@@ -497,15 +564,16 @@ const encryptedStream = async (media, mediaType, { logger, saveOriginalFileIfReq
497
564
  const mediaKey = Crypto.randomBytes(32);
498
565
  const { cipherKey, iv, macKey } = getMediaKeys(mediaKey, mediaType);
499
566
 
500
-
501
567
  const encPath = (0, path_1.join)(getTmpFilesDirectory(), `enc-${mediaType}-${(0, generics_1.generateMessageID)()}`);
502
568
  const encTempWriteStream = (0, fs_1.createWriteStream)(encPath);
503
569
 
504
570
  let bodyPath;
505
571
  let writeStream;
506
572
  let didSaveToTmpPath = false;
573
+ // For 'file' type (includes our spill-to-disk buffers), use the path directly
507
574
  if (type === 'file') {
508
- bodyPath = media.url;
575
+ // If spilled from a Buffer, spilledPath IS the original; use it as bodyPath
576
+ bodyPath = spilledPath || (media && media.url ? media.url : undefined);
509
577
  }
510
578
  else if (saveOriginalFileIfRequired) {
511
579
  bodyPath = (0, path_1.join)(getTmpFilesDirectory(), mediaType + (0, generics_1.generateMessageID)());
@@ -556,15 +624,32 @@ const encryptedStream = async (media, mediaType, { logger, saveOriginalFileIfReq
556
624
  const fileSha256 = sha256Plain.digest();
557
625
  const fileEncSha256 = sha256Enc.digest();
558
626
 
559
- encTempWriteStream.end();
560
- writeStream === null || writeStream === void 0 ? void 0 : writeStream.end();
627
+ await new Promise((resolve, reject) => {
628
+ encTempWriteStream.end(resolve);
629
+ encTempWriteStream.once('error', reject);
630
+ });
631
+ if (writeStream) {
632
+ await new Promise((resolve, reject) => {
633
+ writeStream.end(resolve);
634
+ writeStream.once('error', reject);
635
+ });
636
+ }
561
637
  finalStream.destroy();
638
+ // Clean up the opus spill temp file if used
639
+ if (opusSpillPath) {
640
+ await fs_1.promises.unlink(opusSpillPath).catch(() => {});
641
+ }
642
+ // Clean up the buffer-spill temp file — it's no longer needed once encrypted
643
+ // (the enc temp file is what gets uploaded; bodyPath/spilledPath is only
644
+ // needed for thumbnail/duration computation which happens in parallel)
645
+ // Caller (messages.js) handles cleanup of streamPath & bodyPath.
562
646
 
563
647
  return {
564
648
  mediaKey,
565
649
  encWriteStream: (0, fs_1.createReadStream)(encPath),
566
650
  bodyPath,
567
651
  streamPath: encPath,
652
+ spilledPath, // expose so caller can clean up the buffer spill file
568
653
  mac,
569
654
  fileEncSha256,
570
655
  fileSha256,
@@ -581,6 +666,9 @@ const encryptedStream = async (media, mediaType, { logger, saveOriginalFileIfReq
581
666
  sha256Plain.destroy();
582
667
  sha256Enc.destroy();
583
668
  finalStream.destroy();
669
+ if (opusSpillPath) {
670
+ await fs_1.promises.unlink(opusSpillPath).catch(() => {});
671
+ }
584
672
  try {
585
673
  await fs_1.promises.unlink(encPath);
586
674
  } catch { }
@@ -746,6 +834,7 @@ const getWAUploadToServer = ({ customUploadHosts, fetchAgent, logger, options },
746
834
  headers: {
747
835
  ...options.headers || {},
748
836
  'Content-Type': 'application/octet-stream',
837
+ 'Content-Length': bodyLength.toString(),
749
838
  'Origin': Defaults_1.DEFAULT_ORIGIN
750
839
  },
751
840
  httpsAgent: fetchAgent,
@@ -149,7 +149,7 @@ const prepareWAMessageMedia = async (message, options) => {
149
149
  const requiresAudioBackground = options.backgroundColor && mediaType === 'audio' && uploadData.ptt === true;
150
150
  const requiresOriginalForSomeProcessing = requiresDurationComputation || requiresThumbnailComputation;
151
151
 
152
- const { mediaKey, encWriteStream, bodyPath, streamPath, fileEncSha256, fileSha256, fileLength, didSaveToTmpPath, opusConverted } = await (options.newsletter ? messages_media_1.prepareStream : messages_media_1.encryptedStream)(uploadData.media, options.mediaTypeOverride || mediaType, {
152
+ const { mediaKey, encWriteStream, bodyPath, streamPath, spilledPath, fileEncSha256, fileSha256, fileLength, didSaveToTmpPath, opusConverted } = await (options.newsletter ? messages_media_1.prepareStream : messages_media_1.encryptedStream)(uploadData.media, options.mediaTypeOverride || mediaType, {
153
153
  logger,
154
154
  saveOriginalFileIfRequired: requiresOriginalForSomeProcessing,
155
155
  opts: options.options,
@@ -157,6 +157,9 @@ const prepareWAMessageMedia = async (message, options) => {
157
157
  forceOpus: (mediaType === "audio" && uploadData.mimetype && uploadData.mimetype.includes('opus'))
158
158
  });
159
159
 
160
+ // Nullify the media reference to help GC free the memory if it's a large Buffer
161
+ uploadData.media = undefined;
162
+
160
163
  if (mediaType === 'audio' && opusConverted) {
161
164
  uploadData.mimetype = 'audio/ogg; codecs=opus';
162
165
  }
@@ -210,6 +213,12 @@ const prepareWAMessageMedia = async (message, options) => {
210
213
  if (streamPath) {
211
214
  await fs_1.promises.unlink(streamPath).catch(() => { });
212
215
  }
216
+ // Clean up the buffer-spill temp file (if input was a Buffer, it was
217
+ // written to /tmp to avoid holding it in RAM; delete it now that we
218
+ // are done with thumbnail/duration computation)
219
+ if (spilledPath && spilledPath !== bodyPath) {
220
+ await fs_1.promises.unlink(spilledPath).catch(() => { });
221
+ }
213
222
  logger === null || logger === void 0 ? void 0 : logger.debug('removed tmp files');
214
223
  });
215
224
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "amiudmodz",
3
- "version": "5.0.8",
3
+ "version": "5.1.0",
4
4
  "description": "WhatsApp Baileys mod Powered by UDMODZ",
5
5
  "keywords": [
6
6
  "whatsapp",