baileys-antiban 3.8.10 → 3.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +15 -0
- package/dist/antiban.js +15 -1
- package/dist/cjs/antiban.js +15 -1
- package/dist/cjs/persist.js +10 -2
- package/dist/cjs/proxyRotator.js +9 -10
- package/dist/cjs/rateLimiter.js +9 -0
- package/dist/cjs/wrapper.js +13 -1
- package/dist/persist.js +10 -2
- package/dist/presets.d.ts +6 -1
- package/dist/proxyRotator.js +9 -10
- package/dist/rateLimiter.js +9 -0
- package/dist/wrapper.js +13 -1
- package/package.json +4 -4
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,21 @@ 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.11] - 2026-05-19
|
|
9
|
+
|
|
10
|
+
### Security
|
|
11
|
+
- **persist.ts**: Resolve state file path to absolute (`path.resolve()`), reject null bytes. Add strict JSON shape validation (version, savedAt, knownChats types) before trusting loaded state — prevents type confusion from corrupt or tampered files.
|
|
12
|
+
- **proxyRotator.ts**: Replace `(0, eval)('require')` and `(0, eval)('import.meta.url')` with `new Function()` on static literal strings in the ESM code path. Not user-controlled, but removes the indirect eval chain for static analysis and CSP compliance.
|
|
13
|
+
|
|
14
|
+
### Fixed
|
|
15
|
+
- **rateLimiter.ts**: Added LRU size cap (10,000 entries) to `identicalCount` Map. Time-window eviction alone allowed unbounded growth when sending many unique messages; oldest-by-lastSeen entries are now evicted when the cap is exceeded.
|
|
16
|
+
- **antiban.ts**: Extend `mapLegacyToFlat()` to preserve `autoPauseAt`, `groupMultiplier`, `groupProfiles`, `persist` from flat top-level fields when legacy config detection fires — completing the coverage from 3.8.9.
|
|
17
|
+
|
|
18
|
+
### Changed (3.8.10)
|
|
19
|
+
- README: Expanded v3 flat config example with all `ResolvedConfig` fields; added correct `deafSession` wrapOptions (4th arg) example; marked nested Configuration section as deprecated.
|
|
20
|
+
- Tests: 4 new v3 test cases covering `maxIdenticalMessages`/`burstAllowance` forwarding, mixed legacy+flat preservation, `getConfig()`.
|
|
21
|
+
- CHANGELOG: Added missing 3.8.9 entry.
|
|
22
|
+
|
|
8
23
|
## [3.8.9] - 2026-05-19
|
|
9
24
|
|
|
10
25
|
### Fixed
|
package/dist/antiban.js
CHANGED
|
@@ -79,6 +79,15 @@ function mapLegacyToFlat(legacy) {
|
|
|
79
79
|
flat.identicalMessageWindowMs = legacyAsFlat.identicalMessageWindowMs;
|
|
80
80
|
if (flat.burstAllowance === undefined && typeof legacyAsFlat.burstAllowance === 'number')
|
|
81
81
|
flat.burstAllowance = legacyAsFlat.burstAllowance;
|
|
82
|
+
// v3 fields that may coexist with legacy nested keys
|
|
83
|
+
if (flat.autoPauseAt === undefined && typeof legacyAsFlat.autoPauseAt === 'string')
|
|
84
|
+
flat.autoPauseAt = legacyAsFlat.autoPauseAt;
|
|
85
|
+
if (flat.groupMultiplier === undefined && typeof legacyAsFlat.groupMultiplier === 'number')
|
|
86
|
+
flat.groupMultiplier = legacyAsFlat.groupMultiplier;
|
|
87
|
+
if (flat.groupProfiles === undefined && typeof legacyAsFlat.groupProfiles === 'boolean')
|
|
88
|
+
flat.groupProfiles = legacyAsFlat.groupProfiles;
|
|
89
|
+
if (flat.persist === undefined && typeof legacyAsFlat.persist === 'string')
|
|
90
|
+
flat.persist = legacyAsFlat.persist;
|
|
82
91
|
return flat;
|
|
83
92
|
}
|
|
84
93
|
export class AntiBan {
|
|
@@ -161,7 +170,10 @@ export class AntiBan {
|
|
|
161
170
|
console.log(`[baileys-antiban] ${status.recommendation}`);
|
|
162
171
|
status.reasons.forEach(r => console.log(`[baileys-antiban] → ${r}`));
|
|
163
172
|
}
|
|
164
|
-
|
|
173
|
+
if ((status.risk === 'high' || status.risk === 'critical') && cfg.onAtRisk) {
|
|
174
|
+
cfg.onAtRisk(status);
|
|
175
|
+
}
|
|
176
|
+
cfg.onRiskChange?.(status);
|
|
165
177
|
legacyPassthrough?.health?.onRiskChange?.(status);
|
|
166
178
|
},
|
|
167
179
|
});
|
|
@@ -172,12 +184,14 @@ export class AntiBan {
|
|
|
172
184
|
if (this.logging) {
|
|
173
185
|
console.log(`[baileys-antiban] REACHOUT TIMELOCKED — ${state.enforcementType || 'unknown'}, expires ${state.expiresAt?.toISOString() || 'unknown'}`);
|
|
174
186
|
}
|
|
187
|
+
cfg.onTimelockDetected?.(state);
|
|
175
188
|
legacyPassthrough?.timelock?.onTimelockDetected?.(state);
|
|
176
189
|
},
|
|
177
190
|
onTimelockLifted: (state) => {
|
|
178
191
|
if (this.logging) {
|
|
179
192
|
console.log(`[baileys-antiban] Timelock lifted — resuming new contact messages`);
|
|
180
193
|
}
|
|
194
|
+
cfg.onTimelockLifted?.(state);
|
|
181
195
|
legacyPassthrough?.timelock?.onTimelockLifted?.(state);
|
|
182
196
|
},
|
|
183
197
|
});
|
package/dist/cjs/antiban.js
CHANGED
|
@@ -82,6 +82,15 @@ function mapLegacyToFlat(legacy) {
|
|
|
82
82
|
flat.identicalMessageWindowMs = legacyAsFlat.identicalMessageWindowMs;
|
|
83
83
|
if (flat.burstAllowance === undefined && typeof legacyAsFlat.burstAllowance === 'number')
|
|
84
84
|
flat.burstAllowance = legacyAsFlat.burstAllowance;
|
|
85
|
+
// v3 fields that may coexist with legacy nested keys
|
|
86
|
+
if (flat.autoPauseAt === undefined && typeof legacyAsFlat.autoPauseAt === 'string')
|
|
87
|
+
flat.autoPauseAt = legacyAsFlat.autoPauseAt;
|
|
88
|
+
if (flat.groupMultiplier === undefined && typeof legacyAsFlat.groupMultiplier === 'number')
|
|
89
|
+
flat.groupMultiplier = legacyAsFlat.groupMultiplier;
|
|
90
|
+
if (flat.groupProfiles === undefined && typeof legacyAsFlat.groupProfiles === 'boolean')
|
|
91
|
+
flat.groupProfiles = legacyAsFlat.groupProfiles;
|
|
92
|
+
if (flat.persist === undefined && typeof legacyAsFlat.persist === 'string')
|
|
93
|
+
flat.persist = legacyAsFlat.persist;
|
|
85
94
|
return flat;
|
|
86
95
|
}
|
|
87
96
|
class AntiBan {
|
|
@@ -164,7 +173,10 @@ class AntiBan {
|
|
|
164
173
|
console.log(`[baileys-antiban] ${status.recommendation}`);
|
|
165
174
|
status.reasons.forEach(r => console.log(`[baileys-antiban] → ${r}`));
|
|
166
175
|
}
|
|
167
|
-
|
|
176
|
+
if ((status.risk === 'high' || status.risk === 'critical') && cfg.onAtRisk) {
|
|
177
|
+
cfg.onAtRisk(status);
|
|
178
|
+
}
|
|
179
|
+
cfg.onRiskChange?.(status);
|
|
168
180
|
legacyPassthrough?.health?.onRiskChange?.(status);
|
|
169
181
|
},
|
|
170
182
|
});
|
|
@@ -175,12 +187,14 @@ class AntiBan {
|
|
|
175
187
|
if (this.logging) {
|
|
176
188
|
console.log(`[baileys-antiban] REACHOUT TIMELOCKED — ${state.enforcementType || 'unknown'}, expires ${state.expiresAt?.toISOString() || 'unknown'}`);
|
|
177
189
|
}
|
|
190
|
+
cfg.onTimelockDetected?.(state);
|
|
178
191
|
legacyPassthrough?.timelock?.onTimelockDetected?.(state);
|
|
179
192
|
},
|
|
180
193
|
onTimelockLifted: (state) => {
|
|
181
194
|
if (this.logging) {
|
|
182
195
|
console.log(`[baileys-antiban] Timelock lifted — resuming new contact messages`);
|
|
183
196
|
}
|
|
197
|
+
cfg.onTimelockLifted?.(state);
|
|
184
198
|
legacyPassthrough?.timelock?.onTimelockLifted?.(state);
|
|
185
199
|
},
|
|
186
200
|
});
|
package/dist/cjs/persist.js
CHANGED
|
@@ -35,6 +35,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.StateManager = void 0;
|
|
37
37
|
const fs = __importStar(require("fs"));
|
|
38
|
+
const path = __importStar(require("path"));
|
|
38
39
|
const KNOWN_CHATS_MAX = 1000;
|
|
39
40
|
const DEBOUNCE_MS = 5000;
|
|
40
41
|
/**
|
|
@@ -48,13 +49,20 @@ class StateManager {
|
|
|
48
49
|
path;
|
|
49
50
|
debounceTimer = null;
|
|
50
51
|
constructor(filePath) {
|
|
51
|
-
|
|
52
|
+
// Resolve to absolute path and reject null bytes to prevent path injection
|
|
53
|
+
if (filePath.includes('\0'))
|
|
54
|
+
throw new Error('[baileys-antiban] Invalid state file path: null byte');
|
|
55
|
+
this.path = path.resolve(filePath);
|
|
52
56
|
}
|
|
53
57
|
load() {
|
|
54
58
|
try {
|
|
55
59
|
const raw = fs.readFileSync(this.path, 'utf-8');
|
|
56
60
|
const parsed = JSON.parse(raw);
|
|
57
|
-
|
|
61
|
+
// Strict shape validation before trusting file content
|
|
62
|
+
if (typeof parsed !== 'object' || parsed === null ||
|
|
63
|
+
parsed.version !== 3 ||
|
|
64
|
+
typeof parsed.savedAt !== 'number' ||
|
|
65
|
+
!Array.isArray(parsed.knownChats)) {
|
|
58
66
|
console.warn('[baileys-antiban] WARN: corrupt state file or version mismatch, starting fresh');
|
|
59
67
|
return null;
|
|
60
68
|
}
|
package/dist/cjs/proxyRotator.js
CHANGED
|
@@ -19,20 +19,19 @@
|
|
|
19
19
|
*/
|
|
20
20
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
21
21
|
exports.proxyRotator = proxyRotator;
|
|
22
|
-
//
|
|
23
|
-
//
|
|
22
|
+
// Load optional peer dependencies synchronously in both ESM and CJS builds.
|
|
23
|
+
// CJS: native require is available. ESM: new Function() reads import.meta.url
|
|
24
|
+
// without causing TypeScript parse errors in CJS compilation. The string passed
|
|
25
|
+
// to new Function is a static literal — not user-controlled.
|
|
24
26
|
function lazyRequire(moduleName) {
|
|
25
|
-
// In CJS: use native require
|
|
26
27
|
if (typeof require !== 'undefined') {
|
|
27
28
|
return require(moduleName);
|
|
28
29
|
}
|
|
29
|
-
//
|
|
30
|
-
//
|
|
31
|
-
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
const dynamicRequire = createRequire((0, eval)('import.meta.url'));
|
|
35
|
-
return dynamicRequire(moduleName);
|
|
30
|
+
// ESM path: new Function with static literal string avoids TS CJS-parse error on import.meta.
|
|
31
|
+
// Not user-controlled — both strings are compile-time constants.
|
|
32
|
+
const { createRequire } = (new Function('return require')())('node:module');
|
|
33
|
+
const metaUrl = new Function('return import.meta.url')();
|
|
34
|
+
return createRequire(metaUrl)(moduleName);
|
|
36
35
|
}
|
|
37
36
|
const NoopLogger = {
|
|
38
37
|
info: () => { },
|
package/dist/cjs/rateLimiter.js
CHANGED
|
@@ -177,6 +177,15 @@ class RateLimiter {
|
|
|
177
177
|
this.identicalCount.delete(hash);
|
|
178
178
|
}
|
|
179
179
|
}
|
|
180
|
+
// LRU size cap — prevents unbounded growth when sending many unique messages.
|
|
181
|
+
// Evict oldest-by-lastSeen entries when map exceeds 10,000 entries.
|
|
182
|
+
const IDENTICAL_COUNT_MAX = 10_000;
|
|
183
|
+
if (this.identicalCount.size > IDENTICAL_COUNT_MAX) {
|
|
184
|
+
const sorted = [...this.identicalCount.entries()].sort(([, a], [, b]) => a.lastSeen - b.lastSeen);
|
|
185
|
+
const excess = this.identicalCount.size - IDENTICAL_COUNT_MAX;
|
|
186
|
+
for (let i = 0; i < excess; i++)
|
|
187
|
+
this.identicalCount.delete(sorted[i][0]);
|
|
188
|
+
}
|
|
180
189
|
}
|
|
181
190
|
/** Random delay between min and max (gaussian-ish distribution) */
|
|
182
191
|
jitter(min, max) {
|
package/dist/cjs/wrapper.js
CHANGED
|
@@ -260,7 +260,19 @@ function wrapSocket(sock, config, warmUpState, wrapOptions) {
|
|
|
260
260
|
return result;
|
|
261
261
|
}
|
|
262
262
|
catch (error) {
|
|
263
|
-
|
|
263
|
+
// Baileys PR #2587: partial-encrypt Boom now carries structured data:
|
|
264
|
+
// error.data.failed[] — per-recipient { jid, error } failures
|
|
265
|
+
// error.data.firstCause — most likely root cause string
|
|
266
|
+
// Extract for richer health-monitor diagnostics vs plain error.message.
|
|
267
|
+
const boomData = error?.data;
|
|
268
|
+
if (boomData?.failed?.length) {
|
|
269
|
+
const cause = boomData.firstCause ?? 'unknown';
|
|
270
|
+
const failedJids = boomData.failed.map((f) => f.jid).join(', ');
|
|
271
|
+
antiban.afterSendFailed(`encrypt-all-failed firstCause=${cause} jids=[${failedJids}]`);
|
|
272
|
+
}
|
|
273
|
+
else {
|
|
274
|
+
antiban.afterSendFailed(error instanceof Error ? error.message : String(error));
|
|
275
|
+
}
|
|
264
276
|
throw error;
|
|
265
277
|
}
|
|
266
278
|
});
|
package/dist/persist.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as fs from 'fs';
|
|
2
|
+
import * as path from 'path';
|
|
2
3
|
const KNOWN_CHATS_MAX = 1000;
|
|
3
4
|
const DEBOUNCE_MS = 5000;
|
|
4
5
|
/**
|
|
@@ -12,13 +13,20 @@ export class StateManager {
|
|
|
12
13
|
path;
|
|
13
14
|
debounceTimer = null;
|
|
14
15
|
constructor(filePath) {
|
|
15
|
-
|
|
16
|
+
// Resolve to absolute path and reject null bytes to prevent path injection
|
|
17
|
+
if (filePath.includes('\0'))
|
|
18
|
+
throw new Error('[baileys-antiban] Invalid state file path: null byte');
|
|
19
|
+
this.path = path.resolve(filePath);
|
|
16
20
|
}
|
|
17
21
|
load() {
|
|
18
22
|
try {
|
|
19
23
|
const raw = fs.readFileSync(this.path, 'utf-8');
|
|
20
24
|
const parsed = JSON.parse(raw);
|
|
21
|
-
|
|
25
|
+
// Strict shape validation before trusting file content
|
|
26
|
+
if (typeof parsed !== 'object' || parsed === null ||
|
|
27
|
+
parsed.version !== 3 ||
|
|
28
|
+
typeof parsed.savedAt !== 'number' ||
|
|
29
|
+
!Array.isArray(parsed.knownChats)) {
|
|
22
30
|
console.warn('[baileys-antiban] WARN: corrupt state file or version mismatch, starting fresh');
|
|
23
31
|
return null;
|
|
24
32
|
}
|
package/dist/presets.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type { BanRiskLevel } from './health.js';
|
|
1
|
+
import type { BanRiskLevel, HealthStatus } from './health.js';
|
|
2
|
+
import type { TimelockState } from './timelockGuard.js';
|
|
2
3
|
export interface ResolvedConfig {
|
|
3
4
|
maxPerMinute: number;
|
|
4
5
|
maxPerHour: number;
|
|
@@ -18,6 +19,10 @@ export interface ResolvedConfig {
|
|
|
18
19
|
groupProfiles: boolean;
|
|
19
20
|
persist?: string;
|
|
20
21
|
logging: boolean;
|
|
22
|
+
onAtRisk?: (status: HealthStatus) => void;
|
|
23
|
+
onRiskChange?: (status: HealthStatus) => void;
|
|
24
|
+
onTimelockDetected?: (state: TimelockState) => void;
|
|
25
|
+
onTimelockLifted?: (state: TimelockState) => void;
|
|
21
26
|
}
|
|
22
27
|
export type PresetName = 'conservative' | 'moderate' | 'aggressive' | 'high-volume';
|
|
23
28
|
export type AntiBanInput = PresetName | Partial<ResolvedConfig & {
|
package/dist/proxyRotator.js
CHANGED
|
@@ -16,20 +16,19 @@
|
|
|
16
16
|
* @author Kobus Wentzel <kobie@pop.co.za>
|
|
17
17
|
* @license MIT
|
|
18
18
|
*/
|
|
19
|
-
//
|
|
20
|
-
//
|
|
19
|
+
// Load optional peer dependencies synchronously in both ESM and CJS builds.
|
|
20
|
+
// CJS: native require is available. ESM: new Function() reads import.meta.url
|
|
21
|
+
// without causing TypeScript parse errors in CJS compilation. The string passed
|
|
22
|
+
// to new Function is a static literal — not user-controlled.
|
|
21
23
|
function lazyRequire(moduleName) {
|
|
22
|
-
// In CJS: use native require
|
|
23
24
|
if (typeof require !== 'undefined') {
|
|
24
25
|
return require(moduleName);
|
|
25
26
|
}
|
|
26
|
-
//
|
|
27
|
-
//
|
|
28
|
-
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
const dynamicRequire = createRequire((0, eval)('import.meta.url'));
|
|
32
|
-
return dynamicRequire(moduleName);
|
|
27
|
+
// ESM path: new Function with static literal string avoids TS CJS-parse error on import.meta.
|
|
28
|
+
// Not user-controlled — both strings are compile-time constants.
|
|
29
|
+
const { createRequire } = (new Function('return require')())('node:module');
|
|
30
|
+
const metaUrl = new Function('return import.meta.url')();
|
|
31
|
+
return createRequire(metaUrl)(moduleName);
|
|
33
32
|
}
|
|
34
33
|
const NoopLogger = {
|
|
35
34
|
info: () => { },
|
package/dist/rateLimiter.js
CHANGED
|
@@ -174,6 +174,15 @@ export class RateLimiter {
|
|
|
174
174
|
this.identicalCount.delete(hash);
|
|
175
175
|
}
|
|
176
176
|
}
|
|
177
|
+
// LRU size cap — prevents unbounded growth when sending many unique messages.
|
|
178
|
+
// Evict oldest-by-lastSeen entries when map exceeds 10,000 entries.
|
|
179
|
+
const IDENTICAL_COUNT_MAX = 10_000;
|
|
180
|
+
if (this.identicalCount.size > IDENTICAL_COUNT_MAX) {
|
|
181
|
+
const sorted = [...this.identicalCount.entries()].sort(([, a], [, b]) => a.lastSeen - b.lastSeen);
|
|
182
|
+
const excess = this.identicalCount.size - IDENTICAL_COUNT_MAX;
|
|
183
|
+
for (let i = 0; i < excess; i++)
|
|
184
|
+
this.identicalCount.delete(sorted[i][0]);
|
|
185
|
+
}
|
|
177
186
|
}
|
|
178
187
|
/** Random delay between min and max (gaussian-ish distribution) */
|
|
179
188
|
jitter(min, max) {
|
package/dist/wrapper.js
CHANGED
|
@@ -257,7 +257,19 @@ export function wrapSocket(sock, config, warmUpState, wrapOptions) {
|
|
|
257
257
|
return result;
|
|
258
258
|
}
|
|
259
259
|
catch (error) {
|
|
260
|
-
|
|
260
|
+
// Baileys PR #2587: partial-encrypt Boom now carries structured data:
|
|
261
|
+
// error.data.failed[] — per-recipient { jid, error } failures
|
|
262
|
+
// error.data.firstCause — most likely root cause string
|
|
263
|
+
// Extract for richer health-monitor diagnostics vs plain error.message.
|
|
264
|
+
const boomData = error?.data;
|
|
265
|
+
if (boomData?.failed?.length) {
|
|
266
|
+
const cause = boomData.firstCause ?? 'unknown';
|
|
267
|
+
const failedJids = boomData.failed.map((f) => f.jid).join(', ');
|
|
268
|
+
antiban.afterSendFailed(`encrypt-all-failed firstCause=${cause} jids=[${failedJids}]`);
|
|
269
|
+
}
|
|
270
|
+
else {
|
|
271
|
+
antiban.afterSendFailed(error instanceof Error ? error.message : String(error));
|
|
272
|
+
}
|
|
261
273
|
throw error;
|
|
262
274
|
}
|
|
263
275
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "baileys-antiban",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.9.0",
|
|
4
4
|
"description": "Anti-ban middleware for Baileys WhatsApp bots. Rate limiting, warmup, health monitor, LID resolver, disconnect classifier. Free Whapi.Cloud alternative.",
|
|
5
5
|
"main": "dist/cjs/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
"prepublishOnly": "npm run build"
|
|
30
30
|
},
|
|
31
31
|
"bin": {
|
|
32
|
-
"baileys-antiban": "
|
|
32
|
+
"baileys-antiban": "dist/cli.js"
|
|
33
33
|
},
|
|
34
34
|
"keywords": [
|
|
35
35
|
"baileys",
|
|
@@ -61,7 +61,7 @@
|
|
|
61
61
|
"license": "MIT",
|
|
62
62
|
"repository": {
|
|
63
63
|
"type": "git",
|
|
64
|
-
"url": "https://github.com/kobie3717/baileys-antiban"
|
|
64
|
+
"url": "git+https://github.com/kobie3717/baileys-antiban.git"
|
|
65
65
|
},
|
|
66
66
|
"bugs": {
|
|
67
67
|
"url": "https://github.com/kobie3717/baileys-antiban/issues"
|
|
@@ -90,7 +90,7 @@
|
|
|
90
90
|
},
|
|
91
91
|
"devDependencies": {
|
|
92
92
|
"@types/node": "^20.0.0",
|
|
93
|
-
"@whiskeysockets/baileys": "
|
|
93
|
+
"@whiskeysockets/baileys": "7.0.0-rc12",
|
|
94
94
|
"baileys": "github:WhiskeySockets/Baileys#dfad98f815feb771cc561f32707a00c6e085b1f1",
|
|
95
95
|
"tsx": "^4.21.0",
|
|
96
96
|
"typescript": "^5.0.0",
|