// NOTE: auth/ directory is in .gitignore — do not commit session data const { default: makeWASocket, useMultiFileAuthState, DisconnectReason, fetchLatestBaileysVersion } = require('@whiskeysockets/baileys'); const express = require('express'); const pino = require('pino'); const QRCode = require('qrcode'); const path = require('path'); const PORT = 8098; const HOST = '0.0.0.0'; const AUTH_DIR = path.join(__dirname, 'auth'); const MAX_RECONNECT_ATTEMPTS = 5; const logger = pino({ level: 'warn' }); let sock = null; let connected = false; let phoneNumber = null; let currentQR = null; let reconnectAttempts = 0; let messageQueue = []; let shuttingDown = false; // --- WhatsApp connection --- async function startConnection() { const { state, saveCreds } = await useMultiFileAuthState(AUTH_DIR); const { version } = await fetchLatestBaileysVersion(); sock = makeWASocket({ version, auth: state, logger, printQRInTerminal: false, defaultQueryTimeoutMs: 60000, }); sock.ev.on('creds.update', saveCreds); sock.ev.on('connection.update', async (update) => { const { connection, lastDisconnect, qr } = update; if (qr) { try { currentQR = await QRCode.toDataURL(qr); console.log('[whatsapp] QR code generated — scan with WhatsApp to link'); } catch (err) { console.error('[whatsapp] Failed to generate QR code:', err.message); } } if (connection === 'open') { connected = true; currentQR = null; reconnectAttempts = 0; phoneNumber = sock.user?.id?.split(':')[0] || sock.user?.id?.split('@')[0] || null; console.log(`[whatsapp] Connected as ${phoneNumber}`); } if (connection === 'close') { connected = false; phoneNumber = null; const statusCode = lastDisconnect?.error?.output?.statusCode; const shouldReconnect = statusCode !== DisconnectReason.loggedOut; console.log(`[whatsapp] Disconnected (status: ${statusCode})`); if (shouldReconnect && !shuttingDown) { if (reconnectAttempts < MAX_RECONNECT_ATTEMPTS) { reconnectAttempts++; const delay = Math.min(1000 * Math.pow(2, reconnectAttempts), 30000); console.log(`[whatsapp] Reconnecting in ${delay}ms (attempt ${reconnectAttempts}/${MAX_RECONNECT_ATTEMPTS})`); setTimeout(startConnection, delay); } else { console.error(`[whatsapp] Max reconnect attempts reached (${MAX_RECONNECT_ATTEMPTS})`); } } else if (statusCode === DisconnectReason.loggedOut) { console.log('[whatsapp] Logged out — delete auth/ and restart to re-link'); } } }); sock.ev.on('messages.upsert', ({ messages, type }) => { if (type !== 'notify') return; for (const msg of messages) { // Skip status broadcasts if (msg.key.remoteJid === 'status@broadcast') continue; // Skip own messages in private chats (allow in groups for self-chat) const isGroup = msg.key.remoteJid.endsWith('@g.us'); if (msg.key.fromMe && !isGroup) continue; // Only text messages const text = msg.message?.conversation || msg.message?.extendedTextMessage?.text; if (!text) continue; messageQueue.push({ from: msg.key.remoteJid, participant: msg.key.participant || null, pushName: msg.pushName || null, text, timestamp: msg.messageTimestamp, id: msg.key.id, isGroup, fromMe: msg.key.fromMe || false, }); console.log(`[whatsapp] Message from ${msg.pushName || 'unknown'} in ${msg.key.remoteJid}: ${text.substring(0, 80)}`); } }); } // --- Express API --- const app = express(); app.use(express.json()); app.get('/status', (_req, res) => { res.json({ connected, phone: phoneNumber, qr: connected ? null : currentQR, }); }); app.get('/qr', (_req, res) => { if (connected) { return res.json({ error: 'already connected' }); } if (!currentQR) { return res.json({ error: 'no QR code available yet' }); } // Return as HTML page with QR image for easy scanning const html = ` WhatsApp QR QR Code

Scan with WhatsApp → Linked Devices

`; res.type('html').send(html); }); app.post('/send', async (req, res) => { const { to, text } = req.body || {}; if (!to || !text) { return res.status(400).json({ ok: false, error: 'missing "to" or "text" in body' }); } if (!connected || !sock) { return res.status(503).json({ ok: false, error: 'not connected to WhatsApp' }); } try { const result = await sock.sendMessage(to, { text }); res.json({ ok: true, id: result.key.id }); } catch (err) { console.error('[whatsapp] Send failed:', err.message); res.status(500).json({ ok: false, error: err.message }); } }); app.post('/react', async (req, res) => { const { to, id, emoji, fromMe, participant } = req.body || {}; if (!to || !id || emoji == null) { return res.status(400).json({ ok: false, error: 'missing "to", "id", or "emoji" in body' }); } if (!connected || !sock) { return res.status(503).json({ ok: false, error: 'not connected to WhatsApp' }); } try { const key = { remoteJid: to, id, fromMe: fromMe || false }; if (participant) key.participant = participant; await sock.sendMessage(to, { react: { text: emoji, key } }); res.json({ ok: true }); } catch (err) { console.error('[whatsapp] React failed:', err.message); res.status(500).json({ ok: false, error: err.message }); } }); app.get('/messages', (_req, res) => { const messages = messageQueue.splice(0); res.json({ messages }); }); // --- Startup --- const server = app.listen(PORT, HOST, () => { console.log(`[whatsapp] Bridge API listening on http://${HOST}:${PORT}`); startConnection().catch((err) => { console.error('[whatsapp] Failed to start connection:', err.message); }); }); // --- Graceful shutdown --- function shutdown(signal) { console.log(`[whatsapp] Received ${signal}, shutting down...`); shuttingDown = true; if (sock) { sock.end(undefined); } server.close(() => { console.log('[whatsapp] HTTP server closed'); process.exit(0); }); // Force exit after 5s setTimeout(() => process.exit(1), 5000); } process.on('SIGTERM', () => shutdown('SIGTERM')); process.on('SIGINT', () => shutdown('SIGINT'));