@modzneverdie/baileys 17.1.13 → 17.1.17

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 (113) hide show
  1. package/README.md +364 -0
  2. package/engine-requirements.js +4 -4
  3. package/lib/Defaults/baileys-version.json +1 -1
  4. package/lib/Defaults/index.d.ts +6 -4
  5. package/lib/Defaults/index.js +119 -78
  6. package/lib/Defaults/phonenumber-mcc.json +223 -0
  7. package/lib/Socket/Client/{types.d.ts → abstract-socket-client.d.ts} +2 -1
  8. package/lib/Socket/Client/index.d.ts +3 -2
  9. package/lib/Socket/Client/index.js +3 -2
  10. package/lib/Socket/Client/mobile-socket-client.d.ts +13 -0
  11. package/lib/Socket/Client/mobile-socket-client.js +65 -0
  12. package/lib/Socket/Client/{websocket.d.ts → web-socket-client.d.ts} +1 -2
  13. package/lib/Socket/Client/{websocket.js → web-socket-client.js} +5 -54
  14. package/lib/Socket/business.d.ts +58 -59
  15. package/lib/Socket/chats.d.ts +230 -45
  16. package/lib/Socket/chats.js +238 -139
  17. package/lib/Socket/{setup.ts → dugong.d.ts} +52 -17
  18. package/lib/Socket/dugong.js +484 -0
  19. package/lib/Socket/groups.d.ts +32 -41
  20. package/lib/Socket/groups.js +23 -38
  21. package/lib/Socket/index.d.ts +64 -63
  22. package/lib/Socket/index.js +3 -2
  23. package/lib/Socket/messages-recv.js +65 -9
  24. package/lib/Socket/messages-send.d.ts +47 -49
  25. package/lib/Socket/messages-send.js +399 -415
  26. package/lib/Socket/newsletter.d.ts +37 -39
  27. package/lib/Socket/newsletter.js +136 -71
  28. package/lib/Socket/registration.d.ts +267 -0
  29. package/lib/Socket/registration.js +166 -0
  30. package/lib/Socket/socket.d.ts +10 -10
  31. package/lib/Socket/socket.js +619 -736
  32. package/lib/Socket/usync.d.ts +3 -3
  33. package/lib/Store/index.d.ts +2 -1
  34. package/lib/Store/index.js +3 -1
  35. package/lib/Store/make-cache-manager-store.d.ts +13 -0
  36. package/lib/Store/make-cache-manager-store.js +83 -0
  37. package/lib/Store/make-in-memory-store.d.ts +24 -24
  38. package/lib/Store/make-in-memory-store.js +14 -26
  39. package/lib/Store/make-ordered-dictionary.d.ts +1 -1
  40. package/lib/Store/make-ordered-dictionary.js +2 -2
  41. package/lib/Types/Auth.d.ts +7 -0
  42. package/lib/Types/Call.d.ts +1 -1
  43. package/lib/Types/Chat.d.ts +7 -14
  44. package/lib/Types/Contact.d.ts +1 -5
  45. package/lib/Types/Events.d.ts +2 -44
  46. package/lib/Types/GroupMetadata.d.ts +2 -11
  47. package/lib/Types/Label.js +1 -1
  48. package/lib/Types/LabelAssociation.js +1 -1
  49. package/lib/Types/Message.d.ts +21 -148
  50. package/lib/Types/Message.js +2 -0
  51. package/lib/Types/Newsletter.d.ts +97 -73
  52. package/lib/Types/Newsletter.js +38 -18
  53. package/lib/Types/Socket.d.ts +9 -17
  54. package/lib/Types/index.d.ts +1 -8
  55. package/lib/Types/index.js +2 -2
  56. package/lib/Utils/auth-utils.d.ts +3 -3
  57. package/lib/Utils/auth-utils.js +13 -6
  58. package/lib/Utils/business.js +2 -2
  59. package/lib/Utils/chat-utils.d.ts +16 -15
  60. package/lib/Utils/chat-utils.js +35 -36
  61. package/lib/Utils/crypto.d.ts +16 -15
  62. package/lib/Utils/crypto.js +29 -71
  63. package/lib/Utils/decode-wa-message.d.ts +6 -22
  64. package/lib/Utils/decode-wa-message.js +56 -65
  65. package/lib/Utils/event-buffer.d.ts +2 -2
  66. package/lib/Utils/event-buffer.js +7 -11
  67. package/lib/Utils/generics.d.ts +20 -17
  68. package/lib/Utils/generics.js +84 -102
  69. package/lib/Utils/history.d.ts +0 -4
  70. package/lib/Utils/history.js +6 -4
  71. package/lib/Utils/link-preview.d.ts +2 -2
  72. package/lib/Utils/link-preview.js +1 -34
  73. package/lib/Utils/logger.d.ts +3 -10
  74. package/lib/Utils/lt-hash.d.ts +2 -2
  75. package/lib/Utils/lt-hash.js +6 -6
  76. package/lib/Utils/make-mutex.d.ts +2 -2
  77. package/lib/Utils/messages-media.d.ts +24 -28
  78. package/lib/Utils/messages-media.js +236 -298
  79. package/lib/Utils/messages.d.ts +10 -8
  80. package/lib/Utils/messages.js +60 -304
  81. package/lib/Utils/noise-handler.d.ts +12 -10
  82. package/lib/Utils/noise-handler.js +23 -18
  83. package/lib/Utils/process-message.d.ts +4 -5
  84. package/lib/Utils/process-message.js +25 -89
  85. package/lib/Utils/signal.d.ts +1 -2
  86. package/lib/Utils/signal.js +26 -26
  87. package/lib/Utils/use-multi-file-auth-state.d.ts +1 -0
  88. package/lib/Utils/use-multi-file-auth-state.js +0 -6
  89. package/lib/Utils/validate-connection.d.ts +4 -3
  90. package/lib/Utils/validate-connection.js +76 -20
  91. package/lib/WABinary/constants.d.ts +27 -24
  92. package/lib/WABinary/constants.js +13 -1276
  93. package/lib/WABinary/decode.d.ts +4 -3
  94. package/lib/WABinary/decode.js +13 -26
  95. package/lib/WABinary/encode.d.ts +2 -1
  96. package/lib/WABinary/encode.js +152 -137
  97. package/lib/WABinary/generic-utils.d.ts +4 -1
  98. package/lib/WABinary/generic-utils.js +125 -37
  99. package/lib/WABinary/jid-utils.d.ts +5 -10
  100. package/lib/WABinary/jid-utils.js +5 -26
  101. package/lib/WAM/BinaryInfo.d.ts +11 -2
  102. package/lib/WAM/encode.d.ts +2 -1
  103. package/lib/WAUSync/Protocols/USyncDisappearingModeProtocol.js +1 -1
  104. package/lib/index.d.ts +2 -6
  105. package/lib/index.js +6 -20
  106. package/package.json +105 -107
  107. package/WAProto/GenerateStatics.sh +0 -4
  108. package/WAProto/WAProto.proto +0 -4775
  109. package/WAProto/index.d.ts +0 -55057
  110. package/WAProto/index.ts.ts +0 -53473
  111. package/lib/Socket/setup.js +0 -433
  112. package/lib/WABinary/jid-utils.js.bak +0 -83
  113. /package/lib/Socket/Client/{types.js → abstract-socket-client.js} +0 -0
