@prototypevip/baileys 0.0.5 → 0.0.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.
Files changed (54) hide show
  1. package/lib/AntiBan/antiban.js +922 -0
  2. package/lib/AntiBan/banRecoveryOrchestrator.js +284 -0
  3. package/lib/AntiBan/contactGraph.js +236 -0
  4. package/lib/AntiBan/contentVariator.js +150 -0
  5. package/lib/AntiBan/credsSnapshot.js +121 -0
  6. package/lib/AntiBan/deliveryTracker.js +107 -0
  7. package/lib/AntiBan/deviceFingerprint.js +106 -0
  8. package/lib/AntiBan/fleetEventStore.js +157 -0
  9. package/lib/AntiBan/groupOperationGuard.js +116 -0
  10. package/lib/AntiBan/health.js +207 -0
  11. package/lib/AntiBan/humanEntropy.js +337 -0
  12. package/lib/AntiBan/index.js +77 -0
  13. package/lib/AntiBan/instanceCoordinator.js +175 -0
  14. package/lib/AntiBan/jidCanonicalizer.js +256 -0
  15. package/lib/AntiBan/jidCircuitBreaker.js +186 -0
  16. package/lib/AntiBan/legitimacySignalInjector.js +231 -0
  17. package/lib/AntiBan/lidFirstResolver.js +174 -0
  18. package/lib/AntiBan/lidResolver.js +324 -0
  19. package/lib/AntiBan/messageQueue.js +187 -0
  20. package/lib/AntiBan/messageRecovery.js +283 -0
  21. package/lib/AntiBan/messageTypeRegistry.js +369 -0
  22. package/lib/AntiBan/observability.js +145 -0
  23. package/lib/AntiBan/patch.js +302 -0
  24. package/lib/AntiBan/persist.js +87 -0
  25. package/lib/AntiBan/presenceChoreographer.js +430 -0
  26. package/lib/AntiBan/presets.js +105 -0
  27. package/lib/AntiBan/profiles.js +31 -0
  28. package/lib/AntiBan/proxyRotator.js +306 -0
  29. package/lib/AntiBan/rateLimiter.js +261 -0
  30. package/lib/AntiBan/readReceiptVariance.js +88 -0
  31. package/lib/AntiBan/reconnectThrottle.js +180 -0
  32. package/lib/AntiBan/replyRatio.js +161 -0
  33. package/lib/AntiBan/reputationVoucher.js +373 -0
  34. package/lib/AntiBan/retryReason.js +91 -0
  35. package/lib/AntiBan/retryTracker.js +172 -0
  36. package/lib/AntiBan/scheduler.js +111 -0
  37. package/lib/AntiBan/sessionFingerprint.js +248 -0
  38. package/lib/AntiBan/sessionStability.js +330 -0
  39. package/lib/AntiBan/stateAdapter.js +73 -0
  40. package/lib/AntiBan/stateExport.js +152 -0
  41. package/lib/AntiBan/stealthConnect.js +130 -0
  42. package/lib/AntiBan/timelockGuard.js +181 -0
  43. package/lib/AntiBan/topologyThrottler.js +339 -0
  44. package/lib/AntiBan/warmup.js +140 -0
  45. package/lib/AntiBan/webhooks.js +80 -0
  46. package/lib/AntiBan/wrapper.js +456 -0
  47. package/lib/Socket/chats.js +29 -4
  48. package/lib/Socket/groups.js +7 -2
  49. package/lib/Socket/index.js +68 -3
  50. package/lib/Socket/messages-send.js +44 -8
  51. package/lib/Socket/socket.js +2 -2
  52. package/lib/Store/prototype-store.js +10 -39
  53. package/lib/index.js +5 -0
  54. package/package.json +7 -3
@@ -0,0 +1,456 @@
1
+ /**
2
+ * Socket Wrapper — Drop-in replacement that wraps sendMessage with anti-ban protection
3
+ *
4
+ * Works with both baileys and @oxidezap/baileyrs transports.
5
+ *
6
+ * Usage with baileys:
7
+ * import makeWASocket from 'baileys';
8
+ * import { wrapSocket } from 'baileys-antiban';
9
+ *
10
+ * const sock = makeWASocket({ ... });
11
+ * const safeSock = wrapSocket(sock);
12
+ *
13
+ * Usage with baileyrs:
14
+ * import { makeWASocket } from '@oxidezap/baileyrs';
15
+ * import { wrapSocket } from 'baileys-antiban';
16
+ *
17
+ * const sock = makeWASocket({ ... });
18
+ * const safeSock = wrapSocket(sock);
19
+ *
20
+ * // Use safeSock.sendMessage() — automatically rate-limited and monitored
21
+ * await safeSock.sendMessage(jid, { text: 'Hello!' });
22
+ *
23
+ * // Check health anytime
24
+ * console.log(safeSock.antiban.getStats());
25
+ *
26
+ * Note: reachoutTimeLock timelock module silently noops on baileyrs until upstream
27
+ * emits reachoutTimeLock events — confirmed NOT present in baileyrs v0.0.8.
28
+ * Timelock guard will operate in detection-only mode (relies on 463 errors only).
29
+ */
30
+ import { AntiBan } from './antiban.js';
31
+ import { DeafSessionDetector } from './sessionStability.js';
32
+ import { GroupOperationGuard } from './groupOperationGuard.js';
33
+ import { LegitimacySignalInjector } from './legitimacySignalInjector.js';
34
+ /**
35
+ * Wrap a Baileys socket with anti-ban protection.
36
+ * The returned socket has the same API but sendMessage() is protected.
37
+ */
38
+ export function wrapSocket(sock, config, warmUpState, wrapOptions) {
39
+ const antiban = new AntiBan(config, warmUpState);
40
+ const options = {
41
+ autoRespondToIncoming: false,
42
+ ...wrapOptions,
43
+ };
44
+ // Deaf session detector — optional, enabled via wrapOptions.deafSession
45
+ const deafDetector = options.deafSession
46
+ ? new DeafSessionDetector(options.deafSession)
47
+ : null;
48
+ if (deafDetector)
49
+ deafDetector.attach(sock);
50
+ // Group operation guard — intercept group methods with rate limiting
51
+ if (options.groupOpGuard !== false) {
52
+ const guard = new GroupOperationGuard(options.groupOpGuard || {});
53
+ const origUpdate = sock.groupParticipantsUpdate?.bind(sock);
54
+ if (origUpdate) {
55
+ sock.groupParticipantsUpdate = async (jid, participants, action) => {
56
+ const op = action === 'add' ? 'add' : action === 'remove' ? 'remove' : 'add';
57
+ const check = guard.check(op, jid);
58
+ if (!check.allowed)
59
+ throw new Error(check.reason || 'Group operation rate limited');
60
+ return origUpdate(jid, participants, action);
61
+ };
62
+ }
63
+ const origCreate = sock.groupCreate?.bind(sock);
64
+ if (origCreate) {
65
+ sock.groupCreate = async (subject, participants) => {
66
+ const check = guard.check('create', subject);
67
+ if (!check.allowed)
68
+ throw new Error(check.reason || 'Group creation rate limited');
69
+ return origCreate(subject, participants);
70
+ };
71
+ }
72
+ }
73
+ // Legitimacy signal injector — wrap sendMessage for typo injection
74
+ let legitimacyInjector = null;
75
+ if (options.legitimacySignals !== false) {
76
+ legitimacyInjector = new LegitimacySignalInjector(typeof options.legitimacySignals === 'object' ? options.legitimacySignals : {});
77
+ }
78
+ // Hook into Baileys events for health monitoring
79
+ // Prefer ev.process() (Baileys ≥ late 2022) for batched event handling
80
+ // Fall back to ev.on() for older versions
81
+ if (typeof sock.ev.process === 'function') {
82
+ sock.ev.process(async (events) => {
83
+ // Handle connection updates
84
+ if (events['connection.update']) {
85
+ const update = events['connection.update'];
86
+ if (update.connection === 'close') {
87
+ const reason = update.lastDisconnect?.error?.output?.statusCode || 'unknown';
88
+ antiban.onDisconnect(reason);
89
+ antiban.destroy(); // Clean up all timers
90
+ deafDetector?.onDisconnect();
91
+ deafDetector?.destroy();
92
+ // Fleet event: detect ban-like disconnect codes
93
+ if (options.fleetEventStore) {
94
+ const banCodes = [401, 403, 428, 515]; // 401=logged out, 403=forbidden, 428=banned, 515=restart required
95
+ if (typeof reason === 'number' && banCodes.includes(reason)) {
96
+ void options.fleetEventStore.emit('ban', { statusCode: reason });
97
+ }
98
+ }
99
+ }
100
+ if (update.connection === 'open') {
101
+ antiban.onReconnect();
102
+ deafDetector?.onConnect();
103
+ // Fleet event: recovery from previous ban/warn
104
+ if (options.fleetEventStore) {
105
+ const health = antiban.getStats().health;
106
+ if (health.risk === 'low') {
107
+ void options.fleetEventStore.emit('recovery', { risk: health.risk });
108
+ }
109
+ }
110
+ }
111
+ // Reachout timelock detection
112
+ if (update.reachoutTimeLock) {
113
+ antiban.timelock.onTimelockUpdate({
114
+ isActive: update.reachoutTimeLock.isActive,
115
+ timeEnforcementEnds: update.reachoutTimeLock.timeEnforcementEnds,
116
+ enforcementType: update.reachoutTimeLock.enforcementType,
117
+ });
118
+ // Fleet event: timelock
119
+ if (options.fleetEventStore && update.reachoutTimeLock.isActive) {
120
+ void options.fleetEventStore.emit('timelock', {
121
+ enforcementType: update.reachoutTimeLock.enforcementType,
122
+ endsAt: update.reachoutTimeLock.timeEnforcementEnds,
123
+ });
124
+ }
125
+ }
126
+ }
127
+ // Catch 463 errors from message updates + track retries + learn LID mappings
128
+ if (events['messages.update']) {
129
+ const updates = events['messages.update'];
130
+ deafDetector?.onMessageActivity();
131
+ for (const update of updates) {
132
+ // 463 error detection
133
+ if (update?.update?.messageStubParameters) {
134
+ const params = Array.isArray(update.update.messageStubParameters)
135
+ ? update.update.messageStubParameters
136
+ : [];
137
+ if (params.includes(463) || params.includes('463')) {
138
+ antiban.timelock.record463Error();
139
+ // Fleet event: rate limit detected
140
+ if (options.fleetEventStore) {
141
+ void options.fleetEventStore.emit('rate_limit', { error: '463' });
142
+ }
143
+ }
144
+ }
145
+ // Retry tracking
146
+ antiban.retryTracker.onMessageUpdate(update);
147
+ // Delivery receipt tracking
148
+ const status = update?.update?.status;
149
+ const msgId = update?.key?.id;
150
+ // status 3 = DELIVERY_ACK (double tick), status 4 = READ
151
+ if (msgId && (status === 3 || status === 4)) {
152
+ antiban.onDeliveryReceipt(msgId);
153
+ }
154
+ }
155
+ // LID canonicalizer learning
156
+ antiban.jidCanonicalizer?.onMessageUpdate(updates);
157
+ }
158
+ // Register known chats from incoming messages + handle reply suggestions + learn LID mappings
159
+ if (events['messages.upsert']) {
160
+ const { messages } = events['messages.upsert'];
161
+ deafDetector?.onMessageActivity();
162
+ // Learn LID mappings FIRST (before any other processing)
163
+ antiban.jidCanonicalizer?.onIncomingEvent(events['messages.upsert']);
164
+ for (const msg of messages || []) {
165
+ const jid = msg.key?.remoteJid;
166
+ if (!jid)
167
+ continue;
168
+ // Register known chat
169
+ antiban.timelock.registerKnownChat(jid);
170
+ // Skip self messages
171
+ const isSelf = msg.key?.fromMe || false;
172
+ if (isSelf)
173
+ continue;
174
+ // Extract message text
175
+ const msgText = msg.message?.conversation ||
176
+ msg.message?.extendedTextMessage?.text ||
177
+ msg.message?.imageMessage?.caption ||
178
+ msg.message?.videoMessage?.caption ||
179
+ '';
180
+ // Handle incoming message (updates reply ratio + contact graph)
181
+ const replySuggestion = antiban.onIncomingMessage(jid, msgText);
182
+ // Auto-respond if enabled and suggested
183
+ if (options.autoRespondToIncoming && replySuggestion.shouldReply && replySuggestion.suggestedText) {
184
+ // Random delay 3-15s
185
+ const replyDelay = Math.floor(Math.random() * 12000) + 3000;
186
+ setTimeout(async () => {
187
+ try {
188
+ await sock.sendMessage(jid, { text: replySuggestion.suggestedText });
189
+ }
190
+ catch (error) {
191
+ // Silently fail — auto-reply is best-effort
192
+ }
193
+ }, replyDelay);
194
+ }
195
+ }
196
+ }
197
+ });
198
+ }
199
+ else {
200
+ // Fallback to ev.on() for older Baileys versions
201
+ sock.ev.on('connection.update', (update) => {
202
+ if (update.connection === 'close') {
203
+ const reason = update.lastDisconnect?.error?.output?.statusCode || 'unknown';
204
+ antiban.onDisconnect(reason);
205
+ antiban.destroy(); // Clean up all timers
206
+ deafDetector?.onDisconnect();
207
+ deafDetector?.destroy();
208
+ // Fleet event: detect ban-like disconnect codes
209
+ if (options.fleetEventStore) {
210
+ const banCodes = [401, 403, 428, 515]; // 401=logged out, 403=forbidden, 428=banned, 515=restart required
211
+ if (typeof reason === 'number' && banCodes.includes(reason)) {
212
+ void options.fleetEventStore.emit('ban', { statusCode: reason });
213
+ }
214
+ }
215
+ }
216
+ if (update.connection === 'open') {
217
+ antiban.onReconnect();
218
+ deafDetector?.onConnect();
219
+ // Fleet event: recovery from previous ban/warn
220
+ if (options.fleetEventStore) {
221
+ const health = antiban.getStats().health;
222
+ if (health.risk === 'low') {
223
+ void options.fleetEventStore.emit('recovery', { risk: health.risk });
224
+ }
225
+ }
226
+ }
227
+ // Reachout timelock detection
228
+ if (update.reachoutTimeLock) {
229
+ antiban.timelock.onTimelockUpdate({
230
+ isActive: update.reachoutTimeLock.isActive,
231
+ timeEnforcementEnds: update.reachoutTimeLock.timeEnforcementEnds,
232
+ enforcementType: update.reachoutTimeLock.enforcementType,
233
+ });
234
+ // Fleet event: timelock
235
+ if (options.fleetEventStore && update.reachoutTimeLock.isActive) {
236
+ void options.fleetEventStore.emit('timelock', {
237
+ enforcementType: update.reachoutTimeLock.enforcementType,
238
+ endsAt: update.reachoutTimeLock.timeEnforcementEnds,
239
+ });
240
+ }
241
+ }
242
+ });
243
+ // Catch 463 errors from message updates + track retries + learn LID mappings
244
+ sock.ev.on('messages.update', (updates) => {
245
+ deafDetector?.onMessageActivity();
246
+ for (const update of updates) {
247
+ // 463 error detection
248
+ if (update?.update?.messageStubParameters) {
249
+ const params = update.update.messageStubParameters;
250
+ if (params.includes(463) || params.includes('463')) {
251
+ antiban.timelock.record463Error();
252
+ // Fleet event: rate limit detected
253
+ if (options.fleetEventStore) {
254
+ void options.fleetEventStore.emit('rate_limit', { error: '463' });
255
+ }
256
+ }
257
+ }
258
+ // Retry tracking
259
+ antiban.retryTracker.onMessageUpdate(update);
260
+ // Delivery receipt tracking
261
+ const status = update?.update?.status;
262
+ const msgId = update?.key?.id;
263
+ // status 3 = DELIVERY_ACK (double tick), status 4 = READ
264
+ if (msgId && (status === 3 || status === 4)) {
265
+ antiban.onDeliveryReceipt(msgId);
266
+ }
267
+ }
268
+ // LID canonicalizer learning
269
+ antiban.jidCanonicalizer?.onMessageUpdate(updates);
270
+ });
271
+ // Register known chats from incoming messages + handle reply suggestions + learn LID mappings
272
+ sock.ev.on('messages.upsert', (upsert) => {
273
+ const { messages } = upsert;
274
+ deafDetector?.onMessageActivity();
275
+ // Learn LID mappings FIRST (before any other processing)
276
+ antiban.jidCanonicalizer?.onIncomingEvent(upsert);
277
+ for (const msg of messages || []) {
278
+ const jid = msg.key?.remoteJid;
279
+ if (!jid)
280
+ continue;
281
+ // Register known chat
282
+ antiban.timelock.registerKnownChat(jid);
283
+ // Skip self messages
284
+ const isSelf = msg.key?.fromMe || false;
285
+ if (isSelf)
286
+ continue;
287
+ // Extract message text
288
+ const msgText = msg.message?.conversation ||
289
+ msg.message?.extendedTextMessage?.text ||
290
+ msg.message?.imageMessage?.caption ||
291
+ msg.message?.videoMessage?.caption ||
292
+ '';
293
+ // Handle incoming message (updates reply ratio + contact graph)
294
+ const replySuggestion = antiban.onIncomingMessage(jid, msgText);
295
+ // Auto-respond if enabled and suggested
296
+ if (options.autoRespondToIncoming && replySuggestion.shouldReply && replySuggestion.suggestedText) {
297
+ // Random delay 3-15s
298
+ const replyDelay = Math.floor(Math.random() * 12000) + 3000;
299
+ setTimeout(async () => {
300
+ try {
301
+ await sock.sendMessage(jid, { text: replySuggestion.suggestedText });
302
+ }
303
+ catch (error) {
304
+ // Silently fail — auto-reply is best-effort
305
+ }
306
+ }, replyDelay);
307
+ }
308
+ }
309
+ });
310
+ }
311
+ // Create proxy that intercepts sendMessage
312
+ const originalSendMessage = sock.sendMessage.bind(sock);
313
+ // Serializes beforeSend→afterSend so rate limiter accounting is accurate
314
+ // under concurrent sends. Without this, all concurrent callers read the same
315
+ // committed state before any afterSend records, bypassing per-minute limits.
316
+ let sendLock = Promise.resolve();
317
+ const wrappedSendMessage = async (jid, content, options) => {
318
+ /**
319
+ * LID/PN Canonicalization — Normalize JID to canonical form
320
+ *
321
+ * This mitigates the LID/PN race that causes "Bad MAC / No Session / Invalid PreKey"
322
+ * errors (Baileys #1769, our PR #2372). When a message event arrives under one form
323
+ * (e.g. LID) but the crypto session was established under another (e.g. PN), decryption
324
+ * fails. By normalizing all outbound targets to a single form, we reduce this race.
325
+ *
326
+ * This is middleware-layer mitigation only. Root fix requires PR #2372 merged upstream.
327
+ */
328
+ const canonicalJid = antiban.jidCanonicalizer?.canonicalizeTarget(jid) || jid;
329
+ // Extract text content for rate limiter analysis
330
+ let text = content?.text || content?.caption || content?.image?.caption || '';
331
+ // Check for typo injection (only for text messages)
332
+ let typoResult = null;
333
+ if (legitimacyInjector && content?.text && typeof content.text === 'string' && content.text.length > 10) {
334
+ typoResult = legitimacyInjector.shouldInjectTypo(content.text);
335
+ }
336
+ // Chain this send onto the previous — each waits for the prior send's
337
+ // afterSend to commit before running its own beforeSend check.
338
+ const sendResult = sendLock.then(async () => {
339
+ // Circuit breaker check (before antiban.beforeSend)
340
+ if (options.circuitBreaker) {
341
+ if (!options.circuitBreaker.canSend(canonicalJid)) {
342
+ throw new Error('[baileys-antiban] circuit-breaker: send blocked for ' + canonicalJid);
343
+ }
344
+ }
345
+ const decision = await antiban.beforeSend(canonicalJid, text);
346
+ if (!decision.allowed) {
347
+ throw new Error(`[baileys-antiban] Message blocked: ${decision.reason}`);
348
+ }
349
+ // Apply delay from rate limiter
350
+ if (decision.delayMs > 0) {
351
+ await new Promise(resolve => setTimeout(resolve, decision.delayMs));
352
+ }
353
+ // Apply circuit breaker jitter for broadcast messages
354
+ if (options.circuitBreaker) {
355
+ const isBroadcast = canonicalJid.endsWith('@broadcast') || canonicalJid.endsWith('@newsletter');
356
+ const jitter = options.circuitBreaker.getJitter(isBroadcast);
357
+ if (jitter > 0) {
358
+ await new Promise(resolve => setTimeout(resolve, jitter));
359
+ }
360
+ }
361
+ // Send message (using canonical JID)
362
+ // If typo injection is active, send typo first, then correction
363
+ try {
364
+ let result;
365
+ if (typoResult) {
366
+ // Send typo version first
367
+ const typoContent = { ...content, text: typoResult.typoText };
368
+ await originalSendMessage(canonicalJid, typoContent, options);
369
+ // Wait for correction delay
370
+ await new Promise(r => setTimeout(r, typoResult.correctionDelay));
371
+ // Send corrected version
372
+ const correctionContent = { ...content, text: typoResult.correctionText };
373
+ result = await originalSendMessage(canonicalJid, correctionContent, options);
374
+ }
375
+ else {
376
+ // Normal send
377
+ result = await originalSendMessage(canonicalJid, content, options);
378
+ }
379
+ // Pass msgId to afterSend for delivery tracking
380
+ const msgId = result?.key?.id;
381
+ antiban.afterSend(canonicalJid, text, msgId);
382
+ antiban.timelock.registerKnownChat(canonicalJid);
383
+ // Clear retry tracking on successful send
384
+ if (msgId) {
385
+ antiban.retryTracker.clear(msgId);
386
+ }
387
+ // Circuit breaker success
388
+ if (options.circuitBreaker) {
389
+ options.circuitBreaker.recordSuccess(canonicalJid);
390
+ }
391
+ return result;
392
+ }
393
+ catch (error) {
394
+ // Circuit breaker failure
395
+ if (options.circuitBreaker) {
396
+ options.circuitBreaker.recordFailure(canonicalJid);
397
+ }
398
+ // Baileys PR #2587: partial-encrypt Boom now carries structured data:
399
+ // error.data.failed[] — per-recipient { jid, error } failures
400
+ // error.data.firstCause — most likely root cause string
401
+ // Extract for richer health-monitor diagnostics vs plain error.message.
402
+ const boomData = error?.data;
403
+ if (boomData?.failed?.length) {
404
+ const cause = boomData.firstCause ?? 'unknown';
405
+ const failedJids = boomData.failed.map((f) => f.jid).join(', ');
406
+ antiban.afterSendFailed(`encrypt-all-failed firstCause=${cause} jids=[${failedJids}]`);
407
+ }
408
+ else {
409
+ antiban.afterSendFailed(error instanceof Error ? error.message : String(error));
410
+ }
411
+ throw error;
412
+ }
413
+ });
414
+ // Advance the lock regardless of success/failure so the chain never stalls
415
+ sendLock = sendResult.then(() => { }, () => { });
416
+ return sendResult;
417
+ };
418
+ // Return enhanced socket
419
+ const wrapped = Object.create(sock);
420
+ wrapped.sendMessage = wrappedSendMessage;
421
+ wrapped.antiban = antiban;
422
+ // Expose destroy method directly so consumers can call it manually if needed
423
+ wrapped.antiban.destroy = antiban.destroy.bind(antiban);
424
+ return wrapped;
425
+ }
426
+ /**
427
+ * Helper function to create a wrapped socket with device fingerprint applied.
428
+ *
429
+ * This combines device fingerprint generation, socket creation, and wrapping
430
+ * into a single call.
431
+ *
432
+ * Usage:
433
+ * import makeWASocket from 'baileys';
434
+ * import { wrapSocketWithFingerprint } from 'baileys-antiban';
435
+ *
436
+ * const wrapped = wrapSocketWithFingerprint(makeWASocket, socketConfig, {
437
+ * fingerprintSeed: 'stable-seed-123',
438
+ * groupOpGuard: {},
439
+ * legitimacySignals: {}
440
+ * });
441
+ *
442
+ * @param makeWASocket - Baileys makeWASocket factory function
443
+ * @param socketConfig - Base socket configuration
444
+ * @param wrapOptions - Combined wrapper options + fingerprintSeed
445
+ */
446
+ export function wrapSocketWithFingerprint(makeWASocket, socketConfig, wrapOptions) {
447
+ // Import fingerprint functions dynamically to avoid circular deps
448
+ const { generateFingerprint, applyFingerprint } = require('./deviceFingerprint.js');
449
+ const seed = wrapOptions?.fingerprintSeed ||
450
+ socketConfig.auth?.creds?.me?.id ||
451
+ String(Date.now());
452
+ const fp = generateFingerprint({ seed });
453
+ const config = applyFingerprint(socketConfig, fp);
454
+ const sock = makeWASocket(config);
455
+ return wrapSocket(sock, undefined, undefined, wrapOptions);
456
+ }
@@ -199,6 +199,15 @@ export const makeChatsSocket = (config) => {
199
199
  }
200
200
  };
201
201
 
202
+ // مهله قصوى لاستعلام اليوزرنيم — واتساب أحياناً مبيردش على الاستعلام ده خالص
203
+ // لبعض حسابات اليوزرنيم، وبيرجع في المهله الافتراضيه (60 ثانيه)، وده اللي
204
+ // كان بيسبب تعليق البوت لحد دقيقتين على اي رساله جايه من حساب زي ده
205
+ const withTimeout = (promise, ms, fallback) =>
206
+ Promise.race([
207
+ promise,
208
+ new Promise((resolve) => setTimeout(() => resolve(fallback), ms)),
209
+ ]);
210
+
202
211
  const getUsername = async (jid) => {
203
212
  const usyncQuery = new USyncQuery()
204
213
  .withUsernameProtocol()
@@ -208,10 +217,18 @@ export const makeChatsSocket = (config) => {
208
217
  return result?.list?.[0]?.username || null;
209
218
  };
210
219
 
220
+ // كام ملي ثانيه أقصى نستنى فيها رد واتساب قبل ما نستسلم ونكمل من غير يوزرنيم
221
+ const USERNAME_LOOKUP_TIMEOUT_MS = 3500;
222
+ // مده صلاحيه الكاش السلبي (id-not-found) — بعدها نجرب تاني، لأن الرقم ممكن
223
+ // يبقى قابل للاكتشاف تاني (المستخدم شال اليوزرنيم مثلاً)
224
+ const NEGATIVE_CACHE_TTL_MS = 30 * 60 * 1000;
225
+
211
226
  const findUserId = async (pnLid) => {
212
227
  const cachedId = USER_ID_CACHE.get(pnLid);
213
228
  if (cachedId) {
214
- return cachedId;
229
+ const isNegative = cachedId.phoneNumber === "id-not-found";
230
+ const isStale = isNegative && Date.now() - (cachedId._cachedAt || 0) > NEGATIVE_CACHE_TTL_MS;
231
+ if (!isStale) return cachedId;
215
232
  }
216
233
  const userId = {};
217
234
  if (isPnUser(pnLid)) {
@@ -221,6 +238,7 @@ export const makeChatsSocket = (config) => {
221
238
  )?.[0]?.lid;
222
239
  if (!userId.lid) {
223
240
  userId.lid = "id-not-found";
241
+ userId._cachedAt = Date.now();
224
242
  USER_ID_CACHE.set(pnLid, userId);
225
243
  return userId;
226
244
  }
@@ -234,13 +252,20 @@ export const makeChatsSocket = (config) => {
234
252
  // واتساب، لأن رقمه بيبقى مش قابل للاكتشاف. من غير الكاش ده، كل رساله
235
253
  // جايه من الحساب ده كانت بتعمل استعلام شبكه كامل تاني من الأول، وده
236
254
  // اللي كان بيسبب التأخير/التعليق الملحوظ مع أي حد شايل يوزرنيم.
237
- // كمان بنحاول نجيب اليوزرنيم نفسه عشان يتعرض بدل الرقم/الليد الخام.
255
+ // كمان بنحاول نجيب اليوزرنيم نفسه عشان يتعرض بدل الرقم/الليد الخام،
256
+ // لكن بمهله قصوى (6 ثواني) عشان لو واتساب مارضاش يرد، مانستناهوش المهله
257
+ // الكامله (60 ثانيه) اللي كانت بتعلّق البوت.
238
258
  userId.phoneNumber = "id-not-found";
239
259
  try {
240
- userId.username = await getUsername(pnLid);
260
+ userId.username = await withTimeout(
261
+ getUsername(pnLid),
262
+ USERNAME_LOOKUP_TIMEOUT_MS,
263
+ null,
264
+ );
241
265
  } catch {
242
266
  userId.username = null;
243
267
  }
268
+ userId._cachedAt = Date.now();
244
269
  USER_ID_CACHE.set(pnLid, userId);
245
270
  return userId;
246
271
  }
@@ -1144,4 +1169,4 @@ export const makeChatsSocket = (config) => {
1144
1169
  addOrEditQuickReply: addOrEditQuickReply,
1145
1170
  removeQuickReply: removeQuickReply,
1146
1171
  };
1147
- };
1172
+ };
@@ -452,9 +452,14 @@ export const extractGroupMetadata = (result) => {
452
452
  ? id
453
453
  : undefined;
454
454
  return {
455
- id: phone,
455
+ // لو مفيش رقم ظاهر (حساب يوزرنيم/خاصية إخفاء الرقم)، استخدم الـ
456
+ // LID بدل ما تسيبها undefined — undefined كانت بتوصل لقايمة
457
+ // المشاركين اللي بتتبعت لـ getUSyncDevices وتكسر استعلام
458
+ // الأجهزة للجروب كله، مع إن الاستعلام المباشر بالـ LID بيرجع
459
+ // فورًا لو استخدمناه صح
460
+ id: phone || lid,
456
461
  lid: lid,
457
- jid: phone,
462
+ jid: phone || lid,
458
463
  admin: attrs.type || false,
459
464
  };
460
465
  },
@@ -1,14 +1,79 @@
1
1
  import { DEFAULT_CONNECTION_CONFIG } from "../Defaults/index.js";
2
2
  import { makeCommunitiesSocket } from "./communities.js";
3
- const makeWASocket = (config) => {
4
- delete config.userDevicesCache;
5
- const newConfig = { ...DEFAULT_CONNECTION_CONFIG, ...config };
3
+ import { wrapSocket, wrapSocketWithFingerprint } from "../AntiBan/wrapper.js";
4
+ import { resolveConfig, PRESETS } from "../AntiBan/presets.js";
5
+
6
+ /**
7
+ * Create a WhatsApp socket.
8
+ *
9
+ * Optional anti-ban protection (integrated from baileys-antiban) can be enabled via config.antiBan:
10
+ *
11
+ * // Disabled (default) — normal Baileys behavior
12
+ * makeWASocket({ auth, ... })
13
+ *
14
+ * // Enable with safe defaults (conservative preset)
15
+ * makeWASocket({ auth, antiBan: true })
16
+ *
17
+ * // Enable with a named preset: 'conservative' | 'moderate' | 'aggressive' | 'high-volume'
18
+ * makeWASocket({ auth, antiBan: 'moderate' })
19
+ *
20
+ * // Enable with full custom config (see baileys-antiban README)
21
+ * makeWASocket({
22
+ * auth,
23
+ * antiBan: {
24
+ * maxPerMinute: 8,
25
+ * maxPerHour: 200,
26
+ * warmUp: { enabled: true },
27
+ * // ... or nested style still accepted with deprecation warning
28
+ * }
29
+ * })
30
+ *
31
+ * When enabled, the returned socket has the same API plus:
32
+ * sock.antiban — AntiBan instance
33
+ * sock.antiban.getStats() — current rate / health / risk stats
34
+ *
35
+ * Extra advanced options (optional):
36
+ * antiBanWarmUpState — restore previous warm-up state
37
+ * antiBanWrapOptions — extra wrap options (groupOpGuard, legitimacySignals, etc.)
38
+ *
39
+ * You can also use the low-level helpers directly:
40
+ * import { wrapSocket, wrapSocketWithFingerprint, PRESETS } from '@prototypevip/baileys'
41
+ */
42
+ const makeWASocket = (config = {}) => {
43
+ // Extract anti-ban related options so they don't leak into Baileys internal config
44
+ const {
45
+ antiBan,
46
+ antiBanWarmUpState,
47
+ antiBanWrapOptions,
48
+ ...restConfig
49
+ } = config;
50
+
51
+ delete restConfig.userDevicesCache;
52
+ const newConfig = { ...DEFAULT_CONNECTION_CONFIG, ...restConfig };
53
+
6
54
  let sock;
7
55
  const originalGetMessage = newConfig.getMessage;
8
56
  newConfig.getMessage = async function (key) {
9
57
  return originalGetMessage.call(sock || this, key);
10
58
  };
59
+
11
60
  sock = makeCommunitiesSocket(newConfig);
61
+
62
+ // Optional anti-ban wrapping
63
+ if (antiBan) {
64
+ let antibanConfig;
65
+ if (antiBan === true) {
66
+ antibanConfig = resolveConfig(undefined); // conservative defaults
67
+ } else if (typeof antiBan === "string") {
68
+ antibanConfig = resolveConfig(antiBan);
69
+ } else {
70
+ antibanConfig = antiBan; // user object (supports both flat & nested)
71
+ }
72
+ sock = wrapSocket(sock, antibanConfig, antiBanWarmUpState, antiBanWrapOptions);
73
+ }
74
+
12
75
  return sock;
13
76
  };
77
+
14
78
  export default makeWASocket;
79
+ export { wrapSocket, wrapSocketWithFingerprint, PRESETS };