@alannxd/baileys 6.0.4 → 6.0.5
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/WAProto/GenerateStatics.sh +3 -0
- package/WAProto/WAProto.proto +5479 -0
- package/WAProto/fix-imports.js +85 -0
- package/WAProto/index.d.ts +14017 -0
- package/WAProto/index.js +201 -160
- package/engine-requirements.js +1 -1
- package/lib/Defaults/index.d.ts +37 -15
- package/lib/Defaults/index.js +119 -136
- package/lib/Signal/Group/ciphertext-message.d.ts +1 -0
- package/lib/Signal/Group/ciphertext-message.js +2 -5
- package/lib/Signal/Group/group-session-builder.d.ts +4 -3
- package/lib/Signal/Group/group-session-builder.js +7 -41
- package/lib/Signal/Group/group_cipher.d.ts +4 -4
- package/lib/Signal/Group/group_cipher.js +37 -51
- package/lib/Signal/Group/index.d.ts +12 -11
- package/lib/Signal/Group/index.js +12 -57
- package/lib/Signal/Group/keyhelper.d.ts +2 -1
- package/lib/Signal/Group/keyhelper.js +7 -44
- package/lib/Signal/Group/sender-chain-key.d.ts +3 -2
- package/lib/Signal/Group/sender-chain-key.js +7 -15
- package/lib/Signal/Group/sender-key-distribution-message.d.ts +2 -1
- package/lib/Signal/Group/sender-key-distribution-message.js +8 -11
- package/lib/Signal/Group/sender-key-message.d.ts +2 -1
- package/lib/Signal/Group/sender-key-message.js +9 -12
- package/lib/Signal/Group/sender-key-name.d.ts +1 -0
- package/lib/Signal/Group/sender-key-name.js +2 -5
- package/lib/Signal/Group/sender-key-record.d.ts +3 -2
- package/lib/Signal/Group/sender-key-record.js +9 -21
- package/lib/Signal/Group/sender-key-state.d.ts +7 -6
- package/lib/Signal/Group/sender-key-state.js +27 -42
- package/lib/Signal/Group/sender-message-key.d.ts +1 -0
- package/lib/Signal/Group/sender-message-key.js +4 -7
- package/lib/Signal/libsignal.d.ts +5 -3
- package/lib/Signal/libsignal.js +347 -90
- package/lib/Signal/lid-mapping.d.ts +23 -0
- package/lib/Signal/lid-mapping.js +277 -0
- package/lib/Socket/Client/index.d.ts +3 -3
- package/lib/Socket/Client/index.js +3 -19
- package/lib/Socket/Client/{abstract-socket-client.d.ts → types.d.ts} +4 -5
- package/lib/Socket/Client/types.js +11 -0
- package/lib/Socket/Client/{web-socket-client.d.ts → websocket.d.ts} +3 -2
- package/lib/Socket/Client/websocket.js +54 -0
- package/lib/Socket/business.d.ts +154 -108
- package/lib/Socket/business.js +162 -43
- package/lib/Socket/chats.d.ts +96 -239
- package/lib/Socket/chats.js +627 -427
- package/lib/Socket/communities.d.ts +239 -146
- package/lib/Socket/communities.js +90 -80
- package/lib/Socket/groups.d.ts +104 -57
- package/lib/Socket/groups.js +154 -161
- package/lib/Socket/index.d.ts +202 -115
- package/lib/Socket/index.js +11 -10
- package/lib/Socket/luxu.d.ts +22 -266
- package/lib/Socket/luxu.js +422 -465
- package/lib/Socket/messages-recv.d.ts +136 -84
- package/lib/Socket/messages-recv.js +1421 -615
- package/lib/Socket/messages-send.d.ts +142 -126
- package/lib/Socket/messages-send.js +878 -671
- package/lib/Socket/mex.d.ts +3 -0
- package/lib/Socket/mex.js +42 -0
- package/lib/Socket/newsletter.d.ts +121 -85
- package/lib/Socket/newsletter.js +147 -272
- package/lib/Socket/socket.d.ts +34 -19
- package/lib/Socket/socket.js +544 -313
- package/lib/Store/index.d.ts +10 -3
- package/lib/Store/index.js +10 -10
- package/lib/Store/keyed-db.d.ts +22 -0
- package/lib/Store/keyed-db.js +108 -0
- package/lib/Store/make-cache-manager-store.d.ts +17 -11
- package/lib/Store/make-cache-manager-store.js +43 -41
- package/lib/Store/make-in-memory-store.d.ts +39 -118
- package/lib/Store/make-in-memory-store.js +112 -341
- package/lib/Store/make-ordered-dictionary.d.ts +11 -10
- package/lib/Store/make-ordered-dictionary.js +14 -20
- package/lib/Store/object-repository.d.ts +10 -9
- package/lib/Store/object-repository.js +11 -6
- package/lib/Types/Auth.d.ts +19 -12
- package/lib/Types/Auth.js +2 -2
- package/lib/Types/Bussines.d.ts +25 -0
- package/lib/Types/Bussines.js +2 -0
- package/lib/Types/Call.d.ts +3 -1
- package/lib/Types/Call.js +2 -2
- package/lib/Types/Chat.d.ts +35 -13
- package/lib/Types/Chat.js +8 -4
- package/lib/Types/Contact.d.ts +8 -1
- package/lib/Types/Contact.js +2 -2
- package/lib/Types/Events.d.ts +116 -17
- package/lib/Types/Events.js +2 -2
- package/lib/Types/GroupMetadata.d.ts +21 -5
- package/lib/Types/GroupMetadata.js +2 -2
- package/lib/Types/Label.d.ts +12 -0
- package/lib/Types/Label.js +3 -5
- package/lib/Types/LabelAssociation.d.ts +1 -0
- package/lib/Types/LabelAssociation.js +3 -5
- package/lib/Types/Message.d.ts +105 -58
- package/lib/Types/Message.js +11 -9
- package/lib/Types/Mex.d.ts +141 -0
- package/lib/Types/Mex.js +37 -0
- package/lib/Types/Product.d.ts +2 -1
- package/lib/Types/Product.js +2 -2
- package/lib/Types/Signal.d.ts +32 -2
- package/lib/Types/Signal.js +2 -2
- package/lib/Types/Socket.d.ts +50 -25
- package/lib/Types/Socket.js +3 -2
- package/lib/Types/State.d.ts +72 -2
- package/lib/Types/State.js +56 -2
- package/lib/Types/USync.d.ts +3 -2
- package/lib/Types/USync.js +2 -2
- package/lib/Types/index.d.ts +22 -14
- package/lib/Types/index.js +15 -31
- package/lib/Utils/auth-utils.d.ts +12 -6
- package/lib/Utils/auth-utils.js +239 -143
- package/lib/Utils/browser-utils.d.ts +4 -0
- package/lib/Utils/browser-utils.js +28 -0
- package/lib/Utils/business.d.ts +3 -2
- package/lib/Utils/business.js +66 -69
- package/lib/Utils/chat-utils.d.ts +52 -23
- package/lib/Utils/chat-utils.js +396 -253
- package/lib/Utils/companion-reg-client-utils.d.ts +17 -0
- package/lib/Utils/companion-reg-client-utils.js +35 -0
- package/lib/Utils/crypto.d.ts +18 -22
- package/lib/Utils/crypto.js +57 -90
- package/lib/Utils/decode-wa-message.d.ts +55 -8
- package/lib/Utils/decode-wa-message.js +203 -84
- package/lib/Utils/event-buffer.d.ts +9 -8
- package/lib/Utils/event-buffer.js +185 -77
- package/lib/Utils/generics.d.ts +28 -29
- package/lib/Utils/generics.js +180 -210
- package/lib/Utils/history.d.ts +18 -9
- package/lib/Utils/history.js +93 -55
- package/lib/Utils/identity-change-handler.d.ts +44 -0
- package/lib/Utils/identity-change-handler.js +50 -0
- package/lib/Utils/index.d.ts +22 -17
- package/lib/Utils/index.js +22 -33
- package/lib/Utils/link-preview.d.ts +5 -5
- package/lib/Utils/link-preview.js +16 -24
- package/lib/Utils/logger.d.ts +11 -3
- package/lib/Utils/logger.js +3 -7
- package/lib/Utils/lt-hash.d.ts +8 -12
- package/lib/Utils/lt-hash.js +3 -46
- package/lib/Utils/make-mutex.d.ts +4 -2
- package/lib/Utils/make-mutex.js +24 -34
- package/lib/Utils/message-retry-manager.d.ts +115 -0
- package/lib/Utils/message-retry-manager.js +265 -0
- package/lib/Utils/messages-media.d.ts +61 -44
- package/lib/Utils/messages-media.js +451 -482
- package/lib/Utils/messages.d.ts +32 -18
- package/lib/Utils/messages.js +458 -369
- package/lib/Utils/noise-handler.d.ts +13 -14
- package/lib/Utils/noise-handler.js +145 -99
- package/lib/Utils/offline-node-processor.d.ts +17 -0
- package/lib/Utils/offline-node-processor.js +40 -0
- package/lib/Utils/pre-key-manager.d.ts +28 -0
- package/lib/Utils/pre-key-manager.js +106 -0
- package/lib/Utils/process-message.d.ts +31 -12
- package/lib/Utils/process-message.js +459 -150
- package/lib/Utils/reporting-utils.d.ts +11 -0
- package/lib/Utils/reporting-utils.js +258 -0
- package/lib/Utils/signal.d.ts +20 -5
- package/lib/Utils/signal.js +120 -72
- package/lib/Utils/stanza-ack.d.ts +11 -0
- package/lib/Utils/stanza-ack.js +38 -0
- package/lib/Utils/sync-action-utils.d.ts +19 -0
- package/lib/Utils/sync-action-utils.js +49 -0
- package/lib/Utils/tc-token-utils.d.ts +37 -0
- package/lib/Utils/tc-token-utils.js +163 -0
- package/lib/Utils/use-multi-file-auth-state.d.ts +2 -2
- package/lib/Utils/use-multi-file-auth-state.js +29 -27
- package/lib/Utils/validate-connection.d.ts +7 -7
- package/lib/Utils/validate-connection.js +73 -99
- package/lib/WABinary/constants.d.ts +25 -27
- package/lib/WABinary/constants.js +1281 -20
- package/lib/WABinary/decode.d.ts +5 -5
- package/lib/WABinary/decode.js +52 -42
- package/lib/WABinary/encode.d.ts +3 -3
- package/lib/WABinary/encode.js +110 -155
- package/lib/WABinary/generic-utils.d.ts +8 -7
- package/lib/WABinary/generic-utils.js +48 -49
- package/lib/WABinary/index.d.ts +6 -5
- package/lib/WABinary/index.js +6 -21
- package/lib/WABinary/jid-utils.d.ts +25 -8
- package/lib/WABinary/jid-utils.js +74 -40
- package/lib/WABinary/types.d.ts +2 -1
- package/lib/WABinary/types.js +2 -2
- package/lib/WAM/BinaryInfo.d.ts +3 -11
- package/lib/WAM/BinaryInfo.js +2 -5
- package/lib/WAM/constants.d.ts +5 -3
- package/lib/WAM/constants.js +19071 -11568
- package/lib/WAM/encode.d.ts +3 -3
- package/lib/WAM/encode.js +17 -22
- package/lib/WAM/index.d.ts +4 -3
- package/lib/WAM/index.js +4 -19
- package/lib/WAUSync/Protocols/USyncContactProtocol.d.ts +4 -3
- package/lib/WAUSync/Protocols/USyncContactProtocol.js +33 -13
- package/lib/WAUSync/Protocols/USyncDeviceProtocol.d.ts +3 -2
- package/lib/WAUSync/Protocols/USyncDeviceProtocol.js +11 -14
- package/lib/WAUSync/Protocols/USyncDisappearingModeProtocol.d.ts +3 -2
- package/lib/WAUSync/Protocols/USyncDisappearingModeProtocol.js +9 -12
- package/lib/WAUSync/Protocols/USyncStatusProtocol.d.ts +3 -2
- package/lib/WAUSync/Protocols/USyncStatusProtocol.js +9 -13
- package/lib/WAUSync/Protocols/USyncUsernameProtocol.d.ts +10 -0
- package/lib/WAUSync/Protocols/USyncUsernameProtocol.js +25 -0
- package/lib/WAUSync/Protocols/UsyncBotProfileProtocol.d.ts +4 -3
- package/lib/WAUSync/Protocols/UsyncBotProfileProtocol.js +20 -22
- package/lib/WAUSync/Protocols/UsyncLIDProtocol.d.ts +5 -3
- package/lib/WAUSync/Protocols/UsyncLIDProtocol.js +13 -8
- package/lib/WAUSync/Protocols/index.d.ts +6 -4
- package/lib/WAUSync/Protocols/index.js +6 -20
- package/lib/WAUSync/USyncQuery.d.ts +6 -4
- package/lib/WAUSync/USyncQuery.js +44 -35
- package/lib/WAUSync/USyncUser.d.ts +10 -5
- package/lib/WAUSync/USyncUser.js +10 -5
- package/lib/WAUSync/index.d.ts +4 -0
- package/lib/WAUSync/index.js +4 -19
- package/lib/index.d.ts +10 -9
- package/lib/index.js +12 -34
- package/package.json +84 -51
- package/WAProto/fix-import.js +0 -29
- package/lib/Defaults/baileys-version.json +0 -3
- package/lib/Defaults/phonenumber-mcc.json +0 -223
- package/lib/Signal/Group/queue-job.d.ts +0 -1
- package/lib/Signal/Group/queue-job.js +0 -57
- package/lib/Socket/Client/abstract-socket-client.js +0 -13
- 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.js +0 -62
- package/lib/Socket/registration.d.ts +0 -267
- package/lib/Socket/registration.js +0 -166
- package/lib/Socket/usync.d.ts +0 -36
- package/lib/Socket/usync.js +0 -70
- package/lib/Types/Newsletter.d.ts +0 -103
- package/lib/Types/Newsletter.js +0 -38
- package/lib/Utils/baileys-event-stream.d.ts +0 -16
- package/lib/Utils/baileys-event-stream.js +0 -63
|
@@ -1,91 +1,91 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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)();
|
|
1
|
+
import { Boom } from '@hapi/boom';
|
|
2
|
+
import { exec } from 'child_process';
|
|
3
|
+
import * as Crypto from 'crypto';
|
|
4
|
+
import { once } from 'events';
|
|
5
|
+
import { createReadStream, createWriteStream, promises as fs, WriteStream } from 'fs';
|
|
6
|
+
import { tmpdir } from 'os';
|
|
7
|
+
import { join } from 'path';
|
|
8
|
+
import { Readable, Transform } from 'stream';
|
|
9
|
+
import { URL } from 'url';
|
|
10
|
+
import { proto } from '../../WAProto/index.js';
|
|
11
|
+
import { DEFAULT_ORIGIN, MEDIA_HKDF_KEY_MAPPING, MEDIA_PATH_MAP } from '../Defaults/index.js';
|
|
12
|
+
import { getBinaryNodeChild, getBinaryNodeChildBuffer, jidNormalizedUser } from '../WABinary/index.js';
|
|
13
|
+
import { aesDecryptGCM, aesEncryptGCM, hkdf } from './crypto.js';
|
|
14
|
+
import { generateMessageIDV2 } from './generics.js';
|
|
15
|
+
const getTmpFilesDirectory = () => tmpdir();
|
|
41
16
|
const getImageProcessingLibrary = async () => {
|
|
42
|
-
|
|
43
|
-
|
|
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
|
-
]);
|
|
17
|
+
//@ts-ignore
|
|
18
|
+
const [jimp, sharp] = await Promise.all([import('jimp').catch(() => { }), import('sharp').catch(() => { })]);
|
|
54
19
|
if (sharp) {
|
|
55
20
|
return { sharp };
|
|
56
21
|
}
|
|
57
|
-
const jimp = (_jimp === null || _jimp === void 0 ? void 0 : _jimp.default) || _jimp;
|
|
58
22
|
if (jimp) {
|
|
59
23
|
return { jimp };
|
|
60
24
|
}
|
|
61
|
-
throw new
|
|
25
|
+
throw new Boom('No image processing library available');
|
|
62
26
|
};
|
|
63
|
-
const hkdfInfoKey = (type) => {
|
|
64
|
-
const hkdfInfo =
|
|
27
|
+
export const hkdfInfoKey = (type) => {
|
|
28
|
+
const hkdfInfo = MEDIA_HKDF_KEY_MAPPING[type];
|
|
65
29
|
return `WhatsApp ${hkdfInfo} Keys`;
|
|
66
30
|
};
|
|
67
|
-
|
|
31
|
+
export const getRawMediaUploadData = async (media, mediaType, logger) => {
|
|
32
|
+
const { stream } = await getStream(media);
|
|
33
|
+
logger?.debug('got stream for raw upload');
|
|
34
|
+
const hasher = Crypto.createHash('sha256');
|
|
35
|
+
const filePath = join(tmpdir(), mediaType + generateMessageIDV2());
|
|
36
|
+
const fileWriteStream = createWriteStream(filePath);
|
|
37
|
+
let fileLength = 0;
|
|
38
|
+
try {
|
|
39
|
+
for await (const data of stream) {
|
|
40
|
+
fileLength += data.length;
|
|
41
|
+
hasher.update(data);
|
|
42
|
+
if (!fileWriteStream.write(data)) {
|
|
43
|
+
await once(fileWriteStream, 'drain');
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
fileWriteStream.end();
|
|
47
|
+
await once(fileWriteStream, 'finish');
|
|
48
|
+
stream.destroy();
|
|
49
|
+
const fileSha256 = hasher.digest();
|
|
50
|
+
logger?.debug('hashed data for raw upload');
|
|
51
|
+
return {
|
|
52
|
+
filePath: filePath,
|
|
53
|
+
fileSha256,
|
|
54
|
+
fileLength
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
catch (error) {
|
|
58
|
+
fileWriteStream.destroy();
|
|
59
|
+
stream.destroy();
|
|
60
|
+
try {
|
|
61
|
+
await fs.unlink(filePath);
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
//
|
|
65
|
+
}
|
|
66
|
+
throw error;
|
|
67
|
+
}
|
|
68
|
+
};
|
|
68
69
|
/** generates all the keys required to encrypt/decrypt & sign a media message */
|
|
69
|
-
function getMediaKeys(buffer, mediaType) {
|
|
70
|
+
export async function getMediaKeys(buffer, mediaType) {
|
|
70
71
|
if (!buffer) {
|
|
71
|
-
throw new
|
|
72
|
+
throw new Boom('Cannot derive from empty media key');
|
|
72
73
|
}
|
|
73
74
|
if (typeof buffer === 'string') {
|
|
74
75
|
buffer = Buffer.from(buffer.replace('data:;base64,', ''), 'base64');
|
|
75
76
|
}
|
|
76
77
|
// expand using HKDF to 112 bytes, also pass in the relevant app info
|
|
77
|
-
const expandedMediaKey =
|
|
78
|
+
const expandedMediaKey = hkdf(buffer, 112, { info: hkdfInfoKey(mediaType) });
|
|
78
79
|
return {
|
|
79
80
|
iv: expandedMediaKey.slice(0, 16),
|
|
80
81
|
cipherKey: expandedMediaKey.slice(16, 48),
|
|
81
|
-
macKey: expandedMediaKey.slice(48, 80)
|
|
82
|
+
macKey: expandedMediaKey.slice(48, 80)
|
|
82
83
|
};
|
|
83
84
|
}
|
|
84
|
-
exports.getMediaKeys = getMediaKeys;
|
|
85
85
|
/** Extracts video thumb using FFMPEG */
|
|
86
86
|
const extractVideoThumb = async (path, destPath, time, size) => new Promise((resolve, reject) => {
|
|
87
87
|
const cmd = `ffmpeg -ss ${time} -i ${path} -y -vf scale=${size.width}:-1 -vframes 1 -f image2 ${destPath}`;
|
|
88
|
-
|
|
88
|
+
exec(cmd, err => {
|
|
89
89
|
if (err) {
|
|
90
90
|
reject(err);
|
|
91
91
|
}
|
|
@@ -94,240 +94,151 @@ const extractVideoThumb = async (path, destPath, time, size) => new Promise((res
|
|
|
94
94
|
}
|
|
95
95
|
});
|
|
96
96
|
});
|
|
97
|
-
const extractImageThumb = async (bufferOrFilePath, width = 32) => {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
97
|
+
export const extractImageThumb = async (bufferOrFilePath, width = 32) => {
|
|
98
|
+
// TODO: Move entirely to sharp, removing jimp as it supports readable streams
|
|
99
|
+
// This will have positive speed and performance impacts as well as minimizing RAM usage.
|
|
100
|
+
if (bufferOrFilePath instanceof Readable) {
|
|
101
|
+
bufferOrFilePath = await toBuffer(bufferOrFilePath);
|
|
101
102
|
}
|
|
102
103
|
const lib = await getImageProcessingLibrary();
|
|
103
|
-
if ('sharp' in lib && typeof
|
|
104
|
+
if ('sharp' in lib && typeof lib.sharp?.default === 'function') {
|
|
104
105
|
const img = lib.sharp.default(bufferOrFilePath);
|
|
105
106
|
const dimensions = await img.metadata();
|
|
106
|
-
const buffer = await img
|
|
107
|
-
.resize(width)
|
|
108
|
-
.jpeg({ quality: 50 })
|
|
109
|
-
.toBuffer();
|
|
107
|
+
const buffer = await img.resize(width).jpeg({ quality: 50 }).toBuffer();
|
|
110
108
|
return {
|
|
111
109
|
buffer,
|
|
112
110
|
original: {
|
|
113
111
|
width: dimensions.width,
|
|
114
|
-
height: dimensions.height
|
|
115
|
-
}
|
|
112
|
+
height: dimensions.height
|
|
113
|
+
}
|
|
116
114
|
};
|
|
117
115
|
}
|
|
118
|
-
else if ('jimp' in lib && typeof
|
|
119
|
-
const
|
|
120
|
-
const jimp = await read(bufferOrFilePath);
|
|
116
|
+
else if ('jimp' in lib && typeof lib.jimp?.Jimp === 'object') {
|
|
117
|
+
const jimp = await lib.jimp.Jimp.read(bufferOrFilePath);
|
|
121
118
|
const dimensions = {
|
|
122
|
-
width: jimp.
|
|
123
|
-
height: jimp.
|
|
119
|
+
width: jimp.width,
|
|
120
|
+
height: jimp.height
|
|
124
121
|
};
|
|
125
122
|
const buffer = await jimp
|
|
126
|
-
.
|
|
127
|
-
.
|
|
128
|
-
.getBufferAsync(MIME_JPEG);
|
|
123
|
+
.resize({ w: width, mode: lib.jimp.ResizeStrategy.BILINEAR })
|
|
124
|
+
.getBuffer('image/jpeg', { quality: 50 });
|
|
129
125
|
return {
|
|
130
126
|
buffer,
|
|
131
127
|
original: dimensions
|
|
132
128
|
};
|
|
133
129
|
}
|
|
134
130
|
else {
|
|
135
|
-
throw new
|
|
131
|
+
throw new Boom('No image processing library available');
|
|
136
132
|
}
|
|
137
133
|
};
|
|
138
|
-
|
|
139
|
-
const
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
.replace(/\=+$/, '')));
|
|
143
|
-
exports.encodeBase64EncodedStringForUpload = encodeBase64EncodedStringForUpload;
|
|
144
|
-
const generateProfilePicture = async (mediaUpload) => {
|
|
145
|
-
var _a, _b;
|
|
146
|
-
let bufferOrFilePath;
|
|
134
|
+
export const encodeBase64EncodedStringForUpload = (b64) => encodeURIComponent(b64.replace(/\+/g, '-').replace(/\//g, '_').replace(/\=+$/, ''));
|
|
135
|
+
export const generateProfilePicture = async (mediaUpload, dimensions) => {
|
|
136
|
+
let buffer;
|
|
137
|
+
const { width: w = 640, height: h = 640 } = dimensions || {};
|
|
147
138
|
if (Buffer.isBuffer(mediaUpload)) {
|
|
148
|
-
|
|
149
|
-
}
|
|
150
|
-
else if ('url' in mediaUpload) {
|
|
151
|
-
bufferOrFilePath = mediaUpload.url.toString();
|
|
139
|
+
buffer = mediaUpload;
|
|
152
140
|
}
|
|
153
141
|
else {
|
|
154
|
-
|
|
142
|
+
// Use getStream to handle all WAMediaUpload types (Buffer, Stream, URL)
|
|
143
|
+
const { stream } = await getStream(mediaUpload);
|
|
144
|
+
// Convert the resulting stream to a buffer
|
|
145
|
+
buffer = await toBuffer(stream);
|
|
155
146
|
}
|
|
156
147
|
const lib = await getImageProcessingLibrary();
|
|
157
148
|
let img;
|
|
158
|
-
if ('sharp' in lib && typeof
|
|
159
|
-
img = lib.sharp
|
|
160
|
-
.
|
|
149
|
+
if ('sharp' in lib && typeof lib.sharp?.default === 'function') {
|
|
150
|
+
img = lib.sharp
|
|
151
|
+
.default(buffer)
|
|
152
|
+
.resize(w, h)
|
|
161
153
|
.jpeg({
|
|
162
|
-
quality: 50
|
|
154
|
+
quality: 50
|
|
163
155
|
})
|
|
164
156
|
.toBuffer();
|
|
165
157
|
}
|
|
166
|
-
else if ('jimp' in lib && typeof
|
|
167
|
-
const
|
|
168
|
-
const
|
|
169
|
-
const
|
|
170
|
-
|
|
171
|
-
img = cropped
|
|
172
|
-
.quality(50)
|
|
173
|
-
.resize(640, 640, RESIZE_BILINEAR)
|
|
174
|
-
.getBufferAsync(MIME_JPEG);
|
|
158
|
+
else if ('jimp' in lib && typeof lib.jimp?.Jimp === 'function') {
|
|
159
|
+
const jimp = await lib.jimp.Jimp.read(buffer);
|
|
160
|
+
const min = Math.min(jimp.width, jimp.height);
|
|
161
|
+
const cropped = jimp.crop({ x: 0, y: 0, w: min, h: min });
|
|
162
|
+
img = cropped.resize({ w, h, mode: lib.jimp.ResizeStrategy.BILINEAR }).getBuffer('image/jpeg', { quality: 50 });
|
|
175
163
|
}
|
|
176
164
|
else {
|
|
177
|
-
throw new
|
|
165
|
+
throw new Boom('No image processing library available');
|
|
178
166
|
}
|
|
179
167
|
return {
|
|
180
|
-
img: await img
|
|
168
|
+
img: await img
|
|
181
169
|
};
|
|
182
170
|
};
|
|
183
|
-
exports.generateProfilePicture = generateProfilePicture;
|
|
184
171
|
/** gets the SHA256 of the given media message */
|
|
185
|
-
const mediaMessageSHA256B64 = (message) => {
|
|
172
|
+
export const mediaMessageSHA256B64 = (message) => {
|
|
186
173
|
const media = Object.values(message)[0];
|
|
187
|
-
return
|
|
174
|
+
return media?.fileSha256 && Buffer.from(media.fileSha256).toString('base64');
|
|
188
175
|
};
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
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;
|
|
176
|
+
export async function getAudioDuration(buffer) {
|
|
177
|
+
const musicMetadata = await import('music-metadata');
|
|
178
|
+
let metadata;
|
|
179
|
+
const options = {
|
|
180
|
+
duration: true
|
|
181
|
+
};
|
|
182
|
+
if (Buffer.isBuffer(buffer)) {
|
|
183
|
+
metadata = await musicMetadata.parseBuffer(buffer, undefined, options);
|
|
227
184
|
}
|
|
185
|
+
else if (typeof buffer === 'string') {
|
|
186
|
+
metadata = await musicMetadata.parseFile(buffer, options);
|
|
187
|
+
}
|
|
188
|
+
else {
|
|
189
|
+
metadata = await musicMetadata.parseStream(buffer, undefined, options);
|
|
190
|
+
}
|
|
191
|
+
return metadata.format.duration;
|
|
228
192
|
}
|
|
229
|
-
|
|
230
|
-
|
|
193
|
+
/**
|
|
194
|
+
referenced from and modifying https://github.com/wppconnect-team/wa-js/blob/main/src/chat/functions/prepareAudioWaveform.ts
|
|
195
|
+
*/
|
|
196
|
+
export async function getAudioWaveform(buffer, logger) {
|
|
231
197
|
try {
|
|
232
|
-
|
|
233
|
-
const
|
|
234
|
-
|
|
198
|
+
// @ts-ignore
|
|
199
|
+
const { default: decoder } = await import('audio-decode');
|
|
235
200
|
let audioData;
|
|
236
201
|
if (Buffer.isBuffer(buffer)) {
|
|
237
202
|
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
203
|
}
|
|
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
|
-
|
|
269
|
-
|
|
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);
|
|
204
|
+
else if (typeof buffer === 'string') {
|
|
205
|
+
const rStream = createReadStream(buffer);
|
|
206
|
+
audioData = await toBuffer(rStream);
|
|
207
|
+
}
|
|
208
|
+
else {
|
|
209
|
+
audioData = await toBuffer(buffer);
|
|
210
|
+
}
|
|
211
|
+
const audioBuffer = await decoder(audioData);
|
|
212
|
+
const rawData = audioBuffer.getChannelData(0); // We only need to work with one channel of data
|
|
213
|
+
const samples = 64; // Number of samples we want to have in our final data set
|
|
214
|
+
const blockSize = Math.floor(rawData.length / samples); // the number of samples in each subdivision
|
|
215
|
+
const filteredData = [];
|
|
216
|
+
for (let i = 0; i < samples; i++) {
|
|
217
|
+
const blockStart = blockSize * i; // the location of the first sample in the block
|
|
218
|
+
let sum = 0;
|
|
219
|
+
for (let j = 0; j < blockSize; j++) {
|
|
220
|
+
sum = sum + Math.abs(rawData[blockStart + j]); // find the sum of all the samples in the block
|
|
221
|
+
}
|
|
222
|
+
filteredData.push(sum / blockSize); // divide the sum by the block size to get the average
|
|
223
|
+
}
|
|
224
|
+
// This guarantees that the largest data point will be set to 1, and the rest of the data will scale proportionally.
|
|
225
|
+
const multiplier = Math.pow(Math.max(...filteredData), -1);
|
|
226
|
+
const normalizedData = filteredData.map(n => n * multiplier);
|
|
227
|
+
// Generate waveform like WhatsApp
|
|
228
|
+
const waveform = new Uint8Array(normalizedData.map(n => Math.floor(100 * n)));
|
|
229
|
+
return waveform;
|
|
281
230
|
}
|
|
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;
|
|
231
|
+
catch (e) {
|
|
232
|
+
logger?.debug('Failed to generate waveform: ' + e);
|
|
320
233
|
}
|
|
321
234
|
}
|
|
322
|
-
|
|
323
|
-
const
|
|
324
|
-
const readable = new stream_1.Readable({ read: () => { } });
|
|
235
|
+
export const toReadable = (buffer) => {
|
|
236
|
+
const readable = new Readable({ read: () => { } });
|
|
325
237
|
readable.push(buffer);
|
|
326
238
|
readable.push(null);
|
|
327
239
|
return readable;
|
|
328
240
|
};
|
|
329
|
-
|
|
330
|
-
const toBuffer = async (stream) => {
|
|
241
|
+
export const toBuffer = async (stream) => {
|
|
331
242
|
const chunks = [];
|
|
332
243
|
for await (const chunk of stream) {
|
|
333
244
|
chunks.push(chunk);
|
|
@@ -335,45 +246,47 @@ const toBuffer = async (stream) => {
|
|
|
335
246
|
stream.destroy();
|
|
336
247
|
return Buffer.concat(chunks);
|
|
337
248
|
};
|
|
338
|
-
|
|
339
|
-
const getStream = async (item, opts) => {
|
|
249
|
+
export const getStream = async (item, opts) => {
|
|
340
250
|
if (Buffer.isBuffer(item)) {
|
|
341
|
-
return { stream:
|
|
251
|
+
return { stream: toReadable(item), type: 'buffer' };
|
|
342
252
|
}
|
|
343
253
|
if ('stream' in item) {
|
|
344
254
|
return { stream: item.stream, type: 'readable' };
|
|
345
255
|
}
|
|
346
|
-
|
|
347
|
-
|
|
256
|
+
const urlStr = item.url.toString();
|
|
257
|
+
if (urlStr.startsWith('data:')) {
|
|
258
|
+
const buffer = Buffer.from(urlStr.split(',')[1], 'base64');
|
|
259
|
+
return { stream: toReadable(buffer), type: 'buffer' };
|
|
260
|
+
}
|
|
261
|
+
if (urlStr.startsWith('http://') || urlStr.startsWith('https://')) {
|
|
262
|
+
return { stream: await getHttpStream(item.url, opts), type: 'remote' };
|
|
348
263
|
}
|
|
349
|
-
return { stream:
|
|
264
|
+
return { stream: createReadStream(item.url), type: 'file' };
|
|
350
265
|
};
|
|
351
|
-
exports.getStream = getStream;
|
|
352
266
|
/** generates a thumbnail for a given media, if required */
|
|
353
|
-
async function generateThumbnail(file, mediaType, options) {
|
|
354
|
-
var _a;
|
|
267
|
+
export async function generateThumbnail(file, mediaType, options) {
|
|
355
268
|
let thumbnail;
|
|
356
269
|
let originalImageDimensions;
|
|
357
270
|
if (mediaType === 'image') {
|
|
358
|
-
const { buffer, original } = await
|
|
271
|
+
const { buffer, original } = await extractImageThumb(file);
|
|
359
272
|
thumbnail = buffer.toString('base64');
|
|
360
273
|
if (original.width && original.height) {
|
|
361
274
|
originalImageDimensions = {
|
|
362
275
|
width: original.width,
|
|
363
|
-
height: original.height
|
|
276
|
+
height: original.height
|
|
364
277
|
};
|
|
365
278
|
}
|
|
366
279
|
}
|
|
367
280
|
else if (mediaType === 'video') {
|
|
368
|
-
const imgFilename =
|
|
281
|
+
const imgFilename = join(getTmpFilesDirectory(), generateMessageIDV2() + '.jpg');
|
|
369
282
|
try {
|
|
370
283
|
await extractVideoThumb(file, imgFilename, '00:00:00', { width: 32, height: 32 });
|
|
371
|
-
const buff = await
|
|
284
|
+
const buff = await fs.readFile(imgFilename);
|
|
372
285
|
thumbnail = buff.toString('base64');
|
|
373
|
-
await
|
|
286
|
+
await fs.unlink(imgFilename);
|
|
374
287
|
}
|
|
375
288
|
catch (err) {
|
|
376
|
-
|
|
289
|
+
options.logger?.debug('could not generate video thumb: ' + err);
|
|
377
290
|
}
|
|
378
291
|
}
|
|
379
292
|
return {
|
|
@@ -381,180 +294,141 @@ async function generateThumbnail(file, mediaType, options) {
|
|
|
381
294
|
originalImageDimensions
|
|
382
295
|
};
|
|
383
296
|
}
|
|
384
|
-
|
|
385
|
-
const
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
};
|
|
390
|
-
|
|
391
|
-
|
|
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;
|
|
396
|
-
try {
|
|
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');
|
|
410
|
-
return {
|
|
411
|
-
mediaKey: undefined,
|
|
412
|
-
encWriteStream: buffer,
|
|
413
|
-
fileLength,
|
|
414
|
-
fileSha256,
|
|
415
|
-
fileEncSha256: undefined,
|
|
416
|
-
bodyPath,
|
|
417
|
-
didSaveToTmpPath
|
|
418
|
-
};
|
|
419
|
-
}
|
|
420
|
-
catch (error) {
|
|
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');
|
|
429
|
-
}
|
|
430
|
-
}
|
|
431
|
-
throw error;
|
|
297
|
+
export const getHttpStream = async (url, options = {}) => {
|
|
298
|
+
const response = await fetch(url.toString(), {
|
|
299
|
+
dispatcher: options.dispatcher,
|
|
300
|
+
method: 'GET',
|
|
301
|
+
headers: options.headers
|
|
302
|
+
});
|
|
303
|
+
if (!response.ok) {
|
|
304
|
+
throw new Boom(`Failed to fetch stream from ${url}`, { statusCode: response.status, data: { url } });
|
|
432
305
|
}
|
|
306
|
+
// @ts-ignore Node18+ Readable.fromWeb exists
|
|
307
|
+
return response.body instanceof Readable ? response.body : Readable.fromWeb(response.body);
|
|
433
308
|
};
|
|
434
|
-
|
|
435
|
-
const
|
|
436
|
-
|
|
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
|
-
|
|
309
|
+
export const encryptedStream = async (media, mediaType, { logger, saveOriginalFileIfRequired, opts } = {}) => {
|
|
310
|
+
const { stream, type } = await getStream(media, opts);
|
|
311
|
+
logger?.debug('fetched media stream');
|
|
450
312
|
const mediaKey = Crypto.randomBytes(32);
|
|
451
|
-
const { cipherKey, iv, macKey } = getMediaKeys(mediaKey, mediaType);
|
|
452
|
-
const
|
|
453
|
-
|
|
454
|
-
let
|
|
455
|
-
let
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
}
|
|
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;
|
|
464
|
-
}
|
|
465
|
-
|
|
313
|
+
const { cipherKey, iv, macKey } = await getMediaKeys(mediaKey, mediaType);
|
|
314
|
+
const encFilePath = join(getTmpFilesDirectory(), mediaType + generateMessageIDV2() + '-enc');
|
|
315
|
+
const encFileWriteStream = createWriteStream(encFilePath);
|
|
316
|
+
let originalFileStream;
|
|
317
|
+
let originalFilePath;
|
|
318
|
+
if (saveOriginalFileIfRequired) {
|
|
319
|
+
originalFilePath = join(getTmpFilesDirectory(), mediaType + generateMessageIDV2() + '-original');
|
|
320
|
+
originalFileStream = createWriteStream(originalFilePath);
|
|
321
|
+
}
|
|
466
322
|
let fileLength = 0;
|
|
467
323
|
const aes = Crypto.createCipheriv('aes-256-cbc', cipherKey, iv);
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
324
|
+
const hmac = Crypto.createHmac('sha256', macKey).update(iv);
|
|
325
|
+
const sha256Plain = Crypto.createHash('sha256');
|
|
326
|
+
const sha256Enc = Crypto.createHash('sha256');
|
|
327
|
+
const onChunk = async (buff) => {
|
|
328
|
+
sha256Enc.update(buff);
|
|
329
|
+
hmac.update(buff);
|
|
330
|
+
// Handle backpressure: if write returns false, wait for drain
|
|
331
|
+
if (!encFileWriteStream.write(buff)) {
|
|
332
|
+
await once(encFileWriteStream, 'drain');
|
|
333
|
+
}
|
|
334
|
+
};
|
|
472
335
|
try {
|
|
473
|
-
for await (const data of
|
|
336
|
+
for await (const data of stream) {
|
|
474
337
|
fileLength += data.length;
|
|
475
|
-
if (type === 'remote'
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
throw new
|
|
338
|
+
if (type === 'remote' &&
|
|
339
|
+
opts?.maxContentLength &&
|
|
340
|
+
fileLength + data.length > opts.maxContentLength) {
|
|
341
|
+
throw new Boom(`content length exceeded when encrypting "${type}"`, {
|
|
479
342
|
data: { media, type }
|
|
480
343
|
});
|
|
481
344
|
}
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
if (!writeStream.write(data)) {
|
|
486
|
-
await (0, events_1.once)(writeStream, 'drain');
|
|
345
|
+
if (originalFileStream) {
|
|
346
|
+
if (!originalFileStream.write(data)) {
|
|
347
|
+
await once(originalFileStream, 'drain');
|
|
487
348
|
}
|
|
488
349
|
}
|
|
489
|
-
|
|
350
|
+
sha256Plain.update(data);
|
|
351
|
+
await onChunk(aes.update(data));
|
|
490
352
|
}
|
|
491
|
-
|
|
492
|
-
onChunk(aes.final());
|
|
353
|
+
await onChunk(aes.final());
|
|
493
354
|
const mac = hmac.digest().slice(0, 10);
|
|
494
|
-
sha256Enc
|
|
355
|
+
sha256Enc.update(mac);
|
|
495
356
|
const fileSha256 = sha256Plain.digest();
|
|
496
357
|
const fileEncSha256 = sha256Enc.digest();
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
358
|
+
encFileWriteStream.write(mac);
|
|
359
|
+
const encFinishPromise = once(encFileWriteStream, 'finish');
|
|
360
|
+
const originalFinishPromise = originalFileStream ? once(originalFileStream, 'finish') : Promise.resolve();
|
|
361
|
+
encFileWriteStream.end();
|
|
362
|
+
originalFileStream?.end?.();
|
|
363
|
+
stream.destroy();
|
|
364
|
+
// Wait for write streams to fully flush to disk
|
|
365
|
+
// This helps reduce memory pressure by allowing OS to release buffers
|
|
366
|
+
await encFinishPromise;
|
|
367
|
+
await originalFinishPromise;
|
|
368
|
+
logger?.debug('encrypted data successfully');
|
|
503
369
|
return {
|
|
504
370
|
mediaKey,
|
|
505
|
-
|
|
506
|
-
|
|
371
|
+
originalFilePath,
|
|
372
|
+
encFilePath,
|
|
507
373
|
mac,
|
|
508
374
|
fileEncSha256,
|
|
509
375
|
fileSha256,
|
|
510
|
-
fileLength
|
|
511
|
-
didSaveToTmpPath
|
|
376
|
+
fileLength
|
|
512
377
|
};
|
|
513
378
|
}
|
|
514
379
|
catch (error) {
|
|
515
|
-
|
|
516
|
-
|
|
380
|
+
// destroy all streams with error
|
|
381
|
+
encFileWriteStream.destroy();
|
|
382
|
+
originalFileStream?.destroy?.();
|
|
517
383
|
aes.destroy();
|
|
518
384
|
hmac.destroy();
|
|
519
385
|
sha256Plain.destroy();
|
|
520
386
|
sha256Enc.destroy();
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
await
|
|
526
|
-
}
|
|
527
|
-
catch (err) {
|
|
387
|
+
stream.destroy();
|
|
388
|
+
try {
|
|
389
|
+
await fs.unlink(encFilePath);
|
|
390
|
+
if (originalFilePath) {
|
|
391
|
+
await fs.unlink(originalFilePath);
|
|
528
392
|
}
|
|
529
393
|
}
|
|
394
|
+
catch (err) {
|
|
395
|
+
logger?.error({ err }, 'failed deleting tmp files');
|
|
396
|
+
}
|
|
530
397
|
throw error;
|
|
531
398
|
}
|
|
532
|
-
|
|
533
|
-
function onChunk(buff) {
|
|
534
|
-
sha256Enc = sha256Enc.update(buff);
|
|
535
|
-
hmac = hmac.update(buff);
|
|
536
|
-
encWriteStream.push(buff);
|
|
537
|
-
}
|
|
538
399
|
};
|
|
539
|
-
|
|
540
|
-
const DEF_HOST = 'mmg.whatsapp.net';
|
|
400
|
+
export const DEF_MEDIA_HOST = 'mmg.whatsapp.net';
|
|
541
401
|
const AES_CHUNK_SIZE = 16;
|
|
542
402
|
const toSmallestChunkSize = (num) => {
|
|
543
403
|
return Math.floor(num / AES_CHUNK_SIZE) * AES_CHUNK_SIZE;
|
|
544
404
|
};
|
|
545
|
-
const getUrlFromDirectPath = (directPath) => `https://${
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
405
|
+
export const getUrlFromDirectPath = (directPath, host = DEF_MEDIA_HOST) => `https://${host}${directPath}`;
|
|
406
|
+
const extractHost = (url) => {
|
|
407
|
+
if (!url)
|
|
408
|
+
return undefined;
|
|
409
|
+
try {
|
|
410
|
+
return new URL(url).host;
|
|
411
|
+
}
|
|
412
|
+
catch {
|
|
413
|
+
return undefined;
|
|
414
|
+
}
|
|
415
|
+
};
|
|
416
|
+
export const downloadContentFromMessage = async ({ mediaKey, directPath, url }, type, opts = {}) => {
|
|
417
|
+
// Fallback host: explicit opt > host parsed from `url` > DEF_MEDIA_HOST.
|
|
418
|
+
// Lets us honor a non-default host carried by the proto without forcing callers to thread it through.
|
|
419
|
+
const fallbackHost = opts.host ?? extractHost(url);
|
|
420
|
+
const downloadUrl = directPath ? getUrlFromDirectPath(directPath, fallbackHost) : url;
|
|
421
|
+
if (!downloadUrl) {
|
|
422
|
+
throw new Boom('No valid media URL or directPath present in message', { statusCode: 400 });
|
|
423
|
+
}
|
|
424
|
+
const keys = await getMediaKeys(mediaKey, type);
|
|
425
|
+
return downloadEncryptedContent(downloadUrl, keys, opts);
|
|
551
426
|
};
|
|
552
|
-
exports.downloadContentFromMessage = downloadContentFromMessage;
|
|
553
427
|
/**
|
|
554
428
|
* Decrypts and downloads an AES256-CBC encrypted file given the keys.
|
|
555
429
|
* Assumes the SHA256 of the plaintext is appended to the end of the ciphertext
|
|
556
430
|
* */
|
|
557
|
-
const downloadEncryptedContent = async (downloadUrl, { cipherKey, iv }, { startByte, endByte, options } = {}) => {
|
|
431
|
+
export const downloadEncryptedContent = async (downloadUrl, { cipherKey, iv }, { startByte, endByte, options } = {}) => {
|
|
558
432
|
let bytesFetched = 0;
|
|
559
433
|
let startChunk = 0;
|
|
560
434
|
let firstBlockIsIV = false;
|
|
@@ -568,9 +442,14 @@ const downloadEncryptedContent = async (downloadUrl, { cipherKey, iv }, { startB
|
|
|
568
442
|
}
|
|
569
443
|
}
|
|
570
444
|
const endChunk = endByte ? toSmallestChunkSize(endByte || 0) + AES_CHUNK_SIZE : undefined;
|
|
445
|
+
const headersInit = options?.headers ? options.headers : undefined;
|
|
571
446
|
const headers = {
|
|
572
|
-
...(
|
|
573
|
-
|
|
447
|
+
...(headersInit
|
|
448
|
+
? Array.isArray(headersInit)
|
|
449
|
+
? Object.fromEntries(headersInit)
|
|
450
|
+
: headersInit
|
|
451
|
+
: {}),
|
|
452
|
+
Origin: DEFAULT_ORIGIN
|
|
574
453
|
};
|
|
575
454
|
if (startChunk || endChunk) {
|
|
576
455
|
headers.Range = `bytes=${startChunk}-`;
|
|
@@ -579,11 +458,9 @@ const downloadEncryptedContent = async (downloadUrl, { cipherKey, iv }, { startB
|
|
|
579
458
|
}
|
|
580
459
|
}
|
|
581
460
|
// download the message
|
|
582
|
-
const fetched = await
|
|
583
|
-
...options || {},
|
|
584
|
-
headers
|
|
585
|
-
maxBodyLength: Infinity,
|
|
586
|
-
maxContentLength: Infinity,
|
|
461
|
+
const fetched = await getHttpStream(downloadUrl, {
|
|
462
|
+
...(options || {}),
|
|
463
|
+
headers
|
|
587
464
|
});
|
|
588
465
|
let remainingBytes = Buffer.from([]);
|
|
589
466
|
let aes;
|
|
@@ -598,9 +475,9 @@ const downloadEncryptedContent = async (downloadUrl, { cipherKey, iv }, { startB
|
|
|
598
475
|
push(bytes);
|
|
599
476
|
}
|
|
600
477
|
};
|
|
601
|
-
const output = new
|
|
478
|
+
const output = new Transform({
|
|
602
479
|
transform(chunk, _, callback) {
|
|
603
|
-
let data = Buffer.concat([remainingBytes, chunk]);
|
|
480
|
+
let data = remainingBytes.length ? Buffer.concat([remainingBytes, chunk]) : chunk;
|
|
604
481
|
const decryptLength = toSmallestChunkSize(data.length);
|
|
605
482
|
remainingBytes = data.slice(decryptLength);
|
|
606
483
|
data = data.slice(0, decryptLength);
|
|
@@ -633,18 +510,15 @@ const downloadEncryptedContent = async (downloadUrl, { cipherKey, iv }, { startB
|
|
|
633
510
|
catch (error) {
|
|
634
511
|
callback(error);
|
|
635
512
|
}
|
|
636
|
-
}
|
|
513
|
+
}
|
|
637
514
|
});
|
|
638
515
|
return fetched.pipe(output, { end: true });
|
|
639
516
|
};
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
const getExtension = (mimetype) => mimetype.split(';')[0].split('/')[1];
|
|
517
|
+
export function extensionForMediaMessage(message) {
|
|
518
|
+
const getExtension = (mimetype) => mimetype.split(';')[0]?.split('/')[1];
|
|
643
519
|
const type = Object.keys(message)[0];
|
|
644
520
|
let extension;
|
|
645
|
-
if (type === 'locationMessage' ||
|
|
646
|
-
type === 'liveLocationMessage' ||
|
|
647
|
-
type === 'productMessage') {
|
|
521
|
+
if (type === 'locationMessage' || type === 'liveLocationMessage' || type === 'productMessage') {
|
|
648
522
|
extension = '.jpeg';
|
|
649
523
|
}
|
|
650
524
|
else {
|
|
@@ -653,55 +527,158 @@ function extensionForMediaMessage(message) {
|
|
|
653
527
|
}
|
|
654
528
|
return extension;
|
|
655
529
|
}
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
530
|
+
const isNodeRuntime = () => {
|
|
531
|
+
return (typeof process !== 'undefined' &&
|
|
532
|
+
process.versions?.node !== null &&
|
|
533
|
+
typeof process.versions.bun === 'undefined' &&
|
|
534
|
+
typeof globalThis.Deno === 'undefined');
|
|
535
|
+
};
|
|
536
|
+
export const uploadWithNodeHttp = async ({ url, filePath, headers, timeoutMs, agent }, redirectCount = 0) => {
|
|
537
|
+
if (redirectCount > 5) {
|
|
538
|
+
throw new Error('Too many redirects');
|
|
539
|
+
}
|
|
540
|
+
const parsedUrl = new URL(url);
|
|
541
|
+
const httpModule = parsedUrl.protocol === 'https:' ? await import('https') : await import('http');
|
|
542
|
+
// Get file size for Content-Length header (required for Node.js streaming)
|
|
543
|
+
const fileStats = await fs.stat(filePath);
|
|
544
|
+
const fileSize = fileStats.size;
|
|
545
|
+
return new Promise((resolve, reject) => {
|
|
546
|
+
const req = httpModule.request({
|
|
547
|
+
hostname: parsedUrl.hostname,
|
|
548
|
+
port: parsedUrl.port || (parsedUrl.protocol === 'https:' ? 443 : 80),
|
|
549
|
+
path: parsedUrl.pathname + parsedUrl.search,
|
|
550
|
+
method: 'POST',
|
|
551
|
+
headers: {
|
|
552
|
+
...headers,
|
|
553
|
+
'Content-Length': fileSize
|
|
554
|
+
},
|
|
555
|
+
agent,
|
|
556
|
+
timeout: timeoutMs
|
|
557
|
+
}, res => {
|
|
558
|
+
// Handle redirects (3xx)
|
|
559
|
+
if (res.statusCode && res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
|
|
560
|
+
res.resume(); // Consume response to free resources
|
|
561
|
+
const newUrl = new URL(res.headers.location, url).toString();
|
|
562
|
+
resolve(uploadWithNodeHttp({
|
|
563
|
+
url: newUrl,
|
|
564
|
+
filePath,
|
|
565
|
+
headers,
|
|
566
|
+
timeoutMs,
|
|
567
|
+
agent
|
|
568
|
+
}, redirectCount + 1));
|
|
569
|
+
return;
|
|
570
|
+
}
|
|
571
|
+
let body = '';
|
|
572
|
+
res.on('data', chunk => (body += chunk));
|
|
573
|
+
res.on('end', () => {
|
|
574
|
+
try {
|
|
575
|
+
resolve(JSON.parse(body));
|
|
576
|
+
}
|
|
577
|
+
catch {
|
|
578
|
+
resolve(undefined);
|
|
579
|
+
}
|
|
580
|
+
});
|
|
581
|
+
});
|
|
582
|
+
req.on('error', reject);
|
|
583
|
+
req.on('timeout', () => {
|
|
584
|
+
req.destroy();
|
|
585
|
+
reject(new Error('Upload timeout'));
|
|
586
|
+
});
|
|
587
|
+
const stream = createReadStream(filePath);
|
|
588
|
+
stream.pipe(req);
|
|
589
|
+
stream.on('error', err => {
|
|
590
|
+
req.destroy();
|
|
591
|
+
reject(err);
|
|
592
|
+
});
|
|
593
|
+
});
|
|
594
|
+
};
|
|
595
|
+
const uploadWithFetch = async ({ url, filePath, headers, timeoutMs, agent }) => {
|
|
596
|
+
// Convert Node.js Readable to Web ReadableStream
|
|
597
|
+
const nodeStream = createReadStream(filePath);
|
|
598
|
+
const webStream = Readable.toWeb(nodeStream);
|
|
599
|
+
// Native fetch only accepts Undici-style dispatchers, not generic https Agents.
|
|
600
|
+
const dispatcher = typeof agent?.dispatch === 'function' ? agent : undefined;
|
|
601
|
+
const response = await fetch(url, {
|
|
602
|
+
...(dispatcher ? { dispatcher } : {}),
|
|
603
|
+
method: 'POST',
|
|
604
|
+
body: webStream,
|
|
605
|
+
headers,
|
|
606
|
+
duplex: 'half',
|
|
607
|
+
signal: timeoutMs ? AbortSignal.timeout(timeoutMs) : undefined
|
|
608
|
+
});
|
|
609
|
+
try {
|
|
610
|
+
return (await response.json());
|
|
611
|
+
}
|
|
612
|
+
catch {
|
|
613
|
+
return undefined;
|
|
614
|
+
}
|
|
615
|
+
};
|
|
616
|
+
/**
|
|
617
|
+
* Uploads media to WhatsApp servers.
|
|
618
|
+
*
|
|
619
|
+
* ## Why we have two upload implementations:
|
|
620
|
+
*
|
|
621
|
+
* Node.js's native `fetch` (powered by undici) has a known bug where it buffers
|
|
622
|
+
* the entire request body in memory before sending, even when using streams.
|
|
623
|
+
* This causes memory issues with large files (e.g., 1GB file = 1GB+ memory usage).
|
|
624
|
+
* See: https://github.com/nodejs/undici/issues/4058
|
|
625
|
+
*
|
|
626
|
+
* Other runtimes (Bun, Deno, browsers) correctly stream the request body without
|
|
627
|
+
* buffering, so we can use the web-standard Fetch API there.
|
|
628
|
+
*
|
|
629
|
+
* ## Future considerations:
|
|
630
|
+
* Once the undici bug is fixed, we can simplify this to use only the Fetch API
|
|
631
|
+
* across all runtimes. Monitor the GitHub issue for updates.
|
|
632
|
+
*/
|
|
633
|
+
const uploadMedia = async (params, logger) => {
|
|
634
|
+
if (isNodeRuntime()) {
|
|
635
|
+
logger?.debug('Using Node.js https module for upload (avoids undici buffering bug)');
|
|
636
|
+
return uploadWithNodeHttp(params);
|
|
637
|
+
}
|
|
638
|
+
else {
|
|
639
|
+
logger?.debug('Using web-standard Fetch API for upload');
|
|
640
|
+
return uploadWithFetch(params);
|
|
641
|
+
}
|
|
642
|
+
};
|
|
643
|
+
export const getWAUploadToServer = ({ customUploadHosts, fetchAgent, logger, options }, refreshMediaConn) => {
|
|
644
|
+
return async (filePath, { mediaType, fileEncSha256B64, timeoutMs }) => {
|
|
661
645
|
// send a query JSON to obtain the url & auth token to upload our media
|
|
662
646
|
let uploadInfo = await refreshMediaConn(false);
|
|
663
647
|
let urls;
|
|
664
648
|
const hosts = [...customUploadHosts, ...uploadInfo.hosts];
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
649
|
+
fileEncSha256B64 = encodeBase64EncodedStringForUpload(fileEncSha256B64);
|
|
650
|
+
// Prepare common headers
|
|
651
|
+
const customHeaders = (() => {
|
|
652
|
+
const hdrs = options?.headers;
|
|
653
|
+
if (!hdrs)
|
|
654
|
+
return {};
|
|
655
|
+
return Array.isArray(hdrs) ? Object.fromEntries(hdrs) : hdrs;
|
|
656
|
+
})();
|
|
657
|
+
const headers = {
|
|
658
|
+
...customHeaders,
|
|
659
|
+
'Content-Type': 'application/octet-stream',
|
|
660
|
+
Origin: DEFAULT_ORIGIN
|
|
661
|
+
};
|
|
662
|
+
for (const { hostname } of hosts) {
|
|
678
663
|
logger.debug(`uploading to "${hostname}"`);
|
|
679
|
-
const auth = encodeURIComponent(uploadInfo.auth);
|
|
680
|
-
const url = `https://${hostname}${
|
|
664
|
+
const auth = encodeURIComponent(uploadInfo.auth);
|
|
665
|
+
const url = `https://${hostname}${MEDIA_PATH_MAP[mediaType]}/${fileEncSha256B64}?auth=${auth}&token=${fileEncSha256B64}`;
|
|
681
666
|
let result;
|
|
682
667
|
try {
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
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)) {
|
|
668
|
+
result = await uploadMedia({
|
|
669
|
+
url,
|
|
670
|
+
filePath,
|
|
671
|
+
headers,
|
|
672
|
+
timeoutMs,
|
|
673
|
+
agent: fetchAgent
|
|
674
|
+
}, logger);
|
|
675
|
+
if (result?.url || result?.direct_path) {
|
|
701
676
|
urls = {
|
|
702
677
|
mediaUrl: result.url,
|
|
703
678
|
directPath: result.direct_path,
|
|
704
|
-
|
|
679
|
+
meta_hmac: result.meta_hmac,
|
|
680
|
+
fbid: result.fbid,
|
|
681
|
+
ts: result.ts
|
|
705
682
|
};
|
|
706
683
|
break;
|
|
707
684
|
}
|
|
@@ -711,37 +688,33 @@ const getWAUploadToServer = ({ customUploadHosts, fetchAgent, logger, options },
|
|
|
711
688
|
}
|
|
712
689
|
}
|
|
713
690
|
catch (error) {
|
|
714
|
-
|
|
715
|
-
|
|
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...'}`);
|
|
691
|
+
const isLast = hostname === hosts[uploadInfo.hosts.length - 1]?.hostname;
|
|
692
|
+
logger.warn({ trace: error?.stack, uploadResult: result }, `Error in uploading to ${hostname} ${isLast ? '' : ', retrying...'}`);
|
|
719
693
|
}
|
|
720
694
|
}
|
|
721
695
|
if (!urls) {
|
|
722
|
-
throw new
|
|
696
|
+
throw new Boom('Media upload failed on all hosts', { statusCode: 500 });
|
|
723
697
|
}
|
|
724
698
|
return urls;
|
|
725
699
|
};
|
|
726
700
|
};
|
|
727
|
-
exports.getWAUploadToServer = getWAUploadToServer;
|
|
728
701
|
const getMediaRetryKey = (mediaKey) => {
|
|
729
|
-
return
|
|
702
|
+
return hkdf(mediaKey, 32, { info: 'WhatsApp Media Retry Notification' });
|
|
730
703
|
};
|
|
731
704
|
/**
|
|
732
705
|
* Generate a binary node that will request the phone to re-upload the media & return the newly uploaded URL
|
|
733
706
|
*/
|
|
734
|
-
const encryptMediaRetryRequest = (key, mediaKey, meId) => {
|
|
707
|
+
export const encryptMediaRetryRequest = (key, mediaKey, meId) => {
|
|
735
708
|
const recp = { stanzaId: key.id };
|
|
736
|
-
const recpBuffer =
|
|
709
|
+
const recpBuffer = proto.ServerErrorReceipt.encode(recp).finish();
|
|
737
710
|
const iv = Crypto.randomBytes(12);
|
|
738
711
|
const retryKey = getMediaRetryKey(mediaKey);
|
|
739
|
-
const ciphertext =
|
|
712
|
+
const ciphertext = aesEncryptGCM(recpBuffer, retryKey, iv, Buffer.from(key.id));
|
|
740
713
|
const req = {
|
|
741
714
|
tag: 'receipt',
|
|
742
715
|
attrs: {
|
|
743
716
|
id: key.id,
|
|
744
|
-
to:
|
|
717
|
+
to: jidNormalizedUser(meId),
|
|
745
718
|
type: 'server-error'
|
|
746
719
|
},
|
|
747
720
|
content: [
|
|
@@ -760,7 +733,7 @@ const encryptMediaRetryRequest = (key, mediaKey, meId) => {
|
|
|
760
733
|
tag: 'rmr',
|
|
761
734
|
attrs: {
|
|
762
735
|
jid: key.remoteJid,
|
|
763
|
-
|
|
736
|
+
from_me: (!!key.fromMe).toString(),
|
|
764
737
|
// @ts-ignore
|
|
765
738
|
participant: key.participant || undefined
|
|
766
739
|
}
|
|
@@ -769,9 +742,8 @@ const encryptMediaRetryRequest = (key, mediaKey, meId) => {
|
|
|
769
742
|
};
|
|
770
743
|
return req;
|
|
771
744
|
};
|
|
772
|
-
|
|
773
|
-
const
|
|
774
|
-
const rmrNode = (0, WABinary_1.getBinaryNodeChild)(node, 'rmr');
|
|
745
|
+
export const decodeMediaRetryNode = (node) => {
|
|
746
|
+
const rmrNode = getBinaryNodeChild(node, 'rmr');
|
|
775
747
|
const event = {
|
|
776
748
|
key: {
|
|
777
749
|
id: node.attrs.id,
|
|
@@ -780,40 +752,37 @@ const decodeMediaRetryNode = (node) => {
|
|
|
780
752
|
participant: rmrNode.attrs.participant
|
|
781
753
|
}
|
|
782
754
|
};
|
|
783
|
-
const errorNode =
|
|
755
|
+
const errorNode = getBinaryNodeChild(node, 'error');
|
|
784
756
|
if (errorNode) {
|
|
785
757
|
const errorCode = +errorNode.attrs.code;
|
|
786
|
-
event.error = new
|
|
758
|
+
event.error = new Boom(`Failed to re-upload media (${errorCode})`, {
|
|
759
|
+
data: errorNode.attrs,
|
|
760
|
+
statusCode: getStatusCodeForMediaRetry(errorCode)
|
|
761
|
+
});
|
|
787
762
|
}
|
|
788
763
|
else {
|
|
789
|
-
const encryptedInfoNode =
|
|
790
|
-
const ciphertext =
|
|
791
|
-
const iv =
|
|
764
|
+
const encryptedInfoNode = getBinaryNodeChild(node, 'encrypt');
|
|
765
|
+
const ciphertext = getBinaryNodeChildBuffer(encryptedInfoNode, 'enc_p');
|
|
766
|
+
const iv = getBinaryNodeChildBuffer(encryptedInfoNode, 'enc_iv');
|
|
792
767
|
if (ciphertext && iv) {
|
|
793
768
|
event.media = { ciphertext, iv };
|
|
794
769
|
}
|
|
795
770
|
else {
|
|
796
|
-
event.error = new
|
|
771
|
+
event.error = new Boom('Failed to re-upload media (missing ciphertext)', { statusCode: 404 });
|
|
797
772
|
}
|
|
798
773
|
}
|
|
799
774
|
return event;
|
|
800
775
|
};
|
|
801
|
-
|
|
802
|
-
const decryptMediaRetryData = ({ ciphertext, iv }, mediaKey, msgId) => {
|
|
776
|
+
export const decryptMediaRetryData = ({ ciphertext, iv }, mediaKey, msgId) => {
|
|
803
777
|
const retryKey = getMediaRetryKey(mediaKey);
|
|
804
|
-
const plaintext =
|
|
805
|
-
return
|
|
778
|
+
const plaintext = aesDecryptGCM(ciphertext, retryKey, iv, Buffer.from(msgId));
|
|
779
|
+
return proto.MediaRetryNotification.decode(plaintext);
|
|
806
780
|
};
|
|
807
|
-
|
|
808
|
-
const getStatusCodeForMediaRetry = (code) => MEDIA_RETRY_STATUS_MAP[code];
|
|
809
|
-
exports.getStatusCodeForMediaRetry = getStatusCodeForMediaRetry;
|
|
781
|
+
export const getStatusCodeForMediaRetry = (code) => MEDIA_RETRY_STATUS_MAP[code];
|
|
810
782
|
const MEDIA_RETRY_STATUS_MAP = {
|
|
811
|
-
[
|
|
812
|
-
[
|
|
813
|
-
[
|
|
814
|
-
[
|
|
783
|
+
[proto.MediaRetryNotification.ResultType.SUCCESS]: 200,
|
|
784
|
+
[proto.MediaRetryNotification.ResultType.DECRYPTION_ERROR]: 412,
|
|
785
|
+
[proto.MediaRetryNotification.ResultType.NOT_FOUND]: 404,
|
|
786
|
+
[proto.MediaRetryNotification.ResultType.GENERAL_ERROR]: 418
|
|
815
787
|
};
|
|
816
|
-
|
|
817
|
-
function __importStar(arg0) {
|
|
818
|
-
throw new Error('Function not implemented.');
|
|
819
|
-
}
|
|
788
|
+
//# sourceMappingURL=messages-media.js.map
|