@@ -15,47 +15,23 @@ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (
15
15
  }) : function(o, v) {
16
16
  o["default"] = v;
17
17
  });
18
- var __importStar = (this && this.__importStar) || (function () {
19
- var ownKeys = function(o) {
20
- ownKeys = Object.getOwnPropertyNames || function (o) {
21
- var ar = [];
22
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
- return ar;
24
- };
25
- return ownKeys(o);
26
- };
27
- return function (mod) {
28
- if (mod && mod.__esModule) return mod;
29
- var result = {};
30
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
- __setModuleDefault(result, mod);
32
- return result;
33
- };
34
- })();
35
- var __importDefault = (this && this.__importDefault) || function (mod) {
36
- return (mod && mod.__esModule) ? mod : { "default": mod };
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
37
24
  };
38
25
  Object.defineProperty(exports, "__esModule", { value: true });
39
- exports.getStatusCodeForMediaRetry = exports.decryptMediaRetryData = exports.decodeMediaRetryNode = exports.encryptMediaRetryRequest = exports.getWAUploadToServer = exports.downloadEncryptedContent = exports.downloadContentFromMessage = exports.getUrlFromDirectPath = exports.encryptedStream = exports.prepareStream = exports.getHttpStream = exports.getStream = exports.toBuffer = exports.toReadable = exports.mediaMessageSHA256B64 = exports.generateProfilePicture = exports.encodeBase64EncodedStringForUpload = exports.extractImageThumb = exports.extractVideoThumb = exports.hkdfInfoKey = void 0;
40
- exports.getMediaKeys = getMediaKeys;
41
- exports.uploadFile = uploadFile;
42
- exports.vid2jpg = vid2jpg;
43
- exports.getAudioDuration = getAudioDuration;
44
- exports.getAudioWaveform = getAudioWaveform;
45
- exports.generateThumbnail = generateThumbnail;
46
- exports.extensionForMediaMessage = extensionForMediaMessage;
26
+ exports.getStatusCodeForMediaRetry = exports.decryptMediaRetryData = exports.decodeMediaRetryNode = exports.encryptMediaRetryRequest = exports.getWAUploadToServer = exports.extensionForMediaMessage = exports.downloadEncryptedContent = exports.downloadContentFromMessage = exports.getUrlFromDirectPath = exports.encryptedStream = exports.prepareStream = exports.getHttpStream = exports.generateThumbnail = exports.getStream = exports.toBuffer = exports.toReadable = exports.getAudioWaveform = exports.getAudioDuration = exports.mediaMessageSHA256B64 = exports.generateProfilePicture = exports.encodeBase64EncodedStringForUpload = exports.extractImageThumb = exports.getMediaKeys = exports.hkdfInfoKey = void 0;
47
27
  const boom_1 = require("@hapi/boom");
48
- const axios_1 = __importDefault(require("axios"));
49
- const form_data_1 = __importDefault(require("form-data"));
50
- const cheerio = __importStar(require("cheerio"));
28
+ const child_process_1 = require("child_process");
51
29
  const Crypto = __importStar(require("crypto"));
52
30
  const events_1 = require("events");
53
31
  const fs_1 = require("fs");
54
32
  const os_1 = require("os");
55
33
  const path_1 = require("path");
56
- const jimp_1 = __importDefault(require("jimp"));
57
34
  const stream_1 = require("stream");
58
- const child_process_1 = require("child_process");
59
35
  const WAProto_1 = require("../../WAProto");
60
36
  const Defaults_1 = require("../Defaults");
61
37
  const WABinary_1 = require("../WABinary");
