195 lines
5.9 KiB
JavaScript
195 lines
5.9 KiB
JavaScript
/**
|
|
* BookingWA — Servizio WhatsApp Baileys per Farmacia Ianni
|
|
* Espone REST: GET /status, GET /qr, POST /send, POST /disconnect
|
|
* Sessione persistente in /data/auth per sopravvivere ai restart.
|
|
*/
|
|
const express = require('express');
|
|
const { default: makeWASocket, useMultiFileAuthState, DisconnectReason, fetchLatestBaileysVersion } = require('@whiskeysockets/baileys');
|
|
const qrcode = require('qrcode');
|
|
const pino = require('pino');
|
|
|
|
const app = express();
|
|
app.use(express.json());
|
|
|
|
const PORT = process.env.PORT || 3100;
|
|
const AUTH_DIR = '/data/auth';
|
|
const logger = pino({ level: 'warn' });
|
|
|
|
let sock = null;
|
|
let currentQR = null;
|
|
let qrGeneratedAt = null;
|
|
let qrAttempt = 0;
|
|
let connectionState = 'disconnected';
|
|
let phoneNumber = null;
|
|
let retryCount = 0;
|
|
const MAX_RETRIES = 5;
|
|
const QR_TTL_SEC = 20;
|
|
|
|
async function startSocket() {
|
|
const { state, saveCreds } = await useMultiFileAuthState(AUTH_DIR);
|
|
const { version } = await fetchLatestBaileysVersion();
|
|
|
|
sock = makeWASocket({
|
|
version,
|
|
auth: state,
|
|
logger,
|
|
printQRInTerminal: false,
|
|
browser: ['Farmacia Ianni', 'Chrome', '120.0'],
|
|
connectTimeoutMs: 30000,
|
|
defaultQueryTimeoutMs: 30000,
|
|
generateHighQualityLinkPreview: false,
|
|
syncFullHistory: false,
|
|
qrTimeout: 60000,
|
|
});
|
|
|
|
sock.ev.on('creds.update', saveCreds);
|
|
|
|
sock.ev.on('connection.update', async (update) => {
|
|
const { connection, lastDisconnect, qr } = update;
|
|
|
|
if (qr) {
|
|
currentQR = await qrcode.toDataURL(qr, { width: 280, margin: 2 });
|
|
qrGeneratedAt = Date.now();
|
|
qrAttempt++;
|
|
connectionState = 'connecting';
|
|
console.log(`[WA] QR #${qrAttempt} generato — scade tra ${QR_TTL_SEC}s`);
|
|
}
|
|
|
|
if (connection === 'close') {
|
|
currentQR = null;
|
|
qrGeneratedAt = null;
|
|
const statusCode = lastDisconnect?.error?.output?.statusCode;
|
|
const shouldReconnect = statusCode !== DisconnectReason.loggedOut;
|
|
|
|
if (statusCode === DisconnectReason.loggedOut) {
|
|
connectionState = 'disconnected';
|
|
phoneNumber = null;
|
|
retryCount = 0;
|
|
qrAttempt = 0;
|
|
console.log('[WA] Disconnesso (logout). Serve nuovo QR.');
|
|
const fs = require('fs');
|
|
fs.rmSync(AUTH_DIR, { recursive: true, force: true });
|
|
fs.mkdirSync(AUTH_DIR, { recursive: true });
|
|
} else if (shouldReconnect && retryCount < MAX_RETRIES) {
|
|
retryCount++;
|
|
const delay = Math.min(retryCount * 2000, 15000);
|
|
console.log(`[WA] Riconnessione ${retryCount}/${MAX_RETRIES} in ${delay}ms...`);
|
|
connectionState = 'connecting';
|
|
setTimeout(startSocket, delay);
|
|
} else {
|
|
connectionState = 'disconnected';
|
|
console.log('[WA] Connessione persa, max retry raggiunto.');
|
|
}
|
|
}
|
|
|
|
if (connection === 'open') {
|
|
connectionState = 'connected';
|
|
currentQR = null;
|
|
qrGeneratedAt = null;
|
|
retryCount = 0;
|
|
qrAttempt = 0;
|
|
phoneNumber = sock.user?.id?.split(':')[0] || sock.user?.id;
|
|
console.log(`[WA] Connesso come ${phoneNumber}`);
|
|
}
|
|
});
|
|
}
|
|
|
|
// ── API ──
|
|
|
|
app.get('/status', (req, res) => {
|
|
res.json({
|
|
connected: connectionState === 'connected',
|
|
state: connectionState,
|
|
phone: phoneNumber,
|
|
message: connectionState === 'connected'
|
|
? `Connesso come ${phoneNumber}`
|
|
: connectionState === 'connecting'
|
|
? 'In attesa di scansione QR...'
|
|
: 'Disconnesso'
|
|
});
|
|
});
|
|
|
|
app.get('/qr', (req, res) => {
|
|
if (connectionState === 'connected') {
|
|
return res.json({ qr: null, expired: false, message: 'Già connesso' });
|
|
}
|
|
|
|
if (currentQR && qrGeneratedAt) {
|
|
const elapsed = (Date.now() - qrGeneratedAt) / 1000;
|
|
const remaining = Math.max(0, Math.round(QR_TTL_SEC - elapsed));
|
|
|
|
if (remaining <= 0) {
|
|
return res.json({
|
|
qr: null,
|
|
expired: true,
|
|
attempt: qrAttempt,
|
|
message: 'QR scaduto — in attesa del prossimo...'
|
|
});
|
|
}
|
|
|
|
return res.json({
|
|
qr: currentQR,
|
|
expired: false,
|
|
expires_in: remaining,
|
|
ttl: QR_TTL_SEC,
|
|
attempt: qrAttempt,
|
|
});
|
|
}
|
|
|
|
// Se non c'è QR e non siamo connessi, avvia socket
|
|
if (connectionState === 'disconnected') {
|
|
startSocket().catch(console.error);
|
|
return res.json({ qr: null, expired: false, message: 'Avvio connessione... riprova tra 5 secondi' });
|
|
}
|
|
|
|
res.json({ qr: null, expired: false, message: 'QR in generazione...' });
|
|
});
|
|
|
|
app.post('/send', async (req, res) => {
|
|
const { to, text } = req.body;
|
|
if (!to || !text) return res.status(400).json({ error: 'Servono "to" e "text"' });
|
|
if (connectionState !== 'connected' || !sock) return res.status(503).json({ error: 'WhatsApp non connesso' });
|
|
|
|
try {
|
|
let jid = to.replace(/[^0-9]/g, '');
|
|
if (!jid.endsWith('@s.whatsapp.net')) jid = jid + '@s.whatsapp.net';
|
|
await sock.sendMessage(jid, { text });
|
|
console.log(`[WA] Messaggio inviato a ${to}`);
|
|
res.json({ ok: true, to, message: 'Inviato' });
|
|
} catch (e) {
|
|
console.error('[WA] Errore invio:', e.message);
|
|
res.status(500).json({ error: e.message });
|
|
}
|
|
});
|
|
|
|
app.post('/disconnect', async (req, res) => {
|
|
try {
|
|
if (sock) { await sock.logout(); sock = null; }
|
|
connectionState = 'disconnected';
|
|
phoneNumber = null;
|
|
currentQR = null;
|
|
qrGeneratedAt = null;
|
|
qrAttempt = 0;
|
|
res.json({ ok: true, message: 'Disconnesso' });
|
|
} catch (e) {
|
|
res.status(500).json({ error: e.message });
|
|
}
|
|
});
|
|
|
|
app.get('/health', (req, res) => {
|
|
res.json({ status: 'ok', service: 'booking-wa', state: connectionState });
|
|
});
|
|
|
|
// ── START ──
|
|
app.listen(PORT, '0.0.0.0', () => {
|
|
console.log(`[WA] Servizio avviato su :${PORT}`);
|
|
const fs = require('fs');
|
|
if (fs.existsSync(AUTH_DIR) && fs.readdirSync(AUTH_DIR).length > 0) {
|
|
console.log('[WA] Sessione trovata, riconnessione...');
|
|
startSocket().catch(console.error);
|
|
} else {
|
|
console.log('[WA] Nessuna sessione. Chiama GET /qr per iniziare.');
|
|
fs.mkdirSync(AUTH_DIR, { recursive: true });
|
|
}
|
|
});
|