@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,430 @@
1
+ /**
2
+ * Presence Choreographer — Circadian rhythm, distraction pauses, realistic read-receipts
3
+ *
4
+ * WhatsApp's ML models detect accounts with perfect, robotic timing patterns.
5
+ * This module adds realistic temporal variations:
6
+ * - Circadian rhythm: slower at night, faster during business hours
7
+ * - Distraction pauses: random 5-20min pauses (phone put down)
8
+ * - Offline gaps: occasional 5-15min offline periods
9
+ * - Read receipt timing: 3-45s delay, 15% chance to skip
10
+ *
11
+ * Research: 2025 ban analysis showed accounts with <10% timing variance were
12
+ * flagged at 3x rate vs accounts with circadian patterns. Human users have
13
+ * 40-60% variance in hourly activity.
14
+ */
15
+ const DEFAULT_CONFIG = {
16
+ enabled: false,
17
+ enableCircadianRhythm: true,
18
+ timezone: 'UTC',
19
+ activityCurve: 'office',
20
+ circadian: {
21
+ enabled: true,
22
+ profile: 'default',
23
+ timezone: 'UTC',
24
+ },
25
+ distractionPauseProbability: 0.05,
26
+ distractionPauseMinMs: 300000,
27
+ distractionPauseMaxMs: 1200000,
28
+ readReceiptDelayMinMs: 3000,
29
+ readReceiptDelayMaxMs: 45000,
30
+ readReceiptSkipProbability: 0.15,
31
+ offlineGapProbability: 0.03,
32
+ offlineGapMinMs: 300000,
33
+ offlineGapMaxMs: 900000,
34
+ enableTypingModel: true,
35
+ typingWPM: 45,
36
+ typingWPMStdDev: 15,
37
+ thinkPauseProbability: 0.08,
38
+ thinkPauseMinMs: 800,
39
+ thinkPauseMaxMs: 3500,
40
+ intermittentPausedProbability: 0.4,
41
+ typingMaxMs: 90000,
42
+ typingMinMs: 600,
43
+ };
44
+ /**
45
+ * Activity curves (0.1 to 1.0 multipliers by hour)
46
+ * Values are inverted later: higher activity = shorter delays
47
+ */
48
+ const ACTIVITY_CURVES = {
49
+ office: [
50
+ 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, // 0-7: night quiet
51
+ 0.5, 0.5, // 8-9: morning ramp
52
+ 0.95, 0.95, // 10-11: morning peak
53
+ 0.6, // 12: lunch dip
54
+ 0.9, 0.9, 0.9, 0.9, // 13-16: afternoon
55
+ 0.6, 0.6, // 17-18: wind-down
56
+ 0.4, 0.4, // 19-20: evening
57
+ 0.2, 0.2, 0.2, 0.2, // 21-24: taper
58
+ ],
59
+ social: [
60
+ 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, // 0-7: night quiet
61
+ 0.3, 0.4, // 8-9: slow start
62
+ 0.7, 0.8, // 10-11: ramp up
63
+ 0.5, // 12: lunch
64
+ 0.7, 0.7, // 13-14: afternoon
65
+ 0.4, // 15: tea time dip
66
+ 0.8, 0.9, 0.9, // 16-18: active
67
+ 0.6, // 19: dinner dip
68
+ 0.8, 0.85, 0.9, 0.95, 1.0, // 20-24: evening peak
69
+ ],
70
+ global: [
71
+ 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, // 0-5: night
72
+ 0.4, 0.4, // 6-7: dawn dip
73
+ 0.6, 0.7, 0.8, 0.8, // 8-11: morning
74
+ 0.6, // 12: lunch
75
+ 0.8, 0.8, 0.8, 0.8, // 13-16: afternoon
76
+ 0.7, 0.7, // 17-18: evening
77
+ 0.6, 0.5, 0.5, 0.5, 0.5, 0.5, // 19-24: night taper
78
+ ],
79
+ };
80
+ /**
81
+ * Get circadian delay multiplier based on hour of day.
82
+ * Returns a multiplier to apply to base delays (typing, presence, etc.).
83
+ *
84
+ * Multiplier ranges:
85
+ * - Awake hours (09:00-22:00): ~0.8-1.2 (near baseline)
86
+ * - Evening (22:00-00:00): 1.2 → 2.5
87
+ * - Late night (00:00-02:00): 2.5 → 4.0
88
+ * - Dead zone (02:00-06:00): 4.0-6.0 (peak slow)
89
+ * - Early morning (06:00-09:00): 4.0 → 1.0
90
+ *
91
+ * Uses cosine-based smooth transitions (not stepped).
92
+ *
93
+ * @param date - Date to check (uses hour from this)
94
+ * @param profile - Circadian profile ('default' | 'nightOwl' | 'earlyBird' | 'always_on')
95
+ * @param timezone - IANA timezone (optional, defaults to local)
96
+ * @returns Delay multiplier (0.5 = 2x faster, 2.0 = 2x slower, 5.0 = 5x slower)
97
+ */
98
+ export function getCircadianMultiplier(date = new Date(), profile = 'default', timezone) {
99
+ // always_on profile returns flat 1.0
100
+ if (profile === 'always_on') {
101
+ return 1.0;
102
+ }
103
+ // Get hour in specified timezone
104
+ let hour;
105
+ if (timezone) {
106
+ try {
107
+ const formatter = new Intl.DateTimeFormat('en-US', {
108
+ timeZone: timezone,
109
+ hour: 'numeric',
110
+ hour12: false,
111
+ });
112
+ const parts = formatter.formatToParts(date);
113
+ const hourPart = parts.find(p => p.type === 'hour');
114
+ hour = hourPart ? parseInt(hourPart.value, 10) : date.getHours();
115
+ }
116
+ catch {
117
+ hour = date.getHours();
118
+ }
119
+ }
120
+ else {
121
+ hour = date.getHours();
122
+ }
123
+ // Apply profile shift
124
+ let shiftedHour = hour;
125
+ if (profile === 'nightOwl') {
126
+ shiftedHour = (hour - 3 + 24) % 24; // shift +3hr (active until 02:00, dead 04:00-09:00)
127
+ }
128
+ else if (profile === 'earlyBird') {
129
+ shiftedHour = (hour + 2) % 24; // shift -2hr (active 06:00-20:00, dead 23:00-04:00)
130
+ }
131
+ // Cosine-based smooth curve
132
+ // Model: slow at night (02:00-06:00), fast during day (09:00-22:00)
133
+ // Use piecewise cosine for smooth transitions
134
+ if (shiftedHour >= 9 && shiftedHour < 22) {
135
+ // Awake hours: 09:00-22:00 — baseline ~0.8-1.2
136
+ // Add slight variance: cosine wave with period 13 hours
137
+ const t = (shiftedHour - 9) / 13;
138
+ return 1.0 + 0.2 * Math.cos(2 * Math.PI * t);
139
+ }
140
+ else if (shiftedHour >= 22 && shiftedHour < 24) {
141
+ // Evening: 22:00-00:00 — ramp 1.2 → 2.5
142
+ const t = (shiftedHour - 22) / 2;
143
+ return 1.2 + 1.3 * t;
144
+ }
145
+ else if (shiftedHour >= 0 && shiftedHour < 2) {
146
+ // Late night: 00:00-02:00 — ramp 2.5 → 4.0
147
+ const t = shiftedHour / 2;
148
+ return 2.5 + 1.5 * t;
149
+ }
150
+ else if (shiftedHour >= 2 && shiftedHour < 6) {
151
+ // Dead zone: 02:00-06:00 — peak slow 4.0-6.0
152
+ // Use cosine for smooth peak
153
+ const t = (shiftedHour - 2) / 4;
154
+ return 5.0 + 1.0 * Math.cos(Math.PI * t); // peaks at 6.0 around 04:00
155
+ }
156
+ else {
157
+ // Early morning: 06:00-09:00 — ramp 4.0 → 1.0
158
+ const t = (shiftedHour - 6) / 3;
159
+ return 4.0 - 3.0 * t;
160
+ }
161
+ }
162
+ export class PresenceChoreographer {
163
+ config;
164
+ stats = {
165
+ distractionPausesInjected: 0,
166
+ offlineGapsInjected: 0,
167
+ readReceiptsDelayed: 0,
168
+ readReceiptsSkipped: 0,
169
+ typingPlansComputed: 0,
170
+ typingPlansExecuted: 0,
171
+ totalTypingTimeMs: 0,
172
+ };
173
+ constructor(config = {}) {
174
+ this.config = {
175
+ ...DEFAULT_CONFIG,
176
+ ...config,
177
+ circadian: {
178
+ ...DEFAULT_CONFIG.circadian,
179
+ ...(config.circadian || {}),
180
+ },
181
+ };
182
+ }
183
+ /**
184
+ * Get current activity factor (0.1 to 1.0).
185
+ * Higher = more active = shorter delays.
186
+ * If circadian disabled, returns 1.0.
187
+ */
188
+ getCurrentActivityFactor() {
189
+ if (!this.config.enabled || !this.config.enableCircadianRhythm) {
190
+ return 1.0;
191
+ }
192
+ const hour = this.getLocalHour();
193
+ const curve = ACTIVITY_CURVES[this.config.activityCurve];
194
+ return curve[hour] || 0.5;
195
+ }
196
+ /**
197
+ * Check if should pause for distraction.
198
+ * Returns { pause: true, durationMs: 600000 } if probability check passes.
199
+ */
200
+ shouldPauseForDistraction() {
201
+ if (!this.config.enabled) {
202
+ return { pause: false, durationMs: 0 };
203
+ }
204
+ if (Math.random() < this.config.distractionPauseProbability) {
205
+ const durationMs = this.randomBetween(this.config.distractionPauseMinMs, this.config.distractionPauseMaxMs);
206
+ this.stats.distractionPausesInjected++;
207
+ return { pause: true, durationMs };
208
+ }
209
+ return { pause: false, durationMs: 0 };
210
+ }
211
+ /**
212
+ * Check if should take offline gap.
213
+ * Returns { offline: true, durationMs: 600000 } if probability check passes.
214
+ */
215
+ shouldTakeOfflineGap() {
216
+ if (!this.config.enabled) {
217
+ return { offline: false, durationMs: 0 };
218
+ }
219
+ if (Math.random() < this.config.offlineGapProbability) {
220
+ const durationMs = this.randomBetween(this.config.offlineGapMinMs, this.config.offlineGapMaxMs);
221
+ this.stats.offlineGapsInjected++;
222
+ return { offline: true, durationMs };
223
+ }
224
+ return { offline: false, durationMs: 0 };
225
+ }
226
+ /**
227
+ * Check if should mark message as read.
228
+ * Returns { mark: false } if skip probability hit.
229
+ * Returns { mark: true, delayMs: 5000 } otherwise.
230
+ * Applies circadian multiplier to delay.
231
+ */
232
+ shouldMarkRead() {
233
+ if (!this.config.enabled) {
234
+ return { mark: true, delayMs: 0 };
235
+ }
236
+ // Skip read receipt?
237
+ if (Math.random() < this.config.readReceiptSkipProbability) {
238
+ this.stats.readReceiptsSkipped++;
239
+ return { mark: false, delayMs: 0 };
240
+ }
241
+ // Delayed read receipt
242
+ const baseDelayMs = this.randomBetween(this.config.readReceiptDelayMinMs, this.config.readReceiptDelayMaxMs);
243
+ // Apply circadian multiplier if enabled
244
+ let delayMs = baseDelayMs;
245
+ if (this.config.circadian.enabled) {
246
+ const circadianMultiplier = getCircadianMultiplier(new Date(), this.config.circadian.profile, this.config.circadian.timezone);
247
+ delayMs = Math.floor(baseDelayMs * circadianMultiplier);
248
+ }
249
+ this.stats.readReceiptsDelayed++;
250
+ return { mark: true, delayMs };
251
+ }
252
+ /**
253
+ * Compute realistic typing duration for a message of given length.
254
+ * Includes Gaussian WPM variance + think-pause injection + circadian timing multiplier.
255
+ * Returns a "typing plan": array of { state, durationMs } steps the caller should execute sequentially.
256
+ *
257
+ * plan = [
258
+ * { state: 'composing', durationMs: 4200 },
259
+ * { state: 'paused', durationMs: 950 }, // think pause
260
+ * { state: 'composing', durationMs: 6800 },
261
+ * { state: 'paused', durationMs: 600 }, // brief stop before send
262
+ * ]
263
+ */
264
+ computeTypingPlan(messageLength) {
265
+ if (!this.config.enabled || !this.config.enableTypingModel) {
266
+ return [{ state: 'composing', durationMs: this.config.typingMinMs }];
267
+ }
268
+ this.stats.typingPlansComputed++;
269
+ // Handle empty message
270
+ if (messageLength === 0) {
271
+ return [{ state: 'composing', durationMs: this.config.typingMinMs }];
272
+ }
273
+ // 1. Sample WPM from Gaussian distribution
274
+ const wpmSample = this.clamp(this.gaussianSample(this.config.typingWPM, this.config.typingWPMStdDev), 10, 120);
275
+ // 2. Convert to chars/sec (WPM standard: 5 chars/word)
276
+ const cps = (wpmSample * 5) / 60;
277
+ // 3. Base typing time
278
+ const baseMs = (messageLength / cps) * 1000;
279
+ // 4. Apply circadian multiplier if enabled
280
+ let circadianMultiplier = 1.0;
281
+ if (this.config.circadian.enabled) {
282
+ circadianMultiplier = getCircadianMultiplier(new Date(), this.config.circadian.profile, this.config.circadian.timezone);
283
+ }
284
+ // 5. Clamp to min/max
285
+ const targetMs = this.clamp(baseMs * circadianMultiplier, this.config.typingMinMs, this.config.typingMaxMs);
286
+ // 6. Build plan with think pauses
287
+ const plan = [];
288
+ let remainingBudget = targetMs;
289
+ let position = 0;
290
+ // Walk through message in chunks of 10 chars
291
+ const chunkSize = 10;
292
+ const numChunks = Math.max(1, Math.ceil(messageLength / chunkSize));
293
+ for (let i = 0; i < numChunks && remainingBudget > 0; i++) {
294
+ const charsInChunk = Math.min(chunkSize, messageLength - position);
295
+ // Distribute remaining budget proportionally across remaining chunks
296
+ const remainingChunks = numChunks - i;
297
+ const chunkBudget = remainingBudget / remainingChunks;
298
+ const chunkTypingMs = Math.floor(Math.min(chunkBudget, remainingBudget));
299
+ if (chunkTypingMs <= 0)
300
+ break;
301
+ // Should we inject a think pause after this chunk?
302
+ if (i > 0 && i < numChunks - 1 && Math.random() < this.config.thinkPauseProbability) {
303
+ // Add composing step
304
+ plan.push({ state: 'composing', durationMs: chunkTypingMs });
305
+ remainingBudget -= chunkTypingMs;
306
+ // Add think pause (apply circadian multiplier to pause durations too)
307
+ const basePauseMs = this.randomBetween(this.config.thinkPauseMinMs, this.config.thinkPauseMaxMs);
308
+ const pauseMs = Math.floor(basePauseMs * circadianMultiplier);
309
+ plan.push({ state: 'paused', durationMs: pauseMs });
310
+ }
311
+ else {
312
+ // Accumulate typing time
313
+ if (plan.length === 0 || plan[plan.length - 1].state === 'paused') {
314
+ plan.push({ state: 'composing', durationMs: chunkTypingMs });
315
+ }
316
+ else {
317
+ plan[plan.length - 1].durationMs += chunkTypingMs;
318
+ }
319
+ remainingBudget -= chunkTypingMs;
320
+ }
321
+ position += charsInChunk;
322
+ }
323
+ // 7. Optional final pause before send (apply circadian multiplier)
324
+ if (Math.random() < this.config.intermittentPausedProbability) {
325
+ const baseFinalPauseMs = this.randomBetween(200, 800);
326
+ const finalPauseMs = Math.floor(baseFinalPauseMs * circadianMultiplier);
327
+ plan.push({ state: 'paused', durationMs: finalPauseMs });
328
+ }
329
+ // 8. Ensure we have at least one composing step
330
+ if (plan.length === 0 || !plan.some(step => step.state === 'composing')) {
331
+ return [{ state: 'composing', durationMs: this.config.typingMinMs }];
332
+ }
333
+ return plan;
334
+ }
335
+ /**
336
+ * Execute a typing plan against a Baileys-shaped sock with sendPresenceUpdate(state, jid).
337
+ * Awaits each step's duration. Updates stats.
338
+ *
339
+ * await choreo.executeTypingPlan(sock, jid, plan);
340
+ * await sock.sendMessage(jid, content);
341
+ */
342
+ async executeTypingPlan(sock, jid, plan, options) {
343
+ this.stats.typingPlansExecuted++;
344
+ for (const step of plan) {
345
+ // Check abort signal
346
+ if (options?.signal?.aborted) {
347
+ // Restore presence to paused before throwing
348
+ await Promise.resolve(sock.sendPresenceUpdate('paused', jid));
349
+ throw new Error('Typing plan aborted');
350
+ }
351
+ // Update presence
352
+ await Promise.resolve(sock.sendPresenceUpdate(step.state, jid));
353
+ // Sleep for duration
354
+ await this.sleep(step.durationMs);
355
+ // Track total typing time
356
+ this.stats.totalTypingTimeMs += step.durationMs;
357
+ }
358
+ }
359
+ /**
360
+ * Get statistics.
361
+ */
362
+ getStats() {
363
+ return {
364
+ currentActivityFactor: this.getCurrentActivityFactor(),
365
+ distractionPausesInjected: this.stats.distractionPausesInjected,
366
+ offlineGapsInjected: this.stats.offlineGapsInjected,
367
+ readReceiptsDelayed: this.stats.readReceiptsDelayed,
368
+ readReceiptsSkipped: this.stats.readReceiptsSkipped,
369
+ currentHourLocal: this.getLocalHour(),
370
+ typingPlansComputed: this.stats.typingPlansComputed,
371
+ typingPlansExecuted: this.stats.typingPlansExecuted,
372
+ totalTypingTimeMs: this.stats.totalTypingTimeMs,
373
+ };
374
+ }
375
+ /**
376
+ * Reset statistics.
377
+ */
378
+ reset() {
379
+ this.stats = {
380
+ distractionPausesInjected: 0,
381
+ offlineGapsInjected: 0,
382
+ readReceiptsDelayed: 0,
383
+ readReceiptsSkipped: 0,
384
+ typingPlansComputed: 0,
385
+ typingPlansExecuted: 0,
386
+ totalTypingTimeMs: 0,
387
+ };
388
+ }
389
+ // Private helpers
390
+ getLocalHour() {
391
+ try {
392
+ // Use Intl.DateTimeFormat to get local hour in specified timezone
393
+ const formatter = new Intl.DateTimeFormat('en-US', {
394
+ timeZone: this.config.timezone,
395
+ hour: 'numeric',
396
+ hour12: false,
397
+ });
398
+ const parts = formatter.formatToParts(new Date());
399
+ const hourPart = parts.find(p => p.type === 'hour');
400
+ if (hourPart) {
401
+ return parseInt(hourPart.value, 10);
402
+ }
403
+ }
404
+ catch (error) {
405
+ // Timezone not supported — fall back to UTC
406
+ }
407
+ // Fallback to UTC hour
408
+ return new Date().getUTCHours();
409
+ }
410
+ randomBetween(min, max) {
411
+ return Math.floor(Math.random() * (max - min + 1)) + min;
412
+ }
413
+ clamp(value, min, max) {
414
+ return Math.max(min, Math.min(max, value));
415
+ }
416
+ /**
417
+ * Generate Gaussian sample using Box-Muller transform.
418
+ * Returns a sample from N(mean, stdDev).
419
+ */
420
+ gaussianSample(mean, stdDev) {
421
+ // Box-Muller transform
422
+ const u1 = Math.random();
423
+ const u2 = Math.random();
424
+ const z0 = Math.sqrt(-2 * Math.log(u1)) * Math.cos(2 * Math.PI * u2);
425
+ return mean + z0 * stdDev;
426
+ }
427
+ sleep(ms) {
428
+ return new Promise(resolve => setTimeout(resolve, ms));
429
+ }
430
+ }
@@ -0,0 +1,105 @@
1
+ export const PRESETS = {
2
+ conservative: {
3
+ maxPerMinute: 5,
4
+ maxPerHour: 100,
5
+ maxPerDay: 800,
6
+ minDelayMs: 2500,
7
+ maxDelayMs: 7000,
8
+ newChatDelayMs: 4000,
9
+ maxIdenticalMessages: 3,
10
+ identicalMessageWindowMs: 3600000,
11
+ burstAllowance: 3,
12
+ warmupDays: 10,
13
+ day1Limit: 15,
14
+ growthFactor: 1.8,
15
+ inactivityThresholdHours: 72,
16
+ autoPauseAt: 'medium',
17
+ groupMultiplier: 0.5,
18
+ groupProfiles: true,
19
+ logging: true,
20
+ },
21
+ moderate: {
22
+ maxPerMinute: 10,
23
+ maxPerHour: 300,
24
+ maxPerDay: 1500,
25
+ minDelayMs: 1500,
26
+ maxDelayMs: 5000,
27
+ newChatDelayMs: 3000,
28
+ maxIdenticalMessages: 5,
29
+ identicalMessageWindowMs: 3600000,
30
+ burstAllowance: 5,
31
+ warmupDays: 7,
32
+ day1Limit: 20,
33
+ growthFactor: 1.8,
34
+ inactivityThresholdHours: 72,
35
+ autoPauseAt: 'high',
36
+ groupMultiplier: 0.7,
37
+ groupProfiles: true,
38
+ logging: true,
39
+ },
40
+ aggressive: {
41
+ maxPerMinute: 20,
42
+ maxPerHour: 800,
43
+ maxPerDay: 4000,
44
+ minDelayMs: 800,
45
+ maxDelayMs: 3000,
46
+ newChatDelayMs: 2000,
47
+ maxIdenticalMessages: 10,
48
+ identicalMessageWindowMs: 3600000,
49
+ burstAllowance: 8,
50
+ warmupDays: 4,
51
+ day1Limit: 35,
52
+ growthFactor: 2.0,
53
+ inactivityThresholdHours: 48,
54
+ autoPauseAt: 'high',
55
+ groupMultiplier: 0.9,
56
+ groupProfiles: true,
57
+ logging: true,
58
+ },
59
+ // For established, fully-warmed accounts running enterprise-scale operations.
60
+ // Only use on accounts with 6+ months history and no prior bans.
61
+ 'high-volume': {
62
+ maxPerMinute: 40,
63
+ maxPerHour: 1500,
64
+ maxPerDay: 8000,
65
+ minDelayMs: 400,
66
+ maxDelayMs: 1800,
67
+ newChatDelayMs: 1200,
68
+ maxIdenticalMessages: 20,
69
+ identicalMessageWindowMs: 3600000,
70
+ burstAllowance: 15,
71
+ warmupDays: 3,
72
+ day1Limit: 60,
73
+ growthFactor: 2.5,
74
+ inactivityThresholdHours: 24,
75
+ autoPauseAt: 'high', // Pause at HIGH not CRITICAL (too late)
76
+ groupMultiplier: 0.95,
77
+ groupProfiles: true,
78
+ logging: true,
79
+ },
80
+ };
81
+ export function resolveConfig(input) {
82
+ if (input === undefined) {
83
+ return { ...PRESETS.conservative };
84
+ }
85
+ if (typeof input === 'string') {
86
+ if (!(input in PRESETS)) {
87
+ throw new Error(`Unknown preset "${input}". Valid: conservative, moderate, aggressive, high-volume`);
88
+ }
89
+ // Warn if using high-volume preset
90
+ if (input === 'high-volume') {
91
+ console.warn('[baileys-antiban] WARNING: high-volume preset requires an established account (6+ months). Using on new accounts will result in immediate bans. Set accountAgeDays in config to suppress this warning.');
92
+ }
93
+ return { ...PRESETS[input] };
94
+ }
95
+ // Object form — extract preset base, merge overrides
96
+ const { preset = 'conservative', ...overrides } = input;
97
+ if (!(preset in PRESETS)) {
98
+ throw new Error(`Unknown preset "${preset}". Valid: conservative, moderate, aggressive, high-volume`);
99
+ }
100
+ // Warn if using high-volume preset (unless accountAgeDays is set)
101
+ if (preset === 'high-volume' && !('accountAgeDays' in overrides)) {
102
+ console.warn('[baileys-antiban] WARNING: high-volume preset requires an established account (6+ months). Using on new accounts will result in immediate bans. Set accountAgeDays in config to suppress this warning.');
103
+ }
104
+ return { ...PRESETS[preset], ...overrides };
105
+ }
@@ -0,0 +1,31 @@
1
+ /** @g.us = WhatsApp group */
2
+ export function isGroup(jid) {
3
+ return jid.endsWith('@g.us');
4
+ }
5
+ /** @newsletter = WhatsApp newsletter/channel */
6
+ export function isNewsletter(jid) {
7
+ return jid.endsWith('@newsletter');
8
+ }
9
+ /** status@broadcast = broadcast list */
10
+ export function isBroadcast(jid) {
11
+ return jid === 'status@broadcast' || jid.endsWith('@broadcast');
12
+ }
13
+ /**
14
+ * Returns true if the JID should use stricter (group) rate limits.
15
+ * Groups and newsletters both get the group multiplier in v3.
16
+ * v4: separate newsletter profile.
17
+ */
18
+ export function shouldUseGroupProfile(jid) {
19
+ return isGroup(jid) || isNewsletter(jid);
20
+ }
21
+ /**
22
+ * Scale rate limits by multiplier for group/newsletter JIDs.
23
+ * Floors to integer, minimum 1 per limit.
24
+ */
25
+ export function applyGroupMultiplier(limits, multiplier) {
26
+ return {
27
+ maxPerMinute: Math.max(1, Math.floor(limits.maxPerMinute * multiplier)),
28
+ maxPerHour: Math.max(1, Math.floor(limits.maxPerHour * multiplier)),
29
+ maxPerDay: Math.max(1, Math.floor(limits.maxPerDay * multiplier)),
30
+ };
31
+ }