@raevon/n8n-nodes-whatsapp 2.0.12 → 2.0.14

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@raevon/n8n-nodes-whatsapp",
3
- "version": "2.0.12",
3
+ "version": "2.0.14",
4
4
  "description": "n8n community node for WhatsApp — send and receive messages with anti-ban protection via the Baileys library",
5
5
  "keywords": [
6
6
  "n8n-community-node-package",
@@ -1 +0,0 @@
1
- export {};
@@ -1,390 +0,0 @@
1
- "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
- var __importStar = (this && this.__importStar) || (function () {
19
- var ownKeys = function(o) {
20
- ownKeys = Object.getOwnPropertyNames || function (o) {
21
- var ar = [];
22
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
- return ar;
24
- };
25
- return ownKeys(o);
26
- };
27
- return function (mod) {
28
- if (mod && mod.__esModule) return mod;
29
- var result = {};
30
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
- __setModuleDefault(result, mod);
32
- return result;
33
- };
34
- })();
35
- var __importDefault = (this && this.__importDefault) || function (mod) {
36
- return (mod && mod.__esModule) ? mod : { "default": mod };
37
- };
38
- Object.defineProperty(exports, "__esModule", { value: true });
39
- const baileys_1 = __importStar(require("@whiskeysockets/baileys"));
40
- const p_queue_1 = __importDefault(require("p-queue"));
41
- const node_path_1 = __importDefault(require("node:path"));
42
- const node_fs_1 = __importDefault(require("node:fs"));
43
- const node_http_1 = __importDefault(require("node:http"));
44
- const node_url_1 = require("node:url");
45
- const qrcode_1 = __importDefault(require("qrcode"));
46
- // --- Configuration ---
47
- const SESSION_PATH = process.env.WHATSAPP_SESSION_PATH || '~/.n8n/whatsapp-auth';
48
- const PORT = parseInt(process.env.WHATSAPP_PORT || '3456');
49
- const MESSAGE_DELAY_MIN = parseInt(process.env.MESSAGE_DELAY_MIN || '5000');
50
- const MESSAGE_DELAY_MAX = parseInt(process.env.MESSAGE_DELAY_MAX || '9000');
51
- const BURST_SIZE = parseInt(process.env.BURST_SIZE || '20');
52
- const BURST_PAUSE_MIN = parseInt(process.env.BURST_PAUSE_MIN || '30000');
53
- const BURST_PAUSE_MAX = parseInt(process.env.BURST_PAUSE_MAX || '60000');
54
- const TYPING_SIMULATION = process.env.TYPING_SIMULATION !== 'false';
55
- const DAILY_SEND_LIMIT = parseInt(process.env.DAILY_SEND_LIMIT || '500');
56
- const CHECK_RECIPIENT = process.env.CHECK_RECIPIENT !== 'false';
57
- // --- State ---
58
- let socket = null;
59
- let status = 'stopped';
60
- let qr = null;
61
- let queue = null;
62
- let nextSendAt = 0;
63
- let sentInBurst = 0;
64
- let sentTodayCount = 0;
65
- let sentTodayDate = '';
66
- let lastError = null;
67
- const messageBuffer = [];
68
- // --- Helpers ---
69
- const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));
70
- const randBetween = (min, max) => max > min ? min + Math.floor(Math.random() * (max - min + 1)) : min;
71
- function expandHome(p) {
72
- if (!p.startsWith('~'))
73
- return p;
74
- return node_path_1.default.join(process.env.HOME || process.env.USERPROFILE || '', p.slice(1));
75
- }
76
- function todayStartIso() {
77
- return new Date().toISOString().slice(0, 10) + 'T00:00:00.000Z';
78
- }
79
- function sendGapMs() {
80
- return randBetween(MESSAGE_DELAY_MIN, MESSAGE_DELAY_MAX);
81
- }
82
- function burstPauseMs() {
83
- if (!BURST_SIZE || ++sentInBurst < BURST_SIZE)
84
- return 0;
85
- sentInBurst = 0;
86
- return randBetween(BURST_PAUSE_MIN, BURST_PAUSE_MAX);
87
- }
88
- function normalizeRecipient(to) {
89
- if (to.endsWith('@s.whatsapp.net') || to.endsWith('@g.us'))
90
- return to;
91
- const digits = to.replace(/\D/g, '');
92
- if (!/^[1-9][0-9]{7,14}$/.test(digits)) {
93
- throw new Error(`Invalid phone number: ${to}`);
94
- }
95
- return `${digits}@s.whatsapp.net`;
96
- }
97
- // --- Silent Logger ---
98
- const noop = () => { };
99
- const silentLogger = {
100
- level: 'silent',
101
- info: noop, warn: noop, error: noop, debug: noop, fatal: noop, trace: noop,
102
- child: () => silentLogger,
103
- };
104
- // --- Socket Management ---
105
- async function connect() {
106
- if (socket && status === 'connected')
107
- return;
108
- const resolvedPath = expandHome(SESSION_PATH);
109
- if (!node_fs_1.default.existsSync(resolvedPath)) {
110
- node_fs_1.default.mkdirSync(resolvedPath, { recursive: true });
111
- }
112
- const { state, saveCreds } = await (0, baileys_1.useMultiFileAuthState)(resolvedPath);
113
- const { version } = await (0, baileys_1.fetchLatestBaileysVersion)();
114
- status = 'connecting';
115
- qr = null;
116
- socket = (0, baileys_1.default)({
117
- version,
118
- browser: baileys_1.Browsers.ubuntu('n8n WhatsApp Node'),
119
- auth: {
120
- creds: state.creds,
121
- keys: (0, baileys_1.makeCacheableSignalKeyStore)(state.keys, silentLogger),
122
- },
123
- markOnlineOnConnect: false,
124
- syncFullHistory: false,
125
- shouldSyncHistoryMessage: () => false,
126
- generateHighQualityLinkPreview: false,
127
- logger: silentLogger,
128
- });
129
- socket.ev.on('creds.update', () => {
130
- saveCreds().catch(() => { });
131
- });
132
- socket.ev.on('connection.update', async (update) => {
133
- var _a, _b;
134
- const { connection, lastDisconnect, qr: newQr } = update;
135
- if (newQr) {
136
- qr = newQr;
137
- status = 'connecting';
138
- }
139
- if (connection === 'open') {
140
- status = 'connected';
141
- qr = null;
142
- lastError = null;
143
- console.log('[WhatsApp Server] Connected');
144
- return;
145
- }
146
- if (connection === 'close') {
147
- const code = (_b = (_a = lastDisconnect === null || lastDisconnect === void 0 ? void 0 : lastDisconnect.error) === null || _a === void 0 ? void 0 : _a.output) === null || _b === void 0 ? void 0 : _b.statusCode;
148
- const reason = baileys_1.DisconnectReason[code] || 'Unknown';
149
- lastError = `Code: ${code} (${reason})`;
150
- console.log(`[WhatsApp Server] Disconnected: ${reason}`);
151
- if (code === baileys_1.DisconnectReason.loggedOut) {
152
- status = 'logged_out';
153
- socket = null;
154
- }
155
- else {
156
- status = 'error';
157
- socket = null;
158
- // Auto-reconnect after delay
159
- setTimeout(() => connect().catch(() => { }), 5000);
160
- }
161
- }
162
- });
163
- // Listen for incoming messages
164
- socket.ev.on('messages.upsert', (upsert) => {
165
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q;
166
- const { messages, type } = upsert;
167
- if (type !== 'notify')
168
- return;
169
- for (const msg of messages) {
170
- if (msg.key.fromMe)
171
- continue;
172
- let content = null;
173
- const msgType = Object.keys(msg.message || {})[0] || 'unknown';
174
- if ((_a = msg.message) === null || _a === void 0 ? void 0 : _a.conversation)
175
- content = msg.message.conversation;
176
- else if ((_c = (_b = msg.message) === null || _b === void 0 ? void 0 : _b.extendedTextMessage) === null || _c === void 0 ? void 0 : _c.text)
177
- content = msg.message.extendedTextMessage.text;
178
- else if ((_e = (_d = msg.message) === null || _d === void 0 ? void 0 : _d.imageMessage) === null || _e === void 0 ? void 0 : _e.caption)
179
- content = msg.message.imageMessage.caption;
180
- else if ((_g = (_f = msg.message) === null || _f === void 0 ? void 0 : _f.videoMessage) === null || _g === void 0 ? void 0 : _g.caption)
181
- content = msg.message.videoMessage.caption;
182
- else if ((_j = (_h = msg.message) === null || _h === void 0 ? void 0 : _h.documentMessage) === null || _j === void 0 ? void 0 : _j.fileName)
183
- content = msg.message.documentMessage.fileName;
184
- else if ((_k = msg.message) === null || _k === void 0 ? void 0 : _k.audioMessage)
185
- content = '[Audio]';
186
- else if ((_l = msg.message) === null || _l === void 0 ? void 0 : _l.stickerMessage)
187
- content = '[Sticker]';
188
- else if ((_m = msg.message) === null || _m === void 0 ? void 0 : _m.locationMessage)
189
- content = `[Location] ${msg.message.locationMessage.address || ''}`;
190
- else if ((_p = (_o = msg.message) === null || _o === void 0 ? void 0 : _o.contactMessage) === null || _p === void 0 ? void 0 : _p.displayName)
191
- content = `[Contact] ${msg.message.contactMessage.displayName}`;
192
- else
193
- content = `[${msgType}]`;
194
- const timestampSeconds = msg.messageTimestamp != null ? Number(msg.messageTimestamp) : Date.now() / 1000;
195
- messageBuffer.push({
196
- messageId: msg.key.id,
197
- chatJid: msg.key.remoteJid,
198
- sender: (_q = msg.key.participant) !== null && _q !== void 0 ? _q : msg.key.remoteJid,
199
- content,
200
- timestamp: new Date(timestampSeconds * 1000).toISOString(),
201
- isFromMe: false,
202
- messageType: msgType,
203
- });
204
- }
205
- });
206
- if (!queue) {
207
- queue = new p_queue_1.default({ concurrency: 1 });
208
- }
209
- }
210
- async function sendMessage(to, content) {
211
- if (!socket || status !== 'connected') {
212
- throw new Error('WhatsApp not connected');
213
- }
214
- // Daily limit
215
- if (DAILY_SEND_LIMIT > 0) {
216
- const today = todayStartIso();
217
- if (sentTodayDate !== today) {
218
- sentTodayDate = today;
219
- sentTodayCount = 0;
220
- }
221
- if (sentTodayCount >= DAILY_SEND_LIMIT) {
222
- throw new Error(`Daily limit (${DAILY_SEND_LIMIT}) reached`);
223
- }
224
- }
225
- const jid = normalizeRecipient(to);
226
- // Recipient check
227
- if (CHECK_RECIPIENT && !jid.endsWith('@g.us')) {
228
- const results = await socket.onWhatsApp(jid);
229
- const result = results === null || results === void 0 ? void 0 : results[0];
230
- if (!(result === null || result === void 0 ? void 0 : result.exists)) {
231
- throw new Error(`Recipient ${to} not on WhatsApp`);
232
- }
233
- }
234
- // Queue with anti-ban
235
- const task = queue.add(async () => {
236
- var _a;
237
- const wait = nextSendAt - Date.now();
238
- if (wait > 0)
239
- await sleep(wait);
240
- // Typing simulation
241
- if (TYPING_SIMULATION) {
242
- try {
243
- await socket.sendPresenceUpdate('composing', jid);
244
- const len = (content.text || content.caption || '').length;
245
- await sleep(Math.min(randBetween(900, 1800) + len * 25, 4000));
246
- await socket.sendPresenceUpdate('paused', jid);
247
- }
248
- catch { }
249
- }
250
- const response = await socket.sendMessage(jid, content);
251
- nextSendAt = Date.now() + sendGapMs() + burstPauseMs();
252
- if (DAILY_SEND_LIMIT > 0)
253
- sentTodayCount++;
254
- if (!((_a = response === null || response === void 0 ? void 0 : response.key) === null || _a === void 0 ? void 0 : _a.id))
255
- throw new Error('No message ID returned');
256
- return { messageId: response.key.id, status: 'sent', recipient: jid };
257
- });
258
- // 15s timeout race
259
- let timer;
260
- const winner = await Promise.race([
261
- task.then(result => ({ result }), error => ({ error })),
262
- new Promise(resolve => { timer = setTimeout(() => resolve(null), 15000); }),
263
- ]);
264
- clearTimeout(timer);
265
- if (!winner) {
266
- task.then(() => { }, () => { });
267
- return { messageId: 'queued', status: 'queued', recipient: jid };
268
- }
269
- const w = winner;
270
- if (w.error)
271
- throw w.error;
272
- return w.result;
273
- }
274
- function getStatus() {
275
- return {
276
- status,
277
- connected: status === 'connected',
278
- qrAvailable: status === 'connecting' && qr !== null,
279
- sentToday: sentTodayCount,
280
- dailyLimit: DAILY_SEND_LIMIT,
281
- lastError,
282
- };
283
- }
284
- // --- HTTP Server ---
285
- const server = node_http_1.default.createServer(async (req, res) => {
286
- const url = new node_url_1.URL(req.url || '/', `http://localhost:${PORT}`);
287
- // CORS headers
288
- res.setHeader('Access-Control-Allow-Origin', '*');
289
- res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
290
- res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
291
- if (req.method === 'OPTIONS') {
292
- res.writeHead(204);
293
- res.end();
294
- return;
295
- }
296
- try {
297
- if (url.pathname === '/health') {
298
- res.writeHead(200, { 'Content-Type': 'application/json' });
299
- res.end(JSON.stringify({ ok: true, ...getStatus() }));
300
- }
301
- else if (url.pathname === '/status') {
302
- res.writeHead(200, { 'Content-Type': 'application/json' });
303
- res.end(JSON.stringify(getStatus()));
304
- }
305
- else if (url.pathname === '/connect') {
306
- await connect();
307
- // Wait for connection or QR (up to 30s)
308
- const result = await new Promise((resolve) => {
309
- const check = setInterval(() => {
310
- if (status === 'connected') {
311
- clearInterval(check);
312
- resolve({ connected: true, message: 'Connected' });
313
- }
314
- if (qr) {
315
- clearInterval(check);
316
- qrcode_1.default.toDataURL(qr, { width: 300, margin: 2 }).then(qrUrl => {
317
- resolve({ connected: false, qrUrl, message: 'Scan QR in browser' });
318
- });
319
- }
320
- }, 500);
321
- setTimeout(() => { clearInterval(check); resolve({ connected: false, status, message: 'Timeout' }); }, 30000);
322
- });
323
- res.writeHead(200, { 'Content-Type': 'application/json' });
324
- res.end(JSON.stringify(result));
325
- }
326
- else if (url.pathname === '/qr' && qr) {
327
- const qrUrl = await qrcode_1.default.toDataURL(qr, { width: 400, margin: 2 });
328
- res.writeHead(200, { 'Content-Type': 'text/html' });
329
- res.end(`<!DOCTYPE html><html><head><title>WhatsApp QR</title><style>body{display:flex;justify-content:center;align-items:center;height:100vh;margin:0;background:#111}img{border-radius:12px}</style></head><body><img src="${qrUrl}"/></body></html>`);
330
- }
331
- else if (url.pathname === '/send' && req.method === 'POST') {
332
- let body = '';
333
- req.on('data', chunk => body += chunk);
334
- req.on('end', async () => {
335
- try {
336
- const { to, ...content } = JSON.parse(body);
337
- if (!to)
338
- throw new Error('Missing "to" field');
339
- const result = await sendMessage(to, content);
340
- res.writeHead(200, { 'Content-Type': 'application/json' });
341
- res.end(JSON.stringify(result));
342
- }
343
- catch (err) {
344
- res.writeHead(400, { 'Content-Type': 'application/json' });
345
- res.end(JSON.stringify({ error: err.message }));
346
- }
347
- });
348
- }
349
- else if (url.pathname === '/messages') {
350
- // Return buffered messages and clear buffer
351
- const messages = messageBuffer.splice(0, messageBuffer.length);
352
- res.writeHead(200, { 'Content-Type': 'application/json' });
353
- res.end(JSON.stringify({ messages, count: messages.length }));
354
- }
355
- else if (url.pathname === '/signout') {
356
- if (socket) {
357
- try {
358
- socket.end(new Error('Sign out'));
359
- }
360
- catch { }
361
- socket = null;
362
- }
363
- status = 'stopped';
364
- const resolvedPath = expandHome(SESSION_PATH);
365
- if (node_fs_1.default.existsSync(resolvedPath)) {
366
- const files = node_fs_1.default.readdirSync(resolvedPath);
367
- for (const f of files)
368
- node_fs_1.default.unlinkSync(node_path_1.default.join(resolvedPath, f));
369
- node_fs_1.default.rmdirSync(resolvedPath);
370
- }
371
- res.writeHead(200, { 'Content-Type': 'application/json' });
372
- res.end(JSON.stringify({ success: true, message: 'Signed out' }));
373
- }
374
- else {
375
- res.writeHead(404, { 'Content-Type': 'application/json' });
376
- res.end(JSON.stringify({ error: 'Not found' }));
377
- }
378
- }
379
- catch (err) {
380
- res.writeHead(500, { 'Content-Type': 'application/json' });
381
- res.end(JSON.stringify({ error: err.message }));
382
- }
383
- });
384
- // --- Start ---
385
- server.listen(PORT, () => {
386
- console.log(`[WhatsApp Server] Listening on port ${PORT}`);
387
- console.log(`[WhatsApp Server] Session: ${SESSION_PATH}`);
388
- // Auto-connect on startup
389
- connect().catch(() => { });
390
- });