@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.
- package/lib/AntiBan/antiban.js +922 -0
- package/lib/AntiBan/banRecoveryOrchestrator.js +284 -0
- package/lib/AntiBan/cli.js +177 -0
- package/lib/AntiBan/contactGraph.js +236 -0
- package/lib/AntiBan/contentVariator.js +150 -0
- package/lib/AntiBan/credsSnapshot.js +121 -0
- package/lib/AntiBan/deliveryTracker.js +107 -0
- package/lib/AntiBan/deviceFingerprint.js +106 -0
- package/lib/AntiBan/fleetEventStore.js +157 -0
- package/lib/AntiBan/groupOperationGuard.js +116 -0
- package/lib/AntiBan/health.js +207 -0
- package/lib/AntiBan/humanEntropy.js +337 -0
- package/lib/AntiBan/index.js +77 -0
- package/lib/AntiBan/instanceCoordinator.js +175 -0
- package/lib/AntiBan/jidCanonicalizer.js +256 -0
- package/lib/AntiBan/jidCircuitBreaker.js +186 -0
- package/lib/AntiBan/legitimacySignalInjector.js +231 -0
- package/lib/AntiBan/lidFirstResolver.js +174 -0
- package/lib/AntiBan/lidResolver.js +324 -0
- package/lib/AntiBan/messageQueue.js +187 -0
- package/lib/AntiBan/messageRecovery.js +283 -0
- package/lib/AntiBan/messageTypeRegistry.js +369 -0
- package/lib/AntiBan/observability.js +145 -0
- package/lib/AntiBan/patch.js +302 -0
- package/lib/AntiBan/persist.js +87 -0
- package/lib/AntiBan/presenceChoreographer.js +430 -0
- package/lib/AntiBan/presets.js +105 -0
- package/lib/AntiBan/profiles.js +31 -0
- package/lib/AntiBan/proxyRotator.js +306 -0
- package/lib/AntiBan/rateLimiter.js +261 -0
- package/lib/AntiBan/readReceiptVariance.js +88 -0
- package/lib/AntiBan/reconnectThrottle.js +180 -0
- package/lib/AntiBan/replyRatio.js +161 -0
- package/lib/AntiBan/reputationVoucher.js +373 -0
- package/lib/AntiBan/retryReason.js +91 -0
- package/lib/AntiBan/retryTracker.js +172 -0
- package/lib/AntiBan/scheduler.js +111 -0
- package/lib/AntiBan/sessionFingerprint.js +248 -0
- package/lib/AntiBan/sessionStability.js +330 -0
- package/lib/AntiBan/stateAdapter.js +73 -0
- package/lib/AntiBan/stateExport.js +152 -0
- package/lib/AntiBan/stealthConnect.js +130 -0
- package/lib/AntiBan/timelockGuard.js +181 -0
- package/lib/AntiBan/topologyThrottler.js +339 -0
- package/lib/AntiBan/warmup.js +140 -0
- package/lib/AntiBan/webhooks.js +80 -0
- package/lib/AntiBan/wrapper.js +456 -0
- package/lib/Socket/groups.js +26 -21
- package/lib/Socket/index.js +132 -7
- package/lib/Socket/messages-send.js +68 -5
- package/lib/Utils/ai-rich-formatter.js +256 -0
- package/lib/Utils/index.js +1 -0
- package/lib/index.js +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,456 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Socket Wrapper — Drop-in replacement that wraps sendMessage with anti-ban protection
|
|
3
|
+
*
|
|
4
|
+
* Works with both baileys and @oxidezap/baileyrs transports.
|
|
5
|
+
*
|
|
6
|
+
* Usage with baileys:
|
|
7
|
+
* import makeWASocket from 'baileys';
|
|
8
|
+
* import { wrapSocket } from 'baileys-antiban';
|
|
9
|
+
*
|
|
10
|
+
* const sock = makeWASocket({ ... });
|
|
11
|
+
* const safeSock = wrapSocket(sock);
|
|
12
|
+
*
|
|
13
|
+
* Usage with baileyrs:
|
|
14
|
+
* import { makeWASocket } from '@oxidezap/baileyrs';
|
|
15
|
+
* import { wrapSocket } from 'baileys-antiban';
|
|
16
|
+
*
|
|
17
|
+
* const sock = makeWASocket({ ... });
|
|
18
|
+
* const safeSock = wrapSocket(sock);
|
|
19
|
+
*
|
|
20
|
+
* // Use safeSock.sendMessage() — automatically rate-limited and monitored
|
|
21
|
+
* await safeSock.sendMessage(jid, { text: 'Hello!' });
|
|
22
|
+
*
|
|
23
|
+
* // Check health anytime
|
|
24
|
+
* console.log(safeSock.antiban.getStats());
|
|
25
|
+
*
|
|
26
|
+
* Note: reachoutTimeLock timelock module silently noops on baileyrs until upstream
|
|
27
|
+
* emits reachoutTimeLock events — confirmed NOT present in baileyrs v0.0.8.
|
|
28
|
+
* Timelock guard will operate in detection-only mode (relies on 463 errors only).
|
|
29
|
+
*/
|
|
30
|
+
import { AntiBan } from './antiban.js';
|
|
31
|
+
import { DeafSessionDetector } from './sessionStability.js';
|
|
32
|
+
import { GroupOperationGuard } from './groupOperationGuard.js';
|
|
33
|
+
import { LegitimacySignalInjector } from './legitimacySignalInjector.js';
|
|
34
|
+
/**
|
|
35
|
+
* Wrap a Baileys socket with anti-ban protection.
|
|
36
|
+
* The returned socket has the same API but sendMessage() is protected.
|
|
37
|
+
*/
|
|
38
|
+
export function wrapSocket(sock, config, warmUpState, wrapOptions) {
|
|
39
|
+
const antiban = new AntiBan(config, warmUpState);
|
|
40
|
+
const options = {
|
|
41
|
+
autoRespondToIncoming: false,
|
|
42
|
+
...wrapOptions,
|
|
43
|
+
};
|
|
44
|
+
// Deaf session detector — optional, enabled via wrapOptions.deafSession
|
|
45
|
+
const deafDetector = options.deafSession
|
|
46
|
+
? new DeafSessionDetector(options.deafSession)
|
|
47
|
+
: null;
|
|
48
|
+
if (deafDetector)
|
|
49
|
+
deafDetector.attach(sock);
|
|
50
|
+
// Group operation guard — intercept group methods with rate limiting
|
|
51
|
+
if (options.groupOpGuard !== false) {
|
|
52
|
+
const guard = new GroupOperationGuard(options.groupOpGuard || {});
|
|
53
|
+
const origUpdate = sock.groupParticipantsUpdate?.bind(sock);
|
|
54
|
+
if (origUpdate) {
|
|
55
|
+
sock.groupParticipantsUpdate = async (jid, participants, action) => {
|
|
56
|
+
const op = action === 'add' ? 'add' : action === 'remove' ? 'remove' : 'add';
|
|
57
|
+
const check = guard.check(op, jid);
|
|
58
|
+
if (!check.allowed)
|
|
59
|
+
throw new Error(check.reason || 'Group operation rate limited');
|
|
60
|
+
return origUpdate(jid, participants, action);
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
const origCreate = sock.groupCreate?.bind(sock);
|
|
64
|
+
if (origCreate) {
|
|
65
|
+
sock.groupCreate = async (subject, participants) => {
|
|
66
|
+
const check = guard.check('create', subject);
|
|
67
|
+
if (!check.allowed)
|
|
68
|
+
throw new Error(check.reason || 'Group creation rate limited');
|
|
69
|
+
return origCreate(subject, participants);
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
// Legitimacy signal injector — wrap sendMessage for typo injection
|
|
74
|
+
let legitimacyInjector = null;
|
|
75
|
+
if (options.legitimacySignals !== false) {
|
|
76
|
+
legitimacyInjector = new LegitimacySignalInjector(typeof options.legitimacySignals === 'object' ? options.legitimacySignals : {});
|
|
77
|
+
}
|
|
78
|
+
// Hook into Baileys events for health monitoring
|
|
79
|
+
// Prefer ev.process() (Baileys ≥ late 2022) for batched event handling
|
|
80
|
+
// Fall back to ev.on() for older versions
|
|
81
|
+
if (typeof sock.ev.process === 'function') {
|
|
82
|
+
sock.ev.process(async (events) => {
|
|
83
|
+
// Handle connection updates
|
|
84
|
+
if (events['connection.update']) {
|
|
85
|
+
const update = events['connection.update'];
|
|
86
|
+
if (update.connection === 'close') {
|
|
87
|
+
const reason = update.lastDisconnect?.error?.output?.statusCode || 'unknown';
|
|
88
|
+
antiban.onDisconnect(reason);
|
|
89
|
+
antiban.destroy(); // Clean up all timers
|
|
90
|
+
deafDetector?.onDisconnect();
|
|
91
|
+
deafDetector?.destroy();
|
|
92
|
+
// Fleet event: detect ban-like disconnect codes
|
|
93
|
+
if (options.fleetEventStore) {
|
|
94
|
+
const banCodes = [401, 403, 428, 515]; // 401=logged out, 403=forbidden, 428=banned, 515=restart required
|
|
95
|
+
if (typeof reason === 'number' && banCodes.includes(reason)) {
|
|
96
|
+
void options.fleetEventStore.emit('ban', { statusCode: reason });
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
if (update.connection === 'open') {
|
|
101
|
+
antiban.onReconnect();
|
|
102
|
+
deafDetector?.onConnect();
|
|
103
|
+
// Fleet event: recovery from previous ban/warn
|
|
104
|
+
if (options.fleetEventStore) {
|
|
105
|
+
const health = antiban.getStats().health;
|
|
106
|
+
if (health.risk === 'low') {
|
|
107
|
+
void options.fleetEventStore.emit('recovery', { risk: health.risk });
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
// Reachout timelock detection
|
|
112
|
+
if (update.reachoutTimeLock) {
|
|
113
|
+
antiban.timelock.onTimelockUpdate({
|
|
114
|
+
isActive: update.reachoutTimeLock.isActive,
|
|
115
|
+
timeEnforcementEnds: update.reachoutTimeLock.timeEnforcementEnds,
|
|
116
|
+
enforcementType: update.reachoutTimeLock.enforcementType,
|
|
117
|
+
});
|
|
118
|
+
// Fleet event: timelock
|
|
119
|
+
if (options.fleetEventStore && update.reachoutTimeLock.isActive) {
|
|
120
|
+
void options.fleetEventStore.emit('timelock', {
|
|
121
|
+
enforcementType: update.reachoutTimeLock.enforcementType,
|
|
122
|
+
endsAt: update.reachoutTimeLock.timeEnforcementEnds,
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
// Catch 463 errors from message updates + track retries + learn LID mappings
|
|
128
|
+
if (events['messages.update']) {
|
|
129
|
+
const updates = events['messages.update'];
|
|
130
|
+
deafDetector?.onMessageActivity();
|
|
131
|
+
for (const update of updates) {
|
|
132
|
+
// 463 error detection
|
|
133
|
+
if (update?.update?.messageStubParameters) {
|
|
134
|
+
const params = Array.isArray(update.update.messageStubParameters)
|
|
135
|
+
? update.update.messageStubParameters
|
|
136
|
+
: [];
|
|
137
|
+
if (params.includes(463) || params.includes('463')) {
|
|
138
|
+
antiban.timelock.record463Error();
|
|
139
|
+
// Fleet event: rate limit detected
|
|
140
|
+
if (options.fleetEventStore) {
|
|
141
|
+
void options.fleetEventStore.emit('rate_limit', { error: '463' });
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
// Retry tracking
|
|
146
|
+
antiban.retryTracker.onMessageUpdate(update);
|
|
147
|
+
// Delivery receipt tracking
|
|
148
|
+
const status = update?.update?.status;
|
|
149
|
+
const msgId = update?.key?.id;
|
|
150
|
+
// status 3 = DELIVERY_ACK (double tick), status 4 = READ
|
|
151
|
+
if (msgId && (status === 3 || status === 4)) {
|
|
152
|
+
antiban.onDeliveryReceipt(msgId);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
// LID canonicalizer learning
|
|
156
|
+
antiban.jidCanonicalizer?.onMessageUpdate(updates);
|
|
157
|
+
}
|
|
158
|
+
// Register known chats from incoming messages + handle reply suggestions + learn LID mappings
|
|
159
|
+
if (events['messages.upsert']) {
|
|
160
|
+
const { messages } = events['messages.upsert'];
|
|
161
|
+
deafDetector?.onMessageActivity();
|
|
162
|
+
// Learn LID mappings FIRST (before any other processing)
|
|
163
|
+
antiban.jidCanonicalizer?.onIncomingEvent(events['messages.upsert']);
|
|
164
|
+
for (const msg of messages || []) {
|
|
165
|
+
const jid = msg.key?.remoteJid;
|
|
166
|
+
if (!jid)
|
|
167
|
+
continue;
|
|
168
|
+
// Register known chat
|
|
169
|
+
antiban.timelock.registerKnownChat(jid);
|
|
170
|
+
// Skip self messages
|
|
171
|
+
const isSelf = msg.key?.fromMe || false;
|
|
172
|
+
if (isSelf)
|
|
173
|
+
continue;
|
|
174
|
+
// Extract message text
|
|
175
|
+
const msgText = msg.message?.conversation ||
|
|
176
|
+
msg.message?.extendedTextMessage?.text ||
|
|
177
|
+
msg.message?.imageMessage?.caption ||
|
|
178
|
+
msg.message?.videoMessage?.caption ||
|
|
179
|
+
'';
|
|
180
|
+
// Handle incoming message (updates reply ratio + contact graph)
|
|
181
|
+
const replySuggestion = antiban.onIncomingMessage(jid, msgText);
|
|
182
|
+
// Auto-respond if enabled and suggested
|
|
183
|
+
if (options.autoRespondToIncoming && replySuggestion.shouldReply && replySuggestion.suggestedText) {
|
|
184
|
+
// Random delay 3-15s
|
|
185
|
+
const replyDelay = Math.floor(Math.random() * 12000) + 3000;
|
|
186
|
+
setTimeout(async () => {
|
|
187
|
+
try {
|
|
188
|
+
await sock.sendMessage(jid, { text: replySuggestion.suggestedText });
|
|
189
|
+
}
|
|
190
|
+
catch (error) {
|
|
191
|
+
// Silently fail — auto-reply is best-effort
|
|
192
|
+
}
|
|
193
|
+
}, replyDelay);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
else {
|
|
200
|
+
// Fallback to ev.on() for older Baileys versions
|
|
201
|
+
sock.ev.on('connection.update', (update) => {
|
|
202
|
+
if (update.connection === 'close') {
|
|
203
|
+
const reason = update.lastDisconnect?.error?.output?.statusCode || 'unknown';
|
|
204
|
+
antiban.onDisconnect(reason);
|
|
205
|
+
antiban.destroy(); // Clean up all timers
|
|
206
|
+
deafDetector?.onDisconnect();
|
|
207
|
+
deafDetector?.destroy();
|
|
208
|
+
// Fleet event: detect ban-like disconnect codes
|
|
209
|
+
if (options.fleetEventStore) {
|
|
210
|
+
const banCodes = [401, 403, 428, 515]; // 401=logged out, 403=forbidden, 428=banned, 515=restart required
|
|
211
|
+
if (typeof reason === 'number' && banCodes.includes(reason)) {
|
|
212
|
+
void options.fleetEventStore.emit('ban', { statusCode: reason });
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
if (update.connection === 'open') {
|
|
217
|
+
antiban.onReconnect();
|
|
218
|
+
deafDetector?.onConnect();
|
|
219
|
+
// Fleet event: recovery from previous ban/warn
|
|
220
|
+
if (options.fleetEventStore) {
|
|
221
|
+
const health = antiban.getStats().health;
|
|
222
|
+
if (health.risk === 'low') {
|
|
223
|
+
void options.fleetEventStore.emit('recovery', { risk: health.risk });
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
// Reachout timelock detection
|
|
228
|
+
if (update.reachoutTimeLock) {
|
|
229
|
+
antiban.timelock.onTimelockUpdate({
|
|
230
|
+
isActive: update.reachoutTimeLock.isActive,
|
|
231
|
+
timeEnforcementEnds: update.reachoutTimeLock.timeEnforcementEnds,
|
|
232
|
+
enforcementType: update.reachoutTimeLock.enforcementType,
|
|
233
|
+
});
|
|
234
|
+
// Fleet event: timelock
|
|
235
|
+
if (options.fleetEventStore && update.reachoutTimeLock.isActive) {
|
|
236
|
+
void options.fleetEventStore.emit('timelock', {
|
|
237
|
+
enforcementType: update.reachoutTimeLock.enforcementType,
|
|
238
|
+
endsAt: update.reachoutTimeLock.timeEnforcementEnds,
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
});
|
|
243
|
+
// Catch 463 errors from message updates + track retries + learn LID mappings
|
|
244
|
+
sock.ev.on('messages.update', (updates) => {
|
|
245
|
+
deafDetector?.onMessageActivity();
|
|
246
|
+
for (const update of updates) {
|
|
247
|
+
// 463 error detection
|
|
248
|
+
if (update?.update?.messageStubParameters) {
|
|
249
|
+
const params = update.update.messageStubParameters;
|
|
250
|
+
if (params.includes(463) || params.includes('463')) {
|
|
251
|
+
antiban.timelock.record463Error();
|
|
252
|
+
// Fleet event: rate limit detected
|
|
253
|
+
if (options.fleetEventStore) {
|
|
254
|
+
void options.fleetEventStore.emit('rate_limit', { error: '463' });
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
// Retry tracking
|
|
259
|
+
antiban.retryTracker.onMessageUpdate(update);
|
|
260
|
+
// Delivery receipt tracking
|
|
261
|
+
const status = update?.update?.status;
|
|
262
|
+
const msgId = update?.key?.id;
|
|
263
|
+
// status 3 = DELIVERY_ACK (double tick), status 4 = READ
|
|
264
|
+
if (msgId && (status === 3 || status === 4)) {
|
|
265
|
+
antiban.onDeliveryReceipt(msgId);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
// LID canonicalizer learning
|
|
269
|
+
antiban.jidCanonicalizer?.onMessageUpdate(updates);
|
|
270
|
+
});
|
|
271
|
+
// Register known chats from incoming messages + handle reply suggestions + learn LID mappings
|
|
272
|
+
sock.ev.on('messages.upsert', (upsert) => {
|
|
273
|
+
const { messages } = upsert;
|
|
274
|
+
deafDetector?.onMessageActivity();
|
|
275
|
+
// Learn LID mappings FIRST (before any other processing)
|
|
276
|
+
antiban.jidCanonicalizer?.onIncomingEvent(upsert);
|
|
277
|
+
for (const msg of messages || []) {
|
|
278
|
+
const jid = msg.key?.remoteJid;
|
|
279
|
+
if (!jid)
|
|
280
|
+
continue;
|
|
281
|
+
// Register known chat
|
|
282
|
+
antiban.timelock.registerKnownChat(jid);
|
|
283
|
+
// Skip self messages
|
|
284
|
+
const isSelf = msg.key?.fromMe || false;
|
|
285
|
+
if (isSelf)
|
|
286
|
+
continue;
|
|
287
|
+
// Extract message text
|
|
288
|
+
const msgText = msg.message?.conversation ||
|
|
289
|
+
msg.message?.extendedTextMessage?.text ||
|
|
290
|
+
msg.message?.imageMessage?.caption ||
|
|
291
|
+
msg.message?.videoMessage?.caption ||
|
|
292
|
+
'';
|
|
293
|
+
// Handle incoming message (updates reply ratio + contact graph)
|
|
294
|
+
const replySuggestion = antiban.onIncomingMessage(jid, msgText);
|
|
295
|
+
// Auto-respond if enabled and suggested
|
|
296
|
+
if (options.autoRespondToIncoming && replySuggestion.shouldReply && replySuggestion.suggestedText) {
|
|
297
|
+
// Random delay 3-15s
|
|
298
|
+
const replyDelay = Math.floor(Math.random() * 12000) + 3000;
|
|
299
|
+
setTimeout(async () => {
|
|
300
|
+
try {
|
|
301
|
+
await sock.sendMessage(jid, { text: replySuggestion.suggestedText });
|
|
302
|
+
}
|
|
303
|
+
catch (error) {
|
|
304
|
+
// Silently fail — auto-reply is best-effort
|
|
305
|
+
}
|
|
306
|
+
}, replyDelay);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
// Create proxy that intercepts sendMessage
|
|
312
|
+
const originalSendMessage = sock.sendMessage.bind(sock);
|
|
313
|
+
// Serializes beforeSend→afterSend so rate limiter accounting is accurate
|
|
314
|
+
// under concurrent sends. Without this, all concurrent callers read the same
|
|
315
|
+
// committed state before any afterSend records, bypassing per-minute limits.
|
|
316
|
+
let sendLock = Promise.resolve();
|
|
317
|
+
const wrappedSendMessage = async (jid, content, options) => {
|
|
318
|
+
/**
|
|
319
|
+
* LID/PN Canonicalization — Normalize JID to canonical form
|
|
320
|
+
*
|
|
321
|
+
* This mitigates the LID/PN race that causes "Bad MAC / No Session / Invalid PreKey"
|
|
322
|
+
* errors (Baileys #1769, our PR #2372). When a message event arrives under one form
|
|
323
|
+
* (e.g. LID) but the crypto session was established under another (e.g. PN), decryption
|
|
324
|
+
* fails. By normalizing all outbound targets to a single form, we reduce this race.
|
|
325
|
+
*
|
|
326
|
+
* This is middleware-layer mitigation only. Root fix requires PR #2372 merged upstream.
|
|
327
|
+
*/
|
|
328
|
+
const canonicalJid = antiban.jidCanonicalizer?.canonicalizeTarget(jid) || jid;
|
|
329
|
+
// Extract text content for rate limiter analysis
|
|
330
|
+
let text = content?.text || content?.caption || content?.image?.caption || '';
|
|
331
|
+
// Check for typo injection (only for text messages)
|
|
332
|
+
let typoResult = null;
|
|
333
|
+
if (legitimacyInjector && content?.text && typeof content.text === 'string' && content.text.length > 10) {
|
|
334
|
+
typoResult = legitimacyInjector.shouldInjectTypo(content.text);
|
|
335
|
+
}
|
|
336
|
+
// Chain this send onto the previous — each waits for the prior send's
|
|
337
|
+
// afterSend to commit before running its own beforeSend check.
|
|
338
|
+
const sendResult = sendLock.then(async () => {
|
|
339
|
+
// Circuit breaker check (before antiban.beforeSend)
|
|
340
|
+
if (options.circuitBreaker) {
|
|
341
|
+
if (!options.circuitBreaker.canSend(canonicalJid)) {
|
|
342
|
+
throw new Error('[baileys-antiban] circuit-breaker: send blocked for ' + canonicalJid);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
const decision = await antiban.beforeSend(canonicalJid, text);
|
|
346
|
+
if (!decision.allowed) {
|
|
347
|
+
throw new Error(`[baileys-antiban] Message blocked: ${decision.reason}`);
|
|
348
|
+
}
|
|
349
|
+
// Apply delay from rate limiter
|
|
350
|
+
if (decision.delayMs > 0) {
|
|
351
|
+
await new Promise(resolve => setTimeout(resolve, decision.delayMs));
|
|
352
|
+
}
|
|
353
|
+
// Apply circuit breaker jitter for broadcast messages
|
|
354
|
+
if (options.circuitBreaker) {
|
|
355
|
+
const isBroadcast = canonicalJid.endsWith('@broadcast') || canonicalJid.endsWith('@newsletter');
|
|
356
|
+
const jitter = options.circuitBreaker.getJitter(isBroadcast);
|
|
357
|
+
if (jitter > 0) {
|
|
358
|
+
await new Promise(resolve => setTimeout(resolve, jitter));
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
// Send message (using canonical JID)
|
|
362
|
+
// If typo injection is active, send typo first, then correction
|
|
363
|
+
try {
|
|
364
|
+
let result;
|
|
365
|
+
if (typoResult) {
|
|
366
|
+
// Send typo version first
|
|
367
|
+
const typoContent = { ...content, text: typoResult.typoText };
|
|
368
|
+
await originalSendMessage(canonicalJid, typoContent, options);
|
|
369
|
+
// Wait for correction delay
|
|
370
|
+
await new Promise(r => setTimeout(r, typoResult.correctionDelay));
|
|
371
|
+
// Send corrected version
|
|
372
|
+
const correctionContent = { ...content, text: typoResult.correctionText };
|
|
373
|
+
result = await originalSendMessage(canonicalJid, correctionContent, options);
|
|
374
|
+
}
|
|
375
|
+
else {
|
|
376
|
+
// Normal send
|
|
377
|
+
result = await originalSendMessage(canonicalJid, content, options);
|
|
378
|
+
}
|
|
379
|
+
// Pass msgId to afterSend for delivery tracking
|
|
380
|
+
const msgId = result?.key?.id;
|
|
381
|
+
antiban.afterSend(canonicalJid, text, msgId);
|
|
382
|
+
antiban.timelock.registerKnownChat(canonicalJid);
|
|
383
|
+
// Clear retry tracking on successful send
|
|
384
|
+
if (msgId) {
|
|
385
|
+
antiban.retryTracker.clear(msgId);
|
|
386
|
+
}
|
|
387
|
+
// Circuit breaker success
|
|
388
|
+
if (options.circuitBreaker) {
|
|
389
|
+
options.circuitBreaker.recordSuccess(canonicalJid);
|
|
390
|
+
}
|
|
391
|
+
return result;
|
|
392
|
+
}
|
|
393
|
+
catch (error) {
|
|
394
|
+
// Circuit breaker failure
|
|
395
|
+
if (options.circuitBreaker) {
|
|
396
|
+
options.circuitBreaker.recordFailure(canonicalJid);
|
|
397
|
+
}
|
|
398
|
+
// Baileys PR #2587: partial-encrypt Boom now carries structured data:
|
|
399
|
+
// error.data.failed[] — per-recipient { jid, error } failures
|
|
400
|
+
// error.data.firstCause — most likely root cause string
|
|
401
|
+
// Extract for richer health-monitor diagnostics vs plain error.message.
|
|
402
|
+
const boomData = error?.data;
|
|
403
|
+
if (boomData?.failed?.length) {
|
|
404
|
+
const cause = boomData.firstCause ?? 'unknown';
|
|
405
|
+
const failedJids = boomData.failed.map((f) => f.jid).join(', ');
|
|
406
|
+
antiban.afterSendFailed(`encrypt-all-failed firstCause=${cause} jids=[${failedJids}]`);
|
|
407
|
+
}
|
|
408
|
+
else {
|
|
409
|
+
antiban.afterSendFailed(error instanceof Error ? error.message : String(error));
|
|
410
|
+
}
|
|
411
|
+
throw error;
|
|
412
|
+
}
|
|
413
|
+
});
|
|
414
|
+
// Advance the lock regardless of success/failure so the chain never stalls
|
|
415
|
+
sendLock = sendResult.then(() => { }, () => { });
|
|
416
|
+
return sendResult;
|
|
417
|
+
};
|
|
418
|
+
// Return enhanced socket
|
|
419
|
+
const wrapped = Object.create(sock);
|
|
420
|
+
wrapped.sendMessage = wrappedSendMessage;
|
|
421
|
+
wrapped.antiban = antiban;
|
|
422
|
+
// Expose destroy method directly so consumers can call it manually if needed
|
|
423
|
+
wrapped.antiban.destroy = antiban.destroy.bind(antiban);
|
|
424
|
+
return wrapped;
|
|
425
|
+
}
|
|
426
|
+
/**
|
|
427
|
+
* Helper function to create a wrapped socket with device fingerprint applied.
|
|
428
|
+
*
|
|
429
|
+
* This combines device fingerprint generation, socket creation, and wrapping
|
|
430
|
+
* into a single call.
|
|
431
|
+
*
|
|
432
|
+
* Usage:
|
|
433
|
+
* import makeWASocket from 'baileys';
|
|
434
|
+
* import { wrapSocketWithFingerprint } from 'baileys-antiban';
|
|
435
|
+
*
|
|
436
|
+
* const wrapped = wrapSocketWithFingerprint(makeWASocket, socketConfig, {
|
|
437
|
+
* fingerprintSeed: 'stable-seed-123',
|
|
438
|
+
* groupOpGuard: {},
|
|
439
|
+
* legitimacySignals: {}
|
|
440
|
+
* });
|
|
441
|
+
*
|
|
442
|
+
* @param makeWASocket - Baileys makeWASocket factory function
|
|
443
|
+
* @param socketConfig - Base socket configuration
|
|
444
|
+
* @param wrapOptions - Combined wrapper options + fingerprintSeed
|
|
445
|
+
*/
|
|
446
|
+
export function wrapSocketWithFingerprint(makeWASocket, socketConfig, wrapOptions) {
|
|
447
|
+
// Import fingerprint functions dynamically to avoid circular deps
|
|
448
|
+
const { generateFingerprint, applyFingerprint } = require('./deviceFingerprint.js');
|
|
449
|
+
const seed = wrapOptions?.fingerprintSeed ||
|
|
450
|
+
socketConfig.auth?.creds?.me?.id ||
|
|
451
|
+
String(Date.now());
|
|
452
|
+
const fp = generateFingerprint({ seed });
|
|
453
|
+
const config = applyFingerprint(socketConfig, fp);
|
|
454
|
+
const sock = makeWASocket(config);
|
|
455
|
+
return wrapSocket(sock, undefined, undefined, wrapOptions);
|
|
456
|
+
}
|
package/lib/Socket/groups.js
CHANGED
|
@@ -440,31 +440,36 @@ export const extractGroupMetadata = (result) => {
|
|
|
440
440
|
memberAddMode: memberAddMode,
|
|
441
441
|
participants: getBinaryNodeChildren(group, "participant").map(
|
|
442
442
|
({ attrs }) => {
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
443
|
+
// مهم: الـ id هنا لازم يفضل زي ما السيرفر بعته (attrs.jid) بالظبط,
|
|
444
|
+
// من غير ما نلمسه أو نبدله بـ lid/phone بديل. السبب: القيمة دي هي
|
|
445
|
+
// اللي بتتبعت زي ما هي في participantsList لـ getUSyncDevices وفي
|
|
446
|
+
// الـ addressing_mode بتاع الرسالة الصادرة (messages-send.js) —
|
|
447
|
+
// فلو الجروب addressingMode="pn" لازم كل الـ ids تكون بصيغة PN,
|
|
448
|
+
// ولو "lid" لازم تكون كلها LID. أي تبديل هنا بيسبب mismatch بين
|
|
449
|
+
// هوية المشارك والـ addressing_mode المعلن، وده اللي كان بيطلع
|
|
450
|
+
// "بيشفر"/Waiting for this message لمشاركين الـ username بالذات —
|
|
451
|
+
// مش مشكلة سيشن عشان كده افتكاك السيشن وإعادة الدخول ما كانتش بتحل حاجة.
|
|
452
|
+
// عرض الرقم/اليوزرنيم للمستخدم (لو الرقم مخفي) اتحل بالفعل في مكان
|
|
453
|
+
// تاني (resolveDisplayIdentity) وده المكان الصح ليه، مش هنا.
|
|
454
|
+
const id = attrs.jid;
|
|
454
455
|
return {
|
|
455
|
-
|
|
456
|
-
//
|
|
457
|
-
//
|
|
458
|
-
//
|
|
459
|
-
//
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
456
|
+
id,
|
|
457
|
+
// jid هنا نفس قيمة id بالظبط (مش قيمة تانية زي الكود القديم) —
|
|
458
|
+
// مُبقّاها بس عشان بعض أوامر البوت (كتم.js, مجتمع.js, muteSystem.js,
|
|
459
|
+
// context/group.js) بتقرا p.jid كـ fallback؛ ما بتأثرش على
|
|
460
|
+
// participantsList/addressing_mode لأنها زي id بالظبط دلوقتي
|
|
461
|
+
jid: id,
|
|
462
|
+
phoneNumber:
|
|
463
|
+
isLidUser(id) && isPnUser(attrs.phone_number)
|
|
464
|
+
? attrs.phone_number
|
|
465
|
+
: undefined,
|
|
466
|
+
lid: isPnUser(id) && isLidUser(attrs.lid) ? attrs.lid : undefined,
|
|
467
|
+
username: attrs.participant_username || attrs.username || undefined,
|
|
468
|
+
admin: attrs.type || null,
|
|
464
469
|
};
|
|
465
470
|
},
|
|
466
471
|
),
|
|
467
472
|
ephemeralDuration: eph ? +eph : undefined,
|
|
468
473
|
};
|
|
469
474
|
return metadata;
|
|
470
|
-
};
|
|
475
|
+
};
|
package/lib/Socket/index.js
CHANGED
|
@@ -2,28 +2,153 @@ import { DEFAULT_CONNECTION_CONFIG } from "../Defaults/index.js";
|
|
|
2
2
|
import { makeCommunitiesSocket } from "./communities.js";
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
*
|
|
5
|
+
* Optional AntiBan — مقفول افتراضياً.
|
|
6
|
+
* لتفعيله مرّر في إعدادات makeWASocket:
|
|
7
|
+
* makeWASocket({ antiBan: true }) // moderate preset
|
|
8
|
+
* makeWASocket({ antiBan: 'conservative' }) // preset name
|
|
9
|
+
* makeWASocket({ antiBan: { maxPerMinute: 8, ... } }) // config مخصص
|
|
10
|
+
* makeWASocket({ antiBan: false }) // صراحة مقفول (الافتراضي)
|
|
11
|
+
*
|
|
12
|
+
* الحالة الاختيارية:
|
|
13
|
+
* antiBanWarmUpState — حالة الـ warmup المحفوظة
|
|
14
|
+
* antiBanWrapOptions — خيارات إضافية لـ wrapSocket
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
let _wrapSocket = null;
|
|
18
|
+
let _wrapSocketWithFingerprint = null;
|
|
19
|
+
let _PRESETS = {};
|
|
20
|
+
let _antiBanLoadError = null;
|
|
21
|
+
|
|
22
|
+
async function loadAntiBan() {
|
|
23
|
+
if (_wrapSocket) return true;
|
|
24
|
+
if (_antiBanLoadError) return false;
|
|
25
|
+
try {
|
|
26
|
+
const mod = await import("../AntiBan/index.js");
|
|
27
|
+
_wrapSocket = mod.wrapSocket;
|
|
28
|
+
_wrapSocketWithFingerprint = mod.wrapSocketWithFingerprint;
|
|
29
|
+
_PRESETS = mod.PRESETS || {};
|
|
30
|
+
return true;
|
|
31
|
+
} catch (e) {
|
|
32
|
+
// fallback: try wrapper export path
|
|
33
|
+
try {
|
|
34
|
+
const wrap = await import("../AntiBan/wrapper.js");
|
|
35
|
+
const presets = await import("../AntiBan/presets.js").catch(() => ({}));
|
|
36
|
+
_wrapSocket = wrap.wrapSocket;
|
|
37
|
+
_wrapSocketWithFingerprint = wrap.wrapSocketWithFingerprint;
|
|
38
|
+
_PRESETS = presets.PRESETS || {};
|
|
39
|
+
return true;
|
|
40
|
+
} catch (e2) {
|
|
41
|
+
_antiBanLoadError = e2;
|
|
42
|
+
console.warn(
|
|
43
|
+
"[@prototypevip/baileys] AntiBan module failed to load — continuing without it:",
|
|
44
|
+
e2?.message || e2
|
|
45
|
+
);
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Best-effort background load (fire-and-forget, NOT top-level await —
|
|
52
|
+
// awaiting here breaks require() on this ESM package with ERR_REQUIRE_ASYNC_MODULE).
|
|
53
|
+
// If antiBan is requested before this finishes, makeWASocket() below will just
|
|
54
|
+
// warn and skip protection instead of crashing.
|
|
55
|
+
loadAntiBan();
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Create a WhatsApp socket.
|
|
59
|
+
* AntiBan مقفول افتراضياً — فعّله عبر config.antiBan
|
|
6
60
|
*/
|
|
7
61
|
const makeWASocket = (config = {}) => {
|
|
8
|
-
// strip antiBan options if present so they don't leak into internal config
|
|
9
62
|
const {
|
|
10
|
-
antiBan,
|
|
63
|
+
antiBan = false, // مقفول افتراضياً
|
|
11
64
|
antiBanWarmUpState,
|
|
12
65
|
antiBanWrapOptions,
|
|
13
66
|
...restConfig
|
|
14
|
-
} = config;
|
|
67
|
+
} = config || {};
|
|
15
68
|
|
|
16
69
|
delete restConfig.userDevicesCache;
|
|
17
70
|
const newConfig = { ...DEFAULT_CONNECTION_CONFIG, ...restConfig };
|
|
18
71
|
|
|
19
72
|
let sock;
|
|
20
73
|
const originalGetMessage = newConfig.getMessage;
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
74
|
+
if (typeof originalGetMessage === "function") {
|
|
75
|
+
newConfig.getMessage = async function (key) {
|
|
76
|
+
return originalGetMessage.call(sock || this, key);
|
|
77
|
+
};
|
|
78
|
+
}
|
|
24
79
|
|
|
25
80
|
sock = makeCommunitiesSocket(newConfig);
|
|
81
|
+
|
|
82
|
+
// ── AntiBan (اختياري) ──
|
|
83
|
+
const enabled =
|
|
84
|
+
antiBan === true ||
|
|
85
|
+
(typeof antiBan === "string" && antiBan.length > 0) ||
|
|
86
|
+
(antiBan && typeof antiBan === "object");
|
|
87
|
+
|
|
88
|
+
if (enabled) {
|
|
89
|
+
if (typeof _wrapSocket !== "function") {
|
|
90
|
+
console.warn(
|
|
91
|
+
"[@prototypevip/baileys] antiBan requested but module not available — socket without protection"
|
|
92
|
+
);
|
|
93
|
+
return sock;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const antiBanConfig =
|
|
97
|
+
antiBan === true
|
|
98
|
+
? "moderate"
|
|
99
|
+
: antiBan; // string preset or object config
|
|
100
|
+
|
|
101
|
+
try {
|
|
102
|
+
sock = _wrapSocket(
|
|
103
|
+
sock,
|
|
104
|
+
antiBanConfig,
|
|
105
|
+
antiBanWarmUpState,
|
|
106
|
+
antiBanWrapOptions || {}
|
|
107
|
+
);
|
|
108
|
+
if (newConfig.logger?.info) {
|
|
109
|
+
newConfig.logger.info(
|
|
110
|
+
{ antiBan: typeof antiBanConfig === "string" ? antiBanConfig : "custom" },
|
|
111
|
+
"AntiBan protection enabled"
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
} catch (e) {
|
|
115
|
+
console.warn(
|
|
116
|
+
"[@prototypevip/baileys] AntiBan wrap failed — continuing without it:",
|
|
117
|
+
e?.message || e
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
26
122
|
return sock;
|
|
27
123
|
};
|
|
28
124
|
|
|
125
|
+
/** Pass-through stubs when antiban is not loaded; real functions when loaded */
|
|
126
|
+
const wrapSocket = (sock, ...args) =>
|
|
127
|
+
typeof _wrapSocket === "function" ? _wrapSocket(sock, ...args) : sock;
|
|
128
|
+
|
|
129
|
+
const wrapSocketWithFingerprint = (makeSock, opts, wrapOpts) => {
|
|
130
|
+
if (typeof _wrapSocketWithFingerprint === "function") {
|
|
131
|
+
return _wrapSocketWithFingerprint(makeSock, opts, wrapOpts);
|
|
132
|
+
}
|
|
133
|
+
return typeof makeSock === "function" ? makeSock(opts || {}) : makeSock;
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
const PRESETS = new Proxy(
|
|
137
|
+
{},
|
|
138
|
+
{
|
|
139
|
+
get(_, prop) {
|
|
140
|
+
return _PRESETS[prop];
|
|
141
|
+
},
|
|
142
|
+
ownKeys() {
|
|
143
|
+
return Reflect.ownKeys(_PRESETS);
|
|
144
|
+
},
|
|
145
|
+
getOwnPropertyDescriptor(_, prop) {
|
|
146
|
+
if (prop in _PRESETS) {
|
|
147
|
+
return { enumerable: true, configurable: true, value: _PRESETS[prop] };
|
|
148
|
+
}
|
|
149
|
+
},
|
|
150
|
+
}
|
|
151
|
+
);
|
|
152
|
+
|
|
29
153
|
export default makeWASocket;
|
|
154
|
+
export { makeWASocket, wrapSocket, wrapSocketWithFingerprint, PRESETS };
|