20 KiB
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 eenack=2afgegeven 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 blijftreadleeg terwijldeliverygevuld kan zijn — het enige betrouwbare bridge-signaal isread/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 |
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 |
| Vervangen door wuzapi (2026-10-11), container gestopt | |||
| 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.jiden conflicts inresolveDeviceStore(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}— headerToken:(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
- Code wijzigen lokaal in
/Users/eddy/src/dienst_echo_whatsappgroep/ - Commit + pushen naar remote:
git add -A && git commit -m "..." && git push origin main - Git pull op nextcloud:
NB:ssh aiaiaiaiagent@nextcloud "cd ~/whatsapp-scripts/dienst_echo_whatsappgroep && git pull && npm ci"diensten/,.env, enAGENTS.mdstaan in.gitignore— die worden niet overschreven doorgit pull. Alleen als.envgewijzigd 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 - Herstarten op nextcloud:
Voor EBS-MerPlus geldt dezelfde flow in zijn eigen repo (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~/whatsapp-scripts/EBS-MerPlus, serviceebs-merplus:3006) — inclusive herstart. - 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)
sendTextMessageensendImageMessagehebben 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: trueom TIME_WAIT poortconflicten te voorkomen - Bij
EADDRINUSEwordt 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
(
serializeMediaSendinwwebjs-api-build-p25/src/utils.js) en wachtMEDIA_SEND_DELAY_MS(bridge-.env:/home/aiaiaiaiagent/wwebjs-api/.env, nu500sinds 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 … -appendtot één beeld; zie eddy/whatsappdienst#2) endienst-tijden.jsonverwijst naar één bestand per dienst.dienst-bot(deze repo) stuurt gewoon de bestanden uitpaginas[].bestandsnaamzoals 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_filewordt bij container-create gelezen → eenMEDIA_SEND_DELAY_MS-wijziging werkt pas na een recreate (cd ~/wwebjs-api && sudo -A docker compose up -d); een kaledocker restart(ook de wekelijkse timer) herlaadt de.envniet. 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 sendMsgResultPromisestond al aan —waitUntilMsgSent:truein de fork (clientController.jssendOptions) 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) + botPAGE_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 zetwaitUntilMsgSent: 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_MSkan dit niet oplossen (wacht op optimistisch signaal); poppler/re-render maskeert het alleen (nieuwe poging = nieuwe race-rol, soms gewonnen — wassen neus);sendMessageViaUilost 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; zijnack≥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), wordtPOST /notifynaar 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 |