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 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
  */
@@ -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
  */
@@ -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
  */
@@ -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.6.1",
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",