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