@prototypevip/baileys 0.0.8 → 0.0.9

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 (54) hide show
  1. package/lib/AntiBan/antiban.js +922 -0
  2. package/lib/AntiBan/banRecoveryOrchestrator.js +284 -0
  3. package/lib/AntiBan/cli.js +177 -0
  4. package/lib/AntiBan/contactGraph.js +236 -0
  5. package/lib/AntiBan/contentVariator.js +150 -0
  6. package/lib/AntiBan/credsSnapshot.js +121 -0
  7. package/lib/AntiBan/deliveryTracker.js +107 -0
  8. package/lib/AntiBan/deviceFingerprint.js +106 -0
  9. package/lib/AntiBan/fleetEventStore.js +157 -0
  10. package/lib/AntiBan/groupOperationGuard.js +116 -0
  11. package/lib/AntiBan/health.js +207 -0
  12. package/lib/AntiBan/humanEntropy.js +337 -0
  13. package/lib/AntiBan/index.js +77 -0
  14. package/lib/AntiBan/instanceCoordinator.js +175 -0
  15. package/lib/AntiBan/jidCanonicalizer.js +256 -0
  16. package/lib/AntiBan/jidCircuitBreaker.js +186 -0
  17. package/lib/AntiBan/legitimacySignalInjector.js +231 -0
  18. package/lib/AntiBan/lidFirstResolver.js +174 -0
  19. package/lib/AntiBan/lidResolver.js +324 -0
  20. package/lib/AntiBan/messageQueue.js +187 -0
  21. package/lib/AntiBan/messageRecovery.js +283 -0
  22. package/lib/AntiBan/messageTypeRegistry.js +369 -0
  23. package/lib/AntiBan/observability.js +145 -0
  24. package/lib/AntiBan/patch.js +302 -0
  25. package/lib/AntiBan/persist.js +87 -0
  26. package/lib/AntiBan/presenceChoreographer.js +430 -0
  27. package/lib/AntiBan/presets.js +105 -0
  28. package/lib/AntiBan/profiles.js +31 -0
  29. package/lib/AntiBan/proxyRotator.js +306 -0
  30. package/lib/AntiBan/rateLimiter.js +261 -0
  31. package/lib/AntiBan/readReceiptVariance.js +88 -0
  32. package/lib/AntiBan/reconnectThrottle.js +180 -0
  33. package/lib/AntiBan/replyRatio.js +161 -0
  34. package/lib/AntiBan/reputationVoucher.js +373 -0
  35. package/lib/AntiBan/retryReason.js +91 -0
  36. package/lib/AntiBan/retryTracker.js +172 -0
  37. package/lib/AntiBan/scheduler.js +111 -0
  38. package/lib/AntiBan/sessionFingerprint.js +248 -0
  39. package/lib/AntiBan/sessionStability.js +330 -0
  40. package/lib/AntiBan/stateAdapter.js +73 -0
  41. package/lib/AntiBan/stateExport.js +152 -0
  42. package/lib/AntiBan/stealthConnect.js +130 -0
  43. package/lib/AntiBan/timelockGuard.js +181 -0
  44. package/lib/AntiBan/topologyThrottler.js +339 -0
  45. package/lib/AntiBan/warmup.js +140 -0
  46. package/lib/AntiBan/webhooks.js +80 -0
  47. package/lib/AntiBan/wrapper.js +456 -0
  48. package/lib/Socket/groups.js +26 -21
  49. package/lib/Socket/index.js +139 -7
  50. package/lib/Socket/messages-send.js +68 -5
  51. package/lib/Utils/ai-rich-formatter.js +256 -0
  52. package/lib/Utils/index.js +1 -0
  53. package/lib/index.js +1 -1
  54. package/package.json +1 -1
