baileyz 1.0.6 → 1.0.8
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.
- package/lib/Socket/chats.js +28 -15
- package/lib/Socket/{dxz.d.ts → dugong.d.ts} +37 -51
- package/lib/Socket/dugong.js +484 -0
- package/lib/Socket/messages-send.js +12 -16
- package/lib/Utils/generics.js +1 -1
- package/lib/Utils/messages-media.js +545 -835
- package/lib/Utils/messages.js +497 -1571
- package/lib/Utils/validate-connection.js +1 -1
- package/lib/WABinary/jid-utils.js +0 -3
- package/lib/WAUSync/index.d.ts +3 -0
- package/lib/index.js +1 -1
- package/package.json +1 -1
- package/lib/Defaults/constants.js +0 -74
- package/lib/Defaults/media.js +0 -48
- package/lib/Socket/dxz.js +0 -591
|
@@ -1,319 +1,286 @@
|
|
|
1
|
-
"use strict"
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
const
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
const
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
try {
|
|
55
|
-
sharp = require('sharp')
|
|
56
|
-
} catch {}
|
|
57
|
-
|
|
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 (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;
|
|
24
|
+
};
|
|
25
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
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;
|
|
27
|
+
const boom_1 = require("@hapi/boom");
|
|
28
|
+
const child_process_1 = require("child_process");
|
|
29
|
+
const Crypto = __importStar(require("crypto"));
|
|
30
|
+
const events_1 = require("events");
|
|
31
|
+
const fs_1 = require("fs");
|
|
32
|
+
const os_1 = require("os");
|
|
33
|
+
const path_1 = require("path");
|
|
34
|
+
const stream_1 = require("stream");
|
|
35
|
+
const WAProto_1 = require("../../WAProto");
|
|
36
|
+
const Defaults_1 = require("../Defaults");
|
|
37
|
+
const WABinary_1 = require("../WABinary");
|
|
38
|
+
const crypto_1 = require("./crypto");
|
|
39
|
+
const generics_1 = require("./generics");
|
|
40
|
+
const getTmpFilesDirectory = () => (0, os_1.tmpdir)();
|
|
41
|
+
const getImageProcessingLibrary = async () => {
|
|
42
|
+
const [_jimp, sharp] = await Promise.all([
|
|
43
|
+
(async () => {
|
|
44
|
+
const jimp = await (import('jimp')
|
|
45
|
+
.catch(() => { }));
|
|
46
|
+
return jimp;
|
|
47
|
+
})(),
|
|
48
|
+
(async () => {
|
|
49
|
+
const sharp = await (import('sharp')
|
|
50
|
+
.catch(() => { }));
|
|
51
|
+
return sharp;
|
|
52
|
+
})()
|
|
53
|
+
]);
|
|
58
54
|
if (sharp) {
|
|
59
|
-
return { sharp }
|
|
55
|
+
return { sharp };
|
|
60
56
|
}
|
|
61
|
-
|
|
62
|
-
try {
|
|
63
|
-
jimp = require('jimp')
|
|
64
|
-
} catch {}
|
|
65
|
-
|
|
57
|
+
const jimp = (_jimp === null || _jimp === void 0 ? void 0 : _jimp.default) || _jimp;
|
|
66
58
|
if (jimp) {
|
|
67
|
-
return { jimp }
|
|
59
|
+
return { jimp };
|
|
68
60
|
}
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
}
|
|
72
|
-
|
|
61
|
+
throw new boom_1.Boom('No image processing library available');
|
|
62
|
+
};
|
|
73
63
|
const hkdfInfoKey = (type) => {
|
|
74
|
-
const hkdfInfo = MEDIA_HKDF_KEY_MAPPING[type]
|
|
75
|
-
return `WhatsApp ${hkdfInfo} Keys
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
const getRawMediaUploadData = async (media, mediaType, logger) => {
|
|
79
|
-
const { stream } = await getStream(media)
|
|
80
|
-
|
|
81
|
-
logger?.debug('got stream for raw upload')
|
|
82
|
-
|
|
83
|
-
const hasher = createHash('sha256')
|
|
84
|
-
const filePath = join(getTmpFilesDirectory(), mediaType + generateMessageID())
|
|
85
|
-
const fileWriteStream = createWriteStream(filePath)
|
|
86
|
-
|
|
87
|
-
let fileLength = 0
|
|
88
|
-
|
|
89
|
-
try {
|
|
90
|
-
for await (const data of stream) {
|
|
91
|
-
fileLength += data.length
|
|
92
|
-
hasher.update(data)
|
|
93
|
-
|
|
94
|
-
if (!fileWriteStream.write(data)) {
|
|
95
|
-
await once(fileWriteStream, 'drain')
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
fileWriteStream.end()
|
|
100
|
-
await once(fileWriteStream, 'finish')
|
|
101
|
-
stream.destroy()
|
|
102
|
-
|
|
103
|
-
const fileSha256 = hasher.digest()
|
|
104
|
-
|
|
105
|
-
logger?.debug('hashed data for raw upload')
|
|
106
|
-
|
|
107
|
-
return {
|
|
108
|
-
filePath: filePath,
|
|
109
|
-
fileSha256,
|
|
110
|
-
fileLength
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
catch (error) {
|
|
114
|
-
fileWriteStream.destroy()
|
|
115
|
-
stream.destroy()
|
|
116
|
-
|
|
117
|
-
try {
|
|
118
|
-
await promises.unlink(filePath)
|
|
119
|
-
}
|
|
120
|
-
catch {
|
|
121
|
-
//
|
|
122
|
-
}
|
|
123
|
-
throw error
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
|
|
64
|
+
const hkdfInfo = Defaults_1.MEDIA_HKDF_KEY_MAPPING[type];
|
|
65
|
+
return `WhatsApp ${hkdfInfo} Keys`;
|
|
66
|
+
};
|
|
67
|
+
exports.hkdfInfoKey = hkdfInfoKey;
|
|
127
68
|
/** generates all the keys required to encrypt/decrypt & sign a media message */
|
|
128
|
-
|
|
69
|
+
function getMediaKeys(buffer, mediaType) {
|
|
129
70
|
if (!buffer) {
|
|
130
|
-
throw new Boom('Cannot derive from empty media key')
|
|
71
|
+
throw new boom_1.Boom('Cannot derive from empty media key');
|
|
131
72
|
}
|
|
132
73
|
if (typeof buffer === 'string') {
|
|
133
|
-
buffer = Buffer.from(buffer.replace('data
|
|
74
|
+
buffer = Buffer.from(buffer.replace('data:;base64,', ''), 'base64');
|
|
134
75
|
}
|
|
135
76
|
// expand using HKDF to 112 bytes, also pass in the relevant app info
|
|
136
|
-
const expandedMediaKey =
|
|
77
|
+
const expandedMediaKey = (0, crypto_1.hkdf)(buffer, 112, { info: (0, exports.hkdfInfoKey)(mediaType) });
|
|
137
78
|
return {
|
|
138
79
|
iv: expandedMediaKey.slice(0, 16),
|
|
139
80
|
cipherKey: expandedMediaKey.slice(16, 48),
|
|
140
|
-
macKey: expandedMediaKey.slice(48, 80)
|
|
141
|
-
}
|
|
81
|
+
macKey: expandedMediaKey.slice(48, 80),
|
|
82
|
+
};
|
|
142
83
|
}
|
|
143
|
-
|
|
84
|
+
exports.getMediaKeys = getMediaKeys;
|
|
144
85
|
/** Extracts video thumb using FFMPEG */
|
|
145
86
|
const extractVideoThumb = async (path, destPath, time, size) => new Promise((resolve, reject) => {
|
|
146
|
-
const cmd = `ffmpeg -ss ${time} -i ${path} -y -vf scale=${size.width}:-1 -vframes 1 -f image2 ${destPath}
|
|
147
|
-
exec(cmd, err => {
|
|
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) => {
|
|
148
89
|
if (err) {
|
|
149
|
-
reject(err)
|
|
90
|
+
reject(err);
|
|
150
91
|
}
|
|
151
92
|
else {
|
|
152
|
-
resolve()
|
|
153
|
-
}
|
|
154
|
-
})
|
|
155
|
-
})
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
const buffer = await img.resize(width).jpeg({ quality: 50 }).toBuffer()
|
|
93
|
+
resolve();
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
const extractImageThumb = async (bufferOrFilePath, width = 32) => {
|
|
98
|
+
var _a, _b;
|
|
99
|
+
if (bufferOrFilePath instanceof stream_1.Readable) {
|
|
100
|
+
bufferOrFilePath = await (0, exports.toBuffer)(bufferOrFilePath);
|
|
101
|
+
}
|
|
102
|
+
const lib = await getImageProcessingLibrary();
|
|
103
|
+
if ('sharp' in lib && typeof ((_a = lib.sharp) === null || _a === void 0 ? void 0 : _a.default) === 'function') {
|
|
104
|
+
const img = lib.sharp.default(bufferOrFilePath);
|
|
105
|
+
const dimensions = await img.metadata();
|
|
106
|
+
const buffer = await img
|
|
107
|
+
.resize(width)
|
|
108
|
+
.jpeg({ quality: 50 })
|
|
109
|
+
.toBuffer();
|
|
170
110
|
return {
|
|
171
111
|
buffer,
|
|
172
112
|
original: {
|
|
173
113
|
width: dimensions.width,
|
|
174
|
-
height: dimensions.height
|
|
175
|
-
}
|
|
176
|
-
}
|
|
114
|
+
height: dimensions.height,
|
|
115
|
+
},
|
|
116
|
+
};
|
|
177
117
|
}
|
|
178
|
-
else if ('jimp' in lib && typeof lib.jimp.read === 'function') {
|
|
179
|
-
|
|
180
|
-
const jimp = await read(bufferOrFilePath)
|
|
118
|
+
else if ('jimp' in lib && typeof ((_b = lib.jimp) === null || _b === void 0 ? void 0 : _b.read) === 'function') {
|
|
119
|
+
const { read, MIME_JPEG, RESIZE_BILINEAR, AUTO } = lib.jimp;
|
|
120
|
+
const jimp = await read(bufferOrFilePath);
|
|
181
121
|
const dimensions = {
|
|
182
122
|
width: jimp.getWidth(),
|
|
183
|
-
height: jimp.getHeight()
|
|
184
|
-
}
|
|
123
|
+
height: jimp.getHeight()
|
|
124
|
+
};
|
|
185
125
|
const buffer = await jimp
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
126
|
+
.quality(50)
|
|
127
|
+
.resize(width, AUTO, RESIZE_BILINEAR)
|
|
128
|
+
.getBufferAsync(MIME_JPEG);
|
|
189
129
|
return {
|
|
190
130
|
buffer,
|
|
191
131
|
original: dimensions
|
|
192
|
-
}
|
|
132
|
+
};
|
|
193
133
|
}
|
|
194
134
|
else {
|
|
195
|
-
throw new Boom('No image processing library available')
|
|
135
|
+
throw new boom_1.Boom('No image processing library available');
|
|
196
136
|
}
|
|
197
|
-
}
|
|
198
|
-
|
|
137
|
+
};
|
|
138
|
+
exports.extractImageThumb = extractImageThumb;
|
|
199
139
|
const encodeBase64EncodedStringForUpload = (b64) => (encodeURIComponent(b64
|
|
200
140
|
.replace(/\+/g, '-')
|
|
201
141
|
.replace(/\//g, '_')
|
|
202
|
-
.replace(/\=+$/, '')))
|
|
203
|
-
|
|
204
|
-
const generateProfilePicture = async (mediaUpload
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
const { width: w = 640, height: h = 640 } = dimensions || {}
|
|
208
|
-
|
|
142
|
+
.replace(/\=+$/, '')));
|
|
143
|
+
exports.encodeBase64EncodedStringForUpload = encodeBase64EncodedStringForUpload;
|
|
144
|
+
const generateProfilePicture = async (mediaUpload) => {
|
|
145
|
+
var _a, _b;
|
|
146
|
+
let bufferOrFilePath;
|
|
209
147
|
if (Buffer.isBuffer(mediaUpload)) {
|
|
210
|
-
|
|
148
|
+
bufferOrFilePath = mediaUpload;
|
|
149
|
+
}
|
|
150
|
+
else if ('url' in mediaUpload) {
|
|
151
|
+
bufferOrFilePath = mediaUpload.url.toString();
|
|
211
152
|
}
|
|
212
153
|
else {
|
|
213
|
-
|
|
214
|
-
const { stream } = await getStream(mediaUpload)
|
|
215
|
-
// Convert the resulting stream to a buffer
|
|
216
|
-
buffer = await toBuffer(stream)
|
|
154
|
+
bufferOrFilePath = await (0, exports.toBuffer)(mediaUpload.stream);
|
|
217
155
|
}
|
|
218
|
-
const lib = await getImageProcessingLibrary()
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
img = lib.sharp
|
|
224
|
-
.default(buffer)
|
|
225
|
-
.resize(w, h)
|
|
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)
|
|
226
161
|
.jpeg({
|
|
227
|
-
quality: 50
|
|
228
|
-
})
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
const
|
|
233
|
-
const
|
|
234
|
-
|
|
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);
|
|
235
175
|
}
|
|
236
176
|
else {
|
|
237
|
-
throw new Boom('No image processing library available');
|
|
177
|
+
throw new boom_1.Boom('No image processing library available');
|
|
238
178
|
}
|
|
239
179
|
return {
|
|
240
|
-
img: await img
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
|
|
180
|
+
img: await img,
|
|
181
|
+
};
|
|
182
|
+
};
|
|
183
|
+
exports.generateProfilePicture = generateProfilePicture;
|
|
244
184
|
/** gets the SHA256 of the given media message */
|
|
245
185
|
const mediaMessageSHA256B64 = (message) => {
|
|
246
|
-
const media = Object.values(message)[0]
|
|
247
|
-
return media
|
|
248
|
-
}
|
|
249
|
-
|
|
186
|
+
const media = Object.values(message)[0];
|
|
187
|
+
return (media === null || media === void 0 ? void 0 : media.fileSha256) && Buffer.from(media.fileSha256).toString('base64');
|
|
188
|
+
};
|
|
189
|
+
exports.mediaMessageSHA256B64 = mediaMessageSHA256B64;
|
|
250
190
|
async function getAudioDuration(buffer) {
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
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;
|
|
265
227
|
}
|
|
266
|
-
return metadata.format?.duration
|
|
267
228
|
}
|
|
268
|
-
|
|
269
|
-
/**
|
|
270
|
-
referenced from and modifying https://github.com/wppconnect-team/wa-js/blob/main/src/chat/functions/prepareAudioWaveform.ts
|
|
271
|
-
*/
|
|
229
|
+
exports.getAudioDuration = getAudioDuration;
|
|
272
230
|
async function getAudioWaveform(buffer, logger) {
|
|
273
231
|
try {
|
|
274
|
-
const {
|
|
232
|
+
const { PassThrough } = require('stream');
|
|
233
|
+
const ff = require('fluent-ffmpeg');
|
|
275
234
|
|
|
276
|
-
let audioData
|
|
277
|
-
|
|
235
|
+
let audioData;
|
|
278
236
|
if (Buffer.isBuffer(buffer)) {
|
|
279
|
-
audioData = buffer
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
else {
|
|
286
|
-
audioData = await toBuffer(buffer)
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
const audioBuffer = await decoder(audioData)
|
|
290
|
-
const rawData = audioBuffer.getChannelData(0) // We only need to work with one channel of data
|
|
291
|
-
const samples = 64 // Number of samples we want to have in our final data set
|
|
292
|
-
const blockSize = Math.floor(rawData.length / samples) // the number of samples in each subdivision
|
|
293
|
-
const filteredData = []
|
|
294
|
-
|
|
295
|
-
for (let i = 0; i < samples; i++) {
|
|
296
|
-
const blockStart = blockSize * i // the location of the first sample in the block
|
|
297
|
-
let sum = 0
|
|
298
|
-
for (let j = 0; j < blockSize; j++) {
|
|
299
|
-
sum = sum + Math.abs(rawData[blockStart + j]) // find the sum of all the samples in the block
|
|
300
|
-
}
|
|
301
|
-
filteredData.push(sum / blockSize) // divide the sum by the block size to get the average
|
|
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);
|
|
302
243
|
}
|
|
303
244
|
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
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);
|
|
314
281
|
}
|
|
315
282
|
}
|
|
316
|
-
|
|
283
|
+
exports.getAudioWaveform = getAudioWaveform;
|
|
317
284
|
async function convertToOpusBuffer(buffer, logger) {
|
|
318
285
|
try {
|
|
319
286
|
const { PassThrough } = require('stream');
|
|
@@ -352,659 +319,429 @@ async function convertToOpusBuffer(buffer, logger) {
|
|
|
352
319
|
throw e;
|
|
353
320
|
}
|
|
354
321
|
}
|
|
355
|
-
|
|
322
|
+
exports.convertToOpusBuffer = convertToOpusBuffer;
|
|
356
323
|
const toReadable = (buffer) => {
|
|
357
|
-
const readable = new Readable({ read: () => { } })
|
|
358
|
-
readable.push(buffer)
|
|
359
|
-
readable.push(null)
|
|
360
|
-
return readable
|
|
361
|
-
}
|
|
362
|
-
|
|
324
|
+
const readable = new stream_1.Readable({ read: () => { } });
|
|
325
|
+
readable.push(buffer);
|
|
326
|
+
readable.push(null);
|
|
327
|
+
return readable;
|
|
328
|
+
};
|
|
329
|
+
exports.toReadable = toReadable;
|
|
363
330
|
const toBuffer = async (stream) => {
|
|
364
|
-
const chunks = []
|
|
331
|
+
const chunks = [];
|
|
365
332
|
for await (const chunk of stream) {
|
|
366
|
-
chunks.push(chunk)
|
|
333
|
+
chunks.push(chunk);
|
|
367
334
|
}
|
|
368
|
-
stream.destroy()
|
|
369
|
-
return Buffer.concat(chunks)
|
|
370
|
-
}
|
|
371
|
-
|
|
335
|
+
stream.destroy();
|
|
336
|
+
return Buffer.concat(chunks);
|
|
337
|
+
};
|
|
338
|
+
exports.toBuffer = toBuffer;
|
|
372
339
|
const getStream = async (item, opts) => {
|
|
373
340
|
if (Buffer.isBuffer(item)) {
|
|
374
|
-
return { stream: toReadable(item), type: 'buffer' }
|
|
341
|
+
return { stream: (0, exports.toReadable)(item), type: 'buffer' };
|
|
375
342
|
}
|
|
376
|
-
|
|
377
343
|
if ('stream' in item) {
|
|
378
|
-
return { stream: item.stream, type: 'readable' }
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
const urlStr = item.url.toString()
|
|
382
|
-
|
|
383
|
-
if (urlStr.startsWith('data:')) {
|
|
384
|
-
const buffer = Buffer.from(urlStr.split(',')[1], 'base64')
|
|
385
|
-
return { stream: await toReadable(buffer), type: 'buffer' }
|
|
344
|
+
return { stream: item.stream, type: 'readable' };
|
|
386
345
|
}
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
return { stream: await getHttpStream(item.url, opts), type: 'remote' }
|
|
346
|
+
if (item.url.toString().startsWith('http://') || item.url.toString().startsWith('https://')) {
|
|
347
|
+
return { stream: await (0, exports.getHttpStream)(item.url, opts), type: 'remote' };
|
|
390
348
|
}
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
349
|
+
return { stream: (0, fs_1.createReadStream)(item.url), type: 'file' };
|
|
350
|
+
};
|
|
351
|
+
exports.getStream = getStream;
|
|
395
352
|
/** generates a thumbnail for a given media, if required */
|
|
396
353
|
async function generateThumbnail(file, mediaType, options) {
|
|
397
|
-
|
|
398
|
-
let
|
|
399
|
-
|
|
354
|
+
var _a;
|
|
355
|
+
let thumbnail;
|
|
356
|
+
let originalImageDimensions;
|
|
400
357
|
if (mediaType === 'image') {
|
|
401
|
-
const { buffer, original } = await extractImageThumb(file)
|
|
402
|
-
|
|
403
|
-
thumbnail = buffer.toString('base64')
|
|
404
|
-
|
|
358
|
+
const { buffer, original } = await (0, exports.extractImageThumb)(file);
|
|
359
|
+
thumbnail = buffer.toString('base64');
|
|
405
360
|
if (original.width && original.height) {
|
|
406
361
|
originalImageDimensions = {
|
|
407
362
|
width: original.width,
|
|
408
|
-
height: original.height
|
|
409
|
-
}
|
|
363
|
+
height: original.height,
|
|
364
|
+
};
|
|
410
365
|
}
|
|
411
366
|
}
|
|
412
367
|
else if (mediaType === 'video') {
|
|
413
|
-
const imgFilename = join(getTmpFilesDirectory(), generateMessageID() + '.jpg')
|
|
368
|
+
const imgFilename = (0, path_1.join)(getTmpFilesDirectory(), (0, generics_1.generateMessageID)() + '.jpg');
|
|
414
369
|
try {
|
|
415
|
-
await extractVideoThumb(file, imgFilename, '00:00:00', { width: 32, height: 32 })
|
|
416
|
-
const buff = await promises.readFile(imgFilename)
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
await promises.unlink(imgFilename)
|
|
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');
|
|
373
|
+
await fs_1.promises.unlink(imgFilename);
|
|
421
374
|
}
|
|
422
375
|
catch (err) {
|
|
423
|
-
options.logger
|
|
376
|
+
(_a = options.logger) === null || _a === void 0 ? void 0 : _a.debug('could not generate video thumb: ' + err);
|
|
424
377
|
}
|
|
425
378
|
}
|
|
426
379
|
return {
|
|
427
380
|
thumbnail,
|
|
428
381
|
originalImageDimensions
|
|
429
|
-
}
|
|
382
|
+
};
|
|
430
383
|
}
|
|
431
|
-
|
|
384
|
+
exports.generateThumbnail = generateThumbnail;
|
|
432
385
|
const getHttpStream = async (url, options = {}) => {
|
|
433
|
-
const
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
return response.body instanceof Readable ? response.body : Readable.fromWeb(response.body)
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
/*const prepareStream = async (media, mediaType, { logger, saveOriginalFileIfRequired, opts } = {}) => {
|
|
447
|
-
const { stream, type } = await getStream(media, opts)
|
|
448
|
-
logger?.debug('fetched media stream')
|
|
449
|
-
|
|
450
|
-
const encFilePath = join(tmpdir(), mediaType + generateMessageID() + '-plain')
|
|
451
|
-
const encFileWriteStream = createWriteStream(encFilePath)
|
|
452
|
-
|
|
453
|
-
let originalFilePath
|
|
454
|
-
let originalFileStream
|
|
455
|
-
|
|
456
|
-
if (type === 'file') {
|
|
457
|
-
originalFilePath = media.url.toString()
|
|
458
|
-
} else if (saveOriginalFileIfRequired) {
|
|
459
|
-
originalFilePath = join(tmpdir(), mediaType + generateMessageID() + '-original')
|
|
460
|
-
originalFileStream = createWriteStream(originalFilePath)
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
let fileLength = 0
|
|
464
|
-
const sha256 = createHash('sha256')
|
|
465
|
-
|
|
386
|
+
const { default: axios } = await import('axios');
|
|
387
|
+
const fetched = await axios.get(url.toString(), { ...options, responseType: 'stream' });
|
|
388
|
+
return fetched.data;
|
|
389
|
+
};
|
|
390
|
+
exports.getHttpStream = getHttpStream;
|
|
391
|
+
const prepareStream = async (media, mediaType, { logger, saveOriginalFileIfRequired, opts } = {}) => {
|
|
392
|
+
const { stream, type } = await (0, exports.getStream)(media, opts);
|
|
393
|
+
logger === null || logger === void 0 ? void 0 : logger.debug('fetched media stream');
|
|
394
|
+
let bodyPath;
|
|
395
|
+
let didSaveToTmpPath = false;
|
|
466
396
|
try {
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
if (originalFileStream && !originalFileStream.write(data)) {
|
|
482
|
-
await once(originalFileStream, 'drain')
|
|
483
|
-
}
|
|
484
|
-
}
|
|
485
|
-
|
|
486
|
-
const fileSha256 = sha256.digest()
|
|
487
|
-
encFileWriteStream.end()
|
|
488
|
-
originalFileStream?.end?.call(originalFileStream)
|
|
489
|
-
stream.destroy()
|
|
490
|
-
|
|
491
|
-
logger?.debug('prepared plain stream successfully')
|
|
492
|
-
|
|
397
|
+
const buffer = await (0, exports.toBuffer)(stream);
|
|
398
|
+
if (type === 'file') {
|
|
399
|
+
bodyPath = media.url;
|
|
400
|
+
}
|
|
401
|
+
else if (saveOriginalFileIfRequired) {
|
|
402
|
+
bodyPath = (0, path_1.join)(getTmpFilesDirectory(), mediaType + (0, generics_1.generateMessageID)());
|
|
403
|
+
(0, fs_1.writeFileSync)(bodyPath, buffer);
|
|
404
|
+
didSaveToTmpPath = true;
|
|
405
|
+
}
|
|
406
|
+
const fileLength = buffer.length;
|
|
407
|
+
const fileSha256 = Crypto.createHash('sha256').update(buffer).digest();
|
|
408
|
+
stream === null || stream === void 0 ? void 0 : stream.destroy();
|
|
409
|
+
logger === null || logger === void 0 ? void 0 : logger.debug('prepare stream data successfully');
|
|
493
410
|
return {
|
|
494
411
|
mediaKey: undefined,
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
mac: undefined,
|
|
498
|
-
fileEncSha256: undefined,
|
|
412
|
+
encWriteStream: buffer,
|
|
413
|
+
fileLength,
|
|
499
414
|
fileSha256,
|
|
500
|
-
|
|
501
|
-
|
|
415
|
+
fileEncSha256: undefined,
|
|
416
|
+
bodyPath,
|
|
417
|
+
didSaveToTmpPath
|
|
418
|
+
};
|
|
502
419
|
}
|
|
503
420
|
catch (error) {
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
421
|
+
// destroy all streams with error
|
|
422
|
+
stream.destroy();
|
|
423
|
+
if (didSaveToTmpPath) {
|
|
424
|
+
try {
|
|
425
|
+
await fs_1.promises.unlink(bodyPath);
|
|
426
|
+
}
|
|
427
|
+
catch (err) {
|
|
428
|
+
logger === null || logger === void 0 ? void 0 : logger.error({ err }, 'failed to save to tmp path');
|
|
512
429
|
}
|
|
513
|
-
} catch (err) {
|
|
514
|
-
logger?.error({ err }, 'failed deleting tmp files')
|
|
515
430
|
}
|
|
516
|
-
throw error
|
|
431
|
+
throw error;
|
|
517
432
|
}
|
|
518
|
-
}
|
|
519
|
-
|
|
433
|
+
};
|
|
434
|
+
exports.prepareStream = prepareStream;
|
|
520
435
|
const encryptedStream = async (media, mediaType, { logger, saveOriginalFileIfRequired, opts, isPtt, forceOpus } = {}) => {
|
|
521
|
-
const { stream, type } = await getStream(media, opts)
|
|
436
|
+
const { stream, type } = await (0, exports.getStream)(media, opts);
|
|
522
437
|
|
|
523
438
|
let finalStream = stream;
|
|
524
439
|
if (mediaType === 'audio' && (isPtt === true || forceOpus === true)) {
|
|
525
440
|
try {
|
|
526
|
-
const buffer = await toBuffer(stream);
|
|
527
|
-
const opusBuffer = await convertToOpusBuffer(buffer, logger);
|
|
528
|
-
finalStream = toReadable(opusBuffer);
|
|
441
|
+
const buffer = await (0, exports.toBuffer)(stream);
|
|
442
|
+
const opusBuffer = await exports.convertToOpusBuffer(buffer, logger);
|
|
443
|
+
finalStream = (0, exports.toReadable)(opusBuffer);
|
|
529
444
|
} catch (error) {
|
|
530
|
-
|
|
531
|
-
throw error;
|
|
532
|
-
}
|
|
533
|
-
const { stream: newStream } = await getStream(media, opts);
|
|
445
|
+
const { stream: newStream } = await (0, exports.getStream)(media, opts);
|
|
534
446
|
finalStream = newStream;
|
|
535
447
|
}
|
|
536
448
|
}
|
|
537
449
|
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
const
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
if (saveOriginalFileIfRequired) {
|
|
549
|
-
originalFilePath = join(getTmpFilesDirectory(), mediaType + generateMessageID() + '-original')
|
|
550
|
-
originalFileStream = createWriteStream(originalFilePath)
|
|
450
|
+
const mediaKey = Crypto.randomBytes(32);
|
|
451
|
+
const { cipherKey, iv, macKey } = getMediaKeys(mediaKey, mediaType);
|
|
452
|
+
const encWriteStream = new stream_1.Readable({ read: () => { } });
|
|
453
|
+
let bodyPath;
|
|
454
|
+
let writeStream;
|
|
455
|
+
let didSaveToTmpPath = false;
|
|
456
|
+
|
|
457
|
+
if (type === 'file') {
|
|
458
|
+
bodyPath = media.url;
|
|
551
459
|
}
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
const hmac = createHmac('sha256', macKey).update(iv)
|
|
557
|
-
const sha256Plain = createHash('sha256');
|
|
558
|
-
const sha256Enc = createHash('sha256')
|
|
559
|
-
|
|
560
|
-
const onChunk = async (buff) => {
|
|
561
|
-
sha256Enc.update(buff)
|
|
562
|
-
hmac.update(buff)
|
|
563
|
-
|
|
564
|
-
// Handle backpressure: if write returns false, wait for drain
|
|
565
|
-
if (!encFileWriteStream.write(buff)) {
|
|
566
|
-
await once(encFileWriteStream, 'drain')
|
|
567
|
-
}
|
|
460
|
+
else if (saveOriginalFileIfRequired) {
|
|
461
|
+
bodyPath = (0, path_1.join)(getTmpFilesDirectory(), mediaType + (0, generics_1.generateMessageID)());
|
|
462
|
+
writeStream = (0, fs_1.createWriteStream)(bodyPath);
|
|
463
|
+
didSaveToTmpPath = true;
|
|
568
464
|
}
|
|
569
|
-
|
|
465
|
+
|
|
466
|
+
let fileLength = 0;
|
|
467
|
+
const aes = Crypto.createCipheriv('aes-256-cbc', cipherKey, iv);
|
|
468
|
+
let hmac = Crypto.createHmac('sha256', macKey).update(iv);
|
|
469
|
+
let sha256Plain = Crypto.createHash('sha256');
|
|
470
|
+
let sha256Enc = Crypto.createHash('sha256');
|
|
471
|
+
|
|
570
472
|
try {
|
|
571
|
-
for await (const data of
|
|
572
|
-
fileLength += data.length
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
opts
|
|
576
|
-
|
|
577
|
-
throw new Boom(`content length exceeded when encrypting "${type}"`, {
|
|
473
|
+
for await (const data of finalStream) {
|
|
474
|
+
fileLength += data.length;
|
|
475
|
+
if (type === 'remote'
|
|
476
|
+
&& (opts === null || opts === void 0 ? void 0 : opts.maxContentLength)
|
|
477
|
+
&& fileLength + data.length > opts.maxContentLength) {
|
|
478
|
+
throw new boom_1.Boom(`content length exceeded when encrypting "${type}"`, {
|
|
578
479
|
data: { media, type }
|
|
579
|
-
})
|
|
480
|
+
});
|
|
580
481
|
}
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
482
|
+
|
|
483
|
+
sha256Plain = sha256Plain.update(data);
|
|
484
|
+
if (writeStream) {
|
|
485
|
+
if (!writeStream.write(data)) {
|
|
486
|
+
await (0, events_1.once)(writeStream, 'drain');
|
|
585
487
|
}
|
|
586
488
|
}
|
|
587
|
-
|
|
588
|
-
sha256Plain.update(data)
|
|
589
|
-
|
|
590
|
-
await onChunk(aes.update(data))
|
|
489
|
+
onChunk(aes.update(data));
|
|
591
490
|
}
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
const mac = hmac.digest().slice(0, 10)
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
const originalFinishPromise = originalFileStream ? once(originalFileStream, 'finish') : Promise.resolve()
|
|
605
|
-
|
|
606
|
-
encFileWriteStream.end()
|
|
607
|
-
originalFileStream?.end?.()
|
|
608
|
-
stream.destroy()
|
|
609
|
-
|
|
610
|
-
// Wait for write streams to fully flush to disk
|
|
611
|
-
// This helps reduce memory pressure by allowing OS to release buffers
|
|
612
|
-
await encFinishPromise
|
|
613
|
-
await originalFinishPromise
|
|
614
|
-
|
|
615
|
-
logger?.debug('encrypted data successfully')
|
|
616
|
-
|
|
491
|
+
|
|
492
|
+
onChunk(aes.final());
|
|
493
|
+
const mac = hmac.digest().slice(0, 10);
|
|
494
|
+
sha256Enc = sha256Enc.update(mac);
|
|
495
|
+
const fileSha256 = sha256Plain.digest();
|
|
496
|
+
const fileEncSha256 = sha256Enc.digest();
|
|
497
|
+
|
|
498
|
+
encWriteStream.push(mac);
|
|
499
|
+
encWriteStream.push(null);
|
|
500
|
+
writeStream === null || writeStream === void 0 ? void 0 : writeStream.end();
|
|
501
|
+
finalStream.destroy();
|
|
502
|
+
|
|
617
503
|
return {
|
|
618
504
|
mediaKey,
|
|
619
|
-
|
|
620
|
-
|
|
505
|
+
encWriteStream,
|
|
506
|
+
bodyPath,
|
|
621
507
|
mac,
|
|
622
508
|
fileEncSha256,
|
|
623
509
|
fileSha256,
|
|
624
|
-
fileLength
|
|
625
|
-
|
|
510
|
+
fileLength,
|
|
511
|
+
didSaveToTmpPath
|
|
512
|
+
};
|
|
626
513
|
}
|
|
627
514
|
catch (error) {
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
await promises.unlink(originalFilePath)
|
|
515
|
+
encWriteStream.destroy();
|
|
516
|
+
writeStream === null || writeStream === void 0 ? void 0 : writeStream.destroy();
|
|
517
|
+
aes.destroy();
|
|
518
|
+
hmac.destroy();
|
|
519
|
+
sha256Plain.destroy();
|
|
520
|
+
sha256Enc.destroy();
|
|
521
|
+
finalStream.destroy();
|
|
522
|
+
|
|
523
|
+
if (didSaveToTmpPath) {
|
|
524
|
+
try {
|
|
525
|
+
await fs_1.promises.unlink(bodyPath);
|
|
526
|
+
}
|
|
527
|
+
catch (err) {
|
|
642
528
|
}
|
|
643
529
|
}
|
|
644
|
-
|
|
645
|
-
logger?.error({ err }, 'failed deleting tmp files')
|
|
646
|
-
}
|
|
647
|
-
throw error
|
|
530
|
+
throw error;
|
|
648
531
|
}
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
532
|
+
|
|
533
|
+
function onChunk(buff) {
|
|
534
|
+
sha256Enc = sha256Enc.update(buff);
|
|
535
|
+
hmac = hmac.update(buff);
|
|
536
|
+
encWriteStream.push(buff);
|
|
537
|
+
}
|
|
538
|
+
};
|
|
539
|
+
exports.encryptedStream = encryptedStream;
|
|
540
|
+
const DEF_HOST = 'mmg.whatsapp.net';
|
|
541
|
+
const AES_CHUNK_SIZE = 16;
|
|
655
542
|
const toSmallestChunkSize = (num) => {
|
|
656
|
-
return Math.floor(num / AES_CHUNK_SIZE) * AES_CHUNK_SIZE
|
|
657
|
-
}
|
|
658
|
-
const getUrlFromDirectPath = (directPath) => `https://${DEF_HOST}${directPath}
|
|
659
|
-
|
|
660
|
-
const downloadContentFromMessage =
|
|
661
|
-
|
|
662
|
-
const
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
}
|
|
667
|
-
|
|
668
|
-
const keys = await getMediaKeys(mediaKey, type)
|
|
669
|
-
return downloadEncryptedContent(downloadUrl, keys, opts)
|
|
670
|
-
}
|
|
671
|
-
|
|
543
|
+
return Math.floor(num / AES_CHUNK_SIZE) * AES_CHUNK_SIZE;
|
|
544
|
+
};
|
|
545
|
+
const getUrlFromDirectPath = (directPath) => `https://${DEF_HOST}${directPath}`;
|
|
546
|
+
exports.getUrlFromDirectPath = getUrlFromDirectPath;
|
|
547
|
+
const downloadContentFromMessage = ({ mediaKey, directPath, url }, type, opts = {}) => {
|
|
548
|
+
const downloadUrl = url || (0, exports.getUrlFromDirectPath)(directPath);
|
|
549
|
+
const keys = getMediaKeys(mediaKey, type);
|
|
550
|
+
return (0, exports.downloadEncryptedContent)(downloadUrl, keys, opts);
|
|
551
|
+
};
|
|
552
|
+
exports.downloadContentFromMessage = downloadContentFromMessage;
|
|
672
553
|
/**
|
|
673
554
|
* Decrypts and downloads an AES256-CBC encrypted file given the keys.
|
|
674
555
|
* Assumes the SHA256 of the plaintext is appended to the end of the ciphertext
|
|
675
556
|
* */
|
|
676
557
|
const downloadEncryptedContent = async (downloadUrl, { cipherKey, iv }, { startByte, endByte, options } = {}) => {
|
|
677
|
-
let bytesFetched = 0
|
|
678
|
-
let startChunk = 0
|
|
679
|
-
let firstBlockIsIV = false
|
|
680
|
-
|
|
558
|
+
let bytesFetched = 0;
|
|
559
|
+
let startChunk = 0;
|
|
560
|
+
let firstBlockIsIV = false;
|
|
681
561
|
// if a start byte is specified -- then we need to fetch the previous chunk as that will form the IV
|
|
682
562
|
if (startByte) {
|
|
683
|
-
const chunk = toSmallestChunkSize(startByte || 0)
|
|
684
|
-
|
|
563
|
+
const chunk = toSmallestChunkSize(startByte || 0);
|
|
685
564
|
if (chunk) {
|
|
686
|
-
startChunk = chunk - AES_CHUNK_SIZE
|
|
687
|
-
bytesFetched = chunk
|
|
688
|
-
firstBlockIsIV = true
|
|
565
|
+
startChunk = chunk - AES_CHUNK_SIZE;
|
|
566
|
+
bytesFetched = chunk;
|
|
567
|
+
firstBlockIsIV = true;
|
|
689
568
|
}
|
|
690
569
|
}
|
|
691
|
-
|
|
692
|
-
const endChunk = endByte ? toSmallestChunkSize(endByte || 0) + AES_CHUNK_SIZE : undefined
|
|
693
|
-
const headersInit = options?.headers ? options.headers : undefined
|
|
570
|
+
const endChunk = endByte ? toSmallestChunkSize(endByte || 0) + AES_CHUNK_SIZE : undefined;
|
|
694
571
|
const headers = {
|
|
695
|
-
...(
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
: headersInit
|
|
699
|
-
: {}),
|
|
700
|
-
Origin: DEFAULT_ORIGIN
|
|
701
|
-
}
|
|
702
|
-
|
|
572
|
+
...(options === null || options === void 0 ? void 0 : options.headers) || {},
|
|
573
|
+
Origin: Defaults_1.DEFAULT_ORIGIN,
|
|
574
|
+
};
|
|
703
575
|
if (startChunk || endChunk) {
|
|
704
|
-
headers.Range = `bytes=${startChunk}
|
|
705
|
-
|
|
576
|
+
headers.Range = `bytes=${startChunk}-`;
|
|
706
577
|
if (endChunk) {
|
|
707
|
-
headers.Range += endChunk
|
|
578
|
+
headers.Range += endChunk;
|
|
708
579
|
}
|
|
709
580
|
}
|
|
710
|
-
|
|
711
581
|
// download the message
|
|
712
|
-
const fetched = await getHttpStream(downloadUrl, {
|
|
713
|
-
...
|
|
714
|
-
headers
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
let
|
|
719
|
-
|
|
582
|
+
const fetched = await (0, exports.getHttpStream)(downloadUrl, {
|
|
583
|
+
...options || {},
|
|
584
|
+
headers,
|
|
585
|
+
maxBodyLength: Infinity,
|
|
586
|
+
maxContentLength: Infinity,
|
|
587
|
+
});
|
|
588
|
+
let remainingBytes = Buffer.from([]);
|
|
589
|
+
let aes;
|
|
720
590
|
const pushBytes = (bytes, push) => {
|
|
721
591
|
if (startByte || endByte) {
|
|
722
|
-
const start = bytesFetched >= startByte ? undefined : Math.max(startByte - bytesFetched, 0)
|
|
723
|
-
const end = bytesFetched + bytes.length < endByte ? undefined : Math.max(endByte - bytesFetched, 0)
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
bytesFetched += bytes.length
|
|
592
|
+
const start = bytesFetched >= startByte ? undefined : Math.max(startByte - bytesFetched, 0);
|
|
593
|
+
const end = bytesFetched + bytes.length < endByte ? undefined : Math.max(endByte - bytesFetched, 0);
|
|
594
|
+
push(bytes.slice(start, end));
|
|
595
|
+
bytesFetched += bytes.length;
|
|
727
596
|
}
|
|
728
597
|
else {
|
|
729
|
-
push(bytes)
|
|
598
|
+
push(bytes);
|
|
730
599
|
}
|
|
731
|
-
}
|
|
732
|
-
|
|
733
|
-
const output = new Transform({
|
|
600
|
+
};
|
|
601
|
+
const output = new stream_1.Transform({
|
|
734
602
|
transform(chunk, _, callback) {
|
|
735
|
-
let data = Buffer.concat([remainingBytes, chunk])
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
remainingBytes = data.slice(decryptLength)
|
|
740
|
-
data = data.slice(0, decryptLength)
|
|
741
|
-
|
|
603
|
+
let data = Buffer.concat([remainingBytes, chunk]);
|
|
604
|
+
const decryptLength = toSmallestChunkSize(data.length);
|
|
605
|
+
remainingBytes = data.slice(decryptLength);
|
|
606
|
+
data = data.slice(0, decryptLength);
|
|
742
607
|
if (!aes) {
|
|
743
|
-
let ivValue = iv
|
|
744
|
-
|
|
608
|
+
let ivValue = iv;
|
|
745
609
|
if (firstBlockIsIV) {
|
|
746
|
-
ivValue = data.slice(0, AES_CHUNK_SIZE)
|
|
747
|
-
data = data.slice(AES_CHUNK_SIZE)
|
|
610
|
+
ivValue = data.slice(0, AES_CHUNK_SIZE);
|
|
611
|
+
data = data.slice(AES_CHUNK_SIZE);
|
|
748
612
|
}
|
|
749
|
-
|
|
750
|
-
aes = createDecipheriv('aes-256-cbc', cipherKey, ivValue)
|
|
751
|
-
|
|
613
|
+
aes = Crypto.createDecipheriv('aes-256-cbc', cipherKey, ivValue);
|
|
752
614
|
// if an end byte that is not EOF is specified
|
|
753
615
|
// stop auto padding (PKCS7) -- otherwise throws an error for decryption
|
|
754
616
|
if (endByte) {
|
|
755
|
-
aes.setAutoPadding(false)
|
|
617
|
+
aes.setAutoPadding(false);
|
|
756
618
|
}
|
|
757
619
|
}
|
|
758
620
|
try {
|
|
759
|
-
pushBytes(aes.update(data), b => this.push(b))
|
|
760
|
-
callback()
|
|
621
|
+
pushBytes(aes.update(data), b => this.push(b));
|
|
622
|
+
callback();
|
|
761
623
|
}
|
|
762
624
|
catch (error) {
|
|
763
|
-
callback(error)
|
|
625
|
+
callback(error);
|
|
764
626
|
}
|
|
765
627
|
},
|
|
766
628
|
final(callback) {
|
|
767
629
|
try {
|
|
768
|
-
pushBytes(aes.final(), b => this.push(b))
|
|
769
|
-
callback()
|
|
630
|
+
pushBytes(aes.final(), b => this.push(b));
|
|
631
|
+
callback();
|
|
770
632
|
}
|
|
771
633
|
catch (error) {
|
|
772
|
-
callback(error)
|
|
634
|
+
callback(error);
|
|
773
635
|
}
|
|
774
|
-
}
|
|
775
|
-
})
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
636
|
+
},
|
|
637
|
+
});
|
|
638
|
+
return fetched.pipe(output, { end: true });
|
|
639
|
+
};
|
|
640
|
+
exports.downloadEncryptedContent = downloadEncryptedContent;
|
|
780
641
|
function extensionForMediaMessage(message) {
|
|
781
|
-
const getExtension = (mimetype) => mimetype.split('')[0].split('/')[1]
|
|
782
|
-
const type = Object.keys(message)[0]
|
|
783
|
-
let extension
|
|
642
|
+
const getExtension = (mimetype) => mimetype.split(';')[0].split('/')[1];
|
|
643
|
+
const type = Object.keys(message)[0];
|
|
644
|
+
let extension;
|
|
784
645
|
if (type === 'locationMessage' ||
|
|
785
646
|
type === 'liveLocationMessage' ||
|
|
786
647
|
type === 'productMessage') {
|
|
787
|
-
extension = '.jpeg'
|
|
788
|
-
}
|
|
789
|
-
else {
|
|
790
|
-
const messageContent = message[type]
|
|
791
|
-
extension = getExtension(messageContent.mimetype)
|
|
792
|
-
}
|
|
793
|
-
return extension
|
|
794
|
-
}
|
|
795
|
-
|
|
796
|
-
const isNodeRuntime = () => {
|
|
797
|
-
return (typeof process !== 'undefined' &&
|
|
798
|
-
process.versions?.node !== null &&
|
|
799
|
-
typeof process.versions.bun === 'undefined' &&
|
|
800
|
-
typeof globalThis.Deno === 'undefined')
|
|
801
|
-
}
|
|
802
|
-
|
|
803
|
-
const uploadWithNodeHttp = async ({ url, filePath, headers, timeoutMs, agent }, redirectCount = 0) => {
|
|
804
|
-
if (redirectCount > 5) {
|
|
805
|
-
throw new Error('Too many redirects')
|
|
806
|
-
}
|
|
807
|
-
|
|
808
|
-
const parsedUrl = new URL(url)
|
|
809
|
-
const httpModule = parsedUrl.protocol === 'https:' ? require('https') : require('http')
|
|
810
|
-
|
|
811
|
-
// Get file size for Content-Length header (required for Node.js streaming)
|
|
812
|
-
const fileStats = await promises.stat(filePath)
|
|
813
|
-
const fileSize = fileStats.size
|
|
814
|
-
|
|
815
|
-
return new Promise((resolve, reject) => {
|
|
816
|
-
const req = httpModule.request({
|
|
817
|
-
hostname: parsedUrl.hostname,
|
|
818
|
-
port: parsedUrl.port || (parsedUrl.protocol === 'https:' ? 443 : 80),
|
|
819
|
-
path: parsedUrl.pathname + parsedUrl.search,
|
|
820
|
-
method: 'POST',
|
|
821
|
-
headers: {
|
|
822
|
-
...headers,
|
|
823
|
-
'Content-Length': fileSize
|
|
824
|
-
},
|
|
825
|
-
agent,
|
|
826
|
-
timeout: timeoutMs
|
|
827
|
-
}, res => {
|
|
828
|
-
// Handle redirects (3xx)
|
|
829
|
-
if (res.statusCode && res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
|
|
830
|
-
res.resume() // Consume response to free resources
|
|
831
|
-
|
|
832
|
-
const newUrl = new URL(res.headers.location, url).toString()
|
|
833
|
-
|
|
834
|
-
resolve(uploadWithNodeHttp({
|
|
835
|
-
url: newUrl,
|
|
836
|
-
filePath,
|
|
837
|
-
headers,
|
|
838
|
-
timeoutMs,
|
|
839
|
-
agent
|
|
840
|
-
}, redirectCount + 1))
|
|
841
|
-
return
|
|
842
|
-
}
|
|
843
|
-
|
|
844
|
-
let body = ''
|
|
845
|
-
|
|
846
|
-
res.on('data', chunk => (body += chunk))
|
|
847
|
-
res.on('end', () => {
|
|
848
|
-
try {
|
|
849
|
-
resolve(JSON.parse(body))
|
|
850
|
-
}
|
|
851
|
-
catch {
|
|
852
|
-
resolve(undefined)
|
|
853
|
-
}
|
|
854
|
-
})
|
|
855
|
-
})
|
|
856
|
-
|
|
857
|
-
req.on('error', reject)
|
|
858
|
-
req.on('timeout', () => {
|
|
859
|
-
req.destroy()
|
|
860
|
-
reject(new Error('Upload timeout'))
|
|
861
|
-
})
|
|
862
|
-
|
|
863
|
-
const stream = createReadStream(filePath)
|
|
864
|
-
|
|
865
|
-
stream.pipe(req)
|
|
866
|
-
stream.on('error', err => {
|
|
867
|
-
req.destroy()
|
|
868
|
-
reject(err)
|
|
869
|
-
})
|
|
870
|
-
})
|
|
871
|
-
}
|
|
872
|
-
|
|
873
|
-
const uploadWithFetch = async ({ url, filePath, headers, timeoutMs, agent }) => {
|
|
874
|
-
// Convert Node.js Readable to Web ReadableStream
|
|
875
|
-
const nodeStream = createReadStream(filePath)
|
|
876
|
-
const webStream = Readable.toWeb(nodeStream)
|
|
877
|
-
const response = await fetch(url, {
|
|
878
|
-
dispatcher: agent,
|
|
879
|
-
method: 'POST',
|
|
880
|
-
body: webStream,
|
|
881
|
-
headers,
|
|
882
|
-
duplex: 'half',
|
|
883
|
-
signal: timeoutMs ? AbortSignal.timeout(timeoutMs) : undefined
|
|
884
|
-
})
|
|
885
|
-
|
|
886
|
-
try {
|
|
887
|
-
return (await response.json())
|
|
888
|
-
}
|
|
889
|
-
catch {
|
|
890
|
-
return undefined
|
|
891
|
-
}
|
|
892
|
-
}
|
|
893
|
-
|
|
894
|
-
/**
|
|
895
|
-
* Uploads media to WhatsApp servers.
|
|
896
|
-
*
|
|
897
|
-
* ## Why we have two upload implementations:
|
|
898
|
-
*
|
|
899
|
-
* Node.js's native `fetch` (powered by undici) has a known bug where it buffers
|
|
900
|
-
* the entire request body in memory before sending, even when using streams.
|
|
901
|
-
* This causes memory issues with large files (e.g., 1GB file = 1GB+ memory usage).
|
|
902
|
-
* See: https://github.com/nodejs/undici/issues/4058
|
|
903
|
-
*
|
|
904
|
-
* Other runtimes (Bun, Deno, browsers) correctly stream the request body without
|
|
905
|
-
* buffering, so we can use the web-standard Fetch API there.
|
|
906
|
-
*
|
|
907
|
-
* ## Future considerations:
|
|
908
|
-
* Once the undici bug is fixed, we can simplify this to use only the Fetch API
|
|
909
|
-
* across all runtimes. Monitor the GitHub issue for updates.
|
|
910
|
-
*/
|
|
911
|
-
const uploadMedia = async (params, logger) => {
|
|
912
|
-
if (isNodeRuntime()) {
|
|
913
|
-
logger?.debug('Using Node.js https module for upload (avoids undici buffering bug)')
|
|
914
|
-
return uploadWithNodeHttp(params)
|
|
648
|
+
extension = '.jpeg';
|
|
915
649
|
}
|
|
916
650
|
else {
|
|
917
|
-
|
|
918
|
-
|
|
651
|
+
const messageContent = message[type];
|
|
652
|
+
extension = getExtension(messageContent.mimetype);
|
|
919
653
|
}
|
|
654
|
+
return extension;
|
|
920
655
|
}
|
|
921
|
-
|
|
656
|
+
exports.extensionForMediaMessage = extensionForMediaMessage;
|
|
922
657
|
const getWAUploadToServer = ({ customUploadHosts, fetchAgent, logger, options }, refreshMediaConn) => {
|
|
923
|
-
return async (
|
|
658
|
+
return async (stream, { mediaType, fileEncSha256B64, newsletter, timeoutMs }) => {
|
|
659
|
+
var _a, _b;
|
|
660
|
+
const { default: axios } = await import('axios');
|
|
924
661
|
// send a query JSON to obtain the url & auth token to upload our media
|
|
925
|
-
let uploadInfo = await refreshMediaConn(false)
|
|
926
|
-
let urls
|
|
927
|
-
|
|
928
|
-
const
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
const customHeaders = (() => {
|
|
934
|
-
const hdrs = options?.headers;
|
|
935
|
-
if (!hdrs)
|
|
936
|
-
return {};
|
|
937
|
-
return Array.isArray(hdrs) ? Object.fromEntries(hdrs) : hdrs
|
|
938
|
-
})()
|
|
939
|
-
|
|
940
|
-
const headers = {
|
|
941
|
-
...customHeaders,
|
|
942
|
-
'Content-Type': 'application/octet-stream',
|
|
943
|
-
Origin: DEFAULT_ORIGIN
|
|
662
|
+
let uploadInfo = await refreshMediaConn(false);
|
|
663
|
+
let urls;
|
|
664
|
+
const hosts = [...customUploadHosts, ...uploadInfo.hosts];
|
|
665
|
+
const chunks = [];
|
|
666
|
+
if (!Buffer.isBuffer(stream)) {
|
|
667
|
+
for await (const chunk of stream) {
|
|
668
|
+
chunks.push(chunk);
|
|
669
|
+
}
|
|
944
670
|
}
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
671
|
+
const reqBody = Buffer.isBuffer(stream) ? stream : Buffer.concat(chunks);
|
|
672
|
+
fileEncSha256B64 = (0, exports.encodeBase64EncodedStringForUpload)(fileEncSha256B64);
|
|
673
|
+
let media = Defaults_1.MEDIA_PATH_MAP[mediaType];
|
|
674
|
+
if (newsletter) {
|
|
675
|
+
media = media === null || media === void 0 ? void 0 : media.replace('/mms/', '/newsletter/newsletter-');
|
|
676
|
+
}
|
|
677
|
+
for (const { hostname, maxContentLengthBytes } of hosts) {
|
|
678
|
+
logger.debug(`uploading to "${hostname}"`);
|
|
679
|
+
const auth = encodeURIComponent(uploadInfo.auth); // the auth token
|
|
680
|
+
const url = `https://${hostname}${media}/${fileEncSha256B64}?auth=${auth}&token=${fileEncSha256B64}`;
|
|
681
|
+
let result;
|
|
954
682
|
try {
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
683
|
+
if (maxContentLengthBytes && reqBody.length > maxContentLengthBytes) {
|
|
684
|
+
throw new boom_1.Boom(`Body too large for "${hostname}"`, { statusCode: 413 });
|
|
685
|
+
}
|
|
686
|
+
const body = await axios.post(url, reqBody, {
|
|
687
|
+
...options,
|
|
688
|
+
headers: {
|
|
689
|
+
...options.headers || {},
|
|
690
|
+
'Content-Type': 'application/octet-stream',
|
|
691
|
+
'Origin': Defaults_1.DEFAULT_ORIGIN
|
|
692
|
+
},
|
|
693
|
+
httpsAgent: fetchAgent,
|
|
694
|
+
timeout: timeoutMs,
|
|
695
|
+
responseType: 'json',
|
|
696
|
+
maxBodyLength: Infinity,
|
|
697
|
+
maxContentLength: Infinity,
|
|
698
|
+
});
|
|
699
|
+
result = body.data;
|
|
700
|
+
if ((result === null || result === void 0 ? void 0 : result.url) || (result === null || result === void 0 ? void 0 : result.directPath)) {
|
|
963
701
|
urls = {
|
|
964
702
|
mediaUrl: result.url,
|
|
965
703
|
directPath: result.direct_path,
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
}
|
|
970
|
-
break
|
|
704
|
+
handle: result.handle
|
|
705
|
+
};
|
|
706
|
+
break;
|
|
971
707
|
}
|
|
972
708
|
else {
|
|
973
|
-
uploadInfo = await refreshMediaConn(true)
|
|
974
|
-
throw new Error(`upload failed, reason: ${JSON.stringify(result)}`)
|
|
709
|
+
uploadInfo = await refreshMediaConn(true);
|
|
710
|
+
throw new Error(`upload failed, reason: ${JSON.stringify(result)}`);
|
|
975
711
|
}
|
|
976
712
|
}
|
|
977
713
|
catch (error) {
|
|
978
|
-
|
|
979
|
-
|
|
714
|
+
if (axios.isAxiosError(error)) {
|
|
715
|
+
result = (_a = error.response) === null || _a === void 0 ? void 0 : _a.data;
|
|
716
|
+
}
|
|
717
|
+
const isLast = hostname === ((_b = hosts[uploadInfo.hosts.length - 1]) === null || _b === void 0 ? void 0 : _b.hostname);
|
|
718
|
+
logger.warn({ trace: error.stack, uploadResult: result }, `Error in uploading to ${hostname} ${isLast ? '' : ', retrying...'}`);
|
|
980
719
|
}
|
|
981
720
|
}
|
|
982
|
-
|
|
983
721
|
if (!urls) {
|
|
984
|
-
throw new Boom('Media upload failed on all hosts', { statusCode: 500 })
|
|
722
|
+
throw new boom_1.Boom('Media upload failed on all hosts', { statusCode: 500 });
|
|
985
723
|
}
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
724
|
+
return urls;
|
|
725
|
+
};
|
|
726
|
+
};
|
|
727
|
+
exports.getWAUploadToServer = getWAUploadToServer;
|
|
991
728
|
const getMediaRetryKey = (mediaKey) => {
|
|
992
|
-
return hkdf(mediaKey, 32, { info: 'WhatsApp Media Retry Notification' })
|
|
993
|
-
}
|
|
729
|
+
return (0, crypto_1.hkdf)(mediaKey, 32, { info: 'WhatsApp Media Retry Notification' });
|
|
730
|
+
};
|
|
994
731
|
/**
|
|
995
732
|
* Generate a binary node that will request the phone to re-upload the media & return the newly uploaded URL
|
|
996
733
|
*/
|
|
997
|
-
const encryptMediaRetryRequest =
|
|
998
|
-
const recp = { stanzaId: key.id }
|
|
999
|
-
const recpBuffer = proto.ServerErrorReceipt.encode(recp).finish()
|
|
1000
|
-
const iv = randomBytes(12)
|
|
1001
|
-
const retryKey =
|
|
1002
|
-
const ciphertext = aesEncryptGCM(recpBuffer, retryKey, iv, Buffer.from(key.id))
|
|
734
|
+
const encryptMediaRetryRequest = (key, mediaKey, meId) => {
|
|
735
|
+
const recp = { stanzaId: key.id };
|
|
736
|
+
const recpBuffer = WAProto_1.proto.ServerErrorReceipt.encode(recp).finish();
|
|
737
|
+
const iv = Crypto.randomBytes(12);
|
|
738
|
+
const retryKey = getMediaRetryKey(mediaKey);
|
|
739
|
+
const ciphertext = (0, crypto_1.aesEncryptGCM)(recpBuffer, retryKey, iv, Buffer.from(key.id));
|
|
1003
740
|
const req = {
|
|
1004
741
|
tag: 'receipt',
|
|
1005
742
|
attrs: {
|
|
1006
743
|
id: key.id,
|
|
1007
|
-
to: jidNormalizedUser(meId),
|
|
744
|
+
to: (0, WABinary_1.jidNormalizedUser)(meId),
|
|
1008
745
|
type: 'server-error'
|
|
1009
746
|
},
|
|
1010
747
|
content: [
|
|
@@ -1023,17 +760,18 @@ const encryptMediaRetryRequest = async (key, mediaKey, meId) => {
|
|
|
1023
760
|
tag: 'rmr',
|
|
1024
761
|
attrs: {
|
|
1025
762
|
jid: key.remoteJid,
|
|
1026
|
-
from_me: (!!key.fromMe).toString(),
|
|
763
|
+
'from_me': (!!key.fromMe).toString(),
|
|
764
|
+
// @ts-ignore
|
|
1027
765
|
participant: key.participant || undefined
|
|
1028
766
|
}
|
|
1029
767
|
}
|
|
1030
768
|
]
|
|
1031
|
-
}
|
|
1032
|
-
return req
|
|
1033
|
-
}
|
|
1034
|
-
|
|
769
|
+
};
|
|
770
|
+
return req;
|
|
771
|
+
};
|
|
772
|
+
exports.encryptMediaRetryRequest = encryptMediaRetryRequest;
|
|
1035
773
|
const decodeMediaRetryNode = (node) => {
|
|
1036
|
-
const rmrNode = getBinaryNodeChild(node, 'rmr')
|
|
774
|
+
const rmrNode = (0, WABinary_1.getBinaryNodeChild)(node, 'rmr');
|
|
1037
775
|
const event = {
|
|
1038
776
|
key: {
|
|
1039
777
|
id: node.attrs.id,
|
|
@@ -1041,69 +779,41 @@ const decodeMediaRetryNode = (node) => {
|
|
|
1041
779
|
fromMe: rmrNode.attrs.from_me === 'true',
|
|
1042
780
|
participant: rmrNode.attrs.participant
|
|
1043
781
|
}
|
|
1044
|
-
}
|
|
1045
|
-
const errorNode = getBinaryNodeChild(node, 'error')
|
|
782
|
+
};
|
|
783
|
+
const errorNode = (0, WABinary_1.getBinaryNodeChild)(node, 'error');
|
|
1046
784
|
if (errorNode) {
|
|
1047
|
-
const errorCode = +errorNode.attrs.code
|
|
1048
|
-
event.error = new Boom(`Failed to re-upload media (${errorCode})`, { data: errorNode.attrs, statusCode: getStatusCodeForMediaRetry(errorCode) })
|
|
785
|
+
const errorCode = +errorNode.attrs.code;
|
|
786
|
+
event.error = new boom_1.Boom(`Failed to re-upload media (${errorCode})`, { data: errorNode.attrs, statusCode: (0, exports.getStatusCodeForMediaRetry)(errorCode) });
|
|
1049
787
|
}
|
|
1050
788
|
else {
|
|
1051
|
-
const encryptedInfoNode = getBinaryNodeChild(node, 'encrypt')
|
|
1052
|
-
const ciphertext = getBinaryNodeChildBuffer(encryptedInfoNode, 'enc_p')
|
|
1053
|
-
const iv = getBinaryNodeChildBuffer(encryptedInfoNode, 'enc_iv')
|
|
789
|
+
const encryptedInfoNode = (0, WABinary_1.getBinaryNodeChild)(node, 'encrypt');
|
|
790
|
+
const ciphertext = (0, WABinary_1.getBinaryNodeChildBuffer)(encryptedInfoNode, 'enc_p');
|
|
791
|
+
const iv = (0, WABinary_1.getBinaryNodeChildBuffer)(encryptedInfoNode, 'enc_iv');
|
|
1054
792
|
if (ciphertext && iv) {
|
|
1055
|
-
event.media = { ciphertext, iv }
|
|
793
|
+
event.media = { ciphertext, iv };
|
|
1056
794
|
}
|
|
1057
795
|
else {
|
|
1058
|
-
event.error = new Boom('Failed to re-upload media (missing ciphertext)', { statusCode: 404 })
|
|
1059
|
-
}
|
|
1060
|
-
}
|
|
1061
|
-
return event
|
|
1062
|
-
}
|
|
1063
|
-
|
|
1064
|
-
const decryptMediaRetryData =
|
|
1065
|
-
const retryKey =
|
|
1066
|
-
const plaintext = aesDecryptGCM(ciphertext, retryKey, iv, Buffer.from(msgId))
|
|
1067
|
-
return proto.MediaRetryNotification.decode(plaintext)
|
|
1068
|
-
}
|
|
1069
|
-
|
|
1070
|
-
const getStatusCodeForMediaRetry = (code) => MEDIA_RETRY_STATUS_MAP[code]
|
|
1071
|
-
|
|
796
|
+
event.error = new boom_1.Boom('Failed to re-upload media (missing ciphertext)', { statusCode: 404 });
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
return event;
|
|
800
|
+
};
|
|
801
|
+
exports.decodeMediaRetryNode = decodeMediaRetryNode;
|
|
802
|
+
const decryptMediaRetryData = ({ ciphertext, iv }, mediaKey, msgId) => {
|
|
803
|
+
const retryKey = getMediaRetryKey(mediaKey);
|
|
804
|
+
const plaintext = (0, crypto_1.aesDecryptGCM)(ciphertext, retryKey, iv, Buffer.from(msgId));
|
|
805
|
+
return WAProto_1.proto.MediaRetryNotification.decode(plaintext);
|
|
806
|
+
};
|
|
807
|
+
exports.decryptMediaRetryData = decryptMediaRetryData;
|
|
808
|
+
const getStatusCodeForMediaRetry = (code) => MEDIA_RETRY_STATUS_MAP[code];
|
|
809
|
+
exports.getStatusCodeForMediaRetry = getStatusCodeForMediaRetry;
|
|
1072
810
|
const MEDIA_RETRY_STATUS_MAP = {
|
|
1073
|
-
[proto.MediaRetryNotification.ResultType.SUCCESS]: 200,
|
|
1074
|
-
[proto.MediaRetryNotification.ResultType.DECRYPTION_ERROR]: 412,
|
|
1075
|
-
[proto.MediaRetryNotification.ResultType.NOT_FOUND]: 404,
|
|
1076
|
-
[proto.MediaRetryNotification.ResultType.GENERAL_ERROR]: 418,
|
|
1077
|
-
}
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
getMediaKeys,
|
|
1082
|
-
extractVideoThumb,
|
|
1083
|
-
extractImageThumb,
|
|
1084
|
-
encodeBase64EncodedStringForUpload,
|
|
1085
|
-
generateProfilePicture,
|
|
1086
|
-
mediaMessageSHA256B64,
|
|
1087
|
-
getAudioDuration,
|
|
1088
|
-
getAudioWaveform,
|
|
1089
|
-
toReadable,
|
|
1090
|
-
toBuffer,
|
|
1091
|
-
getStream,
|
|
1092
|
-
generateThumbnail,
|
|
1093
|
-
getHttpStream,
|
|
1094
|
-
//prepareStream,
|
|
1095
|
-
encryptedStream,
|
|
1096
|
-
getUrlFromDirectPath,
|
|
1097
|
-
downloadContentFromMessage,
|
|
1098
|
-
downloadEncryptedContent,
|
|
1099
|
-
extensionForMediaMessage,
|
|
1100
|
-
uploadWithNodeHttp,
|
|
1101
|
-
getRawMediaUploadData,
|
|
1102
|
-
getWAUploadToServer,
|
|
1103
|
-
getMediaRetryKey,
|
|
1104
|
-
encryptMediaRetryRequest,
|
|
1105
|
-
decodeMediaRetryNode,
|
|
1106
|
-
decryptMediaRetryData,
|
|
1107
|
-
getStatusCodeForMediaRetry,
|
|
1108
|
-
MEDIA_RETRY_STATUS_MAP
|
|
811
|
+
[WAProto_1.proto.MediaRetryNotification.ResultType.SUCCESS]: 200,
|
|
812
|
+
[WAProto_1.proto.MediaRetryNotification.ResultType.DECRYPTION_ERROR]: 412,
|
|
813
|
+
[WAProto_1.proto.MediaRetryNotification.ResultType.NOT_FOUND]: 404,
|
|
814
|
+
[WAProto_1.proto.MediaRetryNotification.ResultType.GENERAL_ERROR]: 418,
|
|
815
|
+
};
|
|
816
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
817
|
+
function __importStar(arg0) {
|
|
818
|
+
throw new Error('Function not implemented.');
|
|
1109
819
|
}
|