baileys-antiban 4.7.0 โ†’ 4.9.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/dist/antiban.d.ts CHANGED
@@ -21,6 +21,7 @@ import { ReplyRatioGuard, type ReplyRatioConfig, type ReplyRatioStats } from './
21
21
  import { ContactGraphWarmer, type ContactGraphConfig, type ContactGraphStats } from './contactGraph.js';
22
22
  import { PresenceChoreographer, type PresenceChoreographerConfig, type PresenceChoreographerStats } from './presenceChoreographer.js';
23
23
  import { RetryReasonTracker, type RetryTrackerConfig, type RetryStats } from './retryTracker.js';
24
+ import { TopologyThrottler, type TopologyThrottlerConfig } from './topologyThrottler.js';
24
25
  import { PostReconnectThrottle, type ReconnectThrottleConfig, type ReconnectThrottleStats } from './reconnectThrottle.js';
25
26
  import { LidResolver, type LidResolverConfig, type LidResolverStats } from './lidResolver.js';
26
27
  import { JidCanonicalizer, type JidCanonicalizerConfig, type JidCanonicalizerStats } from './jidCanonicalizer.js';
@@ -29,6 +30,8 @@ import { BanRecoveryOrchestrator, type RecoveryStatus } from './banRecoveryOrche
29
30
  import { type AntiBanInput, type ResolvedConfig } from './presets.js';
30
31
  import { type DeliveryTrackerStats } from './deliveryTracker.js';
31
32
  import { type InstanceCoordinatorStats } from './instanceCoordinator.js';
33
+ import { MessageTypeRegistry } from './messageTypeRegistry.js';
34
+ import { type AntibanSnapshot } from './stateExport.js';
32
35
  export interface AntiBanConfigLegacy {
33
36
  rateLimiter?: Partial<RateLimiterConfig>;
34
37
  warmUp?: Partial<WarmUpConfig>;
@@ -39,6 +42,7 @@ export interface AntiBanConfigLegacy {
39
42
  presence?: Partial<PresenceChoreographerConfig>;
40
43
  retryTracker?: Partial<RetryTrackerConfig>;
41
44
  reconnectThrottle?: Partial<ReconnectThrottleConfig>;
45
+ topologyThrottler?: Partial<TopologyThrottlerConfig>;
42
46
  lidResolver?: LidResolverConfig;
43
47
  jidCanonicalizer?: JidCanonicalizerConfig;
44
48
  sessionStability?: {
@@ -70,12 +74,26 @@ export interface AntiBanStats {
70
74
  presence?: PresenceChoreographerStats;
71
75
  retryTracker?: RetryStats | null;
72
76
  reconnectThrottle?: ReconnectThrottleStats | null;
77
+ topologyThrottler?: {
78
+ newContactsThisHour: number;
79
+ newContactsToday: number;
80
+ replyRatio: number | null;
81
+ blockedRatio: number | null;
82
+ hotspots: Array<{
83
+ sourceGroup: string;
84
+ count: number;
85
+ }>;
86
+ } | null;
73
87
  lidResolver?: LidResolverStats | null;
74
88
  jidCanonicalizer?: JidCanonicalizerStats | null;
75
89
  sessionStability?: SessionHealthStats | null;
76
90
  banRecovery?: RecoveryStatus | null;
77
91
  deliveryTracker: DeliveryTrackerStats;
78
92
  instanceCoordinator?: InstanceCoordinatorStats | null;
93
+ messageRegistry?: {
94
+ typeCount: number;
95
+ warningCount: number;
96
+ } | null;
79
97
  }
80
98
  export declare class AntiBan {
81
99
  private rateLimiter;
@@ -87,12 +105,14 @@ export declare class AntiBan {
87
105
  private presenceChoreographer;
88
106
  private retryTrackerModule;
89
107
  private reconnectThrottleModule;
108
+ private topologyThrottlerModule;
90
109
  private lidResolverModule;
91
110
  private jidCanonicalizerModule;
92
111
  private sessionStabilityMonitor;
93
112
  private banRecovery;
94
113
  private deliveryTracker;
95
114
  private instanceCoordinator;
115
+ private messageTypeRegistry;
96
116
  private stateManager;
97
117
  private resolvedConfig;
98
118
  private logging;
@@ -153,6 +173,10 @@ export declare class AntiBan {
153
173
  get retryTracker(): RetryReasonTracker;
154
174
  /** Get the reconnect throttle for direct access */
155
175
  get reconnectThrottle(): PostReconnectThrottle;
176
+ /** Get the topology throttler for direct access */
177
+ get topologyThrottler(): TopologyThrottler | null;
178
+ /** Get the topology throttler for direct access (alias) */
179
+ get topology(): TopologyThrottler | null;
156
180
  /** Get the LID resolver for direct access */
157
181
  get lidResolver(): LidResolver | null;
158
182
  /** Get the JID canonicalizer for direct access */
@@ -161,6 +185,8 @@ export declare class AntiBan {
161
185
  get sessionStability(): SessionHealthMonitor | null;
162
186
  /** Get the ban recovery orchestrator for direct access */
163
187
  get recoveryOrchestrator(): BanRecoveryOrchestrator;
188
+ /** Get the message type registry for direct access */
189
+ get messageRegistry(): MessageTypeRegistry | null;
164
190
  /**
165
191
  * Export warm-up state for persistence between restarts
166
192
  */
@@ -177,9 +203,20 @@ export declare class AntiBan {
177
203
  * Reset everything (use after a ban period)
178
204
  */
179
205
  reset(): void;
206
+ private isGroupJid;
180
207
  private runAdaptiveCheck;
181
208
  private persistStateDebounced;
182
209
  private persistStateImmediate;
210
+ /**
211
+ * Export unified state snapshot for Redis failover or cross-instance migration.
212
+ * Returns snapshot of all module states (warmup, health, rate limiter, circuits, etc.)
213
+ */
214
+ exportState(): AntibanSnapshot;
215
+ /**
216
+ * Import unified state snapshot.
217
+ * CRDT-safe for rate limiters (never overwrites higher counts).
218
+ */
219
+ importState(snapshot: AntibanSnapshot): void;
183
220
  /**
184
221
  * Clean up all timers and resources.
185
222
  * Call this when disposing of the AntiBan instance or when the socket closes.
package/dist/antiban.js CHANGED
@@ -21,6 +21,7 @@ import { ReplyRatioGuard } from './replyRatio.js';
21
21
  import { ContactGraphWarmer } from './contactGraph.js';
22
22
  import { PresenceChoreographer } from './presenceChoreographer.js';
23
23
  import { RetryReasonTracker } from './retryTracker.js';
24
+ import { TopologyThrottler } from './topologyThrottler.js';
24
25
  import { PostReconnectThrottle } from './reconnectThrottle.js';
25
26
  import { LidResolver } from './lidResolver.js';
26
27
  import { JidCanonicalizer } from './jidCanonicalizer.js';
@@ -31,6 +32,8 @@ import { StateManager } from './persist.js';
31
32
  import { shouldUseGroupProfile, applyGroupMultiplier } from './profiles.js';
32
33
  import { DeliveryTracker } from './deliveryTracker.js';
33
34
  import { InstanceCoordinator } from './instanceCoordinator.js';
35
+ import { MessageTypeRegistry } from './messageTypeRegistry.js';
36
+ import { exportAntibanState, importAntibanState } from './stateExport.js';
34
37
  function isLegacyConfig(cfg) {
35
38
  if (typeof cfg !== 'object' || cfg === null)
36
39
  return false;
@@ -103,12 +106,14 @@ export class AntiBan {
103
106
  presenceChoreographer;
104
107
  retryTrackerModule;
105
108
  reconnectThrottleModule;
109
+ topologyThrottlerModule = null;
106
110
  lidResolverModule = null;
107
111
  jidCanonicalizerModule = null;
108
112
  sessionStabilityMonitor = null;
109
113
  banRecovery;
110
114
  deliveryTracker;
111
115
  instanceCoordinator = null;
116
+ messageTypeRegistry = null;
112
117
  stateManager = null;
113
118
  resolvedConfig;
114
119
  logging;
@@ -243,6 +248,13 @@ export class AntiBan {
243
248
  ...(legacyPassthrough?.reconnectThrottle || {}),
244
249
  baselineRatePerMinute: () => this.rateLimiter.getStats().limits.perMinute,
245
250
  });
251
+ // Initialize topology throttler if configured
252
+ if (legacyPassthrough?.topologyThrottler) {
253
+ this.topologyThrottlerModule = new TopologyThrottler(legacyPassthrough.topologyThrottler);
254
+ if (this.logging) {
255
+ console.log(`[baileys-antiban] ๐ŸŒ Topology throttler enabled โ€” max ${legacyPassthrough.topologyThrottler.maxNewContactsPerHour || 5}/hr, ${legacyPassthrough.topologyThrottler.maxNewContactsPerDay || 20}/day new contacts`);
256
+ }
257
+ }
246
258
  // Initialize LID resolver and canonicalizer if configured
247
259
  // If jidCanonicalizer is enabled but no resolver provided, create standalone resolver
248
260
  if (legacyPassthrough?.jidCanonicalizer?.enabled) {
@@ -297,6 +309,13 @@ export class AntiBan {
297
309
  console.log(`[baileys-antiban] ๐ŸŒ Instance coordination enabled: ${cfg.instanceCoordinator}`);
298
310
  }
299
311
  }
312
+ // Initialize message type registry if configured
313
+ if (cfg.messageTypeRegistry) {
314
+ this.messageTypeRegistry = new MessageTypeRegistry();
315
+ if (this.logging) {
316
+ console.log(`[baileys-antiban] ๐Ÿ“ Message type registry enabled`);
317
+ }
318
+ }
300
319
  }
301
320
  /**
302
321
  * Check if a message can be sent and get required delay.
@@ -371,6 +390,46 @@ export class AntiBan {
371
390
  health: healthStatus,
372
391
  };
373
392
  }
393
+ // Topology throttler check โ€” only applies to DMs to new/unknown contacts
394
+ if (this.topologyThrottlerModule && !this.isGroupJid(recipient)) {
395
+ const knownChats = this.rateLimiter.getKnownChats();
396
+ const isNewContact = !knownChats.has(recipient);
397
+ if (isNewContact) {
398
+ // Check if we can send to new contact based on topology limits
399
+ const topologyDecision = this.topologyThrottlerModule.canSendToNewContact();
400
+ if (!topologyDecision.allowed) {
401
+ this.stats.messagesBlocked++;
402
+ if (this.logging) {
403
+ console.log(`[baileys-antiban] ๐ŸŒ BLOCKED โ€” topology: ${topologyDecision.reason}`);
404
+ }
405
+ return {
406
+ allowed: false,
407
+ delayMs: topologyDecision.retryAfterMs || 0,
408
+ reason: `Topology: ${topologyDecision.reason}`,
409
+ health: healthStatus,
410
+ };
411
+ }
412
+ // Assess contact risk
413
+ const riskAssessment = this.topologyThrottlerModule.assessContact(recipient, {
414
+ messageType: 'dm',
415
+ hasReplied: false,
416
+ knownGroups: [],
417
+ });
418
+ if (riskAssessment.recommendation === 'abort') {
419
+ this.stats.messagesBlocked++;
420
+ if (this.logging) {
421
+ console.log(`[baileys-antiban] โ›” BLOCKED โ€” contact risk ${riskAssessment.risk} (score ${riskAssessment.score}): ${riskAssessment.reasons.join(', ')}`);
422
+ }
423
+ return {
424
+ allowed: false,
425
+ delayMs: 0,
426
+ reason: `Contact risk too high: ${riskAssessment.reasons.join(', ')}`,
427
+ health: healthStatus,
428
+ };
429
+ }
430
+ // If delay recommended, we'll add it to the total delay later
431
+ }
432
+ }
374
433
  // Reply ratio check
375
434
  const replyRatioDecision = this.replyRatioGuard.beforeSend(recipient);
376
435
  if (!replyRatioDecision.allowed) {
@@ -472,6 +531,24 @@ export class AntiBan {
472
531
  }
473
532
  }
474
533
  }
534
+ // Topology throttler recommended delay
535
+ if (this.topologyThrottlerModule && !this.isGroupJid(recipient)) {
536
+ const knownChats = this.rateLimiter.getKnownChats();
537
+ const isNewContact = !knownChats.has(recipient);
538
+ if (isNewContact) {
539
+ const riskAssessment = this.topologyThrottlerModule.assessContact(recipient, {
540
+ messageType: 'dm',
541
+ hasReplied: false,
542
+ knownGroups: [],
543
+ });
544
+ if (riskAssessment.recommendation === 'delay' && riskAssessment.suggestedDelayMs) {
545
+ delay += riskAssessment.suggestedDelayMs;
546
+ if (this.logging) {
547
+ console.log(`[baileys-antiban] โš ๏ธ Topology risk ${riskAssessment.risk} โ€” adding ${Math.floor(riskAssessment.suggestedDelayMs / 60000)}min delay`);
548
+ }
549
+ }
550
+ }
551
+ }
475
552
  // Roll for distraction pause
476
553
  const distractionCheck = this.presenceChoreographer.shouldPauseForDistraction();
477
554
  if (distractionCheck.pause) {
@@ -503,6 +580,7 @@ export class AntiBan {
503
580
  this.rateLimiter.record(recipient, content);
504
581
  this.warmUp.record();
505
582
  this.replyRatioGuard.recordSent(recipient);
583
+ this.topologyThrottlerModule?.recordSent(recipient);
506
584
  this.stats.messagesAllowed++;
507
585
  if (msgId) {
508
586
  this.deliveryTracker.onMessageSent(msgId);
@@ -542,6 +620,7 @@ export class AntiBan {
542
620
  onIncomingMessage(jid, msgText) {
543
621
  this.replyRatioGuard.recordReceived(jid);
544
622
  this.contactGraphWarmer.onIncomingMessage(jid);
623
+ this.topologyThrottlerModule?.recordReplied(jid);
545
624
  return this.replyRatioGuard.suggestReply(jid, msgText);
546
625
  }
547
626
  /**
@@ -585,6 +664,9 @@ export class AntiBan {
585
664
  if (this.reconnectThrottleModule['config']?.enabled) {
586
665
  stats.reconnectThrottle = this.reconnectThrottleModule.getStats();
587
666
  }
667
+ if (this.topologyThrottlerModule) {
668
+ stats.topologyThrottler = this.topologyThrottlerModule.getTopologyStats();
669
+ }
588
670
  if (this.lidResolverModule) {
589
671
  stats.lidResolver = this.lidResolverModule.getStats();
590
672
  }
@@ -597,6 +679,13 @@ export class AntiBan {
597
679
  if (this.instanceCoordinator) {
598
680
  stats.instanceCoordinator = this.instanceCoordinator.getStats();
599
681
  }
682
+ if (this.messageTypeRegistry) {
683
+ const warnings = this.messageTypeRegistry.getWarnings();
684
+ stats.messageRegistry = {
685
+ typeCount: Array.from(this.messageTypeRegistry.types.keys()).length,
686
+ warningCount: warnings.length,
687
+ };
688
+ }
600
689
  return stats;
601
690
  }
602
691
  /** Get the timelock guard for direct access */
@@ -623,6 +712,14 @@ export class AntiBan {
623
712
  get reconnectThrottle() {
624
713
  return this.reconnectThrottleModule;
625
714
  }
715
+ /** Get the topology throttler for direct access */
716
+ get topologyThrottler() {
717
+ return this.topologyThrottlerModule;
718
+ }
719
+ /** Get the topology throttler for direct access (alias) */
720
+ get topology() {
721
+ return this.topologyThrottlerModule;
722
+ }
626
723
  /** Get the LID resolver for direct access */
627
724
  get lidResolver() {
628
725
  return this.lidResolverModule;
@@ -639,6 +736,10 @@ export class AntiBan {
639
736
  get recoveryOrchestrator() {
640
737
  return this.banRecovery;
641
738
  }
739
+ /** Get the message type registry for direct access */
740
+ get messageRegistry() {
741
+ return this.messageTypeRegistry;
742
+ }
642
743
  /**
643
744
  * Export warm-up state for persistence between restarts
644
745
  */
@@ -680,6 +781,9 @@ export class AntiBan {
680
781
  console.log('[baileys-antiban] ๐Ÿ”„ Reset โ€” starting fresh warm-up');
681
782
  }
682
783
  }
784
+ isGroupJid(jid) {
785
+ return jid.endsWith('@g.us') || jid.endsWith('@newsletter');
786
+ }
683
787
  runAdaptiveCheck() {
684
788
  const delivery = this.deliveryTracker.getStats();
685
789
  // Need min sample to be meaningful
@@ -731,6 +835,35 @@ export class AntiBan {
731
835
  };
732
836
  this.stateManager.saveImmediate(state);
733
837
  }
838
+ /**
839
+ * Export unified state snapshot for Redis failover or cross-instance migration.
840
+ * Returns snapshot of all module states (warmup, health, rate limiter, circuits, etc.)
841
+ */
842
+ exportState() {
843
+ return exportAntibanState({
844
+ warmup: this.warmUp,
845
+ health: this.health,
846
+ rateLimiter: this.rateLimiter,
847
+ timelockGuard: this.timelockGuard,
848
+ messageRegistry: this.messageTypeRegistry || undefined,
849
+ topologyThrottler: this.topologyThrottlerModule || undefined,
850
+ instanceId: this.resolvedConfig.instanceId,
851
+ });
852
+ }
853
+ /**
854
+ * Import unified state snapshot.
855
+ * CRDT-safe for rate limiters (never overwrites higher counts).
856
+ */
857
+ importState(snapshot) {
858
+ importAntibanState(snapshot, {
859
+ warmup: this.warmUp,
860
+ health: this.health,
861
+ rateLimiter: this.rateLimiter,
862
+ timelockGuard: this.timelockGuard,
863
+ messageRegistry: this.messageTypeRegistry || undefined,
864
+ topologyThrottler: this.topologyThrottlerModule || undefined,
865
+ });
866
+ }
734
867
  /**
735
868
  * Clean up all timers and resources.
736
869
  * Call this when disposing of the AntiBan instance or when the socket closes.
@@ -746,6 +879,7 @@ export class AntiBan {
746
879
  this.jidCanonicalizerModule?.destroy();
747
880
  this.lidResolverModule?.destroy();
748
881
  this.sessionStabilityMonitor?.reset();
882
+ this.messageTypeRegistry?.cleanup();
749
883
  if (this.logging) {
750
884
  console.log('[baileys-antiban] ๐Ÿงน Destroyed โ€” all timers cleared');
751
885
  }
@@ -24,6 +24,7 @@ const replyRatio_js_1 = require("./replyRatio.js");
24
24
  const contactGraph_js_1 = require("./contactGraph.js");
25
25
  const presenceChoreographer_js_1 = require("./presenceChoreographer.js");
26
26
  const retryTracker_js_1 = require("./retryTracker.js");
27
+ const topologyThrottler_js_1 = require("./topologyThrottler.js");
27
28
  const reconnectThrottle_js_1 = require("./reconnectThrottle.js");
28
29
  const lidResolver_js_1 = require("./lidResolver.js");
29
30
  const jidCanonicalizer_js_1 = require("./jidCanonicalizer.js");
@@ -34,6 +35,8 @@ const persist_js_1 = require("./persist.js");
34
35
  const profiles_js_1 = require("./profiles.js");
35
36
  const deliveryTracker_js_1 = require("./deliveryTracker.js");
36
37
  const instanceCoordinator_js_1 = require("./instanceCoordinator.js");
38
+ const messageTypeRegistry_js_1 = require("./messageTypeRegistry.js");
39
+ const stateExport_js_1 = require("./stateExport.js");
37
40
  function isLegacyConfig(cfg) {
38
41
  if (typeof cfg !== 'object' || cfg === null)
39
42
  return false;
@@ -106,12 +109,14 @@ class AntiBan {
106
109
  presenceChoreographer;
107
110
  retryTrackerModule;
108
111
  reconnectThrottleModule;
112
+ topologyThrottlerModule = null;
109
113
  lidResolverModule = null;
110
114
  jidCanonicalizerModule = null;
111
115
  sessionStabilityMonitor = null;
112
116
  banRecovery;
113
117
  deliveryTracker;
114
118
  instanceCoordinator = null;
119
+ messageTypeRegistry = null;
115
120
  stateManager = null;
116
121
  resolvedConfig;
117
122
  logging;
@@ -246,6 +251,13 @@ class AntiBan {
246
251
  ...(legacyPassthrough?.reconnectThrottle || {}),
247
252
  baselineRatePerMinute: () => this.rateLimiter.getStats().limits.perMinute,
248
253
  });
254
+ // Initialize topology throttler if configured
255
+ if (legacyPassthrough?.topologyThrottler) {
256
+ this.topologyThrottlerModule = new topologyThrottler_js_1.TopologyThrottler(legacyPassthrough.topologyThrottler);
257
+ if (this.logging) {
258
+ console.log(`[baileys-antiban] ๐ŸŒ Topology throttler enabled โ€” max ${legacyPassthrough.topologyThrottler.maxNewContactsPerHour || 5}/hr, ${legacyPassthrough.topologyThrottler.maxNewContactsPerDay || 20}/day new contacts`);
259
+ }
260
+ }
249
261
  // Initialize LID resolver and canonicalizer if configured
250
262
  // If jidCanonicalizer is enabled but no resolver provided, create standalone resolver
251
263
  if (legacyPassthrough?.jidCanonicalizer?.enabled) {
@@ -300,6 +312,13 @@ class AntiBan {
300
312
  console.log(`[baileys-antiban] ๐ŸŒ Instance coordination enabled: ${cfg.instanceCoordinator}`);
301
313
  }
302
314
  }
315
+ // Initialize message type registry if configured
316
+ if (cfg.messageTypeRegistry) {
317
+ this.messageTypeRegistry = new messageTypeRegistry_js_1.MessageTypeRegistry();
318
+ if (this.logging) {
319
+ console.log(`[baileys-antiban] ๐Ÿ“ Message type registry enabled`);
320
+ }
321
+ }
303
322
  }
304
323
  /**
305
324
  * Check if a message can be sent and get required delay.
@@ -374,6 +393,46 @@ class AntiBan {
374
393
  health: healthStatus,
375
394
  };
376
395
  }
396
+ // Topology throttler check โ€” only applies to DMs to new/unknown contacts
397
+ if (this.topologyThrottlerModule && !this.isGroupJid(recipient)) {
398
+ const knownChats = this.rateLimiter.getKnownChats();
399
+ const isNewContact = !knownChats.has(recipient);
400
+ if (isNewContact) {
401
+ // Check if we can send to new contact based on topology limits
402
+ const topologyDecision = this.topologyThrottlerModule.canSendToNewContact();
403
+ if (!topologyDecision.allowed) {
404
+ this.stats.messagesBlocked++;
405
+ if (this.logging) {
406
+ console.log(`[baileys-antiban] ๐ŸŒ BLOCKED โ€” topology: ${topologyDecision.reason}`);
407
+ }
408
+ return {
409
+ allowed: false,
410
+ delayMs: topologyDecision.retryAfterMs || 0,
411
+ reason: `Topology: ${topologyDecision.reason}`,
412
+ health: healthStatus,
413
+ };
414
+ }
415
+ // Assess contact risk
416
+ const riskAssessment = this.topologyThrottlerModule.assessContact(recipient, {
417
+ messageType: 'dm',
418
+ hasReplied: false,
419
+ knownGroups: [],
420
+ });
421
+ if (riskAssessment.recommendation === 'abort') {
422
+ this.stats.messagesBlocked++;
423
+ if (this.logging) {
424
+ console.log(`[baileys-antiban] โ›” BLOCKED โ€” contact risk ${riskAssessment.risk} (score ${riskAssessment.score}): ${riskAssessment.reasons.join(', ')}`);
425
+ }
426
+ return {
427
+ allowed: false,
428
+ delayMs: 0,
429
+ reason: `Contact risk too high: ${riskAssessment.reasons.join(', ')}`,
430
+ health: healthStatus,
431
+ };
432
+ }
433
+ // If delay recommended, we'll add it to the total delay later
434
+ }
435
+ }
377
436
  // Reply ratio check
378
437
  const replyRatioDecision = this.replyRatioGuard.beforeSend(recipient);
379
438
  if (!replyRatioDecision.allowed) {
@@ -475,6 +534,24 @@ class AntiBan {
475
534
  }
476
535
  }
477
536
  }
537
+ // Topology throttler recommended delay
538
+ if (this.topologyThrottlerModule && !this.isGroupJid(recipient)) {
539
+ const knownChats = this.rateLimiter.getKnownChats();
540
+ const isNewContact = !knownChats.has(recipient);
541
+ if (isNewContact) {
542
+ const riskAssessment = this.topologyThrottlerModule.assessContact(recipient, {
543
+ messageType: 'dm',
544
+ hasReplied: false,
545
+ knownGroups: [],
546
+ });
547
+ if (riskAssessment.recommendation === 'delay' && riskAssessment.suggestedDelayMs) {
548
+ delay += riskAssessment.suggestedDelayMs;
549
+ if (this.logging) {
550
+ console.log(`[baileys-antiban] โš ๏ธ Topology risk ${riskAssessment.risk} โ€” adding ${Math.floor(riskAssessment.suggestedDelayMs / 60000)}min delay`);
551
+ }
552
+ }
553
+ }
554
+ }
478
555
  // Roll for distraction pause
479
556
  const distractionCheck = this.presenceChoreographer.shouldPauseForDistraction();
480
557
  if (distractionCheck.pause) {
@@ -506,6 +583,7 @@ class AntiBan {
506
583
  this.rateLimiter.record(recipient, content);
507
584
  this.warmUp.record();
508
585
  this.replyRatioGuard.recordSent(recipient);
586
+ this.topologyThrottlerModule?.recordSent(recipient);
509
587
  this.stats.messagesAllowed++;
510
588
  if (msgId) {
511
589
  this.deliveryTracker.onMessageSent(msgId);
@@ -545,6 +623,7 @@ class AntiBan {
545
623
  onIncomingMessage(jid, msgText) {
546
624
  this.replyRatioGuard.recordReceived(jid);
547
625
  this.contactGraphWarmer.onIncomingMessage(jid);
626
+ this.topologyThrottlerModule?.recordReplied(jid);
548
627
  return this.replyRatioGuard.suggestReply(jid, msgText);
549
628
  }
550
629
  /**
@@ -588,6 +667,9 @@ class AntiBan {
588
667
  if (this.reconnectThrottleModule['config']?.enabled) {
589
668
  stats.reconnectThrottle = this.reconnectThrottleModule.getStats();
590
669
  }
670
+ if (this.topologyThrottlerModule) {
671
+ stats.topologyThrottler = this.topologyThrottlerModule.getTopologyStats();
672
+ }
591
673
  if (this.lidResolverModule) {
592
674
  stats.lidResolver = this.lidResolverModule.getStats();
593
675
  }
@@ -600,6 +682,13 @@ class AntiBan {
600
682
  if (this.instanceCoordinator) {
601
683
  stats.instanceCoordinator = this.instanceCoordinator.getStats();
602
684
  }
685
+ if (this.messageTypeRegistry) {
686
+ const warnings = this.messageTypeRegistry.getWarnings();
687
+ stats.messageRegistry = {
688
+ typeCount: Array.from(this.messageTypeRegistry.types.keys()).length,
689
+ warningCount: warnings.length,
690
+ };
691
+ }
603
692
  return stats;
604
693
  }
605
694
  /** Get the timelock guard for direct access */
@@ -626,6 +715,14 @@ class AntiBan {
626
715
  get reconnectThrottle() {
627
716
  return this.reconnectThrottleModule;
628
717
  }
718
+ /** Get the topology throttler for direct access */
719
+ get topologyThrottler() {
720
+ return this.topologyThrottlerModule;
721
+ }
722
+ /** Get the topology throttler for direct access (alias) */
723
+ get topology() {
724
+ return this.topologyThrottlerModule;
725
+ }
629
726
  /** Get the LID resolver for direct access */
630
727
  get lidResolver() {
631
728
  return this.lidResolverModule;
@@ -642,6 +739,10 @@ class AntiBan {
642
739
  get recoveryOrchestrator() {
643
740
  return this.banRecovery;
644
741
  }
742
+ /** Get the message type registry for direct access */
743
+ get messageRegistry() {
744
+ return this.messageTypeRegistry;
745
+ }
645
746
  /**
646
747
  * Export warm-up state for persistence between restarts
647
748
  */
@@ -683,6 +784,9 @@ class AntiBan {
683
784
  console.log('[baileys-antiban] ๐Ÿ”„ Reset โ€” starting fresh warm-up');
684
785
  }
685
786
  }
787
+ isGroupJid(jid) {
788
+ return jid.endsWith('@g.us') || jid.endsWith('@newsletter');
789
+ }
686
790
  runAdaptiveCheck() {
687
791
  const delivery = this.deliveryTracker.getStats();
688
792
  // Need min sample to be meaningful
@@ -734,6 +838,35 @@ class AntiBan {
734
838
  };
735
839
  this.stateManager.saveImmediate(state);
736
840
  }
841
+ /**
842
+ * Export unified state snapshot for Redis failover or cross-instance migration.
843
+ * Returns snapshot of all module states (warmup, health, rate limiter, circuits, etc.)
844
+ */
845
+ exportState() {
846
+ return (0, stateExport_js_1.exportAntibanState)({
847
+ warmup: this.warmUp,
848
+ health: this.health,
849
+ rateLimiter: this.rateLimiter,
850
+ timelockGuard: this.timelockGuard,
851
+ messageRegistry: this.messageTypeRegistry || undefined,
852
+ topologyThrottler: this.topologyThrottlerModule || undefined,
853
+ instanceId: this.resolvedConfig.instanceId,
854
+ });
855
+ }
856
+ /**
857
+ * Import unified state snapshot.
858
+ * CRDT-safe for rate limiters (never overwrites higher counts).
859
+ */
860
+ importState(snapshot) {
861
+ (0, stateExport_js_1.importAntibanState)(snapshot, {
862
+ warmup: this.warmUp,
863
+ health: this.health,
864
+ rateLimiter: this.rateLimiter,
865
+ timelockGuard: this.timelockGuard,
866
+ messageRegistry: this.messageTypeRegistry || undefined,
867
+ topologyThrottler: this.topologyThrottlerModule || undefined,
868
+ });
869
+ }
737
870
  /**
738
871
  * Clean up all timers and resources.
739
872
  * Call this when disposing of the AntiBan instance or when the socket closes.
@@ -749,6 +882,7 @@ class AntiBan {
749
882
  this.jidCanonicalizerModule?.destroy();
750
883
  this.lidResolverModule?.destroy();
751
884
  this.sessionStabilityMonitor?.reset();
885
+ this.messageTypeRegistry?.cleanup();
752
886
  if (this.logging) {
753
887
  console.log('[baileys-antiban] ๐Ÿงน Destroyed โ€” all timers cleared');
754
888
  }
package/dist/cjs/index.js CHANGED
@@ -10,7 +10,7 @@
10
10
  */
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
12
  exports.getRetryJitter = exports.getTypingJitter = exports.getMessageSendJitter = exports.applySessionFingerprint = exports.generateSessionFingerprint = exports.proxyRotator = exports.readReceiptVariance = exports.credsSnapshot = exports.applyFingerprint = exports.generateFingerprint = exports.messageRecovery = exports.applyGroupMultiplier = exports.shouldUseGroupProfile = exports.isBroadcast = exports.isNewsletter = exports.isGroup = exports.StateManager = exports.PRESETS = exports.resolveConfig = exports.FileStateAdapter = exports.Scheduler = exports.WebhookAlerts = exports.ContentVariator = exports.MessageQueue = exports.wrapSocketWithFingerprint = exports.wrapSocket = exports.getRetryReasonDescription = exports.isMacError = exports.parseRetryReason = exports.MAC_ERROR_CODES = exports.MessageRetryReason = exports.createLidFirstResolver = exports.LidFirstResolver = exports.DeafSessionDetector = exports.classifyDisconnect = exports.wrapWithSessionStability = exports.SessionHealthMonitor = exports.JidCanonicalizer = exports.LidResolver = exports.PostReconnectThrottle = exports.RetryReasonTracker = exports.getCircadianMultiplier = exports.PresenceChoreographer = exports.ContactGraphWarmer = exports.ReplyRatioGuard = exports.TimelockGuard = exports.HealthMonitor = exports.WarmUp = exports.RateLimiter = exports.AntiBan = void 0;
13
- exports.createPeriodicExporter = exports.createMetricsHandler = exports.exportPrometheusMetrics = exports.createConsoleLogger = exports.createHumanEntropyService = exports.HumanEntropyService = exports.createInMemoryEventStoreBackend = exports.createMySQLEventStoreBackend = exports.createFleetEventStore = exports.createJidCircuitBreaker = exports.JidCircuitBreaker = exports.InstanceCoordinator = exports.DeliveryTracker = exports.BanRecoveryOrchestrator = exports.LegitimacySignalInjector = exports.GROUP_OP_ERRORS = exports.extractPrivacyBlock = exports.classifyGroupOpError = exports.GroupOperationGuard = exports.AbortError = exports.STEALTH_BROWSER_POOL = exports.rampPresenceAfterConnect = exports.getStealthSocketConfig = exports.createStealthFingerprint = exports.getBatteryState = exports.getVoiceNoteMetadata = void 0;
13
+ exports.createPeriodicExporter = exports.createMetricsHandler = exports.exportPrometheusMetrics = exports.createConsoleLogger = exports.TopologyThrottler = exports.importAntibanState = exports.exportAntibanState = exports.MessageTypeRegistry = exports.createHumanEntropyService = exports.HumanEntropyService = exports.createInMemoryEventStoreBackend = exports.createMySQLEventStoreBackend = exports.createFleetEventStore = exports.createJidCircuitBreaker = exports.JidCircuitBreaker = exports.InstanceCoordinator = exports.DeliveryTracker = exports.BanRecoveryOrchestrator = exports.LegitimacySignalInjector = exports.GROUP_OP_ERRORS = exports.extractPrivacyBlock = exports.classifyGroupOpError = exports.GroupOperationGuard = exports.AbortError = exports.STEALTH_BROWSER_POOL = exports.rampPresenceAfterConnect = exports.getStealthSocketConfig = exports.createStealthFingerprint = exports.getBatteryState = exports.getVoiceNoteMetadata = void 0;
14
14
  // Core
15
15
  var antiban_js_1 = require("./antiban.js");
16
16
  Object.defineProperty(exports, "AntiBan", { enumerable: true, get: function () { return antiban_js_1.AntiBan; } });
@@ -143,6 +143,14 @@ Object.defineProperty(exports, "createInMemoryEventStoreBackend", { enumerable:
143
143
  var humanEntropy_js_1 = require("./humanEntropy.js");
144
144
  Object.defineProperty(exports, "HumanEntropyService", { enumerable: true, get: function () { return humanEntropy_js_1.HumanEntropyService; } });
145
145
  Object.defineProperty(exports, "createHumanEntropyService", { enumerable: true, get: function () { return humanEntropy_js_1.createHumanEntropyService; } });
146
+ // v4.8 new modules
147
+ var messageTypeRegistry_js_1 = require("./messageTypeRegistry.js");
148
+ Object.defineProperty(exports, "MessageTypeRegistry", { enumerable: true, get: function () { return messageTypeRegistry_js_1.MessageTypeRegistry; } });
149
+ var stateExport_js_1 = require("./stateExport.js");
150
+ Object.defineProperty(exports, "exportAntibanState", { enumerable: true, get: function () { return stateExport_js_1.exportAntibanState; } });
151
+ Object.defineProperty(exports, "importAntibanState", { enumerable: true, get: function () { return stateExport_js_1.importAntibanState; } });
152
+ var topologyThrottler_js_1 = require("./topologyThrottler.js");
153
+ Object.defineProperty(exports, "TopologyThrottler", { enumerable: true, get: function () { return topologyThrottler_js_1.TopologyThrottler; } });
146
154
  // Observability
147
155
  var observability_js_1 = require("./observability.js");
148
156
  Object.defineProperty(exports, "createConsoleLogger", { enumerable: true, get: function () { return observability_js_1.createConsoleLogger; } });