dienst_echo_whatsappgroep/AGENTS.md

17 KiB
Raw Blame History

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

# 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

ssh aiaiaiaiagent@nextcloud
docker ps --filter "name=wwebjs"           # status
docker restart wwebjs-api                   # herstarten
docker logs wwebjs-api --tail 30            # logs

Sessie status

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
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:
    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):
    rsync -avz /Users/eddy/src/dienst_echo_whatsappgroep/.env aiaiaiaiagent@nextcloud:/home/aiaiaiaiagent/whatsapp-scripts/dienst_echo_whatsappgroep/.env
    
  4. Herstarten op nextcloud:
    ssh aiaiaiaiagent@nextcloud "sudo systemctl restart dienst-bot"
    ssh aiaiaiaiagent@nextcloud "sudo systemctl restart telegram-bot"
    
  5. Logs checken:
    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 — VERWIJDERD (dwaalspoor, 2026-09-26)

Er stond een ack-tracker (src/ackTracker.js) die message_ack-events volgde en bij ack<2 na ACK_STUCK_TIMEOUT_MS een admin-melding gaf. Verwijderd uit de code: de bridge fabriceert acks (dienst 5219 blz 2 kreeg ack=2 voor een bericht dat nooit getransmitteerd was) — ze zijn géén bezorgbewijs en gaven verkeerde signalen. Daarom ook message_ack-verwerking in de bot weg. Enige betrouwbare bronnen: het apparaat en getInfo (read/readRemaining). Zie RCA hieronder.

  • Media-pacing zit bridge-breed (wwebjs-api, niet de bot): bij een staged-ghost-race (2026-09-26, dienst 5219 blz 2) overlappen opeenvolgende media-sends in de ene chromium-tab en verliest de 2e (bestaat in store + ack2 maar wordt nooit verzonden). De bridge serialiseert nu álle media-sends over alle sessies/clients door één keten (serializeMediaSend in wwebjs-api-build-p25/src/utils.js) en wacht MEDIA_SEND_DELAY_MS (bridge-.env: /home/aiaiaiaiagent/wwebjs-api/.env, nu 500 sinds 2026-09-28 teruggezet van 1000; delay lost de race niet op, zie RCA) ná elke media-send.
  • Bot-side pagina-pacing (PAGE_SEND_DELAY_MS, bot-.env, nu 3000): sinds 2026-10-05 wacht de bot ná elke pagina (behalve de laatste) voordat de volgende gestuurd wordt. Reden: ook verse single-sends van een "verloren" blz komen aan, maar de paarsgewijze flow (blz 2 direct na blz 1, brug-delay 500 ms) verliest blz 2 steeds opnieuw (casus 4027: 3 botruns + herhaalde opvraag na re-render). De pauze maakt elke pagina een effectief losse send — enige sends falen empirisch niet. 0 zet uit. Waarde gemeten (2026-10-05): transmissie-latentie naar ontvanger-sessie ≈ 0.83 s (848/833/827 ms via zelf-send + sessie-Eddy-poll); 3000 = 0.83 × ~3.5 marge. De brug-delay zelf NIET verhogen (delay loss effect, RCA 26-09).
  • Bridge-.env-wijzigingen: env_file wordt bij container-create gelezen → een MEDIA_SEND_DELAY_MS-wijziging werkt pas na een recreate (cd ~/wwebjs-api && sudo -A docker compose up -d); een kale docker restart (ook de wekelijkse timer) herlaadt de .env niet. Recreate = sessie-herverbinding, alleen met expliciete toestemming (hard verbod op container-restart staat in de vault). Config/code/waarom volledig gedocumenteerd in vault Whatsapp rest-api ("Bridge-wide media-send throttle") en Dienst media bezorgtest (RCA 2026-09-26). Tijdelijke mitigatie — permanente fix (fork: send óf echt transmetteren óf zichtbaar falen) staat nog open.

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.

Verificatie 2026-10-05 — de "permanente fix"-richting is hiermee gefalsifieerd overruled. Gemeten (live webhook-instrumentatie) dat geghoste sends wél ack=1 én ack=2 events afgeven (055.png/jpg ghosten met acks 1+2; alleen ack=3 bleef uit doordat het bericht nooit zichtbaar was). Daardoor: (1) een ack ≥ 1-gate (upstream-advies #201849/#201727) vangt ghosts niet; (2) await sendMsgResultPromise stond al aan — waitUntilMsgSent:true in de fork (clientController.js sendOptions) en wordt al geawait (Utils.js) maar resolved óók optimistisch (= no-op). Er is geen server-zijdig signaal dat ghost vs. bezorgd discrimineert (store, live acks, getInfo/read, ontvangende sessie — alles fabricerbaar of gebruikersafhankelijk). Enige waarheid = apparaat. De geplande fork-patch is daarom afgeblazen vóór bouwen/recreate. Werkende mitigaties i.p.v. daarvan: alle dienstafbeeldingen als JPEG (convert_png_to_jpg.sh in Whatsapp_Dienst/diensten/png/, json op .jpg; PNG-versies behouden) + bot PAGE_SEND_DELAY_MS=3000 (gemeten transmissie-L ≈ 0.83 s × 3.5). Verlies blijft positioneel/contextafhankelijk (4027-blz2 verliest vaker dan bv. 4427), beeldstatistiek discrimineert níet (4027-055 ≈ 4427-202 qua mean/std/entropy). Bij gemist: blz los opnieuw sturen (werkt).

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-levering: twee foutklassen (verwar bloem niet met onkruid)

1. Staged-ghost-race (de hoofdklasse, 2026-09-26) — zie RCA hierboven: een direct-opvolgende media-send overlapt in de ene tab en de 2e verliest. Bestand bytes zijn nooit het probleem; re-render (poppler) is een dwaalspoor en geen fix — het maskeerde de race alleen (nieuwe poging = nieuwe race-rol). Mitigatie = bridge-breed throttle (MEDIA_SEND_DELAY_MS, 500 — delay loss effect, RCA 26-09; teruggezet van 1000 op 2026-09-28) — zie hierboven. Bij vermoeden: opnieuw versturen, apparaat als waarheid.

1b. Transparante (alpha) PNG — WEL een bestandsklasse (2026-09-28, casus 4039). Dienst 4039 (2 blz): blz 79 kwam nooit aan — ongeacht volgorde (078 eerst/laatst), timing (500/1000 ms), UI-reset ertussen of vice versa; verschijnt in beide stores met server-locators maar rendert niet op het apparaat. Verse pdftoppm-render (ondoorzichtig sRGB, 147 KB i.p.v. 186 KB RGBA) kwam wel aan; na vervanging van 4039-079.png kwamen beide blz van 4039 aan. Vermoedelijke oorzaak: de opgeslagen PNG's zijn RGBA met ~90% volledig transparante pixels; WhatsApp's re-encode-pipeline (PNG→JPEG) is berucht onbetrouwbaar met transparante PNGS (o.a. wwebjs-issues #2695 (silent fail PNG send, multi-device), #3603 (transparante PNG blurred), #201772 (image-send hang); loss of alpha op transparante PNG's in officiële clients (StackOverflow 37554514)). 554 van de 555 opgeslagen PNG's zijn RGBA met (deels transparante) achtergrond → zelfde risico op andere diensten (mogelijk de echte oorzaak van de klasse-1 "staged ghosts": de bestand-inhoud i.p.v. timing). De bot stuurt sinds 07b511e mimetype op basis van extensie; ondoorzichtig renderen bij de bron lost het op; documents via sendMediaAsDocument omzeilt de image-pipeline maar is afgewezen als productie-richting.

2. Ontvanger offline/weigert (ack-1-hang) — bericht staat gequeued, apparaat pakt het niet op. Check eerst óf het apparaat online is wacht af; persoonsgebonden.

Detectie/verificatie: de bridgestore en live acks zijn geen bezorgbewijs (bridge fabriceert ze). Enige betrouwbare bronnen: het apparaat en getInfo (message.getInfo() → read/readRemaining). dienst-media-test.js (server, /home/aiaiaiaiagent/whatsapp-scripts/dienst-media-test.js) bestaat nog maar meet acks; zijn ack≥3-criterium is daardoor onbetrouwbaar — bevestig altijd op het apparaat/read.

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).

Script-parameters (voor gebruik)

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