innovators-bot2 1.1.2 → 1.1.4
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/README.md +15 -0
- package/antiban.d.ts +46 -0
- package/antiban.js +236 -0
- package/config.json +1 -1
- package/example.js +3 -0
- package/index.js +36 -10
- package/package.json +2 -2
- package/utils.js +18 -0
package/README.md
CHANGED
|
@@ -12,6 +12,10 @@ A powerful WhatsApp client library that provides seamless integration between Ba
|
|
|
12
12
|
- 💾 Message history and chat management
|
|
13
13
|
- 🔄 Auto-reconnect functionality
|
|
14
14
|
- 📝 Read receipts
|
|
15
|
+
- 🛡️ Anti-abuse protection with rate limiting
|
|
16
|
+
- 🔁 Automatic retry with exponential backoff
|
|
17
|
+
- ⚡ Idempotency to prevent duplicate sends
|
|
18
|
+
- 🚦 Circuit breaker to prevent spam
|
|
15
19
|
|
|
16
20
|
## Installation
|
|
17
21
|
|
|
@@ -185,6 +189,17 @@ const messages = await client.loadMessages(chatId, 50)
|
|
|
185
189
|
const message = await client.loadMessage(chatId, messageId)
|
|
186
190
|
```
|
|
187
191
|
|
|
192
|
+
### 5. Anti-Abuse Protection
|
|
193
|
+
|
|
194
|
+
The library now includes built-in anti-abuse protection to help prevent your bot from being banned:
|
|
195
|
+
|
|
196
|
+
- **Rate Limiting**: Automatic rate limiting to prevent sending messages too quickly
|
|
197
|
+
- **Idempotency**: Prevents duplicate messages from being sent
|
|
198
|
+
- **Retry Logic**: Automatic retry with exponential backoff for failed sends
|
|
199
|
+
- **Circuit Breaker**: Temporarily stops sending when errors are detected
|
|
200
|
+
|
|
201
|
+
All message sending methods automatically use these protections.
|
|
202
|
+
|
|
188
203
|
## More Examples and Information
|
|
189
204
|
|
|
190
205
|
For a complete working example with message handling, group management, and error handling, check out our [`example.js`](https://github.com/innovatorssoft/innovators-bot2/blob/main/example.js) file. This example includes:
|
package/antiban.d.ts
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
type SendFn<R = any> = (jid: string, content: any, opts?: any) => Promise<R>;
|
|
2
|
+
interface GuardOpts {
|
|
3
|
+
maxConcurrent?: number;
|
|
4
|
+
minGlobalIntervalMs?: number;
|
|
5
|
+
perJidMinIntervalMs?: number;
|
|
6
|
+
burstCapacity?: number;
|
|
7
|
+
refillPerSecond?: number;
|
|
8
|
+
maxAttempts?: number;
|
|
9
|
+
baseBackoffMs?: number;
|
|
10
|
+
maxBackoffMs?: number;
|
|
11
|
+
cbWindowMs?: number;
|
|
12
|
+
cbErrorThreshold?: number;
|
|
13
|
+
cbOpenMs?: number;
|
|
14
|
+
idempotencyTtlMs?: number;
|
|
15
|
+
classifyStatus?: (e: any) => number | null;
|
|
16
|
+
onLog?: (msg: string, meta?: Record<string, any>) => void;
|
|
17
|
+
}
|
|
18
|
+
export declare class WhatsAppSafeSender {
|
|
19
|
+
private queue;
|
|
20
|
+
private running;
|
|
21
|
+
private lastGlobalSend;
|
|
22
|
+
private lastPerJid;
|
|
23
|
+
private tokens;
|
|
24
|
+
private burstCapacity;
|
|
25
|
+
private refillPerSecond;
|
|
26
|
+
private lastRefill;
|
|
27
|
+
private attempts;
|
|
28
|
+
private idempotencyTtl;
|
|
29
|
+
private cbEvents;
|
|
30
|
+
private cbOpenUntil;
|
|
31
|
+
private cfg;
|
|
32
|
+
private classifyStatus;
|
|
33
|
+
private onLog;
|
|
34
|
+
constructor(opts?: GuardOpts);
|
|
35
|
+
send<R = any>(sendFn: SendFn<R>, jid: string, content: any, opts?: any, idempotencyKey?: string): Promise<R>;
|
|
36
|
+
private enqueue;
|
|
37
|
+
private coreSend;
|
|
38
|
+
private waitForBudget;
|
|
39
|
+
private refillTokens;
|
|
40
|
+
private enforceIntervals;
|
|
41
|
+
private recordOk;
|
|
42
|
+
private recordError;
|
|
43
|
+
private pruneCb;
|
|
44
|
+
private tripCircuit;
|
|
45
|
+
}
|
|
46
|
+
export {};
|
package/antiban.js
ADDED
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Anti-abuse, policy-friendly WhatsApp sender helper (TypeScript)
|
|
3
|
+
// Goal: be conservative, respect limits, back off on throttling, avoid duplicate sends.
|
|
4
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
5
|
+
exports.WhatsAppSafeSender = void 0;
|
|
6
|
+
class WhatsAppSafeSender {
|
|
7
|
+
constructor(opts = {}) {
|
|
8
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p;
|
|
9
|
+
this.queue = Promise.resolve();
|
|
10
|
+
this.running = 0;
|
|
11
|
+
this.lastGlobalSend = 0;
|
|
12
|
+
this.lastPerJid = new Map();
|
|
13
|
+
this.lastRefill = Date.now();
|
|
14
|
+
this.attempts = new Map(); // idempotency keys
|
|
15
|
+
// circuit breaker
|
|
16
|
+
this.cbEvents = [];
|
|
17
|
+
this.cbOpenUntil = 0;
|
|
18
|
+
this.cfg = {
|
|
19
|
+
maxConcurrent: (_a = opts.maxConcurrent) !== null && _a !== void 0 ? _a : 1,
|
|
20
|
+
minGlobalIntervalMs: (_b = opts.minGlobalIntervalMs) !== null && _b !== void 0 ? _b : 1200, // be conservative
|
|
21
|
+
perJidMinIntervalMs: (_c = opts.perJidMinIntervalMs) !== null && _c !== void 0 ? _c : 5000, // do not spam a single chat
|
|
22
|
+
burstCapacity: (_d = opts.burstCapacity) !== null && _d !== void 0 ? _d : 3,
|
|
23
|
+
refillPerSecond: (_e = opts.refillPerSecond) !== null && _e !== void 0 ? _e : 0.5, // 1 token every 2s
|
|
24
|
+
maxAttempts: (_f = opts.maxAttempts) !== null && _f !== void 0 ? _f : 4,
|
|
25
|
+
baseBackoffMs: (_g = opts.baseBackoffMs) !== null && _g !== void 0 ? _g : 1500,
|
|
26
|
+
maxBackoffMs: (_h = opts.maxBackoffMs) !== null && _h !== void 0 ? _h : 60000,
|
|
27
|
+
cbWindowMs: (_j = opts.cbWindowMs) !== null && _j !== void 0 ? _j : 60000,
|
|
28
|
+
cbErrorThreshold: (_k = opts.cbErrorThreshold) !== null && _k !== void 0 ? _k : 8,
|
|
29
|
+
cbOpenMs: (_l = opts.cbOpenMs) !== null && _l !== void 0 ? _l : 120000,
|
|
30
|
+
idempotencyTtlMs: (_m = opts.idempotencyTtlMs) !== null && _m !== void 0 ? _m : 5 * 60000,
|
|
31
|
+
};
|
|
32
|
+
this.tokens = this.cfg.burstCapacity;
|
|
33
|
+
this.burstCapacity = this.cfg.burstCapacity;
|
|
34
|
+
this.refillPerSecond = this.cfg.refillPerSecond;
|
|
35
|
+
this.idempotencyTtl = this.cfg.idempotencyTtlMs;
|
|
36
|
+
this.classifyStatus = (_o = opts.classifyStatus) !== null && _o !== void 0 ? _o : defaultStatusClassifier;
|
|
37
|
+
this.onLog = (_p = opts.onLog) !== null && _p !== void 0 ? _p : (() => { });
|
|
38
|
+
}
|
|
39
|
+
// Public entry: safe send
|
|
40
|
+
async send(sendFn, jid, content, opts, idempotencyKey) {
|
|
41
|
+
// Serialize with a tiny internal queue to honor maxConcurrent cleanly
|
|
42
|
+
return this.enqueue(() => this.coreSend(sendFn, jid, content, opts, idempotencyKey));
|
|
43
|
+
}
|
|
44
|
+
// Simple concurrency gate + FIFO
|
|
45
|
+
enqueue(task) {
|
|
46
|
+
const run = async () => {
|
|
47
|
+
// Wait for a slot
|
|
48
|
+
while (this.running >= this.cfg.maxConcurrent) {
|
|
49
|
+
await sleep(25);
|
|
50
|
+
}
|
|
51
|
+
this.running++;
|
|
52
|
+
try {
|
|
53
|
+
return await task();
|
|
54
|
+
}
|
|
55
|
+
finally {
|
|
56
|
+
this.running--;
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
this.queue = this.queue.then(run, run);
|
|
60
|
+
return this.queue;
|
|
61
|
+
}
|
|
62
|
+
async coreSend(sendFn, jid, content, opts, idemKey) {
|
|
63
|
+
var _a, _b;
|
|
64
|
+
// Circuit breaker check
|
|
65
|
+
const now = Date.now();
|
|
66
|
+
if (this.cbOpenUntil > now) {
|
|
67
|
+
const wait = this.cbOpenUntil - now;
|
|
68
|
+
this.onLog("circuit_open_wait", { wait });
|
|
69
|
+
await sleep(wait);
|
|
70
|
+
}
|
|
71
|
+
// Idempotency: drop duplicates within TTL
|
|
72
|
+
if (idemKey) {
|
|
73
|
+
const seenAt = this.attempts.get(idemKey);
|
|
74
|
+
if (seenAt && now - seenAt < this.idempotencyTtl) {
|
|
75
|
+
this.onLog("idempotent_drop", { idemKey });
|
|
76
|
+
// @ts-ignore
|
|
77
|
+
return undefined;
|
|
78
|
+
}
|
|
79
|
+
this.attempts.set(idemKey, now);
|
|
80
|
+
(_b = (_a = setTimeout(() => this.attempts.delete(idemKey), this.idempotencyTtl)).unref) === null || _b === void 0 ? void 0 : _b.call(_a);
|
|
81
|
+
}
|
|
82
|
+
// Respect rate limits
|
|
83
|
+
await this.waitForBudget();
|
|
84
|
+
await this.enforceIntervals(jid);
|
|
85
|
+
let attempt = 0;
|
|
86
|
+
let backoff = this.cfg.baseBackoffMs;
|
|
87
|
+
// Retry loop
|
|
88
|
+
while (true) {
|
|
89
|
+
attempt++;
|
|
90
|
+
try {
|
|
91
|
+
const res = await sendFn(jid, content, opts);
|
|
92
|
+
// success: record global/per-jid time and token consumption
|
|
93
|
+
this.lastGlobalSend = Date.now();
|
|
94
|
+
this.lastPerJid.set(jid, this.lastGlobalSend);
|
|
95
|
+
this.recordOk();
|
|
96
|
+
return res;
|
|
97
|
+
}
|
|
98
|
+
catch (err) {
|
|
99
|
+
const status = this.classifyStatus(err);
|
|
100
|
+
// Check Retry-After if present
|
|
101
|
+
const retryAfterMs = parseRetryAfter(err);
|
|
102
|
+
const isThrottle = status === 429 || retryAfterMs > 0;
|
|
103
|
+
const isAuthOrForbidden = status === 401 || status === 403;
|
|
104
|
+
const isClientBad = status && status >= 400 && status < 500 && !isThrottle && !isAuthOrForbidden;
|
|
105
|
+
const isServer = status && status >= 500;
|
|
106
|
+
this.recordError();
|
|
107
|
+
// Client errors (other 4xx): do not retry blindly
|
|
108
|
+
if (isClientBad) {
|
|
109
|
+
this.onLog("client_error_no_retry", { status, attempt, message: safeMsg(err) });
|
|
110
|
+
throw err;
|
|
111
|
+
}
|
|
112
|
+
// Authentication/Forbidden: open circuit for a bit to avoid hammering
|
|
113
|
+
if (isAuthOrForbidden) {
|
|
114
|
+
const waitMs = Math.min(Math.max(retryAfterMs, backoff), this.cfg.maxBackoffMs);
|
|
115
|
+
this.tripCircuit(waitMs * 2);
|
|
116
|
+
this.onLog("auth_forbidden_backoff", { status, waitMs, attempt });
|
|
117
|
+
await sleep(waitMs);
|
|
118
|
+
}
|
|
119
|
+
else if (isThrottle || isServer) {
|
|
120
|
+
// Throttle or 5xx: exponential backoff with jitter
|
|
121
|
+
const waitMs = Math.min(Math.max(retryAfterMs, jitter(backoff)), this.cfg.maxBackoffMs);
|
|
122
|
+
this.onLog("retry_backoff", { status, waitMs, attempt });
|
|
123
|
+
await sleep(waitMs);
|
|
124
|
+
backoff = Math.min(backoff * 2, this.cfg.maxBackoffMs);
|
|
125
|
+
}
|
|
126
|
+
else {
|
|
127
|
+
// Unknown: one conservative wait, then rethrow
|
|
128
|
+
const waitMs = Math.min(jitter(backoff), this.cfg.maxBackoffMs);
|
|
129
|
+
this.onLog("unknown_backoff_once", { status, waitMs, attempt });
|
|
130
|
+
await sleep(waitMs);
|
|
131
|
+
}
|
|
132
|
+
if (attempt >= this.cfg.maxAttempts) {
|
|
133
|
+
this.onLog("max_attempts_reached", { attempt, status, message: safeMsg(err) });
|
|
134
|
+
throw err;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
// Token bucket for bursts
|
|
140
|
+
async waitForBudget() {
|
|
141
|
+
while (true) {
|
|
142
|
+
this.refillTokens();
|
|
143
|
+
if (this.tokens >= 1) {
|
|
144
|
+
this.tokens -= 1;
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
await sleep(100);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
refillTokens() {
|
|
151
|
+
const now = Date.now();
|
|
152
|
+
const delta = (now - this.lastRefill) / 1000;
|
|
153
|
+
if (delta > 0) {
|
|
154
|
+
this.tokens = Math.min(this.burstCapacity, this.tokens + delta * this.refillPerSecond);
|
|
155
|
+
this.lastRefill = now;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
async enforceIntervals(jid) {
|
|
159
|
+
var _a;
|
|
160
|
+
const now = Date.now();
|
|
161
|
+
// global interval
|
|
162
|
+
const nextGlobal = this.lastGlobalSend + this.cfg.minGlobalIntervalMs;
|
|
163
|
+
if (now < nextGlobal) {
|
|
164
|
+
await sleep(nextGlobal - now);
|
|
165
|
+
}
|
|
166
|
+
// per-jid interval
|
|
167
|
+
const lastJid = (_a = this.lastPerJid.get(jid)) !== null && _a !== void 0 ? _a : 0;
|
|
168
|
+
const nextJid = lastJid + this.cfg.perJidMinIntervalMs;
|
|
169
|
+
const n2 = Date.now();
|
|
170
|
+
if (n2 < nextJid) {
|
|
171
|
+
await sleep(nextJid - n2);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
recordOk() {
|
|
175
|
+
this.pruneCb();
|
|
176
|
+
this.cbEvents.push(1); // mark OK as 1 to maintain window length
|
|
177
|
+
}
|
|
178
|
+
recordError() {
|
|
179
|
+
this.pruneCb();
|
|
180
|
+
this.cbEvents.push(0); // errors as 0
|
|
181
|
+
// If too many failures, open circuit
|
|
182
|
+
const errors = this.cbEvents.filter(x => x === 0).length;
|
|
183
|
+
if (errors >= this.cfg.cbErrorThreshold) {
|
|
184
|
+
this.tripCircuit(this.cfg.cbOpenMs);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
pruneCb() {
|
|
188
|
+
const cutoff = Date.now() - this.cfg.cbWindowMs;
|
|
189
|
+
// store timestamps implicitly via array length; we’ll just keep size bounded
|
|
190
|
+
if (this.cbEvents.length > 1000)
|
|
191
|
+
this.cbEvents = this.cbEvents.slice(-500);
|
|
192
|
+
// no exact timestamps; threshold is coarse but fine for a rolling minute window
|
|
193
|
+
}
|
|
194
|
+
tripCircuit(ms) {
|
|
195
|
+
this.cbOpenUntil = Date.now() + ms;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
exports.WhatsAppSafeSender = WhatsAppSafeSender;
|
|
199
|
+
// Helpers
|
|
200
|
+
function sleep(ms) {
|
|
201
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
202
|
+
}
|
|
203
|
+
function jitter(ms) {
|
|
204
|
+
const r = 0.5 + Math.random(); // 0.5x .. 1.5x
|
|
205
|
+
return Math.floor(ms * r);
|
|
206
|
+
}
|
|
207
|
+
function safeMsg(e) {
|
|
208
|
+
var _a;
|
|
209
|
+
const m = (_a = e === null || e === void 0 ? void 0 : e.message) !== null && _a !== void 0 ? _a : String(e);
|
|
210
|
+
return m.substring(0, 500);
|
|
211
|
+
}
|
|
212
|
+
function parseRetryAfter(e) {
|
|
213
|
+
var _a, _b, _c, _d;
|
|
214
|
+
try {
|
|
215
|
+
const ra = (_c = (_b = (_a = e === null || e === void 0 ? void 0 : e.response) === null || _a === void 0 ? void 0 : _a.headers) === null || _b === void 0 ? void 0 : _b["retry-after"]) !== null && _c !== void 0 ? _c : (_d = e === null || e === void 0 ? void 0 : e.headers) === null || _d === void 0 ? void 0 : _d["retry-after"];
|
|
216
|
+
if (!ra)
|
|
217
|
+
return 0;
|
|
218
|
+
const asNum = Number(ra);
|
|
219
|
+
if (!Number.isNaN(asNum))
|
|
220
|
+
return asNum * 1000;
|
|
221
|
+
const asDate = Date.parse(ra);
|
|
222
|
+
if (!Number.isNaN(asDate))
|
|
223
|
+
return Math.max(0, asDate - Date.now());
|
|
224
|
+
return 0;
|
|
225
|
+
}
|
|
226
|
+
catch (_e) {
|
|
227
|
+
return 0;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
function defaultStatusClassifier(e) {
|
|
231
|
+
var _a, _b, _c, _d, _e;
|
|
232
|
+
// Try to normalize common shapes
|
|
233
|
+
const s = (_d = (_b = (_a = e === null || e === void 0 ? void 0 : e.status) !== null && _a !== void 0 ? _a : e === null || e === void 0 ? void 0 : e.code) !== null && _b !== void 0 ? _b : (_c = e === null || e === void 0 ? void 0 : e.response) === null || _c === void 0 ? void 0 : _c.status) !== null && _d !== void 0 ? _d : (_e = e === null || e === void 0 ? void 0 : e.output) === null || _e === void 0 ? void 0 : _e.statusCode;
|
|
234
|
+
const n = Number(s);
|
|
235
|
+
return Number.isFinite(n) ? n : null;
|
|
236
|
+
}
|
package/config.json
CHANGED
package/example.js
CHANGED
|
@@ -452,3 +452,6 @@ client.on('message', async msg => {
|
|
|
452
452
|
client.on('error', error => {
|
|
453
453
|
console.error('Client Error:', error)
|
|
454
454
|
})
|
|
455
|
+
|
|
456
|
+
// The client now automatically uses anti-abuse protection for all message sending methods
|
|
457
|
+
// No additional configuration is needed - all sendMessage, sendMedia, etc. calls are protected
|
package/index.js
CHANGED
|
@@ -12,6 +12,8 @@ const path = require('path');
|
|
|
12
12
|
const mime = require('mime');
|
|
13
13
|
const figlet = require('figlet');
|
|
14
14
|
const readline = require('readline');
|
|
15
|
+
const { WhatsAppSafeSender } = require('./antiban');
|
|
16
|
+
const { buildIdemKey } = require('./utils');
|
|
15
17
|
|
|
16
18
|
let rl;
|
|
17
19
|
const question = (text) => {
|
|
@@ -59,6 +61,15 @@ class WhatsAppClient extends EventEmitter {
|
|
|
59
61
|
this._connectionState = 'disconnected' // Track connection state
|
|
60
62
|
this.authmethod = config.authmethod || 'qr'; // 'qr' or 'pairing'
|
|
61
63
|
this._reconnectDelay = 5000; // 5 seconds
|
|
64
|
+
|
|
65
|
+
// Initialize the WhatsAppSafeSender with conservative settings
|
|
66
|
+
this.guard = new WhatsAppSafeSender({
|
|
67
|
+
minGlobalIntervalMs: 1500,
|
|
68
|
+
perJidMinIntervalMs: 6000,
|
|
69
|
+
burstCapacity: 2,
|
|
70
|
+
refillPerSecond: 0.5,
|
|
71
|
+
onLog: (msg, meta) => console.log(`[guard] ${msg}`, meta),
|
|
72
|
+
});
|
|
62
73
|
}
|
|
63
74
|
|
|
64
75
|
async connect() {
|
|
@@ -333,7 +344,9 @@ class WhatsAppClient extends EventEmitter {
|
|
|
333
344
|
}
|
|
334
345
|
|
|
335
346
|
try {
|
|
336
|
-
|
|
347
|
+
// Use the safe sender to send the message with idempotency
|
|
348
|
+
const idemKey = buildIdemKey(chatId, messageContent);
|
|
349
|
+
return await this.guard.send(this.sock.sendMessage.bind(this.sock), chatId, messageContent, options, idemKey);
|
|
337
350
|
} catch (error) {
|
|
338
351
|
console.error('Error sending message:', error);
|
|
339
352
|
throw error;
|
|
@@ -399,7 +412,9 @@ class WhatsAppClient extends EventEmitter {
|
|
|
399
412
|
throw new Error('Unsupported file type: ' + fileExtension);
|
|
400
413
|
}
|
|
401
414
|
|
|
402
|
-
|
|
415
|
+
// Use the safe sender to send the media with idempotency
|
|
416
|
+
const idemKey = buildIdemKey(chatId, mediaMessage);
|
|
417
|
+
return await this.guard.send(this.sock.sendMessage.bind(this.sock), chatId, mediaMessage, undefined, idemKey);
|
|
403
418
|
} catch (error) {
|
|
404
419
|
console.error('Error sending media:', error);
|
|
405
420
|
throw error;
|
|
@@ -429,12 +444,16 @@ class WhatsAppClient extends EventEmitter {
|
|
|
429
444
|
const fileName = path.basename(filePath);
|
|
430
445
|
const mimeType = mime.getType(filePath);
|
|
431
446
|
|
|
432
|
-
|
|
447
|
+
const documentMessage = {
|
|
433
448
|
document: fileBuffer,
|
|
434
449
|
caption: caption,
|
|
435
450
|
mimetype: mimeType,
|
|
436
451
|
fileName: fileName,
|
|
437
|
-
}
|
|
452
|
+
};
|
|
453
|
+
|
|
454
|
+
// Use the safe sender to send the document with idempotency
|
|
455
|
+
const idemKey = buildIdemKey(chatId, documentMessage);
|
|
456
|
+
return await this.guard.send(this.sock.sendMessage.bind(this.sock), chatId, documentMessage, undefined, idemKey);
|
|
438
457
|
} catch (error) {
|
|
439
458
|
console.error('Error sending document:', error);
|
|
440
459
|
throw error;
|
|
@@ -495,8 +514,9 @@ class WhatsAppClient extends EventEmitter {
|
|
|
495
514
|
};
|
|
496
515
|
}
|
|
497
516
|
|
|
498
|
-
//
|
|
499
|
-
|
|
517
|
+
// Use the safe sender to send the buttons with idempotency
|
|
518
|
+
const idemKey = buildIdemKey(chatId, messageContent);
|
|
519
|
+
return await this.guard.send(this.sock.sendMessage.bind(this.sock), chatId, messageContent, extraOptions, idemKey);
|
|
500
520
|
} catch (error) {
|
|
501
521
|
console.error('Error sending buttons:', error);
|
|
502
522
|
throw error;
|
|
@@ -536,7 +556,9 @@ class WhatsAppClient extends EventEmitter {
|
|
|
536
556
|
})),
|
|
537
557
|
};
|
|
538
558
|
|
|
539
|
-
|
|
559
|
+
// Use the safe sender to send the list with idempotency
|
|
560
|
+
const idemKey = buildIdemKey(chatId, listMessage);
|
|
561
|
+
return await this.guard.send(this.sock.sendMessage.bind(this.sock), chatId, listMessage, undefined, idemKey);
|
|
540
562
|
} catch (error) {
|
|
541
563
|
console.error('Error sending list message:', error);
|
|
542
564
|
throw error;
|
|
@@ -562,8 +584,7 @@ class WhatsAppClient extends EventEmitter {
|
|
|
562
584
|
// Read the local image as Buffer
|
|
563
585
|
const bufferLocalFile = fs.readFileSync(imgpath);
|
|
564
586
|
|
|
565
|
-
|
|
566
|
-
await this.sock.sendMessage(number, {
|
|
587
|
+
const adReplyMessage = {
|
|
567
588
|
text: msg,
|
|
568
589
|
contextInfo: {
|
|
569
590
|
externalAdReply: {
|
|
@@ -578,7 +599,11 @@ class WhatsAppClient extends EventEmitter {
|
|
|
578
599
|
mediaUrl: sourceurl || 'https://m.facebook.com/innovatorssoft'
|
|
579
600
|
}
|
|
580
601
|
}
|
|
581
|
-
}
|
|
602
|
+
};
|
|
603
|
+
|
|
604
|
+
// Use the safe sender to send the ad reply with idempotency
|
|
605
|
+
const idemKey = buildIdemKey(number, adReplyMessage);
|
|
606
|
+
await this.guard.send(this.sock.sendMessage.bind(this.sock), number, adReplyMessage, undefined, idemKey);
|
|
582
607
|
|
|
583
608
|
} catch (err) {
|
|
584
609
|
console.error('Failed to send externalAdReply:', err);
|
|
@@ -648,6 +673,7 @@ class WhatsAppClient extends EventEmitter {
|
|
|
648
673
|
}
|
|
649
674
|
|
|
650
675
|
try {
|
|
676
|
+
// For readMessages, we don't need idempotency since it's a read operation
|
|
651
677
|
await this.sock.readMessages([messageId]);
|
|
652
678
|
} catch (error) {
|
|
653
679
|
console.error('Error marking message as read:', error);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "innovators-bot2",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.4",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
},
|
|
30
30
|
"homepage": "https://github.com/innovatorssoft/WhatsAppAPI?tab=readme-ov-file#whatsapp-api",
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"@itsukichan/baileys": "github:
|
|
32
|
+
"@itsukichan/baileys": "github:WhiskeySockets/Baileys",
|
|
33
33
|
"link-preview-js": "^3.0.13",
|
|
34
34
|
"mime-types": "^2.1.35",
|
|
35
35
|
"pino": "^9.6.0",
|
package/utils.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
const crypto = require('crypto');
|
|
2
|
+
|
|
3
|
+
function hashContent(content) {
|
|
4
|
+
return crypto.createHash("sha256")
|
|
5
|
+
.update(JSON.stringify(content))
|
|
6
|
+
.digest("hex")
|
|
7
|
+
.slice(0, 16); // short is fine
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
// Same message retried? Same idemKey.
|
|
11
|
+
function buildIdemKey(jid, content) {
|
|
12
|
+
return `wa:${jid}:${hashContent(content)}`;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
module.exports = {
|
|
16
|
+
hashContent,
|
|
17
|
+
buildIdemKey
|
|
18
|
+
};
|