@prototypevip/baileys 0.0.9 → 0.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.
Files changed (50) hide show
  1. package/lib/Socket/index.js +12 -135
  2. package/lib/Socket/messages-send.js +27 -44
  3. package/package.json +2 -2
  4. package/lib/AntiBan/antiban.js +0 -922
  5. package/lib/AntiBan/banRecoveryOrchestrator.js +0 -284
  6. package/lib/AntiBan/cli.js +0 -177
  7. package/lib/AntiBan/contactGraph.js +0 -236
  8. package/lib/AntiBan/contentVariator.js +0 -150
  9. package/lib/AntiBan/credsSnapshot.js +0 -121
  10. package/lib/AntiBan/deliveryTracker.js +0 -107
  11. package/lib/AntiBan/deviceFingerprint.js +0 -106
  12. package/lib/AntiBan/fleetEventStore.js +0 -157
  13. package/lib/AntiBan/groupOperationGuard.js +0 -116
  14. package/lib/AntiBan/health.js +0 -207
  15. package/lib/AntiBan/humanEntropy.js +0 -337
  16. package/lib/AntiBan/index.js +0 -77
  17. package/lib/AntiBan/instanceCoordinator.js +0 -175
  18. package/lib/AntiBan/jidCanonicalizer.js +0 -256
  19. package/lib/AntiBan/jidCircuitBreaker.js +0 -186
  20. package/lib/AntiBan/legitimacySignalInjector.js +0 -231
  21. package/lib/AntiBan/lidFirstResolver.js +0 -174
  22. package/lib/AntiBan/lidResolver.js +0 -324
  23. package/lib/AntiBan/messageQueue.js +0 -187
  24. package/lib/AntiBan/messageRecovery.js +0 -283
  25. package/lib/AntiBan/messageTypeRegistry.js +0 -369
  26. package/lib/AntiBan/observability.js +0 -145
  27. package/lib/AntiBan/patch.js +0 -302
  28. package/lib/AntiBan/persist.js +0 -87
  29. package/lib/AntiBan/presenceChoreographer.js +0 -430
  30. package/lib/AntiBan/presets.js +0 -105
  31. package/lib/AntiBan/profiles.js +0 -31
  32. package/lib/AntiBan/proxyRotator.js +0 -306
  33. package/lib/AntiBan/rateLimiter.js +0 -261
  34. package/lib/AntiBan/readReceiptVariance.js +0 -88
  35. package/lib/AntiBan/reconnectThrottle.js +0 -180
  36. package/lib/AntiBan/replyRatio.js +0 -161
  37. package/lib/AntiBan/reputationVoucher.js +0 -373
  38. package/lib/AntiBan/retryReason.js +0 -91
  39. package/lib/AntiBan/retryTracker.js +0 -172
  40. package/lib/AntiBan/scheduler.js +0 -111
  41. package/lib/AntiBan/sessionFingerprint.js +0 -248
  42. package/lib/AntiBan/sessionStability.js +0 -330
  43. package/lib/AntiBan/stateAdapter.js +0 -73
  44. package/lib/AntiBan/stateExport.js +0 -152
  45. package/lib/AntiBan/stealthConnect.js +0 -130
  46. package/lib/AntiBan/timelockGuard.js +0 -181
  47. package/lib/AntiBan/topologyThrottler.js +0 -339
  48. package/lib/AntiBan/warmup.js +0 -140
  49. package/lib/AntiBan/webhooks.js +0 -80
  50. package/lib/AntiBan/wrapper.js +0 -456
@@ -1,73 +0,0 @@
1
- /**
2
- * State Adapter — Interface for persisting anti-ban state to disk/DB
3
- *
4
- * Implement this interface to save/load state between restarts.
5
- * Without persistence, warm-up progress is lost on every restart.
6
- *
7
- * Example implementations:
8
- * - File-based: JSON files in a directory
9
- * - Database: SQLite, PostgreSQL, MongoDB
10
- * - Redis: For distributed systems
11
- *
12
- * Usage:
13
- * const adapter = new FileStateAdapter('./state');
14
- * const antiban = new AntiBan({ stateAdapter: adapter });
15
- * await antiban.loadState(); // On startup
16
- * await antiban.saveState(); // Periodically or on shutdown
17
- */
18
- /**
19
- * Example file-based adapter using JSON files
20
- */
21
- export class FileStateAdapter {
22
- basePath;
23
- constructor(basePath) {
24
- this.basePath = basePath;
25
- }
26
- async save(key, state) {
27
- const fs = await import('fs/promises');
28
- const path = await import('path');
29
- const filePath = path.join(this.basePath, `${key}.json`);
30
- await fs.mkdir(this.basePath, { recursive: true });
31
- await fs.writeFile(filePath, JSON.stringify(state, null, 2), 'utf-8');
32
- }
33
- async load(key) {
34
- const fs = await import('fs/promises');
35
- const path = await import('path');
36
- const filePath = path.join(this.basePath, `${key}.json`);
37
- try {
38
- const data = await fs.readFile(filePath, 'utf-8');
39
- return JSON.parse(data);
40
- }
41
- catch (err) {
42
- if (err.code === 'ENOENT')
43
- return null;
44
- throw err;
45
- }
46
- }
47
- async delete(key) {
48
- const fs = await import('fs/promises');
49
- const path = await import('path');
50
- const filePath = path.join(this.basePath, `${key}.json`);
51
- try {
52
- await fs.unlink(filePath);
53
- }
54
- catch (err) {
55
- if (err.code !== 'ENOENT')
56
- throw err;
57
- }
58
- }
59
- async list() {
60
- const fs = await import('fs/promises');
61
- try {
62
- const files = await fs.readdir(this.basePath);
63
- return files
64
- .filter(f => f.endsWith('.json'))
65
- .map(f => f.replace(/\.json$/, ''));
66
- }
67
- catch (err) {
68
- if (err.code === 'ENOENT')
69
- return [];
70
- throw err;
71
- }
72
- }
73
- }
@@ -1,152 +0,0 @@
1
- /**
2
- * Unified State Export — Single-call export/import of ALL antiban state
3
- *
4
- * Enables Redis failover and cross-instance state migration.
5
- * CRDT-safe rate limit counters using increment-only approach.
6
- *
7
- * Features:
8
- * - Single snapshot of all module states
9
- * - CRDT-safe rate limiter state (never overwrites higher counts)
10
- * - Version tracking for migration
11
- * - Instance tracking for debugging
12
- */
13
- /**
14
- * Export antiban state from individual modules.
15
- * Call with references to active module instances.
16
- */
17
- export function exportAntibanState(modules) {
18
- const snapshot = {
19
- version: 1,
20
- exportedAt: Date.now(),
21
- instanceId: modules.instanceId || `instance-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`,
22
- };
23
- // Export warmup state
24
- if (modules.warmup) {
25
- snapshot.warmup = modules.warmup.exportState();
26
- }
27
- // Export health state
28
- if (modules.health) {
29
- const status = modules.health.getStatus();
30
- snapshot.health = {
31
- riskScore: status.score,
32
- disconnectEvents: [], // Health module doesn't expose raw events, only aggregate stats
33
- };
34
- }
35
- // Export rate limiter state (CRDT-safe)
36
- if (modules.rateLimiter) {
37
- const stats = modules.rateLimiter.getStats();
38
- snapshot.rateLimiter = {
39
- messages: [], // We don't expose raw messages, rely on knownChats + counters
40
- knownChats: Array.from(modules.rateLimiter.getKnownChats()),
41
- identicalCount: {},
42
- burstCount: 0,
43
- lastMessageTime: Date.now(),
44
- currentFactor: modules.rateLimiter.getCurrentFactor(),
45
- sentSinceExport: stats.lastDay, // Use last day count as increment baseline
46
- };
47
- }
48
- // Export circuit breaker states (BUG FIX 2)
49
- if (modules.circuits?.exportState) {
50
- snapshot.circuits = modules.circuits.exportState();
51
- }
52
- // Export timelock guard state
53
- if (modules.timelockGuard) {
54
- const state = modules.timelockGuard.getState();
55
- const knownChats = modules.timelockGuard.getKnownChats ? Array.from(modules.timelockGuard.getKnownChats()) : [];
56
- snapshot.timelockGuard = {
57
- active: state.isActive,
58
- expiresAt: state.expiresAt ? state.expiresAt.getTime() : undefined,
59
- affectedJids: knownChats,
60
- enforcementType: state.enforcementType,
61
- };
62
- }
63
- // Export message registry state
64
- if (modules.messageRegistry) {
65
- snapshot.messageRegistry = modules.messageRegistry.exportState();
66
- }
67
- // Export topology throttler state
68
- if (modules.topologyThrottler) {
69
- snapshot.topologyThrottler = modules.topologyThrottler.exportState();
70
- }
71
- // Export reputation voucher state
72
- if (modules.reputationVoucher) {
73
- snapshot.reputationVoucher = modules.reputationVoucher.exportState();
74
- }
75
- // Export engagement scores
76
- if (modules.engagementScores) {
77
- snapshot.engagementScores = Object.fromEntries(modules.engagementScores.entries());
78
- }
79
- return snapshot;
80
- }
81
- /**
82
- * Import antiban state into modules.
83
- * CRDT-safe for rate limiters (never overwrites higher counts).
84
- */
85
- export function importAntibanState(snapshot, modules) {
86
- // Import warmup state
87
- if (snapshot.warmup && modules.warmup?.importState) {
88
- modules.warmup.importState(snapshot.warmup);
89
- }
90
- // Import health state (reset to clean state, can't import raw events)
91
- if (snapshot.health && modules.health?.reset) {
92
- modules.health.reset();
93
- }
94
- // Import rate limiter state (CRDT-safe)
95
- if (snapshot.rateLimiter && modules.rateLimiter) {
96
- // Restore known chats
97
- if (snapshot.rateLimiter.knownChats) {
98
- modules.rateLimiter.restoreKnownChats(snapshot.rateLimiter.knownChats);
99
- }
100
- // CRDT-safe merge: only restore factor if not already higher
101
- const currentStats = modules.rateLimiter.getStats();
102
- const currentCount = currentStats.lastDay;
103
- const snapshotCount = snapshot.rateLimiter.sentSinceExport;
104
- // If current count is lower, we're likely a fresh instance, so restore factor
105
- if (currentCount < snapshotCount) {
106
- modules.rateLimiter.adaptLimits(snapshot.rateLimiter.currentFactor);
107
- }
108
- // If current count is higher, keep current state (concurrent instance ahead)
109
- }
110
- // Import circuit breaker states (BUG FIX 2)
111
- if (snapshot.circuits && modules.circuits?.importState) {
112
- modules.circuits.importState(snapshot.circuits);
113
- }
114
- // Import timelock guard state
115
- if (snapshot.timelockGuard && modules.timelockGuard) {
116
- if (snapshot.timelockGuard.active && modules.timelockGuard.onTimelockUpdate) {
117
- const expiresAt = snapshot.timelockGuard.expiresAt
118
- ? new Date(snapshot.timelockGuard.expiresAt)
119
- : undefined;
120
- modules.timelockGuard.onTimelockUpdate({
121
- isActive: snapshot.timelockGuard.active,
122
- timeEnforcementEnds: expiresAt,
123
- enforcementType: snapshot.timelockGuard.enforcementType,
124
- });
125
- // Restore known chats
126
- if (snapshot.timelockGuard.affectedJids && modules.timelockGuard.registerKnownChats) {
127
- modules.timelockGuard.registerKnownChats(snapshot.timelockGuard.affectedJids);
128
- }
129
- }
130
- else {
131
- modules.timelockGuard.reset();
132
- }
133
- }
134
- // Import message registry state
135
- if (snapshot.messageRegistry && modules.messageRegistry) {
136
- modules.messageRegistry.importState(snapshot.messageRegistry);
137
- }
138
- // Import topology throttler state
139
- if (snapshot.topologyThrottler && modules.topologyThrottler) {
140
- modules.topologyThrottler.importState(snapshot.topologyThrottler);
141
- }
142
- // Import reputation voucher state
143
- if (snapshot.reputationVoucher && modules.reputationVoucher) {
144
- modules.reputationVoucher.importState(snapshot.reputationVoucher);
145
- }
146
- // Import engagement scores
147
- if (snapshot.engagementScores && modules.engagementScores) {
148
- for (const [jid, score] of Object.entries(snapshot.engagementScores)) {
149
- modules.engagementScores.set(jid, score);
150
- }
151
- }
152
- }
@@ -1,130 +0,0 @@
1
- /**
2
- * Stealth Connect — Reduce ban signal on socket connect + presence ramp
3
- *
4
- * Inspired by GOWA's --presence-on-connect=unavailable flag. Bots that
5
- * snap online immediately and start blasting messages look suspicious to
6
- * WhatsApp's anti-spam classifier. This module ships two helpers:
7
- *
8
- * - `getStealthSocketConfig()` returns a partial Baileys socket config
9
- * that disables `markOnlineOnConnect` and provides a non-default
10
- * browser fingerprint (random pick from a small pool unless overridden).
11
- *
12
- * - `rampPresenceAfterConnect()` waits a randomized delay, then issues
13
- * `sendPresenceUpdate('available')`. Supports `AbortSignal` so the
14
- * caller can cancel if the socket dies before the timer fires.
15
- *
16
- * Usage:
17
- * const config = getStealthSocketConfig({ os: 'MyApp' });
18
- * const sock = makeWASocket({ ...config, auth: state });
19
- *
20
- * const ac = new AbortController();
21
- * sock.ev.on('connection.update', u => {
22
- * if (u.connection === 'close') ac.abort();
23
- * });
24
- * await rampPresenceAfterConnect(sock, { signal: ac.signal });
25
- */
26
- /**
27
- * Pool of realistic browser fingerprints.
28
- *
29
- * Values match formats actually emitted by WhatsApp Web clients in the wild.
30
- * `getStealthSocketConfig()` picks one at random when caller does not supply
31
- * an explicit `browser` or `os` option, so multiple consumers of the library
32
- * do not all advertise an identical fingerprint (which would be trivially
33
- * cluster-able by WhatsApp).
34
- *
35
- * Exported so callers can extend or override the pool if desired.
36
- */
37
- export const STEALTH_BROWSER_POOL = Object.freeze([
38
- ['Mac OS', 'Chrome', '120.0.6099.109'],
39
- ['Mac OS', 'Safari', '17.2.1'],
40
- ['Windows', 'Chrome', '121.0.6167.85'],
41
- ['Windows', 'Firefox', '122.0'],
42
- ['Windows', 'Edge', '120.0.2210.144'],
43
- ['Linux', 'Chrome', '120.0.6099.109'],
44
- ['Linux', 'Firefox', '122.0'],
45
- ['Ubuntu', 'Chrome', '121.0.6167.85'],
46
- ]);
47
- /**
48
- * Returns a partial Baileys socket config tuned for stealth connect.
49
- *
50
- * - `markOnlineOnConnect` set to `false` so the socket joins without
51
- * broadcasting `available` (matches GOWA's `presence-on-connect=unavailable`).
52
- * - `browser` is a randomized realistic tuple from `STEALTH_BROWSER_POOL`
53
- * unless overridden via `opts.browser` or `opts.os`.
54
- *
55
- * Merge the result into your `makeWASocket` options:
56
- *
57
- * const sock = makeWASocket({ ...getStealthSocketConfig(), auth: state });
58
- */
59
- export function getStealthSocketConfig(opts) {
60
- const random = opts?.random ?? Math.random;
61
- let browser;
62
- if (opts?.browser) {
63
- browser = opts.browser;
64
- }
65
- else {
66
- const pick = STEALTH_BROWSER_POOL[Math.floor(random() * STEALTH_BROWSER_POOL.length)];
67
- // pick is guaranteed defined — pool is non-empty and frozen.
68
- const tuple = pick;
69
- if (opts?.os) {
70
- browser = [opts.os, tuple[1], tuple[2]];
71
- }
72
- else {
73
- browser = [tuple[0], tuple[1], tuple[2]];
74
- }
75
- }
76
- return {
77
- markOnlineOnConnect: false,
78
- browser,
79
- };
80
- }
81
- /**
82
- * Custom error thrown when `rampPresenceAfterConnect` is aborted via signal.
83
- * Mirrors DOM `AbortError` semantics so consumers can `instanceof` check.
84
- */
85
- export class AbortError extends Error {
86
- name = 'AbortError';
87
- constructor(message = 'rampPresenceAfterConnect aborted') {
88
- super(message);
89
- }
90
- }
91
- /**
92
- * Waits a randomized delay then calls `sock.sendPresenceUpdate(targetState)`.
93
- *
94
- * Supports `AbortSignal` — abort during the delay window cancels the timer
95
- * and rejects the returned promise with `AbortError`. Aborting after the
96
- * presence update has already been sent is a no-op.
97
- *
98
- * Caller is responsible for invoking abort when the socket disconnects;
99
- * otherwise the post-delay `sendPresenceUpdate` may run against a dead
100
- * socket.
101
- */
102
- export async function rampPresenceAfterConnect(sock, opts) {
103
- const minDelayMs = opts?.minDelayMs ?? 30000;
104
- const maxDelayMs = opts?.maxDelayMs ?? 90000;
105
- const targetState = opts?.targetState ?? 'available';
106
- const random = opts?.random ?? Math.random;
107
- const signal = opts?.signal;
108
- if (signal?.aborted) {
109
- throw new AbortError();
110
- }
111
- const range = Math.max(0, maxDelayMs - minDelayMs);
112
- const delayMs = Math.floor(random() * (range + 1)) + minDelayMs;
113
- await new Promise((resolve, reject) => {
114
- const timer = setTimeout(() => {
115
- if (signal) {
116
- signal.removeEventListener('abort', onAbort);
117
- }
118
- resolve();
119
- }, delayMs);
120
- const onAbort = () => {
121
- clearTimeout(timer);
122
- reject(new AbortError());
123
- };
124
- if (signal) {
125
- signal.addEventListener('abort', onAbort, { once: true });
126
- }
127
- });
128
- // jid undefined => broadcast presence to all conversations
129
- await sock.sendPresenceUpdate(targetState, undefined);
130
- }
@@ -1,181 +0,0 @@
1
- /**
2
- * Timelock Guard — Manages reachout timelock state and routing decisions
3
- *
4
- * When WhatsApp timelocks an account (463 error), this guard:
5
- * - Tracks the timelock state (active, expiry, enforcement type)
6
- * - Blocks messages to NEW contacts (no tctoken / no prior chat)
7
- * - Allows messages to EXISTING contacts (have tctoken / prior chat history)
8
- * - Auto-resumes when the timelock expires
9
- * - Fires callbacks for alerting (Telegram/Discord/webhook)
10
- */
11
- const DEFAULT_CONFIG = {
12
- resumeBufferMs: 10_000,
13
- };
14
- export class TimelockGuard {
15
- config;
16
- state = {
17
- isActive: false,
18
- errorCount: 0,
19
- };
20
- knownChats = new Set();
21
- resumeTimer = null;
22
- timerGeneration = 0; // BUG FIX 4: Track timer validity to prevent race conditions
23
- constructor(config = {}) {
24
- this.config = { ...DEFAULT_CONFIG, ...config };
25
- }
26
- /**
27
- * Update timelock state from Baileys connection.update event
28
- */
29
- onTimelockUpdate(data) {
30
- const wasActive = this.state.isActive;
31
- this.state.isActive = !!data.isActive;
32
- this.state.enforcementType = data.enforcementType;
33
- this.state.expiresAt = data.timeEnforcementEnds;
34
- if (this.state.isActive && !wasActive) {
35
- this.state.detectedAt = new Date();
36
- this.state.errorCount = 0;
37
- this.config.onTimelockDetected?.(this.getState());
38
- this.scheduleResume();
39
- }
40
- else if (this.state.isActive && wasActive) {
41
- // Already locked but expiry updated — reschedule timer with new expiry
42
- this.scheduleResume();
43
- }
44
- if (!this.state.isActive && wasActive) {
45
- this.clearResumeTimer();
46
- this.config.onTimelockLifted?.(this.getState());
47
- }
48
- }
49
- /**
50
- * Record a 463 error from a failed send
51
- */
52
- record463Error() {
53
- this.state.errorCount++;
54
- if (!this.state.isActive) {
55
- // First 463 before MEX query returns — assume locked for 60s
56
- this.state.isActive = true;
57
- this.state.detectedAt = new Date();
58
- this.state.expiresAt = new Date(Date.now() + 60_000);
59
- this.config.onTimelockDetected?.(this.getState());
60
- this.scheduleResume();
61
- }
62
- }
63
- /**
64
- * Register a JID as a known/existing chat (has tctoken / prior history)
65
- */
66
- registerKnownChat(jid) {
67
- this.knownChats.add(jid);
68
- }
69
- /**
70
- * Register multiple known chats at once (e.g. from chat list on connect)
71
- */
72
- registerKnownChats(jids) {
73
- for (const jid of jids) {
74
- this.knownChats.add(jid);
75
- }
76
- }
77
- /**
78
- * Check if a message to this recipient should be allowed
79
- */
80
- canSend(jid) {
81
- if (!this.state.isActive) {
82
- return { allowed: true };
83
- }
84
- // Check if expiry has passed (with buffer)
85
- if (this.state.expiresAt) {
86
- const expiryWithBuffer = this.state.expiresAt.getTime() + this.config.resumeBufferMs;
87
- if (Date.now() >= expiryWithBuffer) {
88
- this.lift();
89
- return { allowed: true };
90
- }
91
- }
92
- // Groups always flow (tctokens not required for group msgs)
93
- if (jid.endsWith('@g.us') || jid.endsWith('@newsletter')) {
94
- return { allowed: true };
95
- }
96
- // Known chats (have tctoken) can proceed
97
- if (this.knownChats.has(jid)) {
98
- return { allowed: true };
99
- }
100
- // New contact while timelocked — block
101
- const expiresIn = this.state.expiresAt
102
- ? Math.max(0, this.state.expiresAt.getTime() - Date.now())
103
- : 60_000;
104
- return {
105
- allowed: false,
106
- reason: `Reachout timelocked (${this.state.enforcementType || 'unknown'}). `
107
- + `New contacts blocked. Expires in ${Math.ceil(expiresIn / 1000)}s.`,
108
- };
109
- }
110
- /**
111
- * Get current timelock state
112
- */
113
- getState() {
114
- return { ...this.state };
115
- }
116
- /**
117
- * Check if currently timelocked
118
- */
119
- isTimelocked() {
120
- if (!this.state.isActive)
121
- return false;
122
- // Auto-lift if expired
123
- if (this.state.expiresAt) {
124
- const expiryWithBuffer = this.state.expiresAt.getTime() + this.config.resumeBufferMs;
125
- if (Date.now() >= expiryWithBuffer) {
126
- this.lift();
127
- return false;
128
- }
129
- }
130
- return true;
131
- }
132
- /**
133
- * Get the set of known chat JIDs
134
- */
135
- getKnownChats() {
136
- return new Set(this.knownChats);
137
- }
138
- /**
139
- * Manually lift the timelock
140
- */
141
- lift() {
142
- if (this.state.isActive) {
143
- this.state.isActive = false;
144
- this.clearResumeTimer();
145
- this.config.onTimelockLifted?.(this.getState());
146
- }
147
- }
148
- /**
149
- * Reset all state
150
- */
151
- reset() {
152
- this.state = { isActive: false, errorCount: 0 };
153
- this.knownChats.clear();
154
- this.clearResumeTimer();
155
- }
156
- scheduleResume() {
157
- this.clearResumeTimer();
158
- if (this.state.expiresAt) {
159
- const delay = this.state.expiresAt.getTime() - Date.now() + this.config.resumeBufferMs;
160
- if (delay > 0) {
161
- // BUG FIX 4: Increment generation to invalidate any pending timers
162
- this.timerGeneration++;
163
- const currentGeneration = this.timerGeneration;
164
- this.resumeTimer = setTimeout(() => {
165
- // Only lift if this timer is still valid (not superseded by a newer scheduleResume call)
166
- if (currentGeneration === this.timerGeneration) {
167
- this.lift();
168
- }
169
- }, delay);
170
- }
171
- }
172
- }
173
- clearResumeTimer() {
174
- if (this.resumeTimer) {
175
- clearTimeout(this.resumeTimer);
176
- this.resumeTimer = null;
177
- // Increment generation to invalidate any pending timer callbacks
178
- this.timerGeneration++;
179
- }
180
- }
181
- }