baileys-antiban 3.4.0 → 3.5.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 CHANGED
@@ -5,6 +5,76 @@ 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.5.0] — 2026-04-26
9
+
10
+ ### Added
11
+ - **proxyRotator** — Native proxy injection with multi-strategy rotation and health tracking
12
+ - Closes the datacenter IP ban vector — WhatsApp's ML flags VPS IPs, residential/4G proxies stay alive
13
+ - Supports SOCKS5, SOCKS5H, HTTP, HTTPS proxies with auth
14
+ - 4 rotation strategies: round-robin, random, least-recently-used, weighted (by health)
15
+ - Auto-failover on endpoint failure with configurable dead thresholds (default: 3 failures)
16
+ - Health tracking: failure counters, dead-marking, auto-resurrection after cooldown (default: 10min)
17
+ - Per-endpoint cooldown periods to avoid hammering proxy providers
18
+ - Scheduled rotation for proactive IP rotation (configurable interval)
19
+ - Rotation triggers: manual, disconnect, ban-warning, scheduled (user-wired)
20
+ - Lazy-loaded proxy agent dependencies (optional peerDeps: socks-proxy-agent, http-proxy-agent, https-proxy-agent)
21
+ - Agent caching for performance (avoids re-creating agents on every request)
22
+ - Comprehensive stats: total rotations, per-trigger breakdowns, endpoint health dashboard
23
+ - Production-ready error handling: graceful fallback when peer deps missing
24
+
25
+ ### Fixed
26
+ - **proxyRotator**: Fixed ESM `require()` regression by using `createRequire()` from `node:module` for ESM-compatible synchronous module loading (caught by live SOCKS5 smoke test before publish)
27
+
28
+ ### Why v3.5
29
+ Per GapHunter analysis, WhatsApp's ban detection includes IP reputation scoring. Datacenter IPs (VPS) are flagged. Residential/4G proxies stay alive. Every Baileys implementation uses DIY proxy hacks — no library handles native proxy injection. `proxyRotator` closes that gap with production-grade rotation strategies, health tracking, and auto-failover.
30
+
31
+ ### Usage
32
+ ```ts
33
+ import { proxyRotator } from 'baileys-antiban';
34
+ import { makeWASocket } from 'baileys';
35
+
36
+ const rotator = proxyRotator({
37
+ pool: [
38
+ { type: 'socks5', host: 'proxy1.example.com', port: 1080, username: 'user', password: 'pass', label: 'Proxy1' },
39
+ { type: 'socks5', host: 'proxy2.example.com', port: 1080, username: 'user', password: 'pass', label: 'Proxy2', cooldownMs: 300_000 },
40
+ ],
41
+ strategy: 'weighted', // Prefer healthier endpoints
42
+ rotateOn: ['disconnect', 'ban-warning'],
43
+ maxFailures: 3,
44
+ deadCooldownMs: 600_000, // 10 minutes
45
+ });
46
+
47
+ const sock = makeWASocket({
48
+ auth: state,
49
+ fetchAgent: rotator.currentAgent(), // Inject proxy into Baileys fetch
50
+ });
51
+
52
+ // Wire disconnect rotation
53
+ sock.ev.on('connection.update', ({ connection }) => {
54
+ if (connection === 'close') {
55
+ rotator.rotate('disconnect');
56
+ }
57
+ });
58
+
59
+ // Wire ban-warning rotation (from sessionStability)
60
+ monitor.onDegraded = () => {
61
+ rotator.rotate('ban-warning');
62
+ };
63
+
64
+ // Check stats
65
+ console.log(rotator.getStats());
66
+ ```
67
+
68
+ ### Technical Details
69
+ - Agent caching: agents are created once per endpoint and reused until rotation
70
+ - Cooldown logic: endpoints are skipped if `Date.now() - lastUsedAt < cooldownMs`
71
+ - Dead resurrection: auto-checks on rotation if `Date.now() - lastUsedAt >= deadCooldownMs`
72
+ - Weighted strategy: `weight = 1 / (failures + 1)` for probabilistic health-biased selection
73
+ - LRU strategy: prioritizes never-used endpoints, then oldest `lastUsedAt`
74
+ - Peer dep handling: uses `require()` with try/catch, logs clear error on missing deps
75
+ - Pool size 1: logs warning once, rotation becomes no-op
76
+ - All endpoints dead: `currentAgent()` returns `null`, user code must handle
77
+
8
78
  ## [3.4.0] — 2026-04-26
