baileys-antiban 3.8.4 → 3.8.6

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 (42) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/dist/cjs/antiban.js +578 -0
  3. package/dist/cjs/cli.js +160 -0
  4. package/dist/cjs/contactGraph.js +240 -0
  5. package/dist/cjs/contentVariator.js +154 -0
  6. package/dist/cjs/credsSnapshot.js +157 -0
  7. package/dist/cjs/deviceFingerprint.js +110 -0
  8. package/dist/cjs/health.js +211 -0
  9. package/dist/cjs/index.js +121 -0
  10. package/dist/cjs/jidCanonicalizer.js +260 -0
  11. package/dist/cjs/lidFirstResolver.js +212 -0
  12. package/dist/cjs/lidResolver.js +328 -0
  13. package/dist/cjs/messageQueue.js +191 -0
  14. package/dist/cjs/messageRecovery.js +335 -0
  15. package/dist/cjs/observability.js +151 -0
  16. package/dist/cjs/package.json +3 -0
  17. package/dist/cjs/persist.js +116 -0
  18. package/dist/cjs/presenceChoreographer.js +435 -0
  19. package/dist/cjs/presets.js +71 -0
  20. package/dist/cjs/profiles.js +38 -0
  21. package/dist/cjs/proxyRotator.js +310 -0
  22. package/dist/cjs/rateLimiter.js +202 -0
  23. package/dist/cjs/readReceiptVariance.js +91 -0
  24. package/dist/cjs/reconnectThrottle.js +184 -0
  25. package/dist/cjs/replyRatio.js +165 -0
  26. package/dist/cjs/retryReason.js +97 -0
  27. package/dist/cjs/retryTracker.js +176 -0
  28. package/dist/cjs/scheduler.js +115 -0
  29. package/dist/cjs/sessionFingerprint.js +258 -0
  30. package/dist/cjs/sessionStability.js +337 -0
  31. package/dist/cjs/stateAdapter.js +110 -0
  32. package/dist/cjs/stealthConnect.js +136 -0
  33. package/dist/cjs/timelockGuard.js +185 -0
  34. package/dist/cjs/warmup.js +113 -0
  35. package/dist/cjs/webhooks.js +84 -0
  36. package/dist/cjs/wrapper.js +278 -0
  37. package/dist/index.d.ts +1 -0
  38. package/dist/index.js +2 -0
  39. package/dist/observability.d.ts +85 -0
  40. package/dist/observability.js +145 -0
  41. package/dist/proxyRotator.js +18 -6
  42. package/package.json +5 -4
package/CHANGELOG.md CHANGED
@@ -5,6 +5,17 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [3.8.5] - 2026-05-09
9
+
10
+ ### Added
11
+ - **Observability module** (`src/observability.ts`) — Prometheus metrics export and pluggable structured logging.
12
+ - `exportPrometheusMetrics(stats, labels?)` — exports 27 metrics (3 counters, 24 gauges) in Prometheus text exposition format v0.0.4. Covers health score/risk, warmup progress, rate limiter windows, known chats, reply ratio, contact graph, retry spirals, reconnect throttle. Accepts custom labels (`instance`, `region`, etc.).
13
+ - `createMetricsHandler(getStats, labels?)` — returns Express/Fastify-compatible `handle(req, res)` + `text()` helpers for a `/metrics` endpoint.
14
+ - `createPeriodicExporter(getStats, config)` — push-based exporter that calls `onMetrics(text)` on a configurable interval (default 30s). Returns `stop()` handle.
15
+ - `createConsoleLogger(prefix?)` — structured console logger compatible with winston/pino interface (`debug`, `info`, `warn`, `error` with ISO timestamps and JSON meta).
16
+ - `AntiBanLogger` interface — plug in any logger: `winston`, `pino`, or the built-in console logger.
17
+ - New exports: `createConsoleLogger`, `exportPrometheusMetrics`, `createMetricsHandler`, `createPeriodicExporter`, `AntiBanLogger`, `PeriodicExporterConfig`, `PeriodicExporterHandle`.
18
+
8
19
  ## [3.8.4] - 2026-05-09