@@ -65,11 +41,13 @@ const getTmpFilesDirectory = () => (0, os_1.tmpdir)();
65
41
  const getImageProcessingLibrary = async () => {
66
42
  const [_jimp, sharp] = await Promise.all([
67
43
  (async () => {
68
- const jimp = await (Promise.resolve().then(() => __importStar(require('jimp'))).catch(() => { }));
44
+ const jimp = await (import('jimp')
45
+ .catch(() => { }));
69
46
  return jimp;
70
47
  })(),
71
48
  (async () => {
72
- const sharp = await (Promise.resolve().then(() => __importStar(require('sharp'))).catch(() => { }));
49
+ const sharp = await (import('sharp')
50
+ .catch(() => { }));
73
51
  return sharp;
74
52
  })()
75
53
  ]);
@@ -88,7 +66,7 @@ const hkdfInfoKey = (type) => {
88
66
  };
89
67
  exports.hkdfInfoKey = hkdfInfoKey;
90
68
  /** generates all the keys required to encrypt/decrypt & sign a media message */
91
- async function getMediaKeys(buffer, mediaType) {
69
+ function getMediaKeys(buffer, mediaType) {
92
70
  if (!buffer) {
93
71
  throw new boom_1.Boom('Cannot derive from empty media key');
94
72
  }
@@ -96,183 +74,26 @@ async function getMediaKeys(buffer, mediaType) {
96
74
  buffer = Buffer.from(buffer.replace('data:;base64,', ''), 'base64');
97
75
  }
98
76
  // expand using HKDF to 112 bytes, also pass in the relevant app info
99
- const expandedMediaKey = await (0, crypto_1.hkdf)(buffer, 112, { info: (0, exports.hkdfInfoKey)(mediaType) });
77
+ const expandedMediaKey = (0, crypto_1.hkdf)(buffer, 112, { info: (0, exports.hkdfInfoKey)(mediaType) });
100
78
  return {
101
79
  iv: expandedMediaKey.slice(0, 16),
102
80
  cipherKey: expandedMediaKey.slice(16, 48),
103
81
  macKey: expandedMediaKey.slice(48, 80),
104
82
  };
105
83
  }
106
- async function uploadFile(buffer, logger) {
107
- const { fromBuffer } = await Promise.resolve().then(() => __importStar(require('file-type')));
108
- const fileType = await fromBuffer(buffer);
109
- if (!fileType)
110
- throw new Error("Failed to detect file type.");
111
- const { ext, mime } = fileType;
112
- const services = [
113
- {
114
- name: "catbox",
115
- url: "https://catbox.moe/user/api.php",
116
- buildForm: () => {
117
- const form = new form_data_1.default();
118
- form.append("fileToUpload", buffer, {
119
- filename: `file.${ext}`,
120
- contentType: mime || "application/octet-stream"
121
- });
122
- form.append("reqtype", "fileupload");
123
- return form;
124
- },
125
- parseResponse: res => res.data
126
- },
127
- {
128
- name: "pdi.moe",
129
- url: "https://scdn.pdi.moe/upload",
130
- buildForm: () => {
131
- const form = new form_data_1.default();
132
- form.append("file", buffer, {
133
- filename: `file.${ext}`,
134
- contentType: mime
135
- });
136
- return form;
137
- },
138
- parseResponse: res => res.data.result.url
139
- },
140
- {
141
- name: "qu.ax",
142
- url: "https://qu.ax/upload.php",
143
- buildForm: () => {
144
- const form = new form_data_1.default();
145
- form.append("files[]", buffer, {
146
- filename: `file.${ext}`,
147
- contentType: mime || "application/octet-stream"
148
- });
149
- return form;
150
- },
151
- parseResponse: res => {
152
- var _a, _b, _c;
153
- if (!((_c = (_b = (_a = res.data) === null || _a === void 0 ? void 0 : _a.files) === null || _b === void 0 ? void 0 : _b[0]) === null || _c === void 0 ? void 0 : _c.url))
154
- throw new Error("Failed to get URL from qu.ax");
155
- return res.data.files[0].url;
156
- }
157
- },
158
- {
159
- name: "uguu.se",
160
- url: "https://uguu.se/upload.php",
161
- buildForm: () => {
162
- const form = new form_data_1.default();
163
- form.append("files[]", buffer, {
164
- filename: `file.${ext}`,
165
- contentType: mime || "application/octet-stream"
166
- });
167
- return form;
168
- },
169
- parseResponse: res => {
170
- var _a, _b, _c;
171
- if (!((_c = (_b = (_a = res.data) === null || _a === void 0 ? void 0 : _a.files) === null || _b === void 0 ? void 0 : _b[0]) === null || _c === void 0 ? void 0 : _c.url))
172
- throw new Error("Failed to get URL from uguu.se");
173
- return res.data.files[0].url;
174
- }
175
- },
176
- {
177
- name: "tmpfiles",
178
- url: "https://tmpfiles.org/api/v1/upload",
179
- buildForm: () => {
180
- const form = new form_data_1.default();
181
- form.append("file", buffer, {
182
- filename: `file.${ext}`,
183
- contentType: mime
184
- });
185
- return form;
186
- },
187
- parseResponse: res => {
188
- const match = res.data.data.url.match(/https:\/\/tmpfiles\.org\/(.*)/);
189
- if (!match)
190
- throw new Error("Failed to parse tmpfiles URL.");
191
- return `https://tmpfiles.org/dl/${match[1]}`;
192
- }
193
- }
194
- ];
195
- for (const service of services) {
196
- try {
197
- const form = service.buildForm();
198
- const res = await axios_1.default.post(service.url, form, {
199
- headers: form.getHeaders()
200
- });
201
- const url = service.parseResponse(res);
202
- return url;
203
- }
204
- catch (error) {
205
- logger === null || logger === void 0 ? void 0 : logger.debug(`[${service.name}] eror:`, (error === null || error === void 0 ? void 0 : error.message) || error);
206
- }
207
- }
208
- throw new Error("All upload services failed.");
209
- }
210
- async function vid2jpg(videoUrl) {
211
- try {
212
- const { data } = await axios_1.default.get(`https://ezgif.com/video-to-jpg?url=${encodeURIComponent(videoUrl)}`);
213
- const $ = cheerio.load(data);
214
- const fileToken = $('input[name="file"]').attr("value");
215
- if (!fileToken) {
216
- throw new Error("Failed to retrieve file token. The video URL may be invalid or inaccessible.");
217
- }
218
- const formData = new URLSearchParams();
219
- formData.append("file", fileToken);
220
- formData.append("end", "1");
221
- formData.append("video-to-jpg", "Convert to JPG!");
222
- const convert = await axios_1.default.post(`https://ezgif.com/video-to-jpg/${fileToken}`, formData);
223
- const $2 = cheerio.load(convert.data);
224
- let imageUrl = $2("#output img").first().attr("src");
225
- if (!imageUrl) {
226
- throw new Error("Could not locate the converted image output.");
227
- }
228
- if (imageUrl.startsWith("//")) {
229
- imageUrl = "https:" + imageUrl;
84
+ exports.getMediaKeys = getMediaKeys;
85
+ /** Extracts video thumb using FFMPEG */
86
+ const extractVideoThumb = async (path, destPath, time, size) => new Promise((resolve, reject) => {
87
+ const cmd = `ffmpeg -ss ${time} -i ${path} -y -vf scale=${size.width}:-1 -vframes 1 -f image2 ${destPath}`;
88
+ (0, child_process_1.exec)(cmd, (err) => {
89
+ if (err) {
90
+ reject(err);
230
91
  }
231
- else if (imageUrl.startsWith("/")) {
232
- const cdnMatch = imageUrl.match(/\/(s\d+\..+?)\/.*/);
233
- if (cdnMatch) {
234
- imageUrl = "https://" + imageUrl.slice(2);
235
- }
236
- else {
237
- imageUrl = "https://ezgif.com" + imageUrl;
238
- }
92
+ else {
93
+ resolve();
239
94
  }
240
- return imageUrl;
241
- }
242
- catch (error) {
243
- throw new Error("Failed to convert video to JPG: " + error.message);
244
- }
245
- }
246
- /**
247
- * Extracts video thumbnail using FFmpeg
248
- */
249
- const extractVideoThumb = async (videoPath, time = '00:00:00', size = { width: 256 }) => {
250
- return new Promise((resolve, reject) => {
251
- const args = [
252
- '-ss', time,
253
- '-i', videoPath,
254
- '-y',
255
- '-vf', `scale=${size.width}:-1`,
256
- '-vframes', '1',
257
- '-f', 'image2',
258
- '-vcodec', 'mjpeg',
259
- 'pipe:1'
260
- ];
261
- const ffmpeg = (0, child_process_1.spawn)('ffmpeg', args);
262
- const chunks = [];
263
- let errorOutput = '';
264
- ffmpeg.stdout.on('data', chunk => chunks.push(chunk));
265
- ffmpeg.stderr.on('data', data => {
266
- errorOutput += data.toString();
267
- });
268
- ffmpeg.on('error', reject);
269
- ffmpeg.on('close', code => {
270
- if (code === 0) return resolve(Buffer.concat(chunks));
271
- reject(new Error(`ffmpeg exited with code ${code}\n${errorOutput}`));
272
- });
273
95
  });
274
- };
275
- exports.extractVideoThumb = extractVideoThumb;
96
+ });
276
97
  const extractImageThumb = async (bufferOrFilePath, width = 32) => {
277
98
  var _a, _b;
278
99
  if (bufferOrFilePath instanceof stream_1.Readable) {
@@ -321,8 +142,8 @@ const encodeBase64EncodedStringForUpload = (b64) => (encodeURIComponent(b64
321
142
  .replace(/\=+$/, '')));
322
143
  exports.encodeBase64EncodedStringForUpload = encodeBase64EncodedStringForUpload;
323
144
  const generateProfilePicture = async (mediaUpload) => {
145
+ var _a, _b;
324
146
  let bufferOrFilePath;
325
- let img;
326
147
  if (Buffer.isBuffer(mediaUpload)) {
327
148
  bufferOrFilePath = mediaUpload;
328
149
  }
@@ -332,11 +153,29 @@ const generateProfilePicture = async (mediaUpload) => {
332
153
  else {
333
154
  bufferOrFilePath = await (0, exports.toBuffer)(mediaUpload.stream);
334
155
  }
335
- const jimp = await jimp_1.default.read(bufferOrFilePath);
336
- const cropped = jimp.getWidth() > jimp.getHeight() ? jimp.resize(550, -1) : jimp.resize(-1, 650);
337
- img = cropped
338
- .quality(100)
339
- .getBufferAsync(jimp_1.default.MIME_JPEG);
156
+ const lib = await getImageProcessingLibrary();
157
+ let img;
158
+ if ('sharp' in lib && typeof ((_a = lib.sharp) === null || _a === void 0 ? void 0 : _a.default) === 'function') {
159
+ img = lib.sharp.default(bufferOrFilePath)
160
+ .resize(640, 640)
161
+ .jpeg({
162
+ quality: 50,
163
+ })
164
+ .toBuffer();
165
+ }
166
+ else if ('jimp' in lib && typeof ((_b = lib.jimp) === null || _b === void 0 ? void 0 : _b.read) === 'function') {
167
+ const { read, MIME_JPEG, RESIZE_BILINEAR } = lib.jimp;
168
+ const jimp = await read(bufferOrFilePath);
169
+ const min = Math.min(jimp.getWidth(), jimp.getHeight());
170
+ const cropped = jimp.crop(0, 0, min, min);
171
+ img = cropped
172
+ .quality(50)
173
+ .resize(640, 640, RESIZE_BILINEAR)
174
+ .getBufferAsync(MIME_JPEG);
175
+ }
176
+ else {
177
+ throw new boom_1.Boom('No image processing library available');
178
+ }
340
179
  return {
341
180
  img: await img,
342
181
  };
@@ -349,60 +188,138 @@ const mediaMessageSHA256B64 = (message) => {
349
188
  };
350
189
  exports.mediaMessageSHA256B64 = mediaMessageSHA256B64;
351
190
  async function getAudioDuration(buffer) {
352
- const musicMetadata = await Promise.resolve().then(() => __importStar(require('music-metadata')));
353
- let metadata;
354
- const options = {
355
- duration: true
356
- };
357
- if (Buffer.isBuffer(buffer)) {
358
- metadata = await musicMetadata.parseBuffer(buffer, undefined, options);
359
- }
360
- else if (typeof buffer === 'string') {
361
- metadata = await musicMetadata.parseFile(buffer, options);
362
- }
363
- else {
364
- metadata = await musicMetadata.parseStream(buffer, undefined, options);
191
+ try {
192
+ const { PassThrough } = require('stream');
193
+ const ff = require('fluent-ffmpeg');
194
+
195
+ return await new Promise((resolve, reject) => {
196
+ const inputStream = new PassThrough();
197
+ inputStream.end(buffer);
198
+
199
+ ff(inputStream)
200
+ .ffprobe((err, data) => {
201
+ if (err) reject(err);
202
+ else resolve(data.format.duration);
203
+ });
204
+ });
205
+ } catch (error) {
206
+ const musicMetadata = await import('music-metadata');
207
+ let metadata;
208
+ if (Buffer.isBuffer(buffer)) {
209
+ metadata = await musicMetadata.parseBuffer(buffer, undefined, {
210
+ duration: true
211
+ });
212
+ } else if (typeof buffer === 'string') {
213
+ const rStream = (0, fs_1.createReadStream)(buffer);
214
+ try {
215
+ metadata = await musicMetadata.parseStream(rStream, undefined, {
216
+ duration: true
217
+ });
218
+ } finally {
219
+ rStream.destroy();
220
+ }
221
+ } else {
222
+ metadata = await musicMetadata.parseStream(buffer, undefined, {
223
+ duration: true
224
+ });
225
+ }
226
+ return metadata.format.duration;
365
227
  }
366
- return metadata.format.duration;
367
228
  }
229
+ exports.getAudioDuration = getAudioDuration;
368
230
  async function getAudioWaveform(buffer, logger) {
369
231
  try {
370
- const { default: decodeAudio } = await import('audio-decode')
232
+ const { PassThrough } = require('stream');
233
+ const ff = require('fluent-ffmpeg');
234
+
371
235
  let audioData;
372
236
  if (Buffer.isBuffer(buffer)) {
373
237
  audioData = buffer;
238
+ } else if (typeof buffer === 'string') {
239
+ const rStream = require('fs').createReadStream(buffer);
240
+ audioData = await exports.toBuffer(rStream);
241
+ } else {
242
+ audioData = await exports.toBuffer(buffer);
374
243
  }
375
- else if (typeof buffer === 'string') {
376
- const rStream = (0, fs_1.createReadStream)(buffer);
377
- audioData = await (0, exports.toBuffer)(rStream);
378
- }
379
- else {
380
- audioData = await (0, exports.toBuffer)(buffer);
381
- }
382
- const audioBuffer = await decodeAudio(audioData);
383
- const rawData = audioBuffer.getChannelData(0); // We only need to work with one channel of data
384
- const samples = 64; // Number of samples we want to have in our final data set
385
- const blockSize = Math.floor(rawData.length / samples); // the number of samples in each subdivision
386
- const filteredData = [];
387
- for (let i = 0; i < samples; i++) {
388
- const blockStart = blockSize * i; // the location of the first sample in the block
389
- let sum = 0;
390
- for (let j = 0; j < blockSize; j++) {
391
- sum = sum + Math.abs(rawData[blockStart + j]); // find the sum of all the samples in the block
392
- }
393
- filteredData.push(sum / blockSize); // divide the sum by the block size to get the average
394
- }
395
- // This guarantees that the largest data point will be set to 1, and the rest of the data will scale proportionally.
396
- const multiplier = Math.pow(Math.max(...filteredData), -1);
397
- const normalizedData = filteredData.map((n) => n * multiplier);
398
- // Generate waveform like WhatsApp
399
- const waveform = new Uint8Array(normalizedData.map((n) => Math.floor(100 * n)));
400
- return waveform;
244
+
245
+ return await new Promise((resolve, reject) => {
246
+ const inputStream = new PassThrough();
247
+ inputStream.end(audioData);
248
+ const chunks = [];
249
+ const bars = 64;
250
+
251
+ ff(inputStream)
252
+ .audioChannels(1)
253
+ .audioFrequency(16000)
254
+ .format('s16le')
255
+ .on('error', reject)
256
+ .on('end', () => {
257
+ const rawData = Buffer.concat(chunks);
258
+ const samples = rawData.length / 2;
259
+ const amplitudes = [];
260
+
261
+ for (let i = 0; i < samples; i++) {
262
+ amplitudes.push(Math.abs(rawData.readInt16LE(i * 2)) / 32768);
263
+ }
264
+
265
+ const blockSize = Math.floor(amplitudes.length / bars);
266
+ const avg = [];
267
+ for (let i = 0; i < bars; i++) {
268
+ const block = amplitudes.slice(i * blockSize, (i + 1) * blockSize);
269
+ avg.push(block.reduce((a, b) => a + b, 0) / block.length);
270
+ }
271
+
272
+ const max = Math.max(...avg);
273
+ const normalized = avg.map(v => Math.floor((v / max) * 100));
274
+ resolve(new Uint8Array(normalized));
275
+ })
276
+ .pipe()
277
+ .on('data', chunk => chunks.push(chunk));
278
+ });
279
+ } catch (e) {
280
+ logger?.debug(e);
401
281
  }
402
- catch (e) {
403
- logger === null || logger === void 0 ? void 0 : logger.debug('Failed to generate waveform: ' + e);
282
+ }
283
+ exports.getAudioWaveform = getAudioWaveform;
284
+ async function convertToOpusBuffer(buffer, logger) {
285
+ try {
286
+ const { PassThrough } = require('stream');
287
+ const ff = require('fluent-ffmpeg');
288
+
289
+ return await new Promise((resolve, reject) => {
290
+ const inStream = new PassThrough();
291
+ const outStream = new PassThrough();
292
+ const chunks = [];
293
+ inStream.end(buffer);
294
+
295
+ ff(inStream)
296
+ .noVideo()
297
+ .audioCodec('libopus')
298
+ .format('ogg')
299
+ .audioBitrate('48k')
300
+ .audioChannels(1)
301
+ .audioFrequency(48000)
302
+ .outputOptions([
303
+ '-vn',
304
+ '-b:a 64k',
305
+ '-ac 2',
306
+ '-ar 48000',
307
+ '-map_metadata', '-1',
308
+ '-application', 'voip'
309
+ ])
310
+ .on('error', reject)
311
+ .on('end', () => resolve(Buffer.concat(chunks)))
312
+ .pipe(outStream, {
313
+ end: true
314
+ });
315
+ outStream.on('data', c => chunks.push(c));
316
+ });
317
+ } catch (e) {
318
+ logger?.debug(e);
319
+ throw e;
404
320
  }
405
321
  }
322
+ exports.convertToOpusBuffer = convertToOpusBuffer;
406
323
  const toReadable = (buffer) => {
407
324
  const readable = new stream_1.Readable({ read: () => { } });
408
325
  readable.push(buffer);
@@ -448,28 +365,12 @@ async function generateThumbnail(file, mediaType, options) {
448
365
  }
449
366
  }
450
367
  else if (mediaType === 'video') {
368
+ const imgFilename = (0, path_1.join)(getTmpFilesDirectory(), (0, generics_1.generateMessageID)() + '.jpg');
451
369
  try {
452
- let videoPath = file;
453
- if (Buffer.isBuffer(file) || file instanceof stream_1.Readable) {
454
- videoPath = (0, path_1.join)(getTmpFilesDirectory(), (0, generics_1.generateMessageIDV2)() + '.mp4');
455
- const buffer = Buffer.isBuffer(file) ? file : await (0, exports.toBuffer)(file);
456
- await fs_1.promises.writeFile(videoPath, buffer);
457
- }
458
- const thumbnailBuffer = await (0, exports.extractVideoThumb)(videoPath);
459
- const imgFilename = (0, path_1.join)(getTmpFilesDirectory(), (0, generics_1.generateMessageIDV2)() + '.jpg');
460
- await fs_1.promises.writeFile(imgFilename, thumbnailBuffer);
461
- const { buffer: processedThumbnailBuffer, original } = await (0, exports.extractImageThumb)(imgFilename);
462
- thumbnail = processedThumbnailBuffer.toString('base64');
463
- if (original.width && original.height) {
464
- originalImageDimensions = {
465
- width: original.width,
466
- height: original.height,
467
- };
468
- }
370
+ await extractVideoThumb(file, imgFilename, '00:00:00', { width: 32, height: 32 });
371
+ const buff = await fs_1.promises.readFile(imgFilename);
372
+ thumbnail = buff.toString('base64');
469
373
  await fs_1.promises.unlink(imgFilename);
470
- if (videoPath !== file) {
471
- await fs_1.promises.unlink(videoPath);
472
- }
473
374
  }
474
375
  catch (err) {
475
376
  (_a = options.logger) === null || _a === void 0 ? void 0 : _a.debug('could not generate video thumb: ' + err);
@@ -480,8 +381,10 @@ async function generateThumbnail(file, mediaType, options) {
480
381
  originalImageDimensions
481
382
  };
482
383
  }
384
+ exports.generateThumbnail = generateThumbnail;
483
385
  const getHttpStream = async (url, options = {}) => {
484
- const fetched = await axios_1.default.get(url.toString(), { ...options, responseType: 'stream' });
386
+ const { default: axios } = await import('axios');
387
+ const fetched = await axios.get(url.toString(), { ...options, responseType: 'stream' });
485
388
  return fetched.data;
486
389
  };
487
390
  exports.getHttpStream = getHttpStream;
@@ -496,7 +399,7 @@ const prepareStream = async (media, mediaType, { logger, saveOriginalFileIfRequi
496
399
  bodyPath = media.url;
497
400
  }
498
401
  else if (saveOriginalFileIfRequired) {
499
- bodyPath = (0, path_1.join)(getTmpFilesDirectory(), mediaType + (0, generics_1.generateMessageIDV2)());
402
+ bodyPath = (0, path_1.join)(getTmpFilesDirectory(), mediaType + (0, generics_1.generateMessageID)());
500
403
  (0, fs_1.writeFileSync)(bodyPath, buffer);
501
404
  didSaveToTmpPath = true;
502
405
  }
@@ -515,6 +418,7 @@ const prepareStream = async (media, mediaType, { logger, saveOriginalFileIfRequi
515
418
  };
516
419
  }
517
420
  catch (error) {
421
+ // destroy all streams with error
518
422
  stream.destroy();
519
423
  if (didSaveToTmpPath) {
520
424
  try {
@@ -528,30 +432,45 @@ const prepareStream = async (media, mediaType, { logger, saveOriginalFileIfRequi
528
432
  }
529
433
  };
530
434
  exports.prepareStream = prepareStream;
531
- const encryptedStream = async (media, mediaType, { logger, saveOriginalFileIfRequired, opts } = {}) => {
435
+ const encryptedStream = async (media, mediaType, { logger, saveOriginalFileIfRequired, opts, isPtt, forceOpus } = {}) => {
532
436
  const { stream, type } = await (0, exports.getStream)(media, opts);
533
- logger === null || logger === void 0 ? void 0 : logger.debug('fetched media stream');
437
+
438
+ let finalStream = stream;
439
+ if (mediaType === 'audio' && (isPtt === true || forceOpus === true)) {
440
+ try {
441
+ const buffer = await (0, exports.toBuffer)(stream);
442
+ const opusBuffer = await exports.convertToOpusBuffer(buffer, logger);
443
+ finalStream = (0, exports.toReadable)(opusBuffer);
444
+ } catch (error) {
445
+ const { stream: newStream } = await (0, exports.getStream)(media, opts);
446
+ finalStream = newStream;
447
+ }
448
+ }
449
+
534
450
  const mediaKey = Crypto.randomBytes(32);
535
- const { cipherKey, iv, macKey } = await getMediaKeys(mediaKey, mediaType);
451
+ const { cipherKey, iv, macKey } = getMediaKeys(mediaKey, mediaType);
536
452
  const encWriteStream = new stream_1.Readable({ read: () => { } });
537
453
  let bodyPath;
538
454
  let writeStream;
539
455
  let didSaveToTmpPath = false;
456
+
540
457
  if (type === 'file') {
541
458
  bodyPath = media.url;
542
459
  }
543
460
  else if (saveOriginalFileIfRequired) {
544
- bodyPath = (0, path_1.join)(getTmpFilesDirectory(), mediaType + (0, generics_1.generateMessageIDV2)());
461
+ bodyPath = (0, path_1.join)(getTmpFilesDirectory(), mediaType + (0, generics_1.generateMessageID)());
545
462
  writeStream = (0, fs_1.createWriteStream)(bodyPath);
546
463
  didSaveToTmpPath = true;
547
464
  }
465
+
548
466
  let fileLength = 0;
549
467
  const aes = Crypto.createCipheriv('aes-256-cbc', cipherKey, iv);
550
468
  let hmac = Crypto.createHmac('sha256', macKey).update(iv);
551
469
  let sha256Plain = Crypto.createHash('sha256');
552
470
  let sha256Enc = Crypto.createHash('sha256');
471
+
553
472
  try {
554
- for await (const data of stream) {
473
+ for await (const data of finalStream) {
555
474
  fileLength += data.length;
556
475
  if (type === 'remote'
557
476
  && (opts === null || opts === void 0 ? void 0 : opts.maxContentLength)
@@ -560,6 +479,7 @@ const encryptedStream = async (media, mediaType, { logger, saveOriginalFileIfReq
560
479
  data: { media, type }
561
480
  });
562
481
  }
482
+
563
483
  sha256Plain = sha256Plain.update(data);
564
484
  if (writeStream) {
565
485
  if (!writeStream.write(data)) {
@@ -568,16 +488,18 @@ const encryptedStream = async (media, mediaType, { logger, saveOriginalFileIfReq
568
488
  }
569
489
  onChunk(aes.update(data));
570
490
  }
491
+
571
492
  onChunk(aes.final());
572
493
  const mac = hmac.digest().slice(0, 10);
573
494
  sha256Enc = sha256Enc.update(mac);
574
495
  const fileSha256 = sha256Plain.digest();
575
496
  const fileEncSha256 = sha256Enc.digest();
497
+
576
498
  encWriteStream.push(mac);
577
499
  encWriteStream.push(null);
578
500
  writeStream === null || writeStream === void 0 ? void 0 : writeStream.end();
579
- stream.destroy();
580
- logger === null || logger === void 0 ? void 0 : logger.debug('encrypted data successfully');
501
+ finalStream.destroy();
502
+
581
503
  return {
582
504
  mediaKey,
583
505
  encWriteStream,
@@ -596,17 +518,18 @@ const encryptedStream = async (media, mediaType, { logger, saveOriginalFileIfReq
596
518
  hmac.destroy();
597
519
  sha256Plain.destroy();
598
520
  sha256Enc.destroy();
599
- stream.destroy();
521
+ finalStream.destroy();
522
+
600
523
  if (didSaveToTmpPath) {
601
524
  try {
602
525
  await fs_1.promises.unlink(bodyPath);
603
526
  }
604
527
  catch (err) {
605
- logger === null || logger === void 0 ? void 0 : logger.error({ err }, 'failed to save to tmp path');
606
528
  }
607
529
  }
608
530
  throw error;
609
531
  }
532
+
610
533
  function onChunk(buff) {
611
534
  sha256Enc = sha256Enc.update(buff);
612
535
  hmac = hmac.update(buff);
@@ -621,20 +544,21 @@ const toSmallestChunkSize = (num) => {
621
544
  };
622
545
  const getUrlFromDirectPath = (directPath) => `https://${DEF_HOST}${directPath}`;
623
546
  exports.getUrlFromDirectPath = getUrlFromDirectPath;
624
- const downloadContentFromMessage = async ({ mediaKey, directPath, url }, type, opts = {}) => {
625
- const isValidMediaUrl = url === null || url === void 0 ? void 0 : url.startsWith('https://mmg.whatsapp.net/');
626
- const downloadUrl = isValidMediaUrl ? url : (0, exports.getUrlFromDirectPath)(directPath);
627
- if (!downloadUrl) {
628
- throw new boom_1.Boom('No valid media URL or directPath present in message', { statusCode: 400 });
629
- }
630
- const keys = await getMediaKeys(mediaKey, type);
547
+ const downloadContentFromMessage = ({ mediaKey, directPath, url }, type, opts = {}) => {
548
+ const downloadUrl = url || (0, exports.getUrlFromDirectPath)(directPath);
549
+ const keys = getMediaKeys(mediaKey, type);
631
550
  return (0, exports.downloadEncryptedContent)(downloadUrl, keys, opts);
632
551
  };
633
552
  exports.downloadContentFromMessage = downloadContentFromMessage;
553
+ /**
554
+ * Decrypts and downloads an AES256-CBC encrypted file given the keys.
555
+ * Assumes the SHA256 of the plaintext is appended to the end of the ciphertext
556
+ * */
634
557
  const downloadEncryptedContent = async (downloadUrl, { cipherKey, iv }, { startByte, endByte, options } = {}) => {
635
558
  let bytesFetched = 0;
636
559
  let startChunk = 0;
637
560
  let firstBlockIsIV = false;
561
+ // if a start byte is specified -- then we need to fetch the previous chunk as that will form the IV
638
562
  if (startByte) {
639
563
  const chunk = toSmallestChunkSize(startByte || 0);
640
564
  if (chunk) {
@@ -654,6 +578,7 @@ const downloadEncryptedContent = async (downloadUrl, { cipherKey, iv }, { startB
654
578
  headers.Range += endChunk;
655
579
  }
656
580
  }
581
+ // download the message
657
582
  const fetched = await (0, exports.getHttpStream)(downloadUrl, {
658
583
  ...options || {},
659
584
  headers,
@@ -686,6 +611,8 @@ const downloadEncryptedContent = async (downloadUrl, { cipherKey, iv }, { startB
686
611
  data = data.slice(AES_CHUNK_SIZE);
687
612
  }
688
613
  aes = Crypto.createDecipheriv('aes-256-cbc', cipherKey, ivValue);
614
+ // if an end byte that is not EOF is specified
615
+ // stop auto padding (PKCS7) -- otherwise throws an error for decryption
689
616
  if (endByte) {
690
617
  aes.setAutoPadding(false);
691
618
  }
@@ -726,9 +653,12 @@ function extensionForMediaMessage(message) {
726
653
  }
727
654
  return extension;
728
655
  }
656
+ exports.extensionForMediaMessage = extensionForMediaMessage;
729
657
  const getWAUploadToServer = ({ customUploadHosts, fetchAgent, logger, options }, refreshMediaConn) => {
730
658
  return async (stream, { mediaType, fileEncSha256B64, newsletter, timeoutMs }) => {
731
659
  var _a, _b;
660
+ const { default: axios } = await import('axios');
661
+ // send a query JSON to obtain the url & auth token to upload our media
732
662
  let uploadInfo = await refreshMediaConn(false);
733
663
  let urls;
734
664
  const hosts = [...customUploadHosts, ...uploadInfo.hosts];
@@ -746,14 +676,14 @@ const getWAUploadToServer = ({ customUploadHosts, fetchAgent, logger, options },
746
676
  }
747
677
  for (const { hostname, maxContentLengthBytes } of hosts) {
748
678
  logger.debug(`uploading to "${hostname}"`);
749
- const auth = encodeURIComponent(uploadInfo.auth);
679
+ const auth = encodeURIComponent(uploadInfo.auth); // the auth token
750
680
  const url = `https://${hostname}${media}/${fileEncSha256B64}?auth=${auth}&token=${fileEncSha256B64}`;
751
681
  let result;
752
682
  try {
753
683
  if (maxContentLengthBytes && reqBody.length > maxContentLengthBytes) {
754
684
  throw new boom_1.Boom(`Body too large for "${hostname}"`, { statusCode: 413 });
755
685
  }
756
- const body = await axios_1.default.post(url, reqBody, {
686
+ const body = await axios.post(url, reqBody, {
757
687
  ...options,
758
688
  headers: {
759
689
  ...options.headers || {},
@@ -781,7 +711,7 @@ const getWAUploadToServer = ({ customUploadHosts, fetchAgent, logger, options },
781
711
  }
782
712
  }
783
713
  catch (error) {
784
- if (axios_1.default.isAxiosError(error)) {
714
+ if (axios.isAxiosError(error)) {
785
715
  result = (_a = error.response) === null || _a === void 0 ? void 0 : _a.data;
786
716
  }
787
717
  const isLast = hostname === ((_b = hosts[uploadInfo.hosts.length - 1]) === null || _b === void 0 ? void 0 : _b.hostname);
@@ -798,11 +728,14 @@ exports.getWAUploadToServer = getWAUploadToServer;
798
728
  const getMediaRetryKey = (mediaKey) => {
799
729
  return (0, crypto_1.hkdf)(mediaKey, 32, { info: 'WhatsApp Media Retry Notification' });
800
730
  };
801
- const encryptMediaRetryRequest = async (key, mediaKey, meId) => {
731
+ /**
732
+ * Generate a binary node that will request the phone to re-upload the media & return the newly uploaded URL
733
+ */
734
+ const encryptMediaRetryRequest = (key, mediaKey, meId) => {
802
735
  const recp = { stanzaId: key.id };
803
736
  const recpBuffer = WAProto_1.proto.ServerErrorReceipt.encode(recp).finish();
804
737
  const iv = Crypto.randomBytes(12);
805
- const retryKey = await getMediaRetryKey(mediaKey);
738
+ const retryKey = getMediaRetryKey(mediaKey);
806
739
  const ciphertext = (0, crypto_1.aesEncryptGCM)(recpBuffer, retryKey, iv, Buffer.from(key.id));
807
740
  const req = {
808
741
  tag: 'receipt',
@@ -812,6 +745,9 @@ const encryptMediaRetryRequest = async (key, mediaKey, meId) => {
812
745
  type: 'server-error'
813
746
  },
814
747
  content: [
748
+ // this encrypt node is actually pretty useless
749
+ // the media is returned even without this node
750
+ // keeping it here to maintain parity with WA Web
815
751
  {
816
752
  tag: 'encrypt',
817
753
  attrs: {},
@@ -825,6 +761,7 @@ const encryptMediaRetryRequest = async (key, mediaKey, meId) => {
825
761
  attrs: {
826
762
  jid: key.remoteJid,
827
763
  'from_me': (!!key.fromMe).toString(),
764
+ // @ts-ignore
828
765
  participant: key.participant || undefined
829
766
  }
830
767
  }
@@ -862,8 +799,8 @@ const decodeMediaRetryNode = (node) => {
862
799
  return event;
863
800
  };
864
801
  exports.decodeMediaRetryNode = decodeMediaRetryNode;
865
- const decryptMediaRetryData = async ({ ciphertext, iv }, mediaKey, msgId) => {
866
- const retryKey = await getMediaRetryKey(mediaKey);
802
+ const decryptMediaRetryData = ({ ciphertext, iv }, mediaKey, msgId) => {
803
+ const retryKey = getMediaRetryKey(mediaKey);
867
804
  const plaintext = (0, crypto_1.aesDecryptGCM)(ciphertext, retryKey, iv, Buffer.from(msgId));
868
805
  return WAProto_1.proto.MediaRetryNotification.decode(plaintext);
869
806
  };
@@ -876,6 +813,7 @@ const MEDIA_RETRY_STATUS_MAP = {
876
813
  [WAProto_1.proto.MediaRetryNotification.ResultType.NOT_FOUND]: 404,
877
814
  [WAProto_1.proto.MediaRetryNotification.ResultType.GENERAL_ERROR]: 418,
878
815
  };
816
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
879
817
  function __importStar(arg0) {
880
818
  throw new Error('Function not implemented.');
881
- }
819
+ }