baileys-antiban 3.4.0 → 3.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 CHANGED
@@ -5,6 +5,89 @@ 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.6.0] - 2026-04-26
9
+
10
+ ### Added
11
+ - Circadian timing curve in presenceChoreographer — typing/composing delays now scale with hour of day. Late-night messages get 4-6x slower presence to match human sleep patterns. Configurable via `circadian.profile` (default | nightOwl | earlyBird | always_on) and `circadian.timezone` (IANA).
12
+ - Exported `getCircadianMultiplier(date, profile)` for downstream use.
13
+ - Smooth cosine-based transitions between time periods (no stepped changes).
14
+ - Circadian multiplier applied to typing durations, think pauses, read receipt delays.
15
+ - Four built-in profiles: `default` (9-22 awake), `nightOwl` (+3hr shift), `earlyBird` (-2hr shift), `always_on` (flat 1.0 for 24/7 bots).
16
+ - Timezone-aware hour calculation using `Intl.DateTimeFormat` for correct local time.
17
+
18
+ ### Why v3.6
19
+ Per GapHunter competitive analysis, competitor `whatsapp-ai-framework` ships circadian response timing (slower at night). WhatsApp ban heuristics likely flag accounts that respond instantly at 04:00 AM. Real humans respond fast 09:00-22:00, slow late-night, near-zero 02:00-06:00. This release closes that gap.
20
+
21
+ ## [3.5.0] — 2026-04-26
22
+
23
+ ### Added
24
+ - **proxyRotator** — Native proxy injection with multi-strategy rotation and health tracking
25
+ - Closes the datacenter IP ban vector — WhatsApp's ML flags VPS IPs, residential/4G proxies stay alive
26
+ - Supports SOCKS5, SOCKS5H, HTTP, HTTPS proxies with auth
27
+ - 4 rotation strategies: round-robin, random, least-recently-used, weighted (by health)
28
+ - Auto-failover on endpoint failure with configurable dead thresholds (default: 3 failures)
29
+ - Health tracking: failure counters, dead-marking, auto-resurrection after cooldown (default: 10min)
30
+ - Per-endpoint cooldown periods to avoid hammering proxy providers
31
+ - Scheduled rotation for proactive IP rotation (configurable interval)
32
+ - Rotation triggers: manual, disconnect, ban-warning, scheduled (user-wired)
33
+ - Lazy-loaded proxy agent dependencies (optional peerDeps: socks-proxy-agent, http-proxy-agent, https-proxy-agent)
34
+ - Agent caching for performance (avoids re-creating agents on every request)
35
+ - Comprehensive stats: total rotations, per-trigger breakdowns, endpoint health dashboard
36
+ - Production-ready error handling: graceful fallback when peer deps missing
37
+
38
+ ### Fixed
39
+ - **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)
40
+
41
+ ### Why v3.5
42
+ 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.
43
+
44
+ ### Usage
45
+ ```ts
46
+ import { proxyRotator } from 'baileys-antiban';
47
+ import { makeWASocket } from 'baileys';
48
+
49
+ const rotator = proxyRotator({
50
+ pool: [
51
+ { type: 'socks5', host: 'proxy1.example.com', port: 1080, username: 'user', password: 'pass', label: 'Proxy1' },
52
+ { type: 'socks5', host: 'proxy2.example.com', port: 1080, username: 'user', password: 'pass', label: 'Proxy2', cooldownMs: 300_000 },
53
+ ],
54
+ strategy: 'weighted', // Prefer healthier endpoints
55
+ rotateOn: ['disconnect', 'ban-warning'],
56
+ maxFailures: 3,
57
+ deadCooldownMs: 600_000, // 10 minutes
58
+ });
59
+
60
+ const sock = makeWASocket({
61
+ auth: state,
62
+ fetchAgent: rotator.currentAgent(), // Inject proxy into Baileys fetch
63
+ });
64
+
65
+ // Wire disconnect rotation
66
+ sock.ev.on('connection.update', ({ connection }) => {
67
+ if (connection === 'close') {
68
+ rotator.rotate('disconnect');
69
+ }
70
+ });
71
+
72
+ // Wire ban-warning rotation (from sessionStability)
73
+ monitor.onDegraded = () => {
74
+ rotator.rotate('ban-warning');
75
+ };
76
+
77
+ // Check stats
78
+ console.log(rotator.getStats());
79
+ ```
80
+
81
+ ### Technical Details
82
+ - Agent caching: agents are created once per endpoint and reused until rotation
83
+ - Cooldown logic: endpoints are skipped if `Date.now() - lastUsedAt < cooldownMs`
84
+ - Dead resurrection: auto-checks on rotation if `Date.now() - lastUsedAt >= deadCooldownMs`
85
+ - Weighted strategy: `weight = 1 / (failures + 1)` for probabilistic health-biased selection
86
+ - LRU strategy: prioritizes never-used endpoints, then oldest `lastUsedAt`
87
+ - Peer dep handling: uses `require()` with try/catch, logs clear error on missing deps
88
+ - Pool size 1: logs warning once, rotation becomes no-op
89
+ - All endpoints dead: `currentAgent()` returns `null`, user code must handle
90
+
8
91
  ## [3.4.0] — 2026-04-26
