baileys-antiban 3.8.6 → 3.8.7
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 +25 -0
- package/README.md +78 -0
- package/dist/cjs/cli.js +52 -0
- package/dist/cjs/messageRecovery.js +4 -20
- package/dist/cjs/patch.js +339 -0
- package/dist/cli.js +52 -0
- package/dist/messageRecovery.d.ts +1 -1
- package/dist/messageRecovery.js +4 -20
- package/dist/patch.d.ts +34 -0
- package/dist/patch.js +302 -0
- package/package.json +1 -4
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,31 @@ 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.7] - 2026-05-14
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- **`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.
|
|
12
|
+
- Auto-discovers Baileys in `node_modules/@openclaw/whatsapp/node_modules/baileys`, `node_modules/baileys`, `node_modules/@whiskeysockets/baileys`, and parent directories.
|
|
13
|
+
- `--path <dir>` — explicit Baileys directory override.
|
|
14
|
+
- `--preset conservative|moderate|aggressive` — antiban profile (default: `conservative`).
|
|
15
|
+
- `--min-delay` / `--max-delay` — override delay range in ms (default 1500–4000).
|
|
16
|
+
- `--no-typing` — disable typing indicators.
|
|
17
|
+
- `--dry-run` — preview without writing files.
|
|
18
|
+
- `--force` — re-patch even if already patched.
|
|
19
|
+
- Runtime config via env: `ANTIBAN_PRESET`, `ANTIBAN_MIN_DELAY`, `ANTIBAN_MAX_DELAY`, `ANTIBAN_TYPING`.
|
|
20
|
+
- **`baileys-antiban unpatch` CLI command** — restores a patched file from its `.antiban-backup` or strips the patch block in-place if backup is missing.
|
|
21
|
+
|
|
22
|
+
## [3.8.6] - 2026-05-14
|
|
23
|
+
|
|
24
|
+
### Fixed
|
|
25
|
+
- **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`.
|
|
26
|
+
- **`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.
|
|
27
|
+
- **`messageRecovery` doc comment.** Placeholder `issues/XXX` issue reference updated to `issues/2491` (deaf-session / silent message loss tracker).
|
|
28
|
+
|
|
29
|
+
### Changed
|
|
30
|
+
- Removed unused `jest`, `@types/jest`, and `ts-jest` devDependencies (test runner uses `tsx` directly; `vitest` retained as future framework).
|
|
31
|
+
- **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.
|
|
32
|
+
|
|
8
33
|
## [3.8.5] - 2026-05-09
|
|
9
34
|
|
|
10
35
|
### Added
|
package/README.md
CHANGED
|
@@ -1045,6 +1045,84 @@ 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
|
+
|
|
1048
1126
|
### State not persisting across restarts
|
|
1049
1127
|
|
|
1050
1128
|
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
|
-
|
|
285
|
-
if (!fs.existsSync(cfg.persistPath))
|
|
269
|
+
if (!(0, node_fs_1.existsSync)(cfg.persistPath))
|
|
286
270
|
return;
|
|
287
|
-
const raw =
|
|
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
|
+
}
|
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/
|
|
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) */
|
package/dist/messageRecovery.js
CHANGED
|
@@ -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
|
-
|
|
249
|
-
if (!fs.existsSync(cfg.persistPath))
|
|
233
|
+
if (!existsSync(cfg.persistPath))
|
|
250
234
|
return;
|
|
251
|
-
const raw =
|
|
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, {
|
package/dist/patch.d.ts
ADDED
|
@@ -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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "baileys-antiban",
|
|
3
|
-
"version": "3.8.
|
|
3
|
+
"version": "3.8.7",
|
|
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,8 @@
|
|
|
89
89
|
}
|
|
90
90
|
},
|
|
91
91
|
"devDependencies": {
|
|
92
|
-
"@types/jest": "^29.5.14",
|
|
93
92
|
"@types/node": "^20.0.0",
|
|
94
93
|
"baileys": "github:WhiskeySockets/Baileys#dfad98f815feb771cc561f32707a00c6e085b1f1",
|
|
95
|
-
"jest": "^29.7.0",
|
|
96
|
-
"ts-jest": "^29.4.9",
|
|
97
94
|
"tsx": "^4.21.0",
|
|
98
95
|
"typescript": "^5.0.0",
|
|
99
96
|
"vitest": "^4.1.5"
|