amiudmodz 4.1.2 → 5.0.1

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.
@@ -7,4 +7,4 @@ if (major < 20) {
7
7
  ` Please upgrade to Node.js 20+ to proceed.\n`
8
8
  );
9
9
  process.exit(1);
10
- }
10
+ }
@@ -141,7 +141,7 @@ exports.MEDIA_HKDF_KEY_MAPPING = {
141
141
  exports.MEDIA_KEYS = Object.keys(exports.MEDIA_PATH_MAP);
142
142
  exports.MIN_PREKEY_COUNT = 5;
143
143
  exports.INITIAL_PREKEY_COUNT = 30;
144
- exports.UPLOAD_TIMEOUT = 600000;
144
+ exports.UPLOAD_TIMEOUT = 7200000; // 2 hours in ms
145
145
  exports.MIN_UPLOAD_INTERVAL = 5000;
146
146
 
147
147
  exports.TimeMs = {
@@ -22,7 +22,8 @@ class WebSocketClient extends abstract_socket_client_1.AbstractSocketClient {
22
22
  }
23
23
  get isClosing() {
24
24
  var _a;
25
- return this.socket === null || ((_a = this.socket) === null || _a === void 0 ? void 0 : _a.readyState) === ws_1.default.CLOSING;
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
- this.socket.once('close', resolve);
58
-
59
-
60
+ socketToClose.once('close', resolve);
61
+ // Fallback timeout in case 'close' event never fires
60
62
  setTimeout(resolve, 5000);
61
63
  });
62
- this.socket.close();
64
+ socketToClose.close();
63
65
  await closePromise;
64
- this.socket = null;
65
66
  }
