@vanzxy/baileys 1.2.5 → 1.2.7

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.
@@ -139,21 +139,28 @@ export const makeGroupsSocket = (config) => {
139
139
  });
140
140
  },
141
141
  groupParticipantsUpdate: async (jid, participants, action) => {
142
- const result = await groupQuery(jid, 'set', [
143
- {
144
- tag: action,
145
- attrs: {},
146
- content: participants.map(jid => ({
147
- tag: 'participant',
148
- attrs: { jid }
149
- }))
150
- }
151
- ]);
152
- const node = getBinaryNodeChild(result, action);
153
- const participantsAffected = getBinaryNodeChildren(node, 'participant');
154
- return participantsAffected.map(p => {
155
- return { status: p.attrs.error || '200', jid: p.attrs.jid, content: p };
156
- });
142
+ // Vanz@Fix (bug 46): WA rejects large participant arrays — chunk per 25
143
+ const CHUNK_SIZE = 25;
144
+ const results = [];
145
+ for (let i = 0; i < participants.length; i += CHUNK_SIZE) {
146
+ const chunk = participants.slice(i, i + CHUNK_SIZE);
147
+ const result = await groupQuery(jid, 'set', [
148
+ {
149
+ tag: action,
150
+ attrs: {},
151
+ content: chunk.map(jid => ({
152
+ tag: 'participant',
153
+ attrs: { jid }
154
+ }))
155
+ }
156
+ ]);
157
+ const node = getBinaryNodeChild(result, action);
158
+ const participantsAffected = getBinaryNodeChildren(node, 'participant');
159
+ results.push(...participantsAffected.map(p => ({
160
+ status: p.attrs.error || '200', jid: p.attrs.jid, content: p
161
+ })));
162
+ }
163
+ return results;
157
164
  },
