318 lines
17 KiB
Markdown
318 lines
17 KiB
Markdown
# 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 — 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](https://stackoverflow.com/questions/37554514/transparent-png-images-in-whatsapp))).
|
||
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)
|
||
|
||
```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 |
|