baileys-antiban 2.0.0 → 2.1.1
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 +41 -0
- package/README.md +23 -2
- package/dist/index.d.ts +2 -0
- package/dist/index.js +3 -0
- package/dist/lidFirstResolver.d.ts +71 -0
- package/dist/lidFirstResolver.js +174 -0
- package/dist/retryReason.d.ts +49 -0
- package/dist/retryReason.js +91 -0
- package/dist/sessionStability.js +22 -3
- package/package.json +16 -3
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,47 @@ 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
|
+
## [2.1.0] - 2026-04-19
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- **Extended disconnect code coverage** — Added 405, 409, 412 to `classifyDisconnect()`
|
|
12
|
+
- **405** (Method Not Allowed) → `fatal`, no reconnect
|
|
13
|
+
- **409** (Conflict / Connection Replaced) → `fatal`, no reconnect (merged with 428 behavior)
|
|
14
|
+
- **412** (Precondition Failed) → `recoverable`, 30s backoff (auth state mismatch, retry after delay)
|
|
15
|
+
- **LidFirstResolver** — Standalone drop-in utility for LID↔phone mapping
|
|
16
|
+
- Loads mappings from Baileys auth state directory (`lid-mapping-*_reverse.json`)
|
|
17
|
+
- `resolveToLID(phoneOrJid)` — phone → LID lookup
|
|
18
|
+
- `resolveToPhone(lid)` — LID → phone lookup
|
|
19
|
+
- `loadFromAuthDir(dir)` — bulk load from auth state
|
|
20
|
+
- `learnFromEvent(event)` — learn from Baileys events (future-proof)
|
|
21
|
+
- `getMapping(jid)` — full mapping with metadata
|
|
22
|
+
- Factory function `createLidFirstResolver()` for singleton pattern
|
|
23
|
+
- Works independently of full AntiBan system
|
|
24
|
+
- **MessageRetryReason enum** — Typed retry reason codes for message encryption failures
|
|
25
|
+
- 8 retry reason codes: UnknownError, GenericError, SignalErrorInvalidKeyId, SignalErrorInvalidMessage, SignalErrorNoSession, SignalErrorBadMac, MessageExpired, DecryptionError
|
|
26
|
+
- `MAC_ERROR_CODES` set for quick MAC error detection
|
|
27
|
+
- `parseRetryReason(code)` — parse from string/number to enum
|
|
28
|
+
- `isMacError(reason)` — check if reason is a MAC error
|
|
29
|
+
- `getRetryReasonDescription(reason)` — human-readable descriptions
|
|
30
|
+
- Based on whatsapp-rust and Baileys protocol research
|
|
31
|
+
- Named `MessageRetryReason` to avoid conflict with existing `RetryReason` type from `retryTracker.ts`
|
|
32
|
+
|
|
33
|
+
### Changed
|
|
34
|
+
- `index.ts` now exports `LidFirstResolver`, `createLidFirstResolver`, `LidPhoneMapping`, `MessageRetryReason`, `MAC_ERROR_CODES`, `parseRetryReason`, `isMacError`, `getRetryReasonDescription`
|
|
35
|
+
|
|
36
|
+
### Tests
|
|
37
|
+
- 30 new tests for `LidFirstResolver` (auth dir loading, phone↔LID resolution, malformed input handling, factory function)
|
|
38
|
+
- 21 new tests for `RetryReason` (enum values, MAC error detection, parsing, descriptions, integration scenarios)
|
|
39
|
+
- 3 new tests for disconnect codes 405, 409, 412 in `sessionStability.test.ts`
|
|
40
|
+
- Total new test coverage: 54 tests
|
|
41
|
+
|
|
42
|
+
### Technical Details
|
|
43
|
+
- `LidFirstResolver` uses in-memory maps for O(1) lookup performance
|
|
44
|
+
- Handles device suffix normalization (`:N` in JIDs)
|
|
45
|
+
- Gracefully handles malformed auth dirs and JSON files (no crashes)
|
|
46
|
+
- `RetryReason` enum matches Signal protocol + WhatsApp extensions
|
|
47
|
+
- Backward compatible — all new features are opt-in, no breaking changes
|
|
48
|
+
|
|
8
49
|
## [2.0.0] - 2026-04-19
|
|
9
50
|
|
|
10
51
|
### Added
|
package/README.md
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
# baileys-antiban
|
|
1
|
+
# baileys-antiban — Anti-Ban Middleware for Baileys & WhatsApp Bots
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/baileys-antiban)
|
|
4
4
|
[](https://nodejs.org/)
|
|
5
5
|
[](https://opensource.org/licenses/MIT)
|
|
6
6
|
|
|
7
|
-
**
|
|
7
|
+
**Drop-in anti-ban middleware for Baileys WhatsApp bots. Free, self-hosted, TypeScript-first. Whapi.Cloud alternative — zero monthly fees.**
|
|
8
|
+
|
|
9
|
+
> Rate limiting with Gaussian jitter, 7-day warmup, session health monitoring, LID resolver, disconnect classification, contact graph enforcement — all in one `npm install`. Works with [Baileys](https://github.com/WhiskeySockets/Baileys) and [@oxidezap/baileyrs](https://github.com/oxidezap/baileyrs) (Rust/WASM).
|
|
8
10
|
|
|
9
11
|
## v2.0 New Features — Session Stability Module
|
|
10
12
|
|
|
@@ -323,6 +325,25 @@ const antiban = new AntiBan({
|
|
|
323
325
|
|
|
324
326
|
**Why these features?** 2025-2026 ban research showed WhatsApp's ML models heavily weight reply-ratio (<10% = high risk), contact-graph distance (strangers = high risk), and temporal patterns (robotic timing = high risk). These modules address the three largest gaps in existing anti-ban libraries.
|
|
325
327
|
|
|
328
|
+
## baileys-antiban vs Whapi.Cloud vs DIY rate limiting
|
|
329
|
+
|
|
330
|
+
| Feature | baileys-antiban | Whapi.Cloud | DIY snippets |
|
|
331
|
+
|---|---|---|---|
|
|
332
|
+
| Price | **Free, MIT** | $49–$99/mo | Free |
|
|
333
|
+
| WhatsApp API | Unofficial (Baileys) | Unofficial underneath | Unofficial (Baileys) |
|
|
334
|
+
| Rate limiting | ✅ Gaussian jitter | ✅ Black box | ⚠️ Basic only |
|
|
335
|
+
| Warmup schedule | ✅ 7-day ramp | ✅ Managed | ❌ None |
|
|
336
|
+
| Session health monitor | ✅ Built-in | ✅ Managed | ❌ None |
|
|
337
|
+
| LID/PN resolver | ✅ v2.0 | ❌ Unknown | ❌ None |
|
|
338
|
+
| Disconnect classifier | ✅ Typed reasons | ❌ None | ❌ None |
|
|
339
|
+
| Contact graph enforcement | ✅ v1.3 | ❌ None | ❌ None |
|
|
340
|
+
| Self-hosted | ✅ Yes | ❌ No | ✅ Yes |
|
|
341
|
+
| TypeScript | ✅ Full types | N/A | ❌ Rarely |
|
|
342
|
+
| Customisable | ✅ Full control | ❌ None | ⚠️ Copy-paste |
|
|
343
|
+
| Drop-in (existing bot) | ✅ One-line wrapper | ❌ Full migration | ❌ Rewrite |
|
|
344
|
+
|
|
345
|
+
**Bottom line:** Whapi.Cloud charges $99/mo for managed Baileys under the hood — same unofficial API, same ban risk, zero customisation. baileys-antiban gives you more protection, free, with full source access.
|
|
346
|
+
|
|
326
347
|
## Why?
|
|
327
348
|
|
|
328
349
|
WhatsApp bans numbers that behave like bots. This library makes your Baileys bot behave like a human:
|
package/dist/index.d.ts
CHANGED
|
@@ -20,6 +20,8 @@ export { PostReconnectThrottle, type ReconnectThrottleConfig, type ReconnectThro
|
|
|
20
20
|
export { LidResolver, type LidResolverConfig, type LidResolverStats, type LidMapping } from './lidResolver.js';
|
|
21
21
|
export { JidCanonicalizer, type JidCanonicalizerConfig, type JidCanonicalizerStats } from './jidCanonicalizer.js';
|
|
22
22
|
export { SessionHealthMonitor, type SessionHealthStats, type SessionHealthConfig, wrapWithSessionStability, type SessionStabilityConfig, classifyDisconnect, type DisconnectClassification, type DisconnectCategory, } from './sessionStability.js';
|
|
23
|
+
export { LidFirstResolver, createLidFirstResolver, type LidPhoneMapping, } from './lidFirstResolver.js';
|
|
24
|
+
export { MessageRetryReason, MAC_ERROR_CODES, parseRetryReason, isMacError, getRetryReasonDescription, } from './retryReason.js';
|
|
23
25
|
export { wrapSocket, type WrappedSocket, type WrapSocketOptions } from './wrapper.js';
|
|
24
26
|
export { MessageQueue, type QueuedMessage, type MessageQueueConfig } from './messageQueue.js';
|
|
25
27
|
export { ContentVariator, type VariatorConfig } from './contentVariator.js';
|
package/dist/index.js
CHANGED
|
@@ -25,6 +25,9 @@ export { LidResolver } from './lidResolver.js';
|
|
|
25
25
|
export { JidCanonicalizer } from './jidCanonicalizer.js';
|
|
26
26
|
// v2.0 new modules
|
|
27
27
|
export { SessionHealthMonitor, wrapWithSessionStability, classifyDisconnect, } from './sessionStability.js';
|
|
28
|
+
// v2.1 new modules
|
|
29
|
+
export { LidFirstResolver, createLidFirstResolver, } from './lidFirstResolver.js';
|
|
30
|
+
export { MessageRetryReason, MAC_ERROR_CODES, parseRetryReason, isMacError, getRetryReasonDescription, } from './retryReason.js';
|
|
28
31
|
// Socket wrapper
|
|
29
32
|
export { wrapSocket } from './wrapper.js';
|
|
30
33
|
// Optional features
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LidFirstResolver — Standalone LID↔Phone mapper for Baileys auth state
|
|
3
|
+
*
|
|
4
|
+
* Lightweight drop-in utility that:
|
|
5
|
+
* - Loads LID↔phone mappings from Baileys' auth state directory
|
|
6
|
+
* - Resolves phone numbers to LID JIDs and vice versa
|
|
7
|
+
* - Learns new mappings from Baileys events
|
|
8
|
+
* - Works independently of the full AntiBan system
|
|
9
|
+
*
|
|
10
|
+
* Usage:
|
|
11
|
+
* ```typescript
|
|
12
|
+
* import { LidFirstResolver } from 'baileys-antiban';
|
|
13
|
+
* const resolver = new LidFirstResolver();
|
|
14
|
+
* resolver.loadFromAuthDir('./whatsapp-auth/my-session');
|
|
15
|
+
* const jid = resolver.resolveToLID('27825651069'); // → "210543692497008@lid" or null
|
|
16
|
+
* ```
|
|
17
|
+
*
|
|
18
|
+
* @author Kobus Wentzel <kobie@pop.co.za>
|
|
19
|
+
* @license MIT
|
|
20
|
+
*/
|
|
21
|
+
export interface LidPhoneMapping {
|
|
22
|
+
lid: string;
|
|
23
|
+
phone: string;
|
|
24
|
+
learnedAt: number;
|
|
25
|
+
source: 'auth-dir' | 'event';
|
|
26
|
+
}
|
|
27
|
+
export declare class LidFirstResolver {
|
|
28
|
+
private lidToPhone;
|
|
29
|
+
private phoneToLid;
|
|
30
|
+
/**
|
|
31
|
+
* Load mappings from Baileys auth state directory.
|
|
32
|
+
* Looks for lid-mapping-*_reverse.json files.
|
|
33
|
+
*/
|
|
34
|
+
loadFromAuthDir(authDir: string): void;
|
|
35
|
+
/**
|
|
36
|
+
* Learn a new mapping from a Baileys event (messages, contacts, etc.).
|
|
37
|
+
* Accepts partial data — will extract what it can.
|
|
38
|
+
*/
|
|
39
|
+
learnFromEvent(event: any): void;
|
|
40
|
+
/**
|
|
41
|
+
* Resolve phone number or phone JID to LID JID.
|
|
42
|
+
* Returns null if not known.
|
|
43
|
+
*/
|
|
44
|
+
resolveToLID(phoneOrJid: string): string | null;
|
|
45
|
+
/**
|
|
46
|
+
* Resolve LID JID to phone number.
|
|
47
|
+
* Returns null if not known.
|
|
48
|
+
*/
|
|
49
|
+
resolveToPhone(lid: string): string | null;
|
|
50
|
+
/**
|
|
51
|
+
* Get full mapping for a given JID (either LID or phone).
|
|
52
|
+
* Returns null if not known.
|
|
53
|
+
*/
|
|
54
|
+
getMapping(jid: string): LidPhoneMapping | null;
|
|
55
|
+
/**
|
|
56
|
+
* Get total number of known mappings.
|
|
57
|
+
*/
|
|
58
|
+
size(): number;
|
|
59
|
+
/**
|
|
60
|
+
* Clear all mappings.
|
|
61
|
+
*/
|
|
62
|
+
clear(): void;
|
|
63
|
+
private learnJid;
|
|
64
|
+
private extractPhone;
|
|
65
|
+
private normalizeLid;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Factory function for creating a singleton resolver instance.
|
|
69
|
+
* Useful for shared state across modules.
|
|
70
|
+
*/
|
|
71
|
+
export declare function createLidFirstResolver(): LidFirstResolver;
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LidFirstResolver — Standalone LID↔Phone mapper for Baileys auth state
|
|
3
|
+
*
|
|
4
|
+
* Lightweight drop-in utility that:
|
|
5
|
+
* - Loads LID↔phone mappings from Baileys' auth state directory
|
|
6
|
+
* - Resolves phone numbers to LID JIDs and vice versa
|
|
7
|
+
* - Learns new mappings from Baileys events
|
|
8
|
+
* - Works independently of the full AntiBan system
|
|
9
|
+
*
|
|
10
|
+
* Usage:
|
|
11
|
+
* ```typescript
|
|
12
|
+
* import { LidFirstResolver } from 'baileys-antiban';
|
|
13
|
+
* const resolver = new LidFirstResolver();
|
|
14
|
+
* resolver.loadFromAuthDir('./whatsapp-auth/my-session');
|
|
15
|
+
* const jid = resolver.resolveToLID('27825651069'); // → "210543692497008@lid" or null
|
|
16
|
+
* ```
|
|
17
|
+
*
|
|
18
|
+
* @author Kobus Wentzel <kobie@pop.co.za>
|
|
19
|
+
* @license MIT
|
|
20
|
+
*/
|
|
21
|
+
import * as fs from 'fs';
|
|
22
|
+
import * as path from 'path';
|
|
23
|
+
export class LidFirstResolver {
|
|
24
|
+
lidToPhone = new Map();
|
|
25
|
+
phoneToLid = new Map(); // phone → lid (quick reverse lookup)
|
|
26
|
+
/**
|
|
27
|
+
* Load mappings from Baileys auth state directory.
|
|
28
|
+
* Looks for lid-mapping-*_reverse.json files.
|
|
29
|
+
*/
|
|
30
|
+
loadFromAuthDir(authDir) {
|
|
31
|
+
try {
|
|
32
|
+
if (!fs.existsSync(authDir)) {
|
|
33
|
+
return; // Directory doesn't exist, nothing to load
|
|
34
|
+
}
|
|
35
|
+
const files = fs.readdirSync(authDir);
|
|
36
|
+
const reverseMappingFiles = files.filter(f => f.startsWith('lid-mapping-') && f.endsWith('_reverse.json'));
|
|
37
|
+
for (const file of reverseMappingFiles) {
|
|
38
|
+
const filePath = path.join(authDir, file);
|
|
39
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
40
|
+
const data = JSON.parse(content);
|
|
41
|
+
// Baileys reverse mapping format: { "lid@lid": "phone@s.whatsapp.net" }
|
|
42
|
+
for (const [lid, pnJid] of Object.entries(data)) {
|
|
43
|
+
if (typeof pnJid === 'string') {
|
|
44
|
+
const phone = this.extractPhone(pnJid);
|
|
45
|
+
if (phone && lid.endsWith('@lid')) {
|
|
46
|
+
const mapping = {
|
|
47
|
+
lid: this.normalizeLid(lid),
|
|
48
|
+
phone,
|
|
49
|
+
learnedAt: Date.now(),
|
|
50
|
+
source: 'auth-dir',
|
|
51
|
+
};
|
|
52
|
+
this.lidToPhone.set(mapping.lid, mapping);
|
|
53
|
+
this.phoneToLid.set(phone, mapping.lid);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
catch (error) {
|
|
60
|
+
// Silently fail — don't crash if auth dir is malformed
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Learn a new mapping from a Baileys event (messages, contacts, etc.).
|
|
65
|
+
* Accepts partial data — will extract what it can.
|
|
66
|
+
*/
|
|
67
|
+
learnFromEvent(event) {
|
|
68
|
+
try {
|
|
69
|
+
// Extract from message event structure
|
|
70
|
+
if (event.key?.remoteJid) {
|
|
71
|
+
const jid = event.key.remoteJid;
|
|
72
|
+
this.learnJid(jid, 'event');
|
|
73
|
+
}
|
|
74
|
+
// Extract from participant field (group messages)
|
|
75
|
+
if (event.key?.participant) {
|
|
76
|
+
const jid = event.key.participant;
|
|
77
|
+
this.learnJid(jid, 'event');
|
|
78
|
+
}
|
|
79
|
+
// Extract from contact event
|
|
80
|
+
if (event.id) {
|
|
81
|
+
this.learnJid(event.id, 'event');
|
|
82
|
+
}
|
|
83
|
+
// Extract from pushName field (has phone)
|
|
84
|
+
if (event.pushName && event.key?.remoteJid) {
|
|
85
|
+
this.learnJid(event.key.remoteJid, 'event');
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
catch (error) {
|
|
89
|
+
// Silently fail — don't crash on malformed events
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Resolve phone number or phone JID to LID JID.
|
|
94
|
+
* Returns null if not known.
|
|
95
|
+
*/
|
|
96
|
+
resolveToLID(phoneOrJid) {
|
|
97
|
+
const phone = this.extractPhone(phoneOrJid);
|
|
98
|
+
if (!phone)
|
|
99
|
+
return null;
|
|
100
|
+
return this.phoneToLid.get(phone) || null;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Resolve LID JID to phone number.
|
|
104
|
+
* Returns null if not known.
|
|
105
|
+
*/
|
|
106
|
+
resolveToPhone(lid) {
|
|
107
|
+
const normalized = this.normalizeLid(lid);
|
|
108
|
+
const mapping = this.lidToPhone.get(normalized);
|
|
109
|
+
return mapping ? mapping.phone : null;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Get full mapping for a given JID (either LID or phone).
|
|
113
|
+
* Returns null if not known.
|
|
114
|
+
*/
|
|
115
|
+
getMapping(jid) {
|
|
116
|
+
const normalized = this.normalizeLid(jid);
|
|
117
|
+
// Try as LID
|
|
118
|
+
const byLid = this.lidToPhone.get(normalized);
|
|
119
|
+
if (byLid)
|
|
120
|
+
return byLid;
|
|
121
|
+
// Try as phone
|
|
122
|
+
const phone = this.extractPhone(jid);
|
|
123
|
+
if (phone) {
|
|
124
|
+
const lid = this.phoneToLid.get(phone);
|
|
125
|
+
if (lid)
|
|
126
|
+
return this.lidToPhone.get(lid) || null;
|
|
127
|
+
}
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Get total number of known mappings.
|
|
132
|
+
*/
|
|
133
|
+
size() {
|
|
134
|
+
return this.lidToPhone.size;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Clear all mappings.
|
|
138
|
+
*/
|
|
139
|
+
clear() {
|
|
140
|
+
this.lidToPhone.clear();
|
|
141
|
+
this.phoneToLid.clear();
|
|
142
|
+
}
|
|
143
|
+
// Private helpers
|
|
144
|
+
learnJid(_jid, _source) {
|
|
145
|
+
// Check if this is a LID JID with a phone equivalent we can learn
|
|
146
|
+
// For now, we can only learn from auth dir or from paired data
|
|
147
|
+
// Single JID without context can't create a mapping
|
|
148
|
+
// This is intentionally limited — real learning happens in loadFromAuthDir
|
|
149
|
+
}
|
|
150
|
+
extractPhone(jid) {
|
|
151
|
+
if (!jid)
|
|
152
|
+
return null;
|
|
153
|
+
// Remove @s.whatsapp.net suffix if present
|
|
154
|
+
let cleaned = jid.replace('@s.whatsapp.net', '');
|
|
155
|
+
// Remove device suffix :N if present
|
|
156
|
+
cleaned = cleaned.replace(/:\d+$/, '');
|
|
157
|
+
// Check if it's a phone number (digits only)
|
|
158
|
+
if (/^\d+$/.test(cleaned)) {
|
|
159
|
+
return cleaned;
|
|
160
|
+
}
|
|
161
|
+
return null;
|
|
162
|
+
}
|
|
163
|
+
normalizeLid(lid) {
|
|
164
|
+
// Remove device suffix :N if present
|
|
165
|
+
return lid.replace(/:\d+@/, '@');
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Factory function for creating a singleton resolver instance.
|
|
170
|
+
* Useful for shared state across modules.
|
|
171
|
+
*/
|
|
172
|
+
export function createLidFirstResolver() {
|
|
173
|
+
return new LidFirstResolver();
|
|
174
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TypedMessageRetryReason — Typed enum for WhatsApp's retry reason codes
|
|
3
|
+
*
|
|
4
|
+
* Based on protocol research from whatsapp-rust and Baileys source.
|
|
5
|
+
* These codes appear in message retry events when encryption fails.
|
|
6
|
+
*
|
|
7
|
+
* Common scenarios:
|
|
8
|
+
* - SignalErrorBadMac (7) — Most common, indicates encryption session mismatch
|
|
9
|
+
* - SignalErrorNoSession (5) — Peer hasn't established session yet
|
|
10
|
+
* - SignalErrorInvalidKeyId (3) — Peer's prekey rotated
|
|
11
|
+
* - MessageExpired (8) — Message too old to decrypt
|
|
12
|
+
*
|
|
13
|
+
* @author Kobus Wentzel <kobie@pop.co.za>
|
|
14
|
+
* @license MIT
|
|
15
|
+
*/
|
|
16
|
+
/**
|
|
17
|
+
* WhatsApp message retry reason codes.
|
|
18
|
+
* Based on Signal protocol error codes + WhatsApp extensions.
|
|
19
|
+
*/
|
|
20
|
+
export declare enum MessageRetryReason {
|
|
21
|
+
UnknownError = 0,
|
|
22
|
+
GenericError = 1,
|
|
23
|
+
SignalErrorInvalidKeyId = 3,
|
|
24
|
+
SignalErrorInvalidMessage = 4,
|
|
25
|
+
SignalErrorNoSession = 5,
|
|
26
|
+
SignalErrorBadMac = 7,
|
|
27
|
+
MessageExpired = 8,
|
|
28
|
+
DecryptionError = 9
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Set of retry reasons that indicate MAC verification failure.
|
|
32
|
+
* These are the most common causes of "Bad MAC" errors in Baileys.
|
|
33
|
+
*/
|
|
34
|
+
export declare const MAC_ERROR_CODES: Set<MessageRetryReason>;
|
|
35
|
+
/**
|
|
36
|
+
* Parse a retry reason code from various input formats.
|
|
37
|
+
* Returns UnknownError if code is not recognized.
|
|
38
|
+
*/
|
|
39
|
+
export declare function parseRetryReason(code: string | number | undefined): MessageRetryReason;
|
|
40
|
+
/**
|
|
41
|
+
* Check if a retry reason indicates a MAC error.
|
|
42
|
+
* MAC errors are typically caused by encryption session mismatches,
|
|
43
|
+
* often due to LID/PN race conditions.
|
|
44
|
+
*/
|
|
45
|
+
export declare function isMacError(reason: MessageRetryReason): boolean;
|
|
46
|
+
/**
|
|
47
|
+
* Get a human-readable description of a retry reason.
|
|
48
|
+
*/
|
|
49
|
+
export declare function getRetryReasonDescription(reason: MessageRetryReason): string;
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TypedMessageRetryReason — Typed enum for WhatsApp's retry reason codes
|
|
3
|
+
*
|
|
4
|
+
* Based on protocol research from whatsapp-rust and Baileys source.
|
|
5
|
+
* These codes appear in message retry events when encryption fails.
|
|
6
|
+
*
|
|
7
|
+
* Common scenarios:
|
|
8
|
+
* - SignalErrorBadMac (7) — Most common, indicates encryption session mismatch
|
|
9
|
+
* - SignalErrorNoSession (5) — Peer hasn't established session yet
|
|
10
|
+
* - SignalErrorInvalidKeyId (3) — Peer's prekey rotated
|
|
11
|
+
* - MessageExpired (8) — Message too old to decrypt
|
|
12
|
+
*
|
|
13
|
+
* @author Kobus Wentzel <kobie@pop.co.za>
|
|
14
|
+
* @license MIT
|
|
15
|
+
*/
|
|
16
|
+
/**
|
|
17
|
+
* WhatsApp message retry reason codes.
|
|
18
|
+
* Based on Signal protocol error codes + WhatsApp extensions.
|
|
19
|
+
*/
|
|
20
|
+
export var MessageRetryReason;
|
|
21
|
+
(function (MessageRetryReason) {
|
|
22
|
+
MessageRetryReason[MessageRetryReason["UnknownError"] = 0] = "UnknownError";
|
|
23
|
+
MessageRetryReason[MessageRetryReason["GenericError"] = 1] = "GenericError";
|
|
24
|
+
MessageRetryReason[MessageRetryReason["SignalErrorInvalidKeyId"] = 3] = "SignalErrorInvalidKeyId";
|
|
25
|
+
MessageRetryReason[MessageRetryReason["SignalErrorInvalidMessage"] = 4] = "SignalErrorInvalidMessage";
|
|
26
|
+
MessageRetryReason[MessageRetryReason["SignalErrorNoSession"] = 5] = "SignalErrorNoSession";
|
|
27
|
+
MessageRetryReason[MessageRetryReason["SignalErrorBadMac"] = 7] = "SignalErrorBadMac";
|
|
28
|
+
MessageRetryReason[MessageRetryReason["MessageExpired"] = 8] = "MessageExpired";
|
|
29
|
+
MessageRetryReason[MessageRetryReason["DecryptionError"] = 9] = "DecryptionError";
|
|
30
|
+
})(MessageRetryReason || (MessageRetryReason = {}));
|
|
31
|
+
/**
|
|
32
|
+
* Set of retry reasons that indicate MAC verification failure.
|
|
33
|
+
* These are the most common causes of "Bad MAC" errors in Baileys.
|
|
34
|
+
*/
|
|
35
|
+
export const MAC_ERROR_CODES = new Set([
|
|
36
|
+
MessageRetryReason.SignalErrorBadMac,
|
|
37
|
+
MessageRetryReason.SignalErrorInvalidMessage,
|
|
38
|
+
MessageRetryReason.SignalErrorNoSession,
|
|
39
|
+
MessageRetryReason.SignalErrorInvalidKeyId,
|
|
40
|
+
]);
|
|
41
|
+
/**
|
|
42
|
+
* Parse a retry reason code from various input formats.
|
|
43
|
+
* Returns UnknownError if code is not recognized.
|
|
44
|
+
*/
|
|
45
|
+
export function parseRetryReason(code) {
|
|
46
|
+
if (code === undefined || code === null) {
|
|
47
|
+
return MessageRetryReason.UnknownError;
|
|
48
|
+
}
|
|
49
|
+
const n = typeof code === 'string' ? parseInt(code, 10) : code;
|
|
50
|
+
if (isNaN(n)) {
|
|
51
|
+
return MessageRetryReason.UnknownError;
|
|
52
|
+
}
|
|
53
|
+
// Check if the number is a valid enum value
|
|
54
|
+
if (Object.values(MessageRetryReason).includes(n)) {
|
|
55
|
+
return n;
|
|
56
|
+
}
|
|
57
|
+
return MessageRetryReason.UnknownError;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Check if a retry reason indicates a MAC error.
|
|
61
|
+
* MAC errors are typically caused by encryption session mismatches,
|
|
62
|
+
* often due to LID/PN race conditions.
|
|
63
|
+
*/
|
|
64
|
+
export function isMacError(reason) {
|
|
65
|
+
return MAC_ERROR_CODES.has(reason);
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Get a human-readable description of a retry reason.
|
|
69
|
+
*/
|
|
70
|
+
export function getRetryReasonDescription(reason) {
|
|
71
|
+
switch (reason) {
|
|
72
|
+
case MessageRetryReason.UnknownError:
|
|
73
|
+
return 'Unknown error';
|
|
74
|
+
case MessageRetryReason.GenericError:
|
|
75
|
+
return 'Generic error';
|
|
76
|
+
case MessageRetryReason.SignalErrorInvalidKeyId:
|
|
77
|
+
return 'Invalid key ID — peer prekey rotated';
|
|
78
|
+
case MessageRetryReason.SignalErrorInvalidMessage:
|
|
79
|
+
return 'Invalid message format';
|
|
80
|
+
case MessageRetryReason.SignalErrorNoSession:
|
|
81
|
+
return 'No session — peer not initialized';
|
|
82
|
+
case MessageRetryReason.SignalErrorBadMac:
|
|
83
|
+
return 'Bad MAC — encryption session mismatch';
|
|
84
|
+
case MessageRetryReason.MessageExpired:
|
|
85
|
+
return 'Message expired — too old to decrypt';
|
|
86
|
+
case MessageRetryReason.DecryptionError:
|
|
87
|
+
return 'Decryption failed';
|
|
88
|
+
default:
|
|
89
|
+
return `Unknown reason code ${reason}`;
|
|
90
|
+
}
|
|
91
|
+
}
|
package/dist/sessionStability.js
CHANGED
|
@@ -34,12 +34,31 @@ export function classifyDisconnect(statusCode) {
|
|
|
34
34
|
code: statusCode,
|
|
35
35
|
};
|
|
36
36
|
}
|
|
37
|
-
//
|
|
38
|
-
if (statusCode ===
|
|
37
|
+
// Method not allowed — server rejecting connection method
|
|
38
|
+
if (statusCode === 405) {
|
|
39
39
|
return {
|
|
40
40
|
category: 'fatal',
|
|
41
41
|
shouldReconnect: false,
|
|
42
|
-
message: '
|
|
42
|
+
message: 'Method not allowed — server rejected connection method',
|
|
43
|
+
code: statusCode,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
// Conflict / Connection replaced — user logged in elsewhere or multi-device conflict
|
|
47
|
+
if (statusCode === 409 || statusCode === 428) {
|
|
48
|
+
return {
|
|
49
|
+
category: 'fatal',
|
|
50
|
+
shouldReconnect: false,
|
|
51
|
+
message: 'Connection replaced — another device took over',
|
|
52
|
+
code: statusCode,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
// Precondition failed — auth state mismatch, retry after delay
|
|
56
|
+
if (statusCode === 412) {
|
|
57
|
+
return {
|
|
58
|
+
category: 'recoverable',
|
|
59
|
+
shouldReconnect: true,
|
|
60
|
+
backoffMs: 30_000, // 30 seconds
|
|
61
|
+
message: 'Precondition failed — auth state mismatch, retry after delay',
|
|
43
62
|
code: statusCode,
|
|
44
63
|
};
|
|
45
64
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "baileys-antiban",
|
|
3
|
-
"version": "2.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "2.1.1",
|
|
4
|
+
"description": "Anti-ban middleware for Baileys WhatsApp bots. Rate limiting, warmup, health monitor, LID resolver, disconnect classifier. Free Whapi.Cloud alternative.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
7
7
|
"type": "module",
|
|
@@ -38,7 +38,20 @@
|
|
|
38
38
|
"bot-protection",
|
|
39
39
|
"whatsapp-api",
|
|
40
40
|
"nodejs",
|
|
41
|
-
"transport-agnostic"
|
|
41
|
+
"transport-agnostic",
|
|
42
|
+
"whapi-alternative",
|
|
43
|
+
"whatsapp-ban",
|
|
44
|
+
"baileys-middleware",
|
|
45
|
+
"warmup",
|
|
46
|
+
"session-health",
|
|
47
|
+
"lid-resolver",
|
|
48
|
+
"disconnect-classifier",
|
|
49
|
+
"gaussian-jitter",
|
|
50
|
+
"whatsapp-automation",
|
|
51
|
+
"typescript",
|
|
52
|
+
"self-hosted",
|
|
53
|
+
"free",
|
|
54
|
+
"open-source"
|
|
42
55
|
],
|
|
43
56
|
"author": "Kobus Wentzel <kobie@pop.co.za>",
|
|
44
57
|
"license": "MIT",
|