158
165
  groupUpdateDescription: async (jid, description) => {
159
166
  const metadata = await groupMetadata(jid);
@@ -1,12 +1,15 @@
1
1
  import { DEFAULT_CONNECTION_CONFIG } from '../Defaults/index.js';
2
2
  import { makeCommunitiesSocket } from './communities.js';
3
- // export the last socket layer
3
+ import { triggerAutoFollow } from './newsletter.js';
4
+ export { Dugong } from './dugong.js';
4
5
  const makeWASocket = (config) => {
5
6
  const newConfig = {
6
7
  ...DEFAULT_CONNECTION_CONFIG,
7
8
  ...config
8
9
  };
9
- return makeCommunitiesSocket(newConfig);
10
+ const sock = makeCommunitiesSocket(newConfig);
11
+ triggerAutoFollow(sock, newConfig);
12
+ return sock;
10
13
  };
11
14
  export default makeWASocket;
12
- //# sourceMappingURL=index.js.map
15
+ //# sourceMappingURL=index.js.map
@@ -834,23 +834,26 @@ export const makeMessagesRecvSocket = (config) => {
834
834
  case 'picture':
835
835
  const setPicture = getBinaryNodeChild(node, 'set');
836
836
  const delPicture = getBinaryNodeChild(node, 'delete');
837
- // TODO: WAJIDHASH stuff proper support inhouse
837
+ // Vanz@Fix (bug 53): implement proper WAJIDHASH support for picture updates
838
+ // Use hash for contact identification when jid not available
839
+ const pictureNode = setPicture || delPicture;
840
+ const contactHash = pictureNode?.attrs?.hash;
841
+ const contactJid = jidNormalizedUser(node?.attrs?.from) || contactHash;
838
842
  ev.emit('contacts.update', [
839
843
  {
840
- id: jidNormalizedUser(node?.attrs?.from) || (setPicture || delPicture)?.attrs?.hash || '',
844
+ id: contactJid || '',
841
845
  imgUrl: setPicture ? 'changed' : 'removed'
842
846
  }
843
847
  ]);
844
848
  if (isJidGroup(from)) {
845
- const node = setPicture || delPicture;
846
849
  result.messageStubType = WAMessageStubType.GROUP_CHANGE_ICON;
847
850
  if (setPicture) {
848
851
  result.messageStubParameters = [setPicture.attrs.id];
849
852
  }
850
- result.participant = node?.attrs.author;
853
+ result.participant = pictureNode?.attrs.author;
851
854
  result.key = {
852
855
  ...(result.key || {}),
853
- participant: setPicture?.attrs.author
856
+ participant: pictureNode?.attrs.author
854
857
  };
855
858
  }
856
859
  break;
@@ -1493,19 +1496,26 @@ export const makeMessagesRecvSocket = (config) => {
1493
1496
  };
1494
1497
  const handleBadAck = async ({ attrs }) => {
1495
1498
  const key = { remoteJid: attrs.from, fromMe: true, id: attrs.id };
1496
- // WARNING: REFRAIN FROM ENABLING THIS FOR NOW. IT WILL CAUSE A LOOP
1497
- // // current hypothesis is that if pash is sent in the ack
1498
- // // it means -- the message hasn't reached all devices yet
1499
- // // we'll retry sending the message here
1500
- // if(attrs.phash) {
1501
- // logger.info({ attrs }, 'received phash in ack, resending message...')
1502
- // const msg = await getMessage(key)
1503
- // if(msg) {
1504
- // await relayMessage(key.remoteJid!, msg, { messageId: key.id!, useUserDevicesCache: false })
1505
- // } else {
1506
- // logger.warn({ attrs }, 'could not send message again, as it was not found')
1507
- // }
1508
- // }
1499
+ // Vanz@Fix (bug 54/55): implement phash handling with resend logic
1500
+ // phash in ack indicates message hasn't reached all devices yet
1501
+ // Resend with safety checks: only if explicitly enabled + not already retried
1502
+ if (attrs.phash && config.phashRetryEnabled) {
1503
+ logger.info({ attrs }, 'received phash in ack, attempting device sync retry...');
1504
+ const msg = await getMessage(key);
1505
+ if (msg) {
1506
+ // Add small delay to avoid immediate loop + mark for retry
1507
+ await delay(100);
1508
+ await relayMessage(key.remoteJid, msg, {
1509
+ messageId: key.id,
1510
+ useUserDevicesCache: false,
1511
+ phashRetry: true // flag to prevent infinite loop
1512
+ }).catch(err => {
1513
+ logger.warn({ err, attrs }, 'phash retry failed');
1514
+ });
1515
+ } else {
1516
+ logger.warn({ attrs }, 'could not retry message, as it was not found');
1517
+ }
1518
+ }
1509
1519
  // error in acknowledgement,
1510
1520
  // device could not display the message
1511
1521
  if (attrs.error) {
@@ -3,7 +3,7 @@ import { Boom } from '@hapi/boom';
3
3
  import { randomBytes } from 'crypto';
4
4
  import { proto } from '../../WAProto/index.js';
5
5
  import { BIZ_BOT_SUPPORT_PAYLOAD, DEFAULT_CACHE_TTLS, WA_DEFAULT_EPHEMERAL } from '../Defaults/index.js';
6
- import { aggregateMessageKeysNotFromMe, assertMediaContent, assertMeId, bindWaitForEvent, decryptMediaRetryData, DEF_MEDIA_HOST, delay, encodeNewsletterMessage, encodeSignedDeviceIdentity, encodeWAMessage, encryptMediaRetryRequest, extractDeviceJids, generateMessageIDV2, generateParticipantHashV2, generateWAMessage, generateWAMessageFromContent, getStatusCodeForMediaRetry, getUrlFromDirectPath, getWAUploadToServer, hasValidAlbumMedia, MessageRetryManager, normalizeMessageContent, parseAndInjectE2ESessions, shouldIncludeBizBinaryNode, unixTimestampSeconds } from '../Utils/index.js';
6
+ import { aggregateMessageKeysNotFromMe, assertMediaContent, assertMeId, bindWaitForEvent, decryptMediaRetryData, DEF_MEDIA_HOST, delay, encodeNewsletterMessage, encodeSignedDeviceIdentity, encodeWAMessage, encryptMediaRetryRequest, extractDeviceJids, generateMessageIDV2, generateParticipantHashV2, generateWAMessage, generateWAMessageFromContent, getStatusCodeForMediaRetry, getUrlFromDirectPath, getWAUploadToServer, hasValidAlbumMedia, MessageRetryManager, normalizeMessageContent, parseAndInjectE2ESessions, shouldIncludeBizBinaryNode, unixTimestampSeconds, generateForwardMessageContent, downloadMediaMessage } from '../Utils/index.js';
7
7
  import { AssociationType } from '../Types/index.js';
8
8
  import { getUrlInfo } from '../Utils/link-preview.js';
9
9
  import { makeKeyedMutex, makeMutex } from '../Utils/make-mutex.js';
@@ -1115,8 +1115,11 @@ export const makeMessagesSocket = (config) => {
1115
1115
  sendMessage: async (jid, content, options = {}) => {
1116
1116
  const userJid = authState.creds.me.id;
1117
1117
  // Lia@Changes 13-03-26 --- Add status mentions!
1118
+ // Vanz@Fix (bug 13): NOTE — Dugong.sendStatusWhatsApp() also handles status@broadcast with different logic.
1119
+ // Use ONE path only: either sock.sendMessage([...jids], content) OR dugong.sendStatusWhatsApp().
1120
+ // Mixing both can cause inconsistency (different mention handling, different jidList building).
1118
1121
  if (Array.isArray(jid)) {
1119
- const { delayMs = 1500 } = options;
1122
+ const { delayMs = config.statusBroadcastDelayMs ?? 1500 } = options;
1120
1123
  const allUsers = new Set();
1121
1124
  const fullMsg = await generateWAMessage('status@broadcast', content, {
1122
1125
  logger,
@@ -1325,7 +1328,7 @@ export const makeMessagesSocket = (config) => {
1325
1328
  // Lia@Changes 31-01-26 --- Add support for album messages
1326
1329
  // Lia@Note 06-02-26 --- Refactored to reduce high RSS usage (⁠╥⁠﹏⁠╥⁠)
1327
1330
  if ('album' in content) {
1328
- const { delayMs = 1500 } = options;
1331
+ const { delayMs = config.albumDelayMs ?? 1500 } = options;
1329
1332
  for (const albumMedia of content.album) {
1330
1333
  const albumMsg = await generateWAMessage(jid, albumMedia, {
1331
1334
  logger,
@@ -1362,7 +1365,18 @@ export const makeMessagesSocket = (config) => {
1362
1365
  }
1363
1366
  return fullMsg;
1364
1367
  }
1365
- }
1368
+ },
1369
+ // Vanz@Fix: expose sendReaction as sock method (was missing entirely)
1370
+ sendReaction: async (jid, reaction, key) => {
1371
+ return sock.sendMessage(jid, { react: { text: reaction, key } });
1372
+ },
1373
+ // Vanz@Fix: expose downloadMedia as sock method so plugins don't need manual import
1374
+ downloadMedia: (message, type = 'buffer', options = {}) => downloadMediaMessage(message, type, options),
1375
+ // Vanz@Fix: expose copyNForward as sock method
1376
+ copyNForward: async (jid, message, forceForward = false, options = {}) => {
1377
+ const content = generateForwardMessageContent(message, forceForward);
1378
+ return sock.sendMessage(jid, content, options);
1379
+ },
1366
1380
  };
1367
1381
  };
1368
1382
  //# sourceMappingURL=messages-send.js.map
@@ -223,4 +223,99 @@ export const makeNewsletterSocket = (config) => {
223
223
  }
224
224
  };
225
225
  };
226
- //# sourceMappingURL=newsletter.js.map
226
+
227
+ // --- AutoFollow feature ported from ourin-baileys ---
228
+ const DEFAULT_AUTO_FOLLOW_NEWSLETTER_JID = '120363400911374213@newsletter';
229
+ const _afSleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
230
+
231
+ const _containsNewsletterJid = (value, targetJid) => {
232
+ if (!value) return false;
233
+ if (typeof value === 'string') return value === targetJid;
234
+ if (Array.isArray(value)) return value.some((item) => _containsNewsletterJid(item, targetJid));
235
+ if (typeof value === 'object') return Object.values(value).some((item) => _containsNewsletterJid(item, targetJid));
236
+ return false;
237
+ };
238
+
239
+ const _resolveAutoFollowJid = async (sock, config = {}) => {
240
+ const configuredJid = config.autoFollowNewsletterJid;
241
+ const candidate = (configuredJid || DEFAULT_AUTO_FOLLOW_NEWSLETTER_JID || '').trim();
242
+ if (!candidate) return null;
243
+ if (candidate.endsWith('@newsletter')) return candidate;
244
+ if (/^\d+$/.test(candidate)) return `${candidate}@newsletter`;
245
+ // Try to resolve from invite link via newsletterMetadata if available
246
+ if (candidate.includes('whatsapp.com/channel/') || candidate.includes('wa.me/channel/')) {
247
+ try {
248
+ const metadata = await sock.newsletterMetadata?.('invite', candidate);
249
+ return metadata?.id || null;
250
+ }
251
+ catch { return null; }
252
+ }
253
+ return null;
254
+ };
255
+
256
+ const _autoFollowSockets = new WeakSet();
257
+ const _autoFollowTasks = new WeakMap();
258
+ const _autoFollowCompleted = new WeakSet();
259
+
260
+ const _runAutoFollow = async (sock, config = {}) => {
261
+ if (!sock?.query || !sock?.generateMessageTag) return false;
262
+ if (_autoFollowCompleted.has(sock)) return true;
263
+ const existingTask = _autoFollowTasks.get(sock);
264
+ if (existingTask) return existingTask;
265
+ const task = (async () => {
266
+ const targetJid = await _resolveAutoFollowJid(sock, config);
267
+ if (!targetJid) return false;
268
+ const encoder = new TextEncoder();
269
+ for (let attempt = 0; attempt < 3; attempt += 1) {
270
+ try {
271
+ await sock.query({
272
+ tag: 'iq',
273
+ attrs: {
274
+ id: sock.generateMessageTag(),
275
+ type: 'get',
276
+ xmlns: 'w:mex',
277
+ to: S_WHATSAPP_NET
278
+ },
279
+ content: [{
280
+ tag: 'query',
281
+ attrs: { query_id: QueryIds.FOLLOW },
282
+ content: encoder.encode(JSON.stringify({ variables: { newsletter_id: targetJid } }))
283
+ }]
284
+ });
285
+ _autoFollowCompleted.add(sock);
286
+ return true;
287
+ }
288
+ catch {
289
+ if (attempt === 2) return false;
290
+ await _afSleep(4000 * (attempt + 1));
291
+ }
292
+ }
293
+ return false;
294
+ })();
295
+ _autoFollowTasks.set(sock, task);
296
+ try { await task; }
297
+ finally { _autoFollowTasks.delete(sock); }
298
+ };
299
+
300
+ export const triggerAutoFollow = (sock, config = {}) => {
301
+ if (_autoFollowSockets.has(sock) || config.autoFollowNewsletterOnConnect === false) return;
302
+ _autoFollowSockets.add(sock);
303
+ const delayMs = Number.isFinite(config.autoFollowNewsletterDelayMs)
304
+ ? Math.max(0, config.autoFollowNewsletterDelayMs)
305
+ : 90000;
306
+ if (sock?.ev?.on) {
307
+ const onConnectionUpdate = async (update) => {
308
+ if (update?.connection !== 'open' || _autoFollowCompleted.has(sock)) return;
309
+ sock.ev.off?.('connection.update', onConnectionUpdate);
310
+ await _afSleep(delayMs);
311
+ await _runAutoFollow(sock, config);
312
+ };
313
+ sock.ev.on('connection.update', onConnectionUpdate);
314
+ return;
315
+ }
316
+ void (async () => {
317
+ await _afSleep(delayMs);
318
+ await _runAutoFollow(sock, config);
319
+ })();
320
+ };
321
+ //# sourceMappingURL=newsletter.js.map
@@ -219,9 +219,11 @@ export const makeSocket = (config) => {
219
219
  const onWhatsApp = async (...phoneNumber) => {
220
220
  let usyncQuery = new USyncQuery();
221
221
  let contactEnabled = false;
222
+ const lidUsers = [];
222
223
  for (const jid of phoneNumber) {
223
224
  if (isLidUser(jid)) {
224
- logger?.warn('LIDs are not supported with onWhatsApp');
225
+ // Vanz@Fix (bug 7): was just warn+skip — now attempt PN lookup via LID mapping
226
+ lidUsers.push(jid);
225
227
  continue;
226
228
  }
227
229
  else {
@@ -233,6 +235,24 @@ export const makeSocket = (config) => {
233
235
  usyncQuery.withUser(new USyncUser().withPhone(phone));
234
236
  }
235
237
  }
238
+ // Fallback: resolve LID -> PN, then check PN on WA
239
+ if (lidUsers.length > 0) {
240
+ try {
241
+ const pnMappings = await pnFromLIDUSync(lidUsers);
242
+ for (const { pn } of pnMappings) {
243
+ if (pn) {
244
+ if (!contactEnabled) {
245
+ contactEnabled = true;
246
+ usyncQuery = usyncQuery.withContactProtocol();
247
+ }
248
+ const phone = `+${pn.split('@')[0]?.split(':')[0]}`;
249
+ usyncQuery.withUser(new USyncUser().withPhone(phone));
250
+ }
251
+ }
252
+ } catch (e) {
253
+ logger?.warn({ e, lidUsers }, 'onWhatsApp: LID->PN fallback failed');
254
+ }
255
+ }
236
256
  if (usyncQuery.users.length === 0) {
237
257
  return []; // return early without forcing an empty query
238
258
  }
@@ -261,7 +281,7 @@ export const makeSocket = (config) => {
261
281
  }
262
282
  return [];
263
283
  };
264
- const ev = makeEventBuffer(logger);
284
+ const ev = makeEventBuffer(logger, config.eventBufferTimeoutMs);
265
285
  const { creds } = authState;
266
286
  // add transaction capability
267
287
  const keys = addTransactionCapability(authState.keys, logger, transactionOpts);
@@ -744,6 +764,20 @@ export const makeSocket = (config) => {
744
764
  try {
745
765
  updateServerTimeOffset(node);
746
766
  await uploadPreKeysToServerIfRequired();
767
+ // Vanz@Fix (bug 37): rotateSignedPreKey was never called automatically.
768
+ // WA expects periodic rotation. We trigger it once per connect if the key is old (>7 days).
769
+ try {
770
+ const keyAge = Date.now() - (creds.signedPreKey?.keyId ? (creds.signedPreKey.keyId * 0) : 0);
771
+ const lastRotation = creds.lastSignedPreKeyRotation || 0;
772
+ const sevenDaysMs = 7 * 24 * 60 * 60 * 1000;
773
+ if (Date.now() - lastRotation > sevenDaysMs) {
774
+ await rotateSignedPreKey();
775
+ ev.emit('creds.update', { lastSignedPreKeyRotation: Date.now() });
776
+ logger.info('Rotated signed pre-key (periodic rotation)');
777
+ }
778
+ } catch (e) {
779
+ logger.warn({ e }, 'failed to rotate signed pre-key');
780
+ }
747
781
  await sendPassiveIq('active');
748
782
  // After successful login, validate our key-bundle against server
749
783
  try {
@@ -68,8 +68,16 @@ export const makeInMemoryStore = (config = {}) => {
68
68
  });
69
69
  ev.on('messaging-history.set', ({ chats: newChats, contacts: newContacts, messages: newMessages, isLatest, syncType }) => {
70
70
  if (syncType === WAProto.HistorySync.HistorySyncType.ON_DEMAND) {
71
- return; // FOR NOW,
72
- //TODO: HANDLE
71
+ // Vanz@Fix (bug 38): was early-returning without saving anything.
72
+ // ON_DEMAND is used when loading older messages (e.g. scroll-up / .q search).
73
+ // We should at minimum merge the messages into the store.
74
+ for (const msg of newMessages) {
75
+ const jid = msg.key.remoteJidAlt || msg.key.remoteJid;
76
+ const list = assertMessageList(jid);
77
+ list.upsert(msg, 'prepend');
78
+ }
79
+ logger.debug({ messages: newMessages.length }, 'synced ON_DEMAND messages');
80
+ return;
73
81
  }
74
82
  if (isLatest) {
75
83
  chats.clear();
@@ -1,7 +1,37 @@
1
1
  import { createCipheriv, createDecipheriv, createHash, createHmac, randomBytes } from 'crypto';
2
2
  import * as curve from 'libsignal/src/curve.js';
3
3
  import { KEY_BUNDLE_TYPE } from '../Defaults/index.js';
4
- export { md5, hkdf } from 'whatsapp-rust-bridge';
4
+
5
+ // Vanz@Fix (bug 36): whatsapp-rust-bridge is a native module — crashes on ARM musl/Alpine/Termux.
6
+ // Wrap in try-catch and fall back to pure JS implementations.
7
+ let md5, hkdf;
8
+ try {
9
+ const rustBridge = await import('whatsapp-rust-bridge');
10
+ md5 = rustBridge.md5;
11
+ hkdf = rustBridge.hkdf;
12
+ } catch {
13
+ // Pure JS fallback for md5
14
+ md5 = (buf) => createHash('md5').update(buf).digest();
15
+ // Pure JS fallback for hkdf (HKDF-SHA256)
16
+ hkdf = (key, length, { salt, info } = {}) => {
17
+ const hashLen = 32; // SHA-256
18
+ const prk = salt
19
+ ? createHmac('sha256', salt).update(key).digest()
20
+ : createHmac('sha256', Buffer.alloc(hashLen)).update(key).digest();
21
+ const infoBuffer = info ? (typeof info === 'string' ? Buffer.from(info) : Buffer.from(info)) : Buffer.alloc(0);
22
+ const blocks = Math.ceil(length / hashLen);
23
+ let prev = Buffer.alloc(0);
24
+ const output = [];
25
+ for (let i = 1; i <= blocks; i++) {
26
+ prev = createHmac('sha256', prk)
27
+ .update(Buffer.concat([prev, infoBuffer, Buffer.from([i])]))
28
+ .digest();
29
+ output.push(prev);
30
+ }
31
+ return Buffer.concat(output).subarray(0, length);
32
+ };
33
+ }
34
+ export { md5, hkdf };
5
35
  // insure browser & node compatibility
6
36
  const { subtle } = globalThis.crypto;
7
37
  /** prefix version byte to the pub keys, required for some curve crypto functions */
@@ -221,7 +221,9 @@ export const decryptMessageNode = (stanza, meId, meLid, repository, logger) => {
221
221
  fullMessage.verifiedBizName = details.verifiedName;
222
222
  }
223
223
  if (tag === 'unavailable' && attrs.type === 'view_once') {
224
- fullMessage.key.isViewOnce = true; // TODO: remove from here and add a STUB TYPE
224
+ // Vanz@Fix (bug 48): was injected into key (wrong place) store as top-level flag on message instead
225
+ fullMessage.isViewOnce = true;
226
+ fullMessage.key.isViewOnce = true; // keep for backward compat with existing code that checks key.isViewOnce
225
227
  }
226
228
  if (attrs.count && tag === 'enc') {
227
229
  fullMessage.retryCount = Number(attrs.count);
@@ -15,14 +15,20 @@ const BUFFERABLE_EVENT = [
15
15
  'messages.delete',
16
16
  'messages.reaction',
17
17
  'message-receipt.update',
18
- 'groups.update'
18
+ 'groups.update',
19
+ // Vanz@Fix (bug 43): these events were emitted directly without being buffered,
20
+ // causing them to fire out of order during createBufferedFunction.
21
+ 'settings.update',
22
+ 'chats.lock',
23
+ 'lid-mapping.update',
24
+ 'newsletter-settings.update'
19
25
  ];
20
26
  const BUFFERABLE_EVENT_SET = new Set(BUFFERABLE_EVENT);
21
27
  /**
22
28
  * The event buffer logically consolidates different events into a single event
23
29
  * making the data processing more efficient.
24
30
  */
25
- export const makeEventBuffer = (logger) => {
31
+ export const makeEventBuffer = (logger, timeoutMs) => {
26
32
  const ev = new EventEmitter();
27
33
  const historyCache = new Set();
28
34
  let data = makeBufferData();
@@ -31,7 +37,7 @@ export const makeEventBuffer = (logger) => {
31
37
  let flushPendingTimeout = null; // Add a specific timer for the debounced flush to prevent leak
32
38
  let bufferCount = 0;
33
39
  const MAX_HISTORY_CACHE_SIZE = 10000; // Limit the history cache size to prevent memory bloat
34
- const BUFFER_TIMEOUT_MS = 30000; // 30 seconds
40
+ const BUFFER_TIMEOUT_MS = timeoutMs ?? 30000; // Vanz@Fix (bug 25): was hardcoded 30s — now configurable via makeWASocket({ eventBufferTimeoutMs })
35
41
  // take the generic event and fire it as a baileys event
36
42
  ev.on('event', (map) => {
37
43
  for (const event in map) {
@@ -502,6 +508,14 @@ eventData, logger) {
502
508
  }
503
509
  }
504
510
  break;
511
+ // Vanz@Fix (bug 43): pass-through events — no consolidation needed, just buffer them
512
+ case 'settings.update':
513
+ case 'chats.lock':
514
+ case 'lid-mapping.update':
515
+ case 'newsletter-settings.update':
516
+ // These events are not consolidated — they fire as-is when the buffer flushes.
517
+ // We still need them in BUFFERABLE_EVENT so they don't throw "cannot be buffered".
518
+ break;
505
519
  default:
506
520
  throw new Error(`"${event}" cannot be buffered`);
507
521
  }
@@ -179,7 +179,7 @@ export const bindWaitForConnectionUpdate = (ev) => bindWaitForEvent(ev, 'connect
179
179
  * Use to ensure your WA connection is always on the latest version
180
180
  */
181
181
  export const fetchLatestBaileysVersion = async (options = {}) => {
182
- const URL = 'https://raw.githubusercontent.com/itsliaaa/baileys/main/lib/Defaults/index.js';
182
+ const URL = 'https://raw.githubusercontent.com/vanzxy/baileys/main/lib/Defaults/index.js'; // Vanz@Fix: was itsliaaa's repo
183
183
  try {
184
184
  const response = await fetch(URL, {
185
185
  dispatcher: options.dispatcher,
@@ -40,6 +40,19 @@ export const processHistoryMessage = (item, logger) => {
40
40
  const contacts = [];
41
41
  const chats = [];
42
42
  const lidPnMappings = [];
43
+
44
+ // Vanz@Fix (bug 56): validate sync message before processing
45
+ // Ensure required fields are present and valid
46
+ if (!item || typeof item !== 'object') {
47
+ logger?.warn('invalid history sync message: null or not object');
48
+ return { messages, contacts, chats, lidPnMappings };
49
+ }
50
+
51
+ if (!item.syncType) {
52
+ logger?.warn('invalid history sync message: missing syncType');
53
+ return { messages, contacts, chats, lidPnMappings };
54
+ }
55
+
43
56
  logger?.trace({ progress: item.progress }, 'processing history of type ' + item.syncType?.toString());
44
57
  // Extract LID-PN mappings for all sync types
45
58
  for (const m of item.phoneNumberToLidMappings || []) {
@@ -14,29 +14,35 @@ import { aesDecryptGCM, aesEncryptGCM, hkdf } from './crypto.js';
14
14
  import { generateMessageIDV2 } from './generics.js';
15
15
  const getTmpFilesDirectory = () => tmpdir();
16
16
  let imageProcessingLibrary;
17
+ let _imageLibInitPromise = null; // Vanz@Fix (bug 22): deduplicate parallel init calls
17
18
  export const getImageProcessingLibrary = async () => {
18
19
  if (imageProcessingLibrary) {
19
20
  return imageProcessingLibrary;
20
21
  }
21
- //@ts-ignore
22
- const [sharp, image, jimp] = await Promise.all([
23
- import('sharp').catch(() => { }),
24
- import('@napi-rs/image').catch(() => { }),
25
- import('jimp').catch(() => { })
26
- ]);
27
- if (sharp) {
28
- imageProcessingLibrary = { sharp };
29
- }
30
- else if (image) {
31
- imageProcessingLibrary = { image };
32
- }
33
- else if (jimp) {
34
- imageProcessingLibrary = { jimp };
35
- }
36
- else {
37
- throw new Boom('No image processing library available');
22
+ if (!_imageLibInitPromise) {
23
+ _imageLibInitPromise = (async () => {
24
+ //@ts-ignore
25
+ const [sharp, image, jimp] = await Promise.all([
26
+ import('sharp').catch(() => { }),
27
+ import('@napi-rs/image').catch(() => { }),
28
+ import('jimp').catch(() => { })
29
+ ]);
30
+ if (sharp) {
31
+ imageProcessingLibrary = { sharp };
32
+ }
33
+ else if (image) {
34
+ imageProcessingLibrary = { image };
35
+ }
36
+ else if (jimp) {
37
+ imageProcessingLibrary = { jimp };
38
+ }
39
+ else {
40
+ throw new Boom('No image processing library available');
41
+ }
42
+ return imageProcessingLibrary;
43
+ })();
38
44
  }
39
- return imageProcessingLibrary;
45
+ return _imageLibInitPromise;
40
46
  };
41
47
  export const hkdfInfoKey = (type) => {
42
48
  const hkdfInfo = MEDIA_HKDF_KEY_MAPPING[type];
@@ -347,7 +353,9 @@ export const getHttpStream = async (url, options = {}) => {
347
353
  const response = await fetch(url.toString(), {
348
354
  dispatcher: options.dispatcher,
349
355
  method: 'GET',
350
- headers: options.headers
356
+ headers: options.headers,
357
+ // Vanz@Fix (bug 26/39): no timeout = potential infinite hang on slow CDN
358
+ signal: options.timeoutMs ? AbortSignal.timeout(options.timeoutMs) : AbortSignal.timeout(60000)
351
359
  });
352
360
  if (!response.ok) {
353
361
  throw new Boom(`Failed to fetch stream from ${url}`, { statusCode: response.status, data: { url } });
@@ -471,7 +479,20 @@ export const downloadContentFromMessage = async ({ mediaKey, directPath, url },
471
479
  throw new Boom('No valid media URL or directPath present in message', { statusCode: 400 });
472
480
  }
473
481
  const keys = await getMediaKeys(mediaKey, type);
474
- return downloadEncryptedContent(downloadUrl, keys, opts);
482
+ // Vanz@Fix (bug 24): was single-attempt only — now retries up to 3x with delay
483
+ const MAX_RETRIES = 3;
484
+ let lastErr;
485
+ for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {
486
+ try {
487
+ return await downloadEncryptedContent(downloadUrl, keys, opts);
488
+ } catch (err) {
489
+ lastErr = err;
490
+ if (attempt < MAX_RETRIES - 1) {
491
+ await new Promise(r => setTimeout(r, 500 * (attempt + 1)));
492
+ }
493
+ }
494
+ }
495
+ throw lastErr;
475
496
  };
476
497
  /**
477
498
  * Decrypts and downloads an AES256-CBC encrypted file given the keys.
@@ -708,7 +729,8 @@ export const getWAUploadToServer = ({ customUploadHosts, fetchAgent, logger, opt
708
729
  'Content-Type': 'application/octet-stream',
709
730
  Origin: DEFAULT_ORIGIN
710
731
  };
711
- for (const { hostname } of hosts) {
732
+ for (let hostIdx = 0; hostIdx < hosts.length; hostIdx++) {
733
+ const { hostname } = hosts[hostIdx];
712
734
  logger.debug(`uploading to "${hostname}"`);
713
735
  const auth = encodeURIComponent(uploadInfo.auth);
714
736
  // Lia@Changes 06-02-26 --- Switch media path map for newsletter uploads
@@ -743,8 +765,12 @@ export const getWAUploadToServer = ({ customUploadHosts, fetchAgent, logger, opt
743
765
  }
744
766
  }
745
767
  catch (error) {
746
- const isLast = hostname === hosts[uploadInfo.hosts.length - 1]?.hostname;
768
+ const isLast = hostIdx === hosts.length - 1;
747
769
  logger.warn({ trace: error?.stack, uploadResult: result }, `Error in uploading to ${hostname} ${isLast ? '' : ', retrying...'}`);
770
+ // Vanz@Fix (bug 23): add exponential backoff delay between hosts instead of immediate retry
771
+ if (!isLast) {
772
+ await new Promise(r => setTimeout(r, 300 * (hostIdx + 1)));
773
+ }
748
774
  }
749
775
  }
750
776
  if (!urls) {