@raevon/n8n-nodes-whatsapp 1.0.13 → 2.0.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/nodes/WhatsApp/WhatsAppApiHelper.d.ts +3 -52
- package/dist/nodes/WhatsApp/WhatsAppApiHelper.js +76 -468
- package/dist/nodes/WhatsApp/WhatsAppConnect.node.js +50 -20
- package/dist/nodes/WhatsApp/WhatsAppSend.node.js +30 -70
- package/dist/nodes/WhatsApp/WhatsAppServer.d.ts +1 -0
- package/dist/nodes/WhatsApp/WhatsAppServer.js +390 -0
- package/dist/nodes/WhatsApp/WhatsAppTrigger.node.js +27 -41
- package/package.json +1 -1
|
@@ -0,0 +1,390 @@
|
|
|
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
|
+
});
|
|
@@ -2,10 +2,6 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.WhatsAppTrigger = void 0;
|
|
4
4
|
const WhatsAppApiHelper_1 = require("./WhatsAppApiHelper");
|
|
5
|
-
// Per-credential buffers — one buffer per session path
|
|
6
|
-
// This ensures multiple trigger nodes with different credentials don't share messages
|
|
7
|
-
const buffersBySession = new Map();
|
|
8
|
-
const initializedBySession = new Set();
|
|
9
5
|
class WhatsAppTrigger {
|
|
10
6
|
constructor() {
|
|
11
7
|
this.description = {
|
|
@@ -15,7 +11,7 @@ class WhatsAppTrigger {
|
|
|
15
11
|
group: ['trigger'],
|
|
16
12
|
version: 1,
|
|
17
13
|
subtitle: '={{$parameter["event"]}}',
|
|
18
|
-
description: 'Triggers on incoming WhatsApp messages',
|
|
14
|
+
description: 'Triggers on incoming WhatsApp messages via background server',
|
|
19
15
|
defaults: { name: 'WhatsApp Trigger' },
|
|
20
16
|
inputs: [],
|
|
21
17
|
outputs: ['main'],
|
|
@@ -36,14 +32,14 @@ class WhatsAppTrigger {
|
|
|
36
32
|
name: 'chatJidFilter',
|
|
37
33
|
type: 'string',
|
|
38
34
|
default: '',
|
|
39
|
-
description: 'Optional: only trigger for messages from this chat JID
|
|
35
|
+
description: 'Optional: only trigger for messages from this chat JID',
|
|
40
36
|
},
|
|
41
37
|
{
|
|
42
38
|
displayName: 'Only Text Messages',
|
|
43
39
|
name: 'onlyText',
|
|
44
40
|
type: 'boolean',
|
|
45
41
|
default: false,
|
|
46
|
-
description: 'If true, only trigger for text messages
|
|
42
|
+
description: 'If true, only trigger for text messages',
|
|
47
43
|
},
|
|
48
44
|
],
|
|
49
45
|
};
|
|
@@ -53,42 +49,32 @@ class WhatsAppTrigger {
|
|
|
53
49
|
const cfg = await (0, WhatsAppApiHelper_1.getWhatsAppCredentials)(credentials);
|
|
54
50
|
const chatFilter = this.getNodeParameter('chatJidFilter', '');
|
|
55
51
|
const onlyText = this.getNodeParameter('onlyText', false);
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
52
|
+
// Ensure server is running
|
|
53
|
+
await (0, WhatsAppApiHelper_1.ensureServerRunning)(cfg);
|
|
54
|
+
// Poll for messages
|
|
55
|
+
try {
|
|
56
|
+
const response = await fetch(`${(0, WhatsAppApiHelper_1.getServerUrl)(cfg)}/messages`);
|
|
57
|
+
const data = await response.json();
|
|
58
|
+
if (!data.messages || data.messages.length === 0)
|
|
59
|
+
return null;
|
|
60
|
+
// Apply filters
|
|
61
|
+
let messages = data.messages;
|
|
62
|
+
if (chatFilter) {
|
|
63
|
+
messages = messages.filter((msg) => msg.chatJid === chatFilter);
|
|
64
|
+
}
|
|
65
|
+
if (onlyText) {
|
|
66
|
+
messages = messages.filter((msg) => msg.messageType === 'conversation' || msg.messageType === 'extendedTextMessage');
|
|
67
|
+
}
|
|
68
|
+
if (messages.length === 0)
|
|
69
|
+
return null;
|
|
70
|
+
return [messages.map((msg) => ({
|
|
71
|
+
json: msg,
|
|
72
|
+
pairedItem: { item: 0 },
|
|
73
|
+
}))];
|
|
60
74
|
}
|
|
61
|
-
|
|
62
|
-
// Initialize socket listener once per credential
|
|
63
|
-
if (!initializedBySession.has(sessionKey)) {
|
|
64
|
-
const sock = await (0, WhatsAppApiHelper_1.ensureConnected)(cfg);
|
|
65
|
-
sock.ev.on('messages.upsert', (upsert) => {
|
|
66
|
-
const { messages, type } = upsert;
|
|
67
|
-
if (type !== 'notify')
|
|
68
|
-
return;
|
|
69
|
-
for (const msg of messages) {
|
|
70
|
-
if (msg.key.fromMe)
|
|
71
|
-
continue;
|
|
72
|
-
if (chatFilter && msg.key.remoteJid !== chatFilter)
|
|
73
|
-
continue;
|
|
74
|
-
const parsed = (0, WhatsAppApiHelper_1.parseIncomingMessage)(msg);
|
|
75
|
-
if (!parsed)
|
|
76
|
-
continue;
|
|
77
|
-
if (onlyText && parsed.messageType !== 'conversation' && parsed.messageType !== 'extendedTextMessage')
|
|
78
|
-
continue;
|
|
79
|
-
buffer.push(parsed);
|
|
80
|
-
}
|
|
81
|
-
});
|
|
82
|
-
initializedBySession.add(sessionKey);
|
|
83
|
-
}
|
|
84
|
-
// Return buffered messages
|
|
85
|
-
if (buffer.length === 0)
|
|
75
|
+
catch {
|
|
86
76
|
return null;
|
|
87
|
-
|
|
88
|
-
return [messages.map((msg) => ({
|
|
89
|
-
json: msg,
|
|
90
|
-
pairedItem: { item: 0 },
|
|
91
|
-
}))];
|
|
77
|
+
}
|
|
92
78
|
}
|
|
93
79
|
}
|
|
94
80
|
exports.WhatsAppTrigger = WhatsAppTrigger;
|
package/package.json
CHANGED