9
79
 
10
80
  ### Added
package/README.md CHANGED
@@ -203,6 +203,91 @@ console.log(stats.throttledSendCount); // Sends gated since reconnect
203
203
 
204
204
  **Why?** When WhatsApp reconnects after a disconnection, sending messages at full rate immediately can trigger rate limit alarms. The reconnect throttle gradually ramps up sending rate over 60 seconds, mimicking how a human would resume messaging after their internet came back.
205
205
 
206
+ ## Proxy Rotation (v3.5)
207
+
208
+ WhatsApp's ban detection includes **IP reputation scoring**. Datacenter IPs (VPS) are flagged. Residential/4G proxies stay alive. No Baileys library handles native proxy injection — every implementation uses DIY hacks. `proxyRotator` closes that gap.
209
+
210
+ ### Features
211
+ - Multi-strategy rotation: round-robin, random, least-recently-used, weighted (by health)
212
+ - Auto-failover on endpoint failure
213
+ - Health tracking with auto-resurrection after cooldown
214
+ - Per-endpoint cooldown periods
215
+ - Scheduled rotation for proactive IP rotation
216
+ - Supports SOCKS5, SOCKS5H, HTTP, HTTPS proxies with auth
217
+
218
+ ### Basic Usage
219
+
220
+ ```typescript
221
+ import { proxyRotator } from 'baileys-antiban';
222
+ import { makeWASocket } from 'baileys';
223
+
224
+ const rotator = proxyRotator({
225
+ pool: [
226
+ {
227
+ type: 'socks5',
228
+ host: 'proxy1.example.com',
229
+ port: 1080,
230
+ username: 'user',
231
+ password: 'pass',
232
+ label: 'Proxy1',
233
+ },
234
+ {
235
+ type: 'socks5',
236
+ host: 'proxy2.example.com',
237
+ port: 1080,
238
+ username: 'user',
239
+ password: 'pass',
240
+ label: 'Proxy2',
241
+ cooldownMs: 300_000, // 5-minute cooldown
242
+ },
243
+ ],
244
+ strategy: 'weighted', // Prefer healthier endpoints
245
+ rotateOn: ['disconnect', 'ban-warning'],
246
+ maxFailures: 3,
247
+ deadCooldownMs: 600_000, // 10 minutes
248
+ });
249
+
250
+ const sock = makeWASocket({
251
+ auth: state,
252
+ fetchAgent: rotator.currentAgent(), // Inject proxy
253
+ });
254
+
255
+ // Wire disconnect rotation
256
+ sock.ev.on('connection.update', ({ connection }) => {
257
+ if (connection === 'close') {
258
+ rotator.rotate('disconnect');
259
+ }
260
+ });
261
+
262
+ // Check stats
263
+ console.log(rotator.getStats());
264
+ ```
265
+
266
+ ### Advanced: Scheduled Rotation
267
+
268
+ ```typescript
269
+ const rotator = proxyRotator({
270
+ pool: [...proxies],
271
+ rotateOn: ['scheduled', 'disconnect'],
272
+ scheduledIntervalMs: 3_600_000, // Rotate every hour
273
+ strategy: 'least-recently-used',
274
+ });
275
+
276
+ // Auto-rotates every hour + on disconnects
277
+ ```
278
+
279
+ ### Peer Dependencies
280
+
281
+ Install proxy agent libraries for the protocols you use:
282
+
283
+ ```bash
284
+ npm install socks-proxy-agent # For SOCKS5/SOCKS5H
285
+ npm install http-proxy-agent # For HTTP
286
+ npm install https-proxy-agent # For HTTPS
287
+ ```
288
+
289
+ All are optional peerDeps — only install what you need.
290
+
206
291
  ## LID / Phone Number Canonicalization
