Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
jajbshjahavahh
GitHub Repository: jajbshjahavahh/Gojo-Satoru
Path: blob/master/node_modules/@adiwajshing/baileys/lib/Utils/messages-media.js
2593 views
1
"use strict";
2
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
if (k2 === undefined) k2 = k;
4
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
5
}) : (function(o, m, k, k2) {
6
if (k2 === undefined) k2 = k;
7
o[k2] = m[k];
8
}));
9
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
10
Object.defineProperty(o, "default", { enumerable: true, value: v });
11
}) : function(o, v) {
12
o["default"] = v;
13
});
14
var __importStar = (this && this.__importStar) || function (mod) {
15
if (mod && mod.__esModule) return mod;
16
var result = {};
17
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
18
__setModuleDefault(result, mod);
19
return result;
20
};
21
Object.defineProperty(exports, "__esModule", { value: true });
22
exports.getWAUploadToServer = exports.extensionForMediaMessage = exports.decryptMediaMessageBuffer = exports.downloadContentFromMessage = exports.encryptedStream = exports.getHttpStream = exports.generateThumbnail = exports.getStream = exports.toBuffer = exports.toReadable = exports.getAudioDuration = exports.mediaMessageSHA256B64 = exports.generateProfilePicture = exports.extractImageThumb = exports.getMediaKeys = exports.hkdfInfoKey = void 0;
23
const boom_1 = require("@hapi/boom");
24
const child_process_1 = require("child_process");
25
const Crypto = __importStar(require("crypto"));
26
const events_1 = require("events");
27
const fs_1 = require("fs");
28
const os_1 = require("os");
29
const path_1 = require("path");
30
const stream_1 = require("stream");
31
const Defaults_1 = require("../Defaults");
32
const crypto_1 = require("./crypto");
33
const generics_1 = require("./generics");
34
const getTmpFilesDirectory = () => os_1.tmpdir();
35
const getImageProcessingLibrary = async () => {
36
const [jimp, sharp] = await Promise.all([
37
(async () => {
38
const jimp = await (Promise.resolve().then(() => __importStar(require('jimp'))).catch(() => { }));
39
return jimp;
40
})(),
41
(async () => {
42
const sharp = await (Promise.resolve().then(() => __importStar(require('sharp'))).catch(() => { }));
43
return sharp;
44
})()
45
]);
46
if (sharp) {
47
return { sharp };
48
}
49
if (jimp) {
50
return { jimp };
51
}
52
throw new boom_1.Boom('No image processing library available');
53
};
54
const hkdfInfoKey = (type) => {
55
let str = type;
56
if (type === 'sticker') {
57
str = 'image';
58
}
59
if (type === 'md-app-state') {
60
str = 'App State';
61
}
62
const hkdfInfo = str[0].toUpperCase() + str.slice(1);
63
return `WhatsApp ${hkdfInfo} Keys`;
64
};
65
exports.hkdfInfoKey = hkdfInfoKey;
66
/** generates all the keys required to encrypt/decrypt & sign a media message */
67
function getMediaKeys(buffer, mediaType) {
68
if (typeof buffer === 'string') {
69
buffer = Buffer.from(buffer.replace('data:;base64,', ''), 'base64');
70
}
71
// expand using HKDF to 112 bytes, also pass in the relevant app info
72
const expandedMediaKey = crypto_1.hkdf(buffer, 112, { info: exports.hkdfInfoKey(mediaType) });
73
return {
74
iv: expandedMediaKey.slice(0, 16),
75
cipherKey: expandedMediaKey.slice(16, 48),
76
macKey: expandedMediaKey.slice(48, 80),
77
};
78
}
79
exports.getMediaKeys = getMediaKeys;
80
/** Extracts video thumb using FFMPEG */
81
const extractVideoThumb = async (path, destPath, time, size) => new Promise((resolve, reject) => {
82
const cmd = `ffmpeg -ss ${time} -i ${path} -y -s ${size.width}x${size.height} -vframes 1 -f image2 ${destPath}`;
83
child_process_1.exec(cmd, (err) => {
84
if (err) {
85
reject(err);
86
}
87
else {
88
resolve();
89
}
90
});
91
});
92
const extractImageThumb = async (bufferOrFilePath) => {
93
if (bufferOrFilePath instanceof stream_1.Readable) {
94
bufferOrFilePath = await exports.toBuffer(bufferOrFilePath);
95
}
96
const lib = await getImageProcessingLibrary();
97
if ('sharp' in lib) {
98
const result = await lib.sharp.default(bufferOrFilePath)
99
.resize(32, 32)
100
.jpeg({ quality: 50 })
101
.toBuffer();
102
return result;
103
}
104
else {
105
const { read, MIME_JPEG, RESIZE_BILINEAR } = lib.jimp;
106
const jimp = await read(bufferOrFilePath);
107
const result = await jimp
108
.quality(50)
109
.resize(32, 32, RESIZE_BILINEAR)
110
.getBufferAsync(MIME_JPEG);
111
return result;
112
}
113
};
114
exports.extractImageThumb = extractImageThumb;
115
const generateProfilePicture = async (mediaUpload) => {
116
let bufferOrFilePath;
117
if (Buffer.isBuffer(mediaUpload)) {
118
bufferOrFilePath = mediaUpload;
119
}
120
else if ('url' in mediaUpload) {
121
bufferOrFilePath = mediaUpload.url.toString();
122
}
123
else {
124
bufferOrFilePath = await exports.toBuffer(mediaUpload.stream);
125
}
126
const lib = await getImageProcessingLibrary();
127
let img;
128
if ('sharp' in lib) {
129
img = lib.sharp.default(bufferOrFilePath)
130
.resize(640, 640)
131
.jpeg({
132
quality: 50,
133
})
134
.toBuffer();
135
}
136
else {
137
const { read, MIME_JPEG, RESIZE_BILINEAR } = lib.jimp;
138
const jimp = await read(bufferOrFilePath);
139
const min = Math.min(jimp.getWidth(), jimp.getHeight());
140
const cropped = jimp.crop(0, 0, min, min);
141
img = cropped
142
.quality(50)
143
.resize(640, 640, RESIZE_BILINEAR)
144
.getBufferAsync(MIME_JPEG);
145
}
146
return {
147
img: await img,
148
};
149
};
150
exports.generateProfilePicture = generateProfilePicture;
151
/** gets the SHA256 of the given media message */
152
const mediaMessageSHA256B64 = (message) => {
153
const media = Object.values(message)[0];
154
return (media === null || media === void 0 ? void 0 : media.fileSha256) && Buffer.from(media.fileSha256).toString('base64');
155
};
156
exports.mediaMessageSHA256B64 = mediaMessageSHA256B64;
157
async function getAudioDuration(buffer) {
158
const musicMetadata = await Promise.resolve().then(() => __importStar(require('music-metadata')));
159
let metadata;
160
if (Buffer.isBuffer(buffer)) {
161
metadata = await musicMetadata.parseBuffer(buffer, null, { duration: true });
162
}
163
else if (typeof buffer === 'string') {
164
const rStream = fs_1.createReadStream(buffer);
165
metadata = await musicMetadata.parseStream(rStream, null, { duration: true });
166
rStream.close();
167
}
168
else {
169
metadata = await musicMetadata.parseStream(buffer, null, { duration: true });
170
}
171
return metadata.format.duration;
172
}
173
exports.getAudioDuration = getAudioDuration;
174
const toReadable = (buffer) => {
175
const readable = new stream_1.Readable({ read: () => { } });
176
readable.push(buffer);
177
readable.push(null);
178
return readable;
179
};
180
exports.toReadable = toReadable;
181
const toBuffer = async (stream) => {
182
let buff = Buffer.alloc(0);
183
for await (const chunk of stream) {
184
buff = Buffer.concat([buff, chunk]);
185
}
186
return buff;
187
};
188
exports.toBuffer = toBuffer;
189
const getStream = async (item) => {
190
if (Buffer.isBuffer(item)) {
191
return { stream: exports.toReadable(item), type: 'buffer' };
192
}
193
if ('stream' in item) {
194
return { stream: item.stream, type: 'readable' };
195
}
196
if (item.url.toString().startsWith('http://') || item.url.toString().startsWith('https://')) {
197
return { stream: await exports.getHttpStream(item.url), type: 'remote' };
198
}
199
return { stream: fs_1.createReadStream(item.url), type: 'file' };
200
};
201
exports.getStream = getStream;
202
/** generates a thumbnail for a given media, if required */
203
async function generateThumbnail(file, mediaType, options) {
204
var _a;
205
let thumbnail;
206
if (mediaType === 'image') {
207
const buff = await exports.extractImageThumb(file);
208
thumbnail = buff.toString('base64');
209
}
210
else if (mediaType === 'video') {
211
const imgFilename = path_1.join(getTmpFilesDirectory(), generics_1.generateMessageID() + '.jpg');
212
try {
213
await extractVideoThumb(file, imgFilename, '00:00:00', { width: 32, height: 32 });
214
const buff = await fs_1.promises.readFile(imgFilename);
215
thumbnail = buff.toString('base64');
216
await fs_1.promises.unlink(imgFilename);
217
}
218
catch (err) {
219
(_a = options.logger) === null || _a === void 0 ? void 0 : _a.debug('could not generate video thumb: ' + err);
220
}
221
}
222
return thumbnail;
223
}
224
exports.generateThumbnail = generateThumbnail;
225
const getHttpStream = async (url, options = {}) => {
226
const { default: axios } = await Promise.resolve().then(() => __importStar(require('axios')));
227
const fetched = await axios.get(url.toString(), { ...options, responseType: 'stream' });
228
return fetched.data;
229
};
230
exports.getHttpStream = getHttpStream;
231
const encryptedStream = async (media, mediaType, saveOriginalFileIfRequired = true, logger) => {
232
const { stream, type } = await exports.getStream(media);
233
logger === null || logger === void 0 ? void 0 : logger.debug('fetched media stream');
234
const mediaKey = Crypto.randomBytes(32);
235
const { cipherKey, iv, macKey } = getMediaKeys(mediaKey, mediaType);
236
// random name
237
//const encBodyPath = join(getTmpFilesDirectory(), mediaType + generateMessageID() + '.enc')
238
// const encWriteStream = createWriteStream(encBodyPath)
239
const encWriteStream = new stream_1.Readable({ read: () => { } });
240
let bodyPath;
241
let writeStream;
242
let didSaveToTmpPath = false;
243
if (type === 'file') {
244
bodyPath = media.url;
245
}
246
else if (saveOriginalFileIfRequired) {
247
bodyPath = path_1.join(getTmpFilesDirectory(), mediaType + generics_1.generateMessageID());
248
writeStream = fs_1.createWriteStream(bodyPath);
249
didSaveToTmpPath = true;
250
}
251
let fileLength = 0;
252
const aes = Crypto.createCipheriv('aes-256-cbc', cipherKey, iv);
253
let hmac = Crypto.createHmac('sha256', macKey).update(iv);
254
let sha256Plain = Crypto.createHash('sha256');
255
let sha256Enc = Crypto.createHash('sha256');
256
const onChunk = (buff) => {
257
sha256Enc = sha256Enc.update(buff);
258
hmac = hmac.update(buff);
259
encWriteStream.push(buff);
260
};
261
try {
262
for await (const data of stream) {
263
fileLength += data.length;
264
sha256Plain = sha256Plain.update(data);
265
if (writeStream) {
266
if (!writeStream.write(data)) {
267
await events_1.once(writeStream, 'drain');
268
}
269
}
270
onChunk(aes.update(data));
271
}
272
onChunk(aes.final());
273
const mac = hmac.digest().slice(0, 10);
274
sha256Enc = sha256Enc.update(mac);
275
const fileSha256 = sha256Plain.digest();
276
const fileEncSha256 = sha256Enc.digest();
277
encWriteStream.push(mac);
278
encWriteStream.push(null);
279
writeStream && writeStream.end();
280
stream.destroy();
281
logger === null || logger === void 0 ? void 0 : logger.debug('encrypted data successfully');
282
return {
283
mediaKey,
284
encWriteStream,
285
bodyPath,
286
mac,
287
fileEncSha256,
288
fileSha256,
289
fileLength,
290
didSaveToTmpPath
291
};
292
}
293
catch (error) {
294
encWriteStream.destroy(error);
295
writeStream.destroy(error);
296
aes.destroy(error);
297
hmac.destroy(error);
298
sha256Plain.destroy(error);
299
sha256Enc.destroy(error);
300
stream.destroy(error);
301
throw error;
302
}
303
};
304
exports.encryptedStream = encryptedStream;
305
const DEF_HOST = 'mmg.whatsapp.net';
306
const AES_CHUNK_SIZE = 16;
307
const toSmallestChunkSize = (num) => {
308
return Math.floor(num / AES_CHUNK_SIZE) * AES_CHUNK_SIZE;
309
};
310
const downloadContentFromMessage = async ({ mediaKey, directPath, url }, type, { startByte, endByte } = {}) => {
311
const downloadUrl = url || `https://${DEF_HOST}${directPath}`;
312
let bytesFetched = 0;
313
let startChunk = 0;
314
let firstBlockIsIV = false;
315
// if a start byte is specified -- then we need to fetch the previous chunk as that will form the IV
316
if (startByte) {
317
const chunk = toSmallestChunkSize(startByte || 0);
318
if (chunk) {
319
startChunk = chunk - AES_CHUNK_SIZE;
320
bytesFetched = chunk;
321
firstBlockIsIV = true;
322
}
323
}
324
const endChunk = endByte ? toSmallestChunkSize(endByte || 0) + AES_CHUNK_SIZE : undefined;
325
const headers = {
326
Origin: Defaults_1.DEFAULT_ORIGIN,
327
};
328
if (startChunk || endChunk) {
329
headers.Range = `bytes=${startChunk}-`;
330
if (endChunk) {
331
headers.Range += endChunk;
332
}
333
}
334
// download the message
335
const fetched = await exports.getHttpStream(downloadUrl, {
336
headers,
337
maxBodyLength: Infinity,
338
maxContentLength: Infinity,
339
});
340
let remainingBytes = Buffer.from([]);
341
const { cipherKey, iv } = getMediaKeys(mediaKey, type);
342
let aes;
343
const pushBytes = (bytes, push) => {
344
if (startByte || endByte) {
345
const start = bytesFetched >= startByte ? undefined : Math.max(startByte - bytesFetched, 0);
346
const end = bytesFetched + bytes.length < endByte ? undefined : Math.max(endByte - bytesFetched, 0);
347
push(bytes.slice(start, end));
348
bytesFetched += bytes.length;
349
}
350
else {
351
push(bytes);
352
}
353
};
354
const output = new stream_1.Transform({
355
transform(chunk, _, callback) {
356
let data = Buffer.concat([remainingBytes, chunk]);
357
const decryptLength = toSmallestChunkSize(data.length);
358
remainingBytes = data.slice(decryptLength);
359
data = data.slice(0, decryptLength);
360
if (!aes) {
361
let ivValue = iv;
362
if (firstBlockIsIV) {
363
ivValue = data.slice(0, AES_CHUNK_SIZE);
364
data = data.slice(AES_CHUNK_SIZE);
365
}
366
aes = Crypto.createDecipheriv('aes-256-cbc', cipherKey, ivValue);
367
// if an end byte that is not EOF is specified
368
// stop auto padding (PKCS7) -- otherwise throws an error for decryption
369
if (endByte) {
370
aes.setAutoPadding(false);
371
}
372
}
373
try {
374
pushBytes(aes.update(data), b => this.push(b));
375
callback();
376
}
377
catch (error) {
378
callback(error);
379
}
380
},
381
final(callback) {
382
try {
383
pushBytes(aes.final(), b => this.push(b));
384
callback();
385
}
386
catch (error) {
387
callback(error);
388
}
389
},
390
});
391
return fetched.pipe(output, { end: true });
392
};
393
exports.downloadContentFromMessage = downloadContentFromMessage;
394
/**
395
* Decode a media message (video, image, document, audio) & return decrypted buffer
396
* @param message the media message you want to decode
397
*/
398
async function decryptMediaMessageBuffer(message) {
399
var _a;
400
/*
401
One can infer media type from the key in the message
402
it is usually written as [mediaType]Message. Eg. imageMessage, audioMessage etc.
403
*/
404
const type = Object.keys(message)[0];
405
if (!type ||
406
type === 'conversation' ||
407
type === 'extendedTextMessage') {
408
throw new boom_1.Boom(`no media message for "${type}"`, { statusCode: 400 });
409
}
410
if (type === 'locationMessage' || type === 'liveLocationMessage') {
411
const buffer = Buffer.from(message[type].jpegThumbnail);
412
const readable = new stream_1.Readable({ read: () => { } });
413
readable.push(buffer);
414
readable.push(null);
415
return readable;
416
}
417
let messageContent;
418
if (message.productMessage) {
419
const product = (_a = message.productMessage.product) === null || _a === void 0 ? void 0 : _a.productImage;
420
if (!product) {
421
throw new boom_1.Boom('product has no image', { statusCode: 400 });
422
}
423
messageContent = product;
424
}
425
else {
426
messageContent = message[type];
427
}
428
return exports.downloadContentFromMessage(messageContent, type.replace('Message', ''));
429
}
430
exports.decryptMediaMessageBuffer = decryptMediaMessageBuffer;
431
function extensionForMediaMessage(message) {
432
const getExtension = (mimetype) => mimetype.split(';')[0].split('/')[1];
433
const type = Object.keys(message)[0];
434
let extension;
435
if (type === 'locationMessage' ||
436
type === 'liveLocationMessage' ||
437
type === 'productMessage') {
438
extension = '.jpeg';
439
}
440
else {
441
const messageContent = message[type];
442
extension = getExtension(messageContent.mimetype);
443
}
444
return extension;
445
}
446
exports.extensionForMediaMessage = extensionForMediaMessage;
447
const getWAUploadToServer = ({ customUploadHosts, fetchAgent, logger }, refreshMediaConn) => {
448
return async (stream, { mediaType, fileEncSha256B64, timeoutMs }) => {
449
var _a, _b;
450
const { default: axios } = await Promise.resolve().then(() => __importStar(require('axios')));
451
// send a query JSON to obtain the url & auth token to upload our media
452
let uploadInfo = await refreshMediaConn(false);
453
let urls;
454
const hosts = [...customUploadHosts, ...uploadInfo.hosts];
455
const chunks = [];
456
for await (const chunk of stream) {
457
chunks.push(chunk);
458
}
459
let reqBody = Buffer.concat(chunks);
460
for (const { hostname, maxContentLengthBytes } of hosts) {
461
logger.debug(`uploading to "${hostname}"`);
462
const auth = encodeURIComponent(uploadInfo.auth); // the auth token
463
const url = `https://${hostname}${Defaults_1.MEDIA_PATH_MAP[mediaType]}/${fileEncSha256B64}?auth=${auth}&token=${fileEncSha256B64}`;
464
let result;
465
try {
466
if (maxContentLengthBytes && reqBody.length > maxContentLengthBytes) {
467
throw new boom_1.Boom(`Body too large for "${hostname}"`, { statusCode: 413 });
468
}
469
const body = await axios.post(url, reqBody, {
470
headers: {
471
'Content-Type': 'application/octet-stream',
472
'Origin': Defaults_1.DEFAULT_ORIGIN
473
},
474
httpsAgent: fetchAgent,
475
timeout: timeoutMs,
476
responseType: 'json',
477
maxBodyLength: Infinity,
478
maxContentLength: Infinity,
479
});
480
result = body.data;
481
if ((result === null || result === void 0 ? void 0 : result.url) || (result === null || result === void 0 ? void 0 : result.directPath)) {
482
urls = {
483
mediaUrl: result.url,
484
directPath: result.direct_path
485
};
486
break;
487
}
488
else {
489
uploadInfo = await refreshMediaConn(true);
490
throw new Error(`upload failed, reason: ${JSON.stringify(result)}`);
491
}
492
}
493
catch (error) {
494
if (axios.isAxiosError(error)) {
495
result = (_a = error.response) === null || _a === void 0 ? void 0 : _a.data;
496
}
497
const isLast = hostname === ((_b = hosts[uploadInfo.hosts.length - 1]) === null || _b === void 0 ? void 0 : _b.hostname);
498
logger.warn({ trace: error.stack, uploadResult: result }, `Error in uploading to ${hostname} ${isLast ? '' : ', retrying...'}`);
499
}
500
}
501
// clear buffer just to be sure we're releasing the memory
502
reqBody = undefined;
503
if (!urls) {
504
throw new boom_1.Boom('Media upload failed on all hosts', { statusCode: 500 });
505
}
506
return urls;
507
};
508
};
509
exports.getWAUploadToServer = getWAUploadToServer;
510
511