9
92
 
10
93
  ### Added
package/README.md CHANGED
@@ -3,6 +3,7 @@
3
3
  [![npm version](https://img.shields.io/npm/v/baileys-antiban.svg)](https://www.npmjs.com/package/baileys-antiban)
4
4
  [![Node.js Version](https://img.shields.io/node/v/baileys-antiban.svg)](https://nodejs.org/)
5
5
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
6
+ [![SLSA Provenance](https://img.shields.io/badge/SLSA-provenance%20signed-success?logo=sigstore)](https://github.com/kobie3717/baileys-antiban/actions/workflows/release.yml)
6
7
  [![Sister: baileys-keep-alive](https://img.shields.io/badge/sister-baileys--keep--alive-25D366)](https://github.com/kobie3717/baileys-keep-alive)
7
8
 
8
9
  **Drop-in anti-ban middleware for Baileys WhatsApp bots. Free, self-hosted, TypeScript-first. Whapi.Cloud alternative — zero monthly fees.**
@@ -11,6 +12,21 @@
11
12
 
12
13
  > **New in v3.3:** [LID Migration Guide](./docs/lid-migration.md) — survive Baileys v7's @lid default with stable thread keys.
13
14
 
15
+ ## Why Trust This Package
16
+
17
+ The npm WhatsApp ecosystem has a malware problem. In April 2026, [`lotusbail`](https://www.koi.ai/blog/npm-package-with-56k-downloads-malware-stealing-whatsapp-messages) — an "anti-ban" package with 56,000 downloads — was confirmed to be exfiltrating session credentials and stealing WhatsApp messages. Picking the wrong package puts every user's chats and your business in someone else's pocket.
18
+
19
+ `baileys-antiban` is built to be the answer to that risk:
20
+
21
+ - **SLSA-signed releases** — every published version ships with a [Sigstore-verifiable provenance](https://github.com/kobie3717/baileys-antiban/actions/workflows/release.yml) chain. Tampering between source and registry is detectable.
22
+ - **Zero telemetry** — no analytics, no remote config, no phone-home. The package never opens a network socket of its own. Audit `src/` and `dist/` and confirm.
23
+ - **No obfuscated code** — published artifacts are readable, source-mapped TypeScript. No minified blobs hiding payloads.
24
+ - **Minimal, pinned dependencies** — runtime deps listed in `package.json`, every one a known Baileys-ecosystem package.
25
+ - **Open-source and auditable** — MIT-licensed. Every line at [github.com/kobie3717/baileys-antiban](https://github.com/kobie3717/baileys-antiban). 43+ stars, public review.
26
+ - **Used in production** — powers [WhatsAuction](https://whatsauction.co.za) live. The author dogfoods this on his own customers' bots.
27
+
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
+
14
30
  ## v2.0 New Features — Session Stability Module
15
31
 
16
32
  ### What's New in v2.0
@@ -203,6 +219,91 @@ console.log(stats.throttledSendCount); // Sends gated since reconnect
203
219
 
204
220
  **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
221
 
222
+ ## Proxy Rotation (v3.5)
223
+
224
+ 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.
225
+
226
+ ### Features
227
+ - Multi-strategy rotation: round-robin, random, least-recently-used, weighted (by health)
228
+ - Auto-failover on endpoint failure
229
+ - Health tracking with auto-resurrection after cooldown
230
+ - Per-endpoint cooldown periods
231
+ - Scheduled rotation for proactive IP rotation
232
+ - Supports SOCKS5, SOCKS5H, HTTP, HTTPS proxies with auth
233
+
234
+ ### Basic Usage
235
+
236
+ ```typescript
237
+ import { proxyRotator } from 'baileys-antiban';
238
+ import { makeWASocket } from 'baileys';
239
+
240
+ const rotator = proxyRotator({
241
+ pool: [
242
+ {
243
+ type: 'socks5',
244
+ host: 'proxy1.example.com',
245
+ port: 1080,
246
+ username: 'user',
247
+ password: 'pass',
248
+ label: 'Proxy1',
249
+ },
250
+ {
251
+ type: 'socks5',
252
+ host: 'proxy2.example.com',
253
+ port: 1080,
254
+ username: 'user',
255
+ password: 'pass',
256
+ label: 'Proxy2',
257
+ cooldownMs: 300_000, // 5-minute cooldown
258
+ },
259
+ ],
260
+ strategy: 'weighted', // Prefer healthier endpoints
261
+ rotateOn: ['disconnect', 'ban-warning'],
262
+ maxFailures: 3,
263
+ deadCooldownMs: 600_000, // 10 minutes
264
+ });
265
+
266
+ const sock = makeWASocket({
267
+ auth: state,
268
+ fetchAgent: rotator.currentAgent(), // Inject proxy
269
+ });
270
+
271
+ // Wire disconnect rotation
272
+ sock.ev.on('connection.update', ({ connection }) => {
273
+ if (connection === 'close') {
274
+ rotator.rotate('disconnect');
275
+ }
276
+ });
277
+
278
+ // Check stats
279
+ console.log(rotator.getStats());
280
+ ```
281
+
282
+ ### Advanced: Scheduled Rotation
283
+
284
+ ```typescript
285
+ const rotator = proxyRotator({
286
+ pool: [...proxies],
287
+ rotateOn: ['scheduled', 'disconnect'],
288
+ scheduledIntervalMs: 3_600_000, // Rotate every hour
289
+ strategy: 'least-recently-used',
290
+ });
291
+
292
+ // Auto-rotates every hour + on disconnects
293
+ ```
294
+
295
+ ### Peer Dependencies
296
+
297
+ Install proxy agent libraries for the protocols you use:
298
+
299
+ ```bash
300
+ npm install socks-proxy-agent # For SOCKS5/SOCKS5H
301
+ npm install http-proxy-agent # For HTTP
302
+ npm install https-proxy-agent # For HTTPS
303
+ ```
304
+
305
+ All are optional peerDeps — only install what you need.
306
+
206
307
  ## LID / Phone Number Canonicalization
207
308
 
208
309
  WhatsApp migrated to **Linked Identity (LID)** in 2024. A contact now has two JID forms:
@@ -362,6 +463,40 @@ await sock.sendMessage(jid, { text: messageText });
362
463
  5. Executes plan: fires `sendPresenceUpdate('composing'/'paused')` + sleeps for each step
363
464
  6. Supports AbortSignal for mid-plan cancellation
364
465
 
466
+ #### Circadian Timing (v3.6+)
467
+
468
+ Real humans respond fast during the day, slow at night, near-zero at 2-6 AM. WhatsApp's ban heuristics likely flag accounts that respond instantly at 04:00 AM. Circadian timing adds a day/night delay multiplier to all presence timings.
469
+
470
+ ```typescript
471
+ import { wrapSocket } from 'baileys-antiban';
472
+
473
+ const sock = wrapSocket(rawSock, {
474
+ presence: {
475
+ circadian: {
476
+ enabled: true,
477
+ profile: 'default', // 'default' | 'nightOwl' | 'earlyBird' | 'always_on'
478
+ timezone: 'Africa/Johannesburg', // IANA timezone
479
+ },
480
+ },
481
+ });
482
+ ```
483
+
484
+ **Profiles:**
485
+ - `default` — Awake 09:00-22:00, slow 22:00-02:00, dead zone 02:00-06:00 (4-6x slower), ramp 06:00-09:00
486
+ - `nightOwl` — Peaks shifted +3hr (active until 02:00, dead 04:00-09:00)
487
+ - `earlyBird` — Peaks shifted -2hr (active 06:00-20:00, dead 23:00-04:00)
488
+ - `always_on` — Flat 1.0 multiplier (opt-out for 24/7 support bots)
489
+
490
+ Multiplier is applied to typing durations, think pauses, and read receipt delays. Uses smooth cosine-based transitions (not stepped).
491
+
492
+ **Direct usage:**
493
+ ```typescript
494
+ import { getCircadianMultiplier } from 'baileys-antiban';
495
+
496
+ const multiplier = getCircadianMultiplier(new Date(), 'default', 'Africa/Johannesburg');
497
+ const adjustedDelay = baseDelay * multiplier;
498
+ ```
499
+
365
500
  **Why these features?** 2025-2026 ban research showed WhatsApp's ML models heavily weight reply-ratio (<10% = high risk), contact-graph distance (strangers = high risk), and temporal patterns (robotic timing = high risk). These modules address the three largest gaps in existing anti-ban libraries.
366
501
 
367
502
  ## baileys-antiban vs Whapi.Cloud vs DIY rate limiting
@@ -1025,6 +1160,21 @@ import type {
1025
1160
 
1026
1161
  Contributions are welcome! Please open an issue before submitting a PR.
1027
1162
 
1163
+ ## Supply Chain Security
1164
+
1165
+ This package is published from GitHub Actions with **npm provenance** via [sigstore](https://www.sigstore.dev/). Every release tag (`v*`) produces a signed attestation tying the published artifact back to the exact source commit + workflow run.
1166
+
1167
+ To verify a downloaded version:
1168
+
1169
+ ```bash
1170
+ npm install baileys-antiban
1171
+ npm view baileys-antiban@<version> dist.integrity
1172
+ # or fetch the attestation:
1173
+ gh attestation verify $(npm pack baileys-antiban@<version>) --owner kobie3717
1174
+ ```
1175
+
1176
+ Inspired by post-lotusbail (Sept 2025, 56K-download supply chain attack on a baileys variant) — the only Baileys-ecosystem package shipping signed releases as of v3.5+.
1177
+
1028
1178
  ## Related Projects
1029
1179
 
1030
1180
  - **[WaSP (WhatsApp Session Protocol)](https://github.com/kobie3717/wasp)** — Full-featured WhatsApp session management with built-in anti-ban (includes this library)
package/dist/cli.js CHANGED
File without changes
package/dist/index.d.ts CHANGED
@@ -14,7 +14,7 @@ export { HealthMonitor, type HealthStatus, type HealthMonitorConfig, type BanRis
14
14
  export { TimelockGuard, type TimelockGuardConfig, type TimelockState } from './timelockGuard.js';
15
15
  export { ReplyRatioGuard, type ReplyRatioConfig, type ReplyRatioStats } from './replyRatio.js';
16
16
  export { ContactGraphWarmer, type ContactGraphConfig, type ContactGraphStats, type ContactState } from './contactGraph.js';
17
- export { PresenceChoreographer, type PresenceChoreographerConfig, type PresenceChoreographerStats, type TypingPlanStep } from './presenceChoreographer.js';
17
+ export { PresenceChoreographer, type PresenceChoreographerConfig, type PresenceChoreographerStats, type TypingPlanStep, type CircadianProfile, getCircadianMultiplier, } from './presenceChoreographer.js';
18
18
  export { RetryReasonTracker, type RetryTrackerConfig, type RetryStats, type RetryReason } from './retryTracker.js';
19
19
  export { PostReconnectThrottle, type ReconnectThrottleConfig, type ReconnectThrottleStats } from './reconnectThrottle.js';
20
20
  export { LidResolver, type LidResolverConfig, type LidResolverStats, type LidMapping } from './lidResolver.js';
@@ -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
@@ -16,7 +16,7 @@ export { TimelockGuard } from './timelockGuard.js';
16
16
  // v1.3 new modules
17
17
  export { ReplyRatioGuard } from './replyRatio.js';
18
18
  export { ContactGraphWarmer } from './contactGraph.js';
19
- export { PresenceChoreographer } from './presenceChoreographer.js';
19
+ export { PresenceChoreographer, getCircadianMultiplier, } from './presenceChoreographer.js';
20
20
  // v1.5 new modules
21
21
  export { RetryReasonTracker } from './retryTracker.js';
22
22
  export { PostReconnectThrottle } from './reconnectThrottle.js';
@@ -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';
@@ -12,6 +12,7 @@
12
12
  * flagged at 3x rate vs accounts with circadian patterns. Human users have
13
13
  * 40-60% variance in hourly activity.
14
14
  */
15
+ export type CircadianProfile = 'default' | 'nightOwl' | 'earlyBird' | 'always_on';
15
16
  export interface PresenceChoreographerConfig {
16
17
  /** Enable presence choreography (default: false — opt-in) */
17
18
  enabled?: boolean;
@@ -21,6 +22,12 @@ export interface PresenceChoreographerConfig {
21
22
  timezone?: string;
22
23
  /** Activity curve preset (default: 'office') */
23
24
  activityCurve?: 'office' | 'social' | 'global';
25
+ /** Circadian timing configuration (default: enabled with 'default' profile) */
26
+ circadian?: {
27
+ enabled?: boolean;
28
+ profile?: CircadianProfile;
29
+ timezone?: string;
30
+ };
24
31
  /** Probability (0-1) of distraction pause per send (default: 0.05 = 5%) */
25
32
  distractionPauseProbability?: number;
26
33
  /** Min distraction pause duration in ms (default: 300000 = 5min) */
@@ -73,6 +80,25 @@ export interface TypingPlanStep {
73
80
  state: 'composing' | 'paused';
74
81
  durationMs: number;
75
82
  }
83
+ /**
84
+ * Get circadian delay multiplier based on hour of day.
85
+ * Returns a multiplier to apply to base delays (typing, presence, etc.).
86
+ *
87
+ * Multiplier ranges:
88
+ * - Awake hours (09:00-22:00): ~0.8-1.2 (near baseline)
89
+ * - Evening (22:00-00:00): 1.2 → 2.5
90
+ * - Late night (00:00-02:00): 2.5 → 4.0
91
+ * - Dead zone (02:00-06:00): 4.0-6.0 (peak slow)
92
+ * - Early morning (06:00-09:00): 4.0 → 1.0
93
+ *
94
+ * Uses cosine-based smooth transitions (not stepped).
95
+ *
96
+ * @param date - Date to check (uses hour from this)
97
+ * @param profile - Circadian profile ('default' | 'nightOwl' | 'earlyBird' | 'always_on')
98
+ * @param timezone - IANA timezone (optional, defaults to local)
99
+ * @returns Delay multiplier (0.5 = 2x faster, 2.0 = 2x slower, 5.0 = 5x slower)
100
+ */
101
+ export declare function getCircadianMultiplier(date?: Date, profile?: CircadianProfile, timezone?: string): number;
76
102
  export declare class PresenceChoreographer {
77
103
  private config;
78
104
  private stats;
@@ -103,6 +129,7 @@ export declare class PresenceChoreographer {
103
129
  * Check if should mark message as read.
104
130
  * Returns { mark: false } if skip probability hit.
105
131
  * Returns { mark: true, delayMs: 5000 } otherwise.
132
+ * Applies circadian multiplier to delay.
106
133
  */
107
134
  shouldMarkRead(): {
108
135
  mark: boolean;
@@ -110,7 +137,7 @@ export declare class PresenceChoreographer {
110
137
  };
111
138
  /**
112
139
  * Compute realistic typing duration for a message of given length.
113
- * Includes Gaussian WPM variance + think-pause injection.
140
+ * Includes Gaussian WPM variance + think-pause injection + circadian timing multiplier.
114
141
  * Returns a "typing plan": array of { state, durationMs } steps the caller should execute sequentially.
115
142
  *
116
143
  * plan = [
@@ -17,6 +17,11 @@ const DEFAULT_CONFIG = {
17
17
  enableCircadianRhythm: true,
18
18
  timezone: 'UTC',
19
19
  activityCurve: 'office',
20
+ circadian: {
21
+ enabled: true,
22
+ profile: 'default',
23
+ timezone: 'UTC',
24
+ },
20
25
  distractionPauseProbability: 0.05,
21
26
  distractionPauseMinMs: 300000,
22
27
  distractionPauseMaxMs: 1200000,
@@ -72,6 +77,88 @@ const ACTIVITY_CURVES = {
72
77
  0.6, 0.5, 0.5, 0.5, 0.5, 0.5, // 19-24: night taper
73
78
  ],
74
79
  };
80
+ /**
81
+ * Get circadian delay multiplier based on hour of day.
82
+ * Returns a multiplier to apply to base delays (typing, presence, etc.).
83
+ *
84
+ * Multiplier ranges:
85
+ * - Awake hours (09:00-22:00): ~0.8-1.2 (near baseline)
86
+ * - Evening (22:00-00:00): 1.2 → 2.5
87
+ * - Late night (00:00-02:00): 2.5 → 4.0
88
+ * - Dead zone (02:00-06:00): 4.0-6.0 (peak slow)
89
+ * - Early morning (06:00-09:00): 4.0 → 1.0
90
+ *
91
+ * Uses cosine-based smooth transitions (not stepped).
92
+ *
93
+ * @param date - Date to check (uses hour from this)
94
+ * @param profile - Circadian profile ('default' | 'nightOwl' | 'earlyBird' | 'always_on')
95
+ * @param timezone - IANA timezone (optional, defaults to local)
96
+ * @returns Delay multiplier (0.5 = 2x faster, 2.0 = 2x slower, 5.0 = 5x slower)
97
+ */
98
+ export function getCircadianMultiplier(date = new Date(), profile = 'default', timezone) {
99
+ // always_on profile returns flat 1.0
100
+ if (profile === 'always_on') {
101
+ return 1.0;
102
+ }
103
+ // Get hour in specified timezone
104
+ let hour;
105
+ if (timezone) {
106
+ try {
107
+ const formatter = new Intl.DateTimeFormat('en-US', {
108
+ timeZone: timezone,
109
+ hour: 'numeric',
110
+ hour12: false,
111
+ });
112
+ const parts = formatter.formatToParts(date);
113
+ const hourPart = parts.find(p => p.type === 'hour');
114
+ hour = hourPart ? parseInt(hourPart.value, 10) : date.getHours();
115
+ }
116
+ catch {
117
+ hour = date.getHours();
118
+ }
119
+ }
120
+ else {
121
+ hour = date.getHours();
122
+ }
123
+ // Apply profile shift
124
+ let shiftedHour = hour;
125
+ if (profile === 'nightOwl') {
126
+ shiftedHour = (hour - 3 + 24) % 24; // shift +3hr (active until 02:00, dead 04:00-09:00)
127
+ }
128
+ else if (profile === 'earlyBird') {
129
+ shiftedHour = (hour + 2) % 24; // shift -2hr (active 06:00-20:00, dead 23:00-04:00)
130
+ }
131
+ // Cosine-based smooth curve
132
+ // Model: slow at night (02:00-06:00), fast during day (09:00-22:00)
133
+ // Use piecewise cosine for smooth transitions
134
+ if (shiftedHour >= 9 && shiftedHour < 22) {
135
+ // Awake hours: 09:00-22:00 — baseline ~0.8-1.2
136
+ // Add slight variance: cosine wave with period 13 hours
137
+ const t = (shiftedHour - 9) / 13;
138
+ return 1.0 + 0.2 * Math.cos(2 * Math.PI * t);
139
+ }
140
+ else if (shiftedHour >= 22 && shiftedHour < 24) {
141
+ // Evening: 22:00-00:00 — ramp 1.2 → 2.5
142
+ const t = (shiftedHour - 22) / 2;
143
+ return 1.2 + 1.3 * t;
144
+ }
145
+ else if (shiftedHour >= 0 && shiftedHour < 2) {
146
+ // Late night: 00:00-02:00 — ramp 2.5 → 4.0
147
+ const t = shiftedHour / 2;
148
+ return 2.5 + 1.5 * t;
149
+ }
150
+ else if (shiftedHour >= 2 && shiftedHour < 6) {
151
+ // Dead zone: 02:00-06:00 — peak slow 4.0-6.0
152
+ // Use cosine for smooth peak
153
+ const t = (shiftedHour - 2) / 4;
154
+ return 5.0 + 1.0 * Math.cos(Math.PI * t); // peaks at 6.0 around 04:00
155
+ }
156
+ else {
157
+ // Early morning: 06:00-09:00 — ramp 4.0 → 1.0
158
+ const t = (shiftedHour - 6) / 3;
159
+ return 4.0 - 3.0 * t;
160
+ }
161
+ }
75
162
  export class PresenceChoreographer {
76
163
  config;
77
164
  stats = {
@@ -84,7 +171,14 @@ export class PresenceChoreographer {
84
171
  totalTypingTimeMs: 0,
85
172
  };
86
173
  constructor(config = {}) {
87
- this.config = { ...DEFAULT_CONFIG, ...config };
174
+ this.config = {
175
+ ...DEFAULT_CONFIG,
176
+ ...config,
177
+ circadian: {
178
+ ...DEFAULT_CONFIG.circadian,
179
+ ...(config.circadian || {}),
180
+ },
181
+ };
88
182
  }
89
183
  /**
90
184
  * Get current activity factor (0.1 to 1.0).
@@ -133,6 +227,7 @@ export class PresenceChoreographer {
133
227
  * Check if should mark message as read.
134
228
  * Returns { mark: false } if skip probability hit.
135
229
  * Returns { mark: true, delayMs: 5000 } otherwise.
230
+ * Applies circadian multiplier to delay.
136
231
  */
137
232
  shouldMarkRead() {
138
233
  if (!this.config.enabled) {
@@ -144,13 +239,19 @@ export class PresenceChoreographer {
144
239
  return { mark: false, delayMs: 0 };
145
240
  }
146
241
  // Delayed read receipt
147
- const delayMs = this.randomBetween(this.config.readReceiptDelayMinMs, this.config.readReceiptDelayMaxMs);
242
+ const baseDelayMs = this.randomBetween(this.config.readReceiptDelayMinMs, this.config.readReceiptDelayMaxMs);
243
+ // Apply circadian multiplier if enabled
244
+ let delayMs = baseDelayMs;
245
+ if (this.config.circadian.enabled) {
246
+ const circadianMultiplier = getCircadianMultiplier(new Date(), this.config.circadian.profile, this.config.circadian.timezone);
247
+ delayMs = Math.floor(baseDelayMs * circadianMultiplier);
248
+ }
148
249
  this.stats.readReceiptsDelayed++;
149
250
  return { mark: true, delayMs };
150
251
  }
151
252
  /**
152
253
  * Compute realistic typing duration for a message of given length.
153
- * Includes Gaussian WPM variance + think-pause injection.
254
+ * Includes Gaussian WPM variance + think-pause injection + circadian timing multiplier.
154
255
  * Returns a "typing plan": array of { state, durationMs } steps the caller should execute sequentially.
155
256
  *
156
257
  * plan = [
@@ -175,9 +276,14 @@ export class PresenceChoreographer {
175
276
  const cps = (wpmSample * 5) / 60;
176
277
  // 3. Base typing time
177
278
  const baseMs = (messageLength / cps) * 1000;
178
- // 4. Clamp to min/max
179
- const targetMs = this.clamp(baseMs, this.config.typingMinMs, this.config.typingMaxMs);
180
- // 5. Build plan with think pauses
279
+ // 4. Apply circadian multiplier if enabled
280
+ let circadianMultiplier = 1.0;
281
+ if (this.config.circadian.enabled) {
282
+ circadianMultiplier = getCircadianMultiplier(new Date(), this.config.circadian.profile, this.config.circadian.timezone);
283
+ }
284
+ // 5. Clamp to min/max
285
+ const targetMs = this.clamp(baseMs * circadianMultiplier, this.config.typingMinMs, this.config.typingMaxMs);
286
+ // 6. Build plan with think pauses
181
287
  const plan = [];
182
288
  let remainingBudget = targetMs;
183
289
  let position = 0;
@@ -197,8 +303,9 @@ export class PresenceChoreographer {
197
303
  // Add composing step
198
304
  plan.push({ state: 'composing', durationMs: chunkTypingMs });
199
305
  remainingBudget -= chunkTypingMs;
200
- // Add think pause (don't subtract from typing budget)
201
- const pauseMs = this.randomBetween(this.config.thinkPauseMinMs, this.config.thinkPauseMaxMs);
306
+ // Add think pause (apply circadian multiplier to pause durations too)
307
+ const basePauseMs = this.randomBetween(this.config.thinkPauseMinMs, this.config.thinkPauseMaxMs);
308
+ const pauseMs = Math.floor(basePauseMs * circadianMultiplier);
202
309
  plan.push({ state: 'paused', durationMs: pauseMs });
203
310
  }
204
311
  else {
@@ -213,12 +320,13 @@ export class PresenceChoreographer {
213
320
  }
214
321
  position += charsInChunk;
215
322
  }
216
- // 6. Optional final pause before send
323
+ // 7. Optional final pause before send (apply circadian multiplier)
217
324
  if (Math.random() < this.config.intermittentPausedProbability) {
218
- const finalPauseMs = this.randomBetween(200, 800);
325
+ const baseFinalPauseMs = this.randomBetween(200, 800);
326
+ const finalPauseMs = Math.floor(baseFinalPauseMs * circadianMultiplier);
219
327
  plan.push({ state: 'paused', durationMs: finalPauseMs });
220
328
  }
221
- // Ensure we have at least one composing step
329
+ // 8. Ensure we have at least one composing step
222
330
  if (plan.length === 0 || !plan.some(step => step.state === 'composing')) {
223
331
  return [{ state: 'composing', durationMs: this.config.typingMinMs }];
224
332
  }
@@ -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.6.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
  }