dienst_echo_whatsappgroep/AGENTS.md

20 KiB
Raw Permalink Blame History

Dienst Bot — Productie-informatie

🚨 VERPLICHT: bridgestore is GEEN waarheid over bezorging

Geldt ook hier (wuzapi-tijdperk): afhandeling/acks van de bridge zijn géén bezorgbewijs. Deze sectie beschrijft de bewezen wwebjs-fabricatie (historie, tot wuzapi-migratie 2026-10-11); de les blijft: alleen het apparaat van de ontvanger is waarheid. Bij wuzapi geldt: de /chat/send/*-respons {"code":200} bevestigt serverontvangst, niet zichtbaarheid op het doelapparaat.

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
8082 wuzapi systemd service WhatsApp bridge (whatsmeow, 1 sessie EdGpt)
3007 wuzapi-multiplexer systemd service Webhook router per chat-groep
3002 dienst-bot systemd service Dienst opvragen bot (dit project)
3003 aankondigingen-webhook systemd service Losse webhook receiver (EBS-aankondigingen)
3004 telegram-bot systemd service Telegram watchdog (dit project)
3006 ebs-merplus systemd service MerPlus→EBS aankondigingen doorstuurbot
3001 wwebjs-api Docker container Vervangen door wuzapi (2026-10-11), container gestopt
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 (wuzapi)

De WhatsApp-bridge is sinds 2026-10-11 wuzapi (vroeger wwebjs-api op :3001, container gestopt). wuzapi draait als systemd service op :8082. Volledige installatie/ config/credentials staan in de vault (Wuzapi + Whatsapp rest-api historie).

Wat Waarde
Systemd service wuzapi.service
URL http://127.0.0.1:8082
Binary /usr/local/bin/wuzapi (SELinux bin_t)
Datadir /home/aiaiaiaiagent/wuzapi/ (.env + SQLite dbdata/)
Sessie EdGpt (user id 09efd1be…, een sessie voor heel nummer 31616173970)
User token EdGpt-BOhLAsGkIfVZnKVi (vault)
Webhook http://127.0.0.1:3007/webhook (multiplexer)
Events Message

1 sessie + multiplexer (besluit 2026-10-11): wuzapi koppelt per user één whatsmeow-device. Twee users op hetzelfde nummer delen dezelfde users.jid en conflicts in resolveDeviceStore (events gingen willekeurig naar één van beide webhooks). Daarom: 1 sessie (EdGpt) voor alle WhatsApp-verkeer van het nummer, en de multiplexer (:3007) routeert per chat-groep naar de juiste bot (zie hieronder).

Multiplexer (src/multiplexer.js, systemd wuzapi-multiplexer.service)

Ontvangt de wuzapi-webhook op :3007 en stuurt per chat-groep door:

Chat-groep Naar
120363407613652082@g.us (Dienst opvragen) dienst-bot :3002
120363429978048879@g.us (Anonieme aankondigingen) EBS-MerPlus :3006

Sessie status

curl http://127.0.0.1:8082/session/status -H "Token: $TOKEN"
# → {"code":200,"data":{"LoggedIn":true,"Connected":true},...}

Wuzapi-commando's

ssh aiaiaiaiagent@nextcloud
sudo -A systemctl status wuzapi            # bridge status
sudo -A journalctl -u wuzapi --since today # logs
# Webhook-verzending zien:
sudo -A journalctl -u wuzapi | grep "Calling user webhook"
# Multiplexer routing (+ dienst-bot + MerPlus):
sudo -A journalctl -u wuzapi-multiplexer / -u dienst-bot / -u ebs-merplus --since today

wuzapi-API-knoppen (voor de bot)

  • Send text: POST /chat/send/text {Phone, Body} — header Token: (user token)
  • Send image: POST /chat/send/image {Phone, Image:"data:image/jpeg;base64,...", Caption?}
  • Send document: POST /chat/send/document {Phone, Document, FileName, Caption?}
  • Mark read: POST /chat/markread {Id:[...], Chat}
  • Admin (users/webhook-beheer): Authorization: header met admin-token (vault)
  • Webhook-payload: {type:"Message", event:{Info:{Chat:{User,Server}, Sender, IsFromMe, ID}, Message:{...}}, base64?, mimeType?, fileName?}

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"
    ssh aiaiaiaiagent@nextcloud "sudo systemctl restart wuzapi-multiplexer"   # als multiplexer.js wijzigde
    
    Voor EBS-MerPlus geldt dezelfde flow in zijn eigen repo (~/whatsapp-scripts/EBS-MerPlus, service ebs-merplus :3006) — inclusive herstart.
  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.

De secties hieronder (Resilience maatregelen, ack-tracking, RCA staged-ghost, Media-levering twee foutklassen, dienst-media-test.js) beschrijven de wwebjs-api- historie vóór de wuzapi-migratie van 2026-10-11. Bewaard voor context/RCA; de huidige bridge is wuzapi (geen wwebjs-container meer). De staged-ghost-race en ack-fabricatie waren eigen aan de wwebjs-fork.

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.
  • Pre-render = de standaard sinds 2026-10-05. De sœur-repo whatsappdienst rendert álle dienstschema's vooraf als één lange JPEG per dienst (meerpagina = magick … -append tot één beeld; zie eddy/whatsappdienst#2) en dienst-tijden.json verwijst naar één bestand per dienst. dienst-bot (deze repo) stuurt gewoon de bestanden uit paginas[].bestandsnaam zoals voorheen — geen runtime-merge, geen pacing. Single send = geen staged-ghost (2e pagina van een paar verliest). UX: één scrollbaar beeld per dienst.
  • Tijdelijk had de bot een runtime-merge als fallback (MERGE_PAGES/MAGICK_BIN/ PAGE_SEND_DELAY_MS); dat is verwijderd zodra whatsappdienst single-file levert. De historie (ack-gate-falsificatie, JPEG-conversie, 4027-casus) staat hieronder.
  • 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