baileys-antiban 3.5.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,19 @@ 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
+
8
21
  ## [3.5.0] — 2026-04-26
9
22
 
10
23
  ### 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
@@ -447,6 +463,40 @@ await sock.sendMessage(jid, { text: messageText });
447
463
  5. Executes plan: fires `sendPresenceUpdate('composing'/'paused')` + sleeps for each step
448
464
  6. Supports AbortSignal for mid-plan cancellation
449
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
+
450
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.
451
501
 
452
502
  ## baileys-antiban vs Whapi.Cloud vs DIY rate limiting
@@ -1110,6 +1160,21 @@ import type {
1110
1160
 
1111
1161
  Contributions are welcome! Please open an issue before submitting a PR.
1112
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
+
1113
1178
  ## Related Projects
1114
1179
 
1115
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/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';
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';
@@ -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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "baileys-antiban",
3
- "version": "3.5.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",