const { Client, LocalAuth } = require('whatsapp-web.js'); const qrcode = require('qrcode-terminal'); const qrcodeLib = require('qrcode'); const cron = require('node-cron'); const http = require('http'); const fs = require('fs'); const path = require('path'); const CONTACTS_PATH = path.join(__dirname, '..', 'config', 'contacts.json'); const SETTINGS_PATH = path.join(__dirname, '..', 'config', 'settings.json'); const DATA_PATH = path.join(__dirname, '..', 'data'); let currentQR = null; let isReady = false; function loadSettings() { return JSON.parse(fs.readFileSync(SETTINGS_PATH, 'utf8')); } function loadContacts() { return JSON.parse(fs.readFileSync(CONTACTS_PATH, 'utf8')); } // Gibt den Kontakt zurück der im angegebenen Monat dran ist (wiederholt sich jedes Jahr). function getContactForMonth(year, month) { const settings = loadSettings(); const contacts = loadContacts(); const schedule = settings.schedule; if (!Array.isArray(schedule) || schedule.length !== 12) { console.log('schedule in settings.json muss ein Array mit 12 Einträgen sein.'); return null; } const contactId = schedule[month - 1]; if (!contactId) { console.log(`Monat ${month}/${year}: kein Kontakt (null) — nichts zu tun.`); return null; } const contact = contacts.find(c => c.id === contactId); if (!contact) { console.log(`Kontakt-ID "${contactId}" nicht in contacts.json gefunden.`); return null; } return contact; } async function sendReminders(overrideYear = null, overrideMonth = null) { const settings = loadSettings(); const now = new Date(); const year = overrideYear ?? now.getFullYear(); const month = overrideMonth ?? (now.getMonth() + 1); const monthName = new Date(year, month - 1, 1).toLocaleString('de-DE', { month: 'long' }); const contact = getContactForMonth(year, month); if (!contact) { return { skipped: true, reason: `Kein Kontakt für ${monthName} ${year}` }; } const vorname = contact.name.split(' ')[0]; const message = settings.message .replace(/{vorname}/g, vorname) .replace(/{name}/g, contact.name) .replace(/{amount}/g, settings.amount ?? '') .replace(/{currency}/g, settings.currency ?? '') .replace(/{month}/g, monthName) .replace(/{year}/g, year); const phone = contact.phone.replace(/\D/g, ''); const chatId = `${phone}@c.us`; console.log(`[${monthName} ${year}] Sende an: ${contact.name} (${contact.phone})`); try { await client.sendMessage(chatId, message); console.log(`[OK] Nachricht gesendet.`); return { sent: true, contact: contact.name }; } catch (err) { console.error(`[FEHLER] ${err.message}`); return { sent: false, error: err.message }; } } // Web-Interface const server = http.createServer(async (req, res) => { const url = new URL(req.url, 'http://localhost'); if (url.pathname === '/qr') { if (!currentQR) { res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' }); return res.end(htmlPage('Status', isReady ? '

✓ Verbunden und bereit

Erinnerung jetzt senden

' : '

Kein QR-Code verfügbar — Client startet noch, bitte kurz warten...

' )); } const qrDataUrl = await qrcodeLib.toDataURL(currentQR); res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' }); return res.end(htmlPage('QR-Code scannen', `

Öffne WhatsApp → Verknüpfte GeräteGerät verknüpfen

Seite neu laden falls der Code abgelaufen ist.

`)); } if (url.pathname === '/send-now') { if (!isReady) { res.writeHead(503, { 'Content-Type': 'text/html; charset=utf-8' }); return res.end(htmlPage('Nicht bereit', '

Client ist noch nicht verbunden. Erst QR-Code scannen.

')); } // Optionale Query-Parameter: ?year=2026&month=5 const year = url.searchParams.has('year') ? parseInt(url.searchParams.get('year')) : null; const month = url.searchParams.has('month') ? parseInt(url.searchParams.get('month')) : null; const result = await sendReminders(year, month); res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' }); if (result.skipped) { return res.end(htmlPage('Übersprungen', `

⏭ ${result.reason}

Zurück

`)); } return res.end(htmlPage(result.sent ? 'Gesendet' : 'Fehler', ` ${result.sent ? `

✓ Nachricht an ${result.contact} gesendet

` : `

✗ Fehler: ${result.error}

`}

Zurück

`)); } if (url.pathname === '/status') { res.writeHead(200, { 'Content-Type': 'application/json' }); return res.end(JSON.stringify({ ready: isReady, qr_pending: !!currentQR })); } // Startseite mit Monatsübersicht let scheduleHtml = ''; try { const settings = loadSettings(); const contacts = loadContacts(); const now = new Date(); const thisYear = now.getFullYear(); const thisMonth = now.getMonth() + 1; const monthNames = ['', 'Januar','Februar','März','April','Mai','Juni','Juli','August','September','Oktober','November','Dezember']; const plan = settings.schedule ?? []; const rows = plan.map((id, i) => { const m = i + 1; const contact = id ? contacts.find(c => c.id === id) : null; const isCurrent = m === thisMonth; return ` ${monthNames[m]} ${contact ? contact.name : '—'} ${isCurrent ? '← aktueller Monat' : ''} `; }).join(''); scheduleHtml = `

Plan ${thisYear}

${rows}
`; } catch (e) { scheduleHtml = `

Fehler beim Laden des Plans: ${e.message}

`; } res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' }); res.end(htmlPage('WhatsApp Reminder', `

Status: ${isReady ? '✓ Verbunden' : currentQR ? 'QR-Code scannen' : 'Startet...'}

▶ Erinnerung für diesen Monat jetzt senden

${scheduleHtml} `)); }); function htmlPage(title, body) { return ` ${title} – WhatsApp Reminder

${title}

${body}`; } server.listen(3000, () => { console.log('Web-Interface: http://localhost:3000'); console.log('QR-Code: http://localhost:3000/qr'); }); // WhatsApp Client const client = new Client({ authStrategy: new LocalAuth({ dataPath: DATA_PATH }), puppeteer: { executablePath: process.env.PUPPETEER_EXECUTABLE_PATH || '/usr/bin/chromium', args: [ '--no-sandbox', '--disable-setuid-sandbox', '--disable-dev-shm-usage', '--disable-accelerated-2d-canvas', '--no-first-run', '--no-zygote', '--single-process', '--disable-gpu' ] } }); client.on('qr', (qr) => { currentQR = qr; console.log('\n>>> QR-Code bereit: http://localhost:3000/qr <<<'); qrcode.generate(qr, { small: true }); }); client.on('authenticated', () => { currentQR = null; console.log('Authentifiziert.'); }); client.on('ready', () => { isReady = true; const settings = loadSettings(); const schedule = settings.cron || '0 9 1 * *'; const tz = settings.timezone || 'Europe/Zurich'; cron.schedule(schedule, async () => { const now = new Date(); console.log(`[CRON] ${now.toLocaleString('de-DE')} — prüfe Monatsplan...`); await sendReminders(); }, { timezone: tz }); console.log(`WhatsApp bereit. Cron: "${schedule}" (${tz})`); }); client.on('disconnected', (reason) => { console.log('Verbindung getrennt:', reason); isReady = false; process.exit(1); }); client.initialize();