baileys-antiban 3.8.6 → 3.8.8

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,37 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [3.8.8] - 2026-05-15
9
+
10
+ ### Added
11
+ - **`high-volume` preset** — for established, fully-warmed accounts running enterprise-scale operations. Limits: 40 msg/min, 1500/hr, 8000/day, 400–1800ms delays. Only use on accounts with 6+ months history and no prior bans. Set via `wrapSocket(sock, 'high-volume')` or env var `ANTIBAN_PRESET=high-volume`.
12
+ - **Env-var integration pattern in docs** — full example showing how to drive every antiban parameter from environment variables inside a bot framework (avoids redeploying to tune limits). Based on real-world usage patterns from Zyra (kaikybrofc/zyra).
13
+
14
+ ## [3.8.7] - 2026-05-14
15
+
16
+ ### Added
17
+ - **`baileys-antiban patch` CLI command** — for frameworks that create the Baileys socket internally (OpenClaw `@openclaw/whatsapp`, custom ESM loaders) and don't expose a `socket:ready` hook. Automatically locates the Baileys install, detects CJS vs ESM output format, backs up the original file, and injects a `wrapSocket()` call around `makeWASocket`. Idempotent — safe to re-run after plugin updates. Pairs with a `postinstall` npm script for zero-maintenance re-patching.
18
+ - Auto-discovers Baileys in `node_modules/@openclaw/whatsapp/node_modules/baileys`, `node_modules/baileys`, `node_modules/@whiskeysockets/baileys`, and parent directories.
19
+ - `--path <dir>` — explicit Baileys directory override.
20
+ - `--preset conservative|moderate|aggressive` — antiban profile (default: `conservative`).
21
+ - `--min-delay` / `--max-delay` — override delay range in ms (default 1500–4000).
22
+ - `--no-typing` — disable typing indicators.
23
+ - `--dry-run` — preview without writing files.
24
+ - `--force` — re-patch even if already patched.
25
+ - Runtime config via env: `ANTIBAN_PRESET`, `ANTIBAN_MIN_DELAY`, `ANTIBAN_MAX_DELAY`, `ANTIBAN_TYPING`.
26
+ - **`baileys-antiban unpatch` CLI command** — restores a patched file from its `.antiban-backup` or strips the patch block in-place if backup is missing.
27
+
28
+ ## [3.8.6] - 2026-05-14
29
+
30
+ ### Fixed
31
+ - **CJS build for NestJS / CommonJS consumers.** Adds `dist/cjs/` output compiled with `module: CommonJS`, a `{"type":"commonjs"}` package marker injected at build time, and an `exports["require"]` condition so `require('baileys-antiban')` now resolves cleanly. Previously CommonJS callers hit `ERR_PACKAGE_PATH_NOT_EXPORTED` or `ERR_REQUIRE_ESM`.
32
+ - **`messageRecovery` persistence load in ESM context.** `loadPersistence()` previously called `require('fs')` which is not defined in native ESM scope, throwing `ReferenceError` at runtime whenever `persistPath` was configured. Fixed by using static `import { existsSync, readFileSync } from 'node:fs'` at module top level.
33
+ - **`messageRecovery` doc comment.** Placeholder `issues/XXX` issue reference updated to `issues/2491` (deaf-session / silent message loss tracker).
34
+
35
+ ### Changed
36
+ - Removed unused `jest`, `@types/jest`, and `ts-jest` devDependencies (test runner uses `tsx` directly; `vitest` retained as future framework).
37
+ - **Docs: ESM plugin-framework integration.** Added troubleshooting section to README covering `wrapSocket()` as the correct integration point for OpenClaw, custom ESM loaders, and other plugin frameworks that use native ESM. Documents why `Module._load` interception does not work with ESM and how to set up `ev.process` + `ev.on` fallback correctly.
38
+
8
39
  ## [3.8.5] - 2026-05-09
9
40
 
10
41
  ### Added
package/README.md CHANGED
@@ -1045,6 +1045,124 @@ const antiban = new AntiBan({
1045
1045
  });
