@prototypevip/baileys 0.0.5 → 0.0.7
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/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/chats.js +29 -4
- package/lib/Socket/groups.js +7 -2
- package/lib/Socket/index.js +68 -3
- package/lib/Socket/messages-send.js +44 -8
- package/lib/Socket/socket.js +2 -2
- package/lib/Store/prototype-store.js +10 -39
- package/lib/index.js +5 -0
- package/package.json +7 -3
|
@@ -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/chats.js
CHANGED
|
@@ -199,6 +199,15 @@ export const makeChatsSocket = (config) => {
|
|
|
199
199
|
}
|
|
200
200
|
};
|
|
201
201
|
|
|
202
|
+
// مهله قصوى لاستعلام اليوزرنيم — واتساب أحياناً مبيردش على الاستعلام ده خالص
|
|
203
|
+
// لبعض حسابات اليوزرنيم، وبيرجع في المهله الافتراضيه (60 ثانيه)، وده اللي
|
|
204
|
+
// كان بيسبب تعليق البوت لحد دقيقتين على اي رساله جايه من حساب زي ده
|
|
205
|
+
const withTimeout = (promise, ms, fallback) =>
|
|
206
|
+
Promise.race([
|
|
207
|
+
promise,
|
|
208
|
+
new Promise((resolve) => setTimeout(() => resolve(fallback), ms)),
|
|
209
|
+
]);
|
|
210
|
+
|
|
202
211
|
const getUsername = async (jid) => {
|
|
203
212
|
const usyncQuery = new USyncQuery()
|
|
204
213
|
.withUsernameProtocol()
|
|
@@ -208,10 +217,18 @@ export const makeChatsSocket = (config) => {
|
|
|
208
217
|
return result?.list?.[0]?.username || null;
|
|
209
218
|
};
|
|
210
219
|
|
|
220
|
+
// كام ملي ثانيه أقصى نستنى فيها رد واتساب قبل ما نستسلم ونكمل من غير يوزرنيم
|
|
221
|
+
const USERNAME_LOOKUP_TIMEOUT_MS = 3500;
|
|
222
|
+
// مده صلاحيه الكاش السلبي (id-not-found) — بعدها نجرب تاني، لأن الرقم ممكن
|
|
223
|
+
// يبقى قابل للاكتشاف تاني (المستخدم شال اليوزرنيم مثلاً)
|
|
224
|
+
const NEGATIVE_CACHE_TTL_MS = 30 * 60 * 1000;
|
|
225
|
+
|
|
211
226
|
const findUserId = async (pnLid) => {
|
|
212
227
|
const cachedId = USER_ID_CACHE.get(pnLid);
|
|
213
228
|
if (cachedId) {
|
|
214
|
-
|
|
229
|
+
const isNegative = cachedId.phoneNumber === "id-not-found";
|
|
230
|
+
const isStale = isNegative && Date.now() - (cachedId._cachedAt || 0) > NEGATIVE_CACHE_TTL_MS;
|
|
231
|
+
if (!isStale) return cachedId;
|
|
215
232
|
}
|
|
216
233
|
const userId = {};
|
|
217
234
|
if (isPnUser(pnLid)) {
|
|
@@ -221,6 +238,7 @@ export const makeChatsSocket = (config) => {
|
|
|
221
238
|
)?.[0]?.lid;
|
|
222
239
|
if (!userId.lid) {
|
|
223
240
|
userId.lid = "id-not-found";
|
|
241
|
+
userId._cachedAt = Date.now();
|
|
224
242
|
USER_ID_CACHE.set(pnLid, userId);
|
|
225
243
|
return userId;
|
|
226
244
|
}
|
|
@@ -234,13 +252,20 @@ export const makeChatsSocket = (config) => {
|
|
|
234
252
|
// واتساب، لأن رقمه بيبقى مش قابل للاكتشاف. من غير الكاش ده، كل رساله
|
|
235
253
|
// جايه من الحساب ده كانت بتعمل استعلام شبكه كامل تاني من الأول، وده
|
|
236
254
|
// اللي كان بيسبب التأخير/التعليق الملحوظ مع أي حد شايل يوزرنيم.
|
|
237
|
-
// كمان بنحاول نجيب اليوزرنيم نفسه عشان يتعرض بدل الرقم/الليد
|
|
255
|
+
// كمان بنحاول نجيب اليوزرنيم نفسه عشان يتعرض بدل الرقم/الليد الخام،
|
|
256
|
+
// لكن بمهله قصوى (6 ثواني) عشان لو واتساب مارضاش يرد، مانستناهوش المهله
|
|
257
|
+
// الكامله (60 ثانيه) اللي كانت بتعلّق البوت.
|
|
238
258
|
userId.phoneNumber = "id-not-found";
|
|
239
259
|
try {
|
|
240
|
-
userId.username = await
|
|
260
|
+
userId.username = await withTimeout(
|
|
261
|
+
getUsername(pnLid),
|
|
262
|
+
USERNAME_LOOKUP_TIMEOUT_MS,
|
|
263
|
+
null,
|
|
264
|
+
);
|
|
241
265
|
} catch {
|
|
242
266
|
userId.username = null;
|
|
243
267
|
}
|
|
268
|
+
userId._cachedAt = Date.now();
|
|
244
269
|
USER_ID_CACHE.set(pnLid, userId);
|
|
245
270
|
return userId;
|
|
246
271
|
}
|
|
@@ -1144,4 +1169,4 @@ export const makeChatsSocket = (config) => {
|
|
|
1144
1169
|
addOrEditQuickReply: addOrEditQuickReply,
|
|
1145
1170
|
removeQuickReply: removeQuickReply,
|
|
1146
1171
|
};
|
|
1147
|
-
};
|
|
1172
|
+
};
|
package/lib/Socket/groups.js
CHANGED
|
@@ -452,9 +452,14 @@ export const extractGroupMetadata = (result) => {
|
|
|
452
452
|
? id
|
|
453
453
|
: undefined;
|
|
454
454
|
return {
|
|
455
|
-
|
|
455
|
+
// لو مفيش رقم ظاهر (حساب يوزرنيم/خاصية إخفاء الرقم)، استخدم الـ
|
|
456
|
+
// LID بدل ما تسيبها undefined — undefined كانت بتوصل لقايمة
|
|
457
|
+
// المشاركين اللي بتتبعت لـ getUSyncDevices وتكسر استعلام
|
|
458
|
+
// الأجهزة للجروب كله، مع إن الاستعلام المباشر بالـ LID بيرجع
|
|
459
|
+
// فورًا لو استخدمناه صح
|
|
460
|
+
id: phone || lid,
|
|
456
461
|
lid: lid,
|
|
457
|
-
jid: phone,
|
|
462
|
+
jid: phone || lid,
|
|
458
463
|
admin: attrs.type || false,
|
|
459
464
|
};
|
|
460
465
|
},
|
package/lib/Socket/index.js
CHANGED
|
@@ -1,14 +1,79 @@
|
|
|
1
1
|
import { DEFAULT_CONNECTION_CONFIG } from "../Defaults/index.js";
|
|
2
2
|
import { makeCommunitiesSocket } from "./communities.js";
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
import { wrapSocket, wrapSocketWithFingerprint } from "../AntiBan/wrapper.js";
|
|
4
|
+
import { resolveConfig, PRESETS } from "../AntiBan/presets.js";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Create a WhatsApp socket.
|
|
8
|
+
*
|
|
9
|
+
* Optional anti-ban protection (integrated from baileys-antiban) can be enabled via config.antiBan:
|
|
10
|
+
*
|
|
11
|
+
* // Disabled (default) — normal Baileys behavior
|
|
12
|
+
* makeWASocket({ auth, ... })
|
|
13
|
+
*
|
|
14
|
+
* // Enable with safe defaults (conservative preset)
|
|
15
|
+
* makeWASocket({ auth, antiBan: true })
|
|
16
|
+
*
|
|
17
|
+
* // Enable with a named preset: 'conservative' | 'moderate' | 'aggressive' | 'high-volume'
|
|
18
|
+
* makeWASocket({ auth, antiBan: 'moderate' })
|
|
19
|
+
*
|
|
20
|
+
* // Enable with full custom config (see baileys-antiban README)
|
|
21
|
+
* makeWASocket({
|
|
22
|
+
* auth,
|
|
23
|
+
* antiBan: {
|
|
24
|
+
* maxPerMinute: 8,
|
|
25
|
+
* maxPerHour: 200,
|
|
26
|
+
* warmUp: { enabled: true },
|
|
27
|
+
* // ... or nested style still accepted with deprecation warning
|
|
28
|
+
* }
|
|
29
|
+
* })
|
|
30
|
+
*
|
|
31
|
+
* When enabled, the returned socket has the same API plus:
|
|
32
|
+
* sock.antiban — AntiBan instance
|
|
33
|
+
* sock.antiban.getStats() — current rate / health / risk stats
|
|
34
|
+
*
|
|
35
|
+
* Extra advanced options (optional):
|
|
36
|
+
* antiBanWarmUpState — restore previous warm-up state
|
|
37
|
+
* antiBanWrapOptions — extra wrap options (groupOpGuard, legitimacySignals, etc.)
|
|
38
|
+
*
|
|
39
|
+
* You can also use the low-level helpers directly:
|
|
40
|
+
* import { wrapSocket, wrapSocketWithFingerprint, PRESETS } from '@prototypevip/baileys'
|
|
41
|
+
*/
|
|
42
|
+
const makeWASocket = (config = {}) => {
|
|
43
|
+
// Extract anti-ban related options so they don't leak into Baileys internal config
|
|
44
|
+
const {
|
|
45
|
+
antiBan,
|
|
46
|
+
antiBanWarmUpState,
|
|
47
|
+
antiBanWrapOptions,
|
|
48
|
+
...restConfig
|
|
49
|
+
} = config;
|
|
50
|
+
|
|
51
|
+
delete restConfig.userDevicesCache;
|
|
52
|
+
const newConfig = { ...DEFAULT_CONNECTION_CONFIG, ...restConfig };
|
|
53
|
+
|
|
6
54
|
let sock;
|
|
7
55
|
const originalGetMessage = newConfig.getMessage;
|
|
8
56
|
newConfig.getMessage = async function (key) {
|
|
9
57
|
return originalGetMessage.call(sock || this, key);
|
|
10
58
|
};
|
|
59
|
+
|
|
11
60
|
sock = makeCommunitiesSocket(newConfig);
|
|
61
|
+
|
|
62
|
+
// Optional anti-ban wrapping
|
|
63
|
+
if (antiBan) {
|
|
64
|
+
let antibanConfig;
|
|
65
|
+
if (antiBan === true) {
|
|
66
|
+
antibanConfig = resolveConfig(undefined); // conservative defaults
|
|
67
|
+
} else if (typeof antiBan === "string") {
|
|
68
|
+
antibanConfig = resolveConfig(antiBan);
|
|
69
|
+
} else {
|
|
70
|
+
antibanConfig = antiBan; // user object (supports both flat & nested)
|
|
71
|
+
}
|
|
72
|
+
sock = wrapSocket(sock, antibanConfig, antiBanWarmUpState, antiBanWrapOptions);
|
|
73
|
+
}
|
|
74
|
+
|
|
12
75
|
return sock;
|
|
13
76
|
};
|
|
77
|
+
|
|
14
78
|
export default makeWASocket;
|
|
79
|
+
export { wrapSocket, wrapSocketWithFingerprint, PRESETS };
|