@baileys-md/baileys 10.1.0 → 11.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 +1303 -2
- package/WAProto/GenerateStatics.sh +3 -0
- package/WAProto/WAProto.proto +4633 -0
- package/WAProto/fix-imports.js +29 -0
- package/WAProto/index.js +13516 -4182
- package/lib/Defaults/baileys-version.js +1 -0
- package/lib/Defaults/index.js +51 -72
- package/lib/Signal/Group/ciphertext-message.js +12 -0
- package/lib/Signal/Group/group-session-builder.js +30 -0
- package/lib/Signal/Group/group_cipher.js +94 -0
- package/lib/Signal/Group/index.js +12 -0
- package/lib/Signal/Group/keyhelper.js +19 -0
- package/lib/Signal/Group/queue-job.js +54 -0
- package/lib/Signal/Group/sender-chain-key.js +32 -0
- package/lib/Signal/Group/sender-key-distribution-message.js +63 -0
- package/lib/Signal/Group/sender-key-message.js +67 -0
- package/lib/Signal/Group/sender-key-name.js +48 -0
- package/lib/Signal/Group/sender-key-record.js +50 -0
- package/lib/Signal/Group/sender-key-state.js +96 -0
- package/{WASignalGroup/sender_message_key.js → lib/Signal/Group/sender-message-key.js} +4 -16
- package/lib/Signal/libsignal.js +41 -61
- package/lib/Socket/Client/index.js +3 -19
- package/lib/Socket/Client/types.js +11 -0
- package/lib/Socket/Client/websocket.js +50 -0
- package/lib/Socket/business.js +37 -42
- package/lib/Socket/chats.js +194 -187
- package/lib/Socket/communities.js +351 -0
- package/lib/Socket/groups.js +87 -90
- package/lib/Socket/index.js +7 -8
- package/lib/Socket/messages-recv.js +360 -335
- package/lib/Socket/messages-send.js +156 -279
- package/lib/Socket/mex.js +42 -0
- package/lib/Socket/newsletter.js +144 -213
- package/lib/Socket/socket.js +128 -161
- package/lib/Socket/usync.js +19 -26
- package/lib/Types/Auth.js +2 -2
- package/lib/Types/Call.js +2 -2
- package/lib/Types/Chat.js +8 -4
- package/lib/Types/Contact.js +2 -2
- package/lib/Types/Events.js +2 -2
- package/lib/Types/GroupMetadata.js +2 -2
- package/lib/Types/Label.js +3 -5
- package/lib/Types/LabelAssociation.js +3 -5
- package/lib/Types/Message.js +7 -7
- package/lib/Types/Newsletter.js +30 -17
- package/lib/Types/Product.js +2 -2
- package/lib/Types/Signal.js +2 -2
- package/lib/Types/Socket.js +3 -2
- package/lib/Types/State.js +2 -2
- package/lib/Types/USync.js +2 -2
- package/lib/Types/index.js +15 -31
- package/lib/Utils/auth-utils.js +31 -47
- package/lib/Utils/baileys-event-stream.js +15 -22
- package/lib/Utils/business.js +66 -69
- package/lib/Utils/chat-utils.js +200 -195
- package/lib/Utils/crypto.js +70 -85
- package/lib/Utils/decode-wa-message.js +47 -51
- package/lib/Utils/event-buffer.js +36 -46
- package/lib/Utils/generics.js +116 -188
- package/lib/Utils/history.js +37 -46
- package/lib/Utils/index.js +19 -33
- package/lib/Utils/link-preview.js +14 -55
- package/lib/Utils/logger.js +3 -7
- package/lib/Utils/lt-hash.js +23 -26
- package/lib/{Store → Utils}/make-in-memory-store.js +19 -27
- package/lib/Utils/make-mutex.js +7 -10
- package/lib/{Store → Utils}/make-ordered-dictionary.js +1 -3
- package/lib/Utils/messages-media.js +236 -368
- package/lib/Utils/messages.js +278 -510
- package/lib/Utils/noise-handler.js +22 -31
- package/lib/{Store → Utils}/object-repository.js +1 -4
- package/lib/Utils/process-message.js +144 -148
- package/lib/Utils/signal.js +71 -64
- package/lib/Utils/use-multi-file-auth-state.js +112 -84
- package/lib/Utils/validate-connection.js +72 -115
- package/lib/WABinary/constants.js +1281 -20
- package/lib/WABinary/decode.js +15 -52
- package/lib/WABinary/encode.js +14 -48
- package/lib/WABinary/generic-utils.js +31 -39
- package/lib/WABinary/index.js +6 -21
- package/lib/WABinary/jid-utils.js +23 -40
- package/lib/WABinary/types.js +2 -2
- package/lib/WAM/BinaryInfo.js +2 -5
- package/lib/WAM/constants.js +2257 -2366
- package/lib/WAM/encode.js +17 -21
- package/lib/WAM/index.js +4 -19
- package/lib/WAUSync/Protocols/USyncContactProtocol.js +8 -11
- package/lib/WAUSync/Protocols/USyncDeviceProtocol.js +11 -14
- package/lib/WAUSync/Protocols/USyncDisappearingModeProtocol.js +9 -12
- package/lib/WAUSync/Protocols/USyncStatusProtocol.js +9 -13
- package/lib/WAUSync/Protocols/UsyncBotProfileProtocol.js +20 -22
- package/lib/WAUSync/Protocols/UsyncLIDProtocol.js +3 -6
- package/lib/WAUSync/Protocols/index.js +5 -20
- package/lib/WAUSync/USyncQuery.js +34 -32
- package/lib/WAUSync/USyncUser.js +2 -5
- package/lib/WAUSync/index.js +4 -19
- package/lib/index.js +11 -33
- package/package.json +25 -54
- package/WASignalGroup/GroupProtocol.js +0 -1697
- package/WASignalGroup/ciphertext_message.js +0 -16
- package/WASignalGroup/group_cipher.js +0 -120
- package/WASignalGroup/group_session_builder.js +0 -46
- package/WASignalGroup/index.js +0 -5
- package/WASignalGroup/keyhelper.js +0 -21
- package/WASignalGroup/protobufs.js +0 -3
- package/WASignalGroup/queue_job.js +0 -69
- package/WASignalGroup/sender_chain_key.js +0 -50
- package/WASignalGroup/sender_key_distribution_message.js +0 -78
- package/WASignalGroup/sender_key_message.js +0 -92
- package/WASignalGroup/sender_key_name.js +0 -70
- package/WASignalGroup/sender_key_record.js +0 -56
- package/WASignalGroup/sender_key_state.js +0 -129
- package/lib/Defaults/baileys-version.json +0 -3
- package/lib/Defaults/phonenumber-mcc.json +0 -223
- package/lib/Socket/Client/abstract-socket-client.js +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.js +0 -166
- package/lib/Store/index.js +0 -8
- package/lib/Store/make-cache-manager-store.js +0 -83
- package/lib/Store/make-mongo-store.js +0 -567
|
@@ -1,108 +1,81 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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 };
|
|
1
|
+
import { Boom } from '@hapi/boom';
|
|
2
|
+
import axios, {} from 'axios';
|
|
3
|
+
import { exec } from 'child_process';
|
|
4
|
+
import * as Crypto from 'crypto';
|
|
5
|
+
import { once } from 'events';
|
|
6
|
+
import { createReadStream, createWriteStream, promises as fs, WriteStream } from 'fs';
|
|
7
|
+
import { tmpdir } from 'os';
|
|
8
|
+
import { join } from 'path';
|
|
9
|
+
import { Readable, Transform } from 'stream';
|
|
10
|
+
import { URL } from 'url';
|
|
11
|
+
import { proto } from '../../WAProto/index.js';
|
|
12
|
+
import { DEFAULT_ORIGIN, MEDIA_HKDF_KEY_MAPPING, MEDIA_PATH_MAP } from '../Defaults/index.js';
|
|
13
|
+
import { getBinaryNodeChild, getBinaryNodeChildBuffer, jidNormalizedUser } from '../WABinary/index.js';
|
|
14
|
+
import { aesDecryptGCM, aesEncryptGCM, hkdf } from './crypto.js';
|
|
15
|
+
import { generateMessageIDV2 } from './generics.js';
|
|
16
|
+
const getTmpFilesDirectory = () => tmpdir();
|
|
17
|
+
export const hkdfInfoKey = (type) => {
|
|
18
|
+
const hkdfInfo = MEDIA_HKDF_KEY_MAPPING[type];
|
|
19
|
+
return `WhatsApp ${hkdfInfo} Keys`;
|
|
37
20
|
};
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
const
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
const
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
(async () => {
|
|
64
|
-
const jimp = await (Promise.resolve().then(() => __importStar(require('jimp'))).catch(() => { }));
|
|
65
|
-
return jimp;
|
|
66
|
-
})(),
|
|
67
|
-
(async () => {
|
|
68
|
-
const sharp = await (Promise.resolve().then(() => __importStar(require('sharp'))).catch(() => { }));
|
|
69
|
-
return sharp;
|
|
70
|
-
})()
|
|
71
|
-
]);
|
|
72
|
-
if (sharp) {
|
|
73
|
-
return { sharp };
|
|
21
|
+
export const getRawMediaUploadData = async (media, mediaType, logger) => {
|
|
22
|
+
const { stream } = await getStream(media);
|
|
23
|
+
logger?.debug('got stream for raw upload');
|
|
24
|
+
const hasher = Crypto.createHash('sha256');
|
|
25
|
+
const filePath = join(tmpdir(), mediaType + generateMessageIDV2());
|
|
26
|
+
const fileWriteStream = createWriteStream(filePath);
|
|
27
|
+
let fileLength = 0;
|
|
28
|
+
try {
|
|
29
|
+
for await (const data of stream) {
|
|
30
|
+
fileLength += data.length;
|
|
31
|
+
hasher.update(data);
|
|
32
|
+
if (!fileWriteStream.write(data)) {
|
|
33
|
+
await once(fileWriteStream, 'drain');
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
fileWriteStream.end();
|
|
37
|
+
await once(fileWriteStream, 'finish');
|
|
38
|
+
stream.destroy();
|
|
39
|
+
const fileSha256 = hasher.digest();
|
|
40
|
+
logger?.debug('hashed data for raw upload');
|
|
41
|
+
return {
|
|
42
|
+
filePath: filePath,
|
|
43
|
+
fileSha256,
|
|
44
|
+
fileLength
|
|
45
|
+
};
|
|
74
46
|
}
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
47
|
+
catch (error) {
|
|
48
|
+
fileWriteStream.destroy();
|
|
49
|
+
stream.destroy();
|
|
50
|
+
try {
|
|
51
|
+
await fs.unlink(filePath);
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
//
|
|
55
|
+
}
|
|
56
|
+
throw error;
|
|
78
57
|
}
|
|
79
|
-
throw new boom_1.Boom('No image processing library available');
|
|
80
58
|
};
|
|
81
|
-
const hkdfInfoKey = (type) => {
|
|
82
|
-
const hkdfInfo = Defaults_1.MEDIA_HKDF_KEY_MAPPING[type];
|
|
83
|
-
return `WhatsApp ${hkdfInfo} Keys`;
|
|
84
|
-
};
|
|
85
|
-
exports.hkdfInfoKey = hkdfInfoKey;
|
|
86
59
|
/** generates all the keys required to encrypt/decrypt & sign a media message */
|
|
87
|
-
async function getMediaKeys(buffer, mediaType) {
|
|
60
|
+
export async function getMediaKeys(buffer, mediaType) {
|
|
88
61
|
if (!buffer) {
|
|
89
|
-
throw new
|
|
62
|
+
throw new Boom('Cannot derive from empty media key');
|
|
90
63
|
}
|
|
91
64
|
if (typeof buffer === 'string') {
|
|
92
65
|
buffer = Buffer.from(buffer.replace('data:;base64,', ''), 'base64');
|
|
93
66
|
}
|
|
94
67
|
// expand using HKDF to 112 bytes, also pass in the relevant app info
|
|
95
|
-
const expandedMediaKey = await
|
|
68
|
+
const expandedMediaKey = await hkdf(buffer, 112, { info: hkdfInfoKey(mediaType) });
|
|
96
69
|
return {
|
|
97
70
|
iv: expandedMediaKey.slice(0, 16),
|
|
98
71
|
cipherKey: expandedMediaKey.slice(16, 48),
|
|
99
|
-
macKey: expandedMediaKey.slice(48, 80)
|
|
72
|
+
macKey: expandedMediaKey.slice(48, 80)
|
|
100
73
|
};
|
|
101
74
|
}
|
|
102
75
|
/** Extracts video thumb using FFMPEG */
|
|
103
76
|
const extractVideoThumb = async (path, destPath, time, size) => new Promise((resolve, reject) => {
|
|
104
77
|
const cmd = `ffmpeg -ss ${time} -i ${path} -y -vf scale=${size.width}:-1 -vframes 1 -f image2 ${destPath}`;
|
|
105
|
-
|
|
78
|
+
exec(cmd, err => {
|
|
106
79
|
if (err) {
|
|
107
80
|
reject(err);
|
|
108
81
|
}
|
|
@@ -111,117 +84,83 @@ const extractVideoThumb = async (path, destPath, time, size) => new Promise((res
|
|
|
111
84
|
}
|
|
112
85
|
});
|
|
113
86
|
});
|
|
114
|
-
const extractImageThumb = async (bufferOrFilePath, width = 32) => {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
};
|
|
134
|
-
}
|
|
135
|
-
else if ('jimp' in lib && typeof ((_b = lib.jimp) === null || _b === void 0 ? void 0 : _b.read) === 'function') {
|
|
136
|
-
const { read, MIME_JPEG, RESIZE_BILINEAR, AUTO } = lib.jimp;
|
|
137
|
-
const jimp = await read(bufferOrFilePath);
|
|
138
|
-
const dimensions = {
|
|
139
|
-
width: jimp.getWidth(),
|
|
140
|
-
height: jimp.getHeight()
|
|
141
|
-
};
|
|
142
|
-
const buffer = await jimp
|
|
143
|
-
.quality(50)
|
|
144
|
-
.resize(width, AUTO, RESIZE_BILINEAR)
|
|
145
|
-
.getBufferAsync(MIME_JPEG);
|
|
146
|
-
return {
|
|
147
|
-
buffer,
|
|
148
|
-
original: dimensions
|
|
149
|
-
};
|
|
150
|
-
}
|
|
151
|
-
else {
|
|
152
|
-
throw new boom_1.Boom('No image processing library available');
|
|
153
|
-
}
|
|
87
|
+
export const extractImageThumb = async (bufferOrFilePath, width = 32) => {
|
|
88
|
+
// TODO: Move entirely to sharp, removing jimp as it supports readable streams
|
|
89
|
+
// This will have positive speed and performance impacts as well as minimizing RAM usage.
|
|
90
|
+
if (bufferOrFilePath instanceof Readable) {
|
|
91
|
+
bufferOrFilePath = await toBuffer(bufferOrFilePath);
|
|
92
|
+
}
|
|
93
|
+
const Jimp = await (await import('jimp')).default
|
|
94
|
+
const jimp = Jimp.read(bufferOrFilePath);
|
|
95
|
+
const dimensions = {
|
|
96
|
+
width: jimp.width,
|
|
97
|
+
height: jimp.height
|
|
98
|
+
};
|
|
99
|
+
const buffer = await jimp
|
|
100
|
+
.resize({ w: width, mode: lib.jimp.ResizeStrategy.BILINEAR })
|
|
101
|
+
.getBuffer('image/jpeg', { quality: 100 });
|
|
102
|
+
return {
|
|
103
|
+
buffer,
|
|
104
|
+
original: dimensions
|
|
105
|
+
};
|
|
154
106
|
};
|
|
155
|
-
|
|
156
|
-
const
|
|
157
|
-
|
|
158
|
-
.replace(/\//g, '_')
|
|
159
|
-
.replace(/\=+$/, '')));
|
|
160
|
-
exports.encodeBase64EncodedStringForUpload = encodeBase64EncodedStringForUpload;
|
|
161
|
-
const generateProfilePicture = async (mediaUpload) => {
|
|
162
|
-
let bufferOrFilePath;
|
|
163
|
-
let img;
|
|
107
|
+
export const encodeBase64EncodedStringForUpload = (b64) => encodeURIComponent(b64.replace(/\+/g, '-').replace(/\//g, '_').replace(/\=+$/, ''));
|
|
108
|
+
export const generateProfilePicture = async (mediaUpload) => {
|
|
109
|
+
let buffer;
|
|
164
110
|
if (Buffer.isBuffer(mediaUpload)) {
|
|
165
|
-
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
const
|
|
174
|
-
const cropped =
|
|
175
|
-
img = cropped
|
|
176
|
-
.quality(100)
|
|
177
|
-
.getBufferAsync(jimp_1.default.MIME_JPEG);
|
|
111
|
+
buffer = mediaUpload;
|
|
112
|
+
} else {
|
|
113
|
+
const { stream } = await getStream(mediaUpload);
|
|
114
|
+
buffer = await toBuffer(stream);
|
|
115
|
+
}
|
|
116
|
+
const Jimp = (await import('jimp')).default;
|
|
117
|
+
const jimp = await Jimp.read(mediaUpload);
|
|
118
|
+
const min = await jimp.getWidth();
|
|
119
|
+
const max = await jimp.getHeight();
|
|
120
|
+
const cropped = await jimp.crop(0, 0, min, max);
|
|
178
121
|
return {
|
|
179
|
-
img: await
|
|
122
|
+
img: await cropped.scaleToFit(720, 720).getBufferAsync(Jimp.MIME_JPEG),
|
|
180
123
|
};
|
|
181
124
|
};
|
|
182
|
-
exports.generateProfilePicture = generateProfilePicture;
|
|
183
125
|
/** gets the SHA256 of the given media message */
|
|
184
|
-
const mediaMessageSHA256B64 = (message) => {
|
|
126
|
+
export const mediaMessageSHA256B64 = (message) => {
|
|
185
127
|
const media = Object.values(message)[0];
|
|
186
|
-
return
|
|
128
|
+
return media?.fileSha256 && Buffer.from(media.fileSha256).toString('base64');
|
|
187
129
|
};
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
const musicMetadata = await Promise.resolve().then(() => __importStar(require('music-metadata')));
|
|
130
|
+
export async function getAudioDuration(buffer) {
|
|
131
|
+
const musicMetadata = await import('music-metadata');
|
|
191
132
|
let metadata;
|
|
133
|
+
const options = {
|
|
134
|
+
duration: true
|
|
135
|
+
};
|
|
192
136
|
if (Buffer.isBuffer(buffer)) {
|
|
193
|
-
metadata = await musicMetadata.parseBuffer(buffer, undefined,
|
|
137
|
+
metadata = await musicMetadata.parseBuffer(buffer, undefined, options);
|
|
194
138
|
}
|
|
195
139
|
else if (typeof buffer === 'string') {
|
|
196
|
-
|
|
197
|
-
try {
|
|
198
|
-
metadata = await musicMetadata.parseStream(rStream, undefined, { duration: true });
|
|
199
|
-
}
|
|
200
|
-
finally {
|
|
201
|
-
rStream.destroy();
|
|
202
|
-
}
|
|
140
|
+
metadata = await musicMetadata.parseFile(buffer, options);
|
|
203
141
|
}
|
|
204
142
|
else {
|
|
205
|
-
metadata = await musicMetadata.parseStream(buffer, undefined,
|
|
143
|
+
metadata = await musicMetadata.parseStream(buffer, undefined, options);
|
|
206
144
|
}
|
|
207
145
|
return metadata.format.duration;
|
|
208
146
|
}
|
|
209
147
|
/**
|
|
210
148
|
referenced from and modifying https://github.com/wppconnect-team/wa-js/blob/main/src/chat/functions/prepareAudioWaveform.ts
|
|
211
149
|
*/
|
|
212
|
-
async function getAudioWaveform(buffer, logger) {
|
|
150
|
+
export async function getAudioWaveform(buffer, logger) {
|
|
213
151
|
try {
|
|
214
|
-
|
|
152
|
+
// @ts-ignore
|
|
153
|
+
const { default: decoder } = await import('audio-decode');
|
|
215
154
|
let audioData;
|
|
216
155
|
if (Buffer.isBuffer(buffer)) {
|
|
217
156
|
audioData = buffer;
|
|
218
157
|
}
|
|
219
158
|
else if (typeof buffer === 'string') {
|
|
220
|
-
const rStream =
|
|
221
|
-
audioData = await
|
|
159
|
+
const rStream = createReadStream(buffer);
|
|
160
|
+
audioData = await toBuffer(rStream);
|
|
222
161
|
}
|
|
223
162
|
else {
|
|
224
|
-
audioData = await
|
|
163
|
+
audioData = await toBuffer(buffer);
|
|
225
164
|
}
|
|
226
165
|
const audioBuffer = await decoder(audioData);
|
|
227
166
|
const rawData = audioBuffer.getChannelData(0); // We only need to work with one channel of data
|
|
@@ -238,23 +177,22 @@ async function getAudioWaveform(buffer, logger) {
|
|
|
238
177
|
}
|
|
239
178
|
// This guarantees that the largest data point will be set to 1, and the rest of the data will scale proportionally.
|
|
240
179
|
const multiplier = Math.pow(Math.max(...filteredData), -1);
|
|
241
|
-
const normalizedData = filteredData.map(
|
|
180
|
+
const normalizedData = filteredData.map(n => n * multiplier);
|
|
242
181
|
// Generate waveform like WhatsApp
|
|
243
|
-
const waveform = new Uint8Array(normalizedData.map(
|
|
182
|
+
const waveform = new Uint8Array(normalizedData.map(n => Math.floor(100 * n)));
|
|
244
183
|
return waveform;
|
|
245
184
|
}
|
|
246
185
|
catch (e) {
|
|
247
|
-
logger
|
|
186
|
+
logger?.debug('Failed to generate waveform: ' + e);
|
|
248
187
|
}
|
|
249
188
|
}
|
|
250
|
-
const toReadable = (buffer) => {
|
|
251
|
-
const readable = new
|
|
189
|
+
export const toReadable = (buffer) => {
|
|
190
|
+
const readable = new Readable({ read: () => { } });
|
|
252
191
|
readable.push(buffer);
|
|
253
192
|
readable.push(null);
|
|
254
193
|
return readable;
|
|
255
194
|
};
|
|
256
|
-
|
|
257
|
-
const toBuffer = async (stream) => {
|
|
195
|
+
export const toBuffer = async (stream) => {
|
|
258
196
|
const chunks = [];
|
|
259
197
|
for await (const chunk of stream) {
|
|
260
198
|
chunks.push(chunk);
|
|
@@ -262,45 +200,47 @@ const toBuffer = async (stream) => {
|
|
|
262
200
|
stream.destroy();
|
|
263
201
|
return Buffer.concat(chunks);
|
|
264
202
|
};
|
|
265
|
-
|
|
266
|
-
const getStream = async (item, opts) => {
|
|
203
|
+
export const getStream = async (item, opts) => {
|
|
267
204
|
if (Buffer.isBuffer(item)) {
|
|
268
|
-
return { stream:
|
|
205
|
+
return { stream: toReadable(item), type: 'buffer' };
|
|
269
206
|
}
|
|
270
207
|
if ('stream' in item) {
|
|
271
208
|
return { stream: item.stream, type: 'readable' };
|
|
272
209
|
}
|
|
273
|
-
|
|
274
|
-
|
|
210
|
+
const urlStr = item.url.toString();
|
|
211
|
+
if (urlStr.startsWith('data:')) {
|
|
212
|
+
const buffer = Buffer.from(urlStr.split(',')[1], 'base64');
|
|
213
|
+
return { stream: toReadable(buffer), type: 'buffer' };
|
|
214
|
+
}
|
|
215
|
+
if (urlStr.startsWith('http://') || urlStr.startsWith('https://')) {
|
|
216
|
+
return { stream: await getHttpStream(item.url, opts), type: 'remote' };
|
|
275
217
|
}
|
|
276
|
-
return { stream:
|
|
218
|
+
return { stream: createReadStream(item.url), type: 'file' };
|
|
277
219
|
};
|
|
278
|
-
exports.getStream = getStream;
|
|
279
220
|
/** generates a thumbnail for a given media, if required */
|
|
280
|
-
async function generateThumbnail(file, mediaType, options) {
|
|
281
|
-
var _a;
|
|
221
|
+
export async function generateThumbnail(file, mediaType, options) {
|
|
282
222
|
let thumbnail;
|
|
283
223
|
let originalImageDimensions;
|
|
284
224
|
if (mediaType === 'image') {
|
|
285
|
-
const { buffer, original } = await
|
|
225
|
+
const { buffer, original } = await extractImageThumb(file);
|
|
286
226
|
thumbnail = buffer.toString('base64');
|
|
287
227
|
if (original.width && original.height) {
|
|
288
228
|
originalImageDimensions = {
|
|
289
229
|
width: original.width,
|
|
290
|
-
height: original.height
|
|
230
|
+
height: original.height
|
|
291
231
|
};
|
|
292
232
|
}
|
|
293
233
|
}
|
|
294
234
|
else if (mediaType === 'video') {
|
|
295
|
-
const imgFilename =
|
|
235
|
+
const imgFilename = join(getTmpFilesDirectory(), generateMessageIDV2() + '.jpg');
|
|
296
236
|
try {
|
|
297
237
|
await extractVideoThumb(file, imgFilename, '00:00:00', { width: 32, height: 32 });
|
|
298
|
-
const buff = await
|
|
238
|
+
const buff = await fs.readFile(imgFilename);
|
|
299
239
|
thumbnail = buff.toString('base64');
|
|
300
|
-
await
|
|
240
|
+
await fs.unlink(imgFilename);
|
|
301
241
|
}
|
|
302
242
|
catch (err) {
|
|
303
|
-
|
|
243
|
+
options.logger?.debug('could not generate video thumb: ' + err);
|
|
304
244
|
}
|
|
305
245
|
}
|
|
306
246
|
return {
|
|
@@ -308,160 +248,110 @@ async function generateThumbnail(file, mediaType, options) {
|
|
|
308
248
|
originalImageDimensions
|
|
309
249
|
};
|
|
310
250
|
}
|
|
311
|
-
const getHttpStream = async (url, options = {}) => {
|
|
312
|
-
const fetched = await
|
|
251
|
+
export const getHttpStream = async (url, options = {}) => {
|
|
252
|
+
const fetched = await axios.get(url.toString(), { ...options, responseType: 'stream' });
|
|
313
253
|
return fetched.data;
|
|
314
254
|
};
|
|
315
|
-
|
|
316
|
-
const
|
|
317
|
-
|
|
318
|
-
logger === null || logger === void 0 ? void 0 : logger.debug('fetched media stream');
|
|
319
|
-
let bodyPath;
|
|
320
|
-
let didSaveToTmpPath = false;
|
|
321
|
-
try {
|
|
322
|
-
const buffer = await (0, exports.toBuffer)(stream);
|
|
323
|
-
if (type === 'file') {
|
|
324
|
-
bodyPath = media.url;
|
|
325
|
-
}
|
|
326
|
-
else if (saveOriginalFileIfRequired) {
|
|
327
|
-
bodyPath = (0, path_1.join)(getTmpFilesDirectory(), mediaType + (0, generics_1.generateMessageIDV2)());
|
|
328
|
-
(0, fs_1.writeFileSync)(bodyPath, buffer);
|
|
329
|
-
didSaveToTmpPath = true;
|
|
330
|
-
}
|
|
331
|
-
const fileLength = buffer.length;
|
|
332
|
-
const fileSha256 = Crypto.createHash('sha256').update(buffer).digest();
|
|
333
|
-
stream === null || stream === void 0 ? void 0 : stream.destroy();
|
|
334
|
-
logger === null || logger === void 0 ? void 0 : logger.debug('prepare stream data successfully');
|
|
335
|
-
return {
|
|
336
|
-
mediaKey: undefined,
|
|
337
|
-
encWriteStream: buffer,
|
|
338
|
-
fileLength,
|
|
339
|
-
fileSha256,
|
|
340
|
-
fileEncSha256: undefined,
|
|
341
|
-
bodyPath,
|
|
342
|
-
didSaveToTmpPath
|
|
343
|
-
};
|
|
344
|
-
}
|
|
345
|
-
catch (error) {
|
|
346
|
-
// destroy all streams with error
|
|
347
|
-
stream.destroy();
|
|
348
|
-
if (didSaveToTmpPath) {
|
|
349
|
-
try {
|
|
350
|
-
await fs_1.promises.unlink(bodyPath);
|
|
351
|
-
}
|
|
352
|
-
catch (err) {
|
|
353
|
-
logger === null || logger === void 0 ? void 0 : logger.error({ err }, 'failed to save to tmp path');
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
throw error;
|
|
357
|
-
}
|
|
358
|
-
};
|
|
359
|
-
exports.prepareStream = prepareStream;
|
|
360
|
-
const encryptedStream = async (media, mediaType, { logger, saveOriginalFileIfRequired, opts } = {}) => {
|
|
361
|
-
const { stream, type } = await (0, exports.getStream)(media, opts);
|
|
362
|
-
logger === null || logger === void 0 ? void 0 : logger.debug('fetched media stream');
|
|
255
|
+
export const encryptedStream = async (media, mediaType, { logger, saveOriginalFileIfRequired, opts } = {}) => {
|
|
256
|
+
const { stream, type } = await getStream(media, opts);
|
|
257
|
+
logger?.debug('fetched media stream');
|
|
363
258
|
const mediaKey = Crypto.randomBytes(32);
|
|
364
259
|
const { cipherKey, iv, macKey } = await getMediaKeys(mediaKey, mediaType);
|
|
365
|
-
const
|
|
366
|
-
|
|
367
|
-
let
|
|
368
|
-
let
|
|
369
|
-
if (
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
else if (saveOriginalFileIfRequired) {
|
|
373
|
-
bodyPath = (0, path_1.join)(getTmpFilesDirectory(), mediaType + (0, generics_1.generateMessageIDV2)());
|
|
374
|
-
writeStream = (0, fs_1.createWriteStream)(bodyPath);
|
|
375
|
-
didSaveToTmpPath = true;
|
|
260
|
+
const encFilePath = join(getTmpFilesDirectory(), mediaType + generateMessageIDV2() + '-enc');
|
|
261
|
+
const encFileWriteStream = createWriteStream(encFilePath);
|
|
262
|
+
let originalFileStream;
|
|
263
|
+
let originalFilePath;
|
|
264
|
+
if (saveOriginalFileIfRequired) {
|
|
265
|
+
originalFilePath = join(getTmpFilesDirectory(), mediaType + generateMessageIDV2() + '-original');
|
|
266
|
+
originalFileStream = createWriteStream(originalFilePath);
|
|
376
267
|
}
|
|
377
268
|
let fileLength = 0;
|
|
378
269
|
const aes = Crypto.createCipheriv('aes-256-cbc', cipherKey, iv);
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
270
|
+
const hmac = Crypto.createHmac('sha256', macKey).update(iv);
|
|
271
|
+
const sha256Plain = Crypto.createHash('sha256');
|
|
272
|
+
const sha256Enc = Crypto.createHash('sha256');
|
|
273
|
+
const onChunk = (buff) => {
|
|
274
|
+
sha256Enc.update(buff);
|
|
275
|
+
hmac.update(buff);
|
|
276
|
+
encFileWriteStream.write(buff);
|
|
277
|
+
};
|
|
382
278
|
try {
|
|
383
279
|
for await (const data of stream) {
|
|
384
280
|
fileLength += data.length;
|
|
385
|
-
if (type === 'remote'
|
|
386
|
-
|
|
387
|
-
&& fileLength + data.length > opts.maxContentLength) {
|
|
388
|
-
throw new boom_1.Boom(`content length exceeded when encrypting "${type}"`, {
|
|
281
|
+
if (type === 'remote' && opts?.maxContentLength && fileLength + data.length > opts.maxContentLength) {
|
|
282
|
+
throw new Boom(`content length exceeded when encrypting "${type}"`, {
|
|
389
283
|
data: { media, type }
|
|
390
284
|
});
|
|
391
285
|
}
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
await (0, events_1.once)(writeStream, 'drain');
|
|
286
|
+
if (originalFileStream) {
|
|
287
|
+
if (!originalFileStream.write(data)) {
|
|
288
|
+
await once(originalFileStream, 'drain');
|
|
396
289
|
}
|
|
397
290
|
}
|
|
291
|
+
sha256Plain.update(data);
|
|
398
292
|
onChunk(aes.update(data));
|
|
399
293
|
}
|
|
400
294
|
onChunk(aes.final());
|
|
401
295
|
const mac = hmac.digest().slice(0, 10);
|
|
402
|
-
sha256Enc
|
|
296
|
+
sha256Enc.update(mac);
|
|
403
297
|
const fileSha256 = sha256Plain.digest();
|
|
404
298
|
const fileEncSha256 = sha256Enc.digest();
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
299
|
+
encFileWriteStream.write(mac);
|
|
300
|
+
encFileWriteStream.end();
|
|
301
|
+
originalFileStream?.end?.();
|
|
408
302
|
stream.destroy();
|
|
409
|
-
logger
|
|
303
|
+
logger?.debug('encrypted data successfully');
|
|
410
304
|
return {
|
|
411
305
|
mediaKey,
|
|
412
|
-
|
|
413
|
-
|
|
306
|
+
originalFilePath,
|
|
307
|
+
encFilePath,
|
|
414
308
|
mac,
|
|
415
309
|
fileEncSha256,
|
|
416
310
|
fileSha256,
|
|
417
|
-
fileLength
|
|
418
|
-
didSaveToTmpPath
|
|
311
|
+
fileLength
|
|
419
312
|
};
|
|
420
313
|
}
|
|
421
314
|
catch (error) {
|
|
422
315
|
// destroy all streams with error
|
|
423
|
-
|
|
424
|
-
|
|
316
|
+
encFileWriteStream.destroy();
|
|
317
|
+
originalFileStream?.destroy?.();
|
|
425
318
|
aes.destroy();
|
|
426
319
|
hmac.destroy();
|
|
427
320
|
sha256Plain.destroy();
|
|
428
321
|
sha256Enc.destroy();
|
|
429
322
|
stream.destroy();
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
catch (err) {
|
|
435
|
-
logger === null || logger === void 0 ? void 0 : logger.error({ err }, 'failed to save to tmp path');
|
|
323
|
+
try {
|
|
324
|
+
await fs.unlink(encFilePath);
|
|
325
|
+
if (originalFilePath) {
|
|
326
|
+
await fs.unlink(originalFilePath);
|
|
436
327
|
}
|
|
437
328
|
}
|
|
329
|
+
catch (err) {
|
|
330
|
+
logger?.error({ err }, 'failed deleting tmp files');
|
|
331
|
+
}
|
|
438
332
|
throw error;
|
|
439
333
|
}
|
|
440
|
-
function onChunk(buff) {
|
|
441
|
-
sha256Enc = sha256Enc.update(buff);
|
|
442
|
-
hmac = hmac.update(buff);
|
|
443
|
-
encWriteStream.push(buff);
|
|
444
|
-
}
|
|
445
334
|
};
|
|
446
|
-
exports.encryptedStream = encryptedStream;
|
|
447
335
|
const DEF_HOST = 'mmg.whatsapp.net';
|
|
448
336
|
const AES_CHUNK_SIZE = 16;
|
|
449
337
|
const toSmallestChunkSize = (num) => {
|
|
450
338
|
return Math.floor(num / AES_CHUNK_SIZE) * AES_CHUNK_SIZE;
|
|
451
339
|
};
|
|
452
|
-
const getUrlFromDirectPath = (directPath) => `https://${DEF_HOST}${directPath}`;
|
|
453
|
-
|
|
454
|
-
const
|
|
455
|
-
const downloadUrl = url
|
|
340
|
+
export const getUrlFromDirectPath = (directPath) => `https://${DEF_HOST}${directPath}`;
|
|
341
|
+
export const downloadContentFromMessage = async ({ mediaKey, directPath, url }, type, opts = {}) => {
|
|
342
|
+
const isValidMediaUrl = url?.startsWith('https://mmg.whatsapp.net/');
|
|
343
|
+
const downloadUrl = isValidMediaUrl ? url : getUrlFromDirectPath(directPath);
|
|
344
|
+
if (!downloadUrl) {
|
|
345
|
+
throw new Boom('No valid media URL or directPath present in message', { statusCode: 400 });
|
|
346
|
+
}
|
|
456
347
|
const keys = await getMediaKeys(mediaKey, type);
|
|
457
|
-
return
|
|
348
|
+
return downloadEncryptedContent(downloadUrl, keys, opts);
|
|
458
349
|
};
|
|
459
|
-
exports.downloadContentFromMessage = downloadContentFromMessage;
|
|
460
350
|
/**
|
|
461
351
|
* Decrypts and downloads an AES256-CBC encrypted file given the keys.
|
|
462
352
|
* Assumes the SHA256 of the plaintext is appended to the end of the ciphertext
|
|
463
353
|
* */
|
|
464
|
-
const downloadEncryptedContent = async (downloadUrl, { cipherKey, iv }, { startByte, endByte, options } = {}) => {
|
|
354
|
+
export const downloadEncryptedContent = async (downloadUrl, { cipherKey, iv }, { startByte, endByte, options } = {}) => {
|
|
465
355
|
let bytesFetched = 0;
|
|
466
356
|
let startChunk = 0;
|
|
467
357
|
let firstBlockIsIV = false;
|
|
@@ -476,8 +366,8 @@ const downloadEncryptedContent = async (downloadUrl, { cipherKey, iv }, { startB
|
|
|
476
366
|
}
|
|
477
367
|
const endChunk = endByte ? toSmallestChunkSize(endByte || 0) + AES_CHUNK_SIZE : undefined;
|
|
478
368
|
const headers = {
|
|
479
|
-
...(options
|
|
480
|
-
Origin:
|
|
369
|
+
...(options?.headers || {}),
|
|
370
|
+
Origin: DEFAULT_ORIGIN
|
|
481
371
|
};
|
|
482
372
|
if (startChunk || endChunk) {
|
|
483
373
|
headers.Range = `bytes=${startChunk}-`;
|
|
@@ -486,11 +376,11 @@ const downloadEncryptedContent = async (downloadUrl, { cipherKey, iv }, { startB
|
|
|
486
376
|
}
|
|
487
377
|
}
|
|
488
378
|
// download the message
|
|
489
|
-
const fetched = await
|
|
490
|
-
...options || {},
|
|
379
|
+
const fetched = await getHttpStream(downloadUrl, {
|
|
380
|
+
...(options || {}),
|
|
491
381
|
headers,
|
|
492
382
|
maxBodyLength: Infinity,
|
|
493
|
-
maxContentLength: Infinity
|
|
383
|
+
maxContentLength: Infinity
|
|
494
384
|
});
|
|
495
385
|
let remainingBytes = Buffer.from([]);
|
|
496
386
|
let aes;
|
|
@@ -505,7 +395,7 @@ const downloadEncryptedContent = async (downloadUrl, { cipherKey, iv }, { startB
|
|
|
505
395
|
push(bytes);
|
|
506
396
|
}
|
|
507
397
|
};
|
|
508
|
-
const output = new
|
|
398
|
+
const output = new Transform({
|
|
509
399
|
transform(chunk, _, callback) {
|
|
510
400
|
let data = Buffer.concat([remainingBytes, chunk]);
|
|
511
401
|
const decryptLength = toSmallestChunkSize(data.length);
|
|
@@ -540,18 +430,15 @@ const downloadEncryptedContent = async (downloadUrl, { cipherKey, iv }, { startB
|
|
|
540
430
|
catch (error) {
|
|
541
431
|
callback(error);
|
|
542
432
|
}
|
|
543
|
-
}
|
|
433
|
+
}
|
|
544
434
|
});
|
|
545
435
|
return fetched.pipe(output, { end: true });
|
|
546
436
|
};
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
const getExtension = (mimetype) => mimetype.split(';')[0].split('/')[1];
|
|
437
|
+
export function extensionForMediaMessage(message) {
|
|
438
|
+
const getExtension = (mimetype) => mimetype.split(';')[0]?.split('/')[1];
|
|
550
439
|
const type = Object.keys(message)[0];
|
|
551
440
|
let extension;
|
|
552
|
-
if (type === 'locationMessage' ||
|
|
553
|
-
type === 'liveLocationMessage' ||
|
|
554
|
-
type === 'productMessage') {
|
|
441
|
+
if (type === 'locationMessage' || type === 'liveLocationMessage' || type === 'productMessage') {
|
|
555
442
|
extension = '.jpeg';
|
|
556
443
|
}
|
|
557
444
|
else {
|
|
@@ -560,53 +447,39 @@ function extensionForMediaMessage(message) {
|
|
|
560
447
|
}
|
|
561
448
|
return extension;
|
|
562
449
|
}
|
|
563
|
-
const getWAUploadToServer = ({ customUploadHosts, fetchAgent, logger, options }, refreshMediaConn) => {
|
|
564
|
-
return async (
|
|
565
|
-
var _a, _b;
|
|
450
|
+
export const getWAUploadToServer = ({ customUploadHosts, fetchAgent, logger, options }, refreshMediaConn) => {
|
|
451
|
+
return async (filePath, { mediaType, fileEncSha256B64, timeoutMs }) => {
|
|
566
452
|
// send a query JSON to obtain the url & auth token to upload our media
|
|
567
453
|
let uploadInfo = await refreshMediaConn(false);
|
|
568
454
|
let urls;
|
|
569
455
|
const hosts = [...customUploadHosts, ...uploadInfo.hosts];
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
for await (const chunk of stream) {
|
|
573
|
-
chunks.push(chunk);
|
|
574
|
-
}
|
|
575
|
-
}
|
|
576
|
-
const reqBody = Buffer.isBuffer(stream) ? stream : Buffer.concat(chunks);
|
|
577
|
-
fileEncSha256B64 = (0, exports.encodeBase64EncodedStringForUpload)(fileEncSha256B64);
|
|
578
|
-
let media = Defaults_1.MEDIA_PATH_MAP[mediaType];
|
|
579
|
-
if (newsletter) {
|
|
580
|
-
media = media === null || media === void 0 ? void 0 : media.replace('/mms/', '/newsletter/newsletter-');
|
|
581
|
-
}
|
|
582
|
-
for (const { hostname, maxContentLengthBytes } of hosts) {
|
|
456
|
+
fileEncSha256B64 = encodeBase64EncodedStringForUpload(fileEncSha256B64);
|
|
457
|
+
for (const { hostname } of hosts) {
|
|
583
458
|
logger.debug(`uploading to "${hostname}"`);
|
|
584
459
|
const auth = encodeURIComponent(uploadInfo.auth); // the auth token
|
|
585
|
-
const url = `https://${hostname}${
|
|
460
|
+
const url = `https://${hostname}${MEDIA_PATH_MAP[mediaType]}/${fileEncSha256B64}?auth=${auth}&token=${fileEncSha256B64}`;
|
|
461
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
586
462
|
let result;
|
|
587
463
|
try {
|
|
588
|
-
|
|
589
|
-
throw new boom_1.Boom(`Body too large for "${hostname}"`, { statusCode: 413 });
|
|
590
|
-
}
|
|
591
|
-
const body = await axios_1.default.post(url, reqBody, {
|
|
464
|
+
const body = await axios.post(url, createReadStream(filePath), {
|
|
592
465
|
...options,
|
|
466
|
+
maxRedirects: 0,
|
|
593
467
|
headers: {
|
|
594
|
-
...options.headers || {},
|
|
468
|
+
...(options.headers || {}),
|
|
595
469
|
'Content-Type': 'application/octet-stream',
|
|
596
|
-
|
|
470
|
+
Origin: DEFAULT_ORIGIN
|
|
597
471
|
},
|
|
598
472
|
httpsAgent: fetchAgent,
|
|
599
473
|
timeout: timeoutMs,
|
|
600
474
|
responseType: 'json',
|
|
601
475
|
maxBodyLength: Infinity,
|
|
602
|
-
maxContentLength: Infinity
|
|
476
|
+
maxContentLength: Infinity
|
|
603
477
|
});
|
|
604
478
|
result = body.data;
|
|
605
|
-
if (
|
|
479
|
+
if (result?.url || result?.directPath) {
|
|
606
480
|
urls = {
|
|
607
481
|
mediaUrl: result.url,
|
|
608
|
-
directPath: result.direct_path
|
|
609
|
-
handle: result.handle
|
|
482
|
+
directPath: result.direct_path
|
|
610
483
|
};
|
|
611
484
|
break;
|
|
612
485
|
}
|
|
@@ -616,37 +489,36 @@ const getWAUploadToServer = ({ customUploadHosts, fetchAgent, logger, options },
|
|
|
616
489
|
}
|
|
617
490
|
}
|
|
618
491
|
catch (error) {
|
|
619
|
-
if (
|
|
620
|
-
result =
|
|
492
|
+
if (axios.isAxiosError(error)) {
|
|
493
|
+
result = error.response?.data;
|
|
621
494
|
}
|
|
622
|
-
const isLast = hostname ===
|
|
495
|
+
const isLast = hostname === hosts[uploadInfo.hosts.length - 1]?.hostname;
|
|
623
496
|
logger.warn({ trace: error.stack, uploadResult: result }, `Error in uploading to ${hostname} ${isLast ? '' : ', retrying...'}`);
|
|
624
497
|
}
|
|
625
498
|
}
|
|
626
499
|
if (!urls) {
|
|
627
|
-
throw new
|
|
500
|
+
throw new Boom('Media upload failed on all hosts', { statusCode: 500 });
|
|
628
501
|
}
|
|
629
502
|
return urls;
|
|
630
503
|
};
|
|
631
504
|
};
|
|
632
|
-
exports.getWAUploadToServer = getWAUploadToServer;
|
|
633
505
|
const getMediaRetryKey = (mediaKey) => {
|
|
634
|
-
return
|
|
506
|
+
return hkdf(mediaKey, 32, { info: 'WhatsApp Media Retry Notification' });
|
|
635
507
|
};
|
|
636
508
|
/**
|
|
637
509
|
* Generate a binary node that will request the phone to re-upload the media & return the newly uploaded URL
|
|
638
510
|
*/
|
|
639
|
-
const encryptMediaRetryRequest = async (key, mediaKey, meId) => {
|
|
511
|
+
export const encryptMediaRetryRequest = async (key, mediaKey, meId) => {
|
|
640
512
|
const recp = { stanzaId: key.id };
|
|
641
|
-
const recpBuffer =
|
|
513
|
+
const recpBuffer = proto.ServerErrorReceipt.encode(recp).finish();
|
|
642
514
|
const iv = Crypto.randomBytes(12);
|
|
643
515
|
const retryKey = await getMediaRetryKey(mediaKey);
|
|
644
|
-
const ciphertext =
|
|
516
|
+
const ciphertext = aesEncryptGCM(recpBuffer, retryKey, iv, Buffer.from(key.id));
|
|
645
517
|
const req = {
|
|
646
518
|
tag: 'receipt',
|
|
647
519
|
attrs: {
|
|
648
520
|
id: key.id,
|
|
649
|
-
to:
|
|
521
|
+
to: jidNormalizedUser(meId),
|
|
650
522
|
type: 'server-error'
|
|
651
523
|
},
|
|
652
524
|
content: [
|
|
@@ -665,7 +537,7 @@ const encryptMediaRetryRequest = async (key, mediaKey, meId) => {
|
|
|
665
537
|
tag: 'rmr',
|
|
666
538
|
attrs: {
|
|
667
539
|
jid: key.remoteJid,
|
|
668
|
-
|
|
540
|
+
from_me: (!!key.fromMe).toString(),
|
|
669
541
|
// @ts-ignore
|
|
670
542
|
participant: key.participant || undefined
|
|
671
543
|
}
|
|
@@ -674,9 +546,8 @@ const encryptMediaRetryRequest = async (key, mediaKey, meId) => {
|
|
|
674
546
|
};
|
|
675
547
|
return req;
|
|
676
548
|
};
|
|
677
|
-
|
|
678
|
-
const
|
|
679
|
-
const rmrNode = (0, WABinary_1.getBinaryNodeChild)(node, 'rmr');
|
|
549
|
+
export const decodeMediaRetryNode = (node) => {
|
|
550
|
+
const rmrNode = getBinaryNodeChild(node, 'rmr');
|
|
680
551
|
const event = {
|
|
681
552
|
key: {
|
|
682
553
|
id: node.attrs.id,
|
|
@@ -685,40 +556,37 @@ const decodeMediaRetryNode = (node) => {
|
|
|
685
556
|
participant: rmrNode.attrs.participant
|
|
686
557
|
}
|
|
687
558
|
};
|
|
688
|
-
const errorNode =
|
|
559
|
+
const errorNode = getBinaryNodeChild(node, 'error');
|
|
689
560
|
if (errorNode) {
|
|
690
561
|
const errorCode = +errorNode.attrs.code;
|
|
691
|
-
event.error = new
|
|
562
|
+
event.error = new Boom(`Failed to re-upload media (${errorCode})`, {
|
|
563
|
+
data: errorNode.attrs,
|
|
564
|
+
statusCode: getStatusCodeForMediaRetry(errorCode)
|
|
565
|
+
});
|
|
692
566
|
}
|
|
693
567
|
else {
|
|
694
|
-
const encryptedInfoNode =
|
|
695
|
-
const ciphertext =
|
|
696
|
-
const iv =
|
|
568
|
+
const encryptedInfoNode = getBinaryNodeChild(node, 'encrypt');
|
|
569
|
+
const ciphertext = getBinaryNodeChildBuffer(encryptedInfoNode, 'enc_p');
|
|
570
|
+
const iv = getBinaryNodeChildBuffer(encryptedInfoNode, 'enc_iv');
|
|
697
571
|
if (ciphertext && iv) {
|
|
698
572
|
event.media = { ciphertext, iv };
|
|
699
573
|
}
|
|
700
574
|
else {
|
|
701
|
-
event.error = new
|
|
575
|
+
event.error = new Boom('Failed to re-upload media (missing ciphertext)', { statusCode: 404 });
|
|
702
576
|
}
|
|
703
577
|
}
|
|
704
578
|
return event;
|
|
705
579
|
};
|
|
706
|
-
|
|
707
|
-
const decryptMediaRetryData = async ({ ciphertext, iv }, mediaKey, msgId) => {
|
|
580
|
+
export const decryptMediaRetryData = async ({ ciphertext, iv }, mediaKey, msgId) => {
|
|
708
581
|
const retryKey = await getMediaRetryKey(mediaKey);
|
|
709
|
-
const plaintext =
|
|
710
|
-
return
|
|
582
|
+
const plaintext = aesDecryptGCM(ciphertext, retryKey, iv, Buffer.from(msgId));
|
|
583
|
+
return proto.MediaRetryNotification.decode(plaintext);
|
|
711
584
|
};
|
|
712
|
-
|
|
713
|
-
const getStatusCodeForMediaRetry = (code) => MEDIA_RETRY_STATUS_MAP[code];
|
|
714
|
-
exports.getStatusCodeForMediaRetry = getStatusCodeForMediaRetry;
|
|
585
|
+
export const getStatusCodeForMediaRetry = (code) => MEDIA_RETRY_STATUS_MAP[code];
|
|
715
586
|
const MEDIA_RETRY_STATUS_MAP = {
|
|
716
|
-
[
|
|
717
|
-
[
|
|
718
|
-
[
|
|
719
|
-
[
|
|
587
|
+
[proto.MediaRetryNotification.ResultType.SUCCESS]: 200,
|
|
588
|
+
[proto.MediaRetryNotification.ResultType.DECRYPTION_ERROR]: 412,
|
|
589
|
+
[proto.MediaRetryNotification.ResultType.NOT_FOUND]: 404,
|
|
590
|
+
[proto.MediaRetryNotification.ResultType.GENERAL_ERROR]: 418
|
|
720
591
|
};
|
|
721
|
-
|
|
722
|
-
function __importStar(arg0) {
|
|
723
|
-
throw new Error('Function not implemented.');
|
|
724
|
-
}
|
|
592
|
+
//# sourceMappingURL=messages-media.js.map
|