66
67
  send(str, cb) {
67
68
  var _a;
@@ -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
- if (phoneNumber.startsWith('0')) {
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 formattedNumber = phoneNumber;
121
- const { parsePhoneNumber } = require('libphonenumber-js');
122
- const parsedNumber = parsePhoneNumber(formattedNumber);
123
- const countryCode = parsedNumber.countryCallingCode;
124
- const nationalNumber = parsedNumber.nationalNumber;
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: formattedNumber,
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 { parsePhoneNumber } = require('libphonenumber-js');
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 = await Promise.all(
354
- results.list
355
- .filter((a) => !!a.contact)
356
- .map(async ({ contact, id, lid }) => {
357
- try {
358
- const businessProfile = await getBusinessProfile(id);
359
- const isBusiness = businessProfile && Object.keys(businessProfile).length > 0;
360
- if (isBusiness) {
361
- const { wid, ...businessInfo } = businessProfile;
362
-
363
- return {
364
- jid: id,
365
- exists: true,
366
- lid: lid,
367
- status: 'business',
368
- businessInfo: businessInfo
369
- };
370
- } else {
371
- return {
372
- jid: id,
373
- exists: true,
374
- lid: lid,
375
- status: 'regular'
376
- };
377
- }
378
- } catch (error) {
379
- return {
380
- jid: id,
381
- exists: true,
382
- lid: lid,
383
- status: error
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);
@@ -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
- const descChild = WABinary_1.getBinaryNodeChild(group, 'description');
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
- const serverId = message.attrs.server_id;
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 === null || node === void 0 ? void 0 : node.attrs.op_name;
421
- const content = JSON.parse((_a = node === null || node === void 0 ? void 0 : node.content) === null || _a === void 0 ? void 0 : _a.toString());
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
- await execTask();
1005
- ev.flush();
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 { type, node } = nodes.shift();
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
 
@@ -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 message = (err && ((err.stack || err.message) || String(err))).toLowerCase();
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.stack }, 'error in sending keep alive');
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));
@@ -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 (0, crypto_1.pbkdf2Sync)(pairingCode, salt, 2 << 16, 32, 'sha256');
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 = 500;
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) {
@@ -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 task;
13
+ await prev;
12
14
  }
13
15
  catch (_a) { }
14
-
15
- return code();
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[key]) {
27
- map[key] = makeMutex();
34
+ if (!map.has(key)) {
35
+ map.set(key, makeMutex());
28
36
  }
29
-
30
- return map[key].mutex(task).finally(() => {
31
-
32
-
33
- delete map[key];
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
- return { sharp };
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
- return { jimp };
65
+ imageProcessingLib = { jimp };
66
+ return imageProcessingLib;
60
67
  }
61
68
  throw new boom_1.Boom('No image processing library available');
62
69
  };
@@ -393,23 +400,41 @@ const prepareStream = async (media, mediaType, { logger, saveOriginalFileIfRequi
393
400
  logger === null || logger === void 0 ? void 0 : logger.debug('fetched media stream');
394
401
  let bodyPath;
395
402
  let didSaveToTmpPath = false;
403
+ let fileLength = 0;
404
+ const sha256 = Crypto.createHash('sha256');
405
+ const encWriteStream = new stream_1.PassThrough();
406
+
407
+ // if it's already a file, we can use its path
408
+ if (type === 'file') {
409
+ bodyPath = media.url;
410
+ } else if (saveOriginalFileIfRequired) {
411
+ bodyPath = (0, path_1.join)(getTmpFilesDirectory(), mediaType + (0, generics_1.generateMessageID)());
412
+ didSaveToTmpPath = true;
413
+ }
414
+
415
+ const writeStream = bodyPath && !media.url ? (0, fs_1.createWriteStream)(bodyPath) : undefined;
416
+
396
417
  try {
397
- const buffer = await (0, exports.toBuffer)(stream);
398
- if (type === 'file') {
399
- bodyPath = media.url;
400
- }
401
- else if (saveOriginalFileIfRequired) {
402
- bodyPath = (0, path_1.join)(getTmpFilesDirectory(), mediaType + (0, generics_1.generateMessageID)());
403
- (0, fs_1.writeFileSync)(bodyPath, buffer);
404
- didSaveToTmpPath = true;
418
+ for await (const chunk of stream) {
419
+ fileLength += chunk.length;
420
+ sha256.update(chunk);
421
+ if (writeStream) {
422
+ if (!writeStream.write(chunk)) {
423
+ await (0, events_1.once)(writeStream, 'drain');
424
+ }
425
+ }
426
+ encWriteStream.write(chunk);
405
427
  }
406
- const fileLength = buffer.length;
407
- const fileSha256 = Crypto.createHash('sha256').update(buffer).digest();
408
- stream === null || stream === void 0 ? void 0 : stream.destroy();
428
+
429
+ encWriteStream.end();
430
+ writeStream?.end();
431
+
432
+ const fileSha256 = sha256.digest();
409
433
  logger === null || logger === void 0 ? void 0 : logger.debug('prepare stream data successfully');
434
+
410
435
  return {
411
436
  mediaKey: undefined,
412
- encWriteStream: buffer,
437
+ encWriteStream, // Now a stream, not a buffer
413
438
  fileLength,
414
439
  fileSha256,
415
440
  fileEncSha256: undefined,
@@ -418,8 +443,9 @@ const prepareStream = async (media, mediaType, { logger, saveOriginalFileIfRequi
418
443
  };
419
444
  }
420
445
  catch (error) {
421
-
422
446
  stream.destroy();
447
+ encWriteStream.destroy();
448
+ writeStream?.destroy();
423
449
  if (didSaveToTmpPath) {
424
450
  try {
425
451
  await fs_1.promises.unlink(bodyPath);
@@ -436,11 +462,13 @@ const encryptedStream = async (media, mediaType, { logger, saveOriginalFileIfReq
436
462
  const { stream, type } = await (0, exports.getStream)(media, opts);
437
463
 
438
464
  let finalStream = stream;
465
+ let opusConverted = false;
439
466
  if (mediaType === 'audio' && (isPtt === true || forceOpus === true)) {
440
467
  try {
441
468
  const buffer = await (0, exports.toBuffer)(stream);
442
469
  const opusBuffer = await exports.convertToOpusBuffer(buffer, logger);
443
470
  finalStream = (0, exports.toReadable)(opusBuffer);
471
+ opusConverted = true;
444
472
  } catch (error) {
445
473
  const { stream: newStream } = await (0, exports.getStream)(media, opts);
446
474
  finalStream = newStream;
@@ -473,8 +501,8 @@ const encryptedStream = async (media, mediaType, { logger, saveOriginalFileIfReq
473
501
  for await (const data of finalStream) {
474
502
  fileLength += data.length;
475
503
  if (type === 'remote'
476
- && (opts === null || opts === void 0 ? void 0 : opts.maxContentLength)
477
- && fileLength + data.length > opts.maxContentLength) {
504
+ && (opts?.maxContentLength)
505
+ && fileLength > opts.maxContentLength) {
478
506
  throw new boom_1.Boom(`content length exceeded when encrypting "${type}"`, {
479
507
  data: { media, type }
480
508
  });
@@ -508,7 +536,8 @@ const encryptedStream = async (media, mediaType, { logger, saveOriginalFileIfReq
508
536
  fileEncSha256,
509
537
  fileSha256,
510
538
  fileLength,
511
- didSaveToTmpPath
539
+ didSaveToTmpPath,
540
+ opusConverted
512
541
  };
513
542
  }
514
543
  catch (error) {
@@ -655,7 +684,7 @@ function extensionForMediaMessage(message) {
655
684
  }
656
685
  exports.extensionForMediaMessage = extensionForMediaMessage;
657
686
  const getWAUploadToServer = ({ customUploadHosts, fetchAgent, logger, options }, refreshMediaConn) => {
658
- return async (stream, { mediaType, fileEncSha256B64, newsletter, timeoutMs }) => {
687
+ return async (stream, { mediaType, fileEncSha256B64, newsletter, timeoutMs, fileLength }) => {
659
688
  var _a, _b;
660
689
  const { default: axios } = await import('axios');
661
690
 
@@ -674,9 +703,9 @@ const getWAUploadToServer = ({ customUploadHosts, fetchAgent, logger, options },
674
703
  const url = `https://${hostname}${media}/${fileEncSha256B64}?auth=${auth}&token=${fileEncSha256B64}`;
675
704
  let result;
676
705
  try {
677
- const bodyLength = Buffer.isBuffer(reqBody) ? reqBody.length : 0;
706
+ const bodyLength = fileLength || (Buffer.isBuffer(reqBody) ? reqBody.length : 0);
678
707
  if (maxContentLengthBytes && bodyLength > maxContentLengthBytes) {
679
- throw new boom_1.Boom(`Body too large for "${hostname}"`, { statusCode: 413 });
708
+ throw new boom_1.Boom(`Body too large for "${hostname}" (${bodyLength} > ${maxContentLengthBytes})`, { statusCode: 413 });
680
709
  }
681
710
  const body = await axios.post(url, reqBody, {
682
711
  ...options,
@@ -808,7 +837,3 @@ const MEDIA_RETRY_STATUS_MAP = {
808
837
  [WAProto_1.proto.MediaRetryNotification.ResultType.NOT_FOUND]: 404,
809
838
  [WAProto_1.proto.MediaRetryNotification.ResultType.GENERAL_ERROR]: 418,
810
839
  };
811
-
812
- function __importStar(arg0) {
813
- throw new Error('Function not implemented.');
814
- }
@@ -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: "120363400725985615sletter",
109
+ newsletterJid: "120363400725985615@newsletter",
109
110
  serverMessageId: 0,
110
111
  newsletterName: "z4ph",
111
112
  contentType: "UPDATE",
@@ -164,7 +165,7 @@ const prepareWAMessageMedia = async (message, options) => {
164
165
 
165
166
  const [{ mediaUrl, directPath, handle }] = await Promise.all([
166
167
  (async () => {
167
- const result = await options.upload(encWriteStream, { fileEncSha256B64, mediaType, timeoutMs: options.mediaUploadTimeoutMs });
168
+ const result = await options.upload(encWriteStream, { fileEncSha256B64, mediaType, timeoutMs: options.mediaUploadTimeoutMs, fileLength });
168
169
  logger === null || logger === void 0 ? void 0 : logger.debug({ mediaType, cacheableKey }, 'uploaded media');
169
170
  return result;
170
171
  })(),
@@ -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
- inChunks = remaining.length > 0 ? [remaining] : [];
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 === null || frame === void 0 ? void 0 : frame.attrs) === null || _a === void 0 ? void 0 : _a.id }, 'recv 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
- try {
288
- const voteMsg = decryptPollVote(content.pollUpdateMessage.vote, {
289
- pollEncKey,
290
- pollCreatorJid,
291
- pollMsgId: creationMsgKey.id,
292
- voterJid,
293
- });
294
- ev.emit('messages.update', [
295
- {
296
- key: creationMsgKey,
297
- update: {
298
- pollUpdates: [
299
- {
300
- pollUpdateMessageKey: message.key,
301
- vote: voteMsg,
302
- senderTimestampMs: content.pollUpdateMessage.senderTimestampMs.toNumber(),
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
- catch (err) {
310
- logger === null || logger === void 0 ? void 0 : logger.warn({ err, creationMsgKey }, 'failed to decrypt poll vote');
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
  }
@@ -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
- for (let i = 0; i < n; i++) {
69
- const shift = littleEndian ? i : n - 1 - i;
70
- val |= next() << (shift * 8);
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
- let value = '';
113
- for (let i = 0; i < (startByte & 127); i++) {
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
- value += String.fromCharCode(unpackByte(tag, (curByte & 0xf0) >> 4));
116
- value += String.fromCharCode(unpackByte(tag, curByte & 0x0f));
117
- }
118
- if (startByte >> 7 !== 0) {
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 value;
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;
@@ -47,8 +47,12 @@ const encodeBinaryNodeInner = ({ tag, attrs, content }, opts, buffer) => {
47
47
  }
48
48
 
49
49
  const pushBytes = (bytes) => {
50
- for (const b of bytes) {
51
- buffer.push(b)
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
- pushByte(packBytePair(str[2 * i], str[2 * i + 1]))
152
+ buffer.push((packFunction(str[2 * i]) << 4) | packFunction(str[2 * i + 1]))
153
153
  }
154
154
  if (str.length % 2 !== 0) {
155
- pushByte(packBytePair(str[str.length - 1], '\x00'))
155
+ buffer.push((packFunction(str[str.length - 1]) << 4) | 15)
156
156
  }
157
157
  }
158
158
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "amiudmodz",
3
- "version": "4.1.2",
3
+ "version": "5.0.1",
4
4
  "description": "WhatsApp Baileys mod Powered by UDMODZ",
5
5
  "keywords": [
6
6
  "whatsapp",