const { Client, LocalAuth } = require('whatsapp-web.js'); const qrcode = require('qrcode'); const qrcodeTerminal = require('qrcode-terminal'); const fs = require('fs'); const path = require('path'); const config = require('./config'); const { sendSMS } = require('./sms'); const { sendTelegramPhoto } = require('./telegram'); let messageQueue = []; let flushTimer = null; let flushTimerStart = null; let client = null; let restartDelay = 1000; let starting = false; let restarting = false; let msgCounter = 0; let startTime = Date.now(); let totalForwarded = 0; let userStats = {}; let groupStats = {}; function ts() { return new Date().toLocaleString('he-IL', { hour12: false }); } function log(level, msg) { console.log(`[${ts()}] [${level}] ${msg}`); } function flushTime() { if (!flushTimerStart) return '--:--'; const t = new Date(Date.now() + Math.max(0, config.batch.intervalMs - (Date.now() - flushTimerStart))); return t.toLocaleTimeString('he-IL', { hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false }); } function formatBatch(queue) { const groups = {}; for (const m of queue) { if (!groups[m.group]) groups[m.group] = []; groups[m.group].push(m); } const parts = []; for (const [group, msgs] of Object.entries(groups)) { parts.push(`[👥 ${group} 👥]`); for (const m of msgs) { parts.push(`${m.sender}: ${m.text}`); } } return parts.join('\n'); } function queueSize() { return messageQueue.reduce( (sum, m) => sum + m.group.length + m.sender.length + m.text.length + 6, 0 ); } function scheduleFlush() { if (flushTimer) clearTimeout(flushTimer); if (!flushTimerStart) flushTimerStart = Date.now(); flushTimer = setTimeout(flushQueue, config.batch.intervalMs); } async function flushQueue() { if (restarting) return; if (messageQueue.length === 0) return; flushTimer = null; flushTimerStart = null; const batch = messageQueue; messageQueue = []; msgCounter = 0; const text = formatBatch(batch); try { await sendSMS(text); log('INFO', `Flushed ${batch.length} messages`); for (const m of batch) { userStats[m.sender] = (userStats[m.sender] || 0) + 1; groupStats[m.group] = (groupStats[m.group] || 0) + 1; totalForwarded++; } } catch (err) { log('ERROR', `Flush failed: ${err.message}`); messageQueue = batch.concat(messageQueue); msgCounter = messageQueue.length; scheduleFlush(); } } function enqueue(group, sender, text) { msgCounter++; messageQueue.push({ group, sender, text }); scheduleFlush(); log('QUEUE', `Queue #${msgCounter} - Message from ${sender}, flushed at ${flushTime()}`); if (queueSize() >= config.batch.maxChars) { clearTimeout(flushTimer); flushTimer = null; flushTimerStart = null; flushQueue(); } } async function killClient() { if (!client) return; try { client.removeAllListeners(); } catch {} try { await client.destroy(); } catch {} /** * IMPORTANT: * Give Chromium time to release Windows file locks */ await new Promise(r => setTimeout(r, 3000)); client = null; } let keepAliveTimer = null; function startKeepAlive() { if (keepAliveTimer) clearInterval(keepAliveTimer); const ka = config.keepAlive; if (!ka || !ka.url) return; log('INIT', `Keep-alive ping every ${ka.intervalMs / 1000}s to ${ka.url}`); const ping = () => { fetch(ka.url).catch(() => {}); }; ping(); keepAliveTimer = setInterval(ping, ka.intervalMs); } async function startClient() { if (starting) { log('WARN', 'Already starting — skipping duplicate call'); return; } starting = true; try { restarting = true; log('INIT', 'Starting OmegaBaSMS...'); await killClient(); log('INIT', `Groups to monitor: ${config.groupNames.join(', ')}`); log('INIT', `Batch interval: ${config.batch.intervalMs / 1000}s / max ${config.batch.maxChars} chars`); log('INIT', `Forwarding SMS to: ${config.smsGateway.recipientNumber}`); if (config.telegram.botToken) { log('INIT', 'Telegram notifications enabled'); } // startKeepAlive(); log('INIT', 'Launching WhatsApp Web...'); client = new Client({ authStrategy: new LocalAuth(), puppeteer: { headless: true, args: ['--no-sandbox', '--disable-setuid-sandbox'] } }); client.on('qr', async (qr) => { log('QR', 'New QR code received — scan with WhatsApp on your phone'); qrcodeTerminal.generate(qr, { small: true }); if (config.telegram.botToken) { try { const buf = await qrcode.toBuffer(qr, { width: 400 }); await sendTelegramPhoto(buf, 'WhatsApp re-auth needed - scan this QR\nhttps://wa.me/settings/linked_devices'); log('QR', 'QR photo sent to Telegram'); } catch (err) { log('ERROR', `Failed to send QR photo: ${err.message}`); } } }); client.on('ready', () => { restarting = false; restartDelay = 1000; log('READY', 'WhatsApp connected successfully'); log('READY', `Monitoring ${config.groupNames.length} group(s): ${config.groupNames.join(', ')}`); log('READY', `Forwarding to ${config.smsGateway.recipientNumber}`); }); client.on('auth_failure', (msg) => { log('ERROR', `Auth failure: ${msg}`); }); client.on('disconnected', async (reason) => { if (starting) return; log('WARN', `Disconnected: ${reason}. Restarting in ${restartDelay / 1000}s...`); restarting = true; if (reason === 'LOGOUT') { const authDir = path.join(__dirname, '.wwebjs_auth'); if (fs.existsSync(authDir)) { try { fs.rmSync(authDir, { recursive: true, force: true, maxRetries: 10, retryDelay: 500 }); log('WARN', 'Cleared old session data'); } catch (err) { log('ERROR', `Failed clearing auth data: ${err.message}`); } } } await new Promise((r) => setTimeout(r, restartDelay)); restartDelay = Math.min(restartDelay * 2, 30000); startClient(); }); client.on('message_create', async (message) => { try { if (restarting) return; if (message.type !== 'chat') return; const chat = await message.getChat(); if (!chat.isGroup) return; if (!config.groupNames.includes(chat.name)) return; if (message.fromMe && !config.includeOwnMessages) return; const contact = await message.getContact(); const sender = message.fromMe ? config.ownName : (contact.name || contact.pushname || contact.shortName || contact.number || 'Unknown'); const body = message.body || (message.hasMedia ? '[Media]' : ''); if (!body) return; enqueue(chat.name, sender, body); } catch (err) { log('ERROR', `Message handler: ${err.message}`); } }); client.initialize(); } finally { starting = false; } } const http = require('http'); function renderDashboard() { const uptime = Math.floor((Date.now() - startTime) / 1000); const h = Math.floor(uptime / 3600); const m = Math.floor((uptime % 3600) / 60); const s = uptime % 60; const uptimeStr = `${h}h ${m}m ${s}s`; const userRows = Object.entries(userStats) .sort((a, b) => b[1] - a[1]) .map(([name, count]) => `
WhatsApp group messages forwarded via SMS
| Name | Messages |
|---|
| Group | Messages |
|---|