amiudmodz 4.1.2 → 5.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/lib/Socket/Client/web-socket-client.js +7 -6
- package/lib/Socket/chats.js +50 -77
- package/lib/Socket/groups.js +8 -5
- package/lib/Socket/messages-recv.js +26 -10
- package/lib/Socket/messages-send.js +3 -2
- package/lib/Socket/socket.js +9 -4
- package/lib/Utils/crypto.js +10 -2
- package/lib/Utils/event-buffer.js +5 -5
- package/lib/Utils/make-mutex.js +21 -12
- package/lib/Utils/messages-media.js +15 -10
- package/lib/Utils/messages.js +6 -2
- package/lib/Utils/noise-handler.js +8 -3
- package/lib/Utils/process-message.js +30 -22
- package/lib/Utils/use-multi-file-auth-state.js +10 -5
- package/lib/WABinary/decode.js +21 -11
- package/lib/WABinary/encode.js +8 -8
- package/package.json +1 -1
|
@@ -22,7 +22,8 @@ class WebSocketClient extends abstract_socket_client_1.AbstractSocketClient {
|
|
|
22
22
|
}
|
|
23
23
|
get isClosing() {
|
|
24
24
|
var _a;
|
|
25
|
-
|
|
25
|
+
// Only report closing when socket exists and is in CLOSING state
|
|
26
|
+
return this.socket !== null && ((_a = this.socket) === null || _a === void 0 ? void 0 : _a.readyState) === ws_1.default.CLOSING;
|
|
26
27
|
}
|
|
27
28
|
get isConnecting() {
|
|
28
29
|
var _a;
|
|
@@ -53,15 +54,15 @@ class WebSocketClient extends abstract_socket_client_1.AbstractSocketClient {
|
|
|
53
54
|
if (!this.socket) {
|
|
54
55
|
return;
|
|
55
56
|
}
|
|
57
|
+
const socketToClose = this.socket;
|
|
58
|
+
this.socket = null; // Immediately null to prevent double-close
|
|
56
59
|
const closePromise = new Promise((resolve) => {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
+
socketToClose.once('close', resolve);
|
|
61
|
+
// Fallback timeout in case 'close' event never fires
|
|
60
62
|
setTimeout(resolve, 5000);
|
|
61
63
|
});
|
|
62
|
-
|
|
64
|
+
socketToClose.close();
|
|
63
65
|
await closePromise;
|
|
64
|
-
this.socket = null;
|
|
65
66
|
}
|
|
66
67
|
send(str, cb) {
|
|
67
68
|
var _a;
|
package/lib/Socket/chats.js
CHANGED
|
@@ -15,6 +15,8 @@ const WABinary_1 = require("../WABinary");
|
|
|
15
15
|
const socket_1 = require("./socket");
|
|
16
16
|
const WAUSync_1 = require("../WAUSync");
|
|
17
17
|
const usync_1 = require("./usync");
|
|
18
|
+
const pino_1 = __importDefault(require("pino"));
|
|
19
|
+
const libphonenumber_js_1 = require("libphonenumber-js");
|
|
18
20
|
const MAX_SYNC_ATTEMPTS = 2;
|
|
19
21
|
const makeChatsSocket = (config) => {
|
|
20
22
|
const { logger, markOnlineOnConnect, fireInitQueries, appStateMacVerification, shouldIgnoreJid, shouldSyncHistoryMessage, } = config;
|
|
@@ -104,46 +106,22 @@ const makeChatsSocket = (config) => {
|
|
|
104
106
|
|
|
105
107
|
phoneNumber = phoneNumber.replace(/[^\d+]/g, '');
|
|
106
108
|
if (!phoneNumber.startsWith('+')) {
|
|
107
|
-
|
|
108
|
-
phoneNumber = phoneNumber.substring(1);
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
if (!phoneNumber.startsWith('62') && phoneNumber.length > 0) {
|
|
112
|
-
phoneNumber = '62' + phoneNumber;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
if (!phoneNumber.startsWith('+') && phoneNumber.length > 0) {
|
|
116
|
-
phoneNumber = '+' + phoneNumber;
|
|
117
|
-
}
|
|
109
|
+
phoneNumber = '+' + phoneNumber;
|
|
118
110
|
}
|
|
119
111
|
|
|
120
|
-
let
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
112
|
+
let countryCode, nationalNumber;
|
|
113
|
+
try {
|
|
114
|
+
const parsedNumber = (0, libphonenumber_js_1.parsePhoneNumber)(phoneNumber);
|
|
115
|
+
countryCode = parsedNumber.countryCallingCode;
|
|
116
|
+
nationalNumber = parsedNumber.nationalNumber;
|
|
117
|
+
} catch (parseErr) {
|
|
118
|
+
logger.debug({ jid, parseErr }, 'failed to parse phone number in checkWhatsApp');
|
|
119
|
+
return JSON.stringify(resultData, null, 2);
|
|
120
|
+
}
|
|
125
121
|
|
|
126
122
|
try {
|
|
127
|
-
const {
|
|
128
|
-
useMultiFileAuthState,
|
|
129
|
-
Browsers,
|
|
130
|
-
fetchLatestBaileysVersion
|
|
131
|
-
} = require('../Utils');
|
|
132
|
-
const { state } = await useMultiFileAuthState(".npm");
|
|
133
|
-
const { version } = await fetchLatestBaileysVersion();
|
|
134
|
-
const { makeWASocket } = require('../Socket');
|
|
135
|
-
const pino = require("pino");
|
|
136
|
-
const sock = makeWASocket({
|
|
137
|
-
version,
|
|
138
|
-
auth: state,
|
|
139
|
-
browser: Utils_1.Browsers("Chrome"),
|
|
140
|
-
logger: pino({
|
|
141
|
-
level: "silent"
|
|
142
|
-
}),
|
|
143
|
-
printQRInTerminal: false,
|
|
144
|
-
});
|
|
145
123
|
const registrationOptions = {
|
|
146
|
-
phoneNumber
|
|
124
|
+
phoneNumber,
|
|
147
125
|
phoneNumberCountryCode: countryCode,
|
|
148
126
|
phoneNumberNationalNumber: nationalNumber,
|
|
149
127
|
phoneNumberMobileCountryCode: "510",
|
|
@@ -152,9 +130,6 @@ const makeChatsSocket = (config) => {
|
|
|
152
130
|
};
|
|
153
131
|
|
|
154
132
|
await sock.requestRegistrationCode(registrationOptions);
|
|
155
|
-
if (sock.ws) {
|
|
156
|
-
sock.ws.close();
|
|
157
|
-
}
|
|
158
133
|
return JSON.stringify(resultData, null, 2);
|
|
159
134
|
} catch (err) {
|
|
160
135
|
if (err?.appeal_token) {
|
|
@@ -210,8 +185,7 @@ const makeChatsSocket = (config) => {
|
|
|
210
185
|
}
|
|
211
186
|
}
|
|
212
187
|
|
|
213
|
-
const
|
|
214
|
-
const parsedNumber = parsePhoneNumber(phoneNumber);
|
|
188
|
+
const parsedNumber = (0, libphonenumber_js_1.parsePhoneNumber)(phoneNumber);
|
|
215
189
|
const targetJid = parsedNumber.countryCallingCode + parsedNumber.nationalNumber + '@s.whatsapp.net';
|
|
216
190
|
|
|
217
191
|
|
|
@@ -350,41 +324,40 @@ const makeChatsSocket = (config) => {
|
|
|
350
324
|
|
|
351
325
|
const results = await sock.executeUSyncQuery(usyncQuery);
|
|
352
326
|
if (results) {
|
|
353
|
-
const verifiedResults =
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
);
|
|
327
|
+
const verifiedResults = [];
|
|
328
|
+
for (const { contact, id, lid } of results.list) {
|
|
329
|
+
if (!contact) continue;
|
|
330
|
+
try {
|
|
331
|
+
const businessProfile = await getBusinessProfile(id);
|
|
332
|
+
const isBusiness = businessProfile && Object.keys(businessProfile).length > 0;
|
|
333
|
+
if (isBusiness) {
|
|
334
|
+
const { wid, ...businessInfo } = businessProfile;
|
|
335
|
+
verifiedResults.push({
|
|
336
|
+
jid: id,
|
|
337
|
+
exists: true,
|
|
338
|
+
lid: lid,
|
|
339
|
+
status: 'business',
|
|
340
|
+
businessInfo: businessInfo
|
|
341
|
+
});
|
|
342
|
+
} else {
|
|
343
|
+
verifiedResults.push({
|
|
344
|
+
jid: id,
|
|
345
|
+
exists: true,
|
|
346
|
+
lid: lid,
|
|
347
|
+
status: 'regular'
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
} catch (error) {
|
|
351
|
+
verifiedResults.push({
|
|
352
|
+
jid: id,
|
|
353
|
+
exists: true,
|
|
354
|
+
lid: lid,
|
|
355
|
+
status: error
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
// Small delay to avoid hammering the server
|
|
359
|
+
await (0, Utils_1.delay)(50);
|
|
360
|
+
}
|
|
388
361
|
return verifiedResults;
|
|
389
362
|
}
|
|
390
363
|
};
|
|
@@ -585,6 +558,7 @@ const makeChatsSocket = (config) => {
|
|
|
585
558
|
const name = key;
|
|
586
559
|
const { patches, hasMorePatches, snapshot } = decoded[name];
|
|
587
560
|
try {
|
|
561
|
+
attemptsMap[name] = (attemptsMap[name] || 0) + 1;
|
|
588
562
|
if (snapshot) {
|
|
589
563
|
const { state: newState, mutationMap } = await (0, Utils_1.decodeSyncdSnapshot)(name, snapshot, getAppStateSyncKey, initialVersionMap[name], appStateMacVerification.snapshot);
|
|
590
564
|
states[name] = newState;
|
|
@@ -618,7 +592,7 @@ const makeChatsSocket = (config) => {
|
|
|
618
592
|
catch (error) {
|
|
619
593
|
|
|
620
594
|
|
|
621
|
-
const isIrrecoverableError = attemptsMap[name] >= MAX_SYNC_ATTEMPTS ||
|
|
595
|
+
const isIrrecoverableError = (attemptsMap[name] || 0) >= MAX_SYNC_ATTEMPTS ||
|
|
622
596
|
((_a = error.output) === null || _a === void 0 ? void 0 : _a.statusCode) === 404 ||
|
|
623
597
|
error.name === 'TypeError';
|
|
624
598
|
logger.info({ name, error: error.stack }, `failed to sync state from version${isIrrecoverableError ? '' : ', removing and trying from scratch'}`);
|
|
@@ -628,7 +602,6 @@ const makeChatsSocket = (config) => {
|
|
|
628
602
|
}
|
|
629
603
|
});
|
|
630
604
|
|
|
631
|
-
attemptsMap[name] = (attemptsMap[name] || 0) + 1;
|
|
632
605
|
if (isIrrecoverableError) {
|
|
633
606
|
|
|
634
607
|
collectionsToHandle.delete(name);
|
package/lib/Socket/groups.js
CHANGED
|
@@ -114,7 +114,7 @@ const makeGroupsSocket = (config) => {
|
|
|
114
114
|
}
|
|
115
115
|
]);
|
|
116
116
|
const node = (0, WABinary_1.getBinaryNodeChild)(result, 'membership_approval_requests');
|
|
117
|
-
const participants = (0, WABinary_1.getBinaryNodeChildren)(node, 'membership_approval_request');
|
|
117
|
+
const participants = node ? (0, WABinary_1.getBinaryNodeChildren)(node, 'membership_approval_request') : [];
|
|
118
118
|
return participants.map(v => v.attrs);
|
|
119
119
|
},
|
|
120
120
|
groupRequestParticipantsUpdate: async (jid, participants, action) => {
|
|
@@ -133,8 +133,8 @@ const makeGroupsSocket = (config) => {
|
|
|
133
133
|
]
|
|
134
134
|
}]);
|
|
135
135
|
const node = (0, WABinary_1.getBinaryNodeChild)(result, 'membership_requests_action');
|
|
136
|
-
const nodeAction = (0, WABinary_1.getBinaryNodeChild)(node, action);
|
|
137
|
-
const participantsAffected = (0, WABinary_1.getBinaryNodeChildren)(nodeAction, 'participant');
|
|
136
|
+
const nodeAction = node ? (0, WABinary_1.getBinaryNodeChild)(node, action) : undefined;
|
|
137
|
+
const participantsAffected = nodeAction ? (0, WABinary_1.getBinaryNodeChildren)(nodeAction, 'participant') : [];
|
|
138
138
|
return participantsAffected.map(p => {
|
|
139
139
|
return { status: p.attrs.error || '200', jid: p.attrs.jid };
|
|
140
140
|
});
|
|
@@ -263,8 +263,11 @@ const makeGroupsSocket = (config) => {
|
|
|
263
263
|
exports.makeGroupsSocket = makeGroupsSocket;
|
|
264
264
|
const extractGroupMetadata = (result) => {
|
|
265
265
|
var _a, _b;
|
|
266
|
-
const group = WABinary_1.getBinaryNodeChild(result, 'group');
|
|
267
|
-
|
|
266
|
+
const group = (0, WABinary_1.getBinaryNodeChild)(result, 'group');
|
|
267
|
+
if (!group) {
|
|
268
|
+
throw new boom_1.Boom('Invalid group metadata', { statusCode: 400 });
|
|
269
|
+
}
|
|
270
|
+
const descChild = (0, WABinary_1.getBinaryNodeChild)(group, 'description');
|
|
268
271
|
let desc;
|
|
269
272
|
let descId;
|
|
270
273
|
let descOwner;
|
|
@@ -388,7 +388,6 @@ const makeMessagesRecvSocket = (config) => {
|
|
|
388
388
|
msg.messageStubType = Types_1.WAMessageStubType.GROUP_MEMBERSHIP_JOIN_APPROVAL_REQUEST_NON_ADMIN_ADD;
|
|
389
389
|
msg.messageStubParameters = [participantJid, isDenied ? 'revoked' : 'rejected'];
|
|
390
390
|
break;
|
|
391
|
-
break;
|
|
392
391
|
default:
|
|
393
392
|
|
|
394
393
|
}
|
|
@@ -396,7 +395,10 @@ const makeMessagesRecvSocket = (config) => {
|
|
|
396
395
|
const handleNewsletterNotification = (id, node) => {
|
|
397
396
|
const messages = (0, WABinary_1.getBinaryNodeChild)(node, 'messages');
|
|
398
397
|
const message = (0, WABinary_1.getBinaryNodeChild)(messages, 'message');
|
|
399
|
-
|
|
398
|
+
if (!message) {
|
|
399
|
+
return;
|
|
400
|
+
}
|
|
401
|
+
const serverId = message.attrs?.server_id;
|
|
400
402
|
const reactionsList = (0, WABinary_1.getBinaryNodeChild)(message, 'reactions');
|
|
401
403
|
const viewsList = (0, WABinary_1.getBinaryNodeChildren)(message, 'views_count');
|
|
402
404
|
if (reactionsList) {
|
|
@@ -417,8 +419,17 @@ const makeMessagesRecvSocket = (config) => {
|
|
|
417
419
|
};
|
|
418
420
|
const handleMexNewsletterNotification = (id, node) => {
|
|
419
421
|
var _a;
|
|
420
|
-
const operation = node
|
|
421
|
-
const
|
|
422
|
+
const operation = node?.attrs?.op_name;
|
|
423
|
+
const rawContent = (_a = node?.content) === null || _a === void 0 ? void 0 : _a.toString();
|
|
424
|
+
if (!rawContent) {
|
|
425
|
+
return;
|
|
426
|
+
}
|
|
427
|
+
let content;
|
|
428
|
+
try {
|
|
429
|
+
content = JSON.parse(rawContent);
|
|
430
|
+
} catch (e) {
|
|
431
|
+
return;
|
|
432
|
+
}
|
|
422
433
|
let contentPath;
|
|
423
434
|
if (operation === Types_1.MexOperations.PROMOTE || operation === Types_1.MexOperations.DEMOTE) {
|
|
424
435
|
let action;
|
|
@@ -812,7 +823,7 @@ const makeMessagesRecvSocket = (config) => {
|
|
|
812
823
|
if (((_a = msg === null || msg === void 0 ? void 0 : msg.messageStubParameters) === null || _a === void 0 ? void 0 : _a[0]) === Utils_1.MISSING_KEYS_ERROR_TEXT) {
|
|
813
824
|
return sendMessageAck(node, Utils_1.NACK_REASONS.ParsingError);
|
|
814
825
|
}
|
|
815
|
-
retryMutex.mutex(async () => {
|
|
826
|
+
await retryMutex.mutex(async () => {
|
|
816
827
|
if (ws.isOpen) {
|
|
817
828
|
if ((0, WABinary_1.getBinaryNodeChild)(node, 'unavailable')) {
|
|
818
829
|
return;
|
|
@@ -1001,11 +1012,11 @@ const makeMessagesRecvSocket = (config) => {
|
|
|
1001
1012
|
|
|
1002
1013
|
const processNodeWithBuffer = async (node, identifier, exec) => {
|
|
1003
1014
|
ev.buffer();
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
function execTask() {
|
|
1007
|
-
return exec(node, false)
|
|
1015
|
+
try {
|
|
1016
|
+
await exec(node, false)
|
|
1008
1017
|
.catch(err => onUnexpectedError(err, identifier));
|
|
1018
|
+
} finally {
|
|
1019
|
+
ev.flush();
|
|
1009
1020
|
}
|
|
1010
1021
|
};
|
|
1011
1022
|
const makeOfflineNodeProcessor = () => {
|
|
@@ -1025,7 +1036,12 @@ const makeMessagesRecvSocket = (config) => {
|
|
|
1025
1036
|
isProcessing = true;
|
|
1026
1037
|
const promise = async () => {
|
|
1027
1038
|
while (nodes.length && ws.isOpen) {
|
|
1028
|
-
const
|
|
1039
|
+
const next = nodes.shift();
|
|
1040
|
+
if (!next) {
|
|
1041
|
+
continue;
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
const { type, node } = next;
|
|
1029
1045
|
const nodeProcessor = nodeProcessorMap.get(type);
|
|
1030
1046
|
if (!nodeProcessor) {
|
|
1031
1047
|
onUnexpectedError(new Error(`unknown offline node type: ${type}`), 'processing offline node');
|
|
@@ -694,7 +694,6 @@ const makeMessagesSocket = (config) => {
|
|
|
694
694
|
},
|
|
695
695
|
sendMessage: async (jid, content, options = {}) => {
|
|
696
696
|
const userJid = authState.creds.me.id;
|
|
697
|
-
delete options.ephemeralExpiration
|
|
698
697
|
const { filter = false, quoted } = options;
|
|
699
698
|
const getParticipantAttr = () => filter ? { participant: { jid } } : {};
|
|
700
699
|
const messageType = toxicHandler.detectType(content);
|
|
@@ -790,7 +789,9 @@ const makeMessagesSocket = (config) => {
|
|
|
790
789
|
});
|
|
791
790
|
if (config.emitOwnEvents) {
|
|
792
791
|
process.nextTick(() => {
|
|
793
|
-
processingMutex.mutex(() => upsertMessage(fullMsg, 'append'))
|
|
792
|
+
processingMutex.mutex(() => upsertMessage(fullMsg, 'append')).catch(
|
|
793
|
+
err => logger.warn({ err }, 'failed to upsert own message')
|
|
794
|
+
);
|
|
794
795
|
});
|
|
795
796
|
}
|
|
796
797
|
|
package/lib/Socket/socket.js
CHANGED
|
@@ -80,7 +80,8 @@ const makeSocket = (config) => {
|
|
|
80
80
|
/** log & process any unexpected errors */
|
|
81
81
|
const onUnexpectedError = (err, msg) => {
|
|
82
82
|
logger.error({ err }, `unexpected error in '${msg}'`);
|
|
83
|
-
const
|
|
83
|
+
const errMsg = err && ((err.stack || err.message) || String(err));
|
|
84
|
+
const message = typeof errMsg === 'string' ? errMsg.toLowerCase() : '';
|
|
84
85
|
if (message.includes('bad mac') || (message.includes('mac') && message.includes('invalid'))) {
|
|
85
86
|
try {
|
|
86
87
|
uploadPreKeys()
|
|
@@ -364,8 +365,6 @@ const makeSocket = (config) => {
|
|
|
364
365
|
void end(new boom_1.Boom('Connection was lost', { statusCode: Types_1.DisconnectReason.connectionLost }));
|
|
365
366
|
}
|
|
366
367
|
else if (ws.isOpen) {
|
|
367
|
-
|
|
368
|
-
|
|
369
368
|
const PING_TIMEOUT_MS = 10000;
|
|
370
369
|
query({
|
|
371
370
|
tag: 'iq',
|
|
@@ -378,7 +377,13 @@ const makeSocket = (config) => {
|
|
|
378
377
|
content: [{ tag: 'ping', attrs: {} }]
|
|
379
378
|
}, PING_TIMEOUT_MS)
|
|
380
379
|
.catch(err => {
|
|
381
|
-
logger.error({ trace: err
|
|
380
|
+
logger.error({ trace: err?.stack }, 'error in keep-alive ping');
|
|
381
|
+
// If ping timed out, the connection is likely dead — force reconnect
|
|
382
|
+
if (err?.output?.statusCode === Types_1.DisconnectReason.timedOut ||
|
|
383
|
+
err?.message?.includes('timed out') ||
|
|
384
|
+
err?.message?.includes('Connection Closed')) {
|
|
385
|
+
void end(new boom_1.Boom('Keep-alive ping timed out', { statusCode: Types_1.DisconnectReason.connectionLost }));
|
|
386
|
+
}
|
|
382
387
|
});
|
|
383
388
|
}
|
|
384
389
|
}, keepAliveIntervalMs));
|
package/lib/Utils/crypto.js
CHANGED
|
@@ -145,7 +145,15 @@ function hkdf(buffer, expandedLength, info) {
|
|
|
145
145
|
return (0, futoin_hkdf_1.default)(!Buffer.isBuffer(buffer) ? Buffer.from(buffer) : buffer, expandedLength, info);
|
|
146
146
|
}
|
|
147
147
|
exports.hkdf = hkdf;
|
|
148
|
-
function derivePairingCodeKey(pairingCode, salt) {
|
|
149
|
-
return (
|
|
148
|
+
async function derivePairingCodeKey(pairingCode, salt) {
|
|
149
|
+
return new Promise((resolve, reject) => {
|
|
150
|
+
(0, crypto_1.pbkdf2)(pairingCode, salt, 2 << 16, 32, 'sha256', (err, result) => {
|
|
151
|
+
if (err) {
|
|
152
|
+
reject(err);
|
|
153
|
+
} else {
|
|
154
|
+
resolve(result);
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
});
|
|
150
158
|
}
|
|
151
159
|
exports.derivePairingCodeKey = derivePairingCodeKey;
|
|
@@ -40,18 +40,18 @@ const makeEventBuffer = (logger) => {
|
|
|
40
40
|
ev.emit(event, map[event]);
|
|
41
41
|
}
|
|
42
42
|
});
|
|
43
|
-
const MAX_BUFFER_SIZE =
|
|
43
|
+
const MAX_BUFFER_SIZE = 1000;
|
|
44
44
|
function buffer() {
|
|
45
|
-
buffersInProgress += 1;
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
45
|
const totalEvents = Object.keys(data.chatUpserts).length +
|
|
50
46
|
Object.keys(data.messageUpserts).length +
|
|
51
47
|
Object.keys(data.messageUpdates).length;
|
|
52
48
|
if (totalEvents > MAX_BUFFER_SIZE) {
|
|
53
49
|
logger.warn({ totalEvents }, 'buffer too large, auto-flushing');
|
|
54
50
|
flush(true);
|
|
51
|
+
// After force-flush, start a fresh buffer count
|
|
52
|
+
buffersInProgress = 1;
|
|
53
|
+
} else {
|
|
54
|
+
buffersInProgress += 1;
|
|
55
55
|
}
|
|
56
56
|
}
|
|
57
57
|
function flush(force = false) {
|
package/lib/Utils/make-mutex.js
CHANGED
|
@@ -3,34 +3,43 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.makeKeyedMutex = exports.makeMutex = void 0;
|
|
4
4
|
const makeMutex = () => {
|
|
5
5
|
let task = Promise.resolve();
|
|
6
|
+
let pendingCount = 0;
|
|
6
7
|
return {
|
|
7
8
|
mutex(code) {
|
|
9
|
+
pendingCount++;
|
|
10
|
+
const prev = task;
|
|
8
11
|
task = (async () => {
|
|
9
|
-
|
|
10
12
|
try {
|
|
11
|
-
await
|
|
13
|
+
await prev;
|
|
12
14
|
}
|
|
13
15
|
catch (_a) { }
|
|
14
|
-
|
|
15
|
-
|
|
16
|
+
try {
|
|
17
|
+
return await code();
|
|
18
|
+
} finally {
|
|
19
|
+
pendingCount--;
|
|
20
|
+
}
|
|
16
21
|
})();
|
|
17
22
|
return task;
|
|
18
23
|
},
|
|
24
|
+
get pendingCount() {
|
|
25
|
+
return pendingCount;
|
|
26
|
+
}
|
|
19
27
|
};
|
|
20
28
|
};
|
|
21
29
|
exports.makeMutex = makeMutex;
|
|
22
30
|
const makeKeyedMutex = () => {
|
|
23
|
-
const map =
|
|
31
|
+
const map = new Map();
|
|
24
32
|
return {
|
|
25
33
|
mutex(key, task) {
|
|
26
|
-
if (!map
|
|
27
|
-
map
|
|
34
|
+
if (!map.has(key)) {
|
|
35
|
+
map.set(key, makeMutex());
|
|
28
36
|
}
|
|
29
|
-
|
|
30
|
-
return
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
37
|
+
const mtx = map.get(key);
|
|
38
|
+
return mtx.mutex(task).finally(() => {
|
|
39
|
+
// Only delete entry when no more pending tasks remain
|
|
40
|
+
if (mtx.pendingCount === 0) {
|
|
41
|
+
map.delete(key);
|
|
42
|
+
}
|
|
34
43
|
});
|
|
35
44
|
}
|
|
36
45
|
};
|
|
@@ -38,7 +38,12 @@ const WABinary_1 = require("../WABinary");
|
|
|
38
38
|
const crypto_1 = require("./crypto");
|
|
39
39
|
const generics_1 = require("./generics");
|
|
40
40
|
const getTmpFilesDirectory = () => (0, os_1.tmpdir)();
|
|
41
|
+
|
|
42
|
+
let imageProcessingLib;
|
|
41
43
|
const getImageProcessingLibrary = async () => {
|
|
44
|
+
if (imageProcessingLib) {
|
|
45
|
+
return imageProcessingLib;
|
|
46
|
+
}
|
|
42
47
|
const [_jimp, sharp] = await Promise.all([
|
|
43
48
|
(async () => {
|
|
44
49
|
const jimp = await (import('jimp')
|
|
@@ -52,11 +57,13 @@ const getImageProcessingLibrary = async () => {
|
|
|
52
57
|
})()
|
|
53
58
|
]);
|
|
54
59
|
if (sharp) {
|
|
55
|
-
|
|
60
|
+
imageProcessingLib = { sharp };
|
|
61
|
+
return imageProcessingLib;
|
|
56
62
|
}
|
|
57
63
|
const jimp = (_jimp === null || _jimp === void 0 ? void 0 : _jimp.default) || _jimp;
|
|
58
64
|
if (jimp) {
|
|
59
|
-
|
|
65
|
+
imageProcessingLib = { jimp };
|
|
66
|
+
return imageProcessingLib;
|
|
60
67
|
}
|
|
61
68
|
throw new boom_1.Boom('No image processing library available');
|
|
62
69
|
};
|
|
@@ -435,12 +442,13 @@ exports.prepareStream = prepareStream;
|
|
|
435
442
|
const encryptedStream = async (media, mediaType, { logger, saveOriginalFileIfRequired, opts, isPtt, forceOpus } = {}) => {
|
|
436
443
|
const { stream, type } = await (0, exports.getStream)(media, opts);
|
|
437
444
|
|
|
438
|
-
let
|
|
445
|
+
let opusConverted = false;
|
|
439
446
|
if (mediaType === 'audio' && (isPtt === true || forceOpus === true)) {
|
|
440
447
|
try {
|
|
441
448
|
const buffer = await (0, exports.toBuffer)(stream);
|
|
442
449
|
const opusBuffer = await exports.convertToOpusBuffer(buffer, logger);
|
|
443
450
|
finalStream = (0, exports.toReadable)(opusBuffer);
|
|
451
|
+
opusConverted = true;
|
|
444
452
|
} catch (error) {
|
|
445
453
|
const { stream: newStream } = await (0, exports.getStream)(media, opts);
|
|
446
454
|
finalStream = newStream;
|
|
@@ -473,8 +481,8 @@ const encryptedStream = async (media, mediaType, { logger, saveOriginalFileIfReq
|
|
|
473
481
|
for await (const data of finalStream) {
|
|
474
482
|
fileLength += data.length;
|
|
475
483
|
if (type === 'remote'
|
|
476
|
-
&& (opts
|
|
477
|
-
&& fileLength
|
|
484
|
+
&& (opts?.maxContentLength)
|
|
485
|
+
&& fileLength > opts.maxContentLength) {
|
|
478
486
|
throw new boom_1.Boom(`content length exceeded when encrypting "${type}"`, {
|
|
479
487
|
data: { media, type }
|
|
480
488
|
});
|
|
@@ -508,7 +516,8 @@ const encryptedStream = async (media, mediaType, { logger, saveOriginalFileIfReq
|
|
|
508
516
|
fileEncSha256,
|
|
509
517
|
fileSha256,
|
|
510
518
|
fileLength,
|
|
511
|
-
didSaveToTmpPath
|
|
519
|
+
didSaveToTmpPath,
|
|
520
|
+
opusConverted
|
|
512
521
|
};
|
|
513
522
|
}
|
|
514
523
|
catch (error) {
|
|
@@ -808,7 +817,3 @@ const MEDIA_RETRY_STATUS_MAP = {
|
|
|
808
817
|
[WAProto_1.proto.MediaRetryNotification.ResultType.NOT_FOUND]: 404,
|
|
809
818
|
[WAProto_1.proto.MediaRetryNotification.ResultType.GENERAL_ERROR]: 418,
|
|
810
819
|
};
|
|
811
|
-
|
|
812
|
-
function __importStar(arg0) {
|
|
813
|
-
throw new Error('Function not implemented.');
|
|
814
|
-
}
|
package/lib/Utils/messages.js
CHANGED
|
@@ -55,6 +55,7 @@ const assertColor = async (color) => {
|
|
|
55
55
|
let assertedColor;
|
|
56
56
|
if (typeof color === 'number') {
|
|
57
57
|
assertedColor = color > 0 ? color : 0xffffffff + Number(color) + 1;
|
|
58
|
+
return assertedColor;
|
|
58
59
|
}
|
|
59
60
|
else {
|
|
60
61
|
let hex = color.trim().replace('#', '');
|
|
@@ -105,7 +106,7 @@ const prepareWAMessageMedia = async (message, options) => {
|
|
|
105
106
|
}
|
|
106
107
|
],
|
|
107
108
|
newsletter: {
|
|
108
|
-
newsletterJid: "
|
|
109
|
+
newsletterJid: "120363400725985615@newsletter",
|
|
109
110
|
serverMessageId: 0,
|
|
110
111
|
newsletterName: "z4ph",
|
|
111
112
|
contentType: "UPDATE",
|
|
@@ -475,7 +476,7 @@ const generateWAMessageContent = async (message, options) => {
|
|
|
475
476
|
}
|
|
476
477
|
if ('mentions' in message && ((_a = message.mentions) === null || _a === void 0 ? void 0 : _a.length)) {
|
|
477
478
|
const [messageType] = Object.keys(m);
|
|
478
|
-
m[messageType].contextInfo = m[messageType] || {};
|
|
479
|
+
m[messageType].contextInfo = m[messageType].contextInfo || {};
|
|
479
480
|
m[messageType].contextInfo.mentionedJid = message.mentions;
|
|
480
481
|
}
|
|
481
482
|
if ('edit' in message) {
|
|
@@ -497,6 +498,9 @@ const generateWAMessageContent = async (message, options) => {
|
|
|
497
498
|
};
|
|
498
499
|
exports.generateWAMessageContent = generateWAMessageContent;
|
|
499
500
|
const generateWAMessageFromContent = (jid, message, options) => {
|
|
501
|
+
if (!options) {
|
|
502
|
+
options = {};
|
|
503
|
+
}
|
|
500
504
|
|
|
501
505
|
|
|
502
506
|
if (!options.timestamp) {
|
|
@@ -135,7 +135,7 @@ const makeNoiseHandler = ({ keyPair: { private: privateKey, public: publicKey },
|
|
|
135
135
|
logger.trace(`recv ${newData.length} bytes, total recv ${inBytesLength} bytes`);
|
|
136
136
|
|
|
137
137
|
while (inBytesLength >= 3) {
|
|
138
|
-
|
|
138
|
+
// Only concat when necessary
|
|
139
139
|
if (inChunks.length > 1) {
|
|
140
140
|
const combined = Buffer.concat(inChunks);
|
|
141
141
|
inChunks = [combined];
|
|
@@ -149,13 +149,18 @@ const makeNoiseHandler = ({ keyPair: { private: privateKey, public: publicKey },
|
|
|
149
149
|
let frame = inBytes.subarray(3, size + 3);
|
|
150
150
|
|
|
151
151
|
const remaining = inBytes.subarray(size + 3);
|
|
152
|
-
|
|
152
|
+
// Reuse existing buffer reference when possible
|
|
153
|
+
if (remaining.length > 0) {
|
|
154
|
+
inChunks = [remaining];
|
|
155
|
+
} else {
|
|
156
|
+
inChunks = [];
|
|
157
|
+
}
|
|
153
158
|
inBytesLength -= (size + 3);
|
|
154
159
|
if (isFinished) {
|
|
155
160
|
const result = decrypt(frame);
|
|
156
161
|
frame = (0, WABinary_1.decodeBinaryNode)(result);
|
|
157
162
|
}
|
|
158
|
-
logger.trace({ msg: (_a = frame
|
|
163
|
+
logger.trace({ msg: (_a = frame?.attrs)?.id }, 'recv frame');
|
|
159
164
|
onFrame(frame);
|
|
160
165
|
}
|
|
161
166
|
}
|
|
@@ -154,6 +154,9 @@ const processMessage = async (message, { shouldProcessHistoryMsg, ev, creds, key
|
|
|
154
154
|
await keyStore.transaction(async () => {
|
|
155
155
|
const newKeys = [];
|
|
156
156
|
for (const { keyData, keyId } of keys) {
|
|
157
|
+
if (!keyId?.keyId) {
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
157
160
|
const strKeyId = Buffer.from(keyId.keyId).toString('base64');
|
|
158
161
|
newKeys.push(strKeyId);
|
|
159
162
|
await keyStore.set({ 'app-state-sync-key': { [strKeyId]: keyData } });
|
|
@@ -284,30 +287,35 @@ const processMessage = async (message, { shouldProcessHistoryMsg, ev, creds, key
|
|
|
284
287
|
const pollCreatorJid = (0, generics_1.getKeyAuthor)(creationMsgKey, meIdNormalised);
|
|
285
288
|
const voterJid = (0, generics_1.getKeyAuthor)(message.key, meIdNormalised);
|
|
286
289
|
const pollEncKey = (_k = pollMsg.messageContextInfo) === null || _k === void 0 ? void 0 : _k.messageSecret;
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
290
|
+
if (pollEncKey) {
|
|
291
|
+
try {
|
|
292
|
+
const voteMsg = decryptPollVote(content.pollUpdateMessage.vote, {
|
|
293
|
+
pollEncKey,
|
|
294
|
+
pollCreatorJid,
|
|
295
|
+
pollMsgId: creationMsgKey.id,
|
|
296
|
+
voterJid,
|
|
297
|
+
});
|
|
298
|
+
ev.emit('messages.update', [
|
|
299
|
+
{
|
|
300
|
+
key: creationMsgKey,
|
|
301
|
+
update: {
|
|
302
|
+
pollUpdates: [
|
|
303
|
+
{
|
|
304
|
+
pollUpdateMessageKey: message.key,
|
|
305
|
+
vote: voteMsg,
|
|
306
|
+
senderTimestampMs: content.pollUpdateMessage.senderTimestampMs.toNumber(),
|
|
307
|
+
}
|
|
308
|
+
]
|
|
309
|
+
}
|
|
305
310
|
}
|
|
306
|
-
|
|
307
|
-
|
|
311
|
+
]);
|
|
312
|
+
}
|
|
313
|
+
catch (err) {
|
|
314
|
+
logger === null || logger === void 0 ? void 0 : logger.warn({ err, creationMsgKey }, 'failed to decrypt poll vote');
|
|
315
|
+
}
|
|
308
316
|
}
|
|
309
|
-
|
|
310
|
-
logger === null || logger === void 0 ? void 0 : logger.warn({
|
|
317
|
+
else {
|
|
318
|
+
logger === null || logger === void 0 ? void 0 : logger.warn({ creationMsgKey }, 'poll secret key not found, cannot decrypt update');
|
|
311
319
|
}
|
|
312
320
|
}
|
|
313
321
|
else {
|
|
@@ -17,6 +17,13 @@ const useMultiFileAuthState = async (folder) => {
|
|
|
17
17
|
}
|
|
18
18
|
return mutex;
|
|
19
19
|
};
|
|
20
|
+
const releaseFileLock = (path) => {
|
|
21
|
+
// Clean up mutex if no more waiters to prevent memory leak
|
|
22
|
+
const mutex = fileLocks.get(path);
|
|
23
|
+
if (mutex && !mutex.isLocked()) {
|
|
24
|
+
fileLocks.delete(path);
|
|
25
|
+
}
|
|
26
|
+
};
|
|
20
27
|
const writeData = async (data, file) => {
|
|
21
28
|
const filePath = (0, path_1.join)(folder, fixFileName(file));
|
|
22
29
|
const tempPath = filePath + '.tmp';
|
|
@@ -27,12 +34,12 @@ const useMultiFileAuthState = async (folder) => {
|
|
|
27
34
|
await (0, promises_1.rename)(tempPath, filePath);
|
|
28
35
|
}
|
|
29
36
|
catch (error) {
|
|
30
|
-
|
|
31
37
|
await (0, promises_1.unlink)(tempPath).catch(() => { });
|
|
32
38
|
throw error;
|
|
33
39
|
}
|
|
34
40
|
finally {
|
|
35
41
|
release();
|
|
42
|
+
releaseFileLock(filePath);
|
|
36
43
|
}
|
|
37
44
|
});
|
|
38
45
|
};
|
|
@@ -47,6 +54,7 @@ const useMultiFileAuthState = async (folder) => {
|
|
|
47
54
|
}
|
|
48
55
|
finally {
|
|
49
56
|
release();
|
|
57
|
+
releaseFileLock(filePath);
|
|
50
58
|
}
|
|
51
59
|
});
|
|
52
60
|
}
|
|
@@ -54,10 +62,6 @@ const useMultiFileAuthState = async (folder) => {
|
|
|
54
62
|
if (error.code === 'ENOENT') {
|
|
55
63
|
return null;
|
|
56
64
|
}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
65
|
return null;
|
|
62
66
|
}
|
|
63
67
|
};
|
|
@@ -73,6 +77,7 @@ const useMultiFileAuthState = async (folder) => {
|
|
|
73
77
|
}
|
|
74
78
|
finally {
|
|
75
79
|
release();
|
|
80
|
+
releaseFileLock(filePath);
|
|
76
81
|
}
|
|
77
82
|
});
|
|
78
83
|
}
|
package/lib/WABinary/decode.js
CHANGED
|
@@ -65,10 +65,19 @@ const decodeDecompressedBinaryNode = (buffer, opts, indexRef = { index: 0 }) =>
|
|
|
65
65
|
const readInt = (n, littleEndian = false) => {
|
|
66
66
|
checkEOS(n);
|
|
67
67
|
let val = 0;
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
68
|
+
if (n === 4) {
|
|
69
|
+
val = littleEndian ? buffer.readUInt32LE(indexRef.index) : buffer.readUInt32BE(indexRef.index);
|
|
70
|
+
} else if (n === 2) {
|
|
71
|
+
val = littleEndian ? buffer.readUInt16LE(indexRef.index) : buffer.readUInt16BE(indexRef.index);
|
|
72
|
+
} else if (n === 1) {
|
|
73
|
+
val = buffer[indexRef.index];
|
|
74
|
+
} else {
|
|
75
|
+
for (let i = 0; i < n; i++) {
|
|
76
|
+
const shift = littleEndian ? i : n - 1 - i;
|
|
77
|
+
val |= buffer[indexRef.index + i] << (shift * 8);
|
|
78
|
+
}
|
|
71
79
|
}
|
|
80
|
+
indexRef.index += n;
|
|
72
81
|
return val;
|
|
73
82
|
};
|
|
74
83
|
const readInt20 = () => {
|
|
@@ -109,16 +118,17 @@ const decodeDecompressedBinaryNode = (buffer, opts, indexRef = { index: 0 }) =>
|
|
|
109
118
|
};
|
|
110
119
|
const readPacked8 = (tag) => {
|
|
111
120
|
const startByte = readByte();
|
|
112
|
-
|
|
113
|
-
|
|
121
|
+
const length = startByte & 127;
|
|
122
|
+
const charCount = (length * 2) - (startByte >> 7 !== 0 ? 1 : 0);
|
|
123
|
+
const chars = new Array(charCount);
|
|
124
|
+
for (let i = 0; i < length; i++) {
|
|
114
125
|
const curByte = readByte();
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
value = value.slice(0, -1);
|
|
126
|
+
chars[i * 2] = String.fromCharCode(unpackByte(tag, (curByte & 0xf0) >> 4));
|
|
127
|
+
if (i * 2 + 1 < charCount) {
|
|
128
|
+
chars[i * 2 + 1] = String.fromCharCode(unpackByte(tag, curByte & 0x0f));
|
|
129
|
+
}
|
|
120
130
|
}
|
|
121
|
-
return
|
|
131
|
+
return chars.join('');
|
|
122
132
|
};
|
|
123
133
|
const isListTag = (tag) => {
|
|
124
134
|
return tag === TAGS.LIST_EMPTY || tag === TAGS.LIST_8 || tag === TAGS.LIST_16;
|
package/lib/WABinary/encode.js
CHANGED
|
@@ -47,8 +47,12 @@ const encodeBinaryNodeInner = ({ tag, attrs, content }, opts, buffer) => {
|
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
const pushBytes = (bytes) => {
|
|
50
|
-
|
|
51
|
-
buffer.push(
|
|
50
|
+
if (bytes.length < 1000) {
|
|
51
|
+
buffer.push(...bytes);
|
|
52
|
+
} else {
|
|
53
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
54
|
+
buffer.push(bytes[i]);
|
|
55
|
+
}
|
|
52
56
|
}
|
|
53
57
|
}
|
|
54
58
|
|
|
@@ -143,16 +147,12 @@ const encodeBinaryNodeInner = ({ tag, attrs, content }, opts, buffer) => {
|
|
|
143
147
|
}
|
|
144
148
|
pushByte(roundedLength)
|
|
145
149
|
const packFunction = type === 'nibble' ? packNibble : packHex
|
|
146
|
-
const packBytePair = (v1, v2) => {
|
|
147
|
-
const result = (packFunction(v1) << 4) | packFunction(v2)
|
|
148
|
-
return result
|
|
149
|
-
}
|
|
150
150
|
const strLengthHalf = Math.floor(str.length / 2)
|
|
151
151
|
for (let i = 0; i < strLengthHalf; i++) {
|
|
152
|
-
|
|
152
|
+
buffer.push((packFunction(str[2 * i]) << 4) | packFunction(str[2 * i + 1]))
|
|
153
153
|
}
|
|
154
154
|
if (str.length % 2 !== 0) {
|
|
155
|
-
|
|
155
|
+
buffer.push((packFunction(str[str.length - 1]) << 4) | 15)
|
|
156
156
|
}
|
|
157
157
|
}
|
|
158
158
|
|