207
292
 
208
293
  WhatsApp migrated to **Linked Identity (LID)** in 2024. A contact now has two JID forms:
package/dist/cli.js CHANGED
File without changes
package/dist/index.d.ts CHANGED
@@ -35,3 +35,4 @@ export { messageRecovery, type MessageRecoveryConfig, type MessageRecoveryStats,
35
35
  export { generateFingerprint, applyFingerprint, type DeviceFingerprint, type DeviceFingerprintConfig, } from './deviceFingerprint.js';
36
36
  export { credsSnapshot, type CredsSnapshot, type CredsSnapshotConfig, } from './credsSnapshot.js';
37
37
  export { readReceiptVariance, type ReadReceiptVariance, type ReadReceiptVarianceConfig, } from './readReceiptVariance.js';
38
+ export { proxyRotator, type ProxyEndpoint, type ProxyRotatorConfig, type ProxyRotatorStats, type ProxyRotatorHandle, } from './proxyRotator.js';
package/dist/index.js CHANGED
@@ -47,3 +47,5 @@ export { messageRecovery } from './messageRecovery.js';
47
47
  export { generateFingerprint, applyFingerprint, } from './deviceFingerprint.js';
48
48
  export { credsSnapshot, } from './credsSnapshot.js';
49
49
  export { readReceiptVariance, } from './readReceiptVariance.js';
50
+ // v3.5 new modules
51
+ export { proxyRotator, } from './proxyRotator.js';
@@ -0,0 +1,79 @@
1
+ /**
2
+ * Proxy Rotation — Native proxy injection for Baileys with health tracking
3
+ *
4
+ * WhatsApp's ban detection includes IP reputation scoring. Datacenter IPs (VPS)
5
+ * are flagged, while residential/4G proxies stay alive. No Baileys library handles
6
+ * native proxy injection — every implementation is DIY hacks. We close that gap.
7
+ *
8
+ * Features:
9
+ * - Multi-strategy rotation (round-robin, random, LRU, weighted)
10
+ * - Auto-failover on endpoint failure
11
+ * - Scheduled rotation for proactive IP rotation
12
+ * - Cooldown periods between endpoint reuse
13
+ * - Health tracking and auto-resurrection
14
+ * - Lazy-loaded proxy agent dependencies (optional peerDeps)
15
+ *
16
+ * @author Kobus Wentzel <kobie@pop.co.za>
17
+ * @license MIT
18
+ */
19
+ import type { Agent } from 'node:http';
20
+ export interface ProxyEndpoint {
21
+ type: 'socks5' | 'socks5h' | 'http' | 'https';
22
+ host: string;
23
+ port: number;
24
+ username?: string;
25
+ password?: string;
26
+ /** Optional health label — humans use this in logs */
27
+ label?: string;
28
+ /** Cooldown after last use, in ms (default: 0) */
29
+ cooldownMs?: number;
30
+ }
31
+ export interface ProxyRotatorConfig {
32
+ /** Pool of proxy endpoints. Required. */
33
+ pool: ProxyEndpoint[];
34
+ /** Strategy for picking next proxy (default: 'round-robin') */
35
+ strategy?: 'round-robin' | 'random' | 'least-recently-used' | 'weighted';
36
+ /** Auto-rotate on these triggers (default: ['disconnect', 'ban-warning']) */
37
+ rotateOn?: Array<'disconnect' | 'ban-warning' | 'scheduled' | 'manual'>;
38
+ /** Scheduled rotation interval in ms (only if rotateOn includes 'scheduled') */
39
+ scheduledIntervalMs?: number;
40
+ /** Max consecutive failures before marking endpoint dead (default: 3) */
41
+ maxFailures?: number;
42
+ /** How long a "dead" endpoint stays out of rotation (default: 600_000 = 10min) */
43
+ deadCooldownMs?: number;
44
+ /** Logger */
45
+ logger?: {
46
+ info?: Function;
47
+ warn?: Function;
48
+ error?: Function;
49
+ };
50
+ }
51
+ export interface ProxyRotatorStats {
52
+ totalRotations: number;
53
+ rotationsByTrigger: Record<string, number>;
54
+ endpointHealth: Array<{
55
+ label: string;
56
+ inUse: boolean;
57
+ failures: number;
58
+ lastUsedAt: Date | null;
59
+ isDead: boolean;
60
+ }>;
61
+ currentEndpoint: string | null;
62
+ }
63
+ export interface ProxyRotatorHandle {
64
+ /** Get an Agent for the current endpoint. Use this in fetchOptions.agent or makeWASocket's options.agent */
65
+ currentAgent(): Agent | null;
66
+ /** Get the current endpoint's metadata */
67
+ current(): ProxyEndpoint | null;
68
+ /** Force rotate to next endpoint. Reason logged in stats. */
69
+ rotate(reason?: 'manual' | 'disconnect' | 'ban-warning' | 'scheduled'): ProxyEndpoint | null;
70
+ /** Mark current endpoint as failed (increments failure counter, may auto-rotate) */
71
+ markFailure(): void;
72
+ /** Clear all dead-flags (e.g. for cooldown override) */
73
+ resurrectAll(): void;
74
+ /** Stop scheduled rotation timer + dispose */
75
+ stop(): void;
76
+ /** Stats */
77
+ getStats(): ProxyRotatorStats;
78
+ }
79
+ export declare function proxyRotator(config: ProxyRotatorConfig): ProxyRotatorHandle;
@@ -0,0 +1,295 @@
1
+ /**
2
+ * Proxy Rotation — Native proxy injection for Baileys with health tracking
3
+ *
4
+ * WhatsApp's ban detection includes IP reputation scoring. Datacenter IPs (VPS)
5
+ * are flagged, while residential/4G proxies stay alive. No Baileys library handles
6
+ * native proxy injection — every implementation is DIY hacks. We close that gap.
7
+ *
8
+ * Features:
9
+ * - Multi-strategy rotation (round-robin, random, LRU, weighted)
10
+ * - Auto-failover on endpoint failure
11
+ * - Scheduled rotation for proactive IP rotation
12
+ * - Cooldown periods between endpoint reuse
13
+ * - Health tracking and auto-resurrection
14
+ * - Lazy-loaded proxy agent dependencies (optional peerDeps)
15
+ *
16
+ * @author Kobus Wentzel <kobie@pop.co.za>
17
+ * @license MIT
18
+ */
19
+ import { createRequire } from 'node:module';
20
+ // Create require for optional peer dependency loading in ESM
21
+ const require = createRequire(import.meta.url);
22
+ const NoopLogger = {
23
+ info: () => { },
24
+ warn: () => { },
25
+ error: () => { },
26
+ };
27
+ export function proxyRotator(config) {
28
+ const { pool, strategy = 'round-robin', rotateOn = ['disconnect', 'ban-warning'], scheduledIntervalMs = 0, maxFailures = 3, deadCooldownMs = 600_000, // 10 minutes
29
+ logger = NoopLogger, } = config;
30
+ if (!pool || pool.length === 0) {
31
+ throw new Error('proxyRotator: pool cannot be empty');
32
+ }
33
+ // Warn once for pool size 1
34
+ if (pool.length === 1) {
35
+ logger.warn?.('proxyRotator: pool size is 1. Rotation is a no-op.');
36
+ }
37
+ // Warn for aggressive scheduled rotation
38
+ if (scheduledIntervalMs > 0 && scheduledIntervalMs < 60_000) {
39
+ logger.warn?.(`proxyRotator: scheduledIntervalMs (${scheduledIntervalMs}ms) is < 60s. May hammer proxy provider.`);
40
+ }
41
+ // Internal state
42
+ const states = pool.map((endpoint) => ({
43
+ endpoint,
44
+ failures: 0,
45
+ lastUsedAt: null,
46
+ isDead: false,
47
+ }));
48
+ let currentIndex = 0;
49
+ let totalRotations = 0;
50
+ const rotationsByTrigger = {};
51
+ let scheduledTimer = null;
52
+ // Agent cache: map endpoint -> Agent (cleared on rotation)
53
+ const agentCache = new Map();
54
+ // Module cache for lazy-loaded proxy agents
55
+ const moduleCache = {};
56
+ function buildProxyUrl(endpoint) {
57
+ const { type, host, port, username, password } = endpoint;
58
+ const auth = username && password ? `${username}:${password}@` : '';
59
+ return `${type}://${auth}${host}:${port}`;
60
+ }
61
+ function createAgentForEndpointSync(endpoint) {
62
+ // Check cache first
63
+ if (agentCache.has(endpoint)) {
64
+ return agentCache.get(endpoint);
65
+ }
66
+ const url = buildProxyUrl(endpoint);
67
+ let agent = null;
68
+ try {
69
+ if (endpoint.type === 'socks5' || endpoint.type === 'socks5h') {
70
+ if (!moduleCache['socks-proxy-agent']) {
71
+ try {
72
+ moduleCache['socks-proxy-agent'] = require('socks-proxy-agent');
73
+ }
74
+ catch {
75
+ logger.error?.('socks-proxy-agent not installed. Run: npm install socks-proxy-agent');
76
+ return null;
77
+ }
78
+ }
79
+ agent = new moduleCache['socks-proxy-agent'].SocksProxyAgent(url);
80
+ }
81
+ else if (endpoint.type === 'http') {
82
+ if (!moduleCache['http-proxy-agent']) {
83
+ try {
84
+ moduleCache['http-proxy-agent'] = require('http-proxy-agent');
85
+ }
86
+ catch {
87
+ logger.error?.('http-proxy-agent not installed. Run: npm install http-proxy-agent');
88
+ return null;
89
+ }
90
+ }
91
+ agent = new moduleCache['http-proxy-agent'].HttpProxyAgent(url);
92
+ }
93
+ else if (endpoint.type === 'https') {
94
+ if (!moduleCache['https-proxy-agent']) {
95
+ try {
96
+ moduleCache['https-proxy-agent'] = require('https-proxy-agent');
97
+ }
98
+ catch {
99
+ logger.error?.('https-proxy-agent not installed. Run: npm install https-proxy-agent');
100
+ return null;
101
+ }
102
+ }
103
+ agent = new moduleCache['https-proxy-agent'].HttpsProxyAgent(url);
104
+ }
105
+ else {
106
+ logger.error?.(`Unknown proxy type: ${endpoint.type}`);
107
+ return null;
108
+ }
109
+ // Cache the agent
110
+ if (agent) {
111
+ agentCache.set(endpoint, agent);
112
+ }
113
+ return agent;
114
+ }
115
+ catch (err) {
116
+ logger.error?.(`Failed to create agent for ${endpoint.label || endpoint.host}: ${err}`);
117
+ return null;
118
+ }
119
+ }
120
+ function getAliveEndpoints() {
121
+ const now = Date.now();
122
+ return states
123
+ .map((s, idx) => {
124
+ // Dead check with auto-resurrection
125
+ if (s.isDead && s.lastUsedAt) {
126
+ if (now - s.lastUsedAt.getTime() >= deadCooldownMs) {
127
+ s.isDead = false;
128
+ s.failures = 0;
129
+ logger.info?.(`Resurrected endpoint ${s.endpoint.label || s.endpoint.host} after cooldown`);
130
+ }
131
+ }
132
+ // Cooldown check
133
+ const cooldown = s.endpoint.cooldownMs || 0;
134
+ if (cooldown > 0 && s.lastUsedAt) {
135
+ if (now - s.lastUsedAt.getTime() < cooldown) {
136
+ return -1; // Still in cooldown
137
+ }
138
+ }
139
+ return !s.isDead ? idx : -1;
140
+ })
141
+ .filter((idx) => idx !== -1);
142
+ }
143
+ function selectNextIndex(alive) {
144
+ if (alive.length === 0)
145
+ return currentIndex; // All dead, stay on current
146
+ if (strategy === 'round-robin') {
147
+ // Pick next after currentIndex in circular fashion
148
+ const afterCurrent = alive.filter((idx) => idx > currentIndex);
149
+ if (afterCurrent.length > 0)
150
+ return afterCurrent[0];
151
+ return alive[0]; // Wrap around
152
+ }
153
+ if (strategy === 'random') {
154
+ return alive[Math.floor(Math.random() * alive.length)];
155
+ }
156
+ if (strategy === 'least-recently-used') {
157
+ // Pick the one with oldest lastUsedAt (never-used = highest priority)
158
+ const neverUsed = alive.filter((idx) => states[idx].lastUsedAt === null);
159
+ if (neverUsed.length > 0) {
160
+ return neverUsed[0];
161
+ }
162
+ let oldestIdx = alive[0];
163
+ let oldestTime = states[oldestIdx].lastUsedAt.getTime();
164
+ for (const idx of alive) {
165
+ const time = states[idx].lastUsedAt.getTime();
166
+ if (time < oldestTime) {
167
+ oldestTime = time;
168
+ oldestIdx = idx;
169
+ }
170
+ }
171
+ return oldestIdx;
172
+ }
173
+ if (strategy === 'weighted') {
174
+ // Weighted by inverse failure count (healthier = more likely)
175
+ const weights = alive.map((idx) => {
176
+ const failures = states[idx].failures;
177
+ return 1 / (failures + 1); // Avoid divide-by-zero
178
+ });
179
+ const totalWeight = weights.reduce((a, b) => a + b, 0);
180
+ let rand = Math.random() * totalWeight;
181
+ for (let i = 0; i < alive.length; i++) {
182
+ rand -= weights[i];
183
+ if (rand <= 0)
184
+ return alive[i];
185
+ }
186
+ return alive[alive.length - 1]; // Fallback
187
+ }
188
+ return alive[0]; // Default fallback
189
+ }
190
+ function rotateImpl(reason = 'manual') {
191
+ if (pool.length === 1) {
192
+ // No-op for single endpoint
193
+ return states[0].endpoint;
194
+ }
195
+ const alive = getAliveEndpoints();
196
+ if (alive.length === 0) {
197
+ logger.warn?.('All endpoints are dead. Cannot rotate.');
198
+ return states[currentIndex].endpoint;
199
+ }
200
+ const nextIdx = selectNextIndex(alive);
201
+ if (nextIdx === currentIndex && alive.length > 1) {
202
+ // Try to pick a different one if possible
203
+ const others = alive.filter((idx) => idx !== currentIndex);
204
+ if (others.length > 0) {
205
+ currentIndex = others[0];
206
+ }
207
+ else {
208
+ currentIndex = nextIdx;
209
+ }
210
+ }
211
+ else {
212
+ currentIndex = nextIdx;
213
+ }
214
+ states[currentIndex].lastUsedAt = new Date();
215
+ totalRotations++;
216
+ rotationsByTrigger[reason] = (rotationsByTrigger[reason] || 0) + 1;
217
+ const label = states[currentIndex].endpoint.label || states[currentIndex].endpoint.host;
218
+ logger.info?.(`Rotated to endpoint ${label} (reason: ${reason})`);
219
+ return states[currentIndex].endpoint;
220
+ }
221
+ function markFailureImpl() {
222
+ const state = states[currentIndex];
223
+ state.failures++;
224
+ const label = state.endpoint.label || state.endpoint.host;
225
+ logger.warn?.(`Endpoint ${label} failed (${state.failures}/${maxFailures})`);
226
+ if (state.failures >= maxFailures) {
227
+ state.isDead = true;
228
+ logger.error?.(`Endpoint ${label} marked DEAD after ${maxFailures} failures`);
229
+ // Auto-rotate to next alive endpoint
230
+ const alive = getAliveEndpoints();
231
+ if (alive.length > 0) {
232
+ rotateImpl('manual'); // Trigger rotation as recovery
233
+ }
234
+ }
235
+ }
236
+ function resurrectAllImpl() {
237
+ let count = 0;
238
+ for (const state of states) {
239
+ if (state.isDead) {
240
+ state.isDead = false;
241
+ state.failures = 0;
242
+ count++;
243
+ }
244
+ }
245
+ if (count > 0) {
246
+ logger.info?.(`Resurrected ${count} dead endpoint(s)`);
247
+ }
248
+ }
249
+ function stopImpl() {
250
+ if (scheduledTimer) {
251
+ clearInterval(scheduledTimer);
252
+ scheduledTimer = null;
253
+ logger.info?.('Stopped scheduled rotation timer');
254
+ }
255
+ }
256
+ function getStatsImpl() {
257
+ return {
258
+ totalRotations,
259
+ rotationsByTrigger: { ...rotationsByTrigger },
260
+ endpointHealth: states.map((s) => ({
261
+ label: s.endpoint.label || s.endpoint.host,
262
+ inUse: states[currentIndex] === s,
263
+ failures: s.failures,
264
+ lastUsedAt: s.lastUsedAt,
265
+ isDead: s.isDead,
266
+ })),
267
+ currentEndpoint: states[currentIndex].endpoint.label || states[currentIndex].endpoint.host,
268
+ };
269
+ }
270
+ function currentAgentImpl() {
271
+ const endpoint = states[currentIndex].endpoint;
272
+ return createAgentForEndpointSync(endpoint);
273
+ }
274
+ function currentImpl() {
275
+ return states[currentIndex].endpoint;
276
+ }
277
+ // Setup scheduled rotation if enabled
278
+ if (rotateOn.includes('scheduled') && scheduledIntervalMs > 0) {
279
+ scheduledTimer = setInterval(() => {
280
+ rotateImpl('scheduled');
281
+ }, scheduledIntervalMs);
282
+ logger.info?.(`Scheduled rotation enabled (every ${scheduledIntervalMs}ms)`);
283
+ }
284
+ // Initialize: select first endpoint
285
+ states[0].lastUsedAt = new Date();
286
+ return {
287
+ currentAgent: currentAgentImpl,
288
+ current: currentImpl,
289
+ rotate: rotateImpl,
290
+ markFailure: markFailureImpl,
291
+ resurrectAll: resurrectAllImpl,
292
+ stop: stopImpl,
293
+ getStats: getStatsImpl,
294
+ };
295
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "baileys-antiban",
3
- "version": "3.4.0",
3
+ "version": "3.5.0",
4
4
  "description": "Anti-ban middleware for Baileys WhatsApp bots. Rate limiting, warmup, health monitor, LID resolver, disconnect classifier. Free Whapi.Cloud alternative.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -76,6 +76,15 @@
76
76
  },
77
77
  "@oxidezap/baileyrs": {
78
78
  "optional": true
79
+ },
80
+ "socks-proxy-agent": {
81
+ "optional": true
82
+ },
83
+ "http-proxy-agent": {
84
+ "optional": true
85
+ },
86
+ "https-proxy-agent": {
87
+ "optional": true
79
88
  }
80
89
  },
81
90
  "devDependencies": {
@@ -86,5 +95,10 @@
86
95
  "tsx": "^4.21.0",
87
96
  "typescript": "^5.0.0",
88
97
  "vitest": "^4.1.5"
98
+ },
99
+ "dependencies": {
100
+ "http-proxy-agent": "^9.0.0",
101
+ "https-proxy-agent": "^9.0.0",
102
+ "socks-proxy-agent": "^10.0.0"
89
103
  }
90
104
  }