diff --git a/.gitignore b/.gitignore index 7ff995e..9f89e61 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,4 @@ node_modules/ .env -AGENTS.md diensten/ diensten_*/ diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..77b3228 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,260 @@ +# Dienst Bot — Productie-informatie + +## 🚨 VERPLICHT: bridgestore is GEEN waarheid over bezorging + +De wwebjs-api **bridgestore is niet betrouwbaar** als bewijs dat een bericht bezorgd/gelezen +is. Bevestigd 2026-09-24: een dienst-PNG werd 3× naar dezelfde ontvanger "verzonden" +(success + message-ID), de store toonde 3 berichten **allemaal ack 3**, maar de ontvanger +zag in de chat maar **één** bericht (ghost-sends: id geretourneerd, nooit echt zichtbaar). + +- **`/chat/fetchMessages`-acks en de lokale message-store van wwebjs-api zijn een lokale + registratie, géén bezorgbewijs.** Ze kunnen ack 1/2/3 tonen voor berichten die nooit + aankwamen. +- **Enige geldige bewijzen van levering:** (1) de **live `message_ack`-progressie** zoals + die vlak ná verzending via de webhook binnenkomt; (2) de **zichtbare werkelijkheid op + het apparaat** van de ontvanger/betrokkene. +- **Trek NOOIT een leveringsconclusie uit een later gelezen fetchMessages-ack.** Claim geen + "bezorgd/gelezen" op basis van de store — dat geeft verkeerde adviezen. +- Dit is de reden dat ack-tracking bestaat: het hangt af van de live events, niet van de + store. + +## Waar draait de bot? + +De bot draait op de **nextcloud** server (192.168.5.252) als **systemd service**. + +| Server | Locatie | +|--------|---------| +| nextcloud (192.168.5.252) | systemd service `dienst-bot.service` | +| nextcloud (192.168.5.252) | systemd service `telegram-bot.service` | +| ~~MacMini~~ | ~~LaunchDaemon (verouderd, gestopt juli 2026)~~ | + +## Bestanden & paden (lokaal) + +| Wat | Pad | +|-----|-----| +| Source code | `/Users/eddy/src/dienst_echo_whatsappgroep/` | +| Environment (.env) | `/Users/eddy/src/dienst_echo_whatsappgroep/.env` | +| Script | `src/index.js` | +| Groep | `Dienst opvragen EdGPT-EBS` (`120363407613652082@g.us`) | + +## Nextcloud server (systemd) + +### Poort-overzicht + +| Poort | Service | Type | Beheer | +|-------|---------|------|--------| +| **3001** | wwebjs-api | Docker container | WhatsApp bridge | +| **3002** | dienst-bot | systemd service | Dienst opvragen bot (dit project) | +| **3003** | aankondigingen-webhook | systemd service | Losse webhook receiver (Eddy sessie) | +| **3004** | telegram-bot | systemd service | Telegram watchdog (dit project) | +| ~~3000~~ | Forgejo | systemd service | **NIET onze service!** Niet aanraken. | + +### dienst-bot + +| Wat | Waarde | +|-----|--------| +| Systemd service | `dienst-bot.service` | +| Working directory | `/home/aiaiaiaiagent/whatsapp-scripts/dienst_echo_whatsappgroep` | +| User | `aiaiaiaiagent` | +| Poort dienst-bot | `3002` | +| Poort telegram-bot | `3004` | +| Health check dienst-bot | `curl http://localhost:3002/health` | +| Health check telegram-bot | `curl http://localhost:3004/health` | + +### Commando's + +```bash +# Status +ssh aiaiaiaiagent@nextcloud "systemctl status dienst-bot" +ssh aiaiaiaiagent@nextcloud "systemctl status telegram-bot" + +# Herstarten +ssh aiaiaiaiagent@nextcloud "sudo systemctl restart dienst-bot" +ssh aiaiaiaiagent@nextcloud "sudo systemctl restart telegram-bot" + +# Logs +ssh aiaiaiaiagent@nextcloud "journalctl -u dienst-bot --since today" +ssh aiaiaiaiagent@nextcloud "journalctl -u telegram-bot --since today" +``` + +## WhatsApp API (wwebjs-api) + +De wwebjs-api draait als Docker container op nextcloud. + +| Wat | Waarde | +|-----|--------| +| Docker container | `wwebjs-api` | +| Image | `wwebjs-api-api` | +| URL | `http://192.168.5.252:3001` | +| Sessie dienst-bot | `EdGpt` | +| Sessie persoonlijk | `Eddy` | +| Webhook dienst-bot | `http://192.168.5.252:3002/webhook` | +| Webhook aankondigingen | `http://192.168.5.252:3003/webhook` (Eddy-sessie, niet dit project) | + +### Docker commando's + +```bash +ssh aiaiaiaiagent@nextcloud +docker ps --filter "name=wwebjs" # status +docker restart wwebjs-api # herstarten +docker logs wwebjs-api --tail 30 # logs +``` + +### Sessie status + +```bash +curl http://192.168.5.252:3001/session/status/EdGpt +# → {"success":true,"state":"CONNECTED","message":"session_connected"} +``` + +## Wekelijkse container restart + +De wwebjs-api container wordt elke **zondag om 02:00** automatisch herstart om vastlopers te voorkomen. + +| Wat | Waarde | +|-----|--------| +| Timer | `docker-restart-wwebjs.timer` | +| Service | `docker-restart-wwebjs.service` | +| Commando | `docker restart wwebjs-api` | +| Volgende run | check: `systemctl list-timers --no-pager \| grep wwebjs` | + +```bash +ssh aiaiaiaiagent@nextcloud "sudo systemctl list-timers --no-pager | grep wwebjs" +``` + +## Release-proces + +1. Code wijzigen lokaal in `/Users/eddy/src/dienst_echo_whatsappgroep/` +2. Commit + pushen naar remote: `git add -A && git commit -m "..." && git push origin main` +3. Git pull op nextcloud: + ```bash + ssh aiaiaiaiagent@nextcloud "cd ~/whatsapp-scripts/dienst_echo_whatsappgroep && git pull && npm ci" + ``` + NB: `diensten/`, `.env`, en `AGENTS.md` staan in `.gitignore` — die worden niet overschreven door `git pull`. + Alleen als `.env` gewijzigd is, sync deze expliciet (géén `--delete`, géén volledige rsync): + ```bash + rsync -avz /Users/eddy/src/dienst_echo_whatsappgroep/.env aiaiaiaiagent@nextcloud:/home/aiaiaiaiagent/whatsapp-scripts/dienst_echo_whatsappgroep/.env + ``` +4. Herstarten op nextcloud: + ```bash + ssh aiaiaiaiagent@nextcloud "sudo systemctl restart dienst-bot" + ssh aiaiaiaiagent@nextcloud "sudo systemctl restart telegram-bot" + ``` +5. Logs checken: + ```bash + ssh aiaiaiaiagent@nextcloud "journalctl -u dienst-bot --since '5 minutes ago'" + ssh aiaiaiaiagent@nextcloud "journalctl -u telegram-bot --since '5 minutes ago'" + ``` + +## Configuratie + +De `.env` bevat alle instellingen (niet in git). Template staat in `.env.example`. + +## Resilience maatregelen (juni 2026) + +Deze maatregelen zijn toegevoegd na een incident met HTTP 500 van de wwebjs-api en een EADDRINUSE restart-loop. + +### Retry-logic (`src/whatsappApi.js`) + +- `sendTextMessage` en `sendImageMessage` hebben ingebouwde retry met exponential backoff +- Maximaal 3 pogingen, met vertraging van 1s → 2s → 4s +- Wordt geactiveerd bij 5xx HTTP-fouten (500, 502, 503, 504) en connectiefouten (ECONNRESET, ECONNREFUSED, ETIMEDOUT) +- Niet-retryable fouten (4xx) worden direct doorgelaten + +### Port binding en graceful shutdown (`src/index.js`) + +- Gebruikt de Node.js HTTP server met `reuseAddr: true` om TIME_WAIT poortconflicten te voorkomen +- Bij `EADDRINUSE` wordt automatisch na 5 seconden opnieuw geprobeerd te binden +- **Graceful shutdown**: bij SIGTERM/SIGINT worden actieve connecties netjes afgesloten (max 10s timeout) + +### Ack-tracking voor hangende media (september 2026) + +Sommige mediabestand-verzendingen keren `success` + message-ID terug maar worden nooit bezorgd (blijft op één grijs vinkje). Bevinding 2026-09-24: dit is een **bestand × ontvanger-interactie**, geen sessie-probleem — hetzelfde bestand hing (ack 1) voor één ontvanger maar bezorgde (ack 3) bij de admin in dezelfde verse sessie, en hing ook ná een sessie-herstart. Daarom wordt er bij een ack-1-hang **geen sessie-herstart** meer gedaan. + +- `src/ackTracker.js` registreert elke media-verzending en volgt de `message_ack`-events (webhook `message_ack` in `messageHandler.js`). +- **ack ≥ 2** = bezorgd → tracking verwijderd. **Blijft > `ACK_STUCK_TIMEOUT_MS` (5 min) op ack < 2** → éénmalige admin-notificatie met bestandsnaam + chat + advies "opnieuw renderen + bezorgtest". Geen resend naar de aanvrager. +- **Pruning** na `ACK_PRUNED_AFTER_MS` (30 min); de check-loop is **activity-driven** (draait alleen bij openstaande tracking, geen polling in rust). +- Vereiste aan de bridge: `message_ack` staat sinds 2026-09-24 in wwebjs-api `.env` **niet** meer in `DISABLED_CALLBACKS`; container is daarvoor herstart. +- Env-vars: `ACK_STUCK_TIMEOUT_MS` (default 300000), `ACK_PRUNED_AFTER_MS` (default 1800000), `ACK_CHECK_INTERVAL_MS` (default 30000). +- **Media-spreiding:** `MEDIA_SEND_DELAY_MS` (default **500**) pauzeert tussen de pagina's van één + dienst (niet vóór de eerste). Doel: de wwebjs media-pipeline (één chromium-tab) per-verzending + lucht geven en gelijktijdige storage/upload-conflicten verminderen. 0 = uitschakelen. + +Logregels: `[ack] track ( → )`, `[ack] delivered ack= `, `[ack] pruned stale entries`. + +**Ack-semantiek (wwebjs 1.34.7):** ack 1 = verstuurd naar server (één grijs vinkje), ack 2 = bezorgd op apparaat (twee vinkjes), ack 3 = geopend/gelezen. Let op: eerdere notities die "één grijs vinkje (ack 2)" noemden, zaten er dus naast. + +## Media bezorgtest (afbeeldingen die WhatsApp weigert) + +Sommige dienst-PNG's worden wél verzonden (success + message-ID) maar komen niet als beeld +aan: één grijs vinkje (ack 2), nooit geopend (ack 3). **Niet te detecteren met een +bestandscheck** (formaat/CRC volledig geldig) — alleen de praktijktest vangt het. +Oorzaak-klasse: wwjs 1.34.7-image-pipeline-regressie (wwebjs #201772/#201732), inhoudsgebonden. + +**Bevinding 2026-09-24 (bestand × ontvanger):** de weigering is een interactie tussen +bestand en ontvanger, niet de sessie. Hetzelfde (geldige, decodeerbare) bestand hing op +ack 1 voor de ene ontvanger, terwijl de admin in dezelfde verse sessie ack 3 kreeg; ná een +sessie-herstart hing hetzelfde bestand opnieuw. Consequentie: **een sessie-herstart lost +een ack-1-hang niet op** en wordt sindsdien niet meer ingezet — wel detectie via +ack-tracking (zie hierboven), re-render via poppler en een verse bezorgtest. +**Ack-telling:** ack 1 = één grijs vinkje (niet bezorgd), ack 2 = bezorgd op apparaat, +ack 3 = geopend. (Eerdere notities "één grijs vinkje (ack 2)" zaten er dus naast.) + +**Na elke omruiling van de dienstenmap: de volledige media-test doorlopen vóór ingebruikname.** + +**Afbeeldingsformaat:** sinds `07b511e` leidt `sendImageMessage` de mimetype af uit de +extensie — `png`→`image/png`, `jpg`/`jpeg`→`image/jpeg` (onbekend→`image/png`). De bot is +dus voorbereid als de bron-repo overgaat naar JPEG; de `bestandsnaam` uit +`dienst-tijden.json` wordt al extensie-agnostisch ingelezen (`getPngFilePaths`). + +### Werkwijze (gedetailleerd: vault-notitie "Dienst media bezorgtest") + +1. Verzend alle png's gedoseerd naar de beheerderchat via het generieke script + `dienst-media-test.js` (server: `/home/aiaiaiaiagent/whatsapp-scripts/dienst-media-test.js`), + batch 20, wachttijd zodat de beheerder de chat opent. +2. Script meet acks (`ack≥3` = geaccepteerd, geïndexeerd in `dienst-media-index.json`; + idempotent — geaccepteerde worden nooit opnieuw verstuurd). +3. Hangende (`ack<3` na wachttijd) regenereren in volgorde: officiële pdf2png-pipeline → + **poppler** `pdftoppm -png -r 72` (bewezen) → gs → magick. Steekproefsgewijs visueel + controleren dat de juiste dienst op de pagina staat. +4. Her-testen tot ack 3; na max rondes → menselijke-inspectielijst. + +### Script-parameters (voor gebruik) + +```bash +ssh aiaiaiaiagent@nextcloud +node /home/aiaiaiaiagent/whatsapp-scripts/dienst-media-test.js \ + --map /home/aiaiaiaiagent/whatsapp-scripts/Whatsapp_Dienst/diensten/png \ + --json /home/aiaiaiaiagent/whatsapp-scripts/Whatsapp_Dienst/diensten/dienst-tijden.json \ + --chat 275749852475605@lid \ + --batch 20 --wacht-minuten 10 --rondes 3 +``` + +## Telegram Watchdog (`src/telegramBot.js`) + +Sinds juli 2026 draait er een apart telegram-bot proces als backup communicatiekanaal. + +| Wat | Waarde | +|-----|--------| +| Systemd service | `telegram-bot.service` | +| Poort | `3004` | +| Health check | `curl http://localhost:3004/health` | + +### Hoe het werkt + +- **Startup**: stuurt "online" melding naar admin via Telegram +- **Health checks** (elke 5 min): checkt dienst-bot (`/health`) en wwebjs-api (`/session/status`) +- **Statusovergangen**: alleen bij up→down of down→up wordt een notificatie gestuurd (geen spam) +- **Fallback**: als `notifyAdmin()` in dienst-bot faalt (WhatsApp API down), wordt `POST /notify` naar telegram-bot gestuurd + +### .env variabelen + +| Variabele | Doel | +|-----------|------| +| `TELEGRAM_BOT_TOKEN` | Token van @BotFather | +| `TELEGRAM_ADMIN_CHAT_ID` | Jouw Telegram chat-ID | +| `TELEGRAM_BOT_PORT=3004` | Poort voor interne `/notify` endpoint | +| `DIENST_BOT_HEALTH_URL` | URL voor dienst-bot health check | +| `WWEBJS_API_SESSION_URL` | URL voor wwebjs-api session status | +| `HEALTH_CHECK_INTERVAL_MS=300000` | Interval tussen health checks | +| `TELEGRAM_BOT_URL` | (in dienst-bot .env) URL voor Telegram fallback | diff --git a/README.md b/README.md index 753e7da..bc20706 100644 --- a/README.md +++ b/README.md @@ -181,6 +181,7 @@ sessie — zie AGENTS.md). De bot detecteert dit via `message_ack`-events en waa | `ACK_STUCK_TIMEOUT_MS` | Leeftijd zonder ack 2 = stuck | `300000` (5 min) | | `ACK_PRUNED_AFTER_MS` | Stale tracking opruimen | `1800000` (30 min) | | `ACK_CHECK_INTERVAL_MS` | Checkinterval (alleen actief bij pending) | `30000` | +| `MEDIA_SEND_DELAY_MS` | Pauze tussen pagina's van één dienst (niet vóór de eerste) — geeft de wwebjs media-pipeline lucht | `500` | Opvallende logregels: `[ack] track …`, `[ack] delivered ack=…`, `[ack] pruned …`.