baileys-antiban 3.8.11 → 4.6.0
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/CHANGELOG.md +15 -0
- package/dist/antiban.d.ts +18 -1
- package/dist/antiban.js +145 -2
- package/dist/banRecoveryOrchestrator.d.ts +87 -0
- package/dist/banRecoveryOrchestrator.js +284 -0
- package/dist/cjs/antiban.js +145 -2
- package/dist/cjs/banRecoveryOrchestrator.js +288 -0
- package/dist/cjs/deliveryTracker.js +111 -0
- package/dist/cjs/groupOperationGuard.js +122 -0
- package/dist/cjs/index.js +21 -2
- package/dist/cjs/instanceCoordinator.js +189 -0
- package/dist/cjs/legitimacySignalInjector.js +235 -0
- package/dist/cjs/presets.js +14 -6
- package/dist/cjs/rateLimiter.js +24 -0
- package/dist/cjs/warmup.js +5 -1
- package/dist/cjs/wrapper.js +116 -6
- package/dist/deliveryTracker.d.ts +55 -0
- package/dist/deliveryTracker.js +107 -0
- package/dist/groupOperationGuard.d.ts +79 -0
- package/dist/groupOperationGuard.js +116 -0
- package/dist/index.d.ts +6 -1
- package/dist/index.js +11 -1
- package/dist/instanceCoordinator.d.ts +60 -0
- package/dist/instanceCoordinator.js +152 -0
- package/dist/legitimacySignalInjector.d.ts +90 -0
- package/dist/legitimacySignalInjector.js +231 -0
- package/dist/presets.d.ts +9 -1
- package/dist/presets.js +14 -6
- package/dist/rateLimiter.d.ts +12 -0
- package/dist/rateLimiter.js +24 -0
- package/dist/warmup.d.ts +7 -2
- package/dist/warmup.js +5 -1
- package/dist/wrapper.d.ts +42 -0
- package/dist/wrapper.js +115 -6
- package/package.json +4 -4
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,21 @@ 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.11] - 2026-05-19
|
|
9
|
+
|
|
10
|
+
### Security
|
|
11
|
+
- **persist.ts**: Resolve state file path to absolute (`path.resolve()`), reject null bytes. Add strict JSON shape validation (version, savedAt, knownChats types) before trusting loaded state — prevents type confusion from corrupt or tampered files.
|
|
12
|
+
- **proxyRotator.ts**: Replace `(0, eval)('require')` and `(0, eval)('import.meta.url')` with `new Function()` on static literal strings in the ESM code path. Not user-controlled, but removes the indirect eval chain for static analysis and CSP compliance.
|
|
13
|
+
|
|
14
|
+
### Fixed
|
|
15
|
+
- **rateLimiter.ts**: Added LRU size cap (10,000 entries) to `identicalCount` Map. Time-window eviction alone allowed unbounded growth when sending many unique messages; oldest-by-lastSeen entries are now evicted when the cap is exceeded.
|
|
16
|
+
- **antiban.ts**: Extend `mapLegacyToFlat()` to preserve `autoPauseAt`, `groupMultiplier`, `groupProfiles`, `persist` from flat top-level fields when legacy config detection fires — completing the coverage from 3.8.9.
|
|
17
|
+
|
|
18
|
+
### Changed (3.8.10)
|
|
19
|
+
- README: Expanded v3 flat config example with all `ResolvedConfig` fields; added correct `deafSession` wrapOptions (4th arg) example; marked nested Configuration section as deprecated.
|
|
20
|
+
- Tests: 4 new v3 test cases covering `maxIdenticalMessages`/`burstAllowance` forwarding, mixed legacy+flat preservation, `getConfig()`.
|
|
21
|
+
- CHANGELOG: Added missing 3.8.9 entry.
|
|
22
|
+
|
|
8
23
|
## [3.8.9] - 2026-05-19
|
|
9
24
|
|
|
10
25
|
### Fixed
|
package/dist/antiban.d.ts
CHANGED
|
@@ -25,7 +25,10 @@ import { PostReconnectThrottle, type ReconnectThrottleConfig, type ReconnectThro
|
|
|
25
25
|
import { LidResolver, type LidResolverConfig, type LidResolverStats } from './lidResolver.js';
|
|
26
26
|
import { JidCanonicalizer, type JidCanonicalizerConfig, type JidCanonicalizerStats } from './jidCanonicalizer.js';
|
|
27
27
|
import { SessionHealthMonitor, type SessionHealthStats } from './sessionStability.js';
|
|
28
|
+
import { BanRecoveryOrchestrator, type RecoveryStatus } from './banRecoveryOrchestrator.js';
|
|
28
29
|
import { type AntiBanInput, type ResolvedConfig } from './presets.js';
|
|
30
|
+
import { type DeliveryTrackerStats } from './deliveryTracker.js';
|
|
31
|
+
import { type InstanceCoordinatorStats } from './instanceCoordinator.js';
|
|
29
32
|
export interface AntiBanConfigLegacy {
|
|
30
33
|
rateLimiter?: Partial<RateLimiterConfig>;
|
|
31
34
|
warmUp?: Partial<WarmUpConfig>;
|
|
@@ -70,6 +73,9 @@ export interface AntiBanStats {
|
|
|
70
73
|
lidResolver?: LidResolverStats | null;
|
|
71
74
|
jidCanonicalizer?: JidCanonicalizerStats | null;
|
|
72
75
|
sessionStability?: SessionHealthStats | null;
|
|
76
|
+
banRecovery?: RecoveryStatus | null;
|
|
77
|
+
deliveryTracker: DeliveryTrackerStats;
|
|
78
|
+
instanceCoordinator?: InstanceCoordinatorStats | null;
|
|
73
79
|
}
|
|
74
80
|
export declare class AntiBan {
|
|
75
81
|
private rateLimiter;
|
|
@@ -84,6 +90,9 @@ export declare class AntiBan {
|
|
|
84
90
|
private lidResolverModule;
|
|
85
91
|
private jidCanonicalizerModule;
|
|
86
92
|
private sessionStabilityMonitor;
|
|
93
|
+
private banRecovery;
|
|
94
|
+
private deliveryTracker;
|
|
95
|
+
private instanceCoordinator;
|
|
87
96
|
private stateManager;
|
|
88
97
|
private resolvedConfig;
|
|
89
98
|
private logging;
|
|
@@ -98,7 +107,7 @@ export declare class AntiBan {
|
|
|
98
107
|
* Record a successfully sent message.
|
|
99
108
|
* Call this AFTER every successful sendMessage().
|
|
100
109
|
*/
|
|
101
|
-
afterSend(recipient: string, content: string): void;
|
|
110
|
+
afterSend(recipient: string, content: string, msgId?: string): void;
|
|
102
111
|
/**
|
|
103
112
|
* Record a failed message send
|
|
104
113
|
*/
|
|
@@ -119,6 +128,11 @@ export declare class AntiBan {
|
|
|
119
128
|
shouldReply: boolean;
|
|
120
129
|
suggestedText?: string;
|
|
121
130
|
};
|
|
131
|
+
/**
|
|
132
|
+
* Record a delivery receipt (status 3 = DELIVERY_ACK, status 4 = READ).
|
|
133
|
+
* Call from messages.update handler when delivery status is received.
|
|
134
|
+
*/
|
|
135
|
+
onDeliveryReceipt(msgId: string): void;
|
|
122
136
|
/**
|
|
123
137
|
* Get the resolved configuration
|
|
124
138
|
*/
|
|
@@ -145,6 +159,8 @@ export declare class AntiBan {
|
|
|
145
159
|
get jidCanonicalizer(): JidCanonicalizer | null;
|
|
146
160
|
/** Get the session stability monitor for direct access */
|
|
147
161
|
get sessionStability(): SessionHealthMonitor | null;
|
|
162
|
+
/** Get the ban recovery orchestrator for direct access */
|
|
163
|
+
get recoveryOrchestrator(): BanRecoveryOrchestrator;
|
|
148
164
|
/**
|
|
149
165
|
* Export warm-up state for persistence between restarts
|
|
150
166
|
*/
|
|
@@ -161,6 +177,7 @@ export declare class AntiBan {
|
|
|
161
177
|
* Reset everything (use after a ban period)
|
|
162
178
|
*/
|
|
163
179
|
reset(): void;
|
|
180
|
+
private runAdaptiveCheck;
|
|
164
181
|
private persistStateDebounced;
|
|
165
182
|
private persistStateImmediate;
|
|
166
183
|
/**
|
package/dist/antiban.js
CHANGED
|
@@ -25,9 +25,12 @@ import { PostReconnectThrottle } from './reconnectThrottle.js';
|
|
|
25
25
|
import { LidResolver } from './lidResolver.js';
|
|
26
26
|
import { JidCanonicalizer } from './jidCanonicalizer.js';
|
|
27
27
|
import { SessionHealthMonitor } from './sessionStability.js';
|
|
28
|
+
import { BanRecoveryOrchestrator } from './banRecoveryOrchestrator.js';
|
|
28
29
|
import { resolveConfig } from './presets.js';
|
|
29
30
|
import { StateManager } from './persist.js';
|
|
30
31
|
import { shouldUseGroupProfile, applyGroupMultiplier } from './profiles.js';
|
|
32
|
+
import { DeliveryTracker } from './deliveryTracker.js';
|
|
33
|
+
import { InstanceCoordinator } from './instanceCoordinator.js';
|
|
31
34
|
function isLegacyConfig(cfg) {
|
|
32
35
|
if (typeof cfg !== 'object' || cfg === null)
|
|
33
36
|
return false;
|
|
@@ -103,6 +106,9 @@ export class AntiBan {
|
|
|
103
106
|
lidResolverModule = null;
|
|
104
107
|
jidCanonicalizerModule = null;
|
|
105
108
|
sessionStabilityMonitor = null;
|
|
109
|
+
banRecovery;
|
|
110
|
+
deliveryTracker;
|
|
111
|
+
instanceCoordinator = null;
|
|
106
112
|
stateManager = null;
|
|
107
113
|
resolvedConfig;
|
|
108
114
|
logging;
|
|
@@ -170,23 +176,54 @@ export class AntiBan {
|
|
|
170
176
|
console.log(`[baileys-antiban] ${status.recommendation}`);
|
|
171
177
|
status.reasons.forEach(r => console.log(`[baileys-antiban] → ${r}`));
|
|
172
178
|
}
|
|
173
|
-
|
|
179
|
+
if ((status.risk === 'high' || status.risk === 'critical') && cfg.onAtRisk) {
|
|
180
|
+
cfg.onAtRisk(status);
|
|
181
|
+
}
|
|
182
|
+
// Trigger recovery orchestrator on critical risk
|
|
183
|
+
if (status.risk === 'critical') {
|
|
184
|
+
this.banRecovery.recordBanEvent('soft_ban');
|
|
185
|
+
}
|
|
186
|
+
cfg.onRiskChange?.(status);
|
|
174
187
|
legacyPassthrough?.health?.onRiskChange?.(status);
|
|
175
188
|
},
|
|
176
189
|
});
|
|
190
|
+
// Initialize ban recovery orchestrator
|
|
191
|
+
this.banRecovery = new BanRecoveryOrchestrator({
|
|
192
|
+
onPhaseChange: (phase, plan) => {
|
|
193
|
+
if (this.logging) {
|
|
194
|
+
console.log(`[baileys-antiban] 🔄 Recovery phase: ${phase} — ${plan.description}`);
|
|
195
|
+
}
|
|
196
|
+
},
|
|
197
|
+
onHardBan: () => {
|
|
198
|
+
if (this.logging) {
|
|
199
|
+
console.log(`[baileys-antiban] 💀 HARD BAN detected — account likely dead, replace SIM`);
|
|
200
|
+
}
|
|
201
|
+
},
|
|
202
|
+
});
|
|
203
|
+
// Initialize delivery tracker
|
|
204
|
+
this.deliveryTracker = new DeliveryTracker({
|
|
205
|
+
onLowDeliveryRate: (rate) => {
|
|
206
|
+
if (this.logging) {
|
|
207
|
+
console.log(`[baileys-antiban] ⚠️ Low delivery rate detected: ${Math.round(rate * 100)}% — possible soft ban`);
|
|
208
|
+
}
|
|
209
|
+
},
|
|
210
|
+
});
|
|
177
211
|
this.timelockGuard = new TimelockGuard({
|
|
178
212
|
...(legacyPassthrough?.timelock || {}),
|
|
179
213
|
onTimelockDetected: (state) => {
|
|
180
214
|
this.health.recordReachoutTimelock(state.enforcementType);
|
|
215
|
+
this.banRecovery.recordBanEvent('timelock');
|
|
181
216
|
if (this.logging) {
|
|
182
217
|
console.log(`[baileys-antiban] REACHOUT TIMELOCKED — ${state.enforcementType || 'unknown'}, expires ${state.expiresAt?.toISOString() || 'unknown'}`);
|
|
183
218
|
}
|
|
219
|
+
cfg.onTimelockDetected?.(state);
|
|
184
220
|
legacyPassthrough?.timelock?.onTimelockDetected?.(state);
|
|
185
221
|
},
|
|
186
222
|
onTimelockLifted: (state) => {
|
|
187
223
|
if (this.logging) {
|
|
188
224
|
console.log(`[baileys-antiban] Timelock lifted — resuming new contact messages`);
|
|
189
225
|
}
|
|
226
|
+
cfg.onTimelockLifted?.(state);
|
|
190
227
|
legacyPassthrough?.timelock?.onTimelockLifted?.(state);
|
|
191
228
|
},
|
|
192
229
|
});
|
|
@@ -249,6 +286,17 @@ export class AntiBan {
|
|
|
249
286
|
};
|
|
250
287
|
this.sessionStabilityMonitor = new SessionHealthMonitor(healthConfig);
|
|
251
288
|
}
|
|
289
|
+
// Initialize instance coordinator if configured
|
|
290
|
+
if (cfg.instanceCoordinator) {
|
|
291
|
+
this.instanceCoordinator = new InstanceCoordinator({
|
|
292
|
+
sharedFilePath: cfg.instanceCoordinator,
|
|
293
|
+
poolMaxPerMinute: cfg.instancePoolMaxPerMinute,
|
|
294
|
+
poolMaxPerHour: cfg.instancePoolMaxPerHour,
|
|
295
|
+
});
|
|
296
|
+
if (this.logging) {
|
|
297
|
+
console.log(`[baileys-antiban] 🌐 Instance coordination enabled: ${cfg.instanceCoordinator}`);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
252
300
|
}
|
|
253
301
|
/**
|
|
254
302
|
* Check if a message can be sent and get required delay.
|
|
@@ -269,6 +317,17 @@ export class AntiBan {
|
|
|
269
317
|
health: healthStatus,
|
|
270
318
|
};
|
|
271
319
|
}
|
|
320
|
+
// Recovery orchestrator rate multiplier
|
|
321
|
+
const recoveryStatus = this.banRecovery.getStatus();
|
|
322
|
+
if (recoveryStatus.phase === 'paused') {
|
|
323
|
+
this.stats.messagesBlocked++;
|
|
324
|
+
return {
|
|
325
|
+
allowed: false,
|
|
326
|
+
delayMs: recoveryStatus.pauseRemainingMs || 0,
|
|
327
|
+
reason: `Ban recovery: ${recoveryStatus.recommendation}`,
|
|
328
|
+
health: healthStatus,
|
|
329
|
+
};
|
|
330
|
+
}
|
|
272
331
|
// Timelock guard (allows existing chats, blocks new contacts)
|
|
273
332
|
const timelockDecision = this.timelockGuard.canSend(recipient);
|
|
274
333
|
if (!timelockDecision.allowed) {
|
|
@@ -340,6 +399,22 @@ export class AntiBan {
|
|
|
340
399
|
health: healthStatus,
|
|
341
400
|
};
|
|
342
401
|
}
|
|
402
|
+
// Cross-instance coordination — check shared IP-level pool
|
|
403
|
+
if (this.instanceCoordinator) {
|
|
404
|
+
const slot = this.instanceCoordinator.tryAcquireSlot();
|
|
405
|
+
if (!slot.allowed) {
|
|
406
|
+
this.stats.messagesBlocked++;
|
|
407
|
+
if (this.logging) {
|
|
408
|
+
console.log(`[baileys-antiban] 🌐 BLOCKED — instance pool exhausted (shared IP limit), retry in ${slot.retryAfterMs}ms`);
|
|
409
|
+
}
|
|
410
|
+
return {
|
|
411
|
+
allowed: false,
|
|
412
|
+
delayMs: slot.retryAfterMs || 5000,
|
|
413
|
+
reason: 'Cross-instance rate pool exhausted',
|
|
414
|
+
health: healthStatus,
|
|
415
|
+
};
|
|
416
|
+
}
|
|
417
|
+
}
|
|
343
418
|
// Group profile rate check (runs before rateLimiter.getDelay for timing)
|
|
344
419
|
if (this.resolvedConfig.groupProfiles && shouldUseGroupProfile(recipient)) {
|
|
345
420
|
const groupLimits = applyGroupMultiplier({
|
|
@@ -379,6 +454,24 @@ export class AntiBan {
|
|
|
379
454
|
const multiplier = Math.min(5, 1 / activityFactor);
|
|
380
455
|
delay = Math.floor(delay * multiplier);
|
|
381
456
|
}
|
|
457
|
+
// Per-contact risk multiplier — cold contacts need longer delays
|
|
458
|
+
// Only apply when contact graph is enabled, otherwise all contacts appear as 'stranger'
|
|
459
|
+
if (this.contactGraphWarmer['config']?.enabled) {
|
|
460
|
+
const contactState = this.contactGraphWarmer.getContactState(recipient);
|
|
461
|
+
const coldMultiplier = {
|
|
462
|
+
stranger: 2.5,
|
|
463
|
+
handshake_sent: 1.8,
|
|
464
|
+
handshake_complete: 1.3,
|
|
465
|
+
known: 1.0,
|
|
466
|
+
};
|
|
467
|
+
const contactRiskMult = coldMultiplier[contactState] ?? 1.0;
|
|
468
|
+
if (contactRiskMult > 1.0) {
|
|
469
|
+
delay = Math.floor(delay * contactRiskMult);
|
|
470
|
+
if (this.logging && contactRiskMult >= 2.0) {
|
|
471
|
+
console.log(`[baileys-antiban] ⚠️ Cold contact ${recipient} — ${contactState}, delay ×${contactRiskMult}`);
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
}
|
|
382
475
|
// Roll for distraction pause
|
|
383
476
|
const distractionCheck = this.presenceChoreographer.shouldPauseForDistraction();
|
|
384
477
|
if (distractionCheck.pause) {
|
|
@@ -406,11 +499,15 @@ export class AntiBan {
|
|
|
406
499
|
* Record a successfully sent message.
|
|
407
500
|
* Call this AFTER every successful sendMessage().
|
|
408
501
|
*/
|
|
409
|
-
afterSend(recipient, content) {
|
|
502
|
+
afterSend(recipient, content, msgId) {
|
|
410
503
|
this.rateLimiter.record(recipient, content);
|
|
411
504
|
this.warmUp.record();
|
|
412
505
|
this.replyRatioGuard.recordSent(recipient);
|
|
413
506
|
this.stats.messagesAllowed++;
|
|
507
|
+
if (msgId) {
|
|
508
|
+
this.deliveryTracker.onMessageSent(msgId);
|
|
509
|
+
}
|
|
510
|
+
this.runAdaptiveCheck();
|
|
414
511
|
this.persistStateDebounced();
|
|
415
512
|
}
|
|
416
513
|
/**
|
|
@@ -436,6 +533,7 @@ export class AntiBan {
|
|
|
436
533
|
onReconnect() {
|
|
437
534
|
this.health.recordReconnect();
|
|
438
535
|
this.reconnectThrottleModule.onReconnect();
|
|
536
|
+
this.rateLimiter.adaptLimits(1.0);
|
|
439
537
|
}
|
|
440
538
|
/**
|
|
441
539
|
* Handle incoming message — record in reply ratio + contact graph.
|
|
@@ -446,6 +544,13 @@ export class AntiBan {
|
|
|
446
544
|
this.contactGraphWarmer.onIncomingMessage(jid);
|
|
447
545
|
return this.replyRatioGuard.suggestReply(jid, msgText);
|
|
448
546
|
}
|
|
547
|
+
/**
|
|
548
|
+
* Record a delivery receipt (status 3 = DELIVERY_ACK, status 4 = READ).
|
|
549
|
+
* Call from messages.update handler when delivery status is received.
|
|
550
|
+
*/
|
|
551
|
+
onDeliveryReceipt(msgId) {
|
|
552
|
+
this.deliveryTracker.onDeliveryReceipt(msgId);
|
|
553
|
+
}
|
|
449
554
|
/**
|
|
450
555
|
* Get the resolved configuration
|
|
451
556
|
*/
|
|
@@ -461,6 +566,8 @@ export class AntiBan {
|
|
|
461
566
|
health: this.health.getStatus(),
|
|
462
567
|
warmUp: this.warmUp.getStatus(),
|
|
463
568
|
rateLimiter: this.rateLimiter.getStats(),
|
|
569
|
+
banRecovery: this.banRecovery.getStatus(),
|
|
570
|
+
deliveryTracker: this.deliveryTracker.getStats(),
|
|
464
571
|
};
|
|
465
572
|
// Only include new stats if enabled
|
|
466
573
|
if (this.replyRatioGuard['config']?.enabled) {
|
|
@@ -487,6 +594,9 @@ export class AntiBan {
|
|
|
487
594
|
if (this.sessionStabilityMonitor) {
|
|
488
595
|
stats.sessionStability = this.sessionStabilityMonitor.getStats();
|
|
489
596
|
}
|
|
597
|
+
if (this.instanceCoordinator) {
|
|
598
|
+
stats.instanceCoordinator = this.instanceCoordinator.getStats();
|
|
599
|
+
}
|
|
490
600
|
return stats;
|
|
491
601
|
}
|
|
492
602
|
/** Get the timelock guard for direct access */
|
|
@@ -525,6 +635,10 @@ export class AntiBan {
|
|
|
525
635
|
get sessionStability() {
|
|
526
636
|
return this.sessionStabilityMonitor;
|
|
527
637
|
}
|
|
638
|
+
/** Get the ban recovery orchestrator for direct access */
|
|
639
|
+
get recoveryOrchestrator() {
|
|
640
|
+
return this.banRecovery;
|
|
641
|
+
}
|
|
528
642
|
/**
|
|
529
643
|
* Export warm-up state for persistence between restarts
|
|
530
644
|
*/
|
|
@@ -566,6 +680,35 @@ export class AntiBan {
|
|
|
566
680
|
console.log('[baileys-antiban] 🔄 Reset — starting fresh warm-up');
|
|
567
681
|
}
|
|
568
682
|
}
|
|
683
|
+
runAdaptiveCheck() {
|
|
684
|
+
const delivery = this.deliveryTracker.getStats();
|
|
685
|
+
// Need min sample to be meaningful
|
|
686
|
+
if (delivery.deliveryRate === null)
|
|
687
|
+
return;
|
|
688
|
+
const rate = delivery.deliveryRate;
|
|
689
|
+
let targetFactor;
|
|
690
|
+
if (rate >= 0.85) {
|
|
691
|
+
targetFactor = 1.0; // Excellent — full speed
|
|
692
|
+
}
|
|
693
|
+
else if (rate >= 0.70) {
|
|
694
|
+
targetFactor = 0.75; // Good — slight reduction
|
|
695
|
+
}
|
|
696
|
+
else if (rate >= 0.55) {
|
|
697
|
+
targetFactor = 0.50; // Poor — halve throughput
|
|
698
|
+
}
|
|
699
|
+
else {
|
|
700
|
+
targetFactor = 0.25; // Bad — severe throttle (soft ban likely)
|
|
701
|
+
}
|
|
702
|
+
const current = this.rateLimiter.getCurrentFactor();
|
|
703
|
+
// Only log + adjust when factor changes meaningfully (>5% delta)
|
|
704
|
+
if (Math.abs(targetFactor - current) > 0.05) {
|
|
705
|
+
if (this.logging) {
|
|
706
|
+
const dir = targetFactor > current ? '📈' : '📉';
|
|
707
|
+
console.log(`[baileys-antiban] ${dir} Adaptive rate: delivery=${(rate * 100).toFixed(0)}% → factor ${current.toFixed(2)}→${targetFactor.toFixed(2)}`);
|
|
708
|
+
}
|
|
709
|
+
this.rateLimiter.adaptLimits(targetFactor);
|
|
710
|
+
}
|
|
711
|
+
}
|
|
569
712
|
persistStateDebounced() {
|
|
570
713
|
if (!this.stateManager)
|
|
571
714
|
return;
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BanRecoveryOrchestrator — Structured recovery after ban/restriction events
|
|
3
|
+
*
|
|
4
|
+
* When WhatsApp restricts your account, the worst thing to do is immediately
|
|
5
|
+
* resume normal activity. This module provides a evidence-based recovery protocol:
|
|
6
|
+
*
|
|
7
|
+
* - Timelocked (reachout_restricted): 24h pause, resume at 10% rate, ramp 15%/week
|
|
8
|
+
* - Rate-overlimit (429): 4h pause, resume at 25% rate, ramp 25%/week
|
|
9
|
+
* - Soft-ban (repeated disconnects): 48h pause, resume at 5% rate, ramp 10%/week
|
|
10
|
+
* - Hard-ban (loggedOut): account is dead, signal for replacement
|
|
11
|
+
*
|
|
12
|
+
* Based on observed recovery times from community reports. Not guaranteed —
|
|
13
|
+
* WA's enforcement is non-deterministic. Treat as best-effort guidance.
|
|
14
|
+
*/
|
|
15
|
+
export type BanEventType = 'timelock' | 'rate_overlimit' | 'soft_ban' | 'hard_ban';
|
|
16
|
+
export type RecoveryPhase = 'paused' | 'recovering' | 'ramping' | 'graduated' | 'dead';
|
|
17
|
+
export interface RecoveryPlan {
|
|
18
|
+
eventType: BanEventType;
|
|
19
|
+
pauseDurationMs: number;
|
|
20
|
+
resumeRateMultiplier: number;
|
|
21
|
+
weeklyRampPercent: number;
|
|
22
|
+
estimatedRecoveryDays: number;
|
|
23
|
+
description: string;
|
|
24
|
+
}
|
|
25
|
+
export interface BanRecoveryConfig {
|
|
26
|
+
/** Custom recovery plans per event type (overrides defaults) */
|
|
27
|
+
plans?: Partial<Record<BanEventType, Partial<RecoveryPlan>>>;
|
|
28
|
+
/** Called when recovery phase changes */
|
|
29
|
+
onPhaseChange?: (phase: RecoveryPhase, plan: RecoveryPlan) => void;
|
|
30
|
+
/** Called when account appears dead (hard ban) — signal to replace SIM */
|
|
31
|
+
onHardBan?: () => void;
|
|
32
|
+
/** Max weeks before giving up on recovery and declaring dead (default: 8) */
|
|
33
|
+
maxRecoveryWeeks?: number;
|
|
34
|
+
}
|
|
35
|
+
export interface RecoveryState {
|
|
36
|
+
active: boolean;
|
|
37
|
+
eventType?: BanEventType;
|
|
38
|
+
phase: RecoveryPhase;
|
|
39
|
+
banDetectedAt?: number;
|
|
40
|
+
pauseUntil?: number;
|
|
41
|
+
currentRateMultiplier: number;
|
|
42
|
+
weeksSinceResume: number;
|
|
43
|
+
banCount30d: number;
|
|
44
|
+
lastBanAt?: number;
|
|
45
|
+
}
|
|
46
|
+
export interface RecoveryStatus {
|
|
47
|
+
phase: RecoveryPhase;
|
|
48
|
+
rateMultiplier: number;
|
|
49
|
+
pauseRemainingMs?: number;
|
|
50
|
+
estimatedFullRecoveryDate?: number;
|
|
51
|
+
recommendation: string;
|
|
52
|
+
shouldReplaceNumber: boolean;
|
|
53
|
+
}
|
|
54
|
+
export declare class BanRecoveryOrchestrator {
|
|
55
|
+
private config;
|
|
56
|
+
private state;
|
|
57
|
+
private plans;
|
|
58
|
+
constructor(config?: BanRecoveryConfig);
|
|
59
|
+
/**
|
|
60
|
+
* Record a ban event and start recovery protocol
|
|
61
|
+
*/
|
|
62
|
+
recordBanEvent(eventType: BanEventType): RecoveryStatus;
|
|
63
|
+
/**
|
|
64
|
+
* Get current recovery status (call before sending to check rate)
|
|
65
|
+
*/
|
|
66
|
+
getStatus(): RecoveryStatus;
|
|
67
|
+
/**
|
|
68
|
+
* Current rate multiplier — multiply your normal limits by this
|
|
69
|
+
*/
|
|
70
|
+
getRateMultiplier(): number;
|
|
71
|
+
/**
|
|
72
|
+
* Should be called daily/weekly to advance the ramp
|
|
73
|
+
*/
|
|
74
|
+
tick(): void;
|
|
75
|
+
/**
|
|
76
|
+
* Classify a raw error into a BanEventType
|
|
77
|
+
*/
|
|
78
|
+
static classifyError(err: unknown): BanEventType | null;
|
|
79
|
+
/**
|
|
80
|
+
* Serializable state for persistence
|
|
81
|
+
*/
|
|
82
|
+
getState(): RecoveryState;
|
|
83
|
+
/**
|
|
84
|
+
* Restore from persisted state
|
|
85
|
+
*/
|
|
86
|
+
static fromState(state: RecoveryState, config?: BanRecoveryConfig): BanRecoveryOrchestrator;
|
|
87
|
+
}
|