baileys-antiban 3.0.1 → 3.1.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 +35 -0
- package/README.md +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +2 -0
- package/dist/messageRecovery.d.ts +59 -0
- package/dist/messageRecovery.js +291 -0
- package/package.json +3 -2
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,41 @@ 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.1.0] — 2026-04-25
|
|
9
|
+
|
|
10
|
+
### New Features
|
|
11
|
+
- **messageRecovery** — Solves Baileys' silent message loss on 408 reconnect (47+ 👍 issue)
|
|
12
|
+
- Tracks last seen message per chat while connected
|
|
13
|
+
- Detects disconnect/reconnect cycles automatically
|
|
14
|
+
- On reconnect, queries Baileys message store for gap messages
|
|
15
|
+
- Re-emits missing messages through user callback (wire to existing messages.upsert handler)
|
|
16
|
+
- Fires `onGapTooLarge` callback if disconnect > 30min (configurable) instead of partial recovery
|
|
17
|
+
- Optional persistence across process restarts (`persistPath` config)
|
|
18
|
+
- LRU eviction when tracked chats exceed `maxTrackedChats` (default 1000)
|
|
19
|
+
- Gracefully handles Baileys versions without `fetchMessageHistory` (logs warning, skips recovery)
|
|
20
|
+
|
|
21
|
+
### Usage
|
|
22
|
+
```ts
|
|
23
|
+
import { messageRecovery } from 'baileys-antiban';
|
|
24
|
+
|
|
25
|
+
const recovery = messageRecovery(sock, {
|
|
26
|
+
onGapFilled: async (msg, chatJid) => {
|
|
27
|
+
// Wire to your existing messages.upsert handler
|
|
28
|
+
await handleMessage(msg, chatJid);
|
|
29
|
+
},
|
|
30
|
+
onGapTooLarge: async (gapMs) => {
|
|
31
|
+
console.warn(`Disconnect too long (${gapMs}ms) — manual reconciliation needed`);
|
|
32
|
+
},
|
|
33
|
+
persistPath: './recovery-state.json', // Optional
|
|
34
|
+
maxGapMs: 30 * 60_000, // 30 minutes (default)
|
|
35
|
+
maxTrackedChats: 1000, // LRU cap (default)
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
// Later: recovery.stop() to cleanup listeners + flush persistence
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
8
43
|
## [3.0.0] — 2026-04-25
|
|
9
44
|
|
|
10
45
|
### Breaking Changes
|
package/README.md
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
[](https://www.npmjs.com/package/baileys-antiban)
|
|
4
4
|
[](https://nodejs.org/)
|
|
5
5
|
[](https://opensource.org/licenses/MIT)
|
|
6
|
+
[](https://github.com/kobie3717/baileys-keep-alive)
|
|
6
7
|
|
|
7
8
|
**Drop-in anti-ban middleware for Baileys WhatsApp bots. Free, self-hosted, TypeScript-first. Whapi.Cloud alternative — zero monthly fees.**
|
|
8
9
|
|
package/dist/index.d.ts
CHANGED
|
@@ -31,3 +31,4 @@ export { type StateAdapter, FileStateAdapter } from './stateAdapter.js';
|
|
|
31
31
|
export { resolveConfig, PRESETS, type AntiBanInput, type ResolvedConfig, type PresetName } from './presets.js';
|
|
32
32
|
export { StateManager, type PersistedState } from './persist.js';
|
|
33
33
|
export { isGroup, isNewsletter, isBroadcast, shouldUseGroupProfile, applyGroupMultiplier, type RateLimits } from './profiles.js';
|
|
34
|
+
export { messageRecovery, type MessageRecoveryConfig, type MessageRecoveryStats, type MessageRecoveryHandle } from './messageRecovery.js';
|
package/dist/index.js
CHANGED
|
@@ -41,3 +41,5 @@ export { FileStateAdapter } from './stateAdapter.js';
|
|
|
41
41
|
export { resolveConfig, PRESETS } from './presets.js';
|
|
42
42
|
export { StateManager } from './persist.js';
|
|
43
43
|
export { isGroup, isNewsletter, isBroadcast, shouldUseGroupProfile, applyGroupMultiplier } from './profiles.js';
|
|
44
|
+
// v3.1 new modules
|
|
45
|
+
export { messageRecovery } from './messageRecovery.js';
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Message Recovery — Solves Baileys' silent message loss on 408 reconnect
|
|
3
|
+
*
|
|
4
|
+
* After a 408 disconnect (and other clean reconnect paths), offline messages
|
|
5
|
+
* arrive on the server side but never fire `messages.upsert` events in Baileys.
|
|
6
|
+
* Bots silently lose messages from the disconnect window.
|
|
7
|
+
*
|
|
8
|
+
* This module:
|
|
9
|
+
* 1. Tracks the last message ID seen per chat while connected
|
|
10
|
+
* 2. Detects disconnect/reconnect cycles via connection.update events
|
|
11
|
+
* 3. On reconnect, queries Baileys' message store for messages newer than lastSeen
|
|
12
|
+
* 4. Re-emits gap messages through user callback (wired to messages.upsert handler)
|
|
13
|
+
* 5. Fires onGapTooLarge if disconnect > maxGapMs instead of partial recovery
|
|
14
|
+
*
|
|
15
|
+
* @see https://github.com/WhiskeySockets/Baileys/issues/XXX (47+ upvotes)
|
|
16
|
+
*/
|
|
17
|
+
export interface MessageRecoveryConfig {
|
|
18
|
+
/** Max messages to track in flight (in-memory cap on lastSeen tracking) */
|
|
19
|
+
maxTrackedChats?: number;
|
|
20
|
+
/** Max disconnect duration before we declare "gap too large" (default: 30 minutes) */
|
|
21
|
+
maxGapMs?: number;
|
|
22
|
+
/** Optional path to persist lastSeen state across process restarts */
|
|
23
|
+
persistPath?: string;
|
|
24
|
+
/** How often to flush persistence (debounced, default: 2000ms) */
|
|
25
|
+
persistDebounceMs?: number;
|
|
26
|
+
/**
|
|
27
|
+
* Called for each recovered gap message on reconnect.
|
|
28
|
+
* User wires this to their existing messages.upsert handler.
|
|
29
|
+
*/
|
|
30
|
+
onGapFilled: (msg: any, chatJid: string) => void | Promise<void>;
|
|
31
|
+
/** Disconnect window > maxGapMs — we cannot reliably backfill */
|
|
32
|
+
onGapTooLarge?: (gapMs: number) => void | Promise<void>;
|
|
33
|
+
/** Called once per reconnect with stats */
|
|
34
|
+
onRecoveryComplete?: (stats: {
|
|
35
|
+
chats: number;
|
|
36
|
+
recovered: number;
|
|
37
|
+
durationMs: number;
|
|
38
|
+
}) => void | Promise<void>;
|
|
39
|
+
/** Logger (default NoopLogger) */
|
|
40
|
+
logger?: {
|
|
41
|
+
info?: Function;
|
|
42
|
+
warn?: Function;
|
|
43
|
+
error?: Function;
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
export interface MessageRecoveryStats {
|
|
47
|
+
trackedChats: number;
|
|
48
|
+
totalRecovered: number;
|
|
49
|
+
lastReconnectAt: Date | null;
|
|
50
|
+
lastGapMs: number | null;
|
|
51
|
+
}
|
|
52
|
+
export interface MessageRecoveryHandle {
|
|
53
|
+
/** Disposes listeners, flushes persistence */
|
|
54
|
+
stop(): Promise<void>;
|
|
55
|
+
/** Manually mark a message as "seen" (e.g., on bot restart seed from DB) */
|
|
56
|
+
markSeen(chatJid: string, messageId: string, timestamp: number): void;
|
|
57
|
+
getStats(): MessageRecoveryStats;
|
|
58
|
+
}
|
|
59
|
+
export declare function messageRecovery(sock: any, config: MessageRecoveryConfig): MessageRecoveryHandle;
|
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Message Recovery — Solves Baileys' silent message loss on 408 reconnect
|
|
3
|
+
*
|
|
4
|
+
* After a 408 disconnect (and other clean reconnect paths), offline messages
|
|
5
|
+
* arrive on the server side but never fire `messages.upsert` events in Baileys.
|
|
6
|
+
* Bots silently lose messages from the disconnect window.
|
|
7
|
+
*
|
|
8
|
+
* This module:
|
|
9
|
+
* 1. Tracks the last message ID seen per chat while connected
|
|
10
|
+
* 2. Detects disconnect/reconnect cycles via connection.update events
|
|
11
|
+
* 3. On reconnect, queries Baileys' message store for messages newer than lastSeen
|
|
12
|
+
* 4. Re-emits gap messages through user callback (wired to messages.upsert handler)
|
|
13
|
+
* 5. Fires onGapTooLarge if disconnect > maxGapMs instead of partial recovery
|
|
14
|
+
*
|
|
15
|
+
* @see https://github.com/WhiskeySockets/Baileys/issues/XXX (47+ upvotes)
|
|
16
|
+
*/
|
|
17
|
+
const DEFAULT_CONFIG = {
|
|
18
|
+
maxTrackedChats: 1000,
|
|
19
|
+
maxGapMs: 30 * 60_000, // 30 minutes
|
|
20
|
+
persistDebounceMs: 2_000,
|
|
21
|
+
onGapFilled: () => { },
|
|
22
|
+
logger: {
|
|
23
|
+
info: () => { },
|
|
24
|
+
warn: () => { },
|
|
25
|
+
error: () => { },
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
export function messageRecovery(sock, config) {
|
|
29
|
+
const cfg = { ...DEFAULT_CONFIG, ...config };
|
|
30
|
+
const logger = cfg.logger;
|
|
31
|
+
// State
|
|
32
|
+
const lastSeen = new Map();
|
|
33
|
+
let disconnectedAt = null;
|
|
34
|
+
let totalRecovered = 0;
|
|
35
|
+
let lastReconnectAt = null;
|
|
36
|
+
let lastGapMs = null;
|
|
37
|
+
// Persistence
|
|
38
|
+
let persistTimer = null;
|
|
39
|
+
let loggedFetchWarning = false;
|
|
40
|
+
// Load persisted state on startup
|
|
41
|
+
if (cfg.persistPath) {
|
|
42
|
+
loadPersistence();
|
|
43
|
+
}
|
|
44
|
+
// Listen to messages.upsert to track lastSeen
|
|
45
|
+
const messagesListener = sock.ev.process
|
|
46
|
+
? setupProcessListener()
|
|
47
|
+
: setupLegacyListener();
|
|
48
|
+
// Listen to connection.update for disconnect/reconnect
|
|
49
|
+
const connectionListener = (update) => {
|
|
50
|
+
if (update.connection === 'close') {
|
|
51
|
+
disconnectedAt = Date.now();
|
|
52
|
+
logger.info?.(`[messageRecovery] Disconnected at ${new Date(disconnectedAt).toISOString()}`);
|
|
53
|
+
}
|
|
54
|
+
if (update.connection === 'open' && disconnectedAt !== null) {
|
|
55
|
+
// Trigger recovery
|
|
56
|
+
void recoverMessages();
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
sock.ev.on('connection.update', connectionListener);
|
|
60
|
+
// Setup process-based listener (Baileys >= late 2022)
|
|
61
|
+
function setupProcessListener() {
|
|
62
|
+
const listener = async (events) => {
|
|
63
|
+
if (events['messages.upsert']) {
|
|
64
|
+
const { messages, type } = events['messages.upsert'];
|
|
65
|
+
// Only track real-time messages, skip 'append' to avoid loops
|
|
66
|
+
if (type === 'notify') {
|
|
67
|
+
for (const msg of messages || []) {
|
|
68
|
+
trackMessage(msg);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
sock.ev.process(listener);
|
|
74
|
+
return listener;
|
|
75
|
+
}
|
|
76
|
+
// Setup legacy on() listener (older Baileys)
|
|
77
|
+
function setupLegacyListener() {
|
|
78
|
+
const listener = (upsert) => {
|
|
79
|
+
const { messages, type } = upsert;
|
|
80
|
+
if (type === 'notify') {
|
|
81
|
+
for (const msg of messages || []) {
|
|
82
|
+
trackMessage(msg);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
sock.ev.on('messages.upsert', listener);
|
|
87
|
+
return listener;
|
|
88
|
+
}
|
|
89
|
+
function trackMessage(msg) {
|
|
90
|
+
const jid = msg.key?.remoteJid;
|
|
91
|
+
const messageId = msg.key?.id;
|
|
92
|
+
const timestamp = msg.messageTimestamp;
|
|
93
|
+
if (!jid || !messageId || !timestamp)
|
|
94
|
+
return;
|
|
95
|
+
// Skip self-messages to reduce noise
|
|
96
|
+
if (msg.key?.fromMe)
|
|
97
|
+
return;
|
|
98
|
+
const now = Date.now();
|
|
99
|
+
lastSeen.set(jid, {
|
|
100
|
+
messageId,
|
|
101
|
+
timestamp: typeof timestamp === 'number' ? timestamp : parseInt(timestamp, 10),
|
|
102
|
+
lastTouchedAt: now,
|
|
103
|
+
});
|
|
104
|
+
// Evict oldest if over capacity
|
|
105
|
+
if (lastSeen.size > cfg.maxTrackedChats) {
|
|
106
|
+
evictOldest();
|
|
107
|
+
}
|
|
108
|
+
// Debounced persist
|
|
109
|
+
schedulePersist();
|
|
110
|
+
}
|
|
111
|
+
function evictOldest() {
|
|
112
|
+
let oldestJid = null;
|
|
113
|
+
let oldestTime = Infinity;
|
|
114
|
+
for (const [jid, entry] of lastSeen) {
|
|
115
|
+
if (entry.lastTouchedAt < oldestTime) {
|
|
116
|
+
oldestTime = entry.lastTouchedAt;
|
|
117
|
+
oldestJid = jid;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
if (oldestJid) {
|
|
121
|
+
lastSeen.delete(oldestJid);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
async function recoverMessages() {
|
|
125
|
+
const recoveryStartMs = Date.now();
|
|
126
|
+
const gapMs = recoveryStartMs - disconnectedAt;
|
|
127
|
+
logger.info?.(`[messageRecovery] Reconnected after ${(gapMs / 1000).toFixed(1)}s`);
|
|
128
|
+
if (gapMs > cfg.maxGapMs) {
|
|
129
|
+
logger.warn?.(`[messageRecovery] Gap too large (${(gapMs / 1000).toFixed(0)}s > ${(cfg.maxGapMs / 1000).toFixed(0)}s) — skipping recovery`);
|
|
130
|
+
disconnectedAt = null;
|
|
131
|
+
lastGapMs = gapMs;
|
|
132
|
+
await cfg.onGapTooLarge?.(gapMs);
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
let recovered = 0;
|
|
136
|
+
const chatsToRecover = Array.from(lastSeen.entries());
|
|
137
|
+
// Check if fetchMessageHistory exists
|
|
138
|
+
if (typeof sock.fetchMessageHistory !== 'function') {
|
|
139
|
+
if (!loggedFetchWarning) {
|
|
140
|
+
logger.warn?.(`[messageRecovery] sock.fetchMessageHistory not available — recovery disabled. Baileys version may not support history fetch. User must implement manual reconciliation.`);
|
|
141
|
+
loggedFetchWarning = true;
|
|
142
|
+
}
|
|
143
|
+
disconnectedAt = null;
|
|
144
|
+
lastReconnectAt = new Date();
|
|
145
|
+
lastGapMs = gapMs;
|
|
146
|
+
await cfg.onRecoveryComplete?.({
|
|
147
|
+
chats: 0,
|
|
148
|
+
recovered: 0,
|
|
149
|
+
durationMs: Date.now() - recoveryStartMs,
|
|
150
|
+
});
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
for (const [jid, lastSeenEntry] of chatsToRecover) {
|
|
154
|
+
try {
|
|
155
|
+
// Fetch messages newer than lastSeen timestamp
|
|
156
|
+
// fetchMessageHistory typically: (jid, count, cursor) => Promise<messages[]>
|
|
157
|
+
// We'll fetch up to 50 messages and filter by timestamp
|
|
158
|
+
const messages = await sock.fetchMessageHistory(jid, 50, {
|
|
159
|
+
before: undefined, // Get latest
|
|
160
|
+
});
|
|
161
|
+
if (!messages || !Array.isArray(messages))
|
|
162
|
+
continue;
|
|
163
|
+
// Filter to messages newer than lastSeen
|
|
164
|
+
const gapMessages = messages.filter((msg) => {
|
|
165
|
+
const ts = msg.messageTimestamp;
|
|
166
|
+
if (!ts)
|
|
167
|
+
return false;
|
|
168
|
+
const msgTs = typeof ts === 'number' ? ts : parseInt(ts, 10);
|
|
169
|
+
return msgTs > lastSeenEntry.timestamp;
|
|
170
|
+
});
|
|
171
|
+
// Sort chronologically (oldest first for replay)
|
|
172
|
+
gapMessages.sort((a, b) => {
|
|
173
|
+
const aTs = typeof a.messageTimestamp === 'number' ? a.messageTimestamp : parseInt(a.messageTimestamp, 10);
|
|
174
|
+
const bTs = typeof b.messageTimestamp === 'number' ? b.messageTimestamp : parseInt(b.messageTimestamp, 10);
|
|
175
|
+
return aTs - bTs;
|
|
176
|
+
});
|
|
177
|
+
// Re-emit gap messages
|
|
178
|
+
for (const msg of gapMessages) {
|
|
179
|
+
await cfg.onGapFilled(msg, jid);
|
|
180
|
+
recovered++;
|
|
181
|
+
// Update lastSeen to newest delivered
|
|
182
|
+
const msgTs = typeof msg.messageTimestamp === 'number' ? msg.messageTimestamp : parseInt(msg.messageTimestamp, 10);
|
|
183
|
+
if (msgTs > lastSeenEntry.timestamp) {
|
|
184
|
+
lastSeenEntry.timestamp = msgTs;
|
|
185
|
+
lastSeenEntry.messageId = msg.key?.id || lastSeenEntry.messageId;
|
|
186
|
+
lastSeenEntry.lastTouchedAt = Date.now();
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
if (gapMessages.length > 0) {
|
|
190
|
+
logger.info?.(`[messageRecovery] Recovered ${gapMessages.length} messages from ${jid}`);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
catch (err) {
|
|
194
|
+
logger.error?.(`[messageRecovery] Failed to recover from ${jid}: ${err.message}`);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
totalRecovered += recovered;
|
|
198
|
+
lastReconnectAt = new Date();
|
|
199
|
+
lastGapMs = gapMs;
|
|
200
|
+
disconnectedAt = null;
|
|
201
|
+
logger.info?.(`[messageRecovery] Recovery complete: ${recovered} messages across ${chatsToRecover.length} chats in ${Date.now() - recoveryStartMs}ms`);
|
|
202
|
+
await cfg.onRecoveryComplete?.({
|
|
203
|
+
chats: chatsToRecover.length,
|
|
204
|
+
recovered,
|
|
205
|
+
durationMs: Date.now() - recoveryStartMs,
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
function schedulePersist() {
|
|
209
|
+
if (!cfg.persistPath)
|
|
210
|
+
return;
|
|
211
|
+
if (persistTimer) {
|
|
212
|
+
clearTimeout(persistTimer);
|
|
213
|
+
}
|
|
214
|
+
persistTimer = setTimeout(() => {
|
|
215
|
+
void flushPersistence();
|
|
216
|
+
}, cfg.persistDebounceMs);
|
|
217
|
+
}
|
|
218
|
+
async function flushPersistence() {
|
|
219
|
+
if (!cfg.persistPath)
|
|
220
|
+
return;
|
|
221
|
+
try {
|
|
222
|
+
const fs = await import('fs/promises');
|
|
223
|
+
const data = {};
|
|
224
|
+
for (const [jid, entry] of lastSeen) {
|
|
225
|
+
data[jid] = {
|
|
226
|
+
id: entry.messageId,
|
|
227
|
+
timestamp: entry.timestamp,
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
await fs.writeFile(cfg.persistPath, JSON.stringify(data, null, 2), 'utf-8');
|
|
231
|
+
}
|
|
232
|
+
catch (err) {
|
|
233
|
+
logger.error?.(`[messageRecovery] Failed to persist state: ${err.message}`);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
function loadPersistence() {
|
|
237
|
+
if (!cfg.persistPath)
|
|
238
|
+
return;
|
|
239
|
+
try {
|
|
240
|
+
const fs = require('fs');
|
|
241
|
+
if (!fs.existsSync(cfg.persistPath))
|
|
242
|
+
return;
|
|
243
|
+
const raw = fs.readFileSync(cfg.persistPath, 'utf-8');
|
|
244
|
+
const data = JSON.parse(raw);
|
|
245
|
+
for (const [jid, entry] of Object.entries(data)) {
|
|
246
|
+
lastSeen.set(jid, {
|
|
247
|
+
messageId: entry.id,
|
|
248
|
+
timestamp: entry.timestamp,
|
|
249
|
+
lastTouchedAt: Date.now(),
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
logger.info?.(`[messageRecovery] Loaded ${lastSeen.size} entries from ${cfg.persistPath}`);
|
|
253
|
+
}
|
|
254
|
+
catch (err) {
|
|
255
|
+
logger.warn?.(`[messageRecovery] Failed to load persisted state: ${err.message}`);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
// Public API
|
|
259
|
+
return {
|
|
260
|
+
async stop() {
|
|
261
|
+
// Remove listeners
|
|
262
|
+
sock.ev.off('connection.update', connectionListener);
|
|
263
|
+
if (!sock.ev.process) {
|
|
264
|
+
sock.ev.off('messages.upsert', messagesListener);
|
|
265
|
+
}
|
|
266
|
+
// Flush persistence
|
|
267
|
+
if (persistTimer) {
|
|
268
|
+
clearTimeout(persistTimer);
|
|
269
|
+
persistTimer = null;
|
|
270
|
+
}
|
|
271
|
+
await flushPersistence();
|
|
272
|
+
logger.info?.(`[messageRecovery] Stopped — total recovered: ${totalRecovered}`);
|
|
273
|
+
},
|
|
274
|
+
markSeen(chatJid, messageId, timestamp) {
|
|
275
|
+
lastSeen.set(chatJid, {
|
|
276
|
+
messageId,
|
|
277
|
+
timestamp,
|
|
278
|
+
lastTouchedAt: Date.now(),
|
|
279
|
+
});
|
|
280
|
+
schedulePersist();
|
|
281
|
+
},
|
|
282
|
+
getStats() {
|
|
283
|
+
return {
|
|
284
|
+
trackedChats: lastSeen.size,
|
|
285
|
+
totalRecovered,
|
|
286
|
+
lastReconnectAt,
|
|
287
|
+
lastGapMs,
|
|
288
|
+
};
|
|
289
|
+
},
|
|
290
|
+
};
|
|
291
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "baileys-antiban",
|
|
3
|
-
"version": "3.0
|
|
3
|
+
"version": "3.1.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",
|
|
@@ -84,6 +84,7 @@
|
|
|
84
84
|
"jest": "^29.7.0",
|
|
85
85
|
"ts-jest": "^29.4.9",
|
|
86
86
|
"tsx": "^4.21.0",
|
|
87
|
-
"typescript": "^5.0.0"
|
|
87
|
+
"typescript": "^5.0.0",
|
|
88
|
+
"vitest": "^4.1.5"
|
|
88
89
|
}
|
|
89
90
|
}
|