# 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. - **Ook de live `message_ack`-progressie is sinds 2026-09-26 géén bezorgbewijs meer:** de staged-ghost van dienst 5219 blz 2 kreeg via de webhook een `ack=2` afgegeven voor een bericht dat nooit getransmitteerd was (stond niet eens in de WhatsApp-app van de eigenaar). De bridge resolvet media-sends **optimistisch op lokale staging** en fabriceert daarna ack-events + delivery-info. Ack-tracking (stopt bij ack ≥ 2) kan deze klasse per ontwerp niet vangen. - **Enige geldige bewijzen van levering:** (1) de **zichtbare werkelijkheid op het apparaat** van de ontvanger/betrokkene; (2) `getInfo` (`message.getInfo()`): bij een staged-ghost blijft `read` leeg terwijl `delivery` gevuld kan zijn — het enige betrouwbare bridge-signaal is `read`/`readRemaining`. - **Trek NOOIT een leveringsconclusie uit een later gelezen fetchMessages-ack.** Claim geen "bezorgd/gelezen" op basis van de store — dat geeft verkeerde adviezen. ## 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**; praktijktest op 500) pauzeert **na élke media-send** (in `sendImageMessage`), niet alleen tussen pagina's van één dienst. De wwebjs-media-pipeline is EEN chromium-tab; opeenvolgende media-sends — óók naar verschillende ontvangers — overlappen anders en veroorzaken de staged-ghost-race. 0 = uitschakelen. (Tijdelijke mitigatie; permanente fix = fork: send óf écht verzenden óf zichtbaar falen, zie RCA.) 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. **Let op 2 (2026-09-26):** óók deze acks zijn geen leveringsbewijs — zie RCA hieronder. ### RCA 2026-09-26: staged-ghost = race-conditie, géén bestandprobleem Casus: dienst 5219 (2 pagina's), 09:29 gevraagd in de groep, bot stuurt beide pagina's prive. App toont alleen blz 1; **blz 2 stond in het geheel niet in de WhatsApp-app van de eigenaar** (geen transmissie). Toch: bridge retourneerde success + message-ID (`3EB040AD…`), store toont het bericht, live `message_ack` gaf `ack=2`, `getInfo` toonde `delivery` gevuld maar `read` leeg. Geen enkele fout in de container-logs. Mechanisme (bewezen uit fork-broncode + logs): - De send loopt via `WAWebSendMsgChatAction.addAndSendMsgToChat(chat, message)` → `[msgPromise, sendMsgResultPromise]`. De bridge zet `waitUntilMsgSent: true` (stock wwebjs-api, `clientController.js`), maar de promise resolve **ook optimistisch op lokale staging** — een echte serverbevestiging wordt nooit afgewacht. HTTP-success + message-ID betekenen dus **niet** "verzonden". - **Patroon (gebruiker, consistent met logs):** de ghost treedt alleen op bij **2+ media direct achter elkaar**, en dan altijd de **2e** van het paar; enkele sends falen nooit. - Verklaring: de bot wacht per pagina op het HTTP-signaal (staging), niet op echte transmissie → blz 2 wordt gestart terwijl blz 1's **echte** upload/transmissie nog in de lucht is. Twee echte media-transmissies overlappen in de ene chromium-tab → de 2e verliest (intermitterend) → staged-ghost. - Gevolgen: **`MEDIA_SEND_DELAY_MS` kan dit niet oplossen** (wacht op optimistisch signaal); **poppler/re-render maskeert het alleen** (nieuwe poging = nieuwe race-rol, soms gewonnen — wassen neus); **`sendMessageViaUi` lost het óók niet op** (wacht op dezelfde promises, alleen omzeilt het de media-editor); **ack-tracking kan het niet vangen** (stopt bij gefabriceerde ack ≥ 2). Enige betrouwbare signalen: apparaat-waarheid; `getInfo` `read`/`readRemaining` (blijft leeg bij staged). Aanbevolen richting (nog niet geïmplementeerd): de fork moet ná `addAndSendMsgToChat` écht wachten op transmissiebevestiging — pas dan serialiseren multi-page sends vanzelf en is de race weg. ## 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.) > **⚠️ 2026-09-26: onderscheid twee foutklassen.** De hierboven beschreven > bestand × ontvanger/offline-klasse (ack 1-hang, ontvanger offline of app weigert > render) is niet hetzelfde als de **staged-ghost-race** (zie RCA hierboven): daar is het > bestand nooit het probleem en **re-render (poppler) is een wassen neus** — het maskert > de race alleen. Check bij een "niet aangekomen bladzijde" eerst: was het de 2e pagina > van een paar binnen ~1s? Dan is het de race, geen bestand. Echte remedie is nog in > ontwerp (fork: wachten op transmissiebevestiging); voor nu: opnieuw versturen (eventuele > nieuwe poging) en apparaat als waarheid. **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 |