@baileys-md/baileys 11.2.4 → 12.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/README.md +113 -2
- package/WAProto/GenerateStatics.sh +4 -0
- package/WAProto/WAProto.proto +4775 -0
- package/WAProto/index.js +14270 -302
- package/lib/Defaults/index.js +50 -54
- package/lib/Defaults/wileys-version.json +3 -0
- package/lib/Signal/Group/ciphertext-message.js +15 -0
- package/lib/Signal/Group/group-session-builder.js +64 -0
- package/lib/Signal/Group/group_cipher.js +96 -0
- package/lib/Signal/Group/index.js +57 -0
- package/lib/Signal/Group/keyhelper.js +55 -0
- package/lib/Signal/Group/queue-job.js +57 -0
- package/lib/Signal/Group/sender-chain-key.js +34 -0
- package/lib/Signal/Group/sender-key-distribution-message.js +66 -0
- package/lib/Signal/Group/sender-key-message.js +69 -0
- package/lib/Signal/Group/sender-key-name.js +51 -0
- package/lib/Signal/Group/sender-key-record.js +53 -0
- package/lib/Signal/Group/sender-key-state.js +99 -0
- package/{WASignalGroup/sender_message_key.js → lib/Signal/Group/sender-message-key.js} +6 -16
- package/lib/Signal/libsignal.js +33 -20
- package/lib/Socket/Client/index.js +2 -3
- package/lib/Socket/Client/{web-socket-client.js → websocket.js} +54 -5
- package/lib/Socket/chats.js +136 -92
- package/lib/Socket/groups.js +16 -11
- package/lib/Socket/index.js +2 -2
- package/lib/Socket/messages-recv.js +26 -15
- package/lib/Socket/messages-send.js +122 -86
- package/lib/Socket/newsletter.js +23 -21
- package/lib/Socket/socket.js +29 -15
- package/lib/Store/make-in-memory-store.js +23 -15
- package/lib/Utils/auth-utils.js +0 -7
- package/lib/Utils/browser-utils.js +35 -0
- package/lib/Utils/chat-utils.js +2 -2
- package/lib/Utils/crypto.js +71 -29
- package/lib/Utils/decode-wa-message.js +15 -7
- package/lib/Utils/event-buffer.js +6 -8
- package/lib/Utils/generics.js +38 -16
- package/lib/Utils/history.js +1 -1
- package/lib/Utils/index.js +3 -1
- package/lib/Utils/message-retry-manager.js +128 -0
- package/lib/Utils/messages-media.js +212 -57
- package/lib/Utils/messages.js +38 -7
- package/lib/Utils/noise-handler.js +5 -10
- package/lib/Utils/process-message.js +34 -2
- package/lib/Utils/signal.js +26 -16
- package/lib/Utils/validate-connection.js +88 -66
- package/lib/Utils/{baileys-event-stream.js → wileys-event-stream.js} +1 -1
- package/lib/WABinary/constants.js +1276 -13
- package/lib/WABinary/jid-utils.js +20 -4
- package/lib/WAUSync/Protocols/USyncDisappearingModeProtocol.js +1 -1
- package/lib/WAUSync/Protocols/USyncStatusProtocol.js +3 -3
- package/lib/index.js +17 -6
- package/package.json +22 -17
- 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/mobile-socket-client.js +0 -65
- package/lib/Socket/registration.js +0 -166
- package/lib/Store/make-cache-manager-store.js +0 -83
- package/lib/Store/make-mongo-store.js +0 -567
- /package/lib/Socket/Client/{abstract-socket-client.js → types.js} +0 -0
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.MessageRetryManager = void 0;
|
|
4
|
+
|
|
5
|
+
const lru_cache_1 = require("lru-cache");
|
|
6
|
+
|
|
7
|
+
const RECENT_MESSAGES_SIZE = 512;
|
|
8
|
+
|
|
9
|
+
const RECREATE_SESSION_TIMEOUT = 60 * 60 * 1000;
|
|
10
|
+
|
|
11
|
+
const PHONE_REQUEST_DELAY = 3000;
|
|
12
|
+
|
|
13
|
+
class MessageRetryManager {
|
|
14
|
+
constructor(logger, maxMsgRetryCount) {
|
|
15
|
+
this.logger = logger;
|
|
16
|
+
this.maxMsgRetryCount = maxMsgRetryCount;
|
|
17
|
+
this._recentMessagesMap = new lru_cache_1.LRUCache({
|
|
18
|
+
max: RECENT_MESSAGES_SIZE
|
|
19
|
+
});
|
|
20
|
+
this._sessionRecreateHistory = new lru_cache_1.LRUCache({
|
|
21
|
+
ttl: RECREATE_SESSION_TIMEOUT * 2,
|
|
22
|
+
ttlAutopurge: true
|
|
23
|
+
});
|
|
24
|
+
this._retryCounters = new lru_cache_1.LRUCache({
|
|
25
|
+
ttl: 15 * 60 * 1000,
|
|
26
|
+
ttlAutopurge: true,
|
|
27
|
+
updateAgeOnGet: true
|
|
28
|
+
});
|
|
29
|
+
this._pendingPhoneRequests = {};
|
|
30
|
+
this.statistics = {
|
|
31
|
+
totalRetries: 0,
|
|
32
|
+
successfulRetries: 0,
|
|
33
|
+
failedRetries: 0,
|
|
34
|
+
mediaRetries: 0,
|
|
35
|
+
sessionRecreations: 0,
|
|
36
|
+
phoneRequests: 0
|
|
37
|
+
};
|
|
38
|
+
this.maxMsgRetryCount = maxMsgRetryCount;
|
|
39
|
+
}
|
|
40
|
+
addRecentMessage(to, id, message) {
|
|
41
|
+
const key = { to, id };
|
|
42
|
+
const keyStr = this._keyToString(key);
|
|
43
|
+
this._recentMessagesMap.set(keyStr, {
|
|
44
|
+
message,
|
|
45
|
+
timestamp: Date.now()
|
|
46
|
+
});
|
|
47
|
+
this.logger.debug(`Added message to retry cache: ${to}/${id}`);
|
|
48
|
+
}
|
|
49
|
+
getRecentMessage(to, id) {
|
|
50
|
+
const key = { to, id };
|
|
51
|
+
const keyStr = this._keyToString(key);
|
|
52
|
+
return this._recentMessagesMap.get(keyStr);
|
|
53
|
+
}
|
|
54
|
+
shouldRecreateSession(jid, retryCount, hasSession) {
|
|
55
|
+
if (!hasSession) {
|
|
56
|
+
this._sessionRecreateHistory.set(jid, Date.now());
|
|
57
|
+
this.statistics.sessionRecreations++;
|
|
58
|
+
return {
|
|
59
|
+
reason: "we don't have a Signal session with them",
|
|
60
|
+
recreate: true
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
if (retryCount < 2) {
|
|
64
|
+
return { reason: '', recreate: false };
|
|
65
|
+
}
|
|
66
|
+
const now = Date.now();
|
|
67
|
+
const prevTime = this._sessionRecreateHistory.get(jid);
|
|
68
|
+
if (!prevTime || now - prevTime > RECREATE_SESSION_TIMEOUT) {
|
|
69
|
+
this._sessionRecreateHistory.set(jid, now);
|
|
70
|
+
this.statistics.sessionRecreations++;
|
|
71
|
+
return {
|
|
72
|
+
reason: 'retry count > 1 and over an hour since last recreation',
|
|
73
|
+
recreate: true
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
return { reason: '', recreate: false };
|
|
77
|
+
}
|
|
78
|
+
incrementRetryCount(messageId) {
|
|
79
|
+
this._retryCounters.set(messageId, (this._retryCounters.get(messageId) || 0) + 1);
|
|
80
|
+
this.statistics.totalRetries++;
|
|
81
|
+
return this._retryCounters.get(messageId);
|
|
82
|
+
}
|
|
83
|
+
getRetryCount(messageId) {
|
|
84
|
+
return this._retryCounters.get(messageId) || 0;
|
|
85
|
+
}
|
|
86
|
+
hasExceededMaxRetries(messageId) {
|
|
87
|
+
return this.getRetryCount(messageId) >= this.maxMsgRetryCount;
|
|
88
|
+
}
|
|
89
|
+
markRetrySuccess(messageId) {
|
|
90
|
+
this.statistics.successfulRetries++;
|
|
91
|
+
this._retryCounters.delete(messageId);
|
|
92
|
+
this._cancelPendingPhoneRequest(messageId);
|
|
93
|
+
}
|
|
94
|
+
markRetryFailed(messageId) {
|
|
95
|
+
this.statistics.failedRetries++;
|
|
96
|
+
this._retryCounters.delete(messageId);
|
|
97
|
+
}
|
|
98
|
+
schedulePhoneRequest(messageId, callback, delay = PHONE_REQUEST_DELAY) {
|
|
99
|
+
this._cancelPendingPhoneRequest(messageId);
|
|
100
|
+
this._pendingPhoneRequests[messageId] = setTimeout(() => {
|
|
101
|
+
delete this._pendingPhoneRequests[messageId];
|
|
102
|
+
this.statistics.phoneRequests++;
|
|
103
|
+
callback();
|
|
104
|
+
}, delay);
|
|
105
|
+
this.logger.debug(`Scheduled phone request for message ${messageId} with ${delay}ms delay`);
|
|
106
|
+
}
|
|
107
|
+
cancelPendingPhoneRequest(messageId) {
|
|
108
|
+
const timeout = this._pendingPhoneRequests[messageId];
|
|
109
|
+
if (timeout) {
|
|
110
|
+
clearTimeout(timeout);
|
|
111
|
+
delete this._pendingPhoneRequests[messageId];
|
|
112
|
+
this.logger.debug(`Cancelled pending phone request for message ${messageId}`);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
_keyToString(key) {
|
|
116
|
+
return `${key.to}:${key.id}`;
|
|
117
|
+
}
|
|
118
|
+
_cancelPendingPhoneRequest(messageId) {
|
|
119
|
+
const timeout = this._pendingPhoneRequests[messageId];
|
|
120
|
+
if (timeout) {
|
|
121
|
+
clearTimeout(timeout);
|
|
122
|
+
delete this._pendingPhoneRequests[messageId];
|
|
123
|
+
this.logger.debug(`Cancelled pending phone request for message ${messageId}`);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
exports.MessageRetryManager = MessageRetryManager;
|
|
@@ -36,15 +36,18 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
36
36
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
37
|
};
|
|
38
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
-
exports.getStatusCodeForMediaRetry = exports.decryptMediaRetryData = exports.decodeMediaRetryNode = exports.encryptMediaRetryRequest = exports.getWAUploadToServer = exports.downloadEncryptedContent = exports.downloadContentFromMessage = exports.getUrlFromDirectPath = exports.encryptedStream = exports.prepareStream = exports.getHttpStream = exports.getStream = exports.toBuffer = exports.toReadable = exports.mediaMessageSHA256B64 = exports.generateProfilePicture = exports.encodeBase64EncodedStringForUpload = exports.extractImageThumb = exports.hkdfInfoKey = void 0;
|
|
39
|
+
exports.getStatusCodeForMediaRetry = exports.decryptMediaRetryData = exports.decodeMediaRetryNode = exports.encryptMediaRetryRequest = exports.getWAUploadToServer = exports.downloadEncryptedContent = exports.downloadContentFromMessage = exports.getUrlFromDirectPath = exports.encryptedStream = exports.prepareStream = exports.getHttpStream = exports.getStream = exports.toBuffer = exports.toReadable = exports.mediaMessageSHA256B64 = exports.generateProfilePicture = exports.encodeBase64EncodedStringForUpload = exports.extractImageThumb = exports.extractVideoThumb = exports.hkdfInfoKey = void 0;
|
|
40
40
|
exports.getMediaKeys = getMediaKeys;
|
|
41
|
+
exports.uploadFile = uploadFile;
|
|
42
|
+
exports.vid2jpg = vid2jpg;
|
|
41
43
|
exports.getAudioDuration = getAudioDuration;
|
|
42
44
|
exports.getAudioWaveform = getAudioWaveform;
|
|
43
45
|
exports.generateThumbnail = generateThumbnail;
|
|
44
46
|
exports.extensionForMediaMessage = extensionForMediaMessage;
|
|
45
47
|
const boom_1 = require("@hapi/boom");
|
|
46
48
|
const axios_1 = __importDefault(require("axios"));
|
|
47
|
-
const
|
|
49
|
+
const form_data_1 = __importDefault(require("form-data"));
|
|
50
|
+
const cheerio = __importStar(require("cheerio"));
|
|
48
51
|
const Crypto = __importStar(require("crypto"));
|
|
49
52
|
const events_1 = require("events");
|
|
50
53
|
const fs_1 = require("fs");
|
|
@@ -52,6 +55,7 @@ const os_1 = require("os");
|
|
|
52
55
|
const path_1 = require("path");
|
|
53
56
|
const jimp_1 = __importDefault(require("jimp"));
|
|
54
57
|
const stream_1 = require("stream");
|
|
58
|
+
const child_process_1 = require("child_process");
|
|
55
59
|
const WAProto_1 = require("../../WAProto");
|
|
56
60
|
const Defaults_1 = require("../Defaults");
|
|
57
61
|
const WABinary_1 = require("../WABinary");
|
|
@@ -99,18 +103,176 @@ async function getMediaKeys(buffer, mediaType) {
|
|
|
99
103
|
macKey: expandedMediaKey.slice(48, 80),
|
|
100
104
|
};
|
|
101
105
|
}
|
|
102
|
-
|
|
103
|
-
const
|
|
104
|
-
const
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
106
|
+
async function uploadFile(buffer, logger) {
|
|
107
|
+
const { fromBuffer } = await Promise.resolve().then(() => __importStar(require('file-type')));
|
|
108
|
+
const fileType = await fromBuffer(buffer);
|
|
109
|
+
if (!fileType)
|
|
110
|
+
throw new Error("Failed to detect file type.");
|
|
111
|
+
const { ext, mime } = fileType;
|
|
112
|
+
const services = [
|
|
113
|
+
{
|
|
114
|
+
name: "catbox",
|
|
115
|
+
url: "https://catbox.moe/user/api.php",
|
|
116
|
+
buildForm: () => {
|
|
117
|
+
const form = new form_data_1.default();
|
|
118
|
+
form.append("fileToUpload", buffer, {
|
|
119
|
+
filename: `file.${ext}`,
|
|
120
|
+
contentType: mime || "application/octet-stream"
|
|
121
|
+
});
|
|
122
|
+
form.append("reqtype", "fileupload");
|
|
123
|
+
return form;
|
|
124
|
+
},
|
|
125
|
+
parseResponse: res => res.data
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
name: "pdi.moe",
|
|
129
|
+
url: "https://scdn.pdi.moe/upload",
|
|
130
|
+
buildForm: () => {
|
|
131
|
+
const form = new form_data_1.default();
|
|
132
|
+
form.append("file", buffer, {
|
|
133
|
+
filename: `file.${ext}`,
|
|
134
|
+
contentType: mime
|
|
135
|
+
});
|
|
136
|
+
return form;
|
|
137
|
+
},
|
|
138
|
+
parseResponse: res => res.data.result.url
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
name: "qu.ax",
|
|
142
|
+
url: "https://qu.ax/upload.php",
|
|
143
|
+
buildForm: () => {
|
|
144
|
+
const form = new form_data_1.default();
|
|
145
|
+
form.append("files[]", buffer, {
|
|
146
|
+
filename: `file.${ext}`,
|
|
147
|
+
contentType: mime || "application/octet-stream"
|
|
148
|
+
});
|
|
149
|
+
return form;
|
|
150
|
+
},
|
|
151
|
+
parseResponse: res => {
|
|
152
|
+
var _a, _b, _c;
|
|
153
|
+
if (!((_c = (_b = (_a = res.data) === null || _a === void 0 ? void 0 : _a.files) === null || _b === void 0 ? void 0 : _b[0]) === null || _c === void 0 ? void 0 : _c.url))
|
|
154
|
+
throw new Error("Failed to get URL from qu.ax");
|
|
155
|
+
return res.data.files[0].url;
|
|
156
|
+
}
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
name: "uguu.se",
|
|
160
|
+
url: "https://uguu.se/upload.php",
|
|
161
|
+
buildForm: () => {
|
|
162
|
+
const form = new form_data_1.default();
|
|
163
|
+
form.append("files[]", buffer, {
|
|
164
|
+
filename: `file.${ext}`,
|
|
165
|
+
contentType: mime || "application/octet-stream"
|
|
166
|
+
});
|
|
167
|
+
return form;
|
|
168
|
+
},
|
|
169
|
+
parseResponse: res => {
|
|
170
|
+
var _a, _b, _c;
|
|
171
|
+
if (!((_c = (_b = (_a = res.data) === null || _a === void 0 ? void 0 : _a.files) === null || _b === void 0 ? void 0 : _b[0]) === null || _c === void 0 ? void 0 : _c.url))
|
|
172
|
+
throw new Error("Failed to get URL from uguu.se");
|
|
173
|
+
return res.data.files[0].url;
|
|
174
|
+
}
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
name: "tmpfiles",
|
|
178
|
+
url: "https://tmpfiles.org/api/v1/upload",
|
|
179
|
+
buildForm: () => {
|
|
180
|
+
const form = new form_data_1.default();
|
|
181
|
+
form.append("file", buffer, {
|
|
182
|
+
filename: `file.${ext}`,
|
|
183
|
+
contentType: mime
|
|
184
|
+
});
|
|
185
|
+
return form;
|
|
186
|
+
},
|
|
187
|
+
parseResponse: res => {
|
|
188
|
+
const match = res.data.data.url.match(/https:\/\/tmpfiles\.org\/(.*)/);
|
|
189
|
+
if (!match)
|
|
190
|
+
throw new Error("Failed to parse tmpfiles URL.");
|
|
191
|
+
return `https://tmpfiles.org/dl/${match[1]}`;
|
|
192
|
+
}
|
|
108
193
|
}
|
|
109
|
-
|
|
110
|
-
|
|
194
|
+
];
|
|
195
|
+
for (const service of services) {
|
|
196
|
+
try {
|
|
197
|
+
const form = service.buildForm();
|
|
198
|
+
const res = await axios_1.default.post(service.url, form, {
|
|
199
|
+
headers: form.getHeaders()
|
|
200
|
+
});
|
|
201
|
+
const url = service.parseResponse(res);
|
|
202
|
+
return url;
|
|
203
|
+
}
|
|
204
|
+
catch (error) {
|
|
205
|
+
logger === null || logger === void 0 ? void 0 : logger.debug(`[${service.name}] eror:`, (error === null || error === void 0 ? void 0 : error.message) || error);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
throw new Error("All upload services failed.");
|
|
209
|
+
}
|
|
210
|
+
async function vid2jpg(videoUrl) {
|
|
211
|
+
try {
|
|
212
|
+
const { data } = await axios_1.default.get(`https://ezgif.com/video-to-jpg?url=${encodeURIComponent(videoUrl)}`);
|
|
213
|
+
const $ = cheerio.load(data);
|
|
214
|
+
const fileToken = $('input[name="file"]').attr("value");
|
|
215
|
+
if (!fileToken) {
|
|
216
|
+
throw new Error("Failed to retrieve file token. The video URL may be invalid or inaccessible.");
|
|
217
|
+
}
|
|
218
|
+
const formData = new URLSearchParams();
|
|
219
|
+
formData.append("file", fileToken);
|
|
220
|
+
formData.append("end", "1");
|
|
221
|
+
formData.append("video-to-jpg", "Convert to JPG!");
|
|
222
|
+
const convert = await axios_1.default.post(`https://ezgif.com/video-to-jpg/${fileToken}`, formData);
|
|
223
|
+
const $2 = cheerio.load(convert.data);
|
|
224
|
+
let imageUrl = $2("#output img").first().attr("src");
|
|
225
|
+
if (!imageUrl) {
|
|
226
|
+
throw new Error("Could not locate the converted image output.");
|
|
111
227
|
}
|
|
228
|
+
if (imageUrl.startsWith("//")) {
|
|
229
|
+
imageUrl = "https:" + imageUrl;
|
|
230
|
+
}
|
|
231
|
+
else if (imageUrl.startsWith("/")) {
|
|
232
|
+
const cdnMatch = imageUrl.match(/\/(s\d+\..+?)\/.*/);
|
|
233
|
+
if (cdnMatch) {
|
|
234
|
+
imageUrl = "https://" + imageUrl.slice(2);
|
|
235
|
+
}
|
|
236
|
+
else {
|
|
237
|
+
imageUrl = "https://ezgif.com" + imageUrl;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
return imageUrl;
|
|
241
|
+
}
|
|
242
|
+
catch (error) {
|
|
243
|
+
throw new Error("Failed to convert video to JPG: " + error.message);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Extracts video thumbnail using FFmpeg
|
|
248
|
+
*/
|
|
249
|
+
const extractVideoThumb = async (videoPath, time = '00:00:00', size = { width: 256 }) => {
|
|
250
|
+
return new Promise((resolve, reject) => {
|
|
251
|
+
const args = [
|
|
252
|
+
'-ss', time,
|
|
253
|
+
'-i', videoPath,
|
|
254
|
+
'-y',
|
|
255
|
+
'-vf', `scale=${size.width}:-1`,
|
|
256
|
+
'-vframes', '1',
|
|
257
|
+
'-f', 'image2',
|
|
258
|
+
'-vcodec', 'mjpeg',
|
|
259
|
+
'pipe:1'
|
|
260
|
+
];
|
|
261
|
+
const ffmpeg = (0, child_process_1.spawn)('ffmpeg', args);
|
|
262
|
+
const chunks = [];
|
|
263
|
+
let errorOutput = '';
|
|
264
|
+
ffmpeg.stdout.on('data', chunk => chunks.push(chunk));
|
|
265
|
+
ffmpeg.stderr.on('data', data => {
|
|
266
|
+
errorOutput += data.toString();
|
|
267
|
+
});
|
|
268
|
+
ffmpeg.on('error', reject);
|
|
269
|
+
ffmpeg.on('close', code => {
|
|
270
|
+
if (code === 0) return resolve(Buffer.concat(chunks));
|
|
271
|
+
reject(new Error(`ffmpeg exited with code ${code}\n${errorOutput}`));
|
|
272
|
+
});
|
|
112
273
|
});
|
|
113
|
-
}
|
|
274
|
+
};
|
|
275
|
+
exports.extractVideoThumb = extractVideoThumb;
|
|
114
276
|
const extractImageThumb = async (bufferOrFilePath, width = 32) => {
|
|
115
277
|
var _a, _b;
|
|
116
278
|
if (bufferOrFilePath instanceof stream_1.Readable) {
|
|
@@ -189,26 +351,20 @@ exports.mediaMessageSHA256B64 = mediaMessageSHA256B64;
|
|
|
189
351
|
async function getAudioDuration(buffer) {
|
|
190
352
|
const musicMetadata = await Promise.resolve().then(() => __importStar(require('music-metadata')));
|
|
191
353
|
let metadata;
|
|
354
|
+
const options = {
|
|
355
|
+
duration: true
|
|
356
|
+
};
|
|
192
357
|
if (Buffer.isBuffer(buffer)) {
|
|
193
|
-
metadata = await musicMetadata.parseBuffer(buffer, undefined,
|
|
358
|
+
metadata = await musicMetadata.parseBuffer(buffer, undefined, options);
|
|
194
359
|
}
|
|
195
360
|
else if (typeof buffer === 'string') {
|
|
196
|
-
|
|
197
|
-
try {
|
|
198
|
-
metadata = await musicMetadata.parseStream(rStream, undefined, { duration: true });
|
|
199
|
-
}
|
|
200
|
-
finally {
|
|
201
|
-
rStream.destroy();
|
|
202
|
-
}
|
|
361
|
+
metadata = await musicMetadata.parseFile(buffer, options);
|
|
203
362
|
}
|
|
204
363
|
else {
|
|
205
|
-
metadata = await musicMetadata.parseStream(buffer, undefined,
|
|
364
|
+
metadata = await musicMetadata.parseStream(buffer, undefined, options);
|
|
206
365
|
}
|
|
207
366
|
return metadata.format.duration;
|
|
208
367
|
}
|
|
209
|
-
/**
|
|
210
|
-
referenced from and modifying https://github.com/wppconnect-team/wa-js/blob/main/src/chat/functions/prepareAudioWaveform.ts
|
|
211
|
-
*/
|
|
212
368
|
async function getAudioWaveform(buffer, logger) {
|
|
213
369
|
try {
|
|
214
370
|
const { default: decoder } = await eval('import(\'audio-decode\')');
|
|
@@ -224,22 +380,20 @@ async function getAudioWaveform(buffer, logger) {
|
|
|
224
380
|
audioData = await (0, exports.toBuffer)(buffer);
|
|
225
381
|
}
|
|
226
382
|
const audioBuffer = await decoder(audioData);
|
|
227
|
-
const rawData = audioBuffer.getChannelData(0);
|
|
228
|
-
const samples = 64;
|
|
229
|
-
const blockSize = Math.floor(rawData.length / samples);
|
|
383
|
+
const rawData = audioBuffer.getChannelData(0);
|
|
384
|
+
const samples = 64;
|
|
385
|
+
const blockSize = Math.floor(rawData.length / samples);
|
|
230
386
|
const filteredData = [];
|
|
231
387
|
for (let i = 0; i < samples; i++) {
|
|
232
|
-
const blockStart = blockSize * i;
|
|
388
|
+
const blockStart = blockSize * i;
|
|
233
389
|
let sum = 0;
|
|
234
390
|
for (let j = 0; j < blockSize; j++) {
|
|
235
|
-
sum = sum + Math.abs(rawData[blockStart + j]);
|
|
391
|
+
sum = sum + Math.abs(rawData[blockStart + j]);
|
|
236
392
|
}
|
|
237
|
-
filteredData.push(sum / blockSize);
|
|
393
|
+
filteredData.push(sum / blockSize);
|
|
238
394
|
}
|
|
239
|
-
// This guarantees that the largest data point will be set to 1, and the rest of the data will scale proportionally.
|
|
240
395
|
const multiplier = Math.pow(Math.max(...filteredData), -1);
|
|
241
396
|
const normalizedData = filteredData.map((n) => n * multiplier);
|
|
242
|
-
// Generate waveform like WhatsApp
|
|
243
397
|
const waveform = new Uint8Array(normalizedData.map((n) => Math.floor(100 * n)));
|
|
244
398
|
return waveform;
|
|
245
399
|
}
|
|
@@ -292,12 +446,28 @@ async function generateThumbnail(file, mediaType, options) {
|
|
|
292
446
|
}
|
|
293
447
|
}
|
|
294
448
|
else if (mediaType === 'video') {
|
|
295
|
-
const imgFilename = (0, path_1.join)(getTmpFilesDirectory(), (0, generics_1.generateMessageIDV2)() + '.jpg');
|
|
296
449
|
try {
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
450
|
+
let videoPath = file;
|
|
451
|
+
if (Buffer.isBuffer(file) || file instanceof stream_1.Readable) {
|
|
452
|
+
videoPath = (0, path_1.join)(getTmpFilesDirectory(), (0, generics_1.generateMessageIDV2)() + '.mp4');
|
|
453
|
+
const buffer = Buffer.isBuffer(file) ? file : await (0, exports.toBuffer)(file);
|
|
454
|
+
await fs_1.promises.writeFile(videoPath, buffer);
|
|
455
|
+
}
|
|
456
|
+
const thumbnailBuffer = await (0, exports.extractVideoThumb)(videoPath);
|
|
457
|
+
const imgFilename = (0, path_1.join)(getTmpFilesDirectory(), (0, generics_1.generateMessageIDV2)() + '.jpg');
|
|
458
|
+
await fs_1.promises.writeFile(imgFilename, thumbnailBuffer);
|
|
459
|
+
const { buffer: processedThumbnailBuffer, original } = await (0, exports.extractImageThumb)(imgFilename);
|
|
460
|
+
thumbnail = processedThumbnailBuffer.toString('base64');
|
|
461
|
+
if (original.width && original.height) {
|
|
462
|
+
originalImageDimensions = {
|
|
463
|
+
width: original.width,
|
|
464
|
+
height: original.height,
|
|
465
|
+
};
|
|
466
|
+
}
|
|
300
467
|
await fs_1.promises.unlink(imgFilename);
|
|
468
|
+
if (videoPath !== file) {
|
|
469
|
+
await fs_1.promises.unlink(videoPath);
|
|
470
|
+
}
|
|
301
471
|
}
|
|
302
472
|
catch (err) {
|
|
303
473
|
(_a = options.logger) === null || _a === void 0 ? void 0 : _a.debug('could not generate video thumb: ' + err);
|
|
@@ -343,7 +513,6 @@ const prepareStream = async (media, mediaType, { logger, saveOriginalFileIfRequi
|
|
|
343
513
|
};
|
|
344
514
|
}
|
|
345
515
|
catch (error) {
|
|
346
|
-
// destroy all streams with error
|
|
347
516
|
stream.destroy();
|
|
348
517
|
if (didSaveToTmpPath) {
|
|
349
518
|
try {
|
|
@@ -419,7 +588,6 @@ const encryptedStream = async (media, mediaType, { logger, saveOriginalFileIfReq
|
|
|
419
588
|
};
|
|
420
589
|
}
|
|
421
590
|
catch (error) {
|
|
422
|
-
// destroy all streams with error
|
|
423
591
|
encWriteStream.destroy();
|
|
424
592
|
writeStream === null || writeStream === void 0 ? void 0 : writeStream.destroy();
|
|
425
593
|
aes.destroy();
|
|
@@ -452,20 +620,19 @@ const toSmallestChunkSize = (num) => {
|
|
|
452
620
|
const getUrlFromDirectPath = (directPath) => `https://${DEF_HOST}${directPath}`;
|
|
453
621
|
exports.getUrlFromDirectPath = getUrlFromDirectPath;
|
|
454
622
|
const downloadContentFromMessage = async ({ mediaKey, directPath, url }, type, opts = {}) => {
|
|
455
|
-
const
|
|
623
|
+
const isValidMediaUrl = url === null || url === void 0 ? void 0 : url.startsWith('https://mmg.whatsapp.net/');
|
|
624
|
+
const downloadUrl = isValidMediaUrl ? url : (0, exports.getUrlFromDirectPath)(directPath);
|
|
625
|
+
if (!downloadUrl) {
|
|
626
|
+
throw new boom_1.Boom('No valid media URL or directPath present in message', { statusCode: 400 });
|
|
627
|
+
}
|
|
456
628
|
const keys = await getMediaKeys(mediaKey, type);
|
|
457
629
|
return (0, exports.downloadEncryptedContent)(downloadUrl, keys, opts);
|
|
458
630
|
};
|
|
459
631
|
exports.downloadContentFromMessage = downloadContentFromMessage;
|
|
460
|
-
/**
|
|
461
|
-
* Decrypts and downloads an AES256-CBC encrypted file given the keys.
|
|
462
|
-
* Assumes the SHA256 of the plaintext is appended to the end of the ciphertext
|
|
463
|
-
* */
|
|
464
632
|
const downloadEncryptedContent = async (downloadUrl, { cipherKey, iv }, { startByte, endByte, options } = {}) => {
|
|
465
633
|
let bytesFetched = 0;
|
|
466
634
|
let startChunk = 0;
|
|
467
635
|
let firstBlockIsIV = false;
|
|
468
|
-
// if a start byte is specified -- then we need to fetch the previous chunk as that will form the IV
|
|
469
636
|
if (startByte) {
|
|
470
637
|
const chunk = toSmallestChunkSize(startByte || 0);
|
|
471
638
|
if (chunk) {
|
|
@@ -485,7 +652,6 @@ const downloadEncryptedContent = async (downloadUrl, { cipherKey, iv }, { startB
|
|
|
485
652
|
headers.Range += endChunk;
|
|
486
653
|
}
|
|
487
654
|
}
|
|
488
|
-
// download the message
|
|
489
655
|
const fetched = await (0, exports.getHttpStream)(downloadUrl, {
|
|
490
656
|
...options || {},
|
|
491
657
|
headers,
|
|
@@ -518,8 +684,6 @@ const downloadEncryptedContent = async (downloadUrl, { cipherKey, iv }, { startB
|
|
|
518
684
|
data = data.slice(AES_CHUNK_SIZE);
|
|
519
685
|
}
|
|
520
686
|
aes = Crypto.createDecipheriv('aes-256-cbc', cipherKey, ivValue);
|
|
521
|
-
// if an end byte that is not EOF is specified
|
|
522
|
-
// stop auto padding (PKCS7) -- otherwise throws an error for decryption
|
|
523
687
|
if (endByte) {
|
|
524
688
|
aes.setAutoPadding(false);
|
|
525
689
|
}
|
|
@@ -563,7 +727,6 @@ function extensionForMediaMessage(message) {
|
|
|
563
727
|
const getWAUploadToServer = ({ customUploadHosts, fetchAgent, logger, options }, refreshMediaConn) => {
|
|
564
728
|
return async (stream, { mediaType, fileEncSha256B64, newsletter, timeoutMs }) => {
|
|
565
729
|
var _a, _b;
|
|
566
|
-
// send a query JSON to obtain the url & auth token to upload our media
|
|
567
730
|
let uploadInfo = await refreshMediaConn(false);
|
|
568
731
|
let urls;
|
|
569
732
|
const hosts = [...customUploadHosts, ...uploadInfo.hosts];
|
|
@@ -581,7 +744,7 @@ const getWAUploadToServer = ({ customUploadHosts, fetchAgent, logger, options },
|
|
|
581
744
|
}
|
|
582
745
|
for (const { hostname, maxContentLengthBytes } of hosts) {
|
|
583
746
|
logger.debug(`uploading to "${hostname}"`);
|
|
584
|
-
const auth = encodeURIComponent(uploadInfo.auth);
|
|
747
|
+
const auth = encodeURIComponent(uploadInfo.auth);
|
|
585
748
|
const url = `https://${hostname}${media}/${fileEncSha256B64}?auth=${auth}&token=${fileEncSha256B64}`;
|
|
586
749
|
let result;
|
|
587
750
|
try {
|
|
@@ -633,9 +796,6 @@ exports.getWAUploadToServer = getWAUploadToServer;
|
|
|
633
796
|
const getMediaRetryKey = (mediaKey) => {
|
|
634
797
|
return (0, crypto_1.hkdf)(mediaKey, 32, { info: 'WhatsApp Media Retry Notification' });
|
|
635
798
|
};
|
|
636
|
-
/**
|
|
637
|
-
* Generate a binary node that will request the phone to re-upload the media & return the newly uploaded URL
|
|
638
|
-
*/
|
|
639
799
|
const encryptMediaRetryRequest = async (key, mediaKey, meId) => {
|
|
640
800
|
const recp = { stanzaId: key.id };
|
|
641
801
|
const recpBuffer = WAProto_1.proto.ServerErrorReceipt.encode(recp).finish();
|
|
@@ -650,9 +810,6 @@ const encryptMediaRetryRequest = async (key, mediaKey, meId) => {
|
|
|
650
810
|
type: 'server-error'
|
|
651
811
|
},
|
|
652
812
|
content: [
|
|
653
|
-
// this encrypt node is actually pretty useless
|
|
654
|
-
// the media is returned even without this node
|
|
655
|
-
// keeping it here to maintain parity with WA Web
|
|
656
813
|
{
|
|
657
814
|
tag: 'encrypt',
|
|
658
815
|
attrs: {},
|
|
@@ -666,7 +823,6 @@ const encryptMediaRetryRequest = async (key, mediaKey, meId) => {
|
|
|
666
823
|
attrs: {
|
|
667
824
|
jid: key.remoteJid,
|
|
668
825
|
'from_me': (!!key.fromMe).toString(),
|
|
669
|
-
// @ts-ignore
|
|
670
826
|
participant: key.participant || undefined
|
|
671
827
|
}
|
|
672
828
|
}
|
|
@@ -718,7 +874,6 @@ const MEDIA_RETRY_STATUS_MAP = {
|
|
|
718
874
|
[WAProto_1.proto.MediaRetryNotification.ResultType.NOT_FOUND]: 404,
|
|
719
875
|
[WAProto_1.proto.MediaRetryNotification.ResultType.GENERAL_ERROR]: 418,
|
|
720
876
|
};
|
|
721
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
722
877
|
function __importStar(arg0) {
|
|
723
878
|
throw new Error('Function not implemented.');
|
|
724
|
-
}
|
|
879
|
+
}
|
package/lib/Utils/messages.js
CHANGED
|
@@ -434,12 +434,12 @@ const generateWAMessageContent = async (message, options) => {
|
|
|
434
434
|
m.pollCreationMessageV2 = pollCreationMessage;
|
|
435
435
|
}
|
|
436
436
|
else {
|
|
437
|
-
if (message.poll.selectableCount
|
|
437
|
+
if (message.poll.selectableCount === 1) {
|
|
438
438
|
// poll v3 is for single select polls
|
|
439
439
|
m.pollCreationMessageV3 = pollCreationMessage;
|
|
440
440
|
}
|
|
441
441
|
else {
|
|
442
|
-
// poll
|
|
442
|
+
// poll for multiple choice polls
|
|
443
443
|
m.pollCreationMessage = pollCreationMessage;
|
|
444
444
|
}
|
|
445
445
|
}
|
|
@@ -499,6 +499,14 @@ const generateWAMessageContent = async (message, options) => {
|
|
|
499
499
|
else if ('requestPhoneNumber' in message) {
|
|
500
500
|
m.requestPhoneNumberMessage = {};
|
|
501
501
|
}
|
|
502
|
+
else if ('album' in message) {
|
|
503
|
+
const imageMessages = message.album.filter(item => 'image' in item);
|
|
504
|
+
const videoMessages = message.album.filter(item => 'video' in item);
|
|
505
|
+
m.albumMessage = WAProto_1.proto.Message.AlbumMessage.fromObject({
|
|
506
|
+
expectedImageCount: imageMessages.length,
|
|
507
|
+
expectedVideoCount: videoMessages.length,
|
|
508
|
+
});
|
|
509
|
+
}
|
|
502
510
|
else {
|
|
503
511
|
m = await (0, exports.prepareWAMessageMedia)(message, options);
|
|
504
512
|
}
|
|
@@ -563,7 +571,7 @@ const generateWAMessageContent = async (message, options) => {
|
|
|
563
571
|
title: message.title,
|
|
564
572
|
footerText: message.footer,
|
|
565
573
|
description: message.text,
|
|
566
|
-
listType:
|
|
574
|
+
listType: WAProto_1.proto.Message.ListMessage.ListType.SINGLE_SELECT
|
|
567
575
|
};
|
|
568
576
|
m = { listMessage };
|
|
569
577
|
}
|
|
@@ -798,7 +806,12 @@ const normalizeMessageContent = (content) => {
|
|
|
798
806
|
|| (message === null || message === void 0 ? void 0 : message.pollCreationMessageV4)
|
|
799
807
|
|| (message === null || message === void 0 ? void 0 : message.pollCreationMessageV5)
|
|
800
808
|
|| (message === null || message === void 0 ? void 0 : message.statusAddYours)
|
|
801
|
-
|| (message === null || message === void 0 ? void 0 : message.groupStatusMessage)
|
|
809
|
+
|| (message === null || message === void 0 ? void 0 : message.groupStatusMessage)
|
|
810
|
+
|| (message === null || message === void 0 ? void 0 : message.limitSharingMessage)
|
|
811
|
+
|| (message === null || message === void 0 ? void 0 : message.botTaskMessage)
|
|
812
|
+
|| (message === null || message === void 0 ? void 0 : message.questionMessage)
|
|
813
|
+
|| (message === null || message === void 0 ? void 0 : message.groupStatusMessageV2)
|
|
814
|
+
|| (message === null || message === void 0 ? void 0 : message.botForwardedMessage));
|
|
802
815
|
}
|
|
803
816
|
};
|
|
804
817
|
exports.normalizeMessageContent = normalizeMessageContent;
|
|
@@ -871,9 +884,8 @@ const updateMessageWithReaction = (msg, reaction) => {
|
|
|
871
884
|
const authorID = (0, generics_1.getKeyAuthor)(reaction.key);
|
|
872
885
|
const reactions = (msg.reactions || [])
|
|
873
886
|
.filter(r => (0, generics_1.getKeyAuthor)(r.key) !== authorID);
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
}
|
|
887
|
+
reaction.text = reaction.text || '';
|
|
888
|
+
reactions.push(reaction);
|
|
877
889
|
msg.reactions = reactions;
|
|
878
890
|
};
|
|
879
891
|
exports.updateMessageWithReaction = updateMessageWithReaction;
|
|
@@ -1016,3 +1028,22 @@ const assertMediaContent = (content) => {
|
|
|
1016
1028
|
return mediaContent;
|
|
1017
1029
|
};
|
|
1018
1030
|
exports.assertMediaContent = assertMediaContent;
|
|
1031
|
+
|
|
1032
|
+
const toJid = (id) => {
|
|
1033
|
+
if (!id)
|
|
1034
|
+
return '';
|
|
1035
|
+
if (id.endsWith('@lid'))
|
|
1036
|
+
return id.replace('@lid', '@s.whatsapp.net');
|
|
1037
|
+
if (id.includes('@'))
|
|
1038
|
+
return id;
|
|
1039
|
+
return `${id}@s.whatsapp.net`;
|
|
1040
|
+
};
|
|
1041
|
+
exports.toJid = toJid;
|
|
1042
|
+
const getSenderLid = (message) => {
|
|
1043
|
+
const sender = message.key.participant || message.key.remoteJid;
|
|
1044
|
+
const user = (0, WABinary_1.jidDecode)(sender)?.user || '';
|
|
1045
|
+
const lid = (0, WABinary_1.jidEncode)(user, 'lid');
|
|
1046
|
+
console.log('sender lid:', lid);
|
|
1047
|
+
return { jid: sender, lid };
|
|
1048
|
+
};
|
|
1049
|
+
exports.getSenderLid = getSenderLid;
|
|
@@ -11,7 +11,7 @@ const generateIV = (counter) => {
|
|
|
11
11
|
new DataView(iv).setUint32(8, counter);
|
|
12
12
|
return new Uint8Array(iv);
|
|
13
13
|
};
|
|
14
|
-
const makeNoiseHandler = ({ keyPair: { private: privateKey, public: publicKey }, NOISE_HEADER,
|
|
14
|
+
const makeNoiseHandler = ({ keyPair: { private: privateKey, public: publicKey }, NOISE_HEADER, logger, routingInfo }) => {
|
|
15
15
|
logger = logger.child({ class: 'ns' });
|
|
16
16
|
const authenticate = (data) => {
|
|
17
17
|
if (!isFinished) {
|
|
@@ -83,15 +83,10 @@ const makeNoiseHandler = ({ keyPair: { private: privateKey, public: publicKey },
|
|
|
83
83
|
const decStaticContent = decrypt(serverHello.static);
|
|
84
84
|
await mixIntoKey(crypto_1.Curve.sharedKey(privateKey, decStaticContent));
|
|
85
85
|
const certDecoded = decrypt(serverHello.payload);
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
const { intermediate: certIntermediate } = WAProto_1.proto.CertChain.decode(certDecoded);
|
|
91
|
-
const { issuerSerial } = WAProto_1.proto.CertChain.NoiseCertificate.Details.decode(certIntermediate.details);
|
|
92
|
-
if (issuerSerial !== Defaults_1.WA_CERT_DETAILS.SERIAL) {
|
|
93
|
-
throw new boom_1.Boom('certification match failed', { statusCode: 400 });
|
|
94
|
-
}
|
|
86
|
+
const { intermediate: certIntermediate } = WAProto_1.proto.CertChain.decode(certDecoded);
|
|
87
|
+
const { issuerSerial } = WAProto_1.proto.CertChain.NoiseCertificate.Details.decode(certIntermediate.details);
|
|
88
|
+
if (issuerSerial !== Defaults_1.WA_CERT_DETAILS.SERIAL) {
|
|
89
|
+
throw new boom_1.Boom('certification match failed', { statusCode: 400 });
|
|
95
90
|
}
|
|
96
91
|
const keyEnc = encrypt(noiseKey.public);
|
|
97
92
|
await mixIntoKey(crypto_1.Curve.sharedKey(noiseKey.private, serverHello.ephemeral));
|