1046
1046
  ```
1047
1047
 
1048
+ ### Using inside a plugin framework (OpenClaw, custom ESM loaders, etc.)
1049
+
1050
+ `baileys-antiban` is a **pure ESM package**. `wrapSocket()` is the correct integration point — it works in any ESM or CJS context without patching Baileys directly.
1051
+
1052
+ If your framework (e.g. OpenClaw's WhatsApp plugin) uses native ESM, **do not attempt `Module._load` interception** — it only intercepts CJS modules and silently does nothing for ESM imports.
1053
+
1054
+ Correct approach — wrap the socket after it's created, regardless of how it was imported:
1055
+
1056
+ ```typescript
1057
+ import { makeWASocket } from 'baileys'; // or however your framework exposes it
1058
+ import { wrapSocket } from 'baileys-antiban';
1059
+
1060
+ const rawSock = makeWASocket({ ... });
1061
+ const sock = wrapSocket(rawSock); // drop-in — use sock everywhere
1062
+ ```
1063
+
1064
+ If your framework creates the socket internally and only exposes it via a callback or event, wrap it at that point:
1065
+
1066
+ ```typescript
1067
+ // OpenClaw / plugin pattern
1068
+ framework.on('socket', (rawSock) => {
1069
+ const sock = wrapSocket(rawSock);
1070
+ // use sock from here on
1071
+ });
1072
+ ```
1073
+
1074
+ **If your framework creates the socket with no event or callback exposed** (common in tightly-integrated plugins), use the `patch` CLI command — see [CLI: patch command](#cli-patch-command) below.
1075
+
1076
+ > **Note:** If you install/update the Baileys package or plugin via a package manager, `wrapSocket()` survives the update untouched. Patching Baileys source directly (as a workaround) will be reset by any reinstall — use the `patch` command to automate re-patching.
1077
+
1078
+ ---
1079
+
1080
+ ### CLI: patch command
1081
+
1082
+ For plugin frameworks where `makeWASocket` is called internally and no socket hook is exposed (e.g. OpenClaw `@openclaw/whatsapp`), the `patch` command modifies the installed Baileys package to inject `wrapSocket()` automatically.
1083
+
1084
+ ```bash
1085
+ # Auto-detect Baileys location + apply patch
1086
+ npx baileys-antiban patch
1087
+
1088
+ # OpenClaw: Baileys is nested inside the plugin
1089
+ npx baileys-antiban patch --path ./node_modules/@openclaw/whatsapp/node_modules/baileys
1090
+
1091
+ # Custom profile
1092
+ npx baileys-antiban patch --preset moderate --min-delay 1000 --max-delay 3000
1093
+
1094
+ # Preview without writing (dry run)
1095
+ npx baileys-antiban patch --dry-run
1096
+
1097
+ # Restore original
1098
+ npx baileys-antiban unpatch --file ./node_modules/baileys/lib/socket/index.js
1099
+ ```
1100
+
1101
+ The patch is **idempotent** — re-running it on an already-patched file is a no-op. A `.antiban-backup` file is kept alongside the patched file for safe restoration.
1102
+
1103
+ **Auto re-patch after plugin updates** — add a `postinstall` script to your project's `package.json`:
1104
+
1105
+ ```json
1106
+ {
1107
+ "scripts": {
1108
+ "postinstall": "npx baileys-antiban patch --path ./node_modules/@openclaw/whatsapp/node_modules/baileys"
1109
+ }
1110
+ }
1111
+ ```
1112
+
1113
+ Now every `npm install` / `openclaw plugins install @openclaw/whatsapp` automatically re-applies the patch.
1114
+
1115
+ **Runtime config via environment variables** (no re-patch needed to change settings):
1116
+
1117
+ | Variable | Default | Description |
1118
+ |---|---|---|
1119
+ | `ANTIBAN_PRESET` | `conservative` | `conservative`, `moderate`, or `aggressive` |
1120
+ | `ANTIBAN_MIN_DELAY` | `1500` | Minimum delay between messages (ms) |
1121
+ | `ANTIBAN_MAX_DELAY` | `4000` | Maximum delay between messages (ms) |
1122
+ | `ANTIBAN_TYPING` | `true` | Enable typing indicators (`false` to disable) |
1123
+
1124
+ ---
1125
+
1126
+ ### Full env-var configuration (framework integration pattern)
1127
+
1128
+ When embedding `baileys-antiban` inside a framework or bot engine, drive every parameter from environment variables so you can tune without redeploying:
1129
+
1130
+ ```typescript
1131
+ import { wrapSocket } from 'baileys-antiban';
1132
+ import type { WrapOptions } from 'baileys-antiban';
1133
+
1134
+ function readBoolean(key: string, fallback: boolean): boolean {
1135
+ const v = process.env[key];
1136
+ return v === undefined ? fallback : v !== 'false' && v !== '0';
1137
+ }
1138
+ function readNumber(key: string, fallback: number): number {
1139
+ const v = process.env[key];
1140
+ return v === undefined ? fallback : parseInt(v, 10);
1141
+ }
1142
+
1143
+ const antibanOptions: WrapOptions = {
1144
+ preset: (process.env.WA_ANTIBAN_PRESET as any) || 'conservative',
1145
+ // rate limits
1146
+ maxPerMinute: readNumber('WA_ANTIBAN_MAX_PER_MINUTE', undefined as any),
1147
+ maxPerHour: readNumber('WA_ANTIBAN_MAX_PER_HOUR', undefined as any),
1148
+ maxPerDay: readNumber('WA_ANTIBAN_MAX_PER_DAY', undefined as any),
1149
+ minDelayMs: readNumber('WA_ANTIBAN_MIN_DELAY_MS', undefined as any),
1150
+ maxDelayMs: readNumber('WA_ANTIBAN_MAX_DELAY_MS', undefined as any),
1151
+ // warmup
1152
+ warmupDays: readNumber('WA_ANTIBAN_WARMUP_DAYS', undefined as any),
1153
+ // health
1154
+ logging: readBoolean('WA_ANTIBAN_LOGGING', true),
1155
+ // deaf session
1156
+ deafSession: readBoolean('WA_ANTIBAN_DEAF_SESSION_ENABLED', true)
1157
+ ? { timeoutMs: readNumber('WA_ANTIBAN_DEAF_TIMEOUT_MS', 300_000) }
1158
+ : undefined,
1159
+ };
1160
+
1161
+ const sock = wrapSocket(makeWASocket({ ... }), antibanOptions);
1162
+ ```
1163
+
1164
+ Undefined values fall back to the preset defaults — so you only override what you need. Set `WA_ANTIBAN_PRESET=high-volume` for established enterprise accounts, `WA_ANTIBAN_PRESET=conservative` for new numbers.
1165
+
1048
1166
  ### State not persisting across restarts
1049
1167
 
1050
1168
  Use the FileStateAdapter:
package/dist/cjs/cli.js CHANGED
@@ -41,6 +41,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
41
41
  const fs = __importStar(require("fs"));
42
42
  const persist_js_1 = require("./persist.js");
43
43
  const presets_js_1 = require("./presets.js");
44
+ const patch_js_1 = require("./patch.js");
44
45
  const args = process.argv.slice(2);
45
46
  const command = args[0];
46
47
  function parseArgs(argv) {
@@ -127,6 +128,45 @@ function cmdWarmupSimulate(opts) {
127
128
  console.log('─'.repeat(50));
128
129
  console.log(`Day ${days + 1}+: graduated (unlimited by warmup)\n`);
129
130
  }
131
+ function cmdPatch(opts) {
132
+ const searchPaths = opts['path'] ? [opts['path']] : undefined;
133
+ const preset = opts['preset'] || 'conservative';
134
+ const minDelay = opts['min-delay'] ? parseInt(opts['min-delay']) : 1500;
135
+ const maxDelay = opts['max-delay'] ? parseInt(opts['max-delay']) : 4000;
136
+ const typingIndicator = opts['no-typing'] ? false : true;
137
+ const dryRun = !!opts['dry-run'];
138
+ const force = !!opts['force'];
139
+ const result = (0, patch_js_1.applyPatch)({
140
+ baileysPaths: searchPaths,
141
+ preset,
142
+ minDelay,
143
+ maxDelay,
144
+ typingIndicator,
145
+ dryRun,
146
+ });
147
+ if (result.alreadyPatched && !force) {
148
+ console.log(result.message);
149
+ process.exit(0);
150
+ }
151
+ if (!result.success) {
152
+ console.error('✗ ' + result.message);
153
+ process.exit(1);
154
+ }
155
+ console.log(result.message);
156
+ }
157
+ function cmdUnpatch(opts) {
158
+ const targetFile = opts['file'];
159
+ if (!targetFile) {
160
+ console.error('Usage: npx baileys-antiban unpatch --file <path-to-patched-file>');
161
+ process.exit(1);
162
+ }
163
+ const result = (0, patch_js_1.unpatchFile)(targetFile);
164
+ if (!result.success) {
165
+ console.error('✗ ' + result.message);
166
+ process.exit(1);
167
+ }
168
+ console.log(result.message);
169
+ }
130
170
  // Main
131
171
  const opts = parseArgs(args);
132
172
  switch (command) {
@@ -145,6 +185,12 @@ switch (command) {
145
185
  process.exit(1);
146
186
  }
147
187
  break;
188
+ case 'patch':
189
+ cmdPatch(opts);
190
+ break;
191
+ case 'unpatch':
192
+ cmdUnpatch(opts);
193
+ break;
148
194
  default:
149
195
  console.log('baileys-antiban v3.0');
150
196
  console.log('');
@@ -152,9 +198,15 @@ switch (command) {
152
198
  console.log(' status [--state <path>] [--json] Show warmup and health status');
153
199
  console.log(' reset --state <path> Delete state file');
154
200
  console.log(' warmup --simulate <days> [--preset] Show warmup schedule');
201
+ console.log(' patch [--path <baileys-dir>] [--preset] [--min-delay] [--max-delay] [--no-typing] [--dry-run]');
202
+ console.log(' unpatch --file <patched-file> Restore file from backup');
155
203
  console.log('');
156
204
  console.log('Examples:');
157
205
  console.log(' npx baileys-antiban status --state ./antiban-state.json');
158
206
  console.log(' npx baileys-antiban warmup --simulate 7 --preset moderate');
159
207
  console.log(' npx baileys-antiban reset --state ./antiban-state.json');
208
+ console.log(' npx baileys-antiban patch');
209
+ console.log(' npx baileys-antiban patch --path ./node_modules/@openclaw/whatsapp/node_modules/baileys');
210
+ console.log(' npx baileys-antiban patch --preset moderate --min-delay 1000 --max-delay 3000');
211
+ console.log(' npx baileys-antiban unpatch --file ./node_modules/baileys/lib/socket/index.js');
160
212
  }
@@ -1,20 +1,4 @@
1
1
  "use strict";
2
- /**
3
- * Message Recovery — Solves Baileys' silent message loss on 408 reconnect
4
- *
5
- * After a 408 disconnect (and other clean reconnect paths), offline messages
6
- * arrive on the server side but never fire `messages.upsert` events in Baileys.
7
- * Bots silently lose messages from the disconnect window.
8
- *
9
- * This module:
10
- * 1. Tracks the last message ID seen per chat while connected
11
- * 2. Detects disconnect/reconnect cycles via connection.update events
12
- * 3. On reconnect, queries Baileys' message store for messages newer than lastSeen
13
- * 4. Re-emits gap messages through user callback (wired to messages.upsert handler)
14
- * 5. Fires onGapTooLarge if disconnect > maxGapMs instead of partial recovery
15
- *
16
- * @see https://github.com/WhiskeySockets/Baileys/issues/XXX (47+ upvotes)
17
- */
18
2
  var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
19
3
  if (k2 === undefined) k2 = k;
20
4
  var desc = Object.getOwnPropertyDescriptor(m, k);
@@ -50,6 +34,7 @@ var __importStar = (this && this.__importStar) || (function () {
50
34
  })();
51
35
  Object.defineProperty(exports, "__esModule", { value: true });
52
36
  exports.messageRecovery = messageRecovery;
37
+ const node_fs_1 = require("node:fs");
53
38
  const DEFAULT_CONFIG = {
54
39
  maxTrackedChats: 1000,
55
40
  maxGapMs: 30 * 60_000, // 30 minutes
@@ -73,7 +58,7 @@ function messageRecovery(sock, config) {
73
58
  // Persistence
74
59
  let persistTimer = null;
75
60
  let loggedFetchWarning = false;
76
- // Load persisted state on startup
61
+ // Load persisted state on startup (synchronous — seeds lastSeen before first event)
77
62
  if (cfg.persistPath) {
78
63
  loadPersistence();
79
64
  }
@@ -281,10 +266,9 @@ function messageRecovery(sock, config) {
281
266
  if (!cfg.persistPath)
282
267
  return;
283
268
  try {
284
- const fs = require('fs');
285
- if (!fs.existsSync(cfg.persistPath))
269
+ if (!(0, node_fs_1.existsSync)(cfg.persistPath))
286
270
  return;
287
- const raw = fs.readFileSync(cfg.persistPath, 'utf-8');
271
+ const raw = (0, node_fs_1.readFileSync)(cfg.persistPath, 'utf-8');
288
272
  const data = JSON.parse(raw);
289
273
  for (const [jid, entry] of Object.entries(data)) {
290
274
  lastSeen.set(jid, {
@@ -0,0 +1,339 @@
1
+ "use strict";
2
+ /**
3
+ * baileys-antiban patch command
4
+ *
5
+ * Patches an installed Baileys package to wrap makeWASocket with antiban middleware.
6
+ * Designed for frameworks (OpenClaw, etc.) that create the socket internally and
7
+ * don't expose a socket:ready hook.
8
+ *
9
+ * Idempotent: safe to re-run after plugin updates. Keeps a .antiban-backup file.
10
+ *
11
+ * Usage:
12
+ * npx baileys-antiban patch [--path <baileys-dir>] [--dry-run] [--preset conservative|moderate|aggressive]
13
+ */
14
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
15
+ if (k2 === undefined) k2 = k;
16
+ var desc = Object.getOwnPropertyDescriptor(m, k);
17
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
18
+ desc = { enumerable: true, get: function() { return m[k]; } };
19
+ }
20
+ Object.defineProperty(o, k2, desc);
21
+ }) : (function(o, m, k, k2) {
22
+ if (k2 === undefined) k2 = k;
23
+ o[k2] = m[k];
24
+ }));
25
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
26
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
27
+ }) : function(o, v) {
28
+ o["default"] = v;
29
+ });
30
+ var __importStar = (this && this.__importStar) || (function () {
31
+ var ownKeys = function(o) {
32
+ ownKeys = Object.getOwnPropertyNames || function (o) {
33
+ var ar = [];
34
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
35
+ return ar;
36
+ };
37
+ return ownKeys(o);
38
+ };
39
+ return function (mod) {
40
+ if (mod && mod.__esModule) return mod;
41
+ var result = {};
42
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
43
+ __setModuleDefault(result, mod);
44
+ return result;
45
+ };
46
+ })();
47
+ Object.defineProperty(exports, "__esModule", { value: true });
48
+ exports.applyPatch = applyPatch;
49
+ exports.unpatchFile = unpatchFile;
50
+ const fs = __importStar(require("fs"));
51
+ const path = __importStar(require("path"));
52
+ const PATCH_MARKER_START = '// [BAILEYS-ANTIBAN-PATCH-START]';
53
+ const PATCH_MARKER_END = '// [BAILEYS-ANTIBAN-PATCH-END]';
54
+ function findBaileysDir(candidates) {
55
+ for (const candidate of candidates) {
56
+ if (!candidate)
57
+ continue;
58
+ const pkgPath = path.join(candidate, 'package.json');
59
+ if (fs.existsSync(pkgPath)) {
60
+ try {
61
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
62
+ if (pkg.name === 'baileys' || pkg.name === '@whiskeysockets/baileys' || pkg.name === '@oxidezap/baileyrs') {
63
+ return path.resolve(candidate);
64
+ }
65
+ }
66
+ catch {
67
+ // not a valid package.json, skip
68
+ }
69
+ }
70
+ }
71
+ return null;
72
+ }
73
+ function findMakeWASocketFile(baileyDir) {
74
+ // Ordered by likelihood of containing the actual makeWASocket definition
75
+ const candidates = [
76
+ 'lib/socket/index.js',
77
+ 'lib/index.js',
78
+ 'lib/socket.js',
79
+ 'src/socket/index.js',
80
+ 'dist/socket/index.js',
81
+ 'dist/index.js',
82
+ ];
83
+ for (const rel of candidates) {
84
+ const full = path.join(baileyDir, rel);
85
+ if (!fs.existsSync(full))
86
+ continue;
87
+ const source = fs.readFileSync(full, 'utf-8');
88
+ // Look for makeWASocket being defined (not just re-exported via export *)
89
+ if (source.includes('function makeWASocket') ||
90
+ source.includes('const makeWASocket') ||
91
+ source.includes('exports.makeWASocket') ||
92
+ source.includes('makeWASocket =')) {
93
+ const isEsm = source.includes('export function makeWASocket') ||
94
+ source.includes('export const makeWASocket') ||
95
+ source.includes('export {') ||
96
+ source.includes('export default');
97
+ return { file: full, format: isEsm ? 'esm' : 'cjs' };
98
+ }
99
+ }
100
+ // Fallback: search lib/ directory for the defining file
101
+ const libDir = path.join(baileyDir, 'lib');
102
+ if (fs.existsSync(libDir)) {
103
+ const jsFiles = fs
104
+ .readdirSync(libDir, { recursive: true })
105
+ .filter((f) => typeof f === 'string' && f.endsWith('.js'))
106
+ .map((f) => path.join(libDir, f));
107
+ for (const full of jsFiles) {
108
+ try {
109
+ const source = fs.readFileSync(full, 'utf-8');
110
+ if (source.includes('function makeWASocket') && source.includes('makeWASocket')) {
111
+ const isEsm = source.includes('export');
112
+ return { file: full, format: isEsm ? 'esm' : 'cjs' };
113
+ }
114
+ }
115
+ catch {
116
+ // skip unreadable files
117
+ }
118
+ }
119
+ }
120
+ return null;
121
+ }
122
+ function buildCjsPatch(opts) {
123
+ return `
124
+ ${PATCH_MARKER_START}
125
+ // Auto-generated by baileys-antiban patch. Re-run after Baileys/plugin updates.
126
+ (function() {
127
+ try {
128
+ const { wrapSocket } = require('baileys-antiban');
129
+ const _orig = module.exports.makeWASocket;
130
+ if (typeof _orig === 'function') {
131
+ module.exports.makeWASocket = function(...args) {
132
+ return wrapSocket(_orig.apply(this, args), {
133
+ preset: process.env.ANTIBAN_PRESET || '${opts.preset}',
134
+ delayBetweenMessages: {
135
+ min: parseInt(process.env.ANTIBAN_MIN_DELAY || '${opts.minDelay}'),
136
+ max: parseInt(process.env.ANTIBAN_MAX_DELAY || '${opts.maxDelay}'),
137
+ },
138
+ typingIndicator: (process.env.ANTIBAN_TYPING || '${opts.typing}') !== 'false',
139
+ });
140
+ };
141
+ }
142
+ } catch(e) {
143
+ console.warn('[baileys-antiban] patch inactive:', e.message);
144
+ }
145
+ })();
146
+ ${PATCH_MARKER_END}
147
+ `;
148
+ }
149
+ function buildEsmPatch(source, opts) {
150
+ // Strategy A: function declaration — rename and re-export wrapped version
151
+ if (source.includes('export function makeWASocket')) {
152
+ const patched = source.replace(/export function makeWASocket\b/, 'function _antiban_makeWASocket_orig');
153
+ return (patched +
154
+ `
155
+ ${PATCH_MARKER_START}
156
+ // Auto-generated by baileys-antiban patch. Re-run after Baileys/plugin updates.
157
+ import { wrapSocket as _antibanWrap } from 'baileys-antiban';
158
+ export const makeWASocket = (...args) => _antibanWrap(_antiban_makeWASocket_orig(...args), {
159
+ preset: process.env.ANTIBAN_PRESET || '${opts.preset}',
160
+ delayBetweenMessages: {
161
+ min: parseInt(process.env.ANTIBAN_MIN_DELAY || '${opts.minDelay}'),
162
+ max: parseInt(process.env.ANTIBAN_MAX_DELAY || '${opts.maxDelay}'),
163
+ },
164
+ typingIndicator: (process.env.ANTIBAN_TYPING || '${opts.typing}') !== 'false',
165
+ });
166
+ ${PATCH_MARKER_END}
167
+ `);
168
+ }
169
+ // Strategy B: const/let declaration — rename and re-export
170
+ if (source.match(/export\s+(const|let)\s+makeWASocket\s*=/)) {
171
+ const patched = source.replace(/export\s+(const|let)\s+makeWASocket\s*=/, 'const _antiban_makeWASocket_orig =');
172
+ return (patched +
173
+ `
174
+ ${PATCH_MARKER_START}
175
+ // Auto-generated by baileys-antiban patch. Re-run after Baileys/plugin updates.
176
+ import { wrapSocket as _antibanWrap } from 'baileys-antiban';
177
+ export const makeWASocket = (...args) => _antibanWrap(_antiban_makeWASocket_orig(...args), {
178
+ preset: process.env.ANTIBAN_PRESET || '${opts.preset}',
179
+ delayBetweenMessages: {
180
+ min: parseInt(process.env.ANTIBAN_MIN_DELAY || '${opts.minDelay}'),
181
+ max: parseInt(process.env.ANTIBAN_MAX_DELAY || '${opts.maxDelay}'),
182
+ },
183
+ typingIndicator: (process.env.ANTIBAN_TYPING || '${opts.typing}') !== 'false',
184
+ });
185
+ ${PATCH_MARKER_END}
186
+ `);
187
+ }
188
+ // Strategy C: named re-export — intercept at this boundary
189
+ if (source.includes('makeWASocket')) {
190
+ // Append import + re-export override at end of file
191
+ return (source +
192
+ `
193
+ ${PATCH_MARKER_START}
194
+ // Auto-generated by baileys-antiban patch. Re-run after Baileys/plugin updates.
195
+ // NOTE: Strategy C (named export intercept) — less precise than A/B.
196
+ // If this breaks, use --path to target the file where makeWASocket is defined.
197
+ import { wrapSocket as _antibanWrap } from 'baileys-antiban';
198
+ const _antiban_raw = makeWASocket;
199
+ export { makeWASocket }; // shadowed below if engine supports block-level export rebind
200
+ // Fallback: log advisory if strategy C can't reassign
201
+ if (typeof makeWASocket === 'function') {
202
+ // eslint-disable-next-line no-global-assign
203
+ try { makeWASocket = (...a) => _antibanWrap(_antiban_raw(...a), { preset: '${opts.preset}', delayBetweenMessages: { min: ${opts.minDelay}, max: ${opts.maxDelay} }, typingIndicator: ${opts.typing} }); } catch {}
204
+ }
205
+ ${PATCH_MARKER_END}
206
+ `);
207
+ }
208
+ throw new Error('Could not locate makeWASocket in this file. Try --path to a different Baileys file.');
209
+ }
210
+ function applyPatch(opts) {
211
+ const { preset = 'conservative', minDelay = 1500, maxDelay = 4000, typingIndicator = true, dryRun = false, } = opts;
212
+ // 1. Locate Baileys
213
+ const defaultSearchPaths = [
214
+ // OC-style nested location
215
+ './node_modules/@openclaw/whatsapp/node_modules/baileys',
216
+ './node_modules/@openclaw/whatsapp/node_modules/@whiskeysockets/baileys',
217
+ // Standard locations
218
+ './node_modules/baileys',
219
+ './node_modules/@whiskeysockets/baileys',
220
+ // Walk up
221
+ '../node_modules/baileys',
222
+ '../../node_modules/baileys',
223
+ ];
224
+ const searchPaths = opts.baileysPaths?.length ? opts.baileysPaths : defaultSearchPaths;
225
+ const baileyDir = findBaileysDir(searchPaths);
226
+ if (!baileyDir) {
227
+ return {
228
+ success: false,
229
+ baileyDir: '(not found)',
230
+ targetFile: '(not found)',
231
+ alreadyPatched: false,
232
+ format: 'cjs',
233
+ message: 'Could not find Baileys package. Use --path <dir> to specify its location.\n' +
234
+ 'Example: npx baileys-antiban patch --path ./node_modules/@openclaw/whatsapp/node_modules/baileys',
235
+ };
236
+ }
237
+ // 2. Find file containing makeWASocket
238
+ const found = findMakeWASocketFile(baileyDir);
239
+ if (!found) {
240
+ return {
241
+ success: false,
242
+ baileyDir,
243
+ targetFile: '(not found)',
244
+ alreadyPatched: false,
245
+ format: 'cjs',
246
+ message: `Found Baileys at ${baileyDir} but could not locate makeWASocket definition.\n` +
247
+ 'The package structure may have changed. Please open an issue.',
248
+ };
249
+ }
250
+ const { file: targetFile, format } = found;
251
+ const source = fs.readFileSync(targetFile, 'utf-8');
252
+ // 3. Check idempotency
253
+ if (source.includes(PATCH_MARKER_START)) {
254
+ return {
255
+ success: true,
256
+ baileyDir,
257
+ targetFile,
258
+ alreadyPatched: true,
259
+ format,
260
+ message: `Already patched: ${targetFile}\nRe-run with --force to re-apply.`,
261
+ };
262
+ }
263
+ if (dryRun) {
264
+ return {
265
+ success: true,
266
+ baileyDir,
267
+ targetFile,
268
+ alreadyPatched: false,
269
+ format,
270
+ message: `[dry-run] Would patch ${targetFile} (format: ${format})`,
271
+ };
272
+ }
273
+ // 4. Backup
274
+ const backupPath = targetFile + '.antiban-backup';
275
+ if (!fs.existsSync(backupPath)) {
276
+ fs.copyFileSync(targetFile, backupPath);
277
+ }
278
+ // 5. Build and apply patch
279
+ const patchOpts = { preset, minDelay, maxDelay, typing: typingIndicator };
280
+ let patched;
281
+ try {
282
+ if (format === 'cjs') {
283
+ patched = source + buildCjsPatch(patchOpts);
284
+ }
285
+ else {
286
+ patched = buildEsmPatch(source, patchOpts);
287
+ }
288
+ }
289
+ catch (e) {
290
+ return {
291
+ success: false,
292
+ baileyDir,
293
+ targetFile,
294
+ alreadyPatched: false,
295
+ format,
296
+ message: `Patch generation failed: ${e.message}`,
297
+ };
298
+ }
299
+ fs.writeFileSync(targetFile, patched, 'utf-8');
300
+ return {
301
+ success: true,
302
+ baileyDir,
303
+ targetFile,
304
+ alreadyPatched: false,
305
+ format,
306
+ backupPath,
307
+ message: `✅ Patched: ${targetFile}\n` +
308
+ ` Format: ${format}\n` +
309
+ ` Backup: ${backupPath}\n` +
310
+ ` Preset: ${preset} (${minDelay}–${maxDelay}ms delays, typing=${typingIndicator})\n` +
311
+ `\nTo auto-re-patch after plugin updates, add to your package.json:\n` +
312
+ ` "scripts": { "postinstall": "npx baileys-antiban patch" }\n\n` +
313
+ `Env overrides: ANTIBAN_PRESET, ANTIBAN_MIN_DELAY, ANTIBAN_MAX_DELAY, ANTIBAN_TYPING`,
314
+ };
315
+ }
316
+ function unpatchFile(targetFile) {
317
+ if (!fs.existsSync(targetFile)) {
318
+ return { success: false, message: `File not found: ${targetFile}` };
319
+ }
320
+ const backupPath = targetFile + '.antiban-backup';
321
+ if (!fs.existsSync(backupPath)) {
322
+ // Try removing the patch block from source
323
+ const source = fs.readFileSync(targetFile, 'utf-8');
324
+ if (!source.includes(PATCH_MARKER_START)) {
325
+ return { success: true, message: 'File is not patched.' };
326
+ }
327
+ const startIdx = source.indexOf(PATCH_MARKER_START);
328
+ const endIdx = source.indexOf(PATCH_MARKER_END);
329
+ if (startIdx === -1 || endIdx === -1) {
330
+ return { success: false, message: 'Patch markers found but malformed. Restore manually from backup.' };
331
+ }
332
+ const cleaned = source.slice(0, startIdx) + source.slice(endIdx + PATCH_MARKER_END.length);
333
+ fs.writeFileSync(targetFile, cleaned, 'utf-8');
334
+ return { success: true, message: `Patch removed from ${targetFile}` };
335
+ }
336
+ fs.copyFileSync(backupPath, targetFile);
337
+ fs.unlinkSync(backupPath);
338
+ return { success: true, message: `Restored from backup: ${backupPath}` };
339
+ }
@@ -51,6 +51,24 @@ exports.PRESETS = {
51
51
  groupProfiles: false,
52
52
  logging: true,
53
53
  },
54
+ // For established, fully-warmed accounts running enterprise-scale operations.
55
+ // Only use on accounts with 6+ months history and no prior bans.
56
+ 'high-volume': {
57
+ maxPerMinute: 40,
58
+ maxPerHour: 1500,
59
+ maxPerDay: 8000,
60
+ minDelayMs: 400,
61
+ maxDelayMs: 1800,
62
+ newChatDelayMs: 1200,
63
+ warmupDays: 3,
64
+ day1Limit: 60,
65
+ growthFactor: 2.5,
66
+ inactivityThresholdHours: 24,
67
+ autoPauseAt: 'critical',
68
+ groupMultiplier: 0.95,
69
+ groupProfiles: false,
70
+ logging: true,
71
+ },
54
72
  };
55
73
  function resolveConfig(input) {
56
74
  if (input === undefined) {
@@ -58,14 +76,14 @@ function resolveConfig(input) {
58
76
  }
59
77
  if (typeof input === 'string') {
60
78
  if (!(input in exports.PRESETS)) {
61
- throw new Error(`Unknown preset "${input}". Valid: ${Object.keys(exports.PRESETS).join(', ')}`);
79
+ throw new Error(`Unknown preset "${input}". Valid: conservative, moderate, aggressive, high-volume`);
62
80
  }
63
81
  return { ...exports.PRESETS[input] };
64
82
  }
65
83
  // Object form — extract preset base, merge overrides
66
84
  const { preset = 'conservative', ...overrides } = input;
67
85
  if (!(preset in exports.PRESETS)) {
68
- throw new Error(`Unknown preset "${preset}". Valid: ${Object.keys(exports.PRESETS).join(', ')}`);
86
+ throw new Error(`Unknown preset "${preset}". Valid: conservative, moderate, aggressive, high-volume`);
69
87
  }
70
88
  return { ...exports.PRESETS[preset], ...overrides };
71
89
  }
package/dist/cli.js CHANGED
@@ -6,6 +6,7 @@
6
6
  import * as fs from 'fs';
7
7
  import { StateManager } from './persist.js';
8
8
  import { resolveConfig } from './presets.js';
9
+ import { applyPatch, unpatchFile } from './patch.js';
9
10
  const args = process.argv.slice(2);
10
11
  const command = args[0];
11
12
  function parseArgs(argv) {
@@ -92,6 +93,45 @@ function cmdWarmupSimulate(opts) {
92
93
  console.log('─'.repeat(50));
93
94
  console.log(`Day ${days + 1}+: graduated (unlimited by warmup)\n`);
94
95
  }
96
+ function cmdPatch(opts) {
97
+ const searchPaths = opts['path'] ? [opts['path']] : undefined;
98
+ const preset = opts['preset'] || 'conservative';
99
+ const minDelay = opts['min-delay'] ? parseInt(opts['min-delay']) : 1500;
100
+ const maxDelay = opts['max-delay'] ? parseInt(opts['max-delay']) : 4000;
101
+ const typingIndicator = opts['no-typing'] ? false : true;
102
+ const dryRun = !!opts['dry-run'];
103
+ const force = !!opts['force'];
104
+ const result = applyPatch({
105
+ baileysPaths: searchPaths,
106
+ preset,
107
+ minDelay,
108
+ maxDelay,
109
+ typingIndicator,
110
+ dryRun,
111
+ });
112
+ if (result.alreadyPatched && !force) {
113
+ console.log(result.message);
114
+ process.exit(0);
115
+ }
116
+ if (!result.success) {
117
+ console.error('✗ ' + result.message);
118
+ process.exit(1);
119
+ }
120
+ console.log(result.message);
121
+ }
122
+ function cmdUnpatch(opts) {
123
+ const targetFile = opts['file'];
124
+ if (!targetFile) {
125
+ console.error('Usage: npx baileys-antiban unpatch --file <path-to-patched-file>');
126
+ process.exit(1);
127
+ }
128
+ const result = unpatchFile(targetFile);
129
+ if (!result.success) {
130
+ console.error('✗ ' + result.message);
131
+ process.exit(1);
132
+ }
133
+ console.log(result.message);
134
+ }
95
135
  // Main
96
136
  const opts = parseArgs(args);
97
137
  switch (command) {
@@ -110,6 +150,12 @@ switch (command) {
110
150
  process.exit(1);
111
151
  }
112
152
  break;
153
+ case 'patch':
154
+ cmdPatch(opts);
155
+ break;
156
+ case 'unpatch':
157
+ cmdUnpatch(opts);
158
+ break;
113
159
  default:
114
160
  console.log('baileys-antiban v3.0');
115
161
  console.log('');
@@ -117,9 +163,15 @@ switch (command) {
117
163
  console.log(' status [--state <path>] [--json] Show warmup and health status');
118
164
  console.log(' reset --state <path> Delete state file');
119
165
  console.log(' warmup --simulate <days> [--preset] Show warmup schedule');
166
+ console.log(' patch [--path <baileys-dir>] [--preset] [--min-delay] [--max-delay] [--no-typing] [--dry-run]');
167
+ console.log(' unpatch --file <patched-file> Restore file from backup');
120
168
  console.log('');
121
169
  console.log('Examples:');
122
170
  console.log(' npx baileys-antiban status --state ./antiban-state.json');
123
171
  console.log(' npx baileys-antiban warmup --simulate 7 --preset moderate');
124
172
  console.log(' npx baileys-antiban reset --state ./antiban-state.json');
173
+ console.log(' npx baileys-antiban patch');
174
+ console.log(' npx baileys-antiban patch --path ./node_modules/@openclaw/whatsapp/node_modules/baileys');
175
+ console.log(' npx baileys-antiban patch --preset moderate --min-delay 1000 --max-delay 3000');
176
+ console.log(' npx baileys-antiban unpatch --file ./node_modules/baileys/lib/socket/index.js');
125
177
  }
@@ -12,7 +12,7 @@
12
12
  * 4. Re-emits gap messages through user callback (wired to messages.upsert handler)
13
13
  * 5. Fires onGapTooLarge if disconnect > maxGapMs instead of partial recovery
14
14
  *
15
- * @see https://github.com/WhiskeySockets/Baileys/issues/XXX (47+ upvotes)
15
+ * @see https://github.com/WhiskeySockets/Baileys/issues/2491
16
16
  */
17
17
  export interface MessageRecoveryConfig {
18
18
  /** Max messages to track in flight (in-memory cap on lastSeen tracking) */
@@ -1,19 +1,4 @@
1
- /**
2
- * Message Recovery — Solves Baileys' silent message loss on 408 reconnect
3
- *
4
- * After a 408 disconnect (and other clean reconnect paths), offline messages
5
- * arrive on the server side but never fire `messages.upsert` events in Baileys.
6
- * Bots silently lose messages from the disconnect window.
7
- *
8
- * This module:
9
- * 1. Tracks the last message ID seen per chat while connected
10
- * 2. Detects disconnect/reconnect cycles via connection.update events
11
- * 3. On reconnect, queries Baileys' message store for messages newer than lastSeen
12
- * 4. Re-emits gap messages through user callback (wired to messages.upsert handler)
13
- * 5. Fires onGapTooLarge if disconnect > maxGapMs instead of partial recovery
14
- *
15
- * @see https://github.com/WhiskeySockets/Baileys/issues/XXX (47+ upvotes)
16
- */
1
+ import { existsSync, readFileSync } from 'node:fs';
17
2
  const DEFAULT_CONFIG = {
18
3
  maxTrackedChats: 1000,
19
4
  maxGapMs: 30 * 60_000, // 30 minutes
@@ -37,7 +22,7 @@ export function messageRecovery(sock, config) {
37
22
  // Persistence
38
23
  let persistTimer = null;
39
24
  let loggedFetchWarning = false;
40
- // Load persisted state on startup
25
+ // Load persisted state on startup (synchronous — seeds lastSeen before first event)
41
26
  if (cfg.persistPath) {
42
27
  loadPersistence();
43
28
  }
@@ -245,10 +230,9 @@ export function messageRecovery(sock, config) {
245
230
  if (!cfg.persistPath)
246
231
  return;
247
232
  try {
248
- const fs = require('fs');
249
- if (!fs.existsSync(cfg.persistPath))
233
+ if (!existsSync(cfg.persistPath))
250
234
  return;
251
- const raw = fs.readFileSync(cfg.persistPath, 'utf-8');
235
+ const raw = readFileSync(cfg.persistPath, 'utf-8');
252
236
  const data = JSON.parse(raw);
253
237
  for (const [jid, entry] of Object.entries(data)) {
254
238
  lastSeen.set(jid, {
@@ -0,0 +1,34 @@
1
+ /**
2
+ * baileys-antiban patch command
3
+ *
4
+ * Patches an installed Baileys package to wrap makeWASocket with antiban middleware.
5
+ * Designed for frameworks (OpenClaw, etc.) that create the socket internally and
6
+ * don't expose a socket:ready hook.
7
+ *
8
+ * Idempotent: safe to re-run after plugin updates. Keeps a .antiban-backup file.
9
+ *
10
+ * Usage:
11
+ * npx baileys-antiban patch [--path <baileys-dir>] [--dry-run] [--preset conservative|moderate|aggressive]
12
+ */
13
+ export interface PatchOptions {
14
+ baileysPaths?: string[];
15
+ preset?: string;
16
+ minDelay?: number;
17
+ maxDelay?: number;
18
+ typingIndicator?: boolean;
19
+ dryRun?: boolean;
20
+ }
21
+ export interface PatchResult {
22
+ success: boolean;
23
+ baileyDir: string;
24
+ targetFile: string;
25
+ alreadyPatched: boolean;
26
+ format: 'esm' | 'cjs';
27
+ backupPath?: string;
28
+ message: string;
29
+ }
30
+ export declare function applyPatch(opts: PatchOptions): PatchResult;
31
+ export declare function unpatchFile(targetFile: string): {
32
+ success: boolean;
33
+ message: string;
34
+ };
package/dist/patch.js ADDED
@@ -0,0 +1,302 @@
1
+ /**
2
+ * baileys-antiban patch command
3
+ *
4
+ * Patches an installed Baileys package to wrap makeWASocket with antiban middleware.
5
+ * Designed for frameworks (OpenClaw, etc.) that create the socket internally and
6
+ * don't expose a socket:ready hook.
7
+ *
8
+ * Idempotent: safe to re-run after plugin updates. Keeps a .antiban-backup file.
9
+ *
10
+ * Usage:
11
+ * npx baileys-antiban patch [--path <baileys-dir>] [--dry-run] [--preset conservative|moderate|aggressive]
12
+ */
13
+ import * as fs from 'fs';
14
+ import * as path from 'path';
15
+ const PATCH_MARKER_START = '// [BAILEYS-ANTIBAN-PATCH-START]';
16
+ const PATCH_MARKER_END = '// [BAILEYS-ANTIBAN-PATCH-END]';
17
+ function findBaileysDir(candidates) {
18
+ for (const candidate of candidates) {
19
+ if (!candidate)
20
+ continue;
21
+ const pkgPath = path.join(candidate, 'package.json');
22
+ if (fs.existsSync(pkgPath)) {
23
+ try {
24
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
25
+ if (pkg.name === 'baileys' || pkg.name === '@whiskeysockets/baileys' || pkg.name === '@oxidezap/baileyrs') {
26
+ return path.resolve(candidate);
27
+ }
28
+ }
29
+ catch {
30
+ // not a valid package.json, skip
31
+ }
32
+ }
33
+ }
34
+ return null;
35
+ }
36
+ function findMakeWASocketFile(baileyDir) {
37
+ // Ordered by likelihood of containing the actual makeWASocket definition
38
+ const candidates = [
39
+ 'lib/socket/index.js',
40
+ 'lib/index.js',
41
+ 'lib/socket.js',
42
+ 'src/socket/index.js',
43
+ 'dist/socket/index.js',
44
+ 'dist/index.js',
45
+ ];
46
+ for (const rel of candidates) {
47
+ const full = path.join(baileyDir, rel);
48
+ if (!fs.existsSync(full))
49
+ continue;
50
+ const source = fs.readFileSync(full, 'utf-8');
51
+ // Look for makeWASocket being defined (not just re-exported via export *)
52
+ if (source.includes('function makeWASocket') ||
53
+ source.includes('const makeWASocket') ||
54
+ source.includes('exports.makeWASocket') ||
55
+ source.includes('makeWASocket =')) {
56
+ const isEsm = source.includes('export function makeWASocket') ||
57
+ source.includes('export const makeWASocket') ||
58
+ source.includes('export {') ||
59
+ source.includes('export default');
60
+ return { file: full, format: isEsm ? 'esm' : 'cjs' };
61
+ }
62
+ }
63
+ // Fallback: search lib/ directory for the defining file
64
+ const libDir = path.join(baileyDir, 'lib');
65
+ if (fs.existsSync(libDir)) {
66
+ const jsFiles = fs
67
+ .readdirSync(libDir, { recursive: true })
68
+ .filter((f) => typeof f === 'string' && f.endsWith('.js'))
69
+ .map((f) => path.join(libDir, f));
70
+ for (const full of jsFiles) {
71
+ try {
72
+ const source = fs.readFileSync(full, 'utf-8');
73
+ if (source.includes('function makeWASocket') && source.includes('makeWASocket')) {
74
+ const isEsm = source.includes('export');
75
+ return { file: full, format: isEsm ? 'esm' : 'cjs' };
76
+ }
77
+ }
78
+ catch {
79
+ // skip unreadable files
80
+ }
81
+ }
82
+ }
83
+ return null;
84
+ }
85
+ function buildCjsPatch(opts) {
86
+ return `
87
+ ${PATCH_MARKER_START}
88
+ // Auto-generated by baileys-antiban patch. Re-run after Baileys/plugin updates.
89
+ (function() {
90
+ try {
91
+ const { wrapSocket } = require('baileys-antiban');
92
+ const _orig = module.exports.makeWASocket;
93
+ if (typeof _orig === 'function') {
94
+ module.exports.makeWASocket = function(...args) {
95
+ return wrapSocket(_orig.apply(this, args), {
96
+ preset: process.env.ANTIBAN_PRESET || '${opts.preset}',
97
+ delayBetweenMessages: {
98
+ min: parseInt(process.env.ANTIBAN_MIN_DELAY || '${opts.minDelay}'),
99
+ max: parseInt(process.env.ANTIBAN_MAX_DELAY || '${opts.maxDelay}'),
100
+ },
101
+ typingIndicator: (process.env.ANTIBAN_TYPING || '${opts.typing}') !== 'false',
102
+ });
103
+ };
104
+ }
105
+ } catch(e) {
106
+ console.warn('[baileys-antiban] patch inactive:', e.message);
107
+ }
108
+ })();
109
+ ${PATCH_MARKER_END}
110
+ `;
111
+ }
112
+ function buildEsmPatch(source, opts) {
113
+ // Strategy A: function declaration — rename and re-export wrapped version
114
+ if (source.includes('export function makeWASocket')) {
115
+ const patched = source.replace(/export function makeWASocket\b/, 'function _antiban_makeWASocket_orig');
116
+ return (patched +
117
+ `
118
+ ${PATCH_MARKER_START}
119
+ // Auto-generated by baileys-antiban patch. Re-run after Baileys/plugin updates.
120
+ import { wrapSocket as _antibanWrap } from 'baileys-antiban';
121
+ export const makeWASocket = (...args) => _antibanWrap(_antiban_makeWASocket_orig(...args), {
122
+ preset: process.env.ANTIBAN_PRESET || '${opts.preset}',
123
+ delayBetweenMessages: {
124
+ min: parseInt(process.env.ANTIBAN_MIN_DELAY || '${opts.minDelay}'),
125
+ max: parseInt(process.env.ANTIBAN_MAX_DELAY || '${opts.maxDelay}'),
126
+ },
127
+ typingIndicator: (process.env.ANTIBAN_TYPING || '${opts.typing}') !== 'false',
128
+ });
129
+ ${PATCH_MARKER_END}
130
+ `);
131
+ }
132
+ // Strategy B: const/let declaration — rename and re-export
133
+ if (source.match(/export\s+(const|let)\s+makeWASocket\s*=/)) {
134
+ const patched = source.replace(/export\s+(const|let)\s+makeWASocket\s*=/, 'const _antiban_makeWASocket_orig =');
135
+ return (patched +
136
+ `
137
+ ${PATCH_MARKER_START}
138
+ // Auto-generated by baileys-antiban patch. Re-run after Baileys/plugin updates.
139
+ import { wrapSocket as _antibanWrap } from 'baileys-antiban';
140
+ export const makeWASocket = (...args) => _antibanWrap(_antiban_makeWASocket_orig(...args), {
141
+ preset: process.env.ANTIBAN_PRESET || '${opts.preset}',
142
+ delayBetweenMessages: {
143
+ min: parseInt(process.env.ANTIBAN_MIN_DELAY || '${opts.minDelay}'),
144
+ max: parseInt(process.env.ANTIBAN_MAX_DELAY || '${opts.maxDelay}'),
145
+ },
146
+ typingIndicator: (process.env.ANTIBAN_TYPING || '${opts.typing}') !== 'false',
147
+ });
148
+ ${PATCH_MARKER_END}
149
+ `);
150
+ }
151
+ // Strategy C: named re-export — intercept at this boundary
152
+ if (source.includes('makeWASocket')) {
153
+ // Append import + re-export override at end of file
154
+ return (source +
155
+ `
156
+ ${PATCH_MARKER_START}
157
+ // Auto-generated by baileys-antiban patch. Re-run after Baileys/plugin updates.
158
+ // NOTE: Strategy C (named export intercept) — less precise than A/B.
159
+ // If this breaks, use --path to target the file where makeWASocket is defined.
160
+ import { wrapSocket as _antibanWrap } from 'baileys-antiban';
161
+ const _antiban_raw = makeWASocket;
162
+ export { makeWASocket }; // shadowed below if engine supports block-level export rebind
163
+ // Fallback: log advisory if strategy C can't reassign
164
+ if (typeof makeWASocket === 'function') {
165
+ // eslint-disable-next-line no-global-assign
166
+ try { makeWASocket = (...a) => _antibanWrap(_antiban_raw(...a), { preset: '${opts.preset}', delayBetweenMessages: { min: ${opts.minDelay}, max: ${opts.maxDelay} }, typingIndicator: ${opts.typing} }); } catch {}
167
+ }
168
+ ${PATCH_MARKER_END}
169
+ `);
170
+ }
171
+ throw new Error('Could not locate makeWASocket in this file. Try --path to a different Baileys file.');
172
+ }
173
+ export function applyPatch(opts) {
174
+ const { preset = 'conservative', minDelay = 1500, maxDelay = 4000, typingIndicator = true, dryRun = false, } = opts;
175
+ // 1. Locate Baileys
176
+ const defaultSearchPaths = [
177
+ // OC-style nested location
178
+ './node_modules/@openclaw/whatsapp/node_modules/baileys',
179
+ './node_modules/@openclaw/whatsapp/node_modules/@whiskeysockets/baileys',
180
+ // Standard locations
181
+ './node_modules/baileys',
182
+ './node_modules/@whiskeysockets/baileys',
183
+ // Walk up
184
+ '../node_modules/baileys',
185
+ '../../node_modules/baileys',
186
+ ];
187
+ const searchPaths = opts.baileysPaths?.length ? opts.baileysPaths : defaultSearchPaths;
188
+ const baileyDir = findBaileysDir(searchPaths);
189
+ if (!baileyDir) {
190
+ return {
191
+ success: false,
192
+ baileyDir: '(not found)',
193
+ targetFile: '(not found)',
194
+ alreadyPatched: false,
195
+ format: 'cjs',
196
+ message: 'Could not find Baileys package. Use --path <dir> to specify its location.\n' +
197
+ 'Example: npx baileys-antiban patch --path ./node_modules/@openclaw/whatsapp/node_modules/baileys',
198
+ };
199
+ }
200
+ // 2. Find file containing makeWASocket
201
+ const found = findMakeWASocketFile(baileyDir);
202
+ if (!found) {
203
+ return {
204
+ success: false,
205
+ baileyDir,
206
+ targetFile: '(not found)',
207
+ alreadyPatched: false,
208
+ format: 'cjs',
209
+ message: `Found Baileys at ${baileyDir} but could not locate makeWASocket definition.\n` +
210
+ 'The package structure may have changed. Please open an issue.',
211
+ };
212
+ }
213
+ const { file: targetFile, format } = found;
214
+ const source = fs.readFileSync(targetFile, 'utf-8');
215
+ // 3. Check idempotency
216
+ if (source.includes(PATCH_MARKER_START)) {
217
+ return {
218
+ success: true,
219
+ baileyDir,
220
+ targetFile,
221
+ alreadyPatched: true,
222
+ format,
223
+ message: `Already patched: ${targetFile}\nRe-run with --force to re-apply.`,
224
+ };
225
+ }
226
+ if (dryRun) {
227
+ return {
228
+ success: true,
229
+ baileyDir,
230
+ targetFile,
231
+ alreadyPatched: false,
232
+ format,
233
+ message: `[dry-run] Would patch ${targetFile} (format: ${format})`,
234
+ };
235
+ }
236
+ // 4. Backup
237
+ const backupPath = targetFile + '.antiban-backup';
238
+ if (!fs.existsSync(backupPath)) {
239
+ fs.copyFileSync(targetFile, backupPath);
240
+ }
241
+ // 5. Build and apply patch
242
+ const patchOpts = { preset, minDelay, maxDelay, typing: typingIndicator };
243
+ let patched;
244
+ try {
245
+ if (format === 'cjs') {
246
+ patched = source + buildCjsPatch(patchOpts);
247
+ }
248
+ else {
249
+ patched = buildEsmPatch(source, patchOpts);
250
+ }
251
+ }
252
+ catch (e) {
253
+ return {
254
+ success: false,
255
+ baileyDir,
256
+ targetFile,
257
+ alreadyPatched: false,
258
+ format,
259
+ message: `Patch generation failed: ${e.message}`,
260
+ };
261
+ }
262
+ fs.writeFileSync(targetFile, patched, 'utf-8');
263
+ return {
264
+ success: true,
265
+ baileyDir,
266
+ targetFile,
267
+ alreadyPatched: false,
268
+ format,
269
+ backupPath,
270
+ message: `✅ Patched: ${targetFile}\n` +
271
+ ` Format: ${format}\n` +
272
+ ` Backup: ${backupPath}\n` +
273
+ ` Preset: ${preset} (${minDelay}–${maxDelay}ms delays, typing=${typingIndicator})\n` +
274
+ `\nTo auto-re-patch after plugin updates, add to your package.json:\n` +
275
+ ` "scripts": { "postinstall": "npx baileys-antiban patch" }\n\n` +
276
+ `Env overrides: ANTIBAN_PRESET, ANTIBAN_MIN_DELAY, ANTIBAN_MAX_DELAY, ANTIBAN_TYPING`,
277
+ };
278
+ }
279
+ export function unpatchFile(targetFile) {
280
+ if (!fs.existsSync(targetFile)) {
281
+ return { success: false, message: `File not found: ${targetFile}` };
282
+ }
283
+ const backupPath = targetFile + '.antiban-backup';
284
+ if (!fs.existsSync(backupPath)) {
285
+ // Try removing the patch block from source
286
+ const source = fs.readFileSync(targetFile, 'utf-8');
287
+ if (!source.includes(PATCH_MARKER_START)) {
288
+ return { success: true, message: 'File is not patched.' };
289
+ }
290
+ const startIdx = source.indexOf(PATCH_MARKER_START);
291
+ const endIdx = source.indexOf(PATCH_MARKER_END);
292
+ if (startIdx === -1 || endIdx === -1) {
293
+ return { success: false, message: 'Patch markers found but malformed. Restore manually from backup.' };
294
+ }
295
+ const cleaned = source.slice(0, startIdx) + source.slice(endIdx + PATCH_MARKER_END.length);
296
+ fs.writeFileSync(targetFile, cleaned, 'utf-8');
297
+ return { success: true, message: `Patch removed from ${targetFile}` };
298
+ }
299
+ fs.copyFileSync(backupPath, targetFile);
300
+ fs.unlinkSync(backupPath);
301
+ return { success: true, message: `Restored from backup: ${backupPath}` };
302
+ }
package/dist/presets.d.ts CHANGED
@@ -16,7 +16,7 @@ export interface ResolvedConfig {
16
16
  persist?: string;
17
17
  logging: boolean;
18
18
  }
19
- export type PresetName = 'conservative' | 'moderate' | 'aggressive';
19
+ export type PresetName = 'conservative' | 'moderate' | 'aggressive' | 'high-volume';
20
20
  export type AntiBanInput = PresetName | Partial<ResolvedConfig & {
21
21
  preset?: PresetName;
22
22
  }> | undefined;
package/dist/presets.js CHANGED
@@ -47,6 +47,24 @@ export const PRESETS = {
47
47
  groupProfiles: false,
48
48
  logging: true,
49
49
  },
50
+ // For established, fully-warmed accounts running enterprise-scale operations.
51
+ // Only use on accounts with 6+ months history and no prior bans.
52
+ 'high-volume': {
53
+ maxPerMinute: 40,
54
+ maxPerHour: 1500,
55
+ maxPerDay: 8000,
56
+ minDelayMs: 400,
57
+ maxDelayMs: 1800,
58
+ newChatDelayMs: 1200,
59
+ warmupDays: 3,
60
+ day1Limit: 60,
61
+ growthFactor: 2.5,
62
+ inactivityThresholdHours: 24,
63
+ autoPauseAt: 'critical',
64
+ groupMultiplier: 0.95,
65
+ groupProfiles: false,
66
+ logging: true,
67
+ },
50
68
  };
51
69
  export function resolveConfig(input) {
52
70
  if (input === undefined) {
@@ -54,14 +72,14 @@ export function resolveConfig(input) {
54
72
  }
55
73
  if (typeof input === 'string') {
56
74
  if (!(input in PRESETS)) {
57
- throw new Error(`Unknown preset "${input}". Valid: ${Object.keys(PRESETS).join(', ')}`);
75
+ throw new Error(`Unknown preset "${input}". Valid: conservative, moderate, aggressive, high-volume`);
58
76
  }
59
77
  return { ...PRESETS[input] };
60
78
  }
61
79
  // Object form — extract preset base, merge overrides
62
80
  const { preset = 'conservative', ...overrides } = input;
63
81
  if (!(preset in PRESETS)) {
64
- throw new Error(`Unknown preset "${preset}". Valid: ${Object.keys(PRESETS).join(', ')}`);
82
+ throw new Error(`Unknown preset "${preset}". Valid: conservative, moderate, aggressive, high-volume`);
65
83
  }
66
84
  return { ...PRESETS[preset], ...overrides };
67
85
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "baileys-antiban",
3
- "version": "3.8.6",
3
+ "version": "3.8.8",
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/cjs/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -89,11 +89,9 @@
89
89
  }
90
90
  },
91
91
  "devDependencies": {
92
- "@types/jest": "^29.5.14",
93
92
  "@types/node": "^20.0.0",
93
+ "@whiskeysockets/baileys": "^7.0.0-rc11",
94
94
  "baileys": "github:WhiskeySockets/Baileys#dfad98f815feb771cc561f32707a00c6e085b1f1",
95
- "jest": "^29.7.0",
96
- "ts-jest": "^29.4.9",
97
95
  "tsx": "^4.21.0",
98
96
  "typescript": "^5.0.0",
99
97
  "vitest": "^4.1.5"