@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,207 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Health Monitor — Detect ban warning signs early
|
|
3
|
+
*
|
|
4
|
+
* Tracks connection patterns to identify when WhatsApp is
|
|
5
|
+
* getting suspicious. Gives you time to back off before a ban.
|
|
6
|
+
*
|
|
7
|
+
* Warning signs:
|
|
8
|
+
* - Frequent disconnections (connection.update → close)
|
|
9
|
+
* - 403 Forbidden errors
|
|
10
|
+
* - 401 Logged Out (possible temp ban)
|
|
11
|
+
* - Messages failing silently (sent but not delivered)
|
|
12
|
+
* - QR re-request loops
|
|
13
|
+
* - Rate limit responses (429-like behavior)
|
|
14
|
+
*/
|
|
15
|
+
const DEFAULT_CONFIG = {
|
|
16
|
+
disconnectWarningThreshold: 3,
|
|
17
|
+
disconnectCriticalThreshold: 5,
|
|
18
|
+
failedMessageThreshold: 5,
|
|
19
|
+
autoPauseAt: 'high',
|
|
20
|
+
};
|
|
21
|
+
export class HealthMonitor {
|
|
22
|
+
config;
|
|
23
|
+
events = [];
|
|
24
|
+
startTime = Date.now();
|
|
25
|
+
paused = false;
|
|
26
|
+
lastRisk = 'low';
|
|
27
|
+
lastBadEventTime = Date.now();
|
|
28
|
+
lastEventWasSevere = false;
|
|
29
|
+
constructor(config = {}) {
|
|
30
|
+
this.config = { ...DEFAULT_CONFIG, ...config };
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Record a disconnection event
|
|
34
|
+
*/
|
|
35
|
+
recordDisconnect(reason) {
|
|
36
|
+
const reasonStr = String(reason);
|
|
37
|
+
if (reasonStr === '403' || reasonStr === 'forbidden') {
|
|
38
|
+
this.events.push({ type: 'forbidden', timestamp: Date.now(), detail: reasonStr });
|
|
39
|
+
this.lastBadEventTime = Date.now();
|
|
40
|
+
this.lastEventWasSevere = true;
|
|
41
|
+
}
|
|
42
|
+
else if (reasonStr === '401' || reasonStr === 'loggedOut') {
|
|
43
|
+
this.events.push({ type: 'loggedOut', timestamp: Date.now(), detail: reasonStr });
|
|
44
|
+
this.lastBadEventTime = Date.now();
|
|
45
|
+
this.lastEventWasSevere = true;
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
this.events.push({ type: 'disconnect', timestamp: Date.now(), detail: reasonStr });
|
|
49
|
+
this.lastBadEventTime = Date.now();
|
|
50
|
+
this.lastEventWasSevere = false;
|
|
51
|
+
}
|
|
52
|
+
this.checkAndNotify();
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Record a successful reconnection
|
|
56
|
+
*/
|
|
57
|
+
recordReconnect() {
|
|
58
|
+
this.events.push({ type: 'reconnect', timestamp: Date.now() });
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Record a failed message send
|
|
62
|
+
*/
|
|
63
|
+
recordMessageFailed(error) {
|
|
64
|
+
this.events.push({ type: 'messageFailed', timestamp: Date.now(), detail: error });
|
|
65
|
+
this.lastBadEventTime = Date.now();
|
|
66
|
+
this.lastEventWasSevere = false;
|
|
67
|
+
this.checkAndNotify();
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Record a 463 reachout timelock error
|
|
71
|
+
*/
|
|
72
|
+
recordReachoutTimelock(detail) {
|
|
73
|
+
this.events.push({ type: 'reachoutTimelocked', timestamp: Date.now(), detail });
|
|
74
|
+
this.lastBadEventTime = Date.now();
|
|
75
|
+
this.lastEventWasSevere = false;
|
|
76
|
+
this.checkAndNotify();
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Get current health status
|
|
80
|
+
*/
|
|
81
|
+
getStatus() {
|
|
82
|
+
const now = Date.now();
|
|
83
|
+
this.cleanup(now);
|
|
84
|
+
const hourEvents = this.events.filter(e => now - e.timestamp < 3600000);
|
|
85
|
+
const disconnects = hourEvents.filter(e => e.type === 'disconnect').length;
|
|
86
|
+
const forbidden = hourEvents.filter(e => e.type === 'forbidden').length;
|
|
87
|
+
const loggedOut = hourEvents.filter(e => e.type === 'loggedOut').length;
|
|
88
|
+
const failedMessages = hourEvents.filter(e => e.type === 'messageFailed').length;
|
|
89
|
+
let score = 0;
|
|
90
|
+
const reasons = [];
|
|
91
|
+
// Forbidden errors are serious
|
|
92
|
+
if (forbidden > 0) {
|
|
93
|
+
score += 40 * forbidden;
|
|
94
|
+
reasons.push(`${forbidden} forbidden (403) error${forbidden > 1 ? 's' : ''} in last hour`);
|
|
95
|
+
}
|
|
96
|
+
// Logged out is very serious
|
|
97
|
+
if (loggedOut > 0) {
|
|
98
|
+
score += 60;
|
|
99
|
+
reasons.push('Logged out by WhatsApp — possible temporary ban');
|
|
100
|
+
}
|
|
101
|
+
// Reachout timelocked (463)
|
|
102
|
+
const timelocked = hourEvents.filter(e => e.type === 'reachoutTimelocked').length;
|
|
103
|
+
if (timelocked > 0) {
|
|
104
|
+
score += 25;
|
|
105
|
+
reasons.push(`${timelocked} reachout timelock (463) error${timelocked > 1 ? 's' : ''} in last hour`);
|
|
106
|
+
}
|
|
107
|
+
// Frequent disconnects
|
|
108
|
+
if (disconnects >= this.config.disconnectCriticalThreshold) {
|
|
109
|
+
score += 30;
|
|
110
|
+
reasons.push(`${disconnects} disconnects in last hour (critical threshold)`);
|
|
111
|
+
}
|
|
112
|
+
else if (disconnects >= this.config.disconnectWarningThreshold) {
|
|
113
|
+
score += 30;
|
|
114
|
+
reasons.push(`${disconnects} disconnects in last hour`);
|
|
115
|
+
}
|
|
116
|
+
// Failed messages
|
|
117
|
+
if (failedMessages >= this.config.failedMessageThreshold) {
|
|
118
|
+
score += 20;
|
|
119
|
+
reasons.push(`${failedMessages} failed messages in last hour`);
|
|
120
|
+
}
|
|
121
|
+
// Determine risk level
|
|
122
|
+
score = Math.min(100, score);
|
|
123
|
+
// Tiered decay: recover based on time since last bad event
|
|
124
|
+
// Severe (403/401): 2pts/min — ~50min to clear 100pts
|
|
125
|
+
// Normal: 5pts/min — ~20min to clear 100pts
|
|
126
|
+
const minutesSinceLastBad = (now - this.lastBadEventTime) / 60000;
|
|
127
|
+
const decayRate = this.lastEventWasSevere ? 2 : 5;
|
|
128
|
+
score = Math.max(0, score - Math.floor(minutesSinceLastBad * decayRate));
|
|
129
|
+
let risk;
|
|
130
|
+
if (score >= 80)
|
|
131
|
+
risk = 'critical';
|
|
132
|
+
else if (score >= 40)
|
|
133
|
+
risk = 'high';
|
|
134
|
+
else if (score >= 15)
|
|
135
|
+
risk = 'medium';
|
|
136
|
+
else
|
|
137
|
+
risk = 'low';
|
|
138
|
+
// Determine recommendation
|
|
139
|
+
let recommendation;
|
|
140
|
+
switch (risk) {
|
|
141
|
+
case 'critical':
|
|
142
|
+
recommendation = 'STOP ALL MESSAGING IMMEDIATELY. Disconnect and wait 24-48 hours before reconnecting.';
|
|
143
|
+
break;
|
|
144
|
+
case 'high':
|
|
145
|
+
recommendation = 'Reduce messaging rate by 80%. Consider pausing for 1-2 hours.';
|
|
146
|
+
break;
|
|
147
|
+
case 'medium':
|
|
148
|
+
recommendation = 'Reduce messaging rate by 50%. Increase delays between messages.';
|
|
149
|
+
break;
|
|
150
|
+
default:
|
|
151
|
+
recommendation = 'Operating normally. Continue monitoring.';
|
|
152
|
+
}
|
|
153
|
+
const lastDisconnect = [...this.events].reverse().find(e => e.type === 'disconnect' || e.type === 'forbidden' || e.type === 'loggedOut');
|
|
154
|
+
return {
|
|
155
|
+
risk,
|
|
156
|
+
score,
|
|
157
|
+
reasons: reasons.length ? reasons : ['No issues detected'],
|
|
158
|
+
recommendation,
|
|
159
|
+
stats: {
|
|
160
|
+
disconnectsLastHour: disconnects,
|
|
161
|
+
failedMessagesLastHour: failedMessages,
|
|
162
|
+
forbiddenErrors: forbidden,
|
|
163
|
+
timelockErrors: timelocked,
|
|
164
|
+
uptimeMs: now - this.startTime,
|
|
165
|
+
lastDisconnectReason: lastDisconnect?.detail,
|
|
166
|
+
},
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Check if sending should be paused
|
|
171
|
+
*/
|
|
172
|
+
isPaused() {
|
|
173
|
+
if (this.paused)
|
|
174
|
+
return true;
|
|
175
|
+
const status = this.getStatus();
|
|
176
|
+
const riskOrder = ['low', 'medium', 'high', 'critical'];
|
|
177
|
+
return riskOrder.indexOf(status.risk) >= riskOrder.indexOf(this.config.autoPauseAt);
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Manually pause/resume
|
|
181
|
+
*/
|
|
182
|
+
setPaused(paused) {
|
|
183
|
+
this.paused = paused;
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Reset all tracked events
|
|
187
|
+
*/
|
|
188
|
+
reset() {
|
|
189
|
+
this.events = [];
|
|
190
|
+
this.startTime = Date.now();
|
|
191
|
+
this.paused = false;
|
|
192
|
+
this.lastRisk = 'low';
|
|
193
|
+
this.lastBadEventTime = Date.now();
|
|
194
|
+
this.lastEventWasSevere = false;
|
|
195
|
+
}
|
|
196
|
+
cleanup(now) {
|
|
197
|
+
// Keep last 6 hours of events
|
|
198
|
+
this.events = this.events.filter(e => now - e.timestamp < 21600000);
|
|
199
|
+
}
|
|
200
|
+
checkAndNotify() {
|
|
201
|
+
const status = this.getStatus();
|
|
202
|
+
if (status.risk !== this.lastRisk) {
|
|
203
|
+
this.lastRisk = status.risk;
|
|
204
|
+
this.config.onRiskChange?.(status);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HumanEntropyService — Background noise for WA sessions
|
|
3
|
+
*
|
|
4
|
+
* Runs periodic human-like actions to make idle sessions appear more realistic:
|
|
5
|
+
* - Random typing presence to recent contacts
|
|
6
|
+
* - Delayed read receipts
|
|
7
|
+
* - Availability status toggles
|
|
8
|
+
*
|
|
9
|
+
* Design:
|
|
10
|
+
* - Works ONLY with WaSP's public API (no direct socket access)
|
|
11
|
+
* - Fail-silent (never crashes wa-pa)
|
|
12
|
+
* - Configurable intervals and probabilities
|
|
13
|
+
* - Only interacts with contacts who messaged first (never strangers)
|
|
14
|
+
*/
|
|
15
|
+
const DEFAULT_CONFIG = {
|
|
16
|
+
enabled: true,
|
|
17
|
+
minIntervalMs: 2 * 60 * 60 * 1000, // 2 hours
|
|
18
|
+
maxIntervalMs: 6 * 60 * 60 * 1000, // 6 hours
|
|
19
|
+
maxRecentContacts: 30,
|
|
20
|
+
typingProbability: 0.3,
|
|
21
|
+
typingMinMs: 3000,
|
|
22
|
+
typingMaxMs: 8000,
|
|
23
|
+
readReceiptProbability: 0.2,
|
|
24
|
+
readReceiptMinDelayMs: 10 * 60 * 1000, // 10 min
|
|
25
|
+
readReceiptMaxDelayMs: 60 * 60 * 1000, // 60 min
|
|
26
|
+
presenceToggleProbability: 0.15,
|
|
27
|
+
presenceToggleMinMs: 30 * 1000, // 30 sec
|
|
28
|
+
presenceToggleMaxMs: 2 * 60 * 1000, // 2 min
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* Human entropy service
|
|
32
|
+
*
|
|
33
|
+
* Adds realistic background noise to WhatsApp sessions by performing
|
|
34
|
+
* random human-like actions periodically.
|
|
35
|
+
*/
|
|
36
|
+
export class HumanEntropyService {
|
|
37
|
+
config;
|
|
38
|
+
wasp; // WaSP instance
|
|
39
|
+
sessionId;
|
|
40
|
+
recentContacts = [];
|
|
41
|
+
unreadMessages = [];
|
|
42
|
+
cycleTimer = null;
|
|
43
|
+
isRunning = false;
|
|
44
|
+
stats = {
|
|
45
|
+
cyclesExecuted: 0,
|
|
46
|
+
typingActionsPerformed: 0,
|
|
47
|
+
readReceiptsMarked: 0,
|
|
48
|
+
presenceToggles: 0,
|
|
49
|
+
errors: 0,
|
|
50
|
+
lastCycleAt: null,
|
|
51
|
+
nextCycleAt: null,
|
|
52
|
+
};
|
|
53
|
+
constructor(wasp, sessionId, config = {}) {
|
|
54
|
+
this.wasp = wasp;
|
|
55
|
+
this.sessionId = sessionId;
|
|
56
|
+
this.config = { ...DEFAULT_CONFIG, ...config };
|
|
57
|
+
// Listen to MESSAGE_RECEIVED events to track recent contacts
|
|
58
|
+
this.wasp.on('MESSAGE_RECEIVED', (event) => {
|
|
59
|
+
if (event.sessionId === this.sessionId) {
|
|
60
|
+
this.trackIncomingMessage(event.data);
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
this.log('info', 'HumanEntropyService initialized', {
|
|
64
|
+
enabled: this.config.enabled,
|
|
65
|
+
minInterval: this.config.minIntervalMs,
|
|
66
|
+
maxInterval: this.config.maxIntervalMs,
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Start the entropy service
|
|
71
|
+
*/
|
|
72
|
+
start() {
|
|
73
|
+
if (!this.config.enabled) {
|
|
74
|
+
this.log('info', 'Entropy service disabled, not starting');
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
if (this.isRunning) {
|
|
78
|
+
this.log('warn', 'Entropy service already running');
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
this.isRunning = true;
|
|
82
|
+
this.scheduleNextCycle();
|
|
83
|
+
this.log('info', 'Entropy service started');
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Stop the entropy service
|
|
87
|
+
*/
|
|
88
|
+
stop() {
|
|
89
|
+
this.isRunning = false;
|
|
90
|
+
if (this.cycleTimer) {
|
|
91
|
+
clearTimeout(this.cycleTimer);
|
|
92
|
+
this.cycleTimer = null;
|
|
93
|
+
}
|
|
94
|
+
this.stats.nextCycleAt = null;
|
|
95
|
+
this.log('info', 'Entropy service stopped');
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Get current statistics
|
|
99
|
+
*/
|
|
100
|
+
getStats() {
|
|
101
|
+
return { ...this.stats };
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Track incoming message to build recent contacts list
|
|
105
|
+
*/
|
|
106
|
+
trackIncomingMessage(message) {
|
|
107
|
+
try {
|
|
108
|
+
// Skip outgoing messages
|
|
109
|
+
if (message.fromMe)
|
|
110
|
+
return;
|
|
111
|
+
const jid = message.from || message.chatId;
|
|
112
|
+
if (!jid)
|
|
113
|
+
return;
|
|
114
|
+
// Update or add to recent contacts
|
|
115
|
+
const existingIndex = this.recentContacts.findIndex(c => c.jid === jid);
|
|
116
|
+
if (existingIndex >= 0) {
|
|
117
|
+
this.recentContacts[existingIndex].lastMessageAt = new Date();
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
this.recentContacts.push({
|
|
121
|
+
jid,
|
|
122
|
+
lastMessageAt: new Date(),
|
|
123
|
+
isGroup: message.isGroup ?? false,
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
// Trim to max size (keep most recent)
|
|
127
|
+
if (this.recentContacts.length > this.config.maxRecentContacts) {
|
|
128
|
+
this.recentContacts.sort((a, b) => b.lastMessageAt.getTime() - a.lastMessageAt.getTime());
|
|
129
|
+
this.recentContacts = this.recentContacts.slice(0, this.config.maxRecentContacts);
|
|
130
|
+
}
|
|
131
|
+
// Track for potential read receipt
|
|
132
|
+
if (message.id && message.key) {
|
|
133
|
+
this.unreadMessages.push({
|
|
134
|
+
jid,
|
|
135
|
+
messageKey: message.key,
|
|
136
|
+
receivedAt: new Date(),
|
|
137
|
+
});
|
|
138
|
+
// Trim old unread messages (keep last 50)
|
|
139
|
+
if (this.unreadMessages.length > 50) {
|
|
140
|
+
this.unreadMessages = this.unreadMessages.slice(-50);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
catch (error) {
|
|
145
|
+
this.log('error', 'Error tracking incoming message', { error: error.message });
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Schedule next entropy cycle
|
|
150
|
+
*/
|
|
151
|
+
scheduleNextCycle() {
|
|
152
|
+
if (!this.isRunning)
|
|
153
|
+
return;
|
|
154
|
+
const delay = this.randomBetween(this.config.minIntervalMs, this.config.maxIntervalMs);
|
|
155
|
+
this.stats.nextCycleAt = new Date(Date.now() + delay);
|
|
156
|
+
this.cycleTimer = setTimeout(() => {
|
|
157
|
+
this.executeCycle().catch(error => {
|
|
158
|
+
this.log('error', 'Entropy cycle failed', { error: error.message });
|
|
159
|
+
this.stats.errors++;
|
|
160
|
+
}).finally(() => {
|
|
161
|
+
this.scheduleNextCycle();
|
|
162
|
+
});
|
|
163
|
+
}, delay);
|
|
164
|
+
this.log('debug', `Next entropy cycle in ${Math.floor(delay / 1000)}s`);
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Execute one entropy cycle
|
|
168
|
+
*/
|
|
169
|
+
async executeCycle() {
|
|
170
|
+
if (!this.isRunning || this.recentContacts.length === 0) {
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
this.log('info', 'Starting entropy cycle', {
|
|
174
|
+
recentContactsCount: this.recentContacts.length,
|
|
175
|
+
unreadMessagesCount: this.unreadMessages.length,
|
|
176
|
+
});
|
|
177
|
+
this.stats.cyclesExecuted++;
|
|
178
|
+
this.stats.lastCycleAt = new Date();
|
|
179
|
+
const actions = [];
|
|
180
|
+
// Action 1: Random typing presence
|
|
181
|
+
if (Math.random() < this.config.typingProbability) {
|
|
182
|
+
actions.push(this.performTypingPresence());
|
|
183
|
+
}
|
|
184
|
+
// Action 2: Mark random message as read
|
|
185
|
+
if (Math.random() < this.config.readReceiptProbability && this.unreadMessages.length > 0) {
|
|
186
|
+
actions.push(this.performReadReceipt());
|
|
187
|
+
}
|
|
188
|
+
// Action 3: Toggle presence status
|
|
189
|
+
if (Math.random() < this.config.presenceToggleProbability) {
|
|
190
|
+
actions.push(this.performPresenceToggle());
|
|
191
|
+
}
|
|
192
|
+
// Execute all actions in parallel
|
|
193
|
+
await Promise.allSettled(actions);
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Send typing presence to a random recent contact
|
|
197
|
+
*/
|
|
198
|
+
async performTypingPresence() {
|
|
199
|
+
try {
|
|
200
|
+
// Pick a random non-group contact
|
|
201
|
+
const eligibleContacts = this.recentContacts.filter(c => !c.isGroup);
|
|
202
|
+
if (eligibleContacts.length === 0) {
|
|
203
|
+
this.log('debug', 'No eligible contacts for typing presence');
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
const contact = eligibleContacts[Math.floor(Math.random() * eligibleContacts.length)];
|
|
207
|
+
const duration = this.randomBetween(this.config.typingMinMs, this.config.typingMaxMs);
|
|
208
|
+
this.log('info', `Entropy action: typing to ${this.maskJid(contact.jid)} for ${duration}ms`);
|
|
209
|
+
// Get provider to access socket
|
|
210
|
+
const provider = this.wasp.getProvider(this.sessionId);
|
|
211
|
+
if (!provider || !provider.socket) {
|
|
212
|
+
throw new Error('Provider or socket not available');
|
|
213
|
+
}
|
|
214
|
+
// Send composing presence
|
|
215
|
+
await provider.socket.sendPresenceUpdate('composing', contact.jid);
|
|
216
|
+
// Wait for duration
|
|
217
|
+
await this.sleep(duration);
|
|
218
|
+
// Send paused presence
|
|
219
|
+
await provider.socket.sendPresenceUpdate('paused', contact.jid);
|
|
220
|
+
this.stats.typingActionsPerformed++;
|
|
221
|
+
}
|
|
222
|
+
catch (error) {
|
|
223
|
+
this.log('error', 'Error performing typing presence', { error: error.message });
|
|
224
|
+
this.stats.errors++;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Mark a random recent message as read with delay
|
|
229
|
+
*/
|
|
230
|
+
async performReadReceipt() {
|
|
231
|
+
try {
|
|
232
|
+
const message = this.unreadMessages[Math.floor(Math.random() * this.unreadMessages.length)];
|
|
233
|
+
if (!message)
|
|
234
|
+
return;
|
|
235
|
+
const delay = this.randomBetween(this.config.readReceiptMinDelayMs, this.config.readReceiptMaxDelayMs);
|
|
236
|
+
this.log('info', `Entropy action: mark read ${this.maskJid(message.jid)} after ${Math.floor(delay / 1000)}s`);
|
|
237
|
+
// Wait for delay
|
|
238
|
+
await this.sleep(delay);
|
|
239
|
+
// Get provider to access socket
|
|
240
|
+
const provider = this.wasp.getProvider(this.sessionId);
|
|
241
|
+
if (!provider || !provider.socket) {
|
|
242
|
+
throw new Error('Provider or socket not available');
|
|
243
|
+
}
|
|
244
|
+
// Mark as read
|
|
245
|
+
await provider.socket.readMessages([message.messageKey]);
|
|
246
|
+
// Remove from unread list
|
|
247
|
+
this.unreadMessages = this.unreadMessages.filter(m => m !== message);
|
|
248
|
+
this.stats.readReceiptsMarked++;
|
|
249
|
+
}
|
|
250
|
+
catch (error) {
|
|
251
|
+
this.log('error', 'Error performing read receipt', { error: error.message });
|
|
252
|
+
this.stats.errors++;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* Toggle presence status (available → unavailable)
|
|
257
|
+
*/
|
|
258
|
+
async performPresenceToggle() {
|
|
259
|
+
try {
|
|
260
|
+
const duration = this.randomBetween(this.config.presenceToggleMinMs, this.config.presenceToggleMaxMs);
|
|
261
|
+
this.log('info', `Entropy action: presence toggle for ${Math.floor(duration / 1000)}s`);
|
|
262
|
+
// Get provider to access socket
|
|
263
|
+
const provider = this.wasp.getProvider(this.sessionId);
|
|
264
|
+
if (!provider || !provider.socket) {
|
|
265
|
+
throw new Error('Provider or socket not available');
|
|
266
|
+
}
|
|
267
|
+
// Set available
|
|
268
|
+
await provider.socket.sendPresenceUpdate('available');
|
|
269
|
+
// Wait for duration
|
|
270
|
+
await this.sleep(duration);
|
|
271
|
+
// Set unavailable
|
|
272
|
+
await provider.socket.sendPresenceUpdate('unavailable');
|
|
273
|
+
this.stats.presenceToggles++;
|
|
274
|
+
}
|
|
275
|
+
catch (error) {
|
|
276
|
+
this.log('error', 'Error performing presence toggle', { error: error.message });
|
|
277
|
+
this.stats.errors++;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* Random value between min and max (inclusive)
|
|
282
|
+
*/
|
|
283
|
+
randomBetween(min, max) {
|
|
284
|
+
return Math.floor(Math.random() * (max - min + 1)) + min;
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* Sleep for specified milliseconds
|
|
288
|
+
*/
|
|
289
|
+
sleep(ms) {
|
|
290
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* Mask JID for logging (privacy)
|
|
294
|
+
*/
|
|
295
|
+
maskJid(jid) {
|
|
296
|
+
if (jid.length < 8)
|
|
297
|
+
return '***';
|
|
298
|
+
return jid.substring(0, 4) + '***' + jid.substring(jid.length - 4);
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* Log message
|
|
302
|
+
*/
|
|
303
|
+
log(level, message, meta) {
|
|
304
|
+
const prefix = '[entropy]';
|
|
305
|
+
const logData = meta ? `${message} ${JSON.stringify(meta)}` : message;
|
|
306
|
+
switch (level) {
|
|
307
|
+
case 'info':
|
|
308
|
+
console.log(`${prefix} ${logData}`);
|
|
309
|
+
break;
|
|
310
|
+
case 'warn':
|
|
311
|
+
console.warn(`${prefix} ${logData}`);
|
|
312
|
+
break;
|
|
313
|
+
case 'error':
|
|
314
|
+
console.error(`${prefix} ${logData}`);
|
|
315
|
+
break;
|
|
316
|
+
case 'debug':
|
|
317
|
+
// Skip debug logs in production
|
|
318
|
+
break;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
/**
|
|
323
|
+
* Factory function to create HumanEntropyService
|
|
324
|
+
*
|
|
325
|
+
* @param wasp WaSP instance
|
|
326
|
+
* @param sessionId Session ID
|
|
327
|
+
* @param config Optional configuration
|
|
328
|
+
* @returns HumanEntropyService instance with start() and stop() methods
|
|
329
|
+
*/
|
|
330
|
+
export function createHumanEntropyService(wasp, sessionId, config) {
|
|
331
|
+
const service = new HumanEntropyService(wasp, sessionId, config);
|
|
332
|
+
return {
|
|
333
|
+
start: () => service.start(),
|
|
334
|
+
stop: () => service.stop(),
|
|
335
|
+
getStats: () => service.getStats(),
|
|
336
|
+
};
|
|
337
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* baileys-antiban — Anti-ban middleware for Baileys
|
|
3
|
+
*
|
|
4
|
+
* Wraps a Baileys socket with human-like messaging patterns
|
|
5
|
+
* to minimize the risk of WhatsApp banning your number.
|
|
6
|
+
*
|
|
7
|
+
* @author Kobus Wentzel <kobie@pop.co.za>
|
|
8
|
+
* @license MIT
|
|
9
|
+
*/
|
|
10
|
+
// Core
|
|
11
|
+
export { AntiBan } from './antiban.js';
|
|
12
|
+
export { RateLimiter } from './rateLimiter.js';
|
|
13
|
+
export { WarmUp } from './warmup.js';
|
|
14
|
+
export { HealthMonitor } from './health.js';
|
|
15
|
+
export { TimelockGuard } from './timelockGuard.js';
|
|
16
|
+
// v1.3 new modules
|
|
17
|
+
export { ReplyRatioGuard } from './replyRatio.js';
|
|
18
|
+
export { ContactGraphWarmer } from './contactGraph.js';
|
|
19
|
+
export { PresenceChoreographer, getCircadianMultiplier, } from './presenceChoreographer.js';
|
|
20
|
+
// v1.5 new modules
|
|
21
|
+
export { RetryReasonTracker } from './retryTracker.js';
|
|
22
|
+
export { PostReconnectThrottle } from './reconnectThrottle.js';
|
|
23
|
+
// v1.6 new modules
|
|
24
|
+
export { LidResolver } from './lidResolver.js';
|
|
25
|
+
export { JidCanonicalizer } from './jidCanonicalizer.js';
|
|
26
|
+
// v2.0 new modules
|
|
27
|
+
export { SessionHealthMonitor, wrapWithSessionStability, classifyDisconnect, DeafSessionDetector, } from './sessionStability.js';
|
|
28
|
+
// v2.1 new modules
|
|
29
|
+
export { LidFirstResolver, createLidFirstResolver, } from './lidFirstResolver.js';
|
|
30
|
+
export { MessageRetryReason, MAC_ERROR_CODES, parseRetryReason, isMacError, getRetryReasonDescription, } from './retryReason.js';
|
|
31
|
+
// Socket wrapper
|
|
32
|
+
export { wrapSocket, wrapSocketWithFingerprint } from './wrapper.js';
|
|
33
|
+
// Optional features
|
|
34
|
+
export { MessageQueue } from './messageQueue.js';
|
|
35
|
+
export { ContentVariator } from './contentVariator.js';
|
|
36
|
+
export { WebhookAlerts } from './webhooks.js';
|
|
37
|
+
export { Scheduler } from './scheduler.js';
|
|
38
|
+
// State persistence
|
|
39
|
+
export { FileStateAdapter } from './stateAdapter.js';
|
|
40
|
+
// v3.0 new modules
|
|
41
|
+
export { resolveConfig, PRESETS } from './presets.js';
|
|
42
|
+
export { StateManager } from './persist.js';
|
|
43
|
+
export { isGroup, isNewsletter, isBroadcast, shouldUseGroupProfile, applyGroupMultiplier } from './profiles.js';
|
|
44
|
+
// v3.1 new modules
|
|
45
|
+
export { messageRecovery } from './messageRecovery.js';
|
|
46
|
+
// v3.2 new modules
|
|
47
|
+
export { generateFingerprint, applyFingerprint, } from './deviceFingerprint.js';
|
|
48
|
+
export { credsSnapshot, } from './credsSnapshot.js';
|
|
49
|
+
export { readReceiptVariance, } from './readReceiptVariance.js';
|
|
50
|
+
// v3.5 new modules
|
|
51
|
+
export { proxyRotator, } from './proxyRotator.js';
|
|
52
|
+
// v3.6 new modules (Obscura-inspired)
|
|
53
|
+
export { generateSessionFingerprint, applySessionFingerprint, getMessageSendJitter, getTypingJitter, getRetryJitter, getVoiceNoteMetadata, getBatteryState, createStealthFingerprint, } from './sessionFingerprint.js';
|
|
54
|
+
// v3.8 new modules
|
|
55
|
+
export { getStealthSocketConfig, rampPresenceAfterConnect, STEALTH_BROWSER_POOL, AbortError, } from './stealthConnect.js';
|
|
56
|
+
// v4.0 new modules
|
|
57
|
+
export { GroupOperationGuard, classifyGroupOpError, extractPrivacyBlock, GROUP_OP_ERRORS, } from './groupOperationGuard.js';
|
|
58
|
+
// v4.1 new modules
|
|
59
|
+
export { LegitimacySignalInjector, } from './legitimacySignalInjector.js';
|
|
60
|
+
// v4.2 new modules
|
|
61
|
+
export { BanRecoveryOrchestrator, } from './banRecoveryOrchestrator.js';
|
|
62
|
+
// v4.3 new modules
|
|
63
|
+
export { DeliveryTracker } from './deliveryTracker.js';
|
|
64
|
+
// v4.4 new modules
|
|
65
|
+
export { InstanceCoordinator } from './instanceCoordinator.js';
|
|
66
|
+
// v4.7 new modules
|
|
67
|
+
export { JidCircuitBreaker, createJidCircuitBreaker } from './jidCircuitBreaker.js';
|
|
68
|
+
export { createFleetEventStore, createMySQLEventStoreBackend, createInMemoryEventStoreBackend } from './fleetEventStore.js';
|
|
69
|
+
export { HumanEntropyService, createHumanEntropyService } from './humanEntropy.js';
|
|
70
|
+
// v4.8 new modules
|
|
71
|
+
export { MessageTypeRegistry } from './messageTypeRegistry.js';
|
|
72
|
+
export { exportAntibanState, importAntibanState } from './stateExport.js';
|
|
73
|
+
export { TopologyThrottler } from './topologyThrottler.js';
|
|
74
|
+
// v4.9 new modules
|
|
75
|
+
export { ReputationVoucher } from './reputationVoucher.js';
|
|
76
|
+
// Observability
|
|
77
|
+
export { createConsoleLogger, exportPrometheusMetrics, createMetricsHandler, createPeriodicExporter, } from './observability.js';
|