@@ -0,0 +1,180 @@
1
+ /**
2
+ * PostReconnectThrottle — Throttle outbound messages after reconnection
3
+ *
4
+ * Inspired by whatsapp-rust's client/sessions.rs which uses semaphore=1 during
5
+ * offline sync (serializes all processing), then swaps to semaphore=64 when sync
6
+ * completes. This prevents burst-floods on reconnect that trigger WA rate limits.
7
+ *
8
+ * In middleware layer: on reconnect, enter a throttled window where beforeSend()
9
+ * gates outbound messages to an artificially low rate, then ramps back to normal
10
+ * over a configurable period.
11
+ *
12
+ * Usage:
13
+ * const throttle = new PostReconnectThrottle({
14
+ * enabled: true,
15
+ * rampDurationMs: 60_000,
16
+ * initialRateMultiplier: 0.1,
17
+ * });
18
+ *
19
+ * // On connection.update with connection === 'open':
20
+ * throttle.onReconnect();
21
+ *
22
+ * // Before sending:
23
+ * const decision = throttle.beforeSend();
24
+ * if (!decision.allowed) {
25
+ * // Wait decision.retryAfterMs
26
+ * }
27
+ *
28
+ * // Get current throttle multiplier (1.0 = no throttle):
29
+ * const multiplier = throttle.getCurrentMultiplier();
30
+ */
31
+ const DEFAULT_CONFIG = {
32
+ enabled: false,
33
+ rampDurationMs: 60_000,
34
+ initialRateMultiplier: 0.1,
35
+ rampSteps: 6,
36
+ baselineRatePerMinute: null,
37
+ };
38
+ export class PostReconnectThrottle {
39
+ config;
40
+ throttledSince = null;
41
+ throttledSendCount = 0;
42
+ lifetimeReconnects = 0;
43
+ rampTimer = null;
44
+ currentStep = 0;
45
+ // Tracking sends in current window
46
+ sendsInCurrentWindow = 0;
47
+ currentWindowStart = 0;
48
+ WINDOW_DURATION_MS = 60_000; // 1 minute window
49
+ constructor(config) {
50
+ this.config = {
51
+ ...DEFAULT_CONFIG,
52
+ ...config,
53
+ baselineRatePerMinute: config?.baselineRatePerMinute || null,
54
+ };
55
+ }
56
+ /**
57
+ * Call when connection is re-established. Starts throttle window.
58
+ */
59
+ onReconnect() {
60
+ if (!this.config.enabled)
61
+ return;
62
+ this.throttledSince = Date.now();
63
+ this.currentStep = 0;
64
+ this.throttledSendCount = 0;
65
+ this.lifetimeReconnects++;
66
+ this.sendsInCurrentWindow = 0;
67
+ this.currentWindowStart = Date.now();
68
+ // Clear any existing ramp timer
69
+ if (this.rampTimer) {
70
+ clearTimeout(this.rampTimer);
71
+ }
72
+ // Set up ramp schedule
73
+ this.scheduleNextRampStep();
74
+ }
75
+ /**
76
+ * Call when connection drops (optional — reset state).
77
+ */
78
+ onDisconnect() {
79
+ // Keep throttle state for now — it will expire naturally
80
+ // This prevents rapid reconnect/disconnect cycles from resetting throttle too early
81
+ }
82
+ /**
83
+ * Schedule the next ramp step
84
+ */
85
+ scheduleNextRampStep() {
86
+ if (this.currentStep >= this.config.rampSteps) {
87
+ // Ramp complete — no longer throttled
88
+ this.throttledSince = null;
89
+ this.rampTimer = null;
90
+ return;
91
+ }
92
+ const stepDuration = this.config.rampDurationMs / this.config.rampSteps;
93
+ this.rampTimer = setTimeout(() => {
94
+ this.currentStep++;
95
+ this.scheduleNextRampStep();
96
+ }, stepDuration);
97
+ }
98
+ /**
99
+ * Returns current rate multiplier (1.0 = no throttle)
100
+ */
101
+ getCurrentMultiplier() {
102
+ if (!this.config.enabled || !this.throttledSince) {
103
+ return 1.0;
104
+ }
105
+ const elapsed = Date.now() - this.throttledSince;
106
+ if (elapsed >= this.config.rampDurationMs) {
107
+ // Ramp complete
108
+ return 1.0;
109
+ }
110
+ // Linear ramp from initialRateMultiplier to 1.0 across rampSteps
111
+ const progress = this.currentStep / this.config.rampSteps;
112
+ const multiplier = this.config.initialRateMultiplier +
113
+ (1.0 - this.config.initialRateMultiplier) * progress;
114
+ return Math.min(1.0, multiplier);
115
+ }
116
+ /**
117
+ * Checks if a send should be gated. Returns {allowed, reason, retryAfterMs?}
118
+ */
119
+ beforeSend() {
120
+ if (!this.config.enabled || !this.throttledSince) {
121
+ return { allowed: true };
122
+ }
123
+ const now = Date.now();
124
+ const multiplier = this.getCurrentMultiplier();
125
+ // If fully ramped up, allow all sends
126
+ if (multiplier >= 1.0) {
127
+ this.throttledSince = null;
128
+ return { allowed: true };
129
+ }
130
+ // Reset window if needed
131
+ if (now - this.currentWindowStart >= this.WINDOW_DURATION_MS) {
132
+ this.sendsInCurrentWindow = 0;
133
+ this.currentWindowStart = now;
134
+ }
135
+ // Calculate budget for current window
136
+ const baselineRate = this.config.baselineRatePerMinute ? this.config.baselineRatePerMinute() : 8;
137
+ const allowedInWindow = Math.max(1, Math.floor(baselineRate * multiplier));
138
+ // Check if we're over budget
139
+ if (this.sendsInCurrentWindow >= allowedInWindow) {
140
+ const windowRemaining = this.WINDOW_DURATION_MS - (now - this.currentWindowStart);
141
+ return {
142
+ allowed: false,
143
+ reason: `Post-reconnect throttle: ${Math.floor(multiplier * 100)}% rate (${this.sendsInCurrentWindow}/${allowedInWindow} sends in window)`,
144
+ retryAfterMs: windowRemaining,
145
+ };
146
+ }
147
+ // Allow send and increment counter
148
+ this.sendsInCurrentWindow++;
149
+ this.throttledSendCount++;
150
+ return { allowed: true };
151
+ }
152
+ /**
153
+ * Get current stats
154
+ */
155
+ getStats() {
156
+ const multiplier = this.getCurrentMultiplier();
157
+ const isThrottled = this.throttledSince !== null && multiplier < 1.0;
158
+ const remainingMs = isThrottled && this.throttledSince
159
+ ? Math.max(0, this.config.rampDurationMs - (Date.now() - this.throttledSince))
160
+ : 0;
161
+ return {
162
+ isThrottled,
163
+ currentMultiplier: multiplier,
164
+ throttledSinceMs: this.throttledSince,
165
+ remainingMs,
166
+ throttledSendCount: this.throttledSendCount,
167
+ lifetimeReconnects: this.lifetimeReconnects,
168
+ };
169
+ }
170
+ /**
171
+ * Destroy and clean up timers
172
+ */
173
+ destroy() {
174
+ if (this.rampTimer) {
175
+ clearTimeout(this.rampTimer);
176
+ this.rampTimer = null;
177
+ }
178
+ this.throttledSince = null;
179
+ }
180
+ }
@@ -0,0 +1,161 @@
1
+ /**
2
+ * Reply Ratio Guard — Tracks outbound:inbound ratio per contact
3
+ *
4
+ * WhatsApp's ML models flag accounts that blast messages with low engagement.
5
+ * This module:
6
+ * - Tracks sent/received counts per JID
7
+ * - Blocks sends to non-responsive contacts (ratio collapse)
8
+ * - Suggests auto-replies to maintain healthy inbound/outbound balance
9
+ *
10
+ * Research: 2025-2026 ban waves correlated with <5% reply rates on accounts
11
+ * sending >100 messages/day. This module enforces a configurable floor.
12
+ */
13
+ const DEFAULT_CONFIG = {
14
+ enabled: false,
15
+ minRatio: 0.10,
16
+ minMessagesBeforeEnforce: 5,
17
+ inboundAutoReplyProbability: 0.25,
18
+ autoReplyTemplates: ['👍', '👌', 'ok', 'noted', 'thanks', '🙏', 'got it'],
19
+ cooldownHoursOnViolation: 24,
20
+ scope: 'individual',
21
+ };
22
+ export class ReplyRatioGuard {
23
+ config;
24
+ contacts = new Map();
25
+ constructor(config = {}) {
26
+ this.config = { ...DEFAULT_CONFIG, ...config };
27
+ }
28
+ /**
29
+ * Check if message can be sent to this contact based on reply ratio.
30
+ * Call before sending.
31
+ */
32
+ beforeSend(jid) {
33
+ if (!this.config.enabled) {
34
+ return { allowed: true };
35
+ }
36
+ // Skip groups unless scope === 'all'
37
+ if (this.isGroup(jid) && this.config.scope === 'individual') {
38
+ return { allowed: true };
39
+ }
40
+ const record = this.contacts.get(jid);
41
+ if (!record) {
42
+ // First message to this contact — allow
43
+ return { allowed: true };
44
+ }
45
+ // Check cooldown
46
+ if (record.cooledUntil && Date.now() < record.cooledUntil) {
47
+ const hoursLeft = Math.ceil((record.cooledUntil - Date.now()) / 3600000);
48
+ return {
49
+ allowed: false,
50
+ reason: `Reply ratio cooldown — ${record.sent} sent, ${record.received} received. Retry in ${hoursLeft}h`,
51
+ };
52
+ }
53
+ // Check ratio if we've sent enough messages
54
+ if (record.sent >= this.config.minMessagesBeforeEnforce) {
55
+ const ratio = record.sent === 0 ? 1 : record.received / record.sent;
56
+ if (ratio < this.config.minRatio) {
57
+ // Ratio violation — apply cooldown
58
+ record.cooledUntil = Date.now() + this.config.cooldownHoursOnViolation * 3600000;
59
+ return {
60
+ allowed: false,
61
+ reason: `Reply ratio too low (${(ratio * 100).toFixed(1)}% < ${(this.config.minRatio * 100).toFixed(1)}%). Cooldown ${this.config.cooldownHoursOnViolation}h`,
62
+ };
63
+ }
64
+ }
65
+ return { allowed: true };
66
+ }
67
+ /**
68
+ * Record an outbound message sent to this contact.
69
+ */
70
+ recordSent(jid) {
71
+ if (!this.config.enabled)
72
+ return;
73
+ const record = this.contacts.get(jid) || { sent: 0, received: 0 };
74
+ record.sent++;
75
+ this.contacts.set(jid, record);
76
+ }
77
+ /**
78
+ * Record an inbound message received from this contact.
79
+ */
80
+ recordReceived(jid) {
81
+ if (!this.config.enabled)
82
+ return;
83
+ const record = this.contacts.get(jid) || { sent: 0, received: 0 };
84
+ record.received++;
85
+ // Clear cooldown on incoming message (they replied!)
86
+ delete record.cooledUntil;
87
+ this.contacts.set(jid, record);
88
+ }
89
+ /**
90
+ * Suggest whether to send an auto-reply to this incoming message.
91
+ * Returns { shouldReply: true, suggestedText: '👍' } if probability check passes.
92
+ * Caller is responsible for actually sending the message.
93
+ */
94
+ suggestReply(jid, _msgText) {
95
+ if (!this.config.enabled) {
96
+ return { shouldReply: false };
97
+ }
98
+ // Skip groups unless scope === 'all'
99
+ if (this.isGroup(jid) && this.config.scope === 'individual') {
100
+ return { shouldReply: false };
101
+ }
102
+ // Roll probability
103
+ if (Math.random() < this.config.inboundAutoReplyProbability) {
104
+ const templates = this.config.autoReplyTemplates;
105
+ const suggestedText = templates[Math.floor(Math.random() * templates.length)];
106
+ return { shouldReply: true, suggestedText };
107
+ }
108
+ return { shouldReply: false };
109
+ }
110
+ /**
111
+ * Get statistics for all contacts and global metrics.
112
+ */
113
+ getStats() {
114
+ const perContact = Array.from(this.contacts.entries()).map(([jid, record]) => ({
115
+ jid,
116
+ sent: record.sent,
117
+ received: record.received,
118
+ ratio: record.sent === 0 ? 0 : record.received / record.sent,
119
+ cooledUntil: record.cooledUntil,
120
+ }));
121
+ const globalSent = perContact.reduce((sum, c) => sum + c.sent, 0);
122
+ const globalReceived = perContact.reduce((sum, c) => sum + c.received, 0);
123
+ const globalRatio = globalSent === 0 ? 0 : globalReceived / globalSent;
124
+ const contactsOnCooldown = perContact.filter(c => c.cooledUntil && Date.now() < c.cooledUntil).length;
125
+ return {
126
+ perContact,
127
+ globalSent,
128
+ globalReceived,
129
+ globalRatio,
130
+ contactsOnCooldown,
131
+ };
132
+ }
133
+ /**
134
+ * Reset all counters.
135
+ */
136
+ reset() {
137
+ this.contacts.clear();
138
+ }
139
+ /**
140
+ * Export state for persistence.
141
+ */
142
+ exportState() {
143
+ return {
144
+ contacts: Array.from(this.contacts.entries()),
145
+ };
146
+ }
147
+ /**
148
+ * Restore state from persistence.
149
+ */
150
+ restoreState(state) {
151
+ if (state?.contacts && Array.isArray(state.contacts)) {
152
+ this.contacts = new Map(state.contacts);
153
+ }
154
+ }
155
+ /**
156
+ * Check if JID is a group.
157
+ */
158
+ isGroup(jid) {
159
+ return jid.endsWith('@g.us');
160
+ }
161
+ }