baileys-antiban 1.2.1 → 1.3.1
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/CHANGELOG.md +24 -0
- package/README.md +76 -0
- package/dist/antiban.d.ts +26 -0
- package/dist/antiban.js +101 -2
- package/dist/contactGraph.d.ts +102 -0
- package/dist/contactGraph.js +236 -0
- package/dist/index.d.ts +4 -1
- package/dist/index.js +4 -0
- package/dist/presenceChoreographer.d.ts +96 -0
- package/dist/presenceChoreographer.js +187 -0
- package/dist/replyRatio.d.ts +91 -0
- package/dist/replyRatio.js +161 -0
- package/dist/wrapper.d.ts +5 -1
- package/dist/wrapper.js +144 -36
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,30 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [1.3.1] - 2026-04-16
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
- Refactor wrapper to use Baileys' `ev.process()` API — single batched event handler reduces listener leaks and cleans up the integration surface
|
|
12
|
+
- Graceful fallback to `ev.on()` for older Baileys versions
|
|
13
|
+
|
|
14
|
+
### Why
|
|
15
|
+
Scattered `ev.on()` registrations are a known leak vector. Consolidating into `process()` shrinks the attack surface for listener-lifecycle bugs and future-proofs for backend-agnostic support.
|
|
16
|
+
|
|
17
|
+
## [1.3.0] - 2026-04-16
|
|
18
|
+
|
|
19
|
+
### Added
|
|
20
|
+
- **ReplyRatioGuard** — tracks outbound:inbound ratio per contact, blocks sends to non-responsive contacts, suggests auto-replies to incoming messages
|
|
21
|
+
- **ContactGraphWarmer** — requires 1:1 handshake before bulk/group send, enforces group lurk period, daily stranger quota
|
|
22
|
+
- **PresenceChoreographer** — circadian rhythm enforcement, distraction pauses, realistic read-receipt timing
|
|
23
|
+
- All three features are **opt-in** via config and backward compatible
|
|
24
|
+
- New wrapSocket option: `autoRespondToIncoming` for hands-off reply-ratio maintenance
|
|
25
|
+
- New config fields: `replyRatio`, `contactGraph`, `presence` in `AntiBanConfig`
|
|
26
|
+
- New public methods: `onIncomingMessage()`, getters for new modules
|
|
27
|
+
- Enhanced `AntiBanStats` with optional `replyRatio`, `contactGraph`, `presence` stats
|
|
28
|
+
|
|
29
|
+
### Why
|
|
30
|
+
Based on 2025-2026 ban detection research: WhatsApp's ML models weight reply-ratio, contact-graph distance, and temporal patterns more heavily than raw volume. These modules address the three largest gaps in existing anti-ban libraries.
|
|
31
|
+
|
|
8
32
|
## [1.2.0] - 2026-04-13
|
|
9
33
|
|
|
10
34
|
### Added
|
package/README.md
CHANGED
|
@@ -6,6 +6,79 @@
|
|
|
6
6
|
|
|
7
7
|
Anti-ban middleware for [Baileys](https://github.com/WhiskeySockets/Baileys) — protect your WhatsApp number with human-like messaging patterns.
|
|
8
8
|
|
|
9
|
+
## v1.3 New Features
|
|
10
|
+
|
|
11
|
+
### ReplyRatioGuard
|
|
12
|
+
Tracks outbound:inbound message ratio per contact. Blocks sends to non-responsive contacts to avoid "spray-and-pray" ban patterns. Optionally suggests auto-replies to maintain healthy engagement.
|
|
13
|
+
|
|
14
|
+
```typescript
|
|
15
|
+
import { AntiBan } from 'baileys-antiban';
|
|
16
|
+
|
|
17
|
+
const antiban = new AntiBan({
|
|
18
|
+
replyRatio: {
|
|
19
|
+
enabled: true,
|
|
20
|
+
minRatio: 0.10, // Block sends to contacts with <10% reply rate
|
|
21
|
+
minMessagesBeforeEnforce: 5, // Enforce after 5 outbound messages
|
|
22
|
+
cooldownHoursOnViolation: 24, // 24h cooldown on ratio violation
|
|
23
|
+
},
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
// Handle incoming messages to track replies
|
|
27
|
+
sock.ev.on('messages.upsert', ({ messages }) => {
|
|
28
|
+
for (const msg of messages) {
|
|
29
|
+
if (!msg.key.fromMe) {
|
|
30
|
+
const suggestion = antiban.onIncomingMessage(msg.key.remoteJid);
|
|
31
|
+
if (suggestion.shouldReply) {
|
|
32
|
+
// Optionally auto-reply with suggestion.suggestedText
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### ContactGraphWarmer
|
|
40
|
+
Requires 1:1 handshake before bulk/group sends. Enforces group lurk period (don't spam immediately after joining). Caps daily new-contact messaging.
|
|
41
|
+
|
|
42
|
+
```typescript
|
|
43
|
+
const antiban = new AntiBan({
|
|
44
|
+
contactGraph: {
|
|
45
|
+
enabled: true,
|
|
46
|
+
requireHandshakeBeforeGroupSend: true,
|
|
47
|
+
handshakeMinDelayMs: 3600000, // 1h between handshake and first real message
|
|
48
|
+
groupLurkPeriodMs: 43200000, // 12h lurk before first group send
|
|
49
|
+
maxStrangerMessagesPerDay: 5, // Max 5 new contacts per day
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
// Mark handshake sent/complete manually
|
|
54
|
+
antiban.contactGraph.markHandshakeSent(jid);
|
|
55
|
+
antiban.contactGraph.markHandshakeComplete(jid);
|
|
56
|
+
|
|
57
|
+
// Or auto-register known contacts on incoming messages
|
|
58
|
+
// (enabled by default with autoRegisterOnIncoming: true)
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### PresenceChoreographer
|
|
62
|
+
Adds circadian rhythm to sending patterns (slower at night, faster during business hours). Injects realistic distraction pauses, offline gaps, and read-receipt timing variations.
|
|
63
|
+
|
|
64
|
+
```typescript
|
|
65
|
+
const antiban = new AntiBan({
|
|
66
|
+
presence: {
|
|
67
|
+
enabled: true,
|
|
68
|
+
enableCircadianRhythm: true,
|
|
69
|
+
timezone: 'Africa/Johannesburg',
|
|
70
|
+
activityCurve: 'office', // 'office' | 'social' | 'global'
|
|
71
|
+
distractionPauseProbability: 0.05, // 5% chance per send to pause 5-20min
|
|
72
|
+
offlineGapProbability: 0.03, // 3% chance to go offline 5-15min
|
|
73
|
+
},
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
// Delays are automatically adjusted based on local time-of-day
|
|
77
|
+
// No manual intervention needed
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
**Why these features?** 2025-2026 ban research showed WhatsApp's ML models heavily weight reply-ratio (<10% = high risk), contact-graph distance (strangers = high risk), and temporal patterns (robotic timing = high risk). These modules address the three largest gaps in existing anti-ban libraries.
|
|
81
|
+
|
|
9
82
|
## Why?
|
|
10
83
|
|
|
11
84
|
WhatsApp bans numbers that behave like bots. This library makes your Baileys bot behave like a human:
|
|
@@ -16,6 +89,9 @@ WhatsApp bans numbers that behave like bots. This library makes your Baileys bot
|
|
|
16
89
|
- **Timelock handling** for 463 reachout errors
|
|
17
90
|
- **Auto-pause** when risk gets too high
|
|
18
91
|
- **Drop-in wrapper** — one line to protect your existing bot
|
|
92
|
+
- **Reply ratio tracking** (v1.3) — blocks sends to non-responsive contacts
|
|
93
|
+
- **Contact graph enforcement** (v1.3) — requires handshakes before bulk/group sends
|
|
94
|
+
- **Circadian rhythm** (v1.3) — realistic time-of-day activity patterns
|
|
19
95
|
|
|
20
96
|
## Installation
|
|
21
97
|
|
package/dist/antiban.d.ts
CHANGED
|
@@ -17,11 +17,17 @@ import { type RateLimiterConfig, type RateLimiterStats } from './rateLimiter.js'
|
|
|
17
17
|
import { type WarmUpConfig, type WarmUpState, type WarmUpStatus } from './warmup.js';
|
|
18
18
|
import { type HealthMonitorConfig, type HealthStatus } from './health.js';
|
|
19
19
|
import { TimelockGuard, type TimelockGuardConfig } from './timelockGuard.js';
|
|
20
|
+
import { ReplyRatioGuard, type ReplyRatioConfig, type ReplyRatioStats } from './replyRatio.js';
|
|
21
|
+
import { ContactGraphWarmer, type ContactGraphConfig, type ContactGraphStats } from './contactGraph.js';
|
|
22
|
+
import { PresenceChoreographer, type PresenceChoreographerConfig, type PresenceChoreographerStats } from './presenceChoreographer.js';
|
|
20
23
|
export interface AntiBanConfig {
|
|
21
24
|
rateLimiter?: Partial<RateLimiterConfig>;
|
|
22
25
|
warmUp?: Partial<WarmUpConfig>;
|
|
23
26
|
health?: Partial<HealthMonitorConfig>;
|
|
24
27
|
timelock?: Partial<TimelockGuardConfig>;
|
|
28
|
+
replyRatio?: Partial<ReplyRatioConfig>;
|
|
29
|
+
contactGraph?: Partial<ContactGraphConfig>;
|
|
30
|
+
presence?: Partial<PresenceChoreographerConfig>;
|
|
25
31
|
/** Log warnings and blocks to console (default: true) */
|
|
26
32
|
logging?: boolean;
|
|
27
33
|
}
|
|
@@ -39,12 +45,18 @@ export interface AntiBanStats {
|
|
|
39
45
|
health: HealthStatus;
|
|
40
46
|
warmUp: WarmUpStatus;
|
|
41
47
|
rateLimiter: RateLimiterStats;
|
|
48
|
+
replyRatio?: ReplyRatioStats;
|
|
49
|
+
contactGraph?: ContactGraphStats;
|
|
50
|
+
presence?: PresenceChoreographerStats;
|
|
42
51
|
}
|
|
43
52
|
export declare class AntiBan {
|
|
44
53
|
private rateLimiter;
|
|
45
54
|
private warmUp;
|
|
46
55
|
private health;
|
|
47
56
|
private timelockGuard;
|
|
57
|
+
private replyRatioGuard;
|
|
58
|
+
private contactGraphWarmer;
|
|
59
|
+
private presenceChoreographer;
|
|
48
60
|
private logging;
|
|
49
61
|
private stats;
|
|
50
62
|
constructor(config?: AntiBanConfig, warmUpState?: WarmUpState);
|
|
@@ -70,12 +82,26 @@ export declare class AntiBan {
|
|
|
70
82
|
* Record a successful reconnection
|
|
71
83
|
*/
|
|
72
84
|
onReconnect(): void;
|
|
85
|
+
/**
|
|
86
|
+
* Handle incoming message — record in reply ratio + contact graph.
|
|
87
|
+
* Returns suggested reply if reply ratio suggests auto-reply.
|
|
88
|
+
*/
|
|
89
|
+
onIncomingMessage(jid: string, msgText?: string): {
|
|
90
|
+
shouldReply: boolean;
|
|
91
|
+
suggestedText?: string;
|
|
92
|
+
};
|
|
73
93
|
/**
|
|
74
94
|
* Get comprehensive stats
|
|
75
95
|
*/
|
|
76
96
|
getStats(): AntiBanStats;
|
|
77
97
|
/** Get the timelock guard for direct access */
|
|
78
98
|
get timelock(): TimelockGuard;
|
|
99
|
+
/** Get the reply ratio guard for direct access */
|
|
100
|
+
get replyRatio(): ReplyRatioGuard;
|
|
101
|
+
/** Get the contact graph warmer for direct access */
|
|
102
|
+
get contactGraph(): ContactGraphWarmer;
|
|
103
|
+
/** Get the presence choreographer for direct access */
|
|
104
|
+
get presence(): PresenceChoreographer;
|
|
79
105
|
/**
|
|
80
106
|
* Export warm-up state for persistence between restarts
|
|
81
107
|
*/
|
package/dist/antiban.js
CHANGED
|
@@ -17,11 +17,17 @@ import { RateLimiter } from './rateLimiter.js';
|
|
|
17
17
|
import { WarmUp } from './warmup.js';
|
|
18
18
|
import { HealthMonitor } from './health.js';
|
|
19
19
|
import { TimelockGuard } from './timelockGuard.js';
|
|
20
|
+
import { ReplyRatioGuard } from './replyRatio.js';
|
|
21
|
+
import { ContactGraphWarmer } from './contactGraph.js';
|
|
22
|
+
import { PresenceChoreographer } from './presenceChoreographer.js';
|
|
20
23
|
export class AntiBan {
|
|
21
24
|
rateLimiter;
|
|
22
25
|
warmUp;
|
|
23
26
|
health;
|
|
24
27
|
timelockGuard;
|
|
28
|
+
replyRatioGuard;
|
|
29
|
+
contactGraphWarmer;
|
|
30
|
+
presenceChoreographer;
|
|
25
31
|
logging;
|
|
26
32
|
stats = {
|
|
27
33
|
messagesAllowed: 0,
|
|
@@ -60,6 +66,9 @@ export class AntiBan {
|
|
|
60
66
|
config.timelock?.onTimelockLifted?.(state);
|
|
61
67
|
},
|
|
62
68
|
});
|
|
69
|
+
this.replyRatioGuard = new ReplyRatioGuard(config.replyRatio);
|
|
70
|
+
this.contactGraphWarmer = new ContactGraphWarmer(config.contactGraph);
|
|
71
|
+
this.presenceChoreographer = new PresenceChoreographer(config.presence);
|
|
63
72
|
}
|
|
64
73
|
/**
|
|
65
74
|
* Check if a message can be sent and get required delay.
|
|
@@ -109,8 +118,36 @@ export class AntiBan {
|
|
|
109
118
|
warmUpDay: warmUpStatus.day,
|
|
110
119
|
};
|
|
111
120
|
}
|
|
121
|
+
// Contact graph check
|
|
122
|
+
const contactGraphDecision = this.contactGraphWarmer.canMessage(recipient);
|
|
123
|
+
if (!contactGraphDecision.allowed) {
|
|
124
|
+
this.stats.messagesBlocked++;
|
|
125
|
+
if (this.logging) {
|
|
126
|
+
console.log(`[baileys-antiban] 📊 BLOCKED — contact graph: ${contactGraphDecision.reason}`);
|
|
127
|
+
}
|
|
128
|
+
return {
|
|
129
|
+
allowed: false,
|
|
130
|
+
delayMs: 0,
|
|
131
|
+
reason: `Contact graph: ${contactGraphDecision.reason}`,
|
|
132
|
+
health: healthStatus,
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
// Reply ratio check
|
|
136
|
+
const replyRatioDecision = this.replyRatioGuard.beforeSend(recipient);
|
|
137
|
+
if (!replyRatioDecision.allowed) {
|
|
138
|
+
this.stats.messagesBlocked++;
|
|
139
|
+
if (this.logging) {
|
|
140
|
+
console.log(`[baileys-antiban] 💬 BLOCKED — reply ratio: ${replyRatioDecision.reason}`);
|
|
141
|
+
}
|
|
142
|
+
return {
|
|
143
|
+
allowed: false,
|
|
144
|
+
delayMs: 0,
|
|
145
|
+
reason: `Reply ratio: ${replyRatioDecision.reason}`,
|
|
146
|
+
health: healthStatus,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
112
149
|
// Rate limiter delay
|
|
113
|
-
|
|
150
|
+
let delay = await this.rateLimiter.getDelay(recipient, content);
|
|
114
151
|
if (delay === -1) {
|
|
115
152
|
this.stats.messagesBlocked++;
|
|
116
153
|
if (this.logging) {
|
|
@@ -123,6 +160,29 @@ export class AntiBan {
|
|
|
123
160
|
health: healthStatus,
|
|
124
161
|
};
|
|
125
162
|
}
|
|
163
|
+
// Apply circadian rhythm multiplier to delay
|
|
164
|
+
const activityFactor = this.presenceChoreographer.getCurrentActivityFactor();
|
|
165
|
+
if (activityFactor < 1.0) {
|
|
166
|
+
// Lower activity = longer delays (cap at 5x)
|
|
167
|
+
const multiplier = Math.min(5, 1 / activityFactor);
|
|
168
|
+
delay = Math.floor(delay * multiplier);
|
|
169
|
+
}
|
|
170
|
+
// Roll for distraction pause
|
|
171
|
+
const distractionCheck = this.presenceChoreographer.shouldPauseForDistraction();
|
|
172
|
+
if (distractionCheck.pause) {
|
|
173
|
+
delay += distractionCheck.durationMs;
|
|
174
|
+
if (this.logging) {
|
|
175
|
+
console.log(`[baileys-antiban] ⏸️ Distraction pause: +${Math.floor(distractionCheck.durationMs / 60000)}min`);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
// Roll for offline gap
|
|
179
|
+
const offlineCheck = this.presenceChoreographer.shouldTakeOfflineGap();
|
|
180
|
+
if (offlineCheck.offline) {
|
|
181
|
+
delay += offlineCheck.durationMs;
|
|
182
|
+
if (this.logging) {
|
|
183
|
+
console.log(`[baileys-antiban] 📴 Offline gap: +${Math.floor(offlineCheck.durationMs / 60000)}min`);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
126
186
|
this.stats.totalDelayMs += delay;
|
|
127
187
|
return {
|
|
128
188
|
allowed: true,
|
|
@@ -137,6 +197,7 @@ export class AntiBan {
|
|
|
137
197
|
afterSend(recipient, content) {
|
|
138
198
|
this.rateLimiter.record(recipient, content);
|
|
139
199
|
this.warmUp.record();
|
|
200
|
+
this.replyRatioGuard.recordSent(recipient);
|
|
140
201
|
this.stats.messagesAllowed++;
|
|
141
202
|
}
|
|
142
203
|
/**
|
|
@@ -157,21 +218,53 @@ export class AntiBan {
|
|
|
157
218
|
onReconnect() {
|
|
158
219
|
this.health.recordReconnect();
|
|
159
220
|
}
|
|
221
|
+
/**
|
|
222
|
+
* Handle incoming message — record in reply ratio + contact graph.
|
|
223
|
+
* Returns suggested reply if reply ratio suggests auto-reply.
|
|
224
|
+
*/
|
|
225
|
+
onIncomingMessage(jid, msgText) {
|
|
226
|
+
this.replyRatioGuard.recordReceived(jid);
|
|
227
|
+
this.contactGraphWarmer.onIncomingMessage(jid);
|
|
228
|
+
return this.replyRatioGuard.suggestReply(jid, msgText);
|
|
229
|
+
}
|
|
160
230
|
/**
|
|
161
231
|
* Get comprehensive stats
|
|
162
232
|
*/
|
|
163
233
|
getStats() {
|
|
164
|
-
|
|
234
|
+
const stats = {
|
|
165
235
|
...this.stats,
|
|
166
236
|
health: this.health.getStatus(),
|
|
167
237
|
warmUp: this.warmUp.getStatus(),
|
|
168
238
|
rateLimiter: this.rateLimiter.getStats(),
|
|
169
239
|
};
|
|
240
|
+
// Only include new stats if enabled
|
|
241
|
+
if (this.replyRatioGuard['config']?.enabled) {
|
|
242
|
+
stats.replyRatio = this.replyRatioGuard.getStats();
|
|
243
|
+
}
|
|
244
|
+
if (this.contactGraphWarmer['config']?.enabled) {
|
|
245
|
+
stats.contactGraph = this.contactGraphWarmer.getStats();
|
|
246
|
+
}
|
|
247
|
+
if (this.presenceChoreographer['config']?.enabled) {
|
|
248
|
+
stats.presence = this.presenceChoreographer.getStats();
|
|
249
|
+
}
|
|
250
|
+
return stats;
|
|
170
251
|
}
|
|
171
252
|
/** Get the timelock guard for direct access */
|
|
172
253
|
get timelock() {
|
|
173
254
|
return this.timelockGuard;
|
|
174
255
|
}
|
|
256
|
+
/** Get the reply ratio guard for direct access */
|
|
257
|
+
get replyRatio() {
|
|
258
|
+
return this.replyRatioGuard;
|
|
259
|
+
}
|
|
260
|
+
/** Get the contact graph warmer for direct access */
|
|
261
|
+
get contactGraph() {
|
|
262
|
+
return this.contactGraphWarmer;
|
|
263
|
+
}
|
|
264
|
+
/** Get the presence choreographer for direct access */
|
|
265
|
+
get presence() {
|
|
266
|
+
return this.presenceChoreographer;
|
|
267
|
+
}
|
|
175
268
|
/**
|
|
176
269
|
* Export warm-up state for persistence between restarts
|
|
177
270
|
*/
|
|
@@ -203,6 +296,9 @@ export class AntiBan {
|
|
|
203
296
|
this.timelockGuard.reset();
|
|
204
297
|
this.health.reset();
|
|
205
298
|
this.warmUp.reset();
|
|
299
|
+
this.replyRatioGuard.reset();
|
|
300
|
+
this.contactGraphWarmer.reset();
|
|
301
|
+
this.presenceChoreographer.reset();
|
|
206
302
|
this.stats = { messagesAllowed: 0, messagesBlocked: 0, totalDelayMs: 0 };
|
|
207
303
|
if (this.logging) {
|
|
208
304
|
console.log('[baileys-antiban] 🔄 Reset — starting fresh warm-up');
|
|
@@ -214,6 +310,9 @@ export class AntiBan {
|
|
|
214
310
|
*/
|
|
215
311
|
destroy() {
|
|
216
312
|
this.timelockGuard.reset(); // Clears the resumeTimer
|
|
313
|
+
this.replyRatioGuard.reset();
|
|
314
|
+
this.contactGraphWarmer.reset();
|
|
315
|
+
this.presenceChoreographer.reset();
|
|
217
316
|
if (this.logging) {
|
|
218
317
|
console.log('[baileys-antiban] 🧹 Destroyed — all timers cleared');
|
|
219
318
|
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Contact Graph Warmer — Requires 1:1 handshake before group/bulk sends
|
|
3
|
+
*
|
|
4
|
+
* WhatsApp's ML models weight "social graph distance" heavily. Accounts that
|
|
5
|
+
* message strangers (contacts who never replied) have higher ban risk.
|
|
6
|
+
*
|
|
7
|
+
* This module:
|
|
8
|
+
* - Tracks contact state: stranger → handshake_sent → handshake_complete → known
|
|
9
|
+
* - Blocks sends to strangers unless handshake completed
|
|
10
|
+
* - Enforces group lurk period (don't send immediately after joining)
|
|
11
|
+
* - Caps daily new-contact messaging (prevent spray-and-pray patterns)
|
|
12
|
+
* - Auto-registers inbound senders as "known contacts"
|
|
13
|
+
*
|
|
14
|
+
* Research: 2025 ban waves correlated with accounts joining groups + spamming
|
|
15
|
+
* instantly. 12-24h lurk period significantly reduced bans.
|
|
16
|
+
*/
|
|
17
|
+
export interface ContactGraphConfig {
|
|
18
|
+
/** Enable contact graph enforcement (default: false — opt-in) */
|
|
19
|
+
enabled?: boolean;
|
|
20
|
+
/** Require handshake completion before group/bulk sends (default: true) */
|
|
21
|
+
requireHandshakeBeforeGroupSend?: boolean;
|
|
22
|
+
/** Min wait time (ms) between handshake and first real message (default: 3600000 = 1h) */
|
|
23
|
+
handshakeMinDelayMs?: number;
|
|
24
|
+
/** Group lurk period (ms) before first send (default: 43200000 = 12h) */
|
|
25
|
+
groupLurkPeriodMs?: number;
|
|
26
|
+
/** Max new-contact messages per day (default: 5) */
|
|
27
|
+
maxStrangerMessagesPerDay?: number;
|
|
28
|
+
/** Auto-register inbound senders as known contacts (default: true) */
|
|
29
|
+
autoRegisterOnIncoming?: boolean;
|
|
30
|
+
}
|
|
31
|
+
export type ContactState = 'stranger' | 'handshake_sent' | 'handshake_complete' | 'known';
|
|
32
|
+
export interface ContactGraphStats {
|
|
33
|
+
knownContacts: number;
|
|
34
|
+
pendingHandshakes: number;
|
|
35
|
+
strangersToday: number;
|
|
36
|
+
groupsJoined: Array<{
|
|
37
|
+
groupJid: string;
|
|
38
|
+
joinedAt: number;
|
|
39
|
+
firstSendUnlocksAt: number;
|
|
40
|
+
}>;
|
|
41
|
+
}
|
|
42
|
+
export declare class ContactGraphWarmer {
|
|
43
|
+
private config;
|
|
44
|
+
private contacts;
|
|
45
|
+
private groups;
|
|
46
|
+
private strangerMessagesToday;
|
|
47
|
+
private lastStrangerResetDay;
|
|
48
|
+
constructor(config?: ContactGraphConfig);
|
|
49
|
+
/**
|
|
50
|
+
* Check if message can be sent to this contact/group.
|
|
51
|
+
* Returns { allowed: false, needsHandshake: true } if handshake required.
|
|
52
|
+
*/
|
|
53
|
+
canMessage(jid: string): {
|
|
54
|
+
allowed: boolean;
|
|
55
|
+
reason?: string;
|
|
56
|
+
needsHandshake?: boolean;
|
|
57
|
+
};
|
|
58
|
+
/**
|
|
59
|
+
* Mark handshake as sent to this contact.
|
|
60
|
+
*/
|
|
61
|
+
markHandshakeSent(jid: string): void;
|
|
62
|
+
/**
|
|
63
|
+
* Mark handshake as complete with this contact.
|
|
64
|
+
*/
|
|
65
|
+
markHandshakeComplete(jid: string): void;
|
|
66
|
+
/**
|
|
67
|
+
* Register a contact as known (skip handshake requirement).
|
|
68
|
+
*/
|
|
69
|
+
registerKnownContact(jid: string): void;
|
|
70
|
+
/**
|
|
71
|
+
* Register a group join event.
|
|
72
|
+
*/
|
|
73
|
+
registerGroupJoin(groupJid: string): void;
|
|
74
|
+
/**
|
|
75
|
+
* Get contact state.
|
|
76
|
+
*/
|
|
77
|
+
getContactState(jid: string): ContactState;
|
|
78
|
+
/**
|
|
79
|
+
* Handle incoming message — auto-register if enabled.
|
|
80
|
+
*/
|
|
81
|
+
onIncomingMessage(jid: string): void;
|
|
82
|
+
/**
|
|
83
|
+
* Get statistics.
|
|
84
|
+
*/
|
|
85
|
+
getStats(): ContactGraphStats;
|
|
86
|
+
/**
|
|
87
|
+
* Reset all state.
|
|
88
|
+
*/
|
|
89
|
+
reset(): void;
|
|
90
|
+
/**
|
|
91
|
+
* Export state for persistence.
|
|
92
|
+
*/
|
|
93
|
+
exportState(): object;
|
|
94
|
+
/**
|
|
95
|
+
* Restore state from persistence.
|
|
96
|
+
*/
|
|
97
|
+
restoreState(state: any): void;
|
|
98
|
+
private isGroup;
|
|
99
|
+
private getCurrentDay;
|
|
100
|
+
private checkGroupMessage;
|
|
101
|
+
private checkIndividualMessage;
|
|
102
|
+
}
|