@raevon/n8n-nodes-whatsapp 2.0.13 → 2.0.15
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.
|
@@ -329,6 +329,18 @@ async function startServer(cfg) {
|
|
|
329
329
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
330
330
|
res.end(JSON.stringify({ success: true, message: 'Signed out' }));
|
|
331
331
|
}
|
|
332
|
+
else if (url.pathname === '/shutdown') {
|
|
333
|
+
if (state.socket) {
|
|
334
|
+
try {
|
|
335
|
+
state.socket.end(new Error('Shutdown'));
|
|
336
|
+
}
|
|
337
|
+
catch { }
|
|
338
|
+
}
|
|
339
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
340
|
+
res.end(JSON.stringify({ success: true }));
|
|
341
|
+
// Close HTTP server after sending response
|
|
342
|
+
setTimeout(() => { httpServer.close(); servers.delete(port); }, 500);
|
|
343
|
+
}
|
|
332
344
|
else {
|
|
333
345
|
res.writeHead(404);
|
|
334
346
|
res.end('Not found');
|
|
@@ -374,9 +386,9 @@ async function startServerIfNeeded(cfg) {
|
|
|
374
386
|
}
|
|
375
387
|
async function stopServer(cfg) {
|
|
376
388
|
const port = getPort(cfg);
|
|
377
|
-
// Try
|
|
389
|
+
// Try to shut down the server via /shutdown endpoint
|
|
378
390
|
try {
|
|
379
|
-
await fetch(`http://127.0.0.1:${port}/
|
|
391
|
+
await fetch(`http://127.0.0.1:${port}/shutdown`, { method: 'POST', signal: AbortSignal.timeout(3000) });
|
|
380
392
|
}
|
|
381
393
|
catch { }
|
|
382
394
|
// Clean up in-memory state
|
|
@@ -61,9 +61,8 @@ class WhatsAppConnect {
|
|
|
61
61
|
});
|
|
62
62
|
}
|
|
63
63
|
else if (operation === 'stopServer') {
|
|
64
|
-
// Try graceful shutdown via /signout
|
|
65
64
|
try {
|
|
66
|
-
await fetch(`${(0, WhatsAppApiHelper_1.getServerUrl)(cfg)}/
|
|
65
|
+
await fetch(`${(0, WhatsAppApiHelper_1.getServerUrl)(cfg)}/shutdown`, { method: 'POST', signal: AbortSignal.timeout(3000) });
|
|
67
66
|
returnData.push({
|
|
68
67
|
json: { success: true, message: 'Server stopped', sessionPath: cfg.sessionPath },
|
|
69
68
|
pairedItem: { item: i },
|
|
@@ -71,7 +70,7 @@ class WhatsAppConnect {
|
|
|
71
70
|
}
|
|
72
71
|
catch {
|
|
73
72
|
returnData.push({
|
|
74
|
-
json: { success: false, message: '
|
|
73
|
+
json: { success: false, message: 'Could not reach server to stop it.', sessionPath: cfg.sessionPath },
|
|
75
74
|
pairedItem: { item: i },
|
|
76
75
|
});
|
|
77
76
|
}
|
package/package.json
CHANGED
|
@@ -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
|
-
});
|