baileys-antiban 3.9.0 → 4.7.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/README.md +163 -2
- package/dist/antiban.d.ts +18 -1
- package/dist/antiban.js +139 -1
- package/dist/banRecoveryOrchestrator.d.ts +87 -0
- package/dist/banRecoveryOrchestrator.js +284 -0
- package/dist/cjs/antiban.js +139 -1
- package/dist/cjs/banRecoveryOrchestrator.js +288 -0
- package/dist/cjs/deliveryTracker.js +111 -0
- package/dist/cjs/fleetEventStore.js +162 -0
- package/dist/cjs/groupOperationGuard.js +122 -0
- package/dist/cjs/humanEntropy.js +342 -0
- package/dist/cjs/index.js +32 -2
- package/dist/cjs/instanceCoordinator.js +189 -0
- package/dist/cjs/jidCircuitBreaker.js +157 -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 +176 -6
- package/dist/deliveryTracker.d.ts +55 -0
- package/dist/deliveryTracker.js +107 -0
- package/dist/fleetEventStore.d.ts +65 -0
- package/dist/fleetEventStore.js +157 -0
- package/dist/groupOperationGuard.d.ts +79 -0
- package/dist/groupOperationGuard.js +116 -0
- package/dist/humanEntropy.d.ts +133 -0
- package/dist/humanEntropy.js +337 -0
- package/dist/index.d.ts +9 -1
- package/dist/index.js +15 -1
- package/dist/instanceCoordinator.d.ts +60 -0
- package/dist/instanceCoordinator.js +152 -0
- package/dist/jidCircuitBreaker.d.ts +48 -0
- package/dist/jidCircuitBreaker.js +152 -0
- package/dist/legitimacySignalInjector.d.ts +90 -0
- package/dist/legitimacySignalInjector.js +231 -0
- package/dist/presets.d.ts +3 -0
- 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 +54 -0
- package/dist/wrapper.js +175 -6
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -8,9 +8,9 @@
|
|
|
8
8
|
|
|
9
9
|
**Drop-in anti-ban middleware for Baileys WhatsApp bots. Free, self-hosted, TypeScript-first. Whapi.Cloud alternative — zero monthly fees.**
|
|
10
10
|
|
|
11
|
-
> Rate limiting with Gaussian jitter, 7-day warmup, session health monitoring, LID resolver, disconnect classification, contact graph enforcement — all in one `npm install`. Works with [Baileys](https://github.com/WhiskeySockets/Baileys) and [@oxidezap/baileyrs](https://github.com/oxidezap/baileyrs) (Rust/WASM).
|
|
11
|
+
> Rate limiting with Gaussian jitter, 7-day warmup, session health monitoring, LID resolver, disconnect classification, contact graph enforcement, device fingerprinting, group operation guards, recovery orchestration, cross-instance coordination — all in one `npm install`. Works with [Baileys](https://github.com/WhiskeySockets/Baileys) and [@oxidezap/baileyrs](https://github.com/oxidezap/baileyrs) (Rust/WASM).
|
|
12
12
|
|
|
13
|
-
> **New in
|
|
13
|
+
> **New in v4.7:** HumanEntropyService — background human-like activity (typing indicators, delayed read receipts, presence cycles) to prevent "too perfect bot" detection. Works with WaSP and any session manager, no socket access needed.
|
|
14
14
|
|
|
15
15
|
## Why Trust This Package
|
|
16
16
|
|
|
@@ -27,6 +27,167 @@ The npm WhatsApp ecosystem has a malware problem. In April 2026, [`lotusbail`](h
|
|
|
27
27
|
|
|
28
28
|
If you can't read the code yourself, lean on these signals: signed releases, public audit trail, no telemetry, and a real product behind it. That's the floor. Everything below is the feature set.
|
|
29
29
|
|
|
30
|
+
## v4.x New Features — Production-Grade Ban Prevention
|
|
31
|
+
|
|
32
|
+
v4.0–v4.7 ship seven major anti-ban modules. All are **auto-wired** by default via `wrapSocket()` or `wrapSocketWithFingerprint()`.
|
|
33
|
+
|
|
34
|
+
### v4.0 — GroupOperationGuard
|
|
35
|
+
|
|
36
|
+
Rate-limits group operations to prevent `account_reachout_restricted` errors. WA limits: ~3 adds/10min, 2 creates/10min.
|
|
37
|
+
|
|
38
|
+
```typescript
|
|
39
|
+
import { wrapSocket } from 'baileys-antiban';
|
|
40
|
+
|
|
41
|
+
const sock = wrapSocket(makeWASocket({ ... }), {
|
|
42
|
+
groupOpGuard: { limits: { add: { max: 3, windowMs: 600_000 } } },
|
|
43
|
+
});
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Classify errors: `classifyGroupOpError(err)` returns `GROUP_OP_ERRORS.REACHOUT_RESTRICTED` | `RATE_OVERLIMIT` | `PRIVACY_BLOCK` | etc.
|
|
47
|
+
Disable: `groupOpGuard: false`
|
|
48
|
+
|
|
49
|
+
### v4.1 — LegitimacySignalInjector
|
|
50
|
+
|
|
51
|
+
Injects realistic imperfections: typos + corrections (2.5% of messages), read gaps, mid-typing pauses. WhatsApp's ML flags accounts that are "too perfect".
|
|
52
|
+
|
|
53
|
+
```typescript
|
|
54
|
+
const sock = wrapSocket(makeWASocket({ ... }), {
|
|
55
|
+
legitimacySignals: { typoProbability: 0.03 },
|
|
56
|
+
});
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Disable: `legitimacySignals: false`
|
|
60
|
+
|
|
61
|
+
### v4.2 — BanRecoveryOrchestrator
|
|
62
|
+
|
|
63
|
+
Structured recovery after ban events. Auto-triggers via HealthMonitor at critical risk.
|
|
64
|
+
|
|
65
|
+
```typescript
|
|
66
|
+
import { BanRecoveryOrchestrator } from 'baileys-antiban';
|
|
67
|
+
|
|
68
|
+
const recovery = new BanRecoveryOrchestrator({
|
|
69
|
+
onPhaseChange: (phase, plan) => console.log(`Phase: ${phase}`),
|
|
70
|
+
onHardBan: () => { /* replace SIM */ },
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
recovery.triggerRecovery('timelock');
|
|
74
|
+
const status = recovery.getStatus();
|
|
75
|
+
console.log(status.rateMultiplier); // 0.1 = 10% speed
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
**Recovery plans:** timelock: 24h pause, 10% resume, 15%/week ramp | rate_overlimit: 4h, 25%, 25%/week | soft_ban: 48h, 5%, 10%/week | hard_ban: dead
|
|
79
|
+
Access via: `antiban.recoveryOrchestrator`
|
|
80
|
+
|
|
81
|
+
### v4.3 — wrapSocketWithFingerprint
|
|
82
|
+
|
|
83
|
+
One-call setup with device fingerprint randomization (appVersion, osVersion, deviceModel).
|
|
84
|
+
|
|
85
|
+
```typescript
|
|
86
|
+
import { wrapSocketWithFingerprint } from 'baileys-antiban';
|
|
87
|
+
|
|
88
|
+
const sock = wrapSocketWithFingerprint(makeWASocket, { auth }, { preset: 'moderate' });
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
**Preset changes:** All presets default `groupProfiles: true`; `aggressive`/`high-volume` now `autoPauseAt: 'high'`
|
|
92
|
+
|
|
93
|
+
### v4.4 — Per-Contact Risk Delays + DeliveryTracker
|
|
94
|
+
|
|
95
|
+
Strangers get 2.5× delay, known contacts 1.0×. Active when `contactGraph.enabled: true`.
|
|
96
|
+
|
|
97
|
+
```typescript
|
|
98
|
+
const sock = wrapSocket(makeWASocket({ ... }), { contactGraph: { enabled: true } });
|
|
99
|
+
// stranger: 2.5×, handshake_sent: 1.8×, handshake_complete: 1.3×, known: 1.0×
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
**DeliveryTracker** tracks double-tick receipts. <60% delivery = soft-ban signal.
|
|
103
|
+
|
|
104
|
+
```typescript
|
|
105
|
+
import { DeliveryTracker } from 'baileys-antiban';
|
|
106
|
+
|
|
107
|
+
const tracker = new DeliveryTracker({
|
|
108
|
+
lowRateThreshold: 0.6,
|
|
109
|
+
onLowDeliveryRate: (rate) => console.error(`Delivery: ${rate * 100}%`),
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
sock.ev.on('messages.upsert', ({ messages }) => {
|
|
113
|
+
messages.forEach(m => m.key.fromMe && tracker.onMessageSent(m.key.id));
|
|
114
|
+
});
|
|
115
|
+
sock.ev.on('messages.update', (updates) => {
|
|
116
|
+
updates.forEach(({ key, update }) => {
|
|
117
|
+
if (update.status >= 3) tracker.onDeliveryReceipt(key.id);
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### v4.5 — Adaptive Rate Limiting
|
|
123
|
+
|
|
124
|
+
Auto-adjusts rate based on delivery success: ≥85% → 100% speed, <55% → 25% speed. Auto-wired.
|
|
125
|
+
|
|
126
|
+
```typescript
|
|
127
|
+
import { RateLimiter } from 'baileys-antiban';
|
|
128
|
+
|
|
129
|
+
const limiter = new RateLimiter({ maxPerMinute: 10 });
|
|
130
|
+
limiter.adaptLimits(0.5); // manual throttle to 50%
|
|
131
|
+
const factor = limiter.getCurrentFactor();
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### v4.6 — Cross-Instance Coordination
|
|
135
|
+
|
|
136
|
+
Shared token bucket across processes. Solves: 5 bots × 8/min = 40/min → flag.
|
|
137
|
+
|
|
138
|
+
```typescript
|
|
139
|
+
const sock = wrapSocket(makeWASocket({ ... }), {
|
|
140
|
+
instanceCoordinator: '/tmp/wa-pool.json',
|
|
141
|
+
instancePoolMaxPerMinute: 20,
|
|
142
|
+
});
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
All instances share 20/min IP-level budget. Atomic writes via rename-swap.
|
|
146
|
+
|
|
147
|
+
### v4.7 — HumanEntropyService
|
|
148
|
+
|
|
149
|
+
Background noise generator that makes a WA session indistinguishable from a real human user during idle periods. Runs independently of your message flow — no socket access required, works with WaSP or any session manager.
|
|
150
|
+
|
|
151
|
+
**The problem it solves:** WhatsApp's ML flags accounts with "too perfect" patterns — instant read receipts, zero typing activity, always-on presence. A listen-only bot that never idles looks like a bot.
|
|
152
|
+
|
|
153
|
+
**What it does every 2-6 hours (randomized):**
|
|
154
|
+
- Sends typing indicator to a recent contact for 3-8 seconds, then stops (mimics "started typing, changed mind")
|
|
155
|
+
- Marks a received message as read with 10-60 min delay (mimics "opened notification, read later")
|
|
156
|
+
- Toggles own presence available → unavailable over 30-120s (mimics "checked phone, put it down")
|
|
157
|
+
|
|
158
|
+
**Safety:** Only contacts people who already messaged you first. Never cold-contacts strangers. All errors caught silently.
|
|
159
|
+
|
|
160
|
+
```typescript
|
|
161
|
+
import { createHumanEntropyService } from 'baileys-antiban';
|
|
162
|
+
|
|
163
|
+
// Works with WaSP (no direct socket access needed)
|
|
164
|
+
const entropy = createHumanEntropyService(wasp, sessionId, {
|
|
165
|
+
enabled: true,
|
|
166
|
+
minIntervalMs: 2 * 60 * 60 * 1000, // 2 hours
|
|
167
|
+
maxIntervalMs: 6 * 60 * 60 * 1000, // 6 hours
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
entropy.start(); // runs in background
|
|
171
|
+
entropy.stop(); // call on shutdown
|
|
172
|
+
|
|
173
|
+
// Or with direct Baileys socket
|
|
174
|
+
import { HumanEntropyService } from 'baileys-antiban';
|
|
175
|
+
const svc = new HumanEntropyService(socket, { enabled: true });
|
|
176
|
+
svc.start();
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
**Tracking recent contacts:** Feed incoming messages so the service knows who to interact with:
|
|
180
|
+
|
|
181
|
+
```typescript
|
|
182
|
+
sock.ev.on('messages.upsert', ({ messages }) => {
|
|
183
|
+
messages.forEach(m => entropy.addRecentContact(m.key.remoteJid, m.key));
|
|
184
|
+
});
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
Stats: `entropy.getStats()` returns `{ typingActions, readActions, presenceActions, cycles }`.
|
|
188
|
+
|
|
189
|
+
---
|
|
190
|
+
|
|
30
191
|
## v2.0 New Features — Session Stability Module
|
|
31
192
|
|
|
32
193
|
### What's New in v2.0
|
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;
|
|
@@ -173,14 +179,40 @@ export class AntiBan {
|
|
|
173
179
|
if ((status.risk === 'high' || status.risk === 'critical') && cfg.onAtRisk) {
|
|
174
180
|
cfg.onAtRisk(status);
|
|
175
181
|
}
|
|
182
|
+
// Trigger recovery orchestrator on critical risk
|
|
183
|
+
if (status.risk === 'critical') {
|
|
184
|
+
this.banRecovery.recordBanEvent('soft_ban');
|
|
185
|
+
}
|
|
176
186
|
cfg.onRiskChange?.(status);
|
|
177
187
|
legacyPassthrough?.health?.onRiskChange?.(status);
|
|
178
188
|
},
|
|
179
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
|
+
});
|
|
180
211
|
this.timelockGuard = new TimelockGuard({
|
|
181
212
|
...(legacyPassthrough?.timelock || {}),
|
|
182
213
|
onTimelockDetected: (state) => {
|
|
183
214
|
this.health.recordReachoutTimelock(state.enforcementType);
|
|
215
|
+
this.banRecovery.recordBanEvent('timelock');
|
|
184
216
|
if (this.logging) {
|
|
185
217
|
console.log(`[baileys-antiban] REACHOUT TIMELOCKED — ${state.enforcementType || 'unknown'}, expires ${state.expiresAt?.toISOString() || 'unknown'}`);
|
|
186
218
|
}
|
|
@@ -254,6 +286,17 @@ export class AntiBan {
|
|
|
254
286
|
};
|
|
255
287
|
this.sessionStabilityMonitor = new SessionHealthMonitor(healthConfig);
|
|
256
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
|
+
}
|
|
257
300
|
}
|
|
258
301
|
/**
|
|
259
302
|
* Check if a message can be sent and get required delay.
|
|
@@ -274,6 +317,17 @@ export class AntiBan {
|
|
|
274
317
|
health: healthStatus,
|
|
275
318
|
};
|
|
276
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
|
+
}
|
|
277
331
|
// Timelock guard (allows existing chats, blocks new contacts)
|
|
278
332
|
const timelockDecision = this.timelockGuard.canSend(recipient);
|
|
279
333
|
if (!timelockDecision.allowed) {
|
|
@@ -345,6 +399,22 @@ export class AntiBan {
|
|
|
345
399
|
health: healthStatus,
|
|
346
400
|
};
|
|
347
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
|
+
}
|
|
348
418
|
// Group profile rate check (runs before rateLimiter.getDelay for timing)
|
|
349
419
|
if (this.resolvedConfig.groupProfiles && shouldUseGroupProfile(recipient)) {
|
|
350
420
|
const groupLimits = applyGroupMultiplier({
|
|
@@ -384,6 +454,24 @@ export class AntiBan {
|
|
|
384
454
|
const multiplier = Math.min(5, 1 / activityFactor);
|
|
385
455
|
delay = Math.floor(delay * multiplier);
|
|
386
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
|
+
}
|
|
387
475
|
// Roll for distraction pause
|
|
388
476
|
const distractionCheck = this.presenceChoreographer.shouldPauseForDistraction();
|
|
389
477
|
if (distractionCheck.pause) {
|
|
@@ -411,11 +499,15 @@ export class AntiBan {
|
|
|
411
499
|
* Record a successfully sent message.
|
|
412
500
|
* Call this AFTER every successful sendMessage().
|
|
413
501
|
*/
|
|
414
|
-
afterSend(recipient, content) {
|
|
502
|
+
afterSend(recipient, content, msgId) {
|
|
415
503
|
this.rateLimiter.record(recipient, content);
|
|
416
504
|
this.warmUp.record();
|
|
417
505
|
this.replyRatioGuard.recordSent(recipient);
|
|
418
506
|
this.stats.messagesAllowed++;
|
|
507
|
+
if (msgId) {
|
|
508
|
+
this.deliveryTracker.onMessageSent(msgId);
|
|
509
|
+
}
|
|
510
|
+
this.runAdaptiveCheck();
|
|
419
511
|
this.persistStateDebounced();
|
|
420
512
|
}
|
|
421
513
|
/**
|
|
@@ -441,6 +533,7 @@ export class AntiBan {
|
|
|
441
533
|
onReconnect() {
|
|
442
534
|
this.health.recordReconnect();
|
|
443
535
|
this.reconnectThrottleModule.onReconnect();
|
|
536
|
+
this.rateLimiter.adaptLimits(1.0);
|
|
444
537
|
}
|
|
445
538
|
/**
|
|
446
539
|
* Handle incoming message — record in reply ratio + contact graph.
|
|
@@ -451,6 +544,13 @@ export class AntiBan {
|
|
|
451
544
|
this.contactGraphWarmer.onIncomingMessage(jid);
|
|
452
545
|
return this.replyRatioGuard.suggestReply(jid, msgText);
|
|
453
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
|
+
}
|
|
454
554
|
/**
|
|
455
555
|
* Get the resolved configuration
|
|
456
556
|
*/
|
|
@@ -466,6 +566,8 @@ export class AntiBan {
|
|
|
466
566
|
health: this.health.getStatus(),
|
|
467
567
|
warmUp: this.warmUp.getStatus(),
|
|
468
568
|
rateLimiter: this.rateLimiter.getStats(),
|
|
569
|
+
banRecovery: this.banRecovery.getStatus(),
|
|
570
|
+
deliveryTracker: this.deliveryTracker.getStats(),
|
|
469
571
|
};
|
|
470
572
|
// Only include new stats if enabled
|
|
471
573
|
if (this.replyRatioGuard['config']?.enabled) {
|
|
@@ -492,6 +594,9 @@ export class AntiBan {
|
|
|
492
594
|
if (this.sessionStabilityMonitor) {
|
|
493
595
|
stats.sessionStability = this.sessionStabilityMonitor.getStats();
|
|
494
596
|
}
|
|
597
|
+
if (this.instanceCoordinator) {
|
|
598
|
+
stats.instanceCoordinator = this.instanceCoordinator.getStats();
|
|
599
|
+
}
|
|
495
600
|
return stats;
|
|
496
601
|
}
|
|
497
602
|
/** Get the timelock guard for direct access */
|
|
@@ -530,6 +635,10 @@ export class AntiBan {
|
|
|
530
635
|
get sessionStability() {
|
|
531
636
|
return this.sessionStabilityMonitor;
|
|
532
637
|
}
|
|
638
|
+
/** Get the ban recovery orchestrator for direct access */
|
|
639
|
+
get recoveryOrchestrator() {
|
|
640
|
+
return this.banRecovery;
|
|
641
|
+
}
|
|
533
642
|
/**
|
|
534
643
|
* Export warm-up state for persistence between restarts
|
|
535
644
|
*/
|
|
@@ -571,6 +680,35 @@ export class AntiBan {
|
|
|
571
680
|
console.log('[baileys-antiban] 🔄 Reset — starting fresh warm-up');
|
|
572
681
|
}
|
|
573
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
|
+
}
|
|
574
712
|
persistStateDebounced() {
|
|
575
713
|
if (!this.stateManager)
|
|
576
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
|
+
}
|