@prototypevip/baileys 0.0.8 → 0.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.
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 +132 -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,339 @@
1
+ /**
2
+ * Topology Throttler — Network topology-based anti-ban enforcement
3
+ *
4
+ * WhatsApp bans based on NETWORK TOPOLOGY, not just message timing:
5
+ * - How fast you expand your contact graph
6
+ * - Cold-contact ratio (strangers vs known contacts)
7
+ * - Reply reciprocity
8
+ * - Group-source clustering (mass-DMing group members)
9
+ *
10
+ * This module enforces graph expansion limits and scores contact risk
11
+ * before each send, acting as the primary enforcement layer for high-risk
12
+ * cold outreach.
13
+ *
14
+ * Key insight: A 30% reply rate is the minimum to unlock more cold sends.
15
+ * Below that, WhatsApp's ML models flag you as a spammer.
16
+ */
17
+ const TIME_CONSTANTS = {
18
+ MS_PER_HOUR: 3600000,
19
+ MS_PER_DAY: 86400000,
20
+ MS_PER_24H: 86400000,
21
+ REPLY_WINDOW_DAYS: 7,
22
+ };
23
+ const DEFAULT_CONFIG = {
24
+ maxNewContactsPerHour: 5,
25
+ maxNewContactsPerDay: 20,
26
+ minReplyRatioForNewContacts: 0.3,
27
+ maxSameGroupContacts: 10,
28
+ maxContactsFromSameSource: 8,
29
+ blockOnLimitReached: true,
30
+ cooldownMs: TIME_CONSTANTS.MS_PER_HOUR,
31
+ riskConfig: {
32
+ firstContactPenalty: 40,
33
+ noReplyPenalty: 20,
34
+ noMutualGroupsPenalty: 15,
35
+ recentContactBonus: -20,
36
+ repliedBeforeBonus: -30,
37
+ delayThreshold: 40,
38
+ abortThreshold: 75,
39
+ },
40
+ };
41
+ const DEFAULT_RISK_CONFIG = {
42
+ firstContactPenalty: 40,
43
+ noReplyPenalty: 20,
44
+ noMutualGroupsPenalty: 15,
45
+ recentContactBonus: -20,
46
+ repliedBeforeBonus: -30,
47
+ delayThreshold: 40,
48
+ abortThreshold: 75,
49
+ };
50
+ export class TopologyThrottler {
51
+ config;
52
+ riskConfig;
53
+ contacts = new Map();
54
+ limits;
55
+ sourceGroupCounts = new Map();
56
+ constructor(config) {
57
+ this.config = { ...DEFAULT_CONFIG, ...config };
58
+ this.riskConfig = { ...DEFAULT_RISK_CONFIG, ...this.config.riskConfig };
59
+ this.limits = {
60
+ newContactsThisHour: 0,
61
+ newContactsToday: 0,
62
+ lastHourResetAt: Date.now(),
63
+ lastDayResetAt: Date.now(),
64
+ };
65
+ }
66
+ /**
67
+ * Assess contact risk before sending.
68
+ * This is the main check — call before every send to a new/unknown contact.
69
+ */
70
+ assessContact(jid, context) {
71
+ this.resetLimitsIfNeeded();
72
+ const record = this.contacts.get(jid);
73
+ const now = Date.now();
74
+ let score = 0;
75
+ const reasons = [];
76
+ // First contact penalty
77
+ if (!record) {
78
+ score += this.riskConfig.firstContactPenalty;
79
+ reasons.push('first_contact');
80
+ }
81
+ else {
82
+ // Has record — check reply history
83
+ if (record.replyTimestamps.length === 0 && record.sendTimestamps.length > 0) {
84
+ score += this.riskConfig.noReplyPenalty;
85
+ reasons.push('no_reply_history');
86
+ }
87
+ }
88
+ // No mutual groups penalty
89
+ if (!context.knownGroups || context.knownGroups.length === 0) {
90
+ score += this.riskConfig.noMutualGroupsPenalty;
91
+ reasons.push('no_mutual_groups');
92
+ }
93
+ // Recent contact bonus
94
+ if (context.lastContactAt && (now - context.lastContactAt) < TIME_CONSTANTS.MS_PER_24H) {
95
+ score += this.riskConfig.recentContactBonus;
96
+ reasons.push('recent_contact');
97
+ }
98
+ // Replied before bonus
99
+ if (context.hasReplied || (record && record.replyTimestamps.length > 0)) {
100
+ score += this.riskConfig.repliedBeforeBonus;
101
+ reasons.push('has_replied');
102
+ }
103
+ // Clamp score to 0-100
104
+ score = Math.max(0, Math.min(100, score));
105
+ // Determine risk level
106
+ let risk;
107
+ let recommendation;
108
+ let suggestedDelayMs;
109
+ if (score >= this.riskConfig.abortThreshold) {
110
+ risk = 'CRITICAL';
111
+ recommendation = 'abort';
112
+ reasons.push('risk_too_high');
113
+ }
114
+ else if (score >= this.riskConfig.delayThreshold) {
115
+ risk = score >= 60 ? 'HIGH' : 'MEDIUM';
116
+ recommendation = 'delay';
117
+ // Exponential delay based on score
118
+ const delayMinutes = Math.floor((score - this.riskConfig.delayThreshold) / 10);
119
+ suggestedDelayMs = delayMinutes * 60000;
120
+ reasons.push('recommend_delay');
121
+ }
122
+ else {
123
+ risk = score >= 30 ? 'MEDIUM' : 'LOW';
124
+ recommendation = 'send';
125
+ }
126
+ return {
127
+ jid,
128
+ risk,
129
+ score,
130
+ reasons,
131
+ recommendation,
132
+ suggestedDelayMs,
133
+ };
134
+ }
135
+ /**
136
+ * Record a sent message to this contact.
137
+ */
138
+ recordSent(jid, sourceGroup) {
139
+ this.resetLimitsIfNeeded();
140
+ const now = Date.now();
141
+ let record = this.contacts.get(jid);
142
+ if (!record) {
143
+ // New contact — increment limits
144
+ record = {
145
+ firstContactAt: now,
146
+ sendTimestamps: [],
147
+ replyTimestamps: [],
148
+ blocked: false,
149
+ sourceGroup,
150
+ };
151
+ this.contacts.set(jid, record);
152
+ this.limits.newContactsThisHour++;
153
+ this.limits.newContactsToday++;
154
+ // Track source group
155
+ if (sourceGroup) {
156
+ const count = this.sourceGroupCounts.get(sourceGroup) || 0;
157
+ this.sourceGroupCounts.set(sourceGroup, count + 1);
158
+ }
159
+ }
160
+ // Add send timestamp (keep sliding window)
161
+ record.sendTimestamps.push(now);
162
+ this.cleanupTimestamps(record.sendTimestamps, TIME_CONSTANTS.REPLY_WINDOW_DAYS * TIME_CONSTANTS.MS_PER_DAY);
163
+ }
164
+ /**
165
+ * Record a reply from this contact.
166
+ */
167
+ recordReplied(jid) {
168
+ const record = this.contacts.get(jid);
169
+ if (!record)
170
+ return;
171
+ const now = Date.now();
172
+ record.replyTimestamps.push(now);
173
+ this.cleanupTimestamps(record.replyTimestamps, TIME_CONSTANTS.REPLY_WINDOW_DAYS * TIME_CONSTANTS.MS_PER_DAY);
174
+ }
175
+ /**
176
+ * Record that this contact blocked you.
177
+ */
178
+ recordBlocked(jid) {
179
+ const record = this.contacts.get(jid);
180
+ if (!record)
181
+ return;
182
+ record.blocked = true;
183
+ }
184
+ /**
185
+ * Check if topology limits allow sending to a new contact.
186
+ * Returns whether allowed and reason/retry time if blocked.
187
+ */
188
+ canSendToNewContact() {
189
+ this.resetLimitsIfNeeded();
190
+ const now = Date.now();
191
+ // Check cooldown
192
+ if (this.limits.limitHitAt) {
193
+ const cooldownEndsAt = this.limits.limitHitAt + this.config.cooldownMs;
194
+ if (now < cooldownEndsAt) {
195
+ const retryAfterMs = cooldownEndsAt - now;
196
+ return {
197
+ allowed: false,
198
+ reason: `Cooldown active — limit hit recently`,
199
+ retryAfterMs,
200
+ };
201
+ }
202
+ else {
203
+ // Cooldown expired
204
+ delete this.limits.limitHitAt;
205
+ }
206
+ }
207
+ // Check hourly limit
208
+ if (this.limits.newContactsThisHour >= this.config.maxNewContactsPerHour) {
209
+ this.limits.limitHitAt = now;
210
+ const retryAfterMs = (this.limits.lastHourResetAt + TIME_CONSTANTS.MS_PER_HOUR) - now;
211
+ return {
212
+ allowed: false,
213
+ reason: `Hourly new contact limit reached (${this.config.maxNewContactsPerHour})`,
214
+ retryAfterMs: Math.max(0, retryAfterMs),
215
+ };
216
+ }
217
+ // Check daily limit
218
+ if (this.limits.newContactsToday >= this.config.maxNewContactsPerDay) {
219
+ this.limits.limitHitAt = now;
220
+ const retryAfterMs = (this.limits.lastDayResetAt + TIME_CONSTANTS.MS_PER_DAY) - now;
221
+ return {
222
+ allowed: false,
223
+ reason: `Daily new contact limit reached (${this.config.maxNewContactsPerDay})`,
224
+ retryAfterMs: Math.max(0, retryAfterMs),
225
+ };
226
+ }
227
+ // Check reply ratio requirement
228
+ const replyRatio = this.calculateReplyRatio();
229
+ if (replyRatio !== null && replyRatio < this.config.minReplyRatioForNewContacts) {
230
+ // Poor reply ratio — need to improve engagement before more cold sends
231
+ return {
232
+ allowed: false,
233
+ reason: `Reply ratio too low (${Math.round(replyRatio * 100)}% < ${Math.round(this.config.minReplyRatioForNewContacts * 100)}%)`,
234
+ retryAfterMs: TIME_CONSTANTS.MS_PER_HOUR, // arbitrary — user needs to improve engagement
235
+ };
236
+ }
237
+ return { allowed: true };
238
+ }
239
+ /**
240
+ * Get topology statistics.
241
+ */
242
+ getTopologyStats() {
243
+ this.resetLimitsIfNeeded();
244
+ const replyRatio = this.calculateReplyRatio();
245
+ const blockedRatio = this.calculateBlockedRatio();
246
+ // Get top 5 source group hotspots
247
+ const hotspots = Array.from(this.sourceGroupCounts.entries())
248
+ .map(([sourceGroup, count]) => ({ sourceGroup, count }))
249
+ .sort((a, b) => b.count - a.count)
250
+ .slice(0, 5);
251
+ return {
252
+ newContactsThisHour: this.limits.newContactsThisHour,
253
+ newContactsToday: this.limits.newContactsToday,
254
+ replyRatio,
255
+ blockedRatio,
256
+ hotspots,
257
+ };
258
+ }
259
+ /**
260
+ * Export state for persistence.
261
+ */
262
+ exportState() {
263
+ return {
264
+ contacts: Array.from(this.contacts.entries()),
265
+ limits: { ...this.limits },
266
+ sourceGroupCounts: Array.from(this.sourceGroupCounts.entries()),
267
+ };
268
+ }
269
+ /**
270
+ * Import state from persistence.
271
+ */
272
+ importState(state) {
273
+ if (state.contacts) {
274
+ this.contacts = new Map(state.contacts);
275
+ }
276
+ if (state.limits) {
277
+ this.limits = { ...state.limits };
278
+ }
279
+ if (state.sourceGroupCounts) {
280
+ this.sourceGroupCounts = new Map(state.sourceGroupCounts);
281
+ }
282
+ }
283
+ // Private helpers
284
+ resetLimitsIfNeeded() {
285
+ const now = Date.now();
286
+ // Reset hourly counter
287
+ if (now - this.limits.lastHourResetAt >= TIME_CONSTANTS.MS_PER_HOUR) {
288
+ this.limits.newContactsThisHour = 0;
289
+ this.limits.lastHourResetAt = now;
290
+ }
291
+ // Reset daily counter
292
+ if (now - this.limits.lastDayResetAt >= TIME_CONSTANTS.MS_PER_DAY) {
293
+ this.limits.newContactsToday = 0;
294
+ this.limits.lastDayResetAt = now;
295
+ // Clear source group counts daily
296
+ this.sourceGroupCounts.clear();
297
+ }
298
+ }
299
+ calculateReplyRatio() {
300
+ const now = Date.now();
301
+ const windowMs = TIME_CONSTANTS.REPLY_WINDOW_DAYS * TIME_CONSTANTS.MS_PER_DAY;
302
+ let totalSent = 0;
303
+ let totalReplies = 0;
304
+ for (const record of this.contacts.values()) {
305
+ // Count sends in window
306
+ const recentSends = record.sendTimestamps.filter(t => now - t < windowMs);
307
+ totalSent += recentSends.length;
308
+ // Count replies in window
309
+ const recentReplies = record.replyTimestamps.filter(t => now - t < windowMs);
310
+ totalReplies += recentReplies.length;
311
+ }
312
+ if (totalSent === 0)
313
+ return null; // No data yet
314
+ return totalReplies / totalSent;
315
+ }
316
+ calculateBlockedRatio() {
317
+ const totalContacts = this.contacts.size;
318
+ if (totalContacts === 0)
319
+ return null;
320
+ let blockedCount = 0;
321
+ for (const record of this.contacts.values()) {
322
+ if (record.blocked)
323
+ blockedCount++;
324
+ }
325
+ return blockedCount / totalContacts;
326
+ }
327
+ cleanupTimestamps(timestamps, maxAgeMs) {
328
+ const now = Date.now();
329
+ const cutoff = now - maxAgeMs;
330
+ // Remove old timestamps (in-place filter)
331
+ let writeIdx = 0;
332
+ for (let readIdx = 0; readIdx < timestamps.length; readIdx++) {
333
+ if (timestamps[readIdx] >= cutoff) {
334
+ timestamps[writeIdx++] = timestamps[readIdx];
335
+ }
336
+ }
337
+ timestamps.length = writeIdx;
338
+ }
339
+ }
@@ -0,0 +1,140 @@
1
+ /**
2
+ * WarmUp — Gradual activity increase for new/reconnected numbers
3
+ *
4
+ * New numbers or numbers reconnecting after a break are under extra scrutiny.
5
+ * This module enforces a gradual ramp-up of messaging activity.
6
+ *
7
+ * WhatsApp flags:
8
+ * - New numbers sending many messages immediately
9
+ * - Numbers going from 0 to 100 messages/day overnight
10
+ * - Sudden pattern changes after period of inactivity
11
+ */
12
+ /** Generate a random growth factor in [1.5, 2.2] to avoid cross-account clustering */
13
+ function randomGrowthFactor() {
14
+ return Math.round((1.5 + Math.random() * 0.7) * 100) / 100;
15
+ }
16
+ const DEFAULT_CONFIG = {
17
+ warmUpDays: 7,
18
+ day1Limit: 20,
19
+ growthFactor: randomGrowthFactor(), // intentionally non-deterministic
20
+ inactivityThresholdHours: 72,
21
+ };
22
+ export class WarmUp {
23
+ config;
24
+ state;
25
+ constructor(config = {}, existingState) {
26
+ this.config = { ...DEFAULT_CONFIG, ...config };
27
+ this.state = existingState || this.freshState();
28
+ }
29
+ /**
30
+ * Get the current daily message limit based on warm-up phase
31
+ */
32
+ getDailyLimit() {
33
+ if (this.state.graduated)
34
+ return Infinity;
35
+ const day = this.getCurrentDay();
36
+ if (day >= this.config.warmUpDays) {
37
+ this.state.graduated = true;
38
+ return Infinity;
39
+ }
40
+ return Math.round(this.config.day1Limit * Math.pow(this.config.growthFactor, day));
41
+ }
42
+ /**
43
+ * Check if a message can be sent (within warm-up limits)
44
+ */
45
+ canSend() {
46
+ this.checkInactivity();
47
+ if (this.state.graduated)
48
+ return true;
49
+ const day = this.getCurrentDay();
50
+ const todayCount = this.state.dailyCounts[day] || 0;
51
+ return todayCount < this.getDailyLimit();
52
+ }
53
+ /**
54
+ * Record a sent message
55
+ */
56
+ record() {
57
+ const now = Date.now();
58
+ const day = this.getCurrentDay();
59
+ while (this.state.dailyCounts.length <= day) {
60
+ this.state.dailyCounts.push(0);
61
+ }
62
+ this.state.dailyCounts[day]++;
63
+ this.state.lastActiveAt = now;
64
+ }
65
+ /**
66
+ * Get current warm-up status
67
+ */
68
+ getStatus() {
69
+ const day = this.getCurrentDay();
70
+ const todaySent = this.state.dailyCounts[day] || 0;
71
+ const limit = this.getDailyLimit();
72
+ return {
73
+ phase: this.state.graduated ? 'graduated' : 'warming',
74
+ day: Math.min(day + 1, this.config.warmUpDays),
75
+ totalDays: this.config.warmUpDays,
76
+ todayLimit: limit === Infinity ? -1 : limit,
77
+ todaySent,
78
+ progress: this.state.graduated ? 100 : Math.round((day / this.config.warmUpDays) * 100),
79
+ };
80
+ }
81
+ /**
82
+ * Export state for persistence
83
+ */
84
+ exportState() {
85
+ const day = this.getCurrentDay();
86
+ const todaySent = this.state.dailyCounts[day] || 0;
87
+ const todayDate = new Date().toISOString().split('T')[0]; // YYYY-MM-DD
88
+ return {
89
+ ...this.state,
90
+ todaySentCount: todaySent,
91
+ todayDate,
92
+ };
93
+ }
94
+ /**
95
+ * Import state from persistence
96
+ */
97
+ importState(state) {
98
+ this.state = { ...state };
99
+ // BUG FIX 1: Restore today's send count from persisted state
100
+ // If todayDate matches current date, restore the count to prevent
101
+ // post-crash burst (issue: warmup day counter survives but send history lost)
102
+ if (state.todayDate && state.todaySentCount !== undefined) {
103
+ const todayDate = new Date().toISOString().split('T')[0];
104
+ if (state.todayDate === todayDate) {
105
+ const day = this.getCurrentDay();
106
+ // Restore the persisted count if higher than current
107
+ while (this.state.dailyCounts.length <= day) {
108
+ this.state.dailyCounts.push(0);
109
+ }
110
+ this.state.dailyCounts[day] = Math.max(this.state.dailyCounts[day] || 0, state.todaySentCount);
111
+ }
112
+ }
113
+ }
114
+ /**
115
+ * Reset warm-up (e.g., after detected ban risk)
116
+ */
117
+ reset() {
118
+ this.state = this.freshState();
119
+ }
120
+ getCurrentDay() {
121
+ return Math.floor((Date.now() - this.state.startedAt) / 86400000);
122
+ }
123
+ checkInactivity() {
124
+ const hoursSinceActive = (Date.now() - this.state.lastActiveAt) / 3600000;
125
+ if (hoursSinceActive > this.config.inactivityThresholdHours && this.state.graduated) {
126
+ // Re-enter warm-up after extended inactivity
127
+ this.state = this.freshState();
128
+ this.state.graduated = false;
129
+ }
130
+ }
131
+ freshState() {
132
+ const now = Date.now();
133
+ return {
134
+ startedAt: now,
135
+ lastActiveAt: now,
136
+ dailyCounts: [],
137
+ graduated: false,
138
+ };
139
+ }
140
+ }
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Webhook Alerts — Get notified on Telegram/Discord/Slack when risk changes
3
+ */
4
+ const DEFAULT_CONFIG = {
5
+ urls: [],
6
+ minRiskLevel: 'medium',
7
+ cooldownMs: 300000,
8
+ includeStats: true,
9
+ };
10
+ export class WebhookAlerts {
11
+ config;
12
+ lastAlertTime = 0;
13
+ constructor(config = {}) {
14
+ this.config = { ...DEFAULT_CONFIG, ...config };
15
+ }
16
+ /**
17
+ * Send alert if risk level warrants it
18
+ */
19
+ async alert(data) {
20
+ const riskOrder = ['low', 'medium', 'high', 'critical'];
21
+ if (riskOrder.indexOf(data.risk) < riskOrder.indexOf(this.config.minRiskLevel)) {
22
+ return; // Below threshold
23
+ }
24
+ const now = Date.now();
25
+ if (now - this.lastAlertTime < this.config.cooldownMs) {
26
+ return; // Cooldown active
27
+ }
28
+ this.lastAlertTime = now;
29
+ const payload = {
30
+ source: 'baileys-antiban',
31
+ timestamp: new Date().toISOString(),
32
+ ...data,
33
+ };
34
+ // Send to generic webhook URLs
35
+ for (const url of this.config.urls) {
36
+ this.postWebhook(url, payload).catch(() => { });
37
+ }
38
+ // Telegram
39
+ if (this.config.telegram) {
40
+ const emoji = { low: '🟢', medium: '🟡', high: '🟠', critical: '🔴' }[data.risk] || '⚪';
41
+ const text = `${emoji} *baileys-antiban Alert*\n\nRisk: *${data.risk.toUpperCase()}* (score: ${data.score})\n${data.recommendation}\n\nReasons:\n${data.reasons.map(r => `• ${r}`).join('\n')}`;
42
+ this.postWebhook(`https://api.telegram.org/bot${this.config.telegram.botToken}/sendMessage`, { chat_id: this.config.telegram.chatId, text, parse_mode: 'Markdown' }).catch(() => { });
43
+ }
44
+ // Discord
45
+ if (this.config.discord) {
46
+ const color = { low: 0x00ff00, medium: 0xffff00, high: 0xff8800, critical: 0xff0000 }[data.risk] || 0;
47
+ this.postWebhook(this.config.discord.webhookUrl, {
48
+ embeds: [{
49
+ title: '🛡️ baileys-antiban Alert',
50
+ color,
51
+ fields: [
52
+ { name: 'Risk', value: data.risk.toUpperCase(), inline: true },
53
+ { name: 'Score', value: String(data.score), inline: true },
54
+ { name: 'Recommendation', value: data.recommendation },
55
+ { name: 'Reasons', value: data.reasons.join('\n') },
56
+ ],
57
+ timestamp: new Date().toISOString(),
58
+ }],
59
+ }).catch(() => { });
60
+ }
61
+ }
62
+ async postWebhook(url, payload) {
63
+ try {
64
+ const response = await fetch(url, {
65
+ method: 'POST',
66
+ headers: {
67
+ 'Content-Type': 'application/json',
68
+ ...this.config.headers,
69
+ },
70
+ body: JSON.stringify(payload),
71
+ });
72
+ if (!response.ok) {
73
+ console.error(`[baileys-antiban] Webhook failed: ${response.status}`);
74
+ }
75
+ }
76
+ catch (err) {
77
+ console.error(`[baileys-antiban] Webhook error:`, err);
78
+ }
79
+ }
80
+ }