@baileys-md/baileys 12.0.0 → 12.2.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.
Files changed (109) hide show
  1. package/LICENSE +21 -13
  2. package/WAProto/WAProto.proto +769 -233
  3. package/WAProto/index.js +65813 -141372
  4. package/lib/Defaults/index.js +128 -113
  5. package/lib/KeyDB/BinarySearch.js +30 -0
  6. package/lib/KeyDB/KeyedDB.js +178 -0
  7. package/lib/KeyDB/index.js +14 -0
  8. package/lib/Signal/Group/ciphertext-message.js +22 -14
  9. package/lib/Signal/Group/group-session-builder.js +21 -42
  10. package/lib/Signal/Group/group_cipher.js +85 -87
  11. package/lib/Signal/Group/index.js +23 -57
  12. package/lib/Signal/Group/keyhelper.js +28 -52
  13. package/lib/Signal/Group/sender-chain-key.js +37 -33
  14. package/lib/Signal/Group/sender-key-distribution-message.js +73 -63
  15. package/lib/Signal/Group/sender-key-message.js +75 -66
  16. package/lib/Signal/Group/sender-key-name.js +55 -44
  17. package/lib/Signal/Group/sender-key-record.js +49 -49
  18. package/lib/Signal/Group/sender-key-state.js +90 -93
  19. package/lib/Signal/Group/sender-message-key.js +37 -28
  20. package/lib/Signal/libsignal.js +324 -163
  21. package/lib/Signal/lid-mapping.js +166 -0
  22. package/lib/Socket/Client/index.js +14 -18
  23. package/lib/Socket/Client/types.js +12 -12
  24. package/lib/Socket/Client/websocket.js +60 -109
  25. package/lib/Socket/business.js +359 -242
  26. package/lib/Socket/chats.js +857 -898
  27. package/lib/Socket/communities.js +413 -0
  28. package/lib/Socket/groups.js +304 -324
  29. package/lib/Socket/index.js +25 -9
  30. package/lib/Socket/messages-recv.js +1109 -1000
  31. package/lib/Socket/messages-send.js +615 -387
  32. package/lib/Socket/mex.js +45 -0
  33. package/lib/Socket/newsletter.js +231 -227
  34. package/lib/Socket/socket.js +803 -628
  35. package/lib/Store/index.js +18 -8
  36. package/lib/Store/make-cache-manager-store.js +75 -0
  37. package/lib/Store/make-in-memory-store.js +286 -435
  38. package/lib/Store/make-ordered-dictionary.js +77 -79
  39. package/lib/Store/object-repository.js +24 -26
  40. package/lib/Types/Auth.js +13 -2
  41. package/lib/Types/Bussines.js +13 -0
  42. package/lib/Types/Call.js +13 -2
  43. package/lib/Types/Chat.js +19 -4
  44. package/lib/Types/Contact.js +13 -2
  45. package/lib/Types/Events.js +13 -2
  46. package/lib/Types/GroupMetadata.js +13 -2
  47. package/lib/Types/Label.js +43 -26
  48. package/lib/Types/LabelAssociation.js +16 -8
  49. package/lib/Types/Message.js +22 -7
  50. package/lib/Types/Newsletter.js +42 -17
  51. package/lib/Types/Product.js +13 -2
  52. package/lib/Types/Signal.js +13 -2
  53. package/lib/Types/Socket.js +14 -2
  54. package/lib/Types/State.js +21 -2
  55. package/lib/Types/USync.js +13 -2
  56. package/lib/Types/index.js +37 -41
  57. package/lib/Utils/auth-utils.js +219 -189
  58. package/lib/Utils/baileys-event-stream.js +54 -0
  59. package/lib/Utils/browser-utils.js +21 -31
  60. package/lib/Utils/business.js +213 -214
  61. package/lib/Utils/chat-utils.js +711 -689
  62. package/lib/Utils/crypto.js +112 -175
  63. package/lib/Utils/decode-wa-message.js +254 -194
  64. package/lib/Utils/event-buffer.js +510 -500
  65. package/lib/Utils/generics.js +318 -430
  66. package/lib/Utils/history.js +83 -90
  67. package/lib/Utils/index.js +31 -35
  68. package/lib/Utils/link-preview.js +71 -116
  69. package/lib/Utils/logger.js +5 -7
  70. package/lib/Utils/lt-hash.js +40 -46
  71. package/lib/Utils/make-mutex.js +34 -41
  72. package/lib/Utils/message-retry-manager.js +33 -48
  73. package/lib/Utils/messages-media.js +516 -784
  74. package/lib/Utils/messages.js +347 -489
  75. package/lib/Utils/noise-handler.js +138 -144
  76. package/lib/Utils/pre-key-manager.js +95 -0
  77. package/lib/Utils/process-message.js +331 -384
  78. package/lib/Utils/signal.js +157 -139
  79. package/lib/Utils/use-multi-file-auth-state.js +119 -91
  80. package/lib/Utils/validate-connection.js +184 -203
  81. package/lib/WABinary/constants.js +1308 -1298
  82. package/lib/WABinary/decode.js +241 -256
  83. package/lib/WABinary/encode.js +217 -239
  84. package/lib/WABinary/generic-utils.js +131 -40
  85. package/lib/WABinary/index.js +17 -21
  86. package/lib/WABinary/jid-utils.js +97 -79
  87. package/lib/WABinary/types.js +13 -2
  88. package/lib/WAM/BinaryInfo.js +20 -12
  89. package/lib/WAM/constants.js +22863 -15348
  90. package/lib/WAM/encode.js +145 -136
  91. package/lib/WAM/index.js +15 -19
  92. package/lib/WAUSync/Protocols/USyncContactProtocol.js +39 -31
  93. package/lib/WAUSync/Protocols/USyncDeviceProtocol.js +61 -54
  94. package/lib/WAUSync/Protocols/USyncDisappearingModeProtocol.js +39 -29
  95. package/lib/WAUSync/Protocols/USyncStatusProtocol.js +48 -40
  96. package/lib/WAUSync/Protocols/UsyncBotProfileProtocol.js +62 -51
  97. package/lib/WAUSync/Protocols/UsyncLIDProtocol.js +38 -21
  98. package/lib/WAUSync/Protocols/index.js +17 -20
  99. package/lib/WAUSync/USyncQuery.js +98 -86
  100. package/lib/WAUSync/USyncUser.js +35 -26
  101. package/lib/WAUSync/index.js +16 -19
  102. package/lib/index.js +23 -41
  103. package/package.json +46 -56
  104. package/README.md +0 -113
  105. package/WAProto/GenerateStatics.sh +0 -4
  106. package/lib/Defaults/wileys-version.json +0 -3
  107. package/lib/Signal/Group/queue-job.js +0 -57
  108. package/lib/Socket/usync.js +0 -70
  109. package/lib/Utils/wileys-event-stream.js +0 -63
@@ -1,326 +1,108 @@
1
- "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
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 };
37
- };
38
- 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;
47
- 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"));
51
- const Crypto = __importStar(require("crypto"));
52
- const events_1 = require("events");
53
- const fs_1 = require("fs");
54
- const os_1 = require("os");
55
- const path_1 = require("path");
56
- const jimp_1 = __importDefault(require("jimp"));
57
- const stream_1 = require("stream");
58
- const child_process_1 = require("child_process");
59
- const WAProto_1 = require("../../WAProto");
60
- const Defaults_1 = require("../Defaults");
61
- const WABinary_1 = require("../WABinary");
62
- const crypto_1 = require("./crypto");
63
- const generics_1 = require("./generics");
64
- const getTmpFilesDirectory = () => (0, os_1.tmpdir)();
65
- const getImageProcessingLibrary = async () => {
66
- const [_jimp, sharp] = await Promise.all([
67
- (async () => {
68
- const jimp = await (Promise.resolve().then(() => __importStar(require('jimp'))).catch(() => { }));
69
- return jimp;
70
- })(),
71
- (async () => {
72
- const sharp = await (Promise.resolve().then(() => __importStar(require('sharp'))).catch(() => { }));
73
- return sharp;
74
- })()
75
- ]);
76
- if (sharp) {
77
- return { sharp };
78
- }
79
- const jimp = (_jimp === null || _jimp === void 0 ? void 0 : _jimp.default) || _jimp;
80
- if (jimp) {
81
- return { jimp };
82
- }
83
- throw new boom_1.Boom('No image processing library available');
84
- };
85
- const hkdfInfoKey = (type) => {
86
- const hkdfInfo = Defaults_1.MEDIA_HKDF_KEY_MAPPING[type];
87
- return `WhatsApp ${hkdfInfo} Keys`;
1
+ //=======================================================//
2
+ import { getBinaryNodeChild, getBinaryNodeChildBuffer, jidNormalizedUser } from "../WABinary/index.js";
3
+ import { DEFAULT_ORIGIN, MEDIA_HKDF_KEY_MAPPING, MEDIA_PATH_MAP } from "../Defaults/index.js";
4
+ import { createReadStream, createWriteStream, promises as fs, WriteStream } from "fs";
5
+ import { aesDecryptGCM, aesEncryptGCM, hkdf } from "./crypto.js";
6
+ import { generateMessageIDV2 } from "./generics.js";
7
+ import { proto } from "../../WAProto/index.js";
8
+ import { Readable, Transform } from "stream";
9
+ import { exec } from "child_process";
10
+ import { Boom } from "@hapi/boom";
11
+ import * as Crypto from "crypto";
12
+ import { once } from "events";
13
+ import { tmpdir } from "os";
14
+ import { join } from "path";
15
+ import { URL } from "url";
16
+ import { createRequire } from "module";
17
+ const _require = createRequire(import.meta.url);
18
+ const Jimp = _require("jimp");
19
+
20
+ //=======================================================//
21
+ const getTmpFilesDirectory = () => tmpdir();
22
+ //=======================================================//
23
+ export const hkdfInfoKey = (type) => {
24
+ const hkdfInfo = MEDIA_HKDF_KEY_MAPPING[type];
25
+ return `WhatsApp ${hkdfInfo} Keys`;
88
26
  };
89
- exports.hkdfInfoKey = hkdfInfoKey;
90
- /** generates all the keys required to encrypt/decrypt & sign a media message */
91
- async function getMediaKeys(buffer, mediaType) {
92
- if (!buffer) {
93
- throw new boom_1.Boom('Cannot derive from empty media key');
94
- }
95
- if (typeof buffer === 'string') {
96
- buffer = Buffer.from(buffer.replace('data:;base64,', ''), 'base64');
97
- }
98
- // 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) });
27
+ //=======================================================//
28
+ export const getRawMediaUploadData = async (media, mediaType, logger) => {
29
+ const { stream } = await getStream(media);
30
+ logger?.debug("got stream for raw upload");
31
+ const hasher = Crypto.createHash("sha256");
32
+ const filePath = join(tmpdir(), mediaType + generateMessageIDV2());
33
+ const fileWriteStream = createWriteStream(filePath);
34
+ let fileLength = 0;
35
+ try {
36
+ for await (const data of stream) {
37
+ fileLength += data.length;
38
+ hasher.update(data);
39
+ if (!fileWriteStream.write(data)) {
40
+ await once(fileWriteStream, "drain");
41
+ }
42
+ }
43
+ fileWriteStream.end();
44
+ await once(fileWriteStream, "finish");
45
+ stream.destroy();
46
+ const fileSha256 = hasher.digest();
47
+ logger?.debug("hashed data for raw upload");
100
48
  return {
101
- iv: expandedMediaKey.slice(0, 16),
102
- cipherKey: expandedMediaKey.slice(16, 48),
103
- macKey: expandedMediaKey.slice(48, 80),
49
+ filePath: filePath,
50
+ fileSha256,
51
+ fileLength
104
52
  };
105
- }
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) {
53
+ }
54
+ catch (error) {
55
+ fileWriteStream.destroy();
56
+ stream.destroy();
211
57
  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;
230
- }
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
- }
239
- }
240
- return imageUrl;
58
+ await fs.unlink(filePath);
241
59
  }
242
- catch (error) {
243
- throw new Error("Failed to convert video to JPG: " + error.message);
60
+ catch {
244
61
  }
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
- });
62
+ throw error;
63
+ }
274
64
  };
275
- exports.extractVideoThumb = extractVideoThumb;
276
- const extractImageThumb = async (bufferOrFilePath, width = 32) => {
277
- var _a, _b;
278
- if (bufferOrFilePath instanceof stream_1.Readable) {
279
- bufferOrFilePath = await (0, exports.toBuffer)(bufferOrFilePath);
280
- }
281
- const lib = await getImageProcessingLibrary();
282
- if ('sharp' in lib && typeof ((_a = lib.sharp) === null || _a === void 0 ? void 0 : _a.default) === 'function') {
283
- const img = lib.sharp.default(bufferOrFilePath);
284
- const dimensions = await img.metadata();
285
- const buffer = await img
286
- .resize(width)
287
- .jpeg({ quality: 50 })
288
- .toBuffer();
289
- return {
290
- buffer,
291
- original: {
292
- width: dimensions.width,
293
- height: dimensions.height,
294
- },
295
- };
296
- }
297
- else if ('jimp' in lib && typeof ((_b = lib.jimp) === null || _b === void 0 ? void 0 : _b.read) === 'function') {
298
- const { read, MIME_JPEG, RESIZE_BILINEAR, AUTO } = lib.jimp;
299
- const jimp = await read(bufferOrFilePath);
300
- const dimensions = {
301
- width: jimp.getWidth(),
302
- height: jimp.getHeight()
303
- };
304
- const buffer = await jimp
305
- .quality(50)
306
- .resize(width, AUTO, RESIZE_BILINEAR)
307
- .getBufferAsync(MIME_JPEG);
308
- return {
309
- buffer,
310
- original: dimensions
311
- };
65
+ //=======================================================//
66
+ export async function getMediaKeys(buffer, mediaType) {
67
+ if (!buffer) {
68
+ throw new Boom("Cannot derive from empty media key");
69
+ }
70
+ if (typeof buffer === "string") {
71
+ buffer = Buffer.from(buffer.replace("data:;base64,", ""), "base64");
72
+ }
73
+ const expandedMediaKey = await hkdf(buffer, 112, { info: hkdfInfoKey(mediaType) });
74
+ return {
75
+ iv: expandedMediaKey.slice(0, 16),
76
+ cipherKey: expandedMediaKey.slice(16, 48),
77
+ macKey: expandedMediaKey.slice(48, 80)
78
+ };
79
+ }
80
+ //=======================================================//
81
+ const extractVideoThumb = async (path, destPath, time, size) => new Promise((resolve, reject) => {
82
+ const cmd = `ffmpeg -ss ${time} -i ${path} -y -vf scale=${size.width}:-1 -vframes 1 -f image2 ${destPath}`;
83
+ exec(cmd, err => {
84
+ if (err) {
85
+ reject(err);
312
86
  }
313
87
  else {
314
- throw new boom_1.Boom('No image processing library available');
88
+ resolve();
315
89
  }
90
+ });
91
+ });
92
+ //=======================================================//
93
+ export const extractImageThumb = async (bufferOrFilePath, width = 32) => {
94
+ if (bufferOrFilePath instanceof Readable) {
95
+ bufferOrFilePath = await toBuffer(bufferOrFilePath);
96
+ }
97
+ const image = await Jimp.read(bufferOrFilePath);
98
+ const dimensions = { width: image.bitmap.width, height: image.bitmap.height };
99
+ const resized = image.resize(width, Jimp.RESIZE_BILINEAR).quality(50);
100
+ const buffer = await resized.getBufferAsync(Jimp.MIME_JPEG);
101
+ return { buffer, original: dimensions };
316
102
  };
317
- exports.extractImageThumb = extractImageThumb;
318
- const encodeBase64EncodedStringForUpload = (b64) => (encodeURIComponent(b64
319
- .replace(/\+/g, '-')
320
- .replace(/\//g, '_')
321
- .replace(/\=+$/, '')));
322
- exports.encodeBase64EncodedStringForUpload = encodeBase64EncodedStringForUpload;
323
- const generateProfilePicture = async (mediaUpload) => {
103
+ //=======================================================//
104
+ export const encodeBase64EncodedStringForUpload = (b64) => encodeURIComponent(b64.replace(/\+/g, "-").replace(/\//g, "_").replace(/\=+$/, ""));
105
+ export const generateProfilePicture = async (mediaUpload) => {
324
106
  let bufferOrFilePath;
325
107
  let img;
326
108
  if (Buffer.isBuffer(mediaUpload)) {
@@ -332,42 +114,42 @@ const generateProfilePicture = async (mediaUpload) => {
332
114
  else {
333
115
  bufferOrFilePath = await (0, exports.toBuffer)(mediaUpload.stream);
334
116
  }
335
- const jimp = await jimp_1.default.read(bufferOrFilePath);
117
+ const jimp = await Jimp.read(bufferOrFilePath);
336
118
  const cropped = jimp.getWidth() > jimp.getHeight() ? jimp.resize(550, -1) : jimp.resize(-1, 650);
337
119
  img = cropped
338
120
  .quality(100)
339
- .getBufferAsync(jimp_1.default.MIME_JPEG);
121
+ .getBufferAsync(Jimp.MIME_JPEG);
340
122
  return {
341
123
  img: await img,
342
124
  };
343
125
  };
344
- exports.generateProfilePicture = generateProfilePicture;
345
- /** gets the SHA256 of the given media message */
346
- const mediaMessageSHA256B64 = (message) => {
347
- const media = Object.values(message)[0];
348
- return (media === null || media === void 0 ? void 0 : media.fileSha256) && Buffer.from(media.fileSha256).toString('base64');
126
+ //=======================================================//
127
+ export const mediaMessageSHA256B64 = (message) => {
128
+ const media = Object.values(message)[0];
129
+ return media?.fileSha256 && Buffer.from(media.fileSha256).toString("base64");
349
130
  };
350
- exports.mediaMessageSHA256B64 = mediaMessageSHA256B64;
351
- 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);
365
- }
366
- return metadata.format.duration;
131
+ //=======================================================//
132
+ export async function getAudioDuration(buffer) {
133
+ const musicMetadata = await import("music-metadata");
134
+ let metadata;
135
+ const options = {
136
+ duration: true
137
+ };
138
+ if (Buffer.isBuffer(buffer)) {
139
+ metadata = await musicMetadata.parseBuffer(buffer, undefined, options);
140
+ }
141
+ else if (typeof buffer === "string") {
142
+ metadata = await musicMetadata.parseFile(buffer, options);
143
+ }
144
+ else {
145
+ metadata = await musicMetadata.parseStream(buffer, undefined, options);
146
+ }
147
+ return metadata.format.duration;
367
148
  }
368
- async function getAudioWaveform(buffer, logger) {
149
+ //=======================================================//
150
+ export async function getAudioWaveform(buffer, logger) {
369
151
  try {
370
- const { default: decoder } = await eval('import(\'audio-decode\')');
152
+ const { default: decoder } = await import('audio-decode')
371
153
  let audioData;
372
154
  if (Buffer.isBuffer(buffer)) {
373
155
  audioData = buffer;
@@ -380,20 +162,22 @@ async function getAudioWaveform(buffer, logger) {
380
162
  audioData = await (0, exports.toBuffer)(buffer);
381
163
  }
382
164
  const audioBuffer = await decoder(audioData);
383
- const rawData = audioBuffer.getChannelData(0);
384
- const samples = 64;
385
- const blockSize = Math.floor(rawData.length / samples);
165
+ const rawData = audioBuffer.getChannelData(0); // We only need to work with one channel of data
166
+ const samples = 64; // Number of samples we want to have in our final data set
167
+ const blockSize = Math.floor(rawData.length / samples); // the number of samples in each subdivision
386
168
  const filteredData = [];
387
169
  for (let i = 0; i < samples; i++) {
388
- const blockStart = blockSize * i;
170
+ const blockStart = blockSize * i; // the location of the first sample in the block
389
171
  let sum = 0;
390
172
  for (let j = 0; j < blockSize; j++) {
391
- sum = sum + Math.abs(rawData[blockStart + j]);
173
+ sum = sum + Math.abs(rawData[blockStart + j]); // find the sum of all the samples in the block
392
174
  }
393
- filteredData.push(sum / blockSize);
175
+ filteredData.push(sum / blockSize); // divide the sum by the block size to get the average
394
176
  }
177
+ // This guarantees that the largest data point will be set to 1, and the rest of the data will scale proportionally.
395
178
  const multiplier = Math.pow(Math.max(...filteredData), -1);
396
179
  const normalizedData = filteredData.map((n) => n * multiplier);
180
+ // Generate waveform like WhatsApp
397
181
  const waveform = new Uint8Array(normalizedData.map((n) => Math.floor(100 * n)));
398
182
  return waveform;
399
183
  }
@@ -401,479 +185,427 @@ async function getAudioWaveform(buffer, logger) {
401
185
  logger === null || logger === void 0 ? void 0 : logger.debug('Failed to generate waveform: ' + e);
402
186
  }
403
187
  }
404
- const toReadable = (buffer) => {
405
- const readable = new stream_1.Readable({ read: () => { } });
406
- readable.push(buffer);
407
- readable.push(null);
408
- return readable;
188
+ //=======================================================//
189
+ export const toReadable = (buffer) => {
190
+ const readable = new Readable({ read: () => { } });
191
+ readable.push(buffer);
192
+ readable.push(null);
193
+ return readable;
409
194
  };
410
- exports.toReadable = toReadable;
411
- const toBuffer = async (stream) => {
412
- const chunks = [];
413
- for await (const chunk of stream) {
414
- chunks.push(chunk);
415
- }
416
- stream.destroy();
417
- return Buffer.concat(chunks);
195
+ //=======================================================//
196
+ export const toBuffer = async (stream) => {
197
+ const chunks = [];
198
+ for await (const chunk of stream) {
199
+ chunks.push(chunk);
200
+ }
201
+ stream.destroy();
202
+ return Buffer.concat(chunks);
418
203
  };
419
- exports.toBuffer = toBuffer;
420
- const getStream = async (item, opts) => {
421
- if (Buffer.isBuffer(item)) {
422
- return { stream: (0, exports.toReadable)(item), type: 'buffer' };
423
- }
424
- if ('stream' in item) {
425
- return { stream: item.stream, type: 'readable' };
426
- }
427
- if (item.url.toString().startsWith('http://') || item.url.toString().startsWith('https://')) {
428
- return { stream: await (0, exports.getHttpStream)(item.url, opts), type: 'remote' };
429
- }
430
- return { stream: (0, fs_1.createReadStream)(item.url), type: 'file' };
204
+ //=======================================================//
205
+ export const getStream = async (item, opts) => {
206
+ if (Buffer.isBuffer(item)) {
207
+ return { stream: toReadable(item), type: "buffer" };
208
+ }
209
+ if ("stream" in item) {
210
+ return { stream: item.stream, type: "readable" };
211
+ }
212
+ const urlStr = item.url.toString();
213
+ if (urlStr.startsWith("data:")) {
214
+ const buffer = Buffer.from(urlStr.split(",")[1], "base64");
215
+ return { stream: toReadable(buffer), type: "buffer" };
216
+ }
217
+ if (urlStr.startsWith("http://") || urlStr.startsWith("https://")) {
218
+ return { stream: await getHttpStream(item.url, opts), type: "remote" };
219
+ }
220
+ return { stream: createReadStream(item.url), type: "file" };
431
221
  };
432
- exports.getStream = getStream;
433
- /** generates a thumbnail for a given media, if required */
434
- async function generateThumbnail(file, mediaType, options) {
435
- var _a;
436
- let thumbnail;
437
- let originalImageDimensions;
438
- if (mediaType === 'image') {
439
- const { buffer, original } = await (0, exports.extractImageThumb)(file);
440
- thumbnail = buffer.toString('base64');
441
- if (original.width && original.height) {
442
- originalImageDimensions = {
443
- width: original.width,
444
- height: original.height,
445
- };
446
- }
447
- }
448
- else if (mediaType === 'video') {
449
- try {
450
- let videoPath = file;
451
- if (Buffer.isBuffer(file) || file instanceof stream_1.Readable) {
452
- videoPath = (0, path_1.join)(getTmpFilesDirectory(), (0, generics_1.generateMessageIDV2)() + '.mp4');
453
- const buffer = Buffer.isBuffer(file) ? file : await (0, exports.toBuffer)(file);
454
- await fs_1.promises.writeFile(videoPath, buffer);
455
- }
456
- const thumbnailBuffer = await (0, exports.extractVideoThumb)(videoPath);
457
- const imgFilename = (0, path_1.join)(getTmpFilesDirectory(), (0, generics_1.generateMessageIDV2)() + '.jpg');
458
- await fs_1.promises.writeFile(imgFilename, thumbnailBuffer);
459
- const { buffer: processedThumbnailBuffer, original } = await (0, exports.extractImageThumb)(imgFilename);
460
- thumbnail = processedThumbnailBuffer.toString('base64');
461
- if (original.width && original.height) {
462
- originalImageDimensions = {
463
- width: original.width,
464
- height: original.height,
465
- };
466
- }
467
- await fs_1.promises.unlink(imgFilename);
468
- if (videoPath !== file) {
469
- await fs_1.promises.unlink(videoPath);
470
- }
471
- }
472
- catch (err) {
473
- (_a = options.logger) === null || _a === void 0 ? void 0 : _a.debug('could not generate video thumb: ' + err);
474
- }
475
- }
476
- return {
477
- thumbnail,
478
- originalImageDimensions
479
- };
222
+ //=======================================================//
223
+ export async function generateThumbnail(file, mediaType, options) {
224
+ let thumbnail;
225
+ let originalImageDimensions;
226
+ if (mediaType === "image") {
227
+ const { buffer, original } = await extractImageThumb(file);
228
+ thumbnail = buffer.toString("base64");
229
+ if (original.width && original.height) {
230
+ originalImageDimensions = {
231
+ width: original.width,
232
+ height: original.height
233
+ };
234
+ }
235
+ }
236
+ else if (mediaType === "video") {
237
+ const imgFilename = join(getTmpFilesDirectory(), generateMessageIDV2() + ".jpg");
238
+ try {
239
+ await extractVideoThumb(file, imgFilename, "00:00:00", { width: 32, height: 32 });
240
+ const buff = await fs.readFile(imgFilename);
241
+ thumbnail = buff.toString("base64");
242
+ await fs.unlink(imgFilename);
243
+ }
244
+ catch (err) {
245
+ options.logger?.debug("could not generate video thumb: " + err);
246
+ }
247
+ }
248
+ return {
249
+ thumbnail,
250
+ originalImageDimensions
251
+ };
480
252
  }
481
- const getHttpStream = async (url, options = {}) => {
482
- const fetched = await axios_1.default.get(url.toString(), { ...options, responseType: 'stream' });
483
- return fetched.data;
253
+ //=======================================================//
254
+ export const getHttpStream = async (url, options = {}) => {
255
+ const response = await fetch(url.toString(), {
256
+ dispatcher: options.dispatcher,
257
+ method: "GET",
258
+ headers: options.headers
259
+ });
260
+ if (!response.ok) {
261
+ throw new Boom(`Failed to fetch stream from ${url}`, { statusCode: response.status, data: { url } });
262
+ }
263
+ return Readable.fromWeb(response.body);
484
264
  };
485
- exports.getHttpStream = getHttpStream;
486
- const prepareStream = async (media, mediaType, { logger, saveOriginalFileIfRequired, opts } = {}) => {
487
- const { stream, type } = await (0, exports.getStream)(media, opts);
488
- logger === null || logger === void 0 ? void 0 : logger.debug('fetched media stream');
489
- let bodyPath;
490
- let didSaveToTmpPath = false;
491
- try {
492
- const buffer = await (0, exports.toBuffer)(stream);
493
- if (type === 'file') {
494
- bodyPath = media.url;
495
- }
496
- else if (saveOriginalFileIfRequired) {
497
- bodyPath = (0, path_1.join)(getTmpFilesDirectory(), mediaType + (0, generics_1.generateMessageIDV2)());
498
- (0, fs_1.writeFileSync)(bodyPath, buffer);
499
- didSaveToTmpPath = true;
500
- }
501
- const fileLength = buffer.length;
502
- const fileSha256 = Crypto.createHash('sha256').update(buffer).digest();
503
- stream === null || stream === void 0 ? void 0 : stream.destroy();
504
- logger === null || logger === void 0 ? void 0 : logger.debug('prepare stream data successfully');
505
- return {
506
- mediaKey: undefined,
507
- encWriteStream: buffer,
508
- fileLength,
509
- fileSha256,
510
- fileEncSha256: undefined,
511
- bodyPath,
512
- didSaveToTmpPath
513
- };
514
- }
515
- catch (error) {
516
- stream.destroy();
517
- if (didSaveToTmpPath) {
518
- try {
519
- await fs_1.promises.unlink(bodyPath);
520
- }
521
- catch (err) {
522
- logger === null || logger === void 0 ? void 0 : logger.error({ err }, 'failed to save to tmp path');
523
- }
265
+ //=======================================================//
266
+ export const encryptedStream = async (media, mediaType, { logger, saveOriginalFileIfRequired, opts } = {}) => {
267
+ const { stream, type } = await getStream(media, opts);
268
+ logger?.debug("fetched media stream");
269
+ const mediaKey = Crypto.randomBytes(32);
270
+ const { cipherKey, iv, macKey } = await getMediaKeys(mediaKey, mediaType);
271
+ const encFilePath = join(getTmpFilesDirectory(), mediaType + generateMessageIDV2() + "-enc");
272
+ const encFileWriteStream = createWriteStream(encFilePath);
273
+ let originalFileStream;
274
+ let originalFilePath;
275
+ if (saveOriginalFileIfRequired) {
276
+ originalFilePath = join(getTmpFilesDirectory(), mediaType + generateMessageIDV2() + "-original");
277
+ originalFileStream = createWriteStream(originalFilePath);
278
+ }
279
+ let fileLength = 0;
280
+ const aes = Crypto.createCipheriv("aes-256-cbc", cipherKey, iv);
281
+ const hmac = Crypto.createHmac("sha256", macKey).update(iv);
282
+ const sha256Plain = Crypto.createHash("sha256");
283
+ const sha256Enc = Crypto.createHash("sha256");
284
+ const onChunk = (buff) => {
285
+ sha256Enc.update(buff);
286
+ hmac.update(buff);
287
+ encFileWriteStream.write(buff);
288
+ };
289
+ try {
290
+ for await (const data of stream) {
291
+ fileLength += data.length;
292
+ if (type === "remote" &&
293
+ opts?.maxContentLength &&
294
+ fileLength + data.length > opts.maxContentLength) {
295
+ throw new Boom(`content length exceeded when encrypting "${type}"`, {
296
+ data: { media, type }
297
+ });
298
+ }
299
+ if (originalFileStream) {
300
+ if (!originalFileStream.write(data)) {
301
+ await once(originalFileStream, "drain");
524
302
  }
525
- throw error;
526
- }
527
- };
528
- exports.prepareStream = prepareStream;
529
- const encryptedStream = async (media, mediaType, { logger, saveOriginalFileIfRequired, opts } = {}) => {
530
- const { stream, type } = await (0, exports.getStream)(media, opts);
531
- logger === null || logger === void 0 ? void 0 : logger.debug('fetched media stream');
532
- const mediaKey = Crypto.randomBytes(32);
533
- const { cipherKey, iv, macKey } = await getMediaKeys(mediaKey, mediaType);
534
- const encWriteStream = new stream_1.Readable({ read: () => { } });
535
- let bodyPath;
536
- let writeStream;
537
- let didSaveToTmpPath = false;
538
- if (type === 'file') {
539
- bodyPath = media.url;
540
- }
541
- else if (saveOriginalFileIfRequired) {
542
- bodyPath = (0, path_1.join)(getTmpFilesDirectory(), mediaType + (0, generics_1.generateMessageIDV2)());
543
- writeStream = (0, fs_1.createWriteStream)(bodyPath);
544
- didSaveToTmpPath = true;
545
- }
546
- let fileLength = 0;
547
- const aes = Crypto.createCipheriv('aes-256-cbc', cipherKey, iv);
548
- let hmac = Crypto.createHmac('sha256', macKey).update(iv);
549
- let sha256Plain = Crypto.createHash('sha256');
550
- let sha256Enc = Crypto.createHash('sha256');
303
+ }
304
+ sha256Plain.update(data);
305
+ onChunk(aes.update(data));
306
+ }
307
+ onChunk(aes.final());
308
+ const mac = hmac.digest().slice(0, 10);
309
+ sha256Enc.update(mac);
310
+ const fileSha256 = sha256Plain.digest();
311
+ const fileEncSha256 = sha256Enc.digest();
312
+ encFileWriteStream.write(mac);
313
+ encFileWriteStream.end();
314
+ originalFileStream?.end?.();
315
+ stream.destroy();
316
+ logger?.debug("encrypted data successfully");
317
+ return {
318
+ mediaKey,
319
+ originalFilePath,
320
+ encFilePath,
321
+ mac,
322
+ fileEncSha256,
323
+ fileSha256,
324
+ fileLength
325
+ };
326
+ }
327
+ catch (error) {
328
+ encFileWriteStream.destroy();
329
+ originalFileStream?.destroy?.();
330
+ aes.destroy();
331
+ hmac.destroy();
332
+ sha256Plain.destroy();
333
+ sha256Enc.destroy();
334
+ stream.destroy();
551
335
  try {
552
- for await (const data of stream) {
553
- fileLength += data.length;
554
- if (type === 'remote'
555
- && (opts === null || opts === void 0 ? void 0 : opts.maxContentLength)
556
- && fileLength + data.length > opts.maxContentLength) {
557
- throw new boom_1.Boom(`content length exceeded when encrypting "${type}"`, {
558
- data: { media, type }
559
- });
560
- }
561
- sha256Plain = sha256Plain.update(data);
562
- if (writeStream) {
563
- if (!writeStream.write(data)) {
564
- await (0, events_1.once)(writeStream, 'drain');
565
- }
566
- }
567
- onChunk(aes.update(data));
568
- }
569
- onChunk(aes.final());
570
- const mac = hmac.digest().slice(0, 10);
571
- sha256Enc = sha256Enc.update(mac);
572
- const fileSha256 = sha256Plain.digest();
573
- const fileEncSha256 = sha256Enc.digest();
574
- encWriteStream.push(mac);
575
- encWriteStream.push(null);
576
- writeStream === null || writeStream === void 0 ? void 0 : writeStream.end();
577
- stream.destroy();
578
- logger === null || logger === void 0 ? void 0 : logger.debug('encrypted data successfully');
579
- return {
580
- mediaKey,
581
- encWriteStream,
582
- bodyPath,
583
- mac,
584
- fileEncSha256,
585
- fileSha256,
586
- fileLength,
587
- didSaveToTmpPath
588
- };
589
- }
590
- catch (error) {
591
- encWriteStream.destroy();
592
- writeStream === null || writeStream === void 0 ? void 0 : writeStream.destroy();
593
- aes.destroy();
594
- hmac.destroy();
595
- sha256Plain.destroy();
596
- sha256Enc.destroy();
597
- stream.destroy();
598
- if (didSaveToTmpPath) {
599
- try {
600
- await fs_1.promises.unlink(bodyPath);
601
- }
602
- catch (err) {
603
- logger === null || logger === void 0 ? void 0 : logger.error({ err }, 'failed to save to tmp path');
604
- }
605
- }
606
- throw error;
336
+ await fs.unlink(encFilePath);
337
+ if (originalFilePath) {
338
+ await fs.unlink(originalFilePath);
339
+ }
607
340
  }
608
- function onChunk(buff) {
609
- sha256Enc = sha256Enc.update(buff);
610
- hmac = hmac.update(buff);
611
- encWriteStream.push(buff);
341
+ catch (err) {
342
+ logger?.error({ err }, "failed deleting tmp files");
612
343
  }
344
+ throw error;
345
+ }
613
346
  };
614
- exports.encryptedStream = encryptedStream;
615
- const DEF_HOST = 'mmg.whatsapp.net';
347
+ //=======================================================//
348
+ const DEF_HOST = "mmg.whatsapp.net";
616
349
  const AES_CHUNK_SIZE = 16;
617
350
  const toSmallestChunkSize = (num) => {
618
- return Math.floor(num / AES_CHUNK_SIZE) * AES_CHUNK_SIZE;
351
+ return Math.floor(num / AES_CHUNK_SIZE) * AES_CHUNK_SIZE;
619
352
  };
620
- const getUrlFromDirectPath = (directPath) => `https://${DEF_HOST}${directPath}`;
621
- exports.getUrlFromDirectPath = getUrlFromDirectPath;
622
- const downloadContentFromMessage = async ({ mediaKey, directPath, url }, type, opts = {}) => {
623
- const isValidMediaUrl = url === null || url === void 0 ? void 0 : url.startsWith('https://mmg.whatsapp.net/');
624
- const downloadUrl = isValidMediaUrl ? url : (0, exports.getUrlFromDirectPath)(directPath);
625
- if (!downloadUrl) {
626
- throw new boom_1.Boom('No valid media URL or directPath present in message', { statusCode: 400 });
627
- }
628
- const keys = await getMediaKeys(mediaKey, type);
629
- return (0, exports.downloadEncryptedContent)(downloadUrl, keys, opts);
353
+ //=======================================================//
354
+ export const getUrlFromDirectPath = (directPath) => `https://${DEF_HOST}${directPath}`;
355
+ export const downloadContentFromMessage = async ({ mediaKey, directPath, url }, type, opts = {}) => {
356
+ const isValidMediaUrl = url?.startsWith("https://mmg.whatsapp.net/");
357
+ const downloadUrl = isValidMediaUrl ? url : getUrlFromDirectPath(directPath);
358
+ if (!downloadUrl) {
359
+ throw new Boom("No valid media URL or directPath present in message", { statusCode: 400 });
360
+ }
361
+ const keys = await getMediaKeys(mediaKey, type);
362
+ return downloadEncryptedContent(downloadUrl, keys, opts);
630
363
  };
631
- exports.downloadContentFromMessage = downloadContentFromMessage;
632
- const downloadEncryptedContent = async (downloadUrl, { cipherKey, iv }, { startByte, endByte, options } = {}) => {
633
- let bytesFetched = 0;
634
- let startChunk = 0;
635
- let firstBlockIsIV = false;
636
- if (startByte) {
637
- const chunk = toSmallestChunkSize(startByte || 0);
638
- if (chunk) {
639
- startChunk = chunk - AES_CHUNK_SIZE;
640
- bytesFetched = chunk;
641
- firstBlockIsIV = true;
642
- }
364
+ //=======================================================//
365
+ export const downloadEncryptedContent = async (downloadUrl, { cipherKey, iv }, { startByte, endByte, options } = {}) => {
366
+ let bytesFetched = 0;
367
+ let startChunk = 0;
368
+ let firstBlockIsIV = false;
369
+ if (startByte) {
370
+ const chunk = toSmallestChunkSize(startByte || 0);
371
+ if (chunk) {
372
+ startChunk = chunk - AES_CHUNK_SIZE;
373
+ bytesFetched = chunk;
374
+ firstBlockIsIV = true;
375
+ }
376
+ }
377
+ const endChunk = endByte ? toSmallestChunkSize(endByte || 0) + AES_CHUNK_SIZE : undefined;
378
+ const headersInit = options?.headers ? options.headers : undefined;
379
+ const headers = {
380
+ ...(headersInit
381
+ ? Array.isArray(headersInit)
382
+ ? Object.fromEntries(headersInit)
383
+ : headersInit
384
+ : {}),
385
+ Origin: DEFAULT_ORIGIN
386
+ };
387
+ if (startChunk || endChunk) {
388
+ headers.Range = `bytes=${startChunk}-`;
389
+ if (endChunk) {
390
+ headers.Range += endChunk;
391
+ }
392
+ }
393
+ const fetched = await getHttpStream(downloadUrl, {
394
+ ...(options || {}),
395
+ headers
396
+ });
397
+ let remainingBytes = Buffer.from([]);
398
+ let aes;
399
+ const pushBytes = (bytes, push) => {
400
+ if (startByte || endByte) {
401
+ const start = bytesFetched >= startByte ? undefined : Math.max(startByte - bytesFetched, 0);
402
+ const end = bytesFetched + bytes.length < endByte ? undefined : Math.max(endByte - bytesFetched, 0);
403
+ push(bytes.slice(start, end));
404
+ bytesFetched += bytes.length;
643
405
  }
644
- const endChunk = endByte ? toSmallestChunkSize(endByte || 0) + AES_CHUNK_SIZE : undefined;
645
- const headers = {
646
- ...(options === null || options === void 0 ? void 0 : options.headers) || {},
647
- Origin: Defaults_1.DEFAULT_ORIGIN,
648
- };
649
- if (startChunk || endChunk) {
650
- headers.Range = `bytes=${startChunk}-`;
651
- if (endChunk) {
652
- headers.Range += endChunk;
653
- }
654
- }
655
- const fetched = await (0, exports.getHttpStream)(downloadUrl, {
656
- ...options || {},
657
- headers,
658
- maxBodyLength: Infinity,
659
- maxContentLength: Infinity,
660
- });
661
- let remainingBytes = Buffer.from([]);
662
- let aes;
663
- const pushBytes = (bytes, push) => {
664
- if (startByte || endByte) {
665
- const start = bytesFetched >= startByte ? undefined : Math.max(startByte - bytesFetched, 0);
666
- const end = bytesFetched + bytes.length < endByte ? undefined : Math.max(endByte - bytesFetched, 0);
667
- push(bytes.slice(start, end));
668
- bytesFetched += bytes.length;
406
+ else {
407
+ push(bytes);
408
+ }
409
+ };
410
+ const output = new Transform({
411
+ transform(chunk, _, callback) {
412
+ let data = Buffer.concat([remainingBytes, chunk]);
413
+ const decryptLength = toSmallestChunkSize(data.length);
414
+ remainingBytes = data.slice(decryptLength);
415
+ data = data.slice(0, decryptLength);
416
+ if (!aes) {
417
+ let ivValue = iv;
418
+ if (firstBlockIsIV) {
419
+ ivValue = data.slice(0, AES_CHUNK_SIZE);
420
+ data = data.slice(AES_CHUNK_SIZE);
669
421
  }
670
- else {
671
- push(bytes);
422
+ aes = Crypto.createDecipheriv("aes-256-cbc", cipherKey, ivValue);
423
+ if (endByte) {
424
+ aes.setAutoPadding(false);
672
425
  }
673
- };
674
- const output = new stream_1.Transform({
675
- transform(chunk, _, callback) {
676
- let data = Buffer.concat([remainingBytes, chunk]);
677
- const decryptLength = toSmallestChunkSize(data.length);
678
- remainingBytes = data.slice(decryptLength);
679
- data = data.slice(0, decryptLength);
680
- if (!aes) {
681
- let ivValue = iv;
682
- if (firstBlockIsIV) {
683
- ivValue = data.slice(0, AES_CHUNK_SIZE);
684
- data = data.slice(AES_CHUNK_SIZE);
685
- }
686
- aes = Crypto.createDecipheriv('aes-256-cbc', cipherKey, ivValue);
687
- if (endByte) {
688
- aes.setAutoPadding(false);
689
- }
690
- }
691
- try {
692
- pushBytes(aes.update(data), b => this.push(b));
693
- callback();
694
- }
695
- catch (error) {
696
- callback(error);
697
- }
698
- },
699
- final(callback) {
700
- try {
701
- pushBytes(aes.final(), b => this.push(b));
702
- callback();
703
- }
704
- catch (error) {
705
- callback(error);
706
- }
707
- },
708
- });
709
- return fetched.pipe(output, { end: true });
426
+ }
427
+ try {
428
+ pushBytes(aes.update(data), b => this.push(b));
429
+ callback();
430
+ }
431
+ catch (error) {
432
+ callback(error);
433
+ }
434
+ },
435
+ final(callback) {
436
+ try {
437
+ pushBytes(aes.final(), b => this.push(b));
438
+ callback();
439
+ }
440
+ catch (error) {
441
+ callback(error);
442
+ }
443
+ }
444
+ });
445
+ return fetched.pipe(output, { end: true });
710
446
  };
711
- exports.downloadEncryptedContent = downloadEncryptedContent;
712
- function extensionForMediaMessage(message) {
713
- const getExtension = (mimetype) => mimetype.split(';')[0].split('/')[1];
714
- const type = Object.keys(message)[0];
715
- let extension;
716
- if (type === 'locationMessage' ||
717
- type === 'liveLocationMessage' ||
718
- type === 'productMessage') {
719
- extension = '.jpeg';
720
- }
721
- else {
722
- const messageContent = message[type];
723
- extension = getExtension(messageContent.mimetype);
724
- }
725
- return extension;
447
+ //=======================================================//
448
+ export function extensionForMediaMessage(message) {
449
+ const getExtension = (mimetype) => mimetype.split(";")[0]?.split("/")[1];
450
+ const type = Object.keys(message)[0];
451
+ let extension;
452
+ if (type === "locationMessage" || type === "liveLocationMessage" || type === "productMessage") {
453
+ extension = ".jpeg";
454
+ }
455
+ else {
456
+ const messageContent = message[type];
457
+ extension = getExtension(messageContent.mimetype);
458
+ }
459
+ return extension;
726
460
  }
727
- const getWAUploadToServer = ({ customUploadHosts, fetchAgent, logger, options }, refreshMediaConn) => {
728
- return async (stream, { mediaType, fileEncSha256B64, newsletter, timeoutMs }) => {
729
- var _a, _b;
730
- let uploadInfo = await refreshMediaConn(false);
731
- let urls;
732
- const hosts = [...customUploadHosts, ...uploadInfo.hosts];
733
- const chunks = [];
734
- if (!Buffer.isBuffer(stream)) {
735
- for await (const chunk of stream) {
736
- chunks.push(chunk);
737
- }
461
+ //=======================================================//
462
+ export const getWAUploadToServer = ({ customUploadHosts, fetchAgent, logger, options }, refreshMediaConn) => {
463
+ return async (filePath, { mediaType, fileEncSha256B64, timeoutMs }) => {
464
+ let uploadInfo = await refreshMediaConn(false);
465
+ let urls;
466
+ const hosts = [...customUploadHosts, ...uploadInfo.hosts];
467
+ fileEncSha256B64 = encodeBase64EncodedStringForUpload(fileEncSha256B64);
468
+ for (const { hostname } of hosts) {
469
+ logger.debug(`uploading to "${hostname}"`);
470
+ const auth = encodeURIComponent(uploadInfo.auth);
471
+ const url = `https://${hostname}${MEDIA_PATH_MAP[mediaType]}/${fileEncSha256B64}?auth=${auth}&token=${fileEncSha256B64}`;
472
+ let result;
473
+ try {
474
+ const stream = createReadStream(filePath);
475
+ const response = await fetch(url, {
476
+ dispatcher: fetchAgent,
477
+ method: "POST",
478
+ body: stream,
479
+ headers: {
480
+ ...(() => {
481
+ const hdrs = options?.headers;
482
+ if (!hdrs)
483
+ return {};
484
+ return Array.isArray(hdrs) ? Object.fromEntries(hdrs) : hdrs;
485
+ })(),
486
+ "Content-Type": "application/octet-stream",
487
+ Origin: DEFAULT_ORIGIN
488
+ },
489
+ duplex: "half",
490
+ signal: timeoutMs ? AbortSignal.timeout(timeoutMs) : undefined
491
+ });
492
+ let parsed = undefined;
493
+ try {
494
+ parsed = await response.json();
738
495
  }
739
- const reqBody = Buffer.isBuffer(stream) ? stream : Buffer.concat(chunks);
740
- fileEncSha256B64 = (0, exports.encodeBase64EncodedStringForUpload)(fileEncSha256B64);
741
- let media = Defaults_1.MEDIA_PATH_MAP[mediaType];
742
- if (newsletter) {
743
- media = media === null || media === void 0 ? void 0 : media.replace('/mms/', '/newsletter/newsletter-');
496
+ catch {
497
+ parsed = undefined;
744
498
  }
745
- for (const { hostname, maxContentLengthBytes } of hosts) {
746
- logger.debug(`uploading to "${hostname}"`);
747
- const auth = encodeURIComponent(uploadInfo.auth);
748
- const url = `https://${hostname}${media}/${fileEncSha256B64}?auth=${auth}&token=${fileEncSha256B64}`;
749
- let result;
750
- try {
751
- if (maxContentLengthBytes && reqBody.length > maxContentLengthBytes) {
752
- throw new boom_1.Boom(`Body too large for "${hostname}"`, { statusCode: 413 });
753
- }
754
- const body = await axios_1.default.post(url, reqBody, {
755
- ...options,
756
- headers: {
757
- ...options.headers || {},
758
- 'Content-Type': 'application/octet-stream',
759
- 'Origin': Defaults_1.DEFAULT_ORIGIN
760
- },
761
- httpsAgent: fetchAgent,
762
- timeout: timeoutMs,
763
- responseType: 'json',
764
- maxBodyLength: Infinity,
765
- maxContentLength: Infinity,
766
- });
767
- result = body.data;
768
- if ((result === null || result === void 0 ? void 0 : result.url) || (result === null || result === void 0 ? void 0 : result.directPath)) {
769
- urls = {
770
- mediaUrl: result.url,
771
- directPath: result.direct_path,
772
- handle: result.handle
773
- };
774
- break;
775
- }
776
- else {
777
- uploadInfo = await refreshMediaConn(true);
778
- throw new Error(`upload failed, reason: ${JSON.stringify(result)}`);
779
- }
780
- }
781
- catch (error) {
782
- if (axios_1.default.isAxiosError(error)) {
783
- result = (_a = error.response) === null || _a === void 0 ? void 0 : _a.data;
784
- }
785
- const isLast = hostname === ((_b = hosts[uploadInfo.hosts.length - 1]) === null || _b === void 0 ? void 0 : _b.hostname);
786
- logger.warn({ trace: error.stack, uploadResult: result }, `Error in uploading to ${hostname} ${isLast ? '' : ', retrying...'}`);
787
- }
499
+ result = parsed;
500
+ if (result?.url || result?.directPath) {
501
+ urls = {
502
+ mediaUrl: result.url,
503
+ directPath: result.direct_path,
504
+ meta_hmac: result.meta_hmac,
505
+ fbid: result.fbid,
506
+ ts: result.ts
507
+ };
508
+ break;
788
509
  }
789
- if (!urls) {
790
- throw new boom_1.Boom('Media upload failed on all hosts', { statusCode: 500 });
510
+ else {
511
+ uploadInfo = await refreshMediaConn(true);
512
+ throw new Error(`upload failed, reason: ${JSON.stringify(result)}`);
791
513
  }
792
- return urls;
793
- };
514
+ }
515
+ catch (error) {
516
+ const isLast = hostname === hosts[uploadInfo.hosts.length - 1]?.hostname;
517
+ logger.warn({ trace: error?.stack, uploadResult: result }, `Error in uploading to ${hostname} ${isLast ? "" : ", retrying..."}`);
518
+ }
519
+ }
520
+ if (!urls) {
521
+ throw new Boom("Media upload failed on all hosts", { statusCode: 500 });
522
+ }
523
+ return urls;
524
+ };
794
525
  };
795
- exports.getWAUploadToServer = getWAUploadToServer;
526
+ //=======================================================//
796
527
  const getMediaRetryKey = (mediaKey) => {
797
- return (0, crypto_1.hkdf)(mediaKey, 32, { info: 'WhatsApp Media Retry Notification' });
528
+ return hkdf(mediaKey, 32, { info: "WhatsApp Media Retry Notification" });
798
529
  };
799
- const encryptMediaRetryRequest = async (key, mediaKey, meId) => {
800
- const recp = { stanzaId: key.id };
801
- const recpBuffer = WAProto_1.proto.ServerErrorReceipt.encode(recp).finish();
802
- const iv = Crypto.randomBytes(12);
803
- const retryKey = await getMediaRetryKey(mediaKey);
804
- const ciphertext = (0, crypto_1.aesEncryptGCM)(recpBuffer, retryKey, iv, Buffer.from(key.id));
805
- const req = {
806
- tag: 'receipt',
807
- attrs: {
808
- id: key.id,
809
- to: (0, WABinary_1.jidNormalizedUser)(meId),
810
- type: 'server-error'
811
- },
530
+ //=======================================================//
531
+ export const encryptMediaRetryRequest = async (key, mediaKey, meId) => {
532
+ const recp = { stanzaId: key.id };
533
+ const recpBuffer = proto.ServerErrorReceipt.encode(recp).finish();
534
+ const iv = Crypto.randomBytes(12);
535
+ const retryKey = await getMediaRetryKey(mediaKey);
536
+ const ciphertext = aesEncryptGCM(recpBuffer, retryKey, iv, Buffer.from(key.id));
537
+ const req = {
538
+ tag: "receipt",
539
+ attrs: {
540
+ id: key.id,
541
+ to: jidNormalizedUser(meId),
542
+ type: "server-error"
543
+ },
544
+ content: [
545
+ {
546
+ tag: "encrypt",
547
+ attrs: {},
812
548
  content: [
813
- {
814
- tag: 'encrypt',
815
- attrs: {},
816
- content: [
817
- { tag: 'enc_p', attrs: {}, content: ciphertext },
818
- { tag: 'enc_iv', attrs: {}, content: iv }
819
- ]
820
- },
821
- {
822
- tag: 'rmr',
823
- attrs: {
824
- jid: key.remoteJid,
825
- 'from_me': (!!key.fromMe).toString(),
826
- participant: key.participant || undefined
827
- }
828
- }
549
+ { tag: "enc_p", attrs: {}, content: ciphertext },
550
+ { tag: "enc_iv", attrs: {}, content: iv }
829
551
  ]
830
- };
831
- return req;
832
- };
833
- exports.encryptMediaRetryRequest = encryptMediaRetryRequest;
834
- const decodeMediaRetryNode = (node) => {
835
- const rmrNode = (0, WABinary_1.getBinaryNodeChild)(node, 'rmr');
836
- const event = {
837
- key: {
838
- id: node.attrs.id,
839
- remoteJid: rmrNode.attrs.jid,
840
- fromMe: rmrNode.attrs.from_me === 'true',
841
- participant: rmrNode.attrs.participant
552
+ },
553
+ {
554
+ tag: "rmr",
555
+ attrs: {
556
+ jid: key.remoteJid,
557
+ from_me: (!!key.fromMe).toString(),
558
+ participant: key.participant || undefined
842
559
  }
843
- };
844
- const errorNode = (0, WABinary_1.getBinaryNodeChild)(node, 'error');
845
- if (errorNode) {
846
- const errorCode = +errorNode.attrs.code;
847
- event.error = new boom_1.Boom(`Failed to re-upload media (${errorCode})`, { data: errorNode.attrs, statusCode: (0, exports.getStatusCodeForMediaRetry)(errorCode) });
560
+ }
561
+ ]
562
+ };
563
+ return req;
564
+ };
565
+ //=======================================================//
566
+ export const decodeMediaRetryNode = (node) => {
567
+ const rmrNode = getBinaryNodeChild(node, "rmr");
568
+ const event = {
569
+ key: {
570
+ id: node.attrs.id,
571
+ remoteJid: rmrNode.attrs.jid,
572
+ fromMe: rmrNode.attrs.from_me === "true",
573
+ participant: rmrNode.attrs.participant
574
+ }
575
+ };
576
+ const errorNode = getBinaryNodeChild(node, "error");
577
+ if (errorNode) {
578
+ const errorCode = +errorNode.attrs.code;
579
+ event.error = new Boom(`Failed to re-upload media (${errorCode})`, {
580
+ data: errorNode.attrs,
581
+ statusCode: getStatusCodeForMediaRetry(errorCode)
582
+ });
583
+ }
584
+ else {
585
+ const encryptedInfoNode = getBinaryNodeChild(node, "encrypt");
586
+ const ciphertext = getBinaryNodeChildBuffer(encryptedInfoNode, "enc_p");
587
+ const iv = getBinaryNodeChildBuffer(encryptedInfoNode, "enc_iv");
588
+ if (ciphertext && iv) {
589
+ event.media = { ciphertext, iv };
848
590
  }
849
591
  else {
850
- const encryptedInfoNode = (0, WABinary_1.getBinaryNodeChild)(node, 'encrypt');
851
- const ciphertext = (0, WABinary_1.getBinaryNodeChildBuffer)(encryptedInfoNode, 'enc_p');
852
- const iv = (0, WABinary_1.getBinaryNodeChildBuffer)(encryptedInfoNode, 'enc_iv');
853
- if (ciphertext && iv) {
854
- event.media = { ciphertext, iv };
855
- }
856
- else {
857
- event.error = new boom_1.Boom('Failed to re-upload media (missing ciphertext)', { statusCode: 404 });
858
- }
592
+ event.error = new Boom("Failed to re-upload media (missing ciphertext)", { statusCode: 404 });
859
593
  }
860
- return event;
594
+ }
595
+ return event;
861
596
  };
862
- exports.decodeMediaRetryNode = decodeMediaRetryNode;
863
- const decryptMediaRetryData = async ({ ciphertext, iv }, mediaKey, msgId) => {
864
- const retryKey = await getMediaRetryKey(mediaKey);
865
- const plaintext = (0, crypto_1.aesDecryptGCM)(ciphertext, retryKey, iv, Buffer.from(msgId));
866
- return WAProto_1.proto.MediaRetryNotification.decode(plaintext);
597
+ //=======================================================//
598
+ export const decryptMediaRetryData = async ({ ciphertext, iv }, mediaKey, msgId) => {
599
+ const retryKey = await getMediaRetryKey(mediaKey);
600
+ const plaintext = aesDecryptGCM(ciphertext, retryKey, iv, Buffer.from(msgId));
601
+ return proto.MediaRetryNotification.decode(plaintext);
867
602
  };
868
- exports.decryptMediaRetryData = decryptMediaRetryData;
869
- const getStatusCodeForMediaRetry = (code) => MEDIA_RETRY_STATUS_MAP[code];
870
- exports.getStatusCodeForMediaRetry = getStatusCodeForMediaRetry;
603
+ //=======================================================//
604
+ export const getStatusCodeForMediaRetry = (code) => MEDIA_RETRY_STATUS_MAP[code];
871
605
  const MEDIA_RETRY_STATUS_MAP = {
872
- [WAProto_1.proto.MediaRetryNotification.ResultType.SUCCESS]: 200,
873
- [WAProto_1.proto.MediaRetryNotification.ResultType.DECRYPTION_ERROR]: 412,
874
- [WAProto_1.proto.MediaRetryNotification.ResultType.NOT_FOUND]: 404,
875
- [WAProto_1.proto.MediaRetryNotification.ResultType.GENERAL_ERROR]: 418,
606
+ [proto.MediaRetryNotification.ResultType.SUCCESS]: 200,
607
+ [proto.MediaRetryNotification.ResultType.DECRYPTION_ERROR]: 412,
608
+ [proto.MediaRetryNotification.ResultType.NOT_FOUND]: 404,
609
+ [proto.MediaRetryNotification.ResultType.GENERAL_ERROR]: 418
876
610
  };
877
- function __importStar(arg0) {
878
- throw new Error('Function not implemented.');
879
- }
611
+ //=======================================================//