baileys-antiban 3.6.1 → 3.8.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 +21 -0
- package/README.md +15 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +4 -0
- package/dist/jidCanonicalizer.d.ts +15 -0
- package/dist/jidCanonicalizer.js +14 -0
- package/dist/lidResolver.d.ts +16 -0
- package/dist/lidResolver.js +32 -0
- package/dist/sessionFingerprint.d.ts +156 -0
- package/dist/sessionFingerprint.js +248 -0
- package/dist/stealthConnect.d.ts +39 -0
- package/dist/stealthConnect.js +48 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,27 @@ 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.0] - 2026-04-28
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- **Stealth Connect** — Gradual presence ramp to reduce ban signals. Inspired by GOWA's `--presence-on-connect=unavailable` flag. Bots that instantly snap online and start blasting messages look suspicious. New helpers:
|
|
12
|
+
- `getStealthSocketConfig({ os?: string })` — returns socket config with `markOnlineOnConnect: false` and sensible browser defaults.
|
|
13
|
+
- `rampPresenceAfterConnect(sock, { minDelayMs?, maxDelayMs?, targetState? })` — waits 30-90s (configurable), then transitions presence to `available` (or custom state). Call after socket connects. Returns a promise.
|
|
14
|
+
- Use together: connect silently, ramp presence gradually when ready to act.
|
|
15
|
+
|
|
16
|
+
## [3.7.0] - 2026-04-27
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
- `LidResolver.learnFromGroupMetadata(participants)` — ingest LID↔PN mappings from Baileys group metadata. Supports v6 (`id: '@s.whatsapp.net', lid: '@lid'`) and v7 (`id: '@lid', phoneNumber: '@s.whatsapp.net'`) participant formats.
|
|
20
|
+
- `JidCanonicalizer.learnFromGroupMetadata(participants)` — passthrough to `LidResolver.learnFromGroupMetadata()`.
|
|
21
|
+
|
|
22
|
+
### Fixed
|
|
23
|
+
- Confirmed compatibility with Baileys v7.0.0-rc.9 LID migration. `resolveCanonical()` correctly falls back to original JID when LID→PN mapping is unknown (no throw).
|
|
24
|
+
|
|
25
|
+
### Notes
|
|
26
|
+
- Default `enabled: false` unchanged — opt-in as always.
|
|
27
|
+
- Group metadata learning is the recommended way to pre-populate LID mappings for auction groups where `getCachedGroupMetadata()` is called anyway.
|
|
28
|
+
|
|
8
29
|
## [3.6.1] - 2026-04-26
|
|
9
30
|
|
|
10
31
|
### Changed
|
package/README.md
CHANGED
|
@@ -599,6 +599,21 @@ npx baileys-antiban warmup --simulate 7 --preset moderate
|
|
|
599
599
|
npx baileys-antiban reset --state ./antiban-state.json
|
|
600
600
|
```
|
|
601
601
|
|
|
602
|
+
### Stealth Connect (v3.8.0)
|
|
603
|
+
|
|
604
|
+
Bots that instantly snap online and start blasting messages look suspicious. Stealth connect delays presence ramp to look more human.
|
|
605
|
+
|
|
606
|
+
```typescript
|
|
607
|
+
import { makeWASocket } from '@whiskeysockets/baileys';
|
|
608
|
+
import { getStealthSocketConfig, rampPresenceAfterConnect } from 'baileys-antiban';
|
|
609
|
+
|
|
610
|
+
const config = getStealthSocketConfig({ os: 'My Custom App' });
|
|
611
|
+
const sock = makeWASocket({ ...config, auth: state });
|
|
612
|
+
|
|
613
|
+
// Wait 30-90s, then go available (or fire-and-forget)
|
|
614
|
+
await rampPresenceAfterConnect(sock, { minDelayMs: 45000, maxDelayMs: 120000 });
|
|
615
|
+
```
|
|
616
|
+
|
|
602
617
|
## Quick Start (Legacy)
|
|
603
618
|
|
|
604
619
|
### Option 1: Wrap Your Socket (Easiest)
|
package/dist/index.d.ts
CHANGED
|
@@ -36,3 +36,5 @@ export { generateFingerprint, applyFingerprint, type DeviceFingerprint, type Dev
|
|
|
36
36
|
export { credsSnapshot, type CredsSnapshot, type CredsSnapshotConfig, } from './credsSnapshot.js';
|
|
37
37
|
export { readReceiptVariance, type ReadReceiptVariance, type ReadReceiptVarianceConfig, } from './readReceiptVariance.js';
|
|
38
38
|
export { proxyRotator, type ProxyEndpoint, type ProxyRotatorConfig, type ProxyRotatorStats, type ProxyRotatorHandle, } from './proxyRotator.js';
|
|
39
|
+
export { generateSessionFingerprint, applySessionFingerprint, getMessageSendJitter, getTypingJitter, getRetryJitter, getVoiceNoteMetadata, getBatteryState, createStealthFingerprint, type SessionFingerprint, type SessionFingerprintConfig, } from './sessionFingerprint.js';
|
|
40
|
+
export { getStealthSocketConfig, rampPresenceAfterConnect, } from './stealthConnect.js';
|
package/dist/index.js
CHANGED
|
@@ -49,3 +49,7 @@ export { credsSnapshot, } from './credsSnapshot.js';
|
|
|
49
49
|
export { readReceiptVariance, } from './readReceiptVariance.js';
|
|
50
50
|
// v3.5 new modules
|
|
51
51
|
export { proxyRotator, } from './proxyRotator.js';
|
|
52
|
+
// v3.6 new modules (Obscura-inspired)
|
|
53
|
+
export { generateSessionFingerprint, applySessionFingerprint, getMessageSendJitter, getTypingJitter, getRetryJitter, getVoiceNoteMetadata, getBatteryState, createStealthFingerprint, } from './sessionFingerprint.js';
|
|
54
|
+
// v3.8 new modules
|
|
55
|
+
export { getStealthSocketConfig, rampPresenceAfterConnect, } from './stealthConnect.js';
|
|
@@ -80,6 +80,21 @@ export declare class JidCanonicalizer {
|
|
|
80
80
|
messages: Array<any>;
|
|
81
81
|
type?: string;
|
|
82
82
|
}): void;
|
|
83
|
+
/**
|
|
84
|
+
* Learn LID↔PN mappings from group metadata participants.
|
|
85
|
+
* Call after fetchGroupMetadata() to pre-populate the resolver map.
|
|
86
|
+
* No-op if canonicalization is disabled.
|
|
87
|
+
*
|
|
88
|
+
* @param participants - Group metadata participants array from Baileys
|
|
89
|
+
* @returns Number of new mappings learned (0 if disabled)
|
|
90
|
+
*/
|
|
91
|
+
learnFromGroupMetadata(participants: Array<{
|
|
92
|
+
id: string;
|
|
93
|
+
lid?: string;
|
|
94
|
+
phoneNumber?: string;
|
|
95
|
+
phone?: string;
|
|
96
|
+
number?: string;
|
|
97
|
+
}>): number;
|
|
83
98
|
/**
|
|
84
99
|
* Called by wrapper on messages.update event. Learns from sent-message refs.
|
|
85
100
|
*/
|
package/dist/jidCanonicalizer.js
CHANGED
|
@@ -149,6 +149,20 @@ export class JidCanonicalizer {
|
|
|
149
149
|
this.learnFromMessage(msg);
|
|
150
150
|
}
|
|
151
151
|
}
|
|
152
|
+
/**
|
|
153
|
+
* Learn LID↔PN mappings from group metadata participants.
|
|
154
|
+
* Call after fetchGroupMetadata() to pre-populate the resolver map.
|
|
155
|
+
* No-op if canonicalization is disabled.
|
|
156
|
+
*
|
|
157
|
+
* @param participants - Group metadata participants array from Baileys
|
|
158
|
+
* @returns Number of new mappings learned (0 if disabled)
|
|
159
|
+
*/
|
|
160
|
+
learnFromGroupMetadata(participants) {
|
|
161
|
+
if (!this.config.enabled || !this.config.learnFromEvents) {
|
|
162
|
+
return 0;
|
|
163
|
+
}
|
|
164
|
+
return this.lidResolver.learnFromGroupMetadata(participants);
|
|
165
|
+
}
|
|
152
166
|
/**
|
|
153
167
|
* Called by wrapper on messages.update event. Learns from sent-message refs.
|
|
154
168
|
*/
|
package/dist/lidResolver.d.ts
CHANGED
|
@@ -71,6 +71,22 @@ export declare class LidResolver {
|
|
|
71
71
|
* Full mapping for inspection
|
|
72
72
|
*/
|
|
73
73
|
getMapping(jid: string): LidMapping | null;
|
|
74
|
+
/**
|
|
75
|
+
* Learn LID↔PN mappings from group metadata participants.
|
|
76
|
+
* Call this after fetchGroupMetadata() to pre-populate the map.
|
|
77
|
+
* Supports both {id: '@lid', phoneNumber: '@s.whatsapp.net'} and
|
|
78
|
+
* {id: '@s.whatsapp.net', lid: '@lid'} participant formats (v7 + v6 shapes).
|
|
79
|
+
*
|
|
80
|
+
* @param participants - Group metadata participants array from Baileys
|
|
81
|
+
* @returns Number of new mappings learned
|
|
82
|
+
*/
|
|
83
|
+
learnFromGroupMetadata(participants: Array<{
|
|
84
|
+
id: string;
|
|
85
|
+
lid?: string;
|
|
86
|
+
phoneNumber?: string;
|
|
87
|
+
phone?: string;
|
|
88
|
+
number?: string;
|
|
89
|
+
}>): number;
|
|
74
90
|
/**
|
|
75
91
|
* Seed from persistence (called automatically in constructor if persistence provided)
|
|
76
92
|
*/
|
package/dist/lidResolver.js
CHANGED
|
@@ -178,6 +178,38 @@ export class LidResolver {
|
|
|
178
178
|
}
|
|
179
179
|
return null;
|
|
180
180
|
}
|
|
181
|
+
/**
|
|
182
|
+
* Learn LID↔PN mappings from group metadata participants.
|
|
183
|
+
* Call this after fetchGroupMetadata() to pre-populate the map.
|
|
184
|
+
* Supports both {id: '@lid', phoneNumber: '@s.whatsapp.net'} and
|
|
185
|
+
* {id: '@s.whatsapp.net', lid: '@lid'} participant formats (v7 + v6 shapes).
|
|
186
|
+
*
|
|
187
|
+
* @param participants - Group metadata participants array from Baileys
|
|
188
|
+
* @returns Number of new mappings learned
|
|
189
|
+
*/
|
|
190
|
+
learnFromGroupMetadata(participants) {
|
|
191
|
+
let learned = 0;
|
|
192
|
+
for (const p of participants) {
|
|
193
|
+
const domain = p.id.split('@')[1] || '';
|
|
194
|
+
if (domain === 'lid' && (p.phoneNumber || p.phone || p.number)) {
|
|
195
|
+
const pn = p.phoneNumber || (p.phone ? `${p.phone}@s.whatsapp.net` : null) || (p.number ? `${p.number}@s.whatsapp.net` : null);
|
|
196
|
+
if (pn) {
|
|
197
|
+
const prevSize = this.lidToPn.size;
|
|
198
|
+
this.learn({ lid: p.id, pn });
|
|
199
|
+
if (this.lidToPn.size > prevSize)
|
|
200
|
+
learned++;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
else if (domain === 's.whatsapp.net' && p.lid) {
|
|
204
|
+
const lid = p.lid.endsWith('@lid') ? p.lid : `${p.lid}@lid`;
|
|
205
|
+
const prevSize = this.lidToPn.size;
|
|
206
|
+
this.learn({ lid, pn: p.id });
|
|
207
|
+
if (this.lidToPn.size > prevSize)
|
|
208
|
+
learned++;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
return learned;
|
|
212
|
+
}
|
|
181
213
|
/**
|
|
182
214
|
* Seed from persistence (called automatically in constructor if persistence provided)
|
|
183
215
|
*/
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session Fingerprint Randomization (Obscura-inspired)
|
|
3
|
+
*
|
|
4
|
+
* Per-session fingerprint randomization to prevent device tracking.
|
|
5
|
+
* Scavenged patterns from Obscura headless browser's stealth mode.
|
|
6
|
+
*
|
|
7
|
+
* Key principles from Obscura:
|
|
8
|
+
* 1. Per-session randomization (not per-request)
|
|
9
|
+
* 2. Consistent within session (same session = same fingerprint)
|
|
10
|
+
* 3. Feature-flag pattern for optional anti-detection
|
|
11
|
+
* 4. Emulation of real device profiles (not synthetic values)
|
|
12
|
+
*
|
|
13
|
+
* Browser fingerprint → WhatsApp signal mapping:
|
|
14
|
+
* - TLS fingerprint → WA protocol version
|
|
15
|
+
* - Canvas noise → message timing jitter
|
|
16
|
+
* - Audio fingerprint → voice note metadata
|
|
17
|
+
* - GPU info → device model/brand
|
|
18
|
+
* - Battery → connection state variation
|
|
19
|
+
* - User agent → WA client version
|
|
20
|
+
*
|
|
21
|
+
* @author Kobus Wentzel <kobie@pop.co.za>
|
|
22
|
+
* @license MIT
|
|
23
|
+
*/
|
|
24
|
+
import { type DeviceFingerprint } from './deviceFingerprint.js';
|
|
25
|
+
export interface SessionFingerprintConfig {
|
|
26
|
+
/** Master switch for enhanced fingerprinting */
|
|
27
|
+
enabled?: boolean;
|
|
28
|
+
/** Device profile randomization (from deviceFingerprint.ts) */
|
|
29
|
+
deviceProfile?: {
|
|
30
|
+
randomizeAppVersion?: boolean;
|
|
31
|
+
randomizeOsVersion?: boolean;
|
|
32
|
+
randomizeDeviceModel?: boolean;
|
|
33
|
+
appVersionPool?: number[][];
|
|
34
|
+
osVersionPool?: string[];
|
|
35
|
+
deviceModelPool?: string[];
|
|
36
|
+
};
|
|
37
|
+
/** Network timing variance (anti-pattern detection) */
|
|
38
|
+
networkTiming?: {
|
|
39
|
+
/** Add jitter to message send timing (ms) */
|
|
40
|
+
sendJitterMs?: [number, number];
|
|
41
|
+
/** Add jitter to typing indicators (ms) */
|
|
42
|
+
typingJitterMs?: [number, number];
|
|
43
|
+
/** Vary connection retry backoff */
|
|
44
|
+
retryJitterMs?: [number, number];
|
|
45
|
+
};
|
|
46
|
+
/** Voice note metadata randomization */
|
|
47
|
+
voiceNote?: {
|
|
48
|
+
/** Vary waveform pattern slightly */
|
|
49
|
+
randomizeWaveform?: boolean;
|
|
50
|
+
/** Vary duration by small amount (ms) */
|
|
51
|
+
durationJitterMs?: number;
|
|
52
|
+
/** Randomize sample rate from pool */
|
|
53
|
+
sampleRatePool?: number[];
|
|
54
|
+
};
|
|
55
|
+
/** Connection state variance */
|
|
56
|
+
connectionState?: {
|
|
57
|
+
/** Vary idle timeout */
|
|
58
|
+
idleTimeoutJitterMs?: [number, number];
|
|
59
|
+
/** Vary keepalive interval */
|
|
60
|
+
keepaliveJitterMs?: [number, number];
|
|
61
|
+
/** Randomize battery state reported */
|
|
62
|
+
randomizeBattery?: boolean;
|
|
63
|
+
/** Battery level pool (0-100) */
|
|
64
|
+
batteryLevelPool?: number[];
|
|
65
|
+
};
|
|
66
|
+
/** Protocol version variance */
|
|
67
|
+
protocolVersion?: {
|
|
68
|
+
/** Randomize protocol sub-version */
|
|
69
|
+
randomizeSubVersion?: boolean;
|
|
70
|
+
/** Protocol version pool (e.g., different patch versions) */
|
|
71
|
+
versionPool?: string[];
|
|
72
|
+
};
|
|
73
|
+
/** Seed for deterministic randomization (testing/debugging) */
|
|
74
|
+
seed?: string;
|
|
75
|
+
}
|
|
76
|
+
export interface SessionFingerprint {
|
|
77
|
+
/** Core device profile */
|
|
78
|
+
device: DeviceFingerprint;
|
|
79
|
+
/** Network timing variances (stable per session) */
|
|
80
|
+
networkTiming: {
|
|
81
|
+
sendJitterMs: number;
|
|
82
|
+
typingJitterMs: number;
|
|
83
|
+
retryJitterMs: number;
|
|
84
|
+
};
|
|
85
|
+
/** Voice note profile */
|
|
86
|
+
voiceNote: {
|
|
87
|
+
waveformSeed: number;
|
|
88
|
+
durationJitterMs: number;
|
|
89
|
+
sampleRate: number;
|
|
90
|
+
};
|
|
91
|
+
/** Connection state profile */
|
|
92
|
+
connectionState: {
|
|
93
|
+
idleTimeoutMs: number;
|
|
94
|
+
keepaliveMs: number;
|
|
95
|
+
batteryLevel: number;
|
|
96
|
+
batteryCharging: boolean;
|
|
97
|
+
};
|
|
98
|
+
/** Protocol version */
|
|
99
|
+
protocolVersion: string;
|
|
100
|
+
/** Session identifier (stable for this fingerprint) */
|
|
101
|
+
sessionId: string;
|
|
102
|
+
/** Timestamp when fingerprint was generated */
|
|
103
|
+
createdAt: number;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Generate a comprehensive session fingerprint.
|
|
107
|
+
* Call once per session (socket initialization).
|
|
108
|
+
*
|
|
109
|
+
* Obscura pattern: consistent per session, randomized across sessions.
|
|
110
|
+
*/
|
|
111
|
+
export declare function generateSessionFingerprint(config?: SessionFingerprintConfig, sessionId?: string): SessionFingerprint;
|
|
112
|
+
/**
|
|
113
|
+
* Apply session fingerprint to Baileys socket config.
|
|
114
|
+
*
|
|
115
|
+
* Usage:
|
|
116
|
+
* const fingerprint = generateSessionFingerprint({ enabled: true });
|
|
117
|
+
* const sock = makeWASocket(applySessionFingerprint(config, fingerprint));
|
|
118
|
+
*/
|
|
119
|
+
export declare function applySessionFingerprint(socketConfig: any, fingerprint: SessionFingerprint): any;
|
|
120
|
+
/**
|
|
121
|
+
* Get timing jitter for message send (helper for presenceChoreographer/rateLimiter)
|
|
122
|
+
*
|
|
123
|
+
* Usage in beforeSend():
|
|
124
|
+
* const jitter = getMessageSendJitter(fingerprint);
|
|
125
|
+
* await sleep(baseDelay + jitter);
|
|
126
|
+
*/
|
|
127
|
+
export declare function getMessageSendJitter(fingerprint: SessionFingerprint): number;
|
|
128
|
+
/**
|
|
129
|
+
* Get typing indicator jitter (helper for presenceChoreographer)
|
|
130
|
+
*/
|
|
131
|
+
export declare function getTypingJitter(fingerprint: SessionFingerprint): number;
|
|
132
|
+
/**
|
|
133
|
+
* Get retry backoff jitter (helper for reconnectThrottle)
|
|
134
|
+
*/
|
|
135
|
+
export declare function getRetryJitter(fingerprint: SessionFingerprint): number;
|
|
136
|
+
/**
|
|
137
|
+
* Get voice note metadata (helper for voice message encoding)
|
|
138
|
+
*
|
|
139
|
+
* Returns suggested sample rate and duration adjustment based on session fingerprint.
|
|
140
|
+
*/
|
|
141
|
+
export declare function getVoiceNoteMetadata(fingerprint: SessionFingerprint): {
|
|
142
|
+
sampleRate: number;
|
|
143
|
+
durationJitterMs: number;
|
|
144
|
+
waveformSeed: number;
|
|
145
|
+
};
|
|
146
|
+
/**
|
|
147
|
+
* Get battery state (helper for presence/connection state signals)
|
|
148
|
+
*/
|
|
149
|
+
export declare function getBatteryState(fingerprint: SessionFingerprint): {
|
|
150
|
+
level: number;
|
|
151
|
+
charging: boolean;
|
|
152
|
+
};
|
|
153
|
+
/**
|
|
154
|
+
* Create a session fingerprint preset (Obscura-inspired feature flag pattern)
|
|
155
|
+
*/
|
|
156
|
+
export declare function createStealthFingerprint(sessionId?: string): SessionFingerprint;
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session Fingerprint Randomization (Obscura-inspired)
|
|
3
|
+
*
|
|
4
|
+
* Per-session fingerprint randomization to prevent device tracking.
|
|
5
|
+
* Scavenged patterns from Obscura headless browser's stealth mode.
|
|
6
|
+
*
|
|
7
|
+
* Key principles from Obscura:
|
|
8
|
+
* 1. Per-session randomization (not per-request)
|
|
9
|
+
* 2. Consistent within session (same session = same fingerprint)
|
|
10
|
+
* 3. Feature-flag pattern for optional anti-detection
|
|
11
|
+
* 4. Emulation of real device profiles (not synthetic values)
|
|
12
|
+
*
|
|
13
|
+
* Browser fingerprint → WhatsApp signal mapping:
|
|
14
|
+
* - TLS fingerprint → WA protocol version
|
|
15
|
+
* - Canvas noise → message timing jitter
|
|
16
|
+
* - Audio fingerprint → voice note metadata
|
|
17
|
+
* - GPU info → device model/brand
|
|
18
|
+
* - Battery → connection state variation
|
|
19
|
+
* - User agent → WA client version
|
|
20
|
+
*
|
|
21
|
+
* @author Kobus Wentzel <kobie@pop.co.za>
|
|
22
|
+
* @license MIT
|
|
23
|
+
*/
|
|
24
|
+
import { generateFingerprint } from './deviceFingerprint.js';
|
|
25
|
+
/**
|
|
26
|
+
* Simple deterministic PRNG using mulberry32
|
|
27
|
+
* Same as deviceFingerprint.ts for consistency
|
|
28
|
+
*/
|
|
29
|
+
class SeededRandom {
|
|
30
|
+
state;
|
|
31
|
+
constructor(seed) {
|
|
32
|
+
let hash = 0;
|
|
33
|
+
for (let i = 0; i < seed.length; i++) {
|
|
34
|
+
hash = (hash << 5) - hash + seed.charCodeAt(i);
|
|
35
|
+
hash = hash & hash;
|
|
36
|
+
}
|
|
37
|
+
this.state = Math.abs(hash) || 1;
|
|
38
|
+
}
|
|
39
|
+
next() {
|
|
40
|
+
let t = (this.state += 0x6d2b79f5);
|
|
41
|
+
t = Math.imul(t ^ (t >>> 15), t | 1);
|
|
42
|
+
t ^= t + Math.imul(t ^ (t >>> 7), t | 61);
|
|
43
|
+
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
|
|
44
|
+
}
|
|
45
|
+
range(min, max) {
|
|
46
|
+
return Math.floor(this.next() * (max - min + 1)) + min;
|
|
47
|
+
}
|
|
48
|
+
rangeFloat(min, max) {
|
|
49
|
+
return this.next() * (max - min) + min;
|
|
50
|
+
}
|
|
51
|
+
pick(array) {
|
|
52
|
+
return array[Math.floor(this.next() * array.length)];
|
|
53
|
+
}
|
|
54
|
+
boolean(probability = 0.5) {
|
|
55
|
+
return this.next() < probability;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
// Default configuration values (conservative, realistic)
|
|
59
|
+
const DEFAULT_SEND_JITTER_MS = [50, 300];
|
|
60
|
+
const DEFAULT_TYPING_JITTER_MS = [30, 150];
|
|
61
|
+
const DEFAULT_RETRY_JITTER_MS = [100, 500];
|
|
62
|
+
const DEFAULT_DURATION_JITTER_MS = 200;
|
|
63
|
+
const DEFAULT_SAMPLE_RATE_POOL = [8000, 16000, 44100, 48000];
|
|
64
|
+
const DEFAULT_IDLE_TIMEOUT_JITTER_MS = [25000, 35000];
|
|
65
|
+
const DEFAULT_KEEPALIVE_JITTER_MS = [15000, 25000];
|
|
66
|
+
const DEFAULT_BATTERY_LEVEL_POOL = [20, 35, 50, 65, 80, 95, 100];
|
|
67
|
+
const DEFAULT_PROTOCOL_VERSION_POOL = ['2.24.5', '2.24.4', '2.24.3'];
|
|
68
|
+
/**
|
|
69
|
+
* Generate a comprehensive session fingerprint.
|
|
70
|
+
* Call once per session (socket initialization).
|
|
71
|
+
*
|
|
72
|
+
* Obscura pattern: consistent per session, randomized across sessions.
|
|
73
|
+
*/
|
|
74
|
+
export function generateSessionFingerprint(config = {}, sessionId) {
|
|
75
|
+
const { enabled = true, deviceProfile = {}, networkTiming = {}, voiceNote = {}, connectionState = {}, protocolVersion = {}, seed, } = config;
|
|
76
|
+
const finalSessionId = sessionId || `session-${Date.now()}-${Math.random()}`;
|
|
77
|
+
const rng = new SeededRandom(seed || finalSessionId);
|
|
78
|
+
// Generate base device fingerprint (delegates to deviceFingerprint.ts)
|
|
79
|
+
const device = generateFingerprint({
|
|
80
|
+
enabled,
|
|
81
|
+
randomizeAppVersion: deviceProfile.randomizeAppVersion ?? true,
|
|
82
|
+
randomizeOsVersion: deviceProfile.randomizeOsVersion ?? true,
|
|
83
|
+
randomizeDeviceModel: deviceProfile.randomizeDeviceModel ?? true,
|
|
84
|
+
seed: seed || finalSessionId,
|
|
85
|
+
appVersionPool: deviceProfile.appVersionPool,
|
|
86
|
+
osVersionPool: deviceProfile.osVersionPool,
|
|
87
|
+
deviceModelPool: deviceProfile.deviceModelPool,
|
|
88
|
+
}, finalSessionId);
|
|
89
|
+
// Network timing variances (Obscura: prevent timing pattern detection)
|
|
90
|
+
const sendJitterRange = networkTiming.sendJitterMs || DEFAULT_SEND_JITTER_MS;
|
|
91
|
+
const typingJitterRange = networkTiming.typingJitterMs || DEFAULT_TYPING_JITTER_MS;
|
|
92
|
+
const retryJitterRange = networkTiming.retryJitterMs || DEFAULT_RETRY_JITTER_MS;
|
|
93
|
+
const networkTimingProfile = enabled
|
|
94
|
+
? {
|
|
95
|
+
sendJitterMs: rng.range(sendJitterRange[0], sendJitterRange[1]),
|
|
96
|
+
typingJitterMs: rng.range(typingJitterRange[0], typingJitterRange[1]),
|
|
97
|
+
retryJitterMs: rng.range(retryJitterRange[0], retryJitterRange[1]),
|
|
98
|
+
}
|
|
99
|
+
: {
|
|
100
|
+
sendJitterMs: 0,
|
|
101
|
+
typingJitterMs: 0,
|
|
102
|
+
retryJitterMs: 0,
|
|
103
|
+
};
|
|
104
|
+
// Voice note profile (Obscura: audio fingerprint variance)
|
|
105
|
+
const sampleRatePool = voiceNote.sampleRatePool || DEFAULT_SAMPLE_RATE_POOL;
|
|
106
|
+
const voiceNoteProfile = {
|
|
107
|
+
waveformSeed: enabled ? rng.range(0, 2147483647) : 0,
|
|
108
|
+
durationJitterMs: enabled && voiceNote.randomizeWaveform !== false
|
|
109
|
+
? rng.range(0, voiceNote.durationJitterMs || DEFAULT_DURATION_JITTER_MS)
|
|
110
|
+
: 0,
|
|
111
|
+
sampleRate: enabled ? rng.pick(sampleRatePool) : sampleRatePool[0],
|
|
112
|
+
};
|
|
113
|
+
// Connection state profile (Obscura: battery/network state variance)
|
|
114
|
+
const idleTimeoutRange = connectionState.idleTimeoutJitterMs || DEFAULT_IDLE_TIMEOUT_JITTER_MS;
|
|
115
|
+
const keepaliveRange = connectionState.keepaliveJitterMs || DEFAULT_KEEPALIVE_JITTER_MS;
|
|
116
|
+
const batteryLevelPool = connectionState.batteryLevelPool || DEFAULT_BATTERY_LEVEL_POOL;
|
|
117
|
+
const connectionStateProfile = {
|
|
118
|
+
idleTimeoutMs: enabled
|
|
119
|
+
? rng.range(idleTimeoutRange[0], idleTimeoutRange[1])
|
|
120
|
+
: 30000,
|
|
121
|
+
keepaliveMs: enabled
|
|
122
|
+
? rng.range(keepaliveRange[0], keepaliveRange[1])
|
|
123
|
+
: 20000,
|
|
124
|
+
batteryLevel: enabled && connectionState.randomizeBattery !== false
|
|
125
|
+
? rng.pick(batteryLevelPool)
|
|
126
|
+
: 100,
|
|
127
|
+
batteryCharging: enabled ? rng.boolean(0.3) : false, // 30% charging probability
|
|
128
|
+
};
|
|
129
|
+
// Protocol version (Obscura: TLS fingerprint variance → WA protocol variance)
|
|
130
|
+
const versionPool = protocolVersion.versionPool || DEFAULT_PROTOCOL_VERSION_POOL;
|
|
131
|
+
const protocolVersionStr = enabled && protocolVersion.randomizeSubVersion !== false
|
|
132
|
+
? rng.pick(versionPool)
|
|
133
|
+
: versionPool[0];
|
|
134
|
+
return {
|
|
135
|
+
device,
|
|
136
|
+
networkTiming: networkTimingProfile,
|
|
137
|
+
voiceNote: voiceNoteProfile,
|
|
138
|
+
connectionState: connectionStateProfile,
|
|
139
|
+
protocolVersion: protocolVersionStr,
|
|
140
|
+
sessionId: finalSessionId,
|
|
141
|
+
createdAt: Date.now(),
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Apply session fingerprint to Baileys socket config.
|
|
146
|
+
*
|
|
147
|
+
* Usage:
|
|
148
|
+
* const fingerprint = generateSessionFingerprint({ enabled: true });
|
|
149
|
+
* const sock = makeWASocket(applySessionFingerprint(config, fingerprint));
|
|
150
|
+
*/
|
|
151
|
+
export function applySessionFingerprint(socketConfig, fingerprint) {
|
|
152
|
+
const config = { ...socketConfig };
|
|
153
|
+
// Apply device profile (appVersion, browser tuple)
|
|
154
|
+
config.version = fingerprint.device.appVersion;
|
|
155
|
+
config.browser = [
|
|
156
|
+
fingerprint.device.deviceModel,
|
|
157
|
+
fingerprint.device.osVersion,
|
|
158
|
+
`WhatsApp/${fingerprint.device.appVersion.join('.')}`,
|
|
159
|
+
];
|
|
160
|
+
// Apply connection timeouts if fields exist
|
|
161
|
+
if ('connectTimeoutMs' in config || config.connectTimeoutMs !== undefined) {
|
|
162
|
+
// Add idle timeout jitter to connection config
|
|
163
|
+
config.connectTimeoutMs = fingerprint.connectionState.idleTimeoutMs;
|
|
164
|
+
}
|
|
165
|
+
// Apply keepalive if supported
|
|
166
|
+
if ('keepAliveIntervalMs' in config || config.keepAliveIntervalMs !== undefined) {
|
|
167
|
+
config.keepAliveIntervalMs = fingerprint.connectionState.keepaliveMs;
|
|
168
|
+
}
|
|
169
|
+
// Store fingerprint for runtime access (helpers can read this)
|
|
170
|
+
config.__sessionFingerprint = fingerprint;
|
|
171
|
+
return config;
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Get timing jitter for message send (helper for presenceChoreographer/rateLimiter)
|
|
175
|
+
*
|
|
176
|
+
* Usage in beforeSend():
|
|
177
|
+
* const jitter = getMessageSendJitter(fingerprint);
|
|
178
|
+
* await sleep(baseDelay + jitter);
|
|
179
|
+
*/
|
|
180
|
+
export function getMessageSendJitter(fingerprint) {
|
|
181
|
+
// Return a random value within ±50% of the session's base jitter
|
|
182
|
+
// This adds per-message variance while staying within session profile
|
|
183
|
+
const base = fingerprint.networkTiming.sendJitterMs;
|
|
184
|
+
return Math.floor(base * 0.5 + Math.random() * base * 0.5);
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Get typing indicator jitter (helper for presenceChoreographer)
|
|
188
|
+
*/
|
|
189
|
+
export function getTypingJitter(fingerprint) {
|
|
190
|
+
const base = fingerprint.networkTiming.typingJitterMs;
|
|
191
|
+
return Math.floor(base * 0.5 + Math.random() * base * 0.5);
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Get retry backoff jitter (helper for reconnectThrottle)
|
|
195
|
+
*/
|
|
196
|
+
export function getRetryJitter(fingerprint) {
|
|
197
|
+
const base = fingerprint.networkTiming.retryJitterMs;
|
|
198
|
+
return Math.floor(base * 0.5 + Math.random() * base * 0.5);
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Get voice note metadata (helper for voice message encoding)
|
|
202
|
+
*
|
|
203
|
+
* Returns suggested sample rate and duration adjustment based on session fingerprint.
|
|
204
|
+
*/
|
|
205
|
+
export function getVoiceNoteMetadata(fingerprint) {
|
|
206
|
+
return {
|
|
207
|
+
sampleRate: fingerprint.voiceNote.sampleRate,
|
|
208
|
+
durationJitterMs: fingerprint.voiceNote.durationJitterMs,
|
|
209
|
+
waveformSeed: fingerprint.voiceNote.waveformSeed,
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Get battery state (helper for presence/connection state signals)
|
|
214
|
+
*/
|
|
215
|
+
export function getBatteryState(fingerprint) {
|
|
216
|
+
return {
|
|
217
|
+
level: fingerprint.connectionState.batteryLevel,
|
|
218
|
+
charging: fingerprint.connectionState.batteryCharging,
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* Create a session fingerprint preset (Obscura-inspired feature flag pattern)
|
|
223
|
+
*/
|
|
224
|
+
export function createStealthFingerprint(sessionId) {
|
|
225
|
+
return generateSessionFingerprint({
|
|
226
|
+
enabled: true,
|
|
227
|
+
deviceProfile: {
|
|
228
|
+
randomizeAppVersion: true,
|
|
229
|
+
randomizeOsVersion: true,
|
|
230
|
+
randomizeDeviceModel: true,
|
|
231
|
+
},
|
|
232
|
+
networkTiming: {
|
|
233
|
+
sendJitterMs: [100, 500],
|
|
234
|
+
typingJitterMs: [50, 200],
|
|
235
|
+
retryJitterMs: [200, 800],
|
|
236
|
+
},
|
|
237
|
+
voiceNote: {
|
|
238
|
+
randomizeWaveform: true,
|
|
239
|
+
durationJitterMs: 300,
|
|
240
|
+
},
|
|
241
|
+
connectionState: {
|
|
242
|
+
randomizeBattery: true,
|
|
243
|
+
},
|
|
244
|
+
protocolVersion: {
|
|
245
|
+
randomizeSubVersion: true,
|
|
246
|
+
},
|
|
247
|
+
}, sessionId);
|
|
248
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stealth Connect — Gradual presence ramp to reduce ban signals
|
|
3
|
+
*
|
|
4
|
+
* Inspired by GOWA's --presence-on-connect=unavailable flag. Bots that
|
|
5
|
+
* instantly snap online and start blasting messages look suspicious.
|
|
6
|
+
* This helper connects without advertising "online" presence, then
|
|
7
|
+
* gradually ramps to "available" after a randomized delay.
|
|
8
|
+
*
|
|
9
|
+
* Usage:
|
|
10
|
+
* const config = getStealthSocketConfig({ os: 'My Custom App' });
|
|
11
|
+
* const sock = makeWASocket({ ...config, ...otherOptions });
|
|
12
|
+
* await rampPresenceAfterConnect(sock, { minDelayMs: 45000, maxDelayMs: 120000 });
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* Returns socket configuration for stealth connect.
|
|
16
|
+
* Sets markOnlineOnConnect=false and provides sensible browser defaults.
|
|
17
|
+
*
|
|
18
|
+
* @param opts.os - Optional custom OS name for device fingerprint (default: 'Baileys')
|
|
19
|
+
* @returns Partial socket config to merge into makeWASocket options
|
|
20
|
+
*/
|
|
21
|
+
export declare function getStealthSocketConfig(opts?: {
|
|
22
|
+
os?: string;
|
|
23
|
+
}): any;
|
|
24
|
+
/**
|
|
25
|
+
* Ramps presence from unavailable to available after a randomized delay.
|
|
26
|
+
* Call this after socket connects. Returns a promise that resolves once
|
|
27
|
+
* presence is set. Can be awaited or fire-and-forget.
|
|
28
|
+
*
|
|
29
|
+
* @param sock - Baileys socket instance (must have sendPresenceUpdate method)
|
|
30
|
+
* @param opts.minDelayMs - Minimum delay in ms (default: 30000 = 30s)
|
|
31
|
+
* @param opts.maxDelayMs - Maximum delay in ms (default: 90000 = 90s)
|
|
32
|
+
* @param opts.targetState - Presence state to set after delay (default: 'available')
|
|
33
|
+
* @returns Promise that resolves when presence is updated
|
|
34
|
+
*/
|
|
35
|
+
export declare function rampPresenceAfterConnect(sock: any, opts?: {
|
|
36
|
+
minDelayMs?: number;
|
|
37
|
+
maxDelayMs?: number;
|
|
38
|
+
targetState?: 'available' | 'unavailable' | 'composing' | 'recording' | 'paused';
|
|
39
|
+
}): Promise<void>;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stealth Connect — Gradual presence ramp to reduce ban signals
|
|
3
|
+
*
|
|
4
|
+
* Inspired by GOWA's --presence-on-connect=unavailable flag. Bots that
|
|
5
|
+
* instantly snap online and start blasting messages look suspicious.
|
|
6
|
+
* This helper connects without advertising "online" presence, then
|
|
7
|
+
* gradually ramps to "available" after a randomized delay.
|
|
8
|
+
*
|
|
9
|
+
* Usage:
|
|
10
|
+
* const config = getStealthSocketConfig({ os: 'My Custom App' });
|
|
11
|
+
* const sock = makeWASocket({ ...config, ...otherOptions });
|
|
12
|
+
* await rampPresenceAfterConnect(sock, { minDelayMs: 45000, maxDelayMs: 120000 });
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* Returns socket configuration for stealth connect.
|
|
16
|
+
* Sets markOnlineOnConnect=false and provides sensible browser defaults.
|
|
17
|
+
*
|
|
18
|
+
* @param opts.os - Optional custom OS name for device fingerprint (default: 'Baileys')
|
|
19
|
+
* @returns Partial socket config to merge into makeWASocket options
|
|
20
|
+
*/
|
|
21
|
+
export function getStealthSocketConfig(opts) {
|
|
22
|
+
return {
|
|
23
|
+
markOnlineOnConnect: false,
|
|
24
|
+
browser: ['Ubuntu', 'Chrome', '20.0.04'],
|
|
25
|
+
...(opts?.os && { defaultQueryTimeoutMs: undefined }), // placeholder — actual os field lives in auth
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Ramps presence from unavailable to available after a randomized delay.
|
|
30
|
+
* Call this after socket connects. Returns a promise that resolves once
|
|
31
|
+
* presence is set. Can be awaited or fire-and-forget.
|
|
32
|
+
*
|
|
33
|
+
* @param sock - Baileys socket instance (must have sendPresenceUpdate method)
|
|
34
|
+
* @param opts.minDelayMs - Minimum delay in ms (default: 30000 = 30s)
|
|
35
|
+
* @param opts.maxDelayMs - Maximum delay in ms (default: 90000 = 90s)
|
|
36
|
+
* @param opts.targetState - Presence state to set after delay (default: 'available')
|
|
37
|
+
* @returns Promise that resolves when presence is updated
|
|
38
|
+
*/
|
|
39
|
+
export async function rampPresenceAfterConnect(sock, opts) {
|
|
40
|
+
const minDelayMs = opts?.minDelayMs ?? 30000;
|
|
41
|
+
const maxDelayMs = opts?.maxDelayMs ?? 90000;
|
|
42
|
+
const targetState = opts?.targetState ?? 'available';
|
|
43
|
+
// Random delay in [minDelayMs, maxDelayMs]
|
|
44
|
+
const delayMs = Math.floor(Math.random() * (maxDelayMs - minDelayMs + 1)) + minDelayMs;
|
|
45
|
+
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
46
|
+
// Send presence update (undefined = broadcast to all conversations)
|
|
47
|
+
await sock.sendPresenceUpdate(targetState, undefined);
|
|
48
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "baileys-antiban",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.8.0",
|
|
4
4
|
"description": "Anti-ban middleware for Baileys WhatsApp bots. Rate limiting, warmup, health monitor, LID resolver, disconnect classifier. Free Whapi.Cloud alternative.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|