baileys-antiban 4.9.0 → 4.10.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/dist/antiban.d.ts +3 -0
- package/dist/antiban.js +4 -0
- package/dist/cjs/antiban.js +4 -0
- package/dist/cjs/index.js +4 -1
- package/dist/cjs/reputationVoucher.js +377 -0
- package/dist/cjs/stateExport.js +8 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +2 -0
- package/dist/reputationVoucher.d.ts +171 -0
- package/dist/reputationVoucher.js +373 -0
- package/dist/stateExport.d.ts +9 -0
- package/dist/stateExport.js +8 -0
- package/package.json +1 -1
package/dist/antiban.d.ts
CHANGED
|
@@ -32,6 +32,7 @@ import { type DeliveryTrackerStats } from './deliveryTracker.js';
|
|
|
32
32
|
import { type InstanceCoordinatorStats } from './instanceCoordinator.js';
|
|
33
33
|
import { MessageTypeRegistry } from './messageTypeRegistry.js';
|
|
34
34
|
import { type AntibanSnapshot } from './stateExport.js';
|
|
35
|
+
import { ReputationVoucher } from './reputationVoucher.js';
|
|
35
36
|
export interface AntiBanConfigLegacy {
|
|
36
37
|
rateLimiter?: Partial<RateLimiterConfig>;
|
|
37
38
|
warmUp?: Partial<WarmUpConfig>;
|
|
@@ -116,6 +117,8 @@ export declare class AntiBan {
|
|
|
116
117
|
private stateManager;
|
|
117
118
|
private resolvedConfig;
|
|
118
119
|
private logging;
|
|
120
|
+
/** Optional reputation voucher (standalone — caller manages separate voucher sockets) */
|
|
121
|
+
reputationVoucher?: ReputationVoucher;
|
|
119
122
|
private stats;
|
|
120
123
|
constructor(input?: AntiBanInput | AntiBanConfigLegacy, warmUpStateArg?: WarmUpState);
|
|
121
124
|
/**
|
package/dist/antiban.js
CHANGED
|
@@ -117,6 +117,8 @@ export class AntiBan {
|
|
|
117
117
|
stateManager = null;
|
|
118
118
|
resolvedConfig;
|
|
119
119
|
logging;
|
|
120
|
+
/** Optional reputation voucher (standalone — caller manages separate voucher sockets) */
|
|
121
|
+
reputationVoucher;
|
|
120
122
|
stats = {
|
|
121
123
|
messagesAllowed: 0,
|
|
122
124
|
messagesBlocked: 0,
|
|
@@ -847,6 +849,7 @@ export class AntiBan {
|
|
|
847
849
|
timelockGuard: this.timelockGuard,
|
|
848
850
|
messageRegistry: this.messageTypeRegistry || undefined,
|
|
849
851
|
topologyThrottler: this.topologyThrottlerModule || undefined,
|
|
852
|
+
reputationVoucher: this.reputationVoucher || undefined,
|
|
850
853
|
instanceId: this.resolvedConfig.instanceId,
|
|
851
854
|
});
|
|
852
855
|
}
|
|
@@ -862,6 +865,7 @@ export class AntiBan {
|
|
|
862
865
|
timelockGuard: this.timelockGuard,
|
|
863
866
|
messageRegistry: this.messageTypeRegistry || undefined,
|
|
864
867
|
topologyThrottler: this.topologyThrottlerModule || undefined,
|
|
868
|
+
reputationVoucher: this.reputationVoucher || undefined,
|
|
865
869
|
});
|
|
866
870
|
}
|
|
867
871
|
/**
|
package/dist/cjs/antiban.js
CHANGED
|
@@ -120,6 +120,8 @@ class AntiBan {
|
|
|
120
120
|
stateManager = null;
|
|
121
121
|
resolvedConfig;
|
|
122
122
|
logging;
|
|
123
|
+
/** Optional reputation voucher (standalone — caller manages separate voucher sockets) */
|
|
124
|
+
reputationVoucher;
|
|
123
125
|
stats = {
|
|
124
126
|
messagesAllowed: 0,
|
|
125
127
|
messagesBlocked: 0,
|
|
@@ -850,6 +852,7 @@ class AntiBan {
|
|
|
850
852
|
timelockGuard: this.timelockGuard,
|
|
851
853
|
messageRegistry: this.messageTypeRegistry || undefined,
|
|
852
854
|
topologyThrottler: this.topologyThrottlerModule || undefined,
|
|
855
|
+
reputationVoucher: this.reputationVoucher || undefined,
|
|
853
856
|
instanceId: this.resolvedConfig.instanceId,
|
|
854
857
|
});
|
|
855
858
|
}
|
|
@@ -865,6 +868,7 @@ class AntiBan {
|
|
|
865
868
|
timelockGuard: this.timelockGuard,
|
|
866
869
|
messageRegistry: this.messageTypeRegistry || undefined,
|
|
867
870
|
topologyThrottler: this.topologyThrottlerModule || undefined,
|
|
871
|
+
reputationVoucher: this.reputationVoucher || undefined,
|
|
868
872
|
});
|
|
869
873
|
}
|
|
870
874
|
/**
|
package/dist/cjs/index.js
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
*/
|
|
11
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
12
|
exports.getRetryJitter = exports.getTypingJitter = exports.getMessageSendJitter = exports.applySessionFingerprint = exports.generateSessionFingerprint = exports.proxyRotator = exports.readReceiptVariance = exports.credsSnapshot = exports.applyFingerprint = exports.generateFingerprint = exports.messageRecovery = exports.applyGroupMultiplier = exports.shouldUseGroupProfile = exports.isBroadcast = exports.isNewsletter = exports.isGroup = exports.StateManager = exports.PRESETS = exports.resolveConfig = exports.FileStateAdapter = exports.Scheduler = exports.WebhookAlerts = exports.ContentVariator = exports.MessageQueue = exports.wrapSocketWithFingerprint = exports.wrapSocket = exports.getRetryReasonDescription = exports.isMacError = exports.parseRetryReason = exports.MAC_ERROR_CODES = exports.MessageRetryReason = exports.createLidFirstResolver = exports.LidFirstResolver = exports.DeafSessionDetector = exports.classifyDisconnect = exports.wrapWithSessionStability = exports.SessionHealthMonitor = exports.JidCanonicalizer = exports.LidResolver = exports.PostReconnectThrottle = exports.RetryReasonTracker = exports.getCircadianMultiplier = exports.PresenceChoreographer = exports.ContactGraphWarmer = exports.ReplyRatioGuard = exports.TimelockGuard = exports.HealthMonitor = exports.WarmUp = exports.RateLimiter = exports.AntiBan = void 0;
|
|
13
|
-
exports.createPeriodicExporter = exports.createMetricsHandler = exports.exportPrometheusMetrics = exports.createConsoleLogger = exports.TopologyThrottler = exports.importAntibanState = exports.exportAntibanState = exports.MessageTypeRegistry = exports.createHumanEntropyService = exports.HumanEntropyService = exports.createInMemoryEventStoreBackend = exports.createMySQLEventStoreBackend = exports.createFleetEventStore = exports.createJidCircuitBreaker = exports.JidCircuitBreaker = exports.InstanceCoordinator = exports.DeliveryTracker = exports.BanRecoveryOrchestrator = exports.LegitimacySignalInjector = exports.GROUP_OP_ERRORS = exports.extractPrivacyBlock = exports.classifyGroupOpError = exports.GroupOperationGuard = exports.AbortError = exports.STEALTH_BROWSER_POOL = exports.rampPresenceAfterConnect = exports.getStealthSocketConfig = exports.createStealthFingerprint = exports.getBatteryState = exports.getVoiceNoteMetadata = void 0;
|
|
13
|
+
exports.createPeriodicExporter = exports.createMetricsHandler = exports.exportPrometheusMetrics = exports.createConsoleLogger = exports.ReputationVoucher = exports.TopologyThrottler = exports.importAntibanState = exports.exportAntibanState = exports.MessageTypeRegistry = exports.createHumanEntropyService = exports.HumanEntropyService = exports.createInMemoryEventStoreBackend = exports.createMySQLEventStoreBackend = exports.createFleetEventStore = exports.createJidCircuitBreaker = exports.JidCircuitBreaker = exports.InstanceCoordinator = exports.DeliveryTracker = exports.BanRecoveryOrchestrator = exports.LegitimacySignalInjector = exports.GROUP_OP_ERRORS = exports.extractPrivacyBlock = exports.classifyGroupOpError = exports.GroupOperationGuard = exports.AbortError = exports.STEALTH_BROWSER_POOL = exports.rampPresenceAfterConnect = exports.getStealthSocketConfig = exports.createStealthFingerprint = exports.getBatteryState = exports.getVoiceNoteMetadata = void 0;
|
|
14
14
|
// Core
|
|
15
15
|
var antiban_js_1 = require("./antiban.js");
|
|
16
16
|
Object.defineProperty(exports, "AntiBan", { enumerable: true, get: function () { return antiban_js_1.AntiBan; } });
|
|
@@ -151,6 +151,9 @@ Object.defineProperty(exports, "exportAntibanState", { enumerable: true, get: fu
|
|
|
151
151
|
Object.defineProperty(exports, "importAntibanState", { enumerable: true, get: function () { return stateExport_js_1.importAntibanState; } });
|
|
152
152
|
var topologyThrottler_js_1 = require("./topologyThrottler.js");
|
|
153
153
|
Object.defineProperty(exports, "TopologyThrottler", { enumerable: true, get: function () { return topologyThrottler_js_1.TopologyThrottler; } });
|
|
154
|
+
// v4.9 new modules
|
|
155
|
+
var reputationVoucher_js_1 = require("./reputationVoucher.js");
|
|
156
|
+
Object.defineProperty(exports, "ReputationVoucher", { enumerable: true, get: function () { return reputationVoucher_js_1.ReputationVoucher; } });
|
|
154
157
|
// Observability
|
|
155
158
|
var observability_js_1 = require("./observability.js");
|
|
156
159
|
Object.defineProperty(exports, "createConsoleLogger", { enumerable: true, get: function () { return observability_js_1.createConsoleLogger; } });
|
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* ReputationVoucher — High-trust accounts vouch for new numbers
|
|
4
|
+
*
|
|
5
|
+
* Establishes genuine conversation history between trusted accounts and new numbers
|
|
6
|
+
* before those new numbers contact customers. Reduces warmup time and makes new
|
|
7
|
+
* accounts appear established with real bidirectional message history.
|
|
8
|
+
*
|
|
9
|
+
* Key design principles:
|
|
10
|
+
* - Dedicated sacrificial vouch accounts (separate from business accounts)
|
|
11
|
+
* - Max 5 vouches per week per vouching account (not per day)
|
|
12
|
+
* - Targets must complete 3 qualifying events before vouching
|
|
13
|
+
* - Strike system: 3 failed vouches = 90-day suspension for voucher
|
|
14
|
+
* - Blast radius containment: vouching accounts isolated from main business
|
|
15
|
+
*
|
|
16
|
+
* Usage:
|
|
17
|
+
* const rv = new ReputationVoucher();
|
|
18
|
+
*
|
|
19
|
+
* // Register a trusted account
|
|
20
|
+
* rv.registerVoucher({
|
|
21
|
+
* jid: '27123456789@s.whatsapp.net',
|
|
22
|
+
* trustScore: 85,
|
|
23
|
+
* accountAgeDays: 240
|
|
24
|
+
* });
|
|
25
|
+
*
|
|
26
|
+
* // Queue a new number for vouching
|
|
27
|
+
* rv.queueTarget({ jid: '27987654321@s.whatsapp.net' });
|
|
28
|
+
*
|
|
29
|
+
* // Record qualifying events (auction completion, payment, etc)
|
|
30
|
+
* rv.recordQualifyingEvent('27987654321@s.whatsapp.net');
|
|
31
|
+
* rv.recordQualifyingEvent('27987654321@s.whatsapp.net');
|
|
32
|
+
* rv.recordQualifyingEvent('27987654321@s.whatsapp.net');
|
|
33
|
+
*
|
|
34
|
+
* // Check if target qualifies
|
|
35
|
+
* const check = rv.targetQualifies('27987654321@s.whatsapp.net');
|
|
36
|
+
* if (check.qualified) {
|
|
37
|
+
* const voucher = rv.getAvailableVoucher();
|
|
38
|
+
* if (voucher) {
|
|
39
|
+
* // Plan the conversation
|
|
40
|
+
* const conversation = rv.planVouchConversation(voucher.jid, '27987654321@s.whatsapp.net');
|
|
41
|
+
*
|
|
42
|
+
* // Execute sends (caller must have separate socket for voucher account)
|
|
43
|
+
* // ... send conversation.messages ...
|
|
44
|
+
*
|
|
45
|
+
* // After target replies
|
|
46
|
+
* rv.recordVouchOutcome('27987654321@s.whatsapp.net', true);
|
|
47
|
+
*
|
|
48
|
+
* // Calculate warmup credit
|
|
49
|
+
* const daysCredit = rv.calculateWarmupCredit('27987654321@s.whatsapp.net');
|
|
50
|
+
* // Skip daysCredit days of warmup for this target
|
|
51
|
+
* }
|
|
52
|
+
* }
|
|
53
|
+
*/
|
|
54
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
55
|
+
exports.ReputationVoucher = void 0;
|
|
56
|
+
const DEFAULT_CONFIG = {
|
|
57
|
+
maxVouchesPerWeek: 5,
|
|
58
|
+
qualifyingEventsRequired: 3,
|
|
59
|
+
strikesForSuspension: 3,
|
|
60
|
+
suspensionDurationMs: 90 * 24 * 60 * 60 * 1000, // 90 days
|
|
61
|
+
minVoucherTrustScore: 60,
|
|
62
|
+
minVoucherAgeDays: 180, // 6 months
|
|
63
|
+
warmupMessages: [
|
|
64
|
+
"Hey, just checking this is the right number?",
|
|
65
|
+
"Hi! Got your details from the group",
|
|
66
|
+
"Morning! Are you available this week?",
|
|
67
|
+
"Hey there, hope this is a good time to connect",
|
|
68
|
+
"Hi, just wanted to reach out",
|
|
69
|
+
],
|
|
70
|
+
};
|
|
71
|
+
class ReputationVoucher {
|
|
72
|
+
config;
|
|
73
|
+
vouchers = new Map();
|
|
74
|
+
targets = new Map();
|
|
75
|
+
conversations = [];
|
|
76
|
+
constructor(config) {
|
|
77
|
+
this.config = { ...DEFAULT_CONFIG, ...config };
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Register a vouching account (high-trust, established number)
|
|
81
|
+
*/
|
|
82
|
+
registerVoucher(account) {
|
|
83
|
+
// Validate account meets minimum requirements
|
|
84
|
+
if (account.trustScore < this.config.minVoucherTrustScore) {
|
|
85
|
+
throw new Error(`Voucher trust score ${account.trustScore} below minimum ${this.config.minVoucherTrustScore}`);
|
|
86
|
+
}
|
|
87
|
+
if (account.accountAgeDays < this.config.minVoucherAgeDays) {
|
|
88
|
+
throw new Error(`Voucher account age ${account.accountAgeDays} days below minimum ${this.config.minVoucherAgeDays} days`);
|
|
89
|
+
}
|
|
90
|
+
const existing = this.vouchers.get(account.jid);
|
|
91
|
+
if (existing) {
|
|
92
|
+
// Update existing voucher (preserve counters)
|
|
93
|
+
this.vouchers.set(account.jid, {
|
|
94
|
+
...account,
|
|
95
|
+
vouchesThisWeek: existing.vouchesThisWeek,
|
|
96
|
+
totalVouches: existing.totalVouches,
|
|
97
|
+
failedVouches: existing.failedVouches,
|
|
98
|
+
strikes: existing.strikes,
|
|
99
|
+
suspendedUntil: existing.suspendedUntil,
|
|
100
|
+
lastVouchAt: existing.lastVouchAt,
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
// New voucher
|
|
105
|
+
this.vouchers.set(account.jid, {
|
|
106
|
+
...account,
|
|
107
|
+
vouchesThisWeek: 0,
|
|
108
|
+
totalVouches: 0,
|
|
109
|
+
failedVouches: 0,
|
|
110
|
+
strikes: 0,
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Queue a target for vouching
|
|
116
|
+
*/
|
|
117
|
+
queueTarget(target) {
|
|
118
|
+
const existing = this.targets.get(target.jid);
|
|
119
|
+
if (existing && existing.status !== 'failed') {
|
|
120
|
+
// Already queued or in progress
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
this.targets.set(target.jid, {
|
|
124
|
+
...target,
|
|
125
|
+
qualifyingEvents: target.qualifyingEvents || 0,
|
|
126
|
+
requestedAt: Date.now(),
|
|
127
|
+
status: 'pending',
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Check if a target qualifies for vouching
|
|
132
|
+
*/
|
|
133
|
+
targetQualifies(jid) {
|
|
134
|
+
const target = this.targets.get(jid);
|
|
135
|
+
if (!target) {
|
|
136
|
+
return { qualified: false, reason: 'Target not queued', eventsNeeded: this.config.qualifyingEventsRequired };
|
|
137
|
+
}
|
|
138
|
+
const events = target.qualifyingEvents || 0;
|
|
139
|
+
if (events < this.config.qualifyingEventsRequired) {
|
|
140
|
+
return {
|
|
141
|
+
qualified: false,
|
|
142
|
+
reason: `Not enough qualifying events (${events}/${this.config.qualifyingEventsRequired})`,
|
|
143
|
+
eventsNeeded: this.config.qualifyingEventsRequired - events,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
if (target.status === 'completed') {
|
|
147
|
+
return { qualified: false, reason: 'Already vouched' };
|
|
148
|
+
}
|
|
149
|
+
if (target.status === 'failed') {
|
|
150
|
+
return { qualified: false, reason: 'Previous vouch failed' };
|
|
151
|
+
}
|
|
152
|
+
return { qualified: true };
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Record a qualifying event for a target (e.g., auction completion, payment cleared)
|
|
156
|
+
*/
|
|
157
|
+
recordQualifyingEvent(jid) {
|
|
158
|
+
const target = this.targets.get(jid);
|
|
159
|
+
if (!target) {
|
|
160
|
+
// Auto-queue with first event
|
|
161
|
+
this.queueTarget({ jid, qualifyingEvents: 1 });
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
target.qualifyingEvents = (target.qualifyingEvents || 0) + 1;
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Get next available voucher for a target (respects limits + suspension)
|
|
168
|
+
*/
|
|
169
|
+
getAvailableVoucher() {
|
|
170
|
+
const now = Date.now();
|
|
171
|
+
const weekMs = 7 * 24 * 60 * 60 * 1000;
|
|
172
|
+
// Find eligible vouchers
|
|
173
|
+
const eligible = [];
|
|
174
|
+
for (const voucher of this.vouchers.values()) {
|
|
175
|
+
// Check suspension
|
|
176
|
+
if (voucher.suspendedUntil && voucher.suspendedUntil > now) {
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
// Reset weekly counter if 7 days have passed since last vouch
|
|
180
|
+
if (voucher.lastVouchAt && now - voucher.lastVouchAt > weekMs) {
|
|
181
|
+
voucher.vouchesThisWeek = 0;
|
|
182
|
+
}
|
|
183
|
+
// Check weekly limit
|
|
184
|
+
if (voucher.vouchesThisWeek >= this.config.maxVouchesPerWeek) {
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
eligible.push(voucher);
|
|
188
|
+
}
|
|
189
|
+
if (eligible.length === 0) {
|
|
190
|
+
return null;
|
|
191
|
+
}
|
|
192
|
+
// Sort by:
|
|
193
|
+
// 1. Lowest vouchesThisWeek (spread load)
|
|
194
|
+
// 2. Highest trustScore (best vouchers first)
|
|
195
|
+
// 3. Lowest totalVouches (rotate usage)
|
|
196
|
+
eligible.sort((a, b) => {
|
|
197
|
+
if (a.vouchesThisWeek !== b.vouchesThisWeek) {
|
|
198
|
+
return a.vouchesThisWeek - b.vouchesThisWeek;
|
|
199
|
+
}
|
|
200
|
+
if (a.trustScore !== b.trustScore) {
|
|
201
|
+
return b.trustScore - a.trustScore;
|
|
202
|
+
}
|
|
203
|
+
return a.totalVouches - b.totalVouches;
|
|
204
|
+
});
|
|
205
|
+
return eligible[0];
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Plan a vouch conversation.
|
|
209
|
+
* Returns a conversation plan — caller executes the sends.
|
|
210
|
+
*/
|
|
211
|
+
planVouchConversation(voucherJid, targetJid) {
|
|
212
|
+
const voucher = this.vouchers.get(voucherJid);
|
|
213
|
+
if (!voucher) {
|
|
214
|
+
throw new Error(`Voucher ${voucherJid} not registered`);
|
|
215
|
+
}
|
|
216
|
+
const target = this.targets.get(targetJid);
|
|
217
|
+
if (!target) {
|
|
218
|
+
throw new Error(`Target ${targetJid} not queued`);
|
|
219
|
+
}
|
|
220
|
+
const check = this.targetQualifies(targetJid);
|
|
221
|
+
if (!check.qualified) {
|
|
222
|
+
throw new Error(`Target ${targetJid} not qualified: ${check.reason}`);
|
|
223
|
+
}
|
|
224
|
+
// Update voucher counters
|
|
225
|
+
voucher.vouchesThisWeek++;
|
|
226
|
+
voucher.totalVouches++;
|
|
227
|
+
voucher.lastVouchAt = Date.now();
|
|
228
|
+
// Update target status
|
|
229
|
+
target.status = 'active';
|
|
230
|
+
target.vouchedAt = Date.now();
|
|
231
|
+
target.vouchedBy = voucherJid;
|
|
232
|
+
// Pick 2-3 warmup messages randomly
|
|
233
|
+
const messageCount = 2 + Math.floor(Math.random() * 2); // 2-3 messages
|
|
234
|
+
const selectedMessages = [];
|
|
235
|
+
const pool = [...this.config.warmupMessages];
|
|
236
|
+
for (let i = 0; i < messageCount && pool.length > 0; i++) {
|
|
237
|
+
const idx = Math.floor(Math.random() * pool.length);
|
|
238
|
+
selectedMessages.push(pool[idx]);
|
|
239
|
+
pool.splice(idx, 1); // Remove to avoid duplicates
|
|
240
|
+
}
|
|
241
|
+
// Create conversation plan
|
|
242
|
+
const conversation = {
|
|
243
|
+
targetJid,
|
|
244
|
+
voucherJid,
|
|
245
|
+
messages: selectedMessages.map((text, idx) => ({
|
|
246
|
+
direction: 'outbound',
|
|
247
|
+
timestamp: Date.now() + idx * 5000, // Stagger by 5s
|
|
248
|
+
text,
|
|
249
|
+
})),
|
|
250
|
+
startedAt: Date.now(),
|
|
251
|
+
success: false, // Will be updated when recordVouchOutcome is called
|
|
252
|
+
};
|
|
253
|
+
this.conversations.push(conversation);
|
|
254
|
+
return conversation;
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* Record outcome of a vouch.
|
|
258
|
+
* success = true if target replied within reasonable time (e.g., 7 days)
|
|
259
|
+
* success = false if target got banned within 7 days of vouch
|
|
260
|
+
*/
|
|
261
|
+
recordVouchOutcome(targetJid, success) {
|
|
262
|
+
const target = this.targets.get(targetJid);
|
|
263
|
+
if (!target || !target.vouchedBy) {
|
|
264
|
+
throw new Error(`Target ${targetJid} not vouched`);
|
|
265
|
+
}
|
|
266
|
+
const voucher = this.vouchers.get(target.vouchedBy);
|
|
267
|
+
if (!voucher) {
|
|
268
|
+
throw new Error(`Voucher ${target.vouchedBy} not found`);
|
|
269
|
+
}
|
|
270
|
+
// Find the conversation
|
|
271
|
+
const conversation = this.conversations.find((c) => c.targetJid === targetJid && c.voucherJid === target.vouchedBy);
|
|
272
|
+
if (conversation) {
|
|
273
|
+
conversation.success = success;
|
|
274
|
+
conversation.completedAt = Date.now();
|
|
275
|
+
}
|
|
276
|
+
if (success) {
|
|
277
|
+
target.status = 'completed';
|
|
278
|
+
}
|
|
279
|
+
else {
|
|
280
|
+
// Failed vouch — apply strike to voucher
|
|
281
|
+
target.status = 'failed';
|
|
282
|
+
voucher.failedVouches++;
|
|
283
|
+
voucher.strikes++;
|
|
284
|
+
// Check if suspension threshold reached
|
|
285
|
+
if (voucher.strikes >= this.config.strikesForSuspension) {
|
|
286
|
+
voucher.suspendedUntil = Date.now() + this.config.suspensionDurationMs;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* Calculate warmup credit for a successfully vouched target.
|
|
292
|
+
* Returns number of warmup days that can be skipped (0-3).
|
|
293
|
+
*
|
|
294
|
+
* Credit logic:
|
|
295
|
+
* - 1 reply = 1 day credit
|
|
296
|
+
* - 2+ replies = 2 days credit
|
|
297
|
+
* - Reply + 3+ days elapsed = 3 days credit (max)
|
|
298
|
+
*/
|
|
299
|
+
calculateWarmupCredit(targetJid) {
|
|
300
|
+
const target = this.targets.get(targetJid);
|
|
301
|
+
if (!target || target.status !== 'completed') {
|
|
302
|
+
return 0;
|
|
303
|
+
}
|
|
304
|
+
const conversation = this.conversations.find((c) => c.targetJid === targetJid && c.success);
|
|
305
|
+
if (!conversation) {
|
|
306
|
+
return 0;
|
|
307
|
+
}
|
|
308
|
+
// Count inbound messages (replies from target)
|
|
309
|
+
const inboundCount = conversation.messages.filter((m) => m.direction === 'inbound').length;
|
|
310
|
+
if (inboundCount === 0) {
|
|
311
|
+
return 0;
|
|
312
|
+
}
|
|
313
|
+
if (inboundCount === 1) {
|
|
314
|
+
return 1;
|
|
315
|
+
}
|
|
316
|
+
if (inboundCount >= 2) {
|
|
317
|
+
// Check if 3+ days elapsed
|
|
318
|
+
const daysElapsed = (Date.now() - conversation.startedAt) / (24 * 60 * 60 * 1000);
|
|
319
|
+
if (daysElapsed >= 3) {
|
|
320
|
+
return 3; // Max credit
|
|
321
|
+
}
|
|
322
|
+
return 2;
|
|
323
|
+
}
|
|
324
|
+
return 0;
|
|
325
|
+
}
|
|
326
|
+
/**
|
|
327
|
+
* Get stats for a specific voucher
|
|
328
|
+
*/
|
|
329
|
+
getVoucherStats(jid) {
|
|
330
|
+
const voucher = this.vouchers.get(jid);
|
|
331
|
+
return voucher ? { ...voucher } : null;
|
|
332
|
+
}
|
|
333
|
+
/**
|
|
334
|
+
* Get all pending targets (awaiting vouching)
|
|
335
|
+
*/
|
|
336
|
+
getPendingTargets() {
|
|
337
|
+
return Array.from(this.targets.values()).filter((t) => t.status === 'pending');
|
|
338
|
+
}
|
|
339
|
+
/**
|
|
340
|
+
* Get vouch history
|
|
341
|
+
*/
|
|
342
|
+
getVouchHistory() {
|
|
343
|
+
return [...this.conversations];
|
|
344
|
+
}
|
|
345
|
+
/**
|
|
346
|
+
* Export state for persistence
|
|
347
|
+
*/
|
|
348
|
+
exportState() {
|
|
349
|
+
return {
|
|
350
|
+
version: 1,
|
|
351
|
+
exportedAt: Date.now(),
|
|
352
|
+
vouchers: Array.from(this.vouchers.values()),
|
|
353
|
+
targets: Array.from(this.targets.values()),
|
|
354
|
+
conversations: [...this.conversations],
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
/**
|
|
358
|
+
* Import state from persistence
|
|
359
|
+
*/
|
|
360
|
+
importState(state) {
|
|
361
|
+
// Clear existing state
|
|
362
|
+
this.vouchers.clear();
|
|
363
|
+
this.targets.clear();
|
|
364
|
+
this.conversations = [];
|
|
365
|
+
// Import vouchers
|
|
366
|
+
for (const voucher of state.vouchers) {
|
|
367
|
+
this.vouchers.set(voucher.jid, { ...voucher });
|
|
368
|
+
}
|
|
369
|
+
// Import targets
|
|
370
|
+
for (const target of state.targets) {
|
|
371
|
+
this.targets.set(target.jid, { ...target });
|
|
372
|
+
}
|
|
373
|
+
// Import conversations
|
|
374
|
+
this.conversations = [...state.conversations];
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
exports.ReputationVoucher = ReputationVoucher;
|
package/dist/cjs/stateExport.js
CHANGED
|
@@ -75,6 +75,10 @@ function exportAntibanState(modules) {
|
|
|
75
75
|
if (modules.topologyThrottler) {
|
|
76
76
|
snapshot.topologyThrottler = modules.topologyThrottler.exportState();
|
|
77
77
|
}
|
|
78
|
+
// Export reputation voucher state
|
|
79
|
+
if (modules.reputationVoucher) {
|
|
80
|
+
snapshot.reputationVoucher = modules.reputationVoucher.exportState();
|
|
81
|
+
}
|
|
78
82
|
// Export engagement scores
|
|
79
83
|
if (modules.engagementScores) {
|
|
80
84
|
snapshot.engagementScores = Object.fromEntries(modules.engagementScores.entries());
|
|
@@ -152,6 +156,10 @@ function importAntibanState(snapshot, modules) {
|
|
|
152
156
|
if (snapshot.topologyThrottler && modules.topologyThrottler) {
|
|
153
157
|
modules.topologyThrottler.importState(snapshot.topologyThrottler);
|
|
154
158
|
}
|
|
159
|
+
// Import reputation voucher state
|
|
160
|
+
if (snapshot.reputationVoucher && modules.reputationVoucher) {
|
|
161
|
+
modules.reputationVoucher.importState(snapshot.reputationVoucher);
|
|
162
|
+
}
|
|
155
163
|
// Import engagement scores
|
|
156
164
|
if (snapshot.engagementScores && modules.engagementScores) {
|
|
157
165
|
for (const [jid, score] of Object.entries(snapshot.engagementScores)) {
|
package/dist/index.d.ts
CHANGED
|
@@ -49,4 +49,5 @@ export { HumanEntropyService, createHumanEntropyService, type HumanEntropyConfig
|
|
|
49
49
|
export { MessageTypeRegistry, type MessageTypeDefinition, type MessageProvenance, type MessageTypeStats, type MessageTypeWarning, type MessageTypeRegistryState } from './messageTypeRegistry.js';
|
|
50
50
|
export { exportAntibanState, importAntibanState, type AntibanSnapshot, type RateLimiterState, type TimelockGuardState, type CircuitState as CircuitStateExport, type DisconnectEvent } from './stateExport.js';
|
|
51
51
|
export { TopologyThrottler, type TopologyThrottlerConfig, type ContactRisk, type ContactRiskAssessment, type ContactRiskConfig, type TopologyThrottlerState } from './topologyThrottler.js';
|
|
52
|
+
export { ReputationVoucher, type ReputationVoucherConfig, type VouchTarget, type VouchingAccount, type VouchConversation, type ReputationVoucherState } from './reputationVoucher.js';
|
|
52
53
|
export { createConsoleLogger, exportPrometheusMetrics, createMetricsHandler, createPeriodicExporter, type AntiBanLogger, type PeriodicExporterConfig, type PeriodicExporterHandle, } from './observability.js';
|
package/dist/index.js
CHANGED
|
@@ -71,5 +71,7 @@ export { HumanEntropyService, createHumanEntropyService } from './humanEntropy.j
|
|
|
71
71
|
export { MessageTypeRegistry } from './messageTypeRegistry.js';
|
|
72
72
|
export { exportAntibanState, importAntibanState } from './stateExport.js';
|
|
73
73
|
export { TopologyThrottler } from './topologyThrottler.js';
|
|
74
|
+
// v4.9 new modules
|
|
75
|
+
export { ReputationVoucher } from './reputationVoucher.js';
|
|
74
76
|
// Observability
|
|
75
77
|
export { createConsoleLogger, exportPrometheusMetrics, createMetricsHandler, createPeriodicExporter, } from './observability.js';
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ReputationVoucher — High-trust accounts vouch for new numbers
|
|
3
|
+
*
|
|
4
|
+
* Establishes genuine conversation history between trusted accounts and new numbers
|
|
5
|
+
* before those new numbers contact customers. Reduces warmup time and makes new
|
|
6
|
+
* accounts appear established with real bidirectional message history.
|
|
7
|
+
*
|
|
8
|
+
* Key design principles:
|
|
9
|
+
* - Dedicated sacrificial vouch accounts (separate from business accounts)
|
|
10
|
+
* - Max 5 vouches per week per vouching account (not per day)
|
|
11
|
+
* - Targets must complete 3 qualifying events before vouching
|
|
12
|
+
* - Strike system: 3 failed vouches = 90-day suspension for voucher
|
|
13
|
+
* - Blast radius containment: vouching accounts isolated from main business
|
|
14
|
+
*
|
|
15
|
+
* Usage:
|
|
16
|
+
* const rv = new ReputationVoucher();
|
|
17
|
+
*
|
|
18
|
+
* // Register a trusted account
|
|
19
|
+
* rv.registerVoucher({
|
|
20
|
+
* jid: '27123456789@s.whatsapp.net',
|
|
21
|
+
* trustScore: 85,
|
|
22
|
+
* accountAgeDays: 240
|
|
23
|
+
* });
|
|
24
|
+
*
|
|
25
|
+
* // Queue a new number for vouching
|
|
26
|
+
* rv.queueTarget({ jid: '27987654321@s.whatsapp.net' });
|
|
27
|
+
*
|
|
28
|
+
* // Record qualifying events (auction completion, payment, etc)
|
|
29
|
+
* rv.recordQualifyingEvent('27987654321@s.whatsapp.net');
|
|
30
|
+
* rv.recordQualifyingEvent('27987654321@s.whatsapp.net');
|
|
31
|
+
* rv.recordQualifyingEvent('27987654321@s.whatsapp.net');
|
|
32
|
+
*
|
|
33
|
+
* // Check if target qualifies
|
|
34
|
+
* const check = rv.targetQualifies('27987654321@s.whatsapp.net');
|
|
35
|
+
* if (check.qualified) {
|
|
36
|
+
* const voucher = rv.getAvailableVoucher();
|
|
37
|
+
* if (voucher) {
|
|
38
|
+
* // Plan the conversation
|
|
39
|
+
* const conversation = rv.planVouchConversation(voucher.jid, '27987654321@s.whatsapp.net');
|
|
40
|
+
*
|
|
41
|
+
* // Execute sends (caller must have separate socket for voucher account)
|
|
42
|
+
* // ... send conversation.messages ...
|
|
43
|
+
*
|
|
44
|
+
* // After target replies
|
|
45
|
+
* rv.recordVouchOutcome('27987654321@s.whatsapp.net', true);
|
|
46
|
+
*
|
|
47
|
+
* // Calculate warmup credit
|
|
48
|
+
* const daysCredit = rv.calculateWarmupCredit('27987654321@s.whatsapp.net');
|
|
49
|
+
* // Skip daysCredit days of warmup for this target
|
|
50
|
+
* }
|
|
51
|
+
* }
|
|
52
|
+
*/
|
|
53
|
+
export interface VouchTarget {
|
|
54
|
+
jid: string;
|
|
55
|
+
qualifyingEvents?: number;
|
|
56
|
+
requestedAt: number;
|
|
57
|
+
vouchedAt?: number;
|
|
58
|
+
vouchedBy?: string;
|
|
59
|
+
status: 'pending' | 'active' | 'completed' | 'failed';
|
|
60
|
+
}
|
|
61
|
+
export interface VouchingAccount {
|
|
62
|
+
jid: string;
|
|
63
|
+
trustScore: number;
|
|
64
|
+
accountAgeDays: number;
|
|
65
|
+
vouchesThisWeek: number;
|
|
66
|
+
totalVouches: number;
|
|
67
|
+
failedVouches: number;
|
|
68
|
+
strikes: number;
|
|
69
|
+
suspendedUntil?: number;
|
|
70
|
+
lastVouchAt?: number;
|
|
71
|
+
}
|
|
72
|
+
export interface VouchConversation {
|
|
73
|
+
targetJid: string;
|
|
74
|
+
voucherJid: string;
|
|
75
|
+
messages: Array<{
|
|
76
|
+
direction: 'outbound' | 'inbound';
|
|
77
|
+
timestamp: number;
|
|
78
|
+
text: string;
|
|
79
|
+
}>;
|
|
80
|
+
startedAt: number;
|
|
81
|
+
completedAt?: number;
|
|
82
|
+
success: boolean;
|
|
83
|
+
}
|
|
84
|
+
export interface ReputationVoucherConfig {
|
|
85
|
+
maxVouchesPerWeek?: number;
|
|
86
|
+
qualifyingEventsRequired?: number;
|
|
87
|
+
strikesForSuspension?: number;
|
|
88
|
+
suspensionDurationMs?: number;
|
|
89
|
+
minVoucherTrustScore?: number;
|
|
90
|
+
minVoucherAgeDays?: number;
|
|
91
|
+
warmupMessages?: string[];
|
|
92
|
+
}
|
|
93
|
+
export interface ReputationVoucherState {
|
|
94
|
+
version: number;
|
|
95
|
+
exportedAt: number;
|
|
96
|
+
vouchers: VouchingAccount[];
|
|
97
|
+
targets: VouchTarget[];
|
|
98
|
+
conversations: VouchConversation[];
|
|
99
|
+
}
|
|
100
|
+
export declare class ReputationVoucher {
|
|
101
|
+
private config;
|
|
102
|
+
private vouchers;
|
|
103
|
+
private targets;
|
|
104
|
+
private conversations;
|
|
105
|
+
constructor(config?: ReputationVoucherConfig);
|
|
106
|
+
/**
|
|
107
|
+
* Register a vouching account (high-trust, established number)
|
|
108
|
+
*/
|
|
109
|
+
registerVoucher(account: Omit<VouchingAccount, 'vouchesThisWeek' | 'totalVouches' | 'failedVouches' | 'strikes'>): void;
|
|
110
|
+
/**
|
|
111
|
+
* Queue a target for vouching
|
|
112
|
+
*/
|
|
113
|
+
queueTarget(target: Omit<VouchTarget, 'status' | 'requestedAt'>): void;
|
|
114
|
+
/**
|
|
115
|
+
* Check if a target qualifies for vouching
|
|
116
|
+
*/
|
|
117
|
+
targetQualifies(jid: string): {
|
|
118
|
+
qualified: boolean;
|
|
119
|
+
reason?: string;
|
|
120
|
+
eventsNeeded?: number;
|
|
121
|
+
};
|
|
122
|
+
/**
|
|
123
|
+
* Record a qualifying event for a target (e.g., auction completion, payment cleared)
|
|
124
|
+
*/
|
|
125
|
+
recordQualifyingEvent(jid: string): void;
|
|
126
|
+
/**
|
|
127
|
+
* Get next available voucher for a target (respects limits + suspension)
|
|
128
|
+
*/
|
|
129
|
+
getAvailableVoucher(): VouchingAccount | null;
|
|
130
|
+
/**
|
|
131
|
+
* Plan a vouch conversation.
|
|
132
|
+
* Returns a conversation plan — caller executes the sends.
|
|
133
|
+
*/
|
|
134
|
+
planVouchConversation(voucherJid: string, targetJid: string): VouchConversation;
|
|
135
|
+
/**
|
|
136
|
+
* Record outcome of a vouch.
|
|
137
|
+
* success = true if target replied within reasonable time (e.g., 7 days)
|
|
138
|
+
* success = false if target got banned within 7 days of vouch
|
|
139
|
+
*/
|
|
140
|
+
recordVouchOutcome(targetJid: string, success: boolean): void;
|
|
141
|
+
/**
|
|
142
|
+
* Calculate warmup credit for a successfully vouched target.
|
|
143
|
+
* Returns number of warmup days that can be skipped (0-3).
|
|
144
|
+
*
|
|
145
|
+
* Credit logic:
|
|
146
|
+
* - 1 reply = 1 day credit
|
|
147
|
+
* - 2+ replies = 2 days credit
|
|
148
|
+
* - Reply + 3+ days elapsed = 3 days credit (max)
|
|
149
|
+
*/
|
|
150
|
+
calculateWarmupCredit(targetJid: string): number;
|
|
151
|
+
/**
|
|
152
|
+
* Get stats for a specific voucher
|
|
153
|
+
*/
|
|
154
|
+
getVoucherStats(jid: string): VouchingAccount | null;
|
|
155
|
+
/**
|
|
156
|
+
* Get all pending targets (awaiting vouching)
|
|
157
|
+
*/
|
|
158
|
+
getPendingTargets(): VouchTarget[];
|
|
159
|
+
/**
|
|
160
|
+
* Get vouch history
|
|
161
|
+
*/
|
|
162
|
+
getVouchHistory(): VouchConversation[];
|
|
163
|
+
/**
|
|
164
|
+
* Export state for persistence
|
|
165
|
+
*/
|
|
166
|
+
exportState(): ReputationVoucherState;
|
|
167
|
+
/**
|
|
168
|
+
* Import state from persistence
|
|
169
|
+
*/
|
|
170
|
+
importState(state: ReputationVoucherState): void;
|
|
171
|
+
}
|
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ReputationVoucher — High-trust accounts vouch for new numbers
|
|
3
|
+
*
|
|
4
|
+
* Establishes genuine conversation history between trusted accounts and new numbers
|
|
5
|
+
* before those new numbers contact customers. Reduces warmup time and makes new
|
|
6
|
+
* accounts appear established with real bidirectional message history.
|
|
7
|
+
*
|
|
8
|
+
* Key design principles:
|
|
9
|
+
* - Dedicated sacrificial vouch accounts (separate from business accounts)
|
|
10
|
+
* - Max 5 vouches per week per vouching account (not per day)
|
|
11
|
+
* - Targets must complete 3 qualifying events before vouching
|
|
12
|
+
* - Strike system: 3 failed vouches = 90-day suspension for voucher
|
|
13
|
+
* - Blast radius containment: vouching accounts isolated from main business
|
|
14
|
+
*
|
|
15
|
+
* Usage:
|
|
16
|
+
* const rv = new ReputationVoucher();
|
|
17
|
+
*
|
|
18
|
+
* // Register a trusted account
|
|
19
|
+
* rv.registerVoucher({
|
|
20
|
+
* jid: '27123456789@s.whatsapp.net',
|
|
21
|
+
* trustScore: 85,
|
|
22
|
+
* accountAgeDays: 240
|
|
23
|
+
* });
|
|
24
|
+
*
|
|
25
|
+
* // Queue a new number for vouching
|
|
26
|
+
* rv.queueTarget({ jid: '27987654321@s.whatsapp.net' });
|
|
27
|
+
*
|
|
28
|
+
* // Record qualifying events (auction completion, payment, etc)
|
|
29
|
+
* rv.recordQualifyingEvent('27987654321@s.whatsapp.net');
|
|
30
|
+
* rv.recordQualifyingEvent('27987654321@s.whatsapp.net');
|
|
31
|
+
* rv.recordQualifyingEvent('27987654321@s.whatsapp.net');
|
|
32
|
+
*
|
|
33
|
+
* // Check if target qualifies
|
|
34
|
+
* const check = rv.targetQualifies('27987654321@s.whatsapp.net');
|
|
35
|
+
* if (check.qualified) {
|
|
36
|
+
* const voucher = rv.getAvailableVoucher();
|
|
37
|
+
* if (voucher) {
|
|
38
|
+
* // Plan the conversation
|
|
39
|
+
* const conversation = rv.planVouchConversation(voucher.jid, '27987654321@s.whatsapp.net');
|
|
40
|
+
*
|
|
41
|
+
* // Execute sends (caller must have separate socket for voucher account)
|
|
42
|
+
* // ... send conversation.messages ...
|
|
43
|
+
*
|
|
44
|
+
* // After target replies
|
|
45
|
+
* rv.recordVouchOutcome('27987654321@s.whatsapp.net', true);
|
|
46
|
+
*
|
|
47
|
+
* // Calculate warmup credit
|
|
48
|
+
* const daysCredit = rv.calculateWarmupCredit('27987654321@s.whatsapp.net');
|
|
49
|
+
* // Skip daysCredit days of warmup for this target
|
|
50
|
+
* }
|
|
51
|
+
* }
|
|
52
|
+
*/
|
|
53
|
+
const DEFAULT_CONFIG = {
|
|
54
|
+
maxVouchesPerWeek: 5,
|
|
55
|
+
qualifyingEventsRequired: 3,
|
|
56
|
+
strikesForSuspension: 3,
|
|
57
|
+
suspensionDurationMs: 90 * 24 * 60 * 60 * 1000, // 90 days
|
|
58
|
+
minVoucherTrustScore: 60,
|
|
59
|
+
minVoucherAgeDays: 180, // 6 months
|
|
60
|
+
warmupMessages: [
|
|
61
|
+
"Hey, just checking this is the right number?",
|
|
62
|
+
"Hi! Got your details from the group",
|
|
63
|
+
"Morning! Are you available this week?",
|
|
64
|
+
"Hey there, hope this is a good time to connect",
|
|
65
|
+
"Hi, just wanted to reach out",
|
|
66
|
+
],
|
|
67
|
+
};
|
|
68
|
+
export class ReputationVoucher {
|
|
69
|
+
config;
|
|
70
|
+
vouchers = new Map();
|
|
71
|
+
targets = new Map();
|
|
72
|
+
conversations = [];
|
|
73
|
+
constructor(config) {
|
|
74
|
+
this.config = { ...DEFAULT_CONFIG, ...config };
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Register a vouching account (high-trust, established number)
|
|
78
|
+
*/
|
|
79
|
+
registerVoucher(account) {
|
|
80
|
+
// Validate account meets minimum requirements
|
|
81
|
+
if (account.trustScore < this.config.minVoucherTrustScore) {
|
|
82
|
+
throw new Error(`Voucher trust score ${account.trustScore} below minimum ${this.config.minVoucherTrustScore}`);
|
|
83
|
+
}
|
|
84
|
+
if (account.accountAgeDays < this.config.minVoucherAgeDays) {
|
|
85
|
+
throw new Error(`Voucher account age ${account.accountAgeDays} days below minimum ${this.config.minVoucherAgeDays} days`);
|
|
86
|
+
}
|
|
87
|
+
const existing = this.vouchers.get(account.jid);
|
|
88
|
+
if (existing) {
|
|
89
|
+
// Update existing voucher (preserve counters)
|
|
90
|
+
this.vouchers.set(account.jid, {
|
|
91
|
+
...account,
|
|
92
|
+
vouchesThisWeek: existing.vouchesThisWeek,
|
|
93
|
+
totalVouches: existing.totalVouches,
|
|
94
|
+
failedVouches: existing.failedVouches,
|
|
95
|
+
strikes: existing.strikes,
|
|
96
|
+
suspendedUntil: existing.suspendedUntil,
|
|
97
|
+
lastVouchAt: existing.lastVouchAt,
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
// New voucher
|
|
102
|
+
this.vouchers.set(account.jid, {
|
|
103
|
+
...account,
|
|
104
|
+
vouchesThisWeek: 0,
|
|
105
|
+
totalVouches: 0,
|
|
106
|
+
failedVouches: 0,
|
|
107
|
+
strikes: 0,
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Queue a target for vouching
|
|
113
|
+
*/
|
|
114
|
+
queueTarget(target) {
|
|
115
|
+
const existing = this.targets.get(target.jid);
|
|
116
|
+
if (existing && existing.status !== 'failed') {
|
|
117
|
+
// Already queued or in progress
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
this.targets.set(target.jid, {
|
|
121
|
+
...target,
|
|
122
|
+
qualifyingEvents: target.qualifyingEvents || 0,
|
|
123
|
+
requestedAt: Date.now(),
|
|
124
|
+
status: 'pending',
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Check if a target qualifies for vouching
|
|
129
|
+
*/
|
|
130
|
+
targetQualifies(jid) {
|
|
131
|
+
const target = this.targets.get(jid);
|
|
132
|
+
if (!target) {
|
|
133
|
+
return { qualified: false, reason: 'Target not queued', eventsNeeded: this.config.qualifyingEventsRequired };
|
|
134
|
+
}
|
|
135
|
+
const events = target.qualifyingEvents || 0;
|
|
136
|
+
if (events < this.config.qualifyingEventsRequired) {
|
|
137
|
+
return {
|
|
138
|
+
qualified: false,
|
|
139
|
+
reason: `Not enough qualifying events (${events}/${this.config.qualifyingEventsRequired})`,
|
|
140
|
+
eventsNeeded: this.config.qualifyingEventsRequired - events,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
if (target.status === 'completed') {
|
|
144
|
+
return { qualified: false, reason: 'Already vouched' };
|
|
145
|
+
}
|
|
146
|
+
if (target.status === 'failed') {
|
|
147
|
+
return { qualified: false, reason: 'Previous vouch failed' };
|
|
148
|
+
}
|
|
149
|
+
return { qualified: true };
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Record a qualifying event for a target (e.g., auction completion, payment cleared)
|
|
153
|
+
*/
|
|
154
|
+
recordQualifyingEvent(jid) {
|
|
155
|
+
const target = this.targets.get(jid);
|
|
156
|
+
if (!target) {
|
|
157
|
+
// Auto-queue with first event
|
|
158
|
+
this.queueTarget({ jid, qualifyingEvents: 1 });
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
target.qualifyingEvents = (target.qualifyingEvents || 0) + 1;
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Get next available voucher for a target (respects limits + suspension)
|
|
165
|
+
*/
|
|
166
|
+
getAvailableVoucher() {
|
|
167
|
+
const now = Date.now();
|
|
168
|
+
const weekMs = 7 * 24 * 60 * 60 * 1000;
|
|
169
|
+
// Find eligible vouchers
|
|
170
|
+
const eligible = [];
|
|
171
|
+
for (const voucher of this.vouchers.values()) {
|
|
172
|
+
// Check suspension
|
|
173
|
+
if (voucher.suspendedUntil && voucher.suspendedUntil > now) {
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
// Reset weekly counter if 7 days have passed since last vouch
|
|
177
|
+
if (voucher.lastVouchAt && now - voucher.lastVouchAt > weekMs) {
|
|
178
|
+
voucher.vouchesThisWeek = 0;
|
|
179
|
+
}
|
|
180
|
+
// Check weekly limit
|
|
181
|
+
if (voucher.vouchesThisWeek >= this.config.maxVouchesPerWeek) {
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
eligible.push(voucher);
|
|
185
|
+
}
|
|
186
|
+
if (eligible.length === 0) {
|
|
187
|
+
return null;
|
|
188
|
+
}
|
|
189
|
+
// Sort by:
|
|
190
|
+
// 1. Lowest vouchesThisWeek (spread load)
|
|
191
|
+
// 2. Highest trustScore (best vouchers first)
|
|
192
|
+
// 3. Lowest totalVouches (rotate usage)
|
|
193
|
+
eligible.sort((a, b) => {
|
|
194
|
+
if (a.vouchesThisWeek !== b.vouchesThisWeek) {
|
|
195
|
+
return a.vouchesThisWeek - b.vouchesThisWeek;
|
|
196
|
+
}
|
|
197
|
+
if (a.trustScore !== b.trustScore) {
|
|
198
|
+
return b.trustScore - a.trustScore;
|
|
199
|
+
}
|
|
200
|
+
return a.totalVouches - b.totalVouches;
|
|
201
|
+
});
|
|
202
|
+
return eligible[0];
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Plan a vouch conversation.
|
|
206
|
+
* Returns a conversation plan — caller executes the sends.
|
|
207
|
+
*/
|
|
208
|
+
planVouchConversation(voucherJid, targetJid) {
|
|
209
|
+
const voucher = this.vouchers.get(voucherJid);
|
|
210
|
+
if (!voucher) {
|
|
211
|
+
throw new Error(`Voucher ${voucherJid} not registered`);
|
|
212
|
+
}
|
|
213
|
+
const target = this.targets.get(targetJid);
|
|
214
|
+
if (!target) {
|
|
215
|
+
throw new Error(`Target ${targetJid} not queued`);
|
|
216
|
+
}
|
|
217
|
+
const check = this.targetQualifies(targetJid);
|
|
218
|
+
if (!check.qualified) {
|
|
219
|
+
throw new Error(`Target ${targetJid} not qualified: ${check.reason}`);
|
|
220
|
+
}
|
|
221
|
+
// Update voucher counters
|
|
222
|
+
voucher.vouchesThisWeek++;
|
|
223
|
+
voucher.totalVouches++;
|
|
224
|
+
voucher.lastVouchAt = Date.now();
|
|
225
|
+
// Update target status
|
|
226
|
+
target.status = 'active';
|
|
227
|
+
target.vouchedAt = Date.now();
|
|
228
|
+
target.vouchedBy = voucherJid;
|
|
229
|
+
// Pick 2-3 warmup messages randomly
|
|
230
|
+
const messageCount = 2 + Math.floor(Math.random() * 2); // 2-3 messages
|
|
231
|
+
const selectedMessages = [];
|
|
232
|
+
const pool = [...this.config.warmupMessages];
|
|
233
|
+
for (let i = 0; i < messageCount && pool.length > 0; i++) {
|
|
234
|
+
const idx = Math.floor(Math.random() * pool.length);
|
|
235
|
+
selectedMessages.push(pool[idx]);
|
|
236
|
+
pool.splice(idx, 1); // Remove to avoid duplicates
|
|
237
|
+
}
|
|
238
|
+
// Create conversation plan
|
|
239
|
+
const conversation = {
|
|
240
|
+
targetJid,
|
|
241
|
+
voucherJid,
|
|
242
|
+
messages: selectedMessages.map((text, idx) => ({
|
|
243
|
+
direction: 'outbound',
|
|
244
|
+
timestamp: Date.now() + idx * 5000, // Stagger by 5s
|
|
245
|
+
text,
|
|
246
|
+
})),
|
|
247
|
+
startedAt: Date.now(),
|
|
248
|
+
success: false, // Will be updated when recordVouchOutcome is called
|
|
249
|
+
};
|
|
250
|
+
this.conversations.push(conversation);
|
|
251
|
+
return conversation;
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* Record outcome of a vouch.
|
|
255
|
+
* success = true if target replied within reasonable time (e.g., 7 days)
|
|
256
|
+
* success = false if target got banned within 7 days of vouch
|
|
257
|
+
*/
|
|
258
|
+
recordVouchOutcome(targetJid, success) {
|
|
259
|
+
const target = this.targets.get(targetJid);
|
|
260
|
+
if (!target || !target.vouchedBy) {
|
|
261
|
+
throw new Error(`Target ${targetJid} not vouched`);
|
|
262
|
+
}
|
|
263
|
+
const voucher = this.vouchers.get(target.vouchedBy);
|
|
264
|
+
if (!voucher) {
|
|
265
|
+
throw new Error(`Voucher ${target.vouchedBy} not found`);
|
|
266
|
+
}
|
|
267
|
+
// Find the conversation
|
|
268
|
+
const conversation = this.conversations.find((c) => c.targetJid === targetJid && c.voucherJid === target.vouchedBy);
|
|
269
|
+
if (conversation) {
|
|
270
|
+
conversation.success = success;
|
|
271
|
+
conversation.completedAt = Date.now();
|
|
272
|
+
}
|
|
273
|
+
if (success) {
|
|
274
|
+
target.status = 'completed';
|
|
275
|
+
}
|
|
276
|
+
else {
|
|
277
|
+
// Failed vouch — apply strike to voucher
|
|
278
|
+
target.status = 'failed';
|
|
279
|
+
voucher.failedVouches++;
|
|
280
|
+
voucher.strikes++;
|
|
281
|
+
// Check if suspension threshold reached
|
|
282
|
+
if (voucher.strikes >= this.config.strikesForSuspension) {
|
|
283
|
+
voucher.suspendedUntil = Date.now() + this.config.suspensionDurationMs;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
/**
|
|
288
|
+
* Calculate warmup credit for a successfully vouched target.
|
|
289
|
+
* Returns number of warmup days that can be skipped (0-3).
|
|
290
|
+
*
|
|
291
|
+
* Credit logic:
|
|
292
|
+
* - 1 reply = 1 day credit
|
|
293
|
+
* - 2+ replies = 2 days credit
|
|
294
|
+
* - Reply + 3+ days elapsed = 3 days credit (max)
|
|
295
|
+
*/
|
|
296
|
+
calculateWarmupCredit(targetJid) {
|
|
297
|
+
const target = this.targets.get(targetJid);
|
|
298
|
+
if (!target || target.status !== 'completed') {
|
|
299
|
+
return 0;
|
|
300
|
+
}
|
|
301
|
+
const conversation = this.conversations.find((c) => c.targetJid === targetJid && c.success);
|
|
302
|
+
if (!conversation) {
|
|
303
|
+
return 0;
|
|
304
|
+
}
|
|
305
|
+
// Count inbound messages (replies from target)
|
|
306
|
+
const inboundCount = conversation.messages.filter((m) => m.direction === 'inbound').length;
|
|
307
|
+
if (inboundCount === 0) {
|
|
308
|
+
return 0;
|
|
309
|
+
}
|
|
310
|
+
if (inboundCount === 1) {
|
|
311
|
+
return 1;
|
|
312
|
+
}
|
|
313
|
+
if (inboundCount >= 2) {
|
|
314
|
+
// Check if 3+ days elapsed
|
|
315
|
+
const daysElapsed = (Date.now() - conversation.startedAt) / (24 * 60 * 60 * 1000);
|
|
316
|
+
if (daysElapsed >= 3) {
|
|
317
|
+
return 3; // Max credit
|
|
318
|
+
}
|
|
319
|
+
return 2;
|
|
320
|
+
}
|
|
321
|
+
return 0;
|
|
322
|
+
}
|
|
323
|
+
/**
|
|
324
|
+
* Get stats for a specific voucher
|
|
325
|
+
*/
|
|
326
|
+
getVoucherStats(jid) {
|
|
327
|
+
const voucher = this.vouchers.get(jid);
|
|
328
|
+
return voucher ? { ...voucher } : null;
|
|
329
|
+
}
|
|
330
|
+
/**
|
|
331
|
+
* Get all pending targets (awaiting vouching)
|
|
332
|
+
*/
|
|
333
|
+
getPendingTargets() {
|
|
334
|
+
return Array.from(this.targets.values()).filter((t) => t.status === 'pending');
|
|
335
|
+
}
|
|
336
|
+
/**
|
|
337
|
+
* Get vouch history
|
|
338
|
+
*/
|
|
339
|
+
getVouchHistory() {
|
|
340
|
+
return [...this.conversations];
|
|
341
|
+
}
|
|
342
|
+
/**
|
|
343
|
+
* Export state for persistence
|
|
344
|
+
*/
|
|
345
|
+
exportState() {
|
|
346
|
+
return {
|
|
347
|
+
version: 1,
|
|
348
|
+
exportedAt: Date.now(),
|
|
349
|
+
vouchers: Array.from(this.vouchers.values()),
|
|
350
|
+
targets: Array.from(this.targets.values()),
|
|
351
|
+
conversations: [...this.conversations],
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
/**
|
|
355
|
+
* Import state from persistence
|
|
356
|
+
*/
|
|
357
|
+
importState(state) {
|
|
358
|
+
// Clear existing state
|
|
359
|
+
this.vouchers.clear();
|
|
360
|
+
this.targets.clear();
|
|
361
|
+
this.conversations = [];
|
|
362
|
+
// Import vouchers
|
|
363
|
+
for (const voucher of state.vouchers) {
|
|
364
|
+
this.vouchers.set(voucher.jid, { ...voucher });
|
|
365
|
+
}
|
|
366
|
+
// Import targets
|
|
367
|
+
for (const target of state.targets) {
|
|
368
|
+
this.targets.set(target.jid, { ...target });
|
|
369
|
+
}
|
|
370
|
+
// Import conversations
|
|
371
|
+
this.conversations = [...state.conversations];
|
|
372
|
+
}
|
|
373
|
+
}
|
package/dist/stateExport.d.ts
CHANGED
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
import type { WarmUpState } from './warmup.js';
|
|
14
14
|
import type { MessageTypeRegistryState } from './messageTypeRegistry.js';
|
|
15
15
|
import type { TopologyThrottlerState } from './topologyThrottler.js';
|
|
16
|
+
import type { ReputationVoucherState } from './reputationVoucher.js';
|
|
16
17
|
export interface DisconnectEvent {
|
|
17
18
|
type: 'disconnect' | 'forbidden' | 'loggedOut' | 'messageFailed' | 'reconnect' | 'reachoutTimelocked';
|
|
18
19
|
timestamp: number;
|
|
@@ -78,6 +79,8 @@ export interface AntibanSnapshot {
|
|
|
78
79
|
messageRegistry?: MessageTypeRegistryState;
|
|
79
80
|
/** Topology throttler state */
|
|
80
81
|
topologyThrottler?: TopologyThrottlerState;
|
|
82
|
+
/** Reputation voucher state */
|
|
83
|
+
reputationVoucher?: ReputationVoucherState;
|
|
81
84
|
/** Per-JID engagement scores */
|
|
82
85
|
engagementScores?: Record<string, number>;
|
|
83
86
|
}
|
|
@@ -119,6 +122,9 @@ export declare function exportAntibanState(modules: {
|
|
|
119
122
|
topologyThrottler?: {
|
|
120
123
|
exportState: () => TopologyThrottlerState;
|
|
121
124
|
};
|
|
125
|
+
reputationVoucher?: {
|
|
126
|
+
exportState: () => ReputationVoucherState;
|
|
127
|
+
};
|
|
122
128
|
engagementScores?: Map<string, number>;
|
|
123
129
|
instanceId?: string;
|
|
124
130
|
}): AntibanSnapshot;
|
|
@@ -150,5 +156,8 @@ export declare function importAntibanState(snapshot: AntibanSnapshot, modules: {
|
|
|
150
156
|
topologyThrottler?: {
|
|
151
157
|
importState: (state: TopologyThrottlerState) => void;
|
|
152
158
|
};
|
|
159
|
+
reputationVoucher?: {
|
|
160
|
+
importState: (state: ReputationVoucherState) => void;
|
|
161
|
+
};
|
|
153
162
|
engagementScores?: Map<string, number>;
|
|
154
163
|
}): void;
|
package/dist/stateExport.js
CHANGED
|
@@ -71,6 +71,10 @@ export function exportAntibanState(modules) {
|
|
|
71
71
|
if (modules.topologyThrottler) {
|
|
72
72
|
snapshot.topologyThrottler = modules.topologyThrottler.exportState();
|
|
73
73
|
}
|
|
74
|
+
// Export reputation voucher state
|
|
75
|
+
if (modules.reputationVoucher) {
|
|
76
|
+
snapshot.reputationVoucher = modules.reputationVoucher.exportState();
|
|
77
|
+
}
|
|
74
78
|
// Export engagement scores
|
|
75
79
|
if (modules.engagementScores) {
|
|
76
80
|
snapshot.engagementScores = Object.fromEntries(modules.engagementScores.entries());
|
|
@@ -148,6 +152,10 @@ export function importAntibanState(snapshot, modules) {
|
|
|
148
152
|
if (snapshot.topologyThrottler && modules.topologyThrottler) {
|
|
149
153
|
modules.topologyThrottler.importState(snapshot.topologyThrottler);
|
|
150
154
|
}
|
|
155
|
+
// Import reputation voucher state
|
|
156
|
+
if (snapshot.reputationVoucher && modules.reputationVoucher) {
|
|
157
|
+
modules.reputationVoucher.importState(snapshot.reputationVoucher);
|
|
158
|
+
}
|
|
151
159
|
// Import engagement scores
|
|
152
160
|
if (snapshot.engagementScores && modules.engagementScores) {
|
|
153
161
|
for (const [jid, score] of Object.entries(snapshot.engagementScores)) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "baileys-antiban",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.10.0",
|
|
4
4
|
"description": "Anti-ban middleware for Baileys WhatsApp bots. Rate limiting, warmup, health monitor, LID resolver, disconnect classifier. Free Whapi.Cloud alternative.",
|
|
5
5
|
"main": "dist/cjs/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|