// Punte WhatsApp (Baileys) pentru Maria — implicit doar self-chat (vezi // TEST_MODE_SELF_CHAT_ONLY). Sesiunea WhatsApp (auth/) NU intra in git — // contine chei de sesiune, e in ~/.maria-bridge/whatsapp-auth/ (vezi config.py). const express = require('express'); const pino = require('pino'); const QRCode = require('qrcode'); const path = require('path'); let makeWASocket, useMultiFileAuthState, DisconnectReason, fetchLatestBaileysVersion; const PORT = parseInt(process.env.BRIDGE_PORT || '8099', 10); const HOST = process.env.BRIDGE_HOST || '127.0.0.1'; const AUTH_DIR = process.env.MARIA_BRIDGE_DIR ? path.join(process.env.MARIA_BRIDGE_DIR, 'whatsapp-auth') : path.join(__dirname, 'auth'); const MAX_RECONNECT_ATTEMPTS = 5; const TEST_MODE_SELF_CHAT_ONLY = (process.env.TEST_MODE_SELF_CHAT_ONLY || 'true') !== 'false'; const logger = pino({ level: 'warn' }); let sock = null; let connected = false; let phoneNumber = null; let ownJid = null; let ownLid = null; // acelasi cont, adresat in formatul nou LID let currentQR = null; let currentPairingCode = null; let reconnectAttempts = 0; let messageQueue = []; let shuttingDown = false; async function startConnection() { if (!makeWASocket) { const baileys = await import('@whiskeysockets/baileys'); makeWASocket = baileys.default; useMultiFileAuthState = baileys.useMultiFileAuthState; DisconnectReason = baileys.DisconnectReason; fetchLatestBaileysVersion = baileys.fetchLatestBaileysVersion; } 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; ownJid = phoneNumber ? `${phoneNumber}@s.whatsapp.net` : null; // WhatsApp adreseaza conturile si prin LID (Linked ID), nu doar prin numar. Pe // conturile noi, self-chat-ul soseste ca `@lid`, deci comparatia doar cu // `@s.whatsapp.net` arunca toate mesajele. Sufixul de dispozitiv (":13") // apare in credentiale dar nu si in remoteJid-ul mesajelor — il taiem. const rawLid = sock.user?.lid || sock.authState?.creds?.me?.lid || null; ownLid = rawLid ? `${rawLid.split(':')[0].split('@')[0]}@lid` : null; console.log(`[whatsapp] Connected as ${phoneNumber} (lid: ${ownLid || 'n/a'}, self-chat-only mode: ${TEST_MODE_SELF_CHAT_ONLY})`); } if (connection === 'close') { connected = false; phoneNumber = null; ownLid = 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 dir and restart to re-link'); } } }); sock.ev.on('messages.upsert', ({ messages, type }) => { if (type !== 'notify') return; for (const msg of messages) { if (msg.key.remoteJid === 'status@broadcast') continue; const isGroup = msg.key.remoteJid.endsWith('@g.us'); const isSelfChat = (ownJid && msg.key.remoteJid === ownJid) || (ownLid && msg.key.remoteJid === ownLid); // Mesajele aruncate se logheaza. Fara asta, un JID care nu se potriveste cu // ownJid (ex. formatul @lid) face bridge-ul sa taca fara nicio urma in log. if (TEST_MODE_SELF_CHAT_ONLY && !isSelfChat) { console.log(`[whatsapp] ignorat (nu e self-chat): remoteJid=${msg.key.remoteJid} ownJid=${ownJid} ownLid=${ownLid}`); continue; } if (msg.key.fromMe && !isGroup && !isSelfChat) { console.log(`[whatsapp] ignorat (fromMe, nu e self-chat): remoteJid=${msg.key.remoteJid}`); continue; } const text = msg.message?.conversation || msg.message?.extendedTextMessage?.text; if (!text) { console.log(`[whatsapp] ignorat (fara text): remoteJid=${msg.key.remoteJid} tipuri=${Object.keys(msg.message || {}).join(',')}`); 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)}`); } }); } const app = express(); app.use(express.json({ limit: '50mb' })); app.get('/status', (_req, res) => { res.json({ connected, phone: phoneNumber, selfChatOnly: TEST_MODE_SELF_CHAT_ONLY, lid: ownLid, qr: connected ? null : currentQR, }); }); app.post('/pair', async (req, res) => { if (connected) { return res.json({ error: 'already connected' }); } const { phone } = req.body || {}; if (!phone) { return res.status(400).json({ error: 'missing "phone" in body' }); } if (!sock) { return res.status(503).json({ error: 'socket not ready yet, try again in a few seconds' }); } try { const code = await sock.requestPairingCode(phone.replace(/\D/g, '')); currentPairingCode = code; console.log(`[whatsapp] Pairing code for ${phone}: ${code}`); res.json({ ok: true, code }); } catch (err) { console.error('[whatsapp] Pairing code error:', err.message); res.status(500).json({ error: err.message }); } }); app.get('/pair-code', (_req, res) => { if (connected) return res.json({ error: 'already connected' }); if (!currentPairingCode) return res.json({ error: 'no pairing code yet — POST /pair first' }); res.json({ code: currentPairingCode }); }); app.get('/qr', (_req, res) => { if (connected) { return res.json({ error: 'already connected' }); } if (!currentQR) { return res.json({ error: 'no QR code available yet' }); } 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 }); }); 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); }); }); 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); }); setTimeout(() => process.exit(1), 5000); } process.on('SIGTERM', () => shutdown('SIGTERM')); process.on('SIGINT', () => shutdown('SIGINT'));