diff --git a/.env.example b/.env.example index ce6f5ab..84dd46e 100644 --- a/.env.example +++ b/.env.example @@ -16,3 +16,14 @@ MAX_REQUESTS_PER_DAY=5 # Admin notifications ADMIN_NUMBER=31612345678@c.us + +# Telegram watchdog (fallback when WhatsApp API is down) +TELEGRAM_BOT_URL=http://localhost:3004 + +# Telegram watchdog configuration (telegram-bot process) +TELEGRAM_BOT_TOKEN= +TELEGRAM_ADMIN_CHAT_ID= +TELEGRAM_BOT_PORT=3004 +DIENST_BOT_HEALTH_URL=http://localhost:3002/health +WWEBJS_API_SESSION_URL=http://192.168.5.252:3001/session/status/EdGpt +HEALTH_CHECK_INTERVAL_MS=300000 diff --git a/docs/superpowers/plans/2026-07-12-telegram-watchdog.md b/docs/superpowers/plans/2026-07-12-telegram-watchdog.md new file mode 100644 index 0000000..57702bd --- /dev/null +++ b/docs/superpowers/plans/2026-07-12-telegram-watchdog.md @@ -0,0 +1,202 @@ +# Telegram Watchdog Implementation Plan + +> **For agentic workers:** Stappen gebruiken checkbox (`- [ ]`) syntax voor tracking. + +**Goal:** Apart Telegram bot process dat de admin waarschuwt als WhatsApp API of dienst-bot plat ligt. + +**Architecture:** `telegramBot.js` draait als apart systemd process op nextcloud, gebruikt Telegram Bot API via axios. Dienst-bot roept `POST /notify` aan als WhatsApp notificatie faalt. Periodieke health checks naar beide services. + +**Tech Stack:** Node.js, Express, axios, dotenv (bestaande dependencies, geen nieuwe) + +--- + +### Task 1: Create `src/telegramBot.js` + +- [ ] **Create `src/telegramBot.js`** + +```js +require('dotenv').config() +const express = require('express') +const axios = require('axios') + +const BOT_TOKEN = process.env.TELEGRAM_BOT_TOKEN +const ADMIN_CHAT_ID = process.env.TELEGRAM_ADMIN_CHAT_ID +const PORT = process.env.TELEGRAM_BOT_PORT || 3004 +const DIENST_BOT_HEALTH = process.env.DIENST_BOT_HEALTH_URL || 'http://localhost:3002/health' +const WWEBJS_SESSION = process.env.WWEBJS_API_SESSION_URL || 'http://192.168.5.252:3001/session/status/EdGpt' +const CHECK_INTERVAL = parseInt(process.env.HEALTH_CHECK_INTERVAL_MS || '300000', 10) + +const app = express() +app.use(express.json()) + +let dienstBotOk = true +let wwebjsOk = true + +function sendTelegram(text) { + return axios.post(`https://api.telegram.org/bot${BOT_TOKEN}/sendMessage`, { + chat_id: ADMIN_CHAT_ID, + text, + parse_mode: 'Markdown' + }, { timeout: 10000 }) +} + +async function startupNotification() { + try { + await sendTelegram('✅ Telegram watchdog online — gezondheidschecks actief') + console.log('Startup notification sent') + } catch (err) { + console.error('Failed to send startup notification:', err.message) + } +} + +async function checkHealth() { + let currentDienstBotOk = true + let currentWwebjsOk = true + + try { + await axios.get(DIENST_BOT_HEALTH, { timeout: 5000 }) + } catch { + currentDienstBotOk = false + } + + try { + const res = await axios.get(WWEBJS_SESSION, { timeout: 5000 }) + currentWwebjsOk = res.data?.state === 'CONNECTED' + } catch { + currentWwebjsOk = false + } + + if (dienstBotOk !== currentDienstBotOk) { + const icon = currentDienstBotOk ? '✅' : '❌' + const status = currentDienstBotOk ? 'weer online' : 'niet bereikbaar' + try { + await sendTelegram(`${icon} dienst-bot ${status}`) + } catch (err) { + console.error('Failed to send dienst-bot status:', err.message) + } + dienstBotOk = currentDienstBotOk + } + + if (wwebjsOk !== currentWwebjsOk) { + const icon = currentWwebjsOk ? '✅' : '❌' + const status = currentWwebjsOk ? 'weer online' : 'niet bereikbaar' + try { + await sendTelegram(`${icon} wwebjs-api ${status}`) + } catch (err) { + console.error('Failed to send wwebjs-api status:', err.message) + } + wwebjsOk = currentWwebjsOk + } + + console.log(`Health check: dienst-bot=${currentDienstBotOk ? 'UP' : 'DOWN'}, wwebjs-api=${currentWwebjsOk ? 'UP' : 'DOWN'}`) +} + +app.post('/notify', async (req, res) => { + const { text } = req.body + if (!text) { + return res.status(400).json({ success: false, error: 'missing text' }) + } + try { + await sendTelegram(`⚠️ ${text}`) + res.json({ success: true }) + } catch (err) { + console.error('Failed to forward notification:', err.message) + res.status(500).json({ success: false, error: err.message }) + } +}) + +app.get('/health', (req, res) => { + res.json({ status: 'ok', services: { dienstBot: dienstBotOk, wwebjsApi: wwebjsOk } }) +}) + +startupNotification() +setInterval(checkHealth, CHECK_INTERVAL) +checkHealth() + +app.listen(PORT, () => { + console.log(`telegram-bot listening on port ${PORT}`) +}) +``` + +- [ ] **Update `.env.example` — add Telegram variables** + +``` +# Telegram watchdog +TELEGRAM_BOT_TOKEN= +TELEGRAM_ADMIN_CHAT_ID= +TELEGRAM_BOT_PORT=3004 +DIENST_BOT_HEALTH_URL=http://localhost:3002/health +WWEBJS_API_SESSION_URL=http://192.168.5.252:3001/session/status/EdGpt +HEALTH_CHECK_INTERVAL_MS=300000 +``` + +### Task 2: Update `src/whatsappApi.js` — add Telegram fallback + +- [ ] **Modify `notifyAdmin()` in `src/whatsappApi.js`** + +Replace: +```js +async function notifyAdmin(errorText) { + const adminNumber = process.env.ADMIN_NUMBER + if (!adminNumber) { + console.error('ADMIN_NUMBER not configured, skipping admin notification') + return + } + try { + await sendTextMessage(adminNumber, `[dienst-bot fout] ${errorText}`) + } catch (err) { + console.error('Failed to notify admin:', err.message) + } +} +``` + +With: +```js +async function notifyAdmin(errorText) { + const adminNumber = process.env.ADMIN_NUMBER + if (!adminNumber) { + console.error('ADMIN_NUMBER not configured, skipping admin notification') + return + } + try { + await sendTextMessage(adminNumber, `[dienst-bot fout] ${errorText}`) + } catch (err) { + console.error('WhatsApp admin notification failed, falling back to Telegram:', err.message) + try { + const telegramUrl = process.env.TELEGRAM_BOT_URL + if (telegramUrl) { + await axios.post(`${telegramUrl}/notify`, { text: errorText }, { timeout: 5000 }) + } + } catch (telegramErr) { + console.error('Telegram notification also failed:', telegramErr.message) + } + } +} +``` + +- [ ] **Update `.env.example` — add TELEGRAM_BOT_URL to dienst-bot section** + +``` +# Telegram watchdog (fallback when WhatsApp API is down) +TELEGRAM_BOT_URL=http://localhost:3004 +``` + +### Task 3: Update AGENTS.md + +- [ ] **Add telegram-bot to tables and commands** + +Add rows: +- Systemd service: `telegram-bot.service` +- Poort: `3004` +- Health check: `curl http://localhost:3004/health` +- Commands for status/restart/logs + +- [ ] **Update release-proces** — add step for restarting telegram-bot + +### Task 4: Deploy & verify on nextcloud + +- [ ] `ssh aiaiaiaiagent@nextcloud` — create systemd service +- [ ] Add TELEGRAM_* vars to .env on nextcloud +- [ ] `sudo systemctl daemon-reload && sudo systemctl enable --now telegram-bot` +- [ ] Check status & logs +- [ ] Update release-proces: rsync + restart beide services diff --git a/docs/superpowers/specs/2026-07-12-telegram-watchdog-design.md b/docs/superpowers/specs/2026-07-12-telegram-watchdog-design.md new file mode 100644 index 0000000..68c6da6 --- /dev/null +++ b/docs/superpowers/specs/2026-07-12-telegram-watchdog-design.md @@ -0,0 +1,135 @@ +# Telegram Watchdog — Design Document + +## Doel + +Een apart Telegram bot proces dat de admin waarschuwt wanneer de WhatsApp API (wwebjs-api) of de dienst-bot zelf niet bereikbaar is. Dit is het enige communicatiekanaal dat overblijft als WhatsApp uitvalt. + +## Architectuur + +``` + ┌──────────────────┐ + │ dienst-bot │ Node.js (nextcloud:3002) + │ (Express) │ + └──────┬───────────┘ + │ POST /notify (alleen bij falen WhatsApp) + │ + ┌──────▼───────────┐ + │ telegram-bot │ Node.js (nextcloud:3003) + │ (Express) │ + │ │ + │ ─── health check │ dienst-bot /health (elke 5min) + │ ─── health check │ wwebjs-api /session/status (elke 5min) + │ ─── Telegram API │ verstuur notificaties naar admin + └──────────────────┘ +``` + +### Twee processen + +- **dienst-bot** (bestaand, ongewijzigd behalve `notifyAdmin`): stuurt bij fouten eerst WhatsApp, als dat faalt → Telegram via POST /notify +- **telegram-bot** (nieuw): ontvangt notificaties van dienst-bot, voert zelfstandig health checks uit, stuurt Telegram berichten + +### Geen nieuwe npm packages + +Zowel dienst-bot als telegram-bot gebruiken alleen `axios` en `dotenv` — beide al in gebruik. + +## telegram-bot — specificatie + +### Endpoints + +| Endpoint | Doel | +|----------|------| +| `POST /notify` | Ontvangt notificaties van dienst-bot (alleen als WhatsApp faalde) | +| `GET /health` | Health check voor systemd | + +**POST /notify body:** +```json +{ + "text": "Kan wwebjs-api niet bereiken na 3 pogingen (ECONNREFUSED)" +} +``` + +### Health checks (periodiek) + +Elke 5 minuten worden beide endpoints gecheckt: + +1. `GET http://localhost:3002/health` — dienst-bot +2. `GET http://192.168.5.252:3001/session/status/EdGpt` — wwebjs-api + +Statusovergangen triggeren een notificatie: +- **down → up**: "✅ dienst-bot is weer online" +- **up → down**: "❌ dienst-bot niet bereikbaar (ECONNREFUSED)" + +### Notificaties bij opstart + +Bij het opstarten stuurt telegram-bot direct een bericht: +"✅ Telegram watchdog online — gezondheidschecks aktief" + +### .env variabelen (telegram-bot) + +``` +TELEGRAM_BOT_TOKEN=123456:ABC-DEF1234 +TELEGRAM_ADMIN_CHAT_ID=123456789 +TELEGRAM_BOT_PORT=3003 +DIENST_BOT_HEALTH_URL=http://localhost:3002/health +WWEBJS_API_SESSION_URL=http://192.168.5.252:3001/session/status/EdGpt +HEALTH_CHECK_INTERVAL_MS=300000 +``` + +## Wijzigingen aan dienst-bot + +### whatsappApi.js — notifyAdmin() + +```js +async function notifyAdmin(errorText) { + // WhatsApp eerst (als de API werkt) + try { + await sendTextMessage(adminNumber, `[dienst-bot fout] ${errorText}`) + } catch (err) { + // WhatsApp faalt (API down) → Telegram backup + await notifyTelegram(errorText) + } +} + +async function notifyTelegram(text) { + const url = process.env.TELEGRAM_BOT_URL + if (!url) return + try { + await axios.post(`${url}/notify`, { text }, { timeout: 5000 }) + } catch (err) { + console.error('Telegram notification failed:', err.message) + } +} +``` + +### Nieuwe .env variabele (dienst-bot) + +``` +TELEGRAM_BOT_URL=http://localhost:3003 +``` + +## Systemd service + +Bestand: `/etc/systemd/system/telegram-bot.service` + +- Working directory: `/home/aiaiaiaiagent/whatsapp-scripts/dienst_echo_whatsappgroep` +- ExecStart: `/usr/bin/node src/telegramBot.js` +- User: `aiaiaiaiagent` +- Depends: dienst-bot.service (After= dienst-bot.service) +- Restart: always + +## Bestanden in de repo + +| Bestand | Doel | +|---------|------| +| `src/telegramBot.js` | Telegram watchdog process (Express server) | +| `.env.example` | Nieuwe TELEGRAM_* variabelen toevoegen | +| `docs/superpowers/specs/2026-07-12-telegram-watchdog-design.md` | Dit document | + +Geen wijzigingen aan `package.json` — beide processen gebruiken dezelfde dependencies. + +## Error handling + +- telegram-bot faalt: dienst-bot logt error, verder geen impact op WhatsApp verkeer +- dienst-bot faalt: telegram-bot detecteert via health check en stuurt notificatie +- telegram-bot zelf fault: systemd restart (Restart=always) +- Telegram API timeout: loggen en negeren (geen retry nodig) diff --git a/src/telegramBot.js b/src/telegramBot.js new file mode 100644 index 0000000..ff27557 --- /dev/null +++ b/src/telegramBot.js @@ -0,0 +1,101 @@ +require('dotenv').config() +const express = require('express') +const axios = require('axios') + +const BOT_TOKEN = process.env.TELEGRAM_BOT_TOKEN +const ADMIN_CHAT_ID = process.env.TELEGRAM_ADMIN_CHAT_ID +const PORT = process.env.TELEGRAM_BOT_PORT || 3004 +const DIENST_BOT_HEALTH = process.env.DIENST_BOT_HEALTH_URL || 'http://localhost:3002/health' +const WWEBJS_SESSION = process.env.WWEBJS_API_SESSION_URL || 'http://192.168.5.252:3001/session/status/EdGpt' +const CHECK_INTERVAL = parseInt(process.env.HEALTH_CHECK_INTERVAL_MS || '300000', 10) + +const app = express() +app.use(express.json()) + +let dienstBotOk = true +let wwebjsOk = true + +function sendTelegram(text) { + return axios.post(`https://api.telegram.org/bot${BOT_TOKEN}/sendMessage`, { + chat_id: ADMIN_CHAT_ID, + text, + parse_mode: 'Markdown' + }, { timeout: 10000 }) +} + +async function startupNotification() { + try { + await sendTelegram('✅ Telegram watchdog online — gezondheidschecks actief') + console.log('Startup notification sent') + } catch (err) { + console.error('Failed to send startup notification:', err.message) + } +} + +async function checkHealth() { + let currentDienstBotOk = true + let currentWwebjsOk = true + + try { + await axios.get(DIENST_BOT_HEALTH, { timeout: 5000 }) + } catch { + currentDienstBotOk = false + } + + try { + const res = await axios.get(WWEBJS_SESSION, { timeout: 5000 }) + currentWwebjsOk = res.data?.state === 'CONNECTED' + } catch { + currentWwebjsOk = false + } + + if (dienstBotOk !== currentDienstBotOk) { + const icon = currentDienstBotOk ? '✅' : '❌' + const status = currentDienstBotOk ? 'weer online' : 'niet bereikbaar' + try { + await sendTelegram(`${icon} dienst-bot ${status}`) + } catch (err) { + console.error('Failed to send dienst-bot status:', err.message) + } + dienstBotOk = currentDienstBotOk + } + + if (wwebjsOk !== currentWwebjsOk) { + const icon = currentWwebjsOk ? '✅' : '❌' + const status = currentWwebjsOk ? 'weer online' : 'niet bereikbaar' + try { + await sendTelegram(`${icon} wwebjs-api ${status}`) + } catch (err) { + console.error('Failed to send wwebjs-api status:', err.message) + } + wwebjsOk = currentWwebjsOk + } + + console.log(`Health check: dienst-bot=${currentDienstBotOk ? 'UP' : 'DOWN'}, wwebjs-api=${currentWwebjsOk ? 'UP' : 'DOWN'}`) +} + +app.post('/notify', async (req, res) => { + const { text } = req.body + if (!text) { + return res.status(400).json({ success: false, error: 'missing text' }) + } + try { + await sendTelegram(`⚠️ ${text}`) + res.json({ success: true }) + } catch (err) { + console.error('Failed to forward notification:', err.message) + res.status(500).json({ success: false, error: err.message }) + } +}) + +app.get('/health', (req, res) => { + res.json({ status: 'ok', services: { dienstBot: dienstBotOk, wwebjsApi: wwebjsOk } }) +}) + +startupNotification() +setInterval(checkHealth, CHECK_INTERVAL) +checkHealth() + +app.listen(PORT, () => { + console.log(`telegram-bot listening on port ${PORT}`) +}) diff --git a/src/whatsappApi.js b/src/whatsappApi.js index 72b29dd..b4eb39d 100644 --- a/src/whatsappApi.js +++ b/src/whatsappApi.js @@ -73,7 +73,15 @@ async function notifyAdmin(errorText) { try { await sendTextMessage(adminNumber, `[dienst-bot fout] ${errorText}`) } catch (err) { - console.error('Failed to notify admin:', err.message) + console.error('WhatsApp admin notification failed, falling back to Telegram:', err.message) + try { + const telegramUrl = process.env.TELEGRAM_BOT_URL + if (telegramUrl) { + await axios.post(`${telegramUrl}/notify`, { text: errorText }, { timeout: 5000 }) + } + } catch (telegramErr) { + console.error('Telegram notification also failed:', telegramErr.message) + } } }