baileys-antiban 3.8.1 → 3.8.3

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,14 @@ 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.3] - 2026-05-09
9
+
10
+ ### Fixed
11
+ - **Compiled dist now ships with rc10 fixes.** v3.8.2 published stale dist to npm — the sendLock serialization, `fetchMessageHistory` defensive guard, and `retryTracker` rc10 path were in source but not in the published package. v3.8.3 corrects this.
12
+ - **`sendMessage` concurrent send serialization.** Wraps each send in a `sendLock` promise chain so `beforeSend`→`afterSend` accounting is serialized. Without this, all concurrent callers read the same committed rate-limiter state before any `afterSend` records, allowing burst sends to bypass per-minute limits.
13
+ - **`messageRecovery`: defensive `fetchMessageHistory` guard.** Baileys v7 may change this signature. Now catches any error, logs a one-time warning, and skips recovery for that reconnect rather than crashing the handler.
14
+ - **`retryTracker`: rc10 `update.update` path.** Baileys rc10 wraps error info in `update.update` rather than `update.error` in some cases. Classifier now checks all three forms.
15
+
8
16
  ## [3.8.1] - 2026-04-28
9
17
 
10
18
  ### Fixed
@@ -152,12 +152,20 @@ export function messageRecovery(sock, config) {
152
152
  }
153
153
  for (const [jid, lastSeenEntry] of chatsToRecover) {
154
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
- });
155
+ // Fetch messages newer than lastSeen timestamp.
156
+ // Baileys v7 may have changed this signature — fall back gracefully on any error.
157
+ let messages;
158
+ try {
159
+ const result = await sock.fetchMessageHistory(jid, 50, { before: undefined });
160
+ messages = Array.isArray(result) ? result : [];
161
+ }
162
+ catch {
163
+ if (!loggedFetchWarning) {
164
+ logger.warn?.(`[messageRecovery] sock.fetchMessageHistory failed — signature may have changed in this Baileys version. Recovery skipped for this reconnect.`);
165
+ loggedFetchWarning = true;
166
+ }
167
+ continue;
168
+ }
161
169
  if (!messages || !Array.isArray(messages))
162
170
  continue;
163
171
  // Filter to messages newer than lastSeen
@@ -54,6 +54,7 @@ export declare class RetryReasonTracker {
54
54
  id?: string;
55
55
  };
56
56
  status?: number;
57
+ update?: any;
57
58
  error?: any;
58
59
  }): void;
59
60
  /**
@@ -61,10 +61,11 @@ export class RetryReasonTracker {
61
61
  const msgId = update.key?.id;
62
62
  if (!msgId)
63
63
  return;
64
- // Only track error statuses
64
+ // Only track error statuses (status 0 = error in Baileys WAMessageStatus)
65
65
  if (update.status !== 0 && !update.error)
66
66
  return;
67
- const reason = this.classify(update.error || update);
67
+ // rc10 may surface error info in update.update (wrapped message); check all forms
68
+ const reason = this.classify(update.error || update.update || update);
68
69
  this.recordRetry(msgId, reason);
69
70
  }
70
71
  /**
package/dist/wrapper.js CHANGED
@@ -69,7 +69,9 @@ export function wrapSocket(sock, config, warmUpState, wrapOptions) {
69
69
  for (const update of updates) {
70
70
  // 463 error detection
71
71
  if (update?.update?.messageStubParameters) {
72
- const params = update.update.messageStubParameters;
72
+ const params = Array.isArray(update.update.messageStubParameters)
73
+ ? update.update.messageStubParameters
74
+ : [];
73
75
  if (params.includes(463) || params.includes('463')) {
74
76
  antiban.timelock.record463Error();
75
77
  }
@@ -197,6 +199,10 @@ export function wrapSocket(sock, config, warmUpState, wrapOptions) {
197
199
  }
198
200
  // Create proxy that intercepts sendMessage
199
201
  const originalSendMessage = sock.sendMessage.bind(sock);
202
+ // Serializes beforeSend→afterSend so rate limiter accounting is accurate
203
+ // under concurrent sends. Without this, all concurrent callers read the same
204
+ // committed state before any afterSend records, bypassing per-minute limits.
205
+ let sendLock = Promise.resolve();
200
206
  const wrappedSendMessage = async (jid, content, options) => {
201
207
  /**
202
208
  * LID/PN Canonicalization — Normalize JID to canonical form
@@ -211,29 +217,36 @@ export function wrapSocket(sock, config, warmUpState, wrapOptions) {
211
217
  const canonicalJid = antiban.jidCanonicalizer?.canonicalizeTarget(jid) || jid;
212
218
  // Extract text content for rate limiter analysis
213
219
  const text = content?.text || content?.caption || content?.image?.caption || '';
214
- const decision = await antiban.beforeSend(canonicalJid, text);
215
- if (!decision.allowed) {
216
- throw new Error(`[baileys-antiban] Message blocked: ${decision.reason}`);
217
- }
218
- // Apply delay
219
- if (decision.delayMs > 0) {
220
- await new Promise(resolve => setTimeout(resolve, decision.delayMs));
221
- }
222
- // Send message (using canonical JID)
223
- try {
224
- const result = await originalSendMessage(canonicalJid, content, options);
225
- antiban.afterSend(canonicalJid, text);
226
- antiban.timelock.registerKnownChat(canonicalJid);
227
- // Clear retry tracking on successful send
228
- if (result?.key?.id) {
229
- antiban.retryTracker.clear(result.key.id);
220
+ // Chain this send onto the previous — each waits for the prior send's
221
+ // afterSend to commit before running its own beforeSend check.
222
+ const sendResult = sendLock.then(async () => {
223
+ const decision = await antiban.beforeSend(canonicalJid, text);
224
+ if (!decision.allowed) {
225
+ throw new Error(`[baileys-antiban] Message blocked: ${decision.reason}`);
230
226
  }
231
- return result;
232
- }
233
- catch (error) {
234
- antiban.afterSendFailed(error instanceof Error ? error.message : String(error));
235
- throw error;
236
- }
227
+ // Apply delay
228
+ if (decision.delayMs > 0) {
229
+ await new Promise(resolve => setTimeout(resolve, decision.delayMs));
230
+ }
231
+ // Send message (using canonical JID)
232
+ try {
233
+ const result = await originalSendMessage(canonicalJid, content, options);
234
+ antiban.afterSend(canonicalJid, text);
235
+ antiban.timelock.registerKnownChat(canonicalJid);
236
+ // Clear retry tracking on successful send
237
+ if (result?.key?.id) {
238
+ antiban.retryTracker.clear(result.key.id);
239
+ }
240
+ return result;
241
+ }
242
+ catch (error) {
243
+ antiban.afterSendFailed(error instanceof Error ? error.message : String(error));
244
+ throw error;
245
+ }
246
+ });
247
+ // Advance the lock regardless of success/failure so the chain never stalls
248
+ sendLock = sendResult.then(() => { }, () => { });
249
+ return sendResult;
237
250
  };
238
251
  // Return enhanced socket
239
252
  const wrapped = Object.create(sock);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "baileys-antiban",
3
- "version": "3.8.1",
3
+ "version": "3.8.3",
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",
@@ -90,6 +90,7 @@
90
90
  "devDependencies": {
91
91
  "@types/jest": "^29.5.14",
92
92
  "@types/node": "^20.0.0",
93
+ "baileys": "github:WhiskeySockets/Baileys#dfad98f815feb771cc561f32707a00c6e085b1f1",
93
94
  "jest": "^29.7.0",
94
95
  "ts-jest": "^29.4.9",
95
96
  "tsx": "^4.21.0",