9
20
 
10
21
  ### Added
@@ -0,0 +1,578 @@
1
+ "use strict";
2
+ /**
3
+ * AntiBan — Main orchestrator combining rate limiting, warm-up, and health monitoring
4
+ *
5
+ * Usage:
6
+ * import { AntiBan } from 'baileys-antiban';
7
+ * const antiban = new AntiBan();
8
+ *
9
+ * // Before sending a message:
10
+ * const result = await antiban.beforeSend(recipient, content);
11
+ * if (result.allowed) {
12
+ * await new Promise(r => setTimeout(r, result.delayMs));
13
+ * await sock.sendMessage(recipient, { text: content });
14
+ * antiban.afterSend(recipient, content);
15
+ * }
16
+ */
17
+ Object.defineProperty(exports, "__esModule", { value: true });
18
+ exports.AntiBan = void 0;
19
+ const rateLimiter_js_1 = require("./rateLimiter.js");
20
+ const warmup_js_1 = require("./warmup.js");
21
+ const health_js_1 = require("./health.js");
22
+ const timelockGuard_js_1 = require("./timelockGuard.js");
23
+ const replyRatio_js_1 = require("./replyRatio.js");
24
+ const contactGraph_js_1 = require("./contactGraph.js");
25
+ const presenceChoreographer_js_1 = require("./presenceChoreographer.js");
26
+ const retryTracker_js_1 = require("./retryTracker.js");
27
+ const reconnectThrottle_js_1 = require("./reconnectThrottle.js");
28
+ const lidResolver_js_1 = require("./lidResolver.js");
29
+ const jidCanonicalizer_js_1 = require("./jidCanonicalizer.js");
30
+ const sessionStability_js_1 = require("./sessionStability.js");
31
+ const presets_js_1 = require("./presets.js");
32
+ const persist_js_1 = require("./persist.js");
33
+ const profiles_js_1 = require("./profiles.js");
34
+ function isLegacyConfig(cfg) {
35
+ if (typeof cfg !== 'object' || cfg === null)
36
+ return false;
37
+ return 'rateLimiter' in cfg || 'warmUp' in cfg || 'health' in cfg || 'timelock' in cfg ||
38
+ 'replyRatio' in cfg || 'contactGraph' in cfg || 'presence' in cfg || 'retryTracker' in cfg ||
39
+ 'reconnectThrottle' in cfg || 'lidResolver' in cfg || 'jidCanonicalizer' in cfg ||
40
+ 'sessionStability' in cfg;
41
+ }
42
+ function mapLegacyToFlat(legacy) {
43
+ console.warn('[baileys-antiban] DEPRECATED: Nested config (v2 style) detected. ' +
44
+ 'Migrate to flat config: new AntiBan({ maxPerMinute: 8 }). ' +
45
+ 'See: https://github.com/kobie3717/baileys-antiban#migration');
46
+ const flat = {};
47
+ if (legacy.rateLimiter?.maxPerMinute !== undefined)
48
+ flat.maxPerMinute = legacy.rateLimiter.maxPerMinute;
49
+ if (legacy.rateLimiter?.maxPerHour !== undefined)
50
+ flat.maxPerHour = legacy.rateLimiter.maxPerHour;
51
+ if (legacy.rateLimiter?.maxPerDay !== undefined)
52
+ flat.maxPerDay = legacy.rateLimiter.maxPerDay;
53
+ if (legacy.rateLimiter?.minDelayMs !== undefined)
54
+ flat.minDelayMs = legacy.rateLimiter.minDelayMs;
55
+ if (legacy.rateLimiter?.maxDelayMs !== undefined)
56
+ flat.maxDelayMs = legacy.rateLimiter.maxDelayMs;
57
+ if (legacy.rateLimiter?.newChatDelayMs !== undefined)
58
+ flat.newChatDelayMs = legacy.rateLimiter.newChatDelayMs;
59
+ if (legacy.warmUp?.warmUpDays !== undefined)
60
+ flat.warmupDays = legacy.warmUp.warmUpDays;
61
+ if (legacy.warmUp?.day1Limit !== undefined)
62
+ flat.day1Limit = legacy.warmUp.day1Limit;
63
+ if (legacy.warmUp?.growthFactor !== undefined)
64
+ flat.growthFactor = legacy.warmUp.growthFactor;
65
+ if (legacy.logging !== undefined)
66
+ flat.logging = legacy.logging;
67
+ return flat;
68
+ }
69
+ class AntiBan {
70
+ rateLimiter;
71
+ warmUp;
72
+ health;
73
+ timelockGuard;
74
+ replyRatioGuard;
75
+ contactGraphWarmer;
76
+ presenceChoreographer;
77
+ retryTrackerModule;
78
+ reconnectThrottleModule;
79
+ lidResolverModule = null;
80
+ jidCanonicalizerModule = null;
81
+ sessionStabilityMonitor = null;
82
+ stateManager = null;
83
+ resolvedConfig;
84
+ logging;
85
+ stats = {
86
+ messagesAllowed: 0,
87
+ messagesBlocked: 0,
88
+ totalDelayMs: 0,
89
+ };
90
+ constructor(input, warmUpStateArg) {
91
+ let flatConfig;
92
+ let legacyPassthrough = null;
93
+ let warmUpState = warmUpStateArg;
94
+ if (isLegacyConfig(input)) {
95
+ legacyPassthrough = input;
96
+ flatConfig = mapLegacyToFlat(legacyPassthrough);
97
+ }
98
+ else {
99
+ flatConfig = {};
100
+ legacyPassthrough = null;
101
+ }
102
+ const cfg = isLegacyConfig(input)
103
+ ? (0, presets_js_1.resolveConfig)(flatConfig)
104
+ : (0, presets_js_1.resolveConfig)(input);
105
+ this.resolvedConfig = cfg;
106
+ // Initialize persistence — load state before constructing modules
107
+ let savedState = null;
108
+ if (cfg.persist) {
109
+ this.stateManager = new persist_js_1.StateManager(cfg.persist);
110
+ savedState = this.stateManager.load();
111
+ if (savedState) {
112
+ warmUpState = savedState.warmup;
113
+ }
114
+ }
115
+ this.logging = cfg.logging ?? true;
116
+ this.rateLimiter = new rateLimiter_js_1.RateLimiter({
117
+ maxPerMinute: cfg.maxPerMinute,
118
+ maxPerHour: cfg.maxPerHour,
119
+ maxPerDay: cfg.maxPerDay,
120
+ minDelayMs: cfg.minDelayMs,
121
+ maxDelayMs: cfg.maxDelayMs,
122
+ newChatDelayMs: cfg.newChatDelayMs,
123
+ ...(legacyPassthrough?.rateLimiter || {}),
124
+ });
125
+ // Restore knownChats from persisted state after rateLimiter is constructed
126
+ if (savedState?.knownChats) {
127
+ this.rateLimiter.restoreKnownChats(savedState.knownChats);
128
+ }
129
+ this.warmUp = new warmup_js_1.WarmUp({
130
+ warmUpDays: cfg.warmupDays,
131
+ day1Limit: cfg.day1Limit,
132
+ growthFactor: cfg.growthFactor,
133
+ inactivityThresholdHours: cfg.inactivityThresholdHours,
134
+ ...(legacyPassthrough?.warmUp || {}),
135
+ }, warmUpState);
136
+ this.health = new health_js_1.HealthMonitor({
137
+ autoPauseAt: cfg.autoPauseAt,
138
+ ...(legacyPassthrough?.health || {}),
139
+ onRiskChange: (status) => {
140
+ if (this.logging) {
141
+ const emoji = { low: '🟢', medium: '🟡', high: '🟠', critical: '🔴' };
142
+ console.log(`[baileys-antiban] ${emoji[status.risk]} Risk level: ${status.risk.toUpperCase()} (score: ${status.score})`);
143
+ console.log(`[baileys-antiban] ${status.recommendation}`);
144
+ status.reasons.forEach(r => console.log(`[baileys-antiban] → ${r}`));
145
+ }
146
+ // Call original callback if present
147
+ legacyPassthrough?.health?.onRiskChange?.(status);
148
+ },
149
+ });
150
+ this.timelockGuard = new timelockGuard_js_1.TimelockGuard({
151
+ ...(legacyPassthrough?.timelock || {}),
152
+ onTimelockDetected: (state) => {
153
+ this.health.recordReachoutTimelock(state.enforcementType);
154
+ if (this.logging) {
155
+ console.log(`[baileys-antiban] REACHOUT TIMELOCKED — ${state.enforcementType || 'unknown'}, expires ${state.expiresAt?.toISOString() || 'unknown'}`);
156
+ }
157
+ legacyPassthrough?.timelock?.onTimelockDetected?.(state);
158
+ },
159
+ onTimelockLifted: (state) => {
160
+ if (this.logging) {
161
+ console.log(`[baileys-antiban] Timelock lifted — resuming new contact messages`);
162
+ }
163
+ legacyPassthrough?.timelock?.onTimelockLifted?.(state);
164
+ },
165
+ });
166
+ this.replyRatioGuard = new replyRatio_js_1.ReplyRatioGuard(legacyPassthrough?.replyRatio);
167
+ this.contactGraphWarmer = new contactGraph_js_1.ContactGraphWarmer(legacyPassthrough?.contactGraph);
168
+ this.presenceChoreographer = new presenceChoreographer_js_1.PresenceChoreographer(legacyPassthrough?.presence);
169
+ this.retryTrackerModule = new retryTracker_js_1.RetryReasonTracker({
170
+ ...(legacyPassthrough?.retryTracker || {}),
171
+ onSpiral: (msgId, reason) => {
172
+ if (this.logging) {
173
+ console.log(`[baileys-antiban] ⚠️ Message ${msgId} stuck in retry spiral (${reason})`);
174
+ }
175
+ legacyPassthrough?.retryTracker?.onSpiral?.(msgId, reason);
176
+ },
177
+ });
178
+ this.reconnectThrottleModule = new reconnectThrottle_js_1.PostReconnectThrottle({
179
+ ...(legacyPassthrough?.reconnectThrottle || {}),
180
+ baselineRatePerMinute: () => this.rateLimiter.getStats().limits.perMinute,
181
+ });
182
+ // Initialize LID resolver and canonicalizer if configured
183
+ // If jidCanonicalizer is enabled but no resolver provided, create standalone resolver
184
+ if (legacyPassthrough?.jidCanonicalizer?.enabled) {
185
+ // Create or use provided resolver
186
+ if (legacyPassthrough.jidCanonicalizer.resolver) {
187
+ // User provided their own resolver
188
+ this.jidCanonicalizerModule = new jidCanonicalizer_js_1.JidCanonicalizer(legacyPassthrough.jidCanonicalizer);
189
+ this.lidResolverModule = legacyPassthrough.jidCanonicalizer.resolver;
190
+ }
191
+ else {
192
+ // Create new resolver using lidResolver config if provided
193
+ const resolverConfig = legacyPassthrough.lidResolver || legacyPassthrough.jidCanonicalizer.resolverConfig;
194
+ const resolver = new lidResolver_js_1.LidResolver(resolverConfig);
195
+ this.lidResolverModule = resolver;
196
+ this.jidCanonicalizerModule = new jidCanonicalizer_js_1.JidCanonicalizer({
197
+ ...legacyPassthrough.jidCanonicalizer,
198
+ resolver,
199
+ });
200
+ }
201
+ }
202
+ else if (legacyPassthrough?.lidResolver) {
203
+ // Standalone resolver without canonicalizer
204
+ this.lidResolverModule = new lidResolver_js_1.LidResolver(legacyPassthrough.lidResolver);
205
+ }
206
+ // Initialize session stability monitor if enabled
207
+ if (legacyPassthrough?.sessionStability?.enabled) {
208
+ const healthConfig = {
209
+ badMacThreshold: legacyPassthrough.sessionStability.badMacThreshold,
210
+ badMacWindowMs: legacyPassthrough.sessionStability.badMacWindowMs,
211
+ onDegraded: (stats) => {
212
+ if (this.logging) {
213
+ console.log(`[baileys-antiban] 🔴 SESSION DEGRADED — Bad MAC rate: ${stats.badMacCount} in last ${legacyPassthrough?.sessionStability?.badMacWindowMs || 60000}ms`);
214
+ console.log(`[baileys-antiban] Consider restarting session or switching to LID-based canonical form`);
215
+ }
216
+ },
217
+ onRecovered: () => {
218
+ if (this.logging) {
219
+ console.log(`[baileys-antiban] 🟢 SESSION RECOVERED — decrypt success rate improved`);
220
+ }
221
+ },
222
+ };
223
+ this.sessionStabilityMonitor = new sessionStability_js_1.SessionHealthMonitor(healthConfig);
224
+ }
225
+ }
226
+ /**
227
+ * Check if a message can be sent and get required delay.
228
+ * Call this BEFORE every sendMessage().
229
+ */
230
+ async beforeSend(recipient, content) {
231
+ const healthStatus = this.health.getStatus();
232
+ // Health monitor says stop
233
+ if (this.health.isPaused()) {
234
+ this.stats.messagesBlocked++;
235
+ if (this.logging) {
236
+ console.log(`[baileys-antiban] ⛔ BLOCKED — health risk too high (${healthStatus.risk})`);
237
+ }
238
+ return {
239
+ allowed: false,
240
+ delayMs: 0,
241
+ reason: `Health risk ${healthStatus.risk}: ${healthStatus.recommendation}`,
242
+ health: healthStatus,
243
+ };
244
+ }
245
+ // Timelock guard (allows existing chats, blocks new contacts)
246
+ const timelockDecision = this.timelockGuard.canSend(recipient);
247
+ if (!timelockDecision.allowed) {
248
+ this.stats.messagesBlocked++;
249
+ if (this.logging) {
250
+ console.log(`[baileys-antiban] TIMELOCKED — ${timelockDecision.reason}`);
251
+ }
252
+ return {
253
+ allowed: false,
254
+ delayMs: 0,
255
+ reason: timelockDecision.reason,
256
+ health: healthStatus,
257
+ };
258
+ }
259
+ // Warm-up limit check
260
+ if (!this.warmUp.canSend()) {
261
+ this.stats.messagesBlocked++;
262
+ const warmUpStatus = this.warmUp.getStatus();
263
+ if (this.logging) {
264
+ console.log(`[baileys-antiban] ⏳ BLOCKED — warm-up day ${warmUpStatus.day}/${warmUpStatus.totalDays}, limit reached (${warmUpStatus.todaySent}/${warmUpStatus.todayLimit})`);
265
+ }
266
+ return {
267
+ allowed: false,
268
+ delayMs: 0,
269
+ reason: `Warm-up limit: ${warmUpStatus.todaySent}/${warmUpStatus.todayLimit} messages today (day ${warmUpStatus.day})`,
270
+ health: healthStatus,
271
+ warmUpDay: warmUpStatus.day,
272
+ };
273
+ }
274
+ // Contact graph check
275
+ const contactGraphDecision = this.contactGraphWarmer.canMessage(recipient);
276
+ if (!contactGraphDecision.allowed) {
277
+ this.stats.messagesBlocked++;
278
+ if (this.logging) {
279
+ console.log(`[baileys-antiban] 📊 BLOCKED — contact graph: ${contactGraphDecision.reason}`);
280
+ }
281
+ return {
282
+ allowed: false,
283
+ delayMs: 0,
284
+ reason: `Contact graph: ${contactGraphDecision.reason}`,
285
+ health: healthStatus,
286
+ };
287
+ }
288
+ // Reply ratio check
289
+ const replyRatioDecision = this.replyRatioGuard.beforeSend(recipient);
290
+ if (!replyRatioDecision.allowed) {
291
+ this.stats.messagesBlocked++;
292
+ if (this.logging) {
293
+ console.log(`[baileys-antiban] 💬 BLOCKED — reply ratio: ${replyRatioDecision.reason}`);
294
+ }
295
+ return {
296
+ allowed: false,
297
+ delayMs: 0,
298
+ reason: `Reply ratio: ${replyRatioDecision.reason}`,
299
+ health: healthStatus,
300
+ };
301
+ }
302
+ // Reconnect throttle check
303
+ const reconnectThrottleDecision = this.reconnectThrottleModule.beforeSend();
304
+ if (!reconnectThrottleDecision.allowed) {
305
+ this.stats.messagesBlocked++;
306
+ if (this.logging) {
307
+ console.log(`[baileys-antiban] 🔄 BLOCKED — reconnect throttle: ${reconnectThrottleDecision.reason}`);
308
+ }
309
+ return {
310
+ allowed: false,
311
+ delayMs: reconnectThrottleDecision.retryAfterMs || 0,
312
+ reason: reconnectThrottleDecision.reason || 'Post-reconnect throttle',
313
+ health: healthStatus,
314
+ };
315
+ }
316
+ // Group profile rate check (runs before rateLimiter.getDelay for timing)
317
+ if (this.resolvedConfig.groupProfiles && (0, profiles_js_1.shouldUseGroupProfile)(recipient)) {
318
+ const groupLimits = (0, profiles_js_1.applyGroupMultiplier)({
319
+ maxPerMinute: this.resolvedConfig.maxPerMinute,
320
+ maxPerHour: this.resolvedConfig.maxPerHour,
321
+ maxPerDay: this.resolvedConfig.maxPerDay,
322
+ }, this.resolvedConfig.groupMultiplier);
323
+ const stats = this.rateLimiter.getStats();
324
+ if (stats.lastMinute >= groupLimits.maxPerMinute ||
325
+ stats.lastHour >= groupLimits.maxPerHour ||
326
+ stats.lastDay >= groupLimits.maxPerDay) {
327
+ this.stats.messagesBlocked++;
328
+ if (this.logging) {
329
+ console.log(`[baileys-antiban] 🚫 BLOCKED — group rate limit exceeded for ${recipient}`);
330
+ }
331
+ return { allowed: false, delayMs: 0, reason: 'Group rate limit exceeded', health: healthStatus };
332
+ }
333
+ }
334
+ // Rate limiter delay
335
+ let delay = await this.rateLimiter.getDelay(recipient, content);
336
+ if (delay === -1) {
337
+ this.stats.messagesBlocked++;
338
+ if (this.logging) {
339
+ console.log(`[baileys-antiban] 🚫 BLOCKED — rate limit or identical message spam`);
340
+ }
341
+ return {
342
+ allowed: false,
343
+ delayMs: 0,
344
+ reason: 'Rate limit exceeded or identical message spam detected',
345
+ health: healthStatus,
346
+ };
347
+ }
348
+ // Apply circadian rhythm multiplier to delay
349
+ const activityFactor = this.presenceChoreographer.getCurrentActivityFactor();
350
+ if (activityFactor < 1.0) {
351
+ // Lower activity = longer delays (cap at 5x)
352
+ const multiplier = Math.min(5, 1 / activityFactor);
353
+ delay = Math.floor(delay * multiplier);
354
+ }
355
+ // Roll for distraction pause
356
+ const distractionCheck = this.presenceChoreographer.shouldPauseForDistraction();
357
+ if (distractionCheck.pause) {
358
+ delay += distractionCheck.durationMs;
359
+ if (this.logging) {
360
+ console.log(`[baileys-antiban] ⏸️ Distraction pause: +${Math.floor(distractionCheck.durationMs / 60000)}min`);
361
+ }
362
+ }
363
+ // Roll for offline gap
364
+ const offlineCheck = this.presenceChoreographer.shouldTakeOfflineGap();
365
+ if (offlineCheck.offline) {
366
+ delay += offlineCheck.durationMs;
367
+ if (this.logging) {
368
+ console.log(`[baileys-antiban] 📴 Offline gap: +${Math.floor(offlineCheck.durationMs / 60000)}min`);
369
+ }
370
+ }
371
+ this.stats.totalDelayMs += delay;
372
+ return {
373
+ allowed: true,
374
+ delayMs: delay,
375
+ health: healthStatus,
376
+ };
377
+ }
378
+ /**
379
+ * Record a successfully sent message.
380
+ * Call this AFTER every successful sendMessage().
381
+ */
382
+ afterSend(recipient, content) {
383
+ this.rateLimiter.record(recipient, content);
384
+ this.warmUp.record();
385
+ this.replyRatioGuard.recordSent(recipient);
386
+ this.stats.messagesAllowed++;
387
+ this.persistStateDebounced();
388
+ }
389
+ /**
390
+ * Record a failed message send
391
+ */
392
+ afterSendFailed(error) {
393
+ this.health.recordMessageFailed(error);
394
+ }
395
+ /**
396
+ * Record a disconnection (call from connection.update handler)
397
+ */
398
+ onDisconnect(reason) {
399
+ this.health.recordDisconnect(reason);
400
+ this.reconnectThrottleModule.onDisconnect();
401
+ const reasonStr = String(reason);
402
+ if (reasonStr === '403' || reasonStr === '401' || reasonStr === 'forbidden' || reasonStr === 'loggedOut') {
403
+ this.persistStateImmediate();
404
+ }
405
+ }
406
+ /**
407
+ * Record a successful reconnection
408
+ */
409
+ onReconnect() {
410
+ this.health.recordReconnect();
411
+ this.reconnectThrottleModule.onReconnect();
412
+ }
413
+ /**
414
+ * Handle incoming message — record in reply ratio + contact graph.
415
+ * Returns suggested reply if reply ratio suggests auto-reply.
416
+ */
417
+ onIncomingMessage(jid, msgText) {
418
+ this.replyRatioGuard.recordReceived(jid);
419
+ this.contactGraphWarmer.onIncomingMessage(jid);
420
+ return this.replyRatioGuard.suggestReply(jid, msgText);
421
+ }
422
+ /**
423
+ * Get comprehensive stats
424
+ */
425
+ getStats() {
426
+ const stats = {
427
+ ...this.stats,
428
+ health: this.health.getStatus(),
429
+ warmUp: this.warmUp.getStatus(),
430
+ rateLimiter: this.rateLimiter.getStats(),
431
+ };
432
+ // Only include new stats if enabled
433
+ if (this.replyRatioGuard['config']?.enabled) {
434
+ stats.replyRatio = this.replyRatioGuard.getStats();
435
+ }
436
+ if (this.contactGraphWarmer['config']?.enabled) {
437
+ stats.contactGraph = this.contactGraphWarmer.getStats();
438
+ }
439
+ if (this.presenceChoreographer['config']?.enabled) {
440
+ stats.presence = this.presenceChoreographer.getStats();
441
+ }
442
+ if (this.retryTrackerModule['config']?.enabled) {
443
+ stats.retryTracker = this.retryTrackerModule.getStats();
444
+ }
445
+ if (this.reconnectThrottleModule['config']?.enabled) {
446
+ stats.reconnectThrottle = this.reconnectThrottleModule.getStats();
447
+ }
448
+ if (this.lidResolverModule) {
449
+ stats.lidResolver = this.lidResolverModule.getStats();
450
+ }
451
+ if (this.jidCanonicalizerModule) {
452
+ stats.jidCanonicalizer = this.jidCanonicalizerModule.getStats();
453
+ }
454
+ if (this.sessionStabilityMonitor) {
455
+ stats.sessionStability = this.sessionStabilityMonitor.getStats();
456
+ }
457
+ return stats;
458
+ }
459
+ /** Get the timelock guard for direct access */
460
+ get timelock() {
461
+ return this.timelockGuard;
462
+ }
463
+ /** Get the reply ratio guard for direct access */
464
+ get replyRatio() {
465
+ return this.replyRatioGuard;
466
+ }
467
+ /** Get the contact graph warmer for direct access */
468
+ get contactGraph() {
469
+ return this.contactGraphWarmer;
470
+ }
471
+ /** Get the presence choreographer for direct access */
472
+ get presence() {
473
+ return this.presenceChoreographer;
474
+ }
475
+ /** Get the retry tracker for direct access */
476
+ get retryTracker() {
477
+ return this.retryTrackerModule;
478
+ }
479
+ /** Get the reconnect throttle for direct access */
480
+ get reconnectThrottle() {
481
+ return this.reconnectThrottleModule;
482
+ }
483
+ /** Get the LID resolver for direct access */
484
+ get lidResolver() {
485
+ return this.lidResolverModule;
486
+ }
487
+ /** Get the JID canonicalizer for direct access */
488
+ get jidCanonicalizer() {
489
+ return this.jidCanonicalizerModule;
490
+ }
491
+ /** Get the session stability monitor for direct access */
492
+ get sessionStability() {
493
+ return this.sessionStabilityMonitor;
494
+ }
495
+ /**
496
+ * Export warm-up state for persistence between restarts
497
+ */
498
+ exportWarmUpState() {
499
+ return this.warmUp.exportState();
500
+ }
501
+ /**
502
+ * Force pause all sending
503
+ */
504
+ pause() {
505
+ this.health.setPaused(true);
506
+ if (this.logging) {
507
+ console.log('[baileys-antiban] ⏸️ Sending paused manually');
508
+ }
509
+ }
510
+ /**
511
+ * Resume sending
512
+ */
513
+ resume() {
514
+ this.health.setPaused(false);
515
+ if (this.logging) {
516
+ console.log('[baileys-antiban] ▶️ Sending resumed');
517
+ }
518
+ }
519
+ /**
520
+ * Reset everything (use after a ban period)
521
+ */
522
+ reset() {
523
+ this.timelockGuard.reset();
524
+ this.health.reset();
525
+ this.warmUp.reset();
526
+ this.replyRatioGuard.reset();
527
+ this.contactGraphWarmer.reset();
528
+ this.presenceChoreographer.reset();
529
+ this.retryTrackerModule.destroy();
530
+ this.reconnectThrottleModule.destroy();
531
+ this.stats = { messagesAllowed: 0, messagesBlocked: 0, totalDelayMs: 0 };
532
+ if (this.logging) {
533
+ console.log('[baileys-antiban] 🔄 Reset — starting fresh warm-up');
534
+ }
535
+ }
536
+ persistStateDebounced() {
537
+ if (!this.stateManager)
538
+ return;
539
+ const state = {
540
+ warmup: this.warmUp.exportState(),
541
+ knownChats: Array.from(this.rateLimiter.getKnownChats()),
542
+ savedAt: Date.now(),
543
+ version: 3,
544
+ };
545
+ this.stateManager.saveDebounced(state);
546
+ }
547
+ persistStateImmediate() {
548
+ if (!this.stateManager)
549
+ return;
550
+ const state = {
551
+ warmup: this.warmUp.exportState(),
552
+ knownChats: Array.from(this.rateLimiter.getKnownChats()),
553
+ savedAt: Date.now(),
554
+ version: 3,
555
+ };
556
+ this.stateManager.saveImmediate(state);
557
+ }
558
+ /**
559
+ * Clean up all timers and resources.
560
+ * Call this when disposing of the AntiBan instance or when the socket closes.
561
+ */
562
+ destroy() {
563
+ this.stateManager?.destroy();
564
+ this.timelockGuard.reset(); // Clears the resumeTimer
565
+ this.replyRatioGuard.reset();
566
+ this.contactGraphWarmer.reset();
567
+ this.presenceChoreographer.reset();
568
+ this.retryTrackerModule.destroy();
569
+ this.reconnectThrottleModule.destroy();
570
+ this.jidCanonicalizerModule?.destroy();
571
+ this.lidResolverModule?.destroy();
572
+ this.sessionStabilityMonitor?.reset();
573
+ if (this.logging) {
574
+ console.log('[baileys-antiban] 🧹 Destroyed — all timers cleared');
575
+ }
576
+ }
577
+ }
578
+ exports.AntiBan = AntiBan;