revert: verwijder ackTracker (dwaalspoor) — bridge fabriceert acks
ackTracker + message_ack-verwerking + check-loop + ACK_* env + docs uit de bot. De bridge fabriceert acks (5219 blz 2 kreeg ack=2 zonder transmissie), dus ack-tracking gaf verkeerde signalen. Enige betrouwbare bron: apparaat + getInfo. Media-pacing zit nu bridge-breed (MEDIA_SEND_DELAY_MS). Poppler re-render als fix verwijderd uit docs (wassen neus voor de race-klasse).
This commit is contained in:
parent
214893205b
commit
ee9b67a78e
7 changed files with 33 additions and 264 deletions
|
|
@ -21,11 +21,6 @@ DIENSTEN_DIR=../Whatsapp_Dienst/diensten
|
||||||
# Rate limiting
|
# Rate limiting
|
||||||
MAX_REQUESTS_PER_DAY=5
|
MAX_REQUESTS_PER_DAY=5
|
||||||
|
|
||||||
# Ack tracking (ghost-send detectie; vereist message_ack aangezet in wwebjs-api .env)
|
|
||||||
ACK_STUCK_TIMEOUT_MS=300000 # 5 min zonder ack 2 = stuck
|
|
||||||
ACK_PRUNED_AFTER_MS=1800000 # 30 min: stale trackings opruimen (loop stopt dan)
|
|
||||||
ACK_CHECK_INTERVAL_MS=30000 # checkinterval (alleen actief als er iets pending is)
|
|
||||||
|
|
||||||
# Admin notifications
|
# Admin notifications
|
||||||
ADMIN_NUMBER=31612345678@c.us
|
ADMIN_NUMBER=31612345678@c.us
|
||||||
|
|
||||||
|
|
|
||||||
66
AGENTS.md
66
AGENTS.md
|
|
@ -172,15 +172,14 @@ Deze maatregelen zijn toegevoegd na een incident met HTTP 500 van de wwebjs-api
|
||||||
- Bij `EADDRINUSE` wordt automatisch na 5 seconden opnieuw geprobeerd te binden
|
- Bij `EADDRINUSE` wordt automatisch na 5 seconden opnieuw geprobeerd te binden
|
||||||
- **Graceful shutdown**: bij SIGTERM/SIGINT worden actieve connecties netjes afgesloten (max 10s timeout)
|
- **Graceful shutdown**: bij SIGTERM/SIGINT worden actieve connecties netjes afgesloten (max 10s timeout)
|
||||||
|
|
||||||
### Ack-tracking voor hangende media (september 2026)
|
### Ack-tracking voor hangende media — VERWIJDERD (dwaalspoor, 2026-09-26)
|
||||||
|
|
||||||
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.
|
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
|
||||||
- `src/ackTracker.js` registreert elke media-verzending en volgt de `message_ack`-events (webhook `message_ack` in `messageHandler.js`).
|
bridge fabriceert acks (dienst 5219 blz 2 kreeg `ack=2` voor een bericht dat nooit
|
||||||
- **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.
|
getransmitteerd was) — ze zijn géén bezorgbewijs en gaven verkeerde signalen. Daarom ook
|
||||||
- **Pruning** na `ACK_PRUNED_AFTER_MS` (30 min); de check-loop is **activity-driven** (draait alleen bij openstaande tracking, geen polling in rust).
|
`message_ack`-verwerking in de bot weg. Enige betrouwbare bronnen: het apparaat en
|
||||||
- Vereiste aan de bridge: `message_ack` staat sinds 2026-09-24 in wwebjs-api `.env` **niet** meer in `DISABLED_CALLBACKS`; container is daarvoor herstart.
|
`getInfo` (`read`/`readRemaining`). Zie RCA hieronder.
|
||||||
- Env-vars: `ACK_STUCK_TIMEOUT_MS` (default 300000), `ACK_PRUNED_AFTER_MS` (default 1800000), `ACK_CHECK_INTERVAL_MS` (default 30000).
|
|
||||||
- **Media-pacing zit bridge-breed** (wwebjs-api, niet de bot): bij een staged-ghost-race
|
- **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
|
(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).
|
chromium-tab en verliest de 2e (bestaat in store + ack2 maar wordt nooit verzonden).
|
||||||
|
|
@ -192,8 +191,6 @@ Sommige mediabestand-verzendingen keren `success` + message-ID terug maar worden
|
||||||
[[Dienst media bezorgtest]] (RCA 2026-09-26). Tijdelijke mitigatie — permanente fix
|
[[Dienst media bezorgtest]] (RCA 2026-09-26). Tijdelijke mitigatie — permanente fix
|
||||||
(fork: send óf echt transmetteren óf zichtbaar falen) staat nog open.
|
(fork: send óf echt transmetteren óf zichtbaar falen) staat nog open.
|
||||||
|
|
||||||
Logregels: `[ack] track <id> (<bestand> → <chat>)`, `[ack] delivered ack=<n> <id>`, `[ack] pruned <n> 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.
|
**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
|
### RCA 2026-09-26: staged-ghost = race-conditie, géén bestandprobleem
|
||||||
|
|
@ -227,50 +224,29 @@ leeg bij staged). Aanbevolen richting (nog niet geïmplementeerd): de fork moet
|
||||||
`addAndSendMsgToChat` écht wachten op transmissiebevestiging — pas dan serialiseren
|
`addAndSendMsgToChat` écht wachten op transmissiebevestiging — pas dan serialiseren
|
||||||
multi-page sends vanzelf en is de race weg.
|
multi-page sends vanzelf en is de race weg.
|
||||||
|
|
||||||
## Media bezorgtest (afbeeldingen die WhatsApp weigert)
|
## Media-levering: twee foutklassen (verwar bloem niet met onkruid)
|
||||||
|
|
||||||
Sommige dienst-PNG's worden wél verzonden (success + message-ID) maar komen niet als beeld
|
**1. Staged-ghost-race (de hoofdklasse, 2026-09-26)** — zie RCA hierboven: een
|
||||||
aan: één grijs vinkje (ack 2), nooit geopend (ack 3). **Niet te detecteren met een
|
direct-opvolgende media-send overlapt in de ene tab en de 2e verliest.
|
||||||
bestandscheck** (formaat/CRC volledig geldig) — alleen de praktijktest vangt het.
|
Bestand bytes zijn nooit het probleem; **re-render (poppler) is een dwaalspoor en geen
|
||||||
Oorzaak-klasse: wwjs 1.34.7-image-pipeline-regressie (wwebjs #201772/#201732), inhoudsgebonden.
|
fix** — het maskeerde de race alleen (nieuwe poging = nieuwe race-rol). Mitigatie =
|
||||||
|
bridge-breed throttle (`MEDIA_SEND_DELAY_MS`, 500) — zie hierboven. Bij vermoeden:
|
||||||
|
opnieuw versturen, apparaat als waarheid.
|
||||||
|
|
||||||
**Bevinding 2026-09-24 (bestand × ontvanger):** de weigering is een interactie tussen
|
**2. Ontvanger offline/weigert (ack-1-hang)** — bericht staat gequeued, apparaat pakt het
|
||||||
bestand en ontvanger, niet de sessie. Hetzelfde (geldige, decodeerbare) bestand hing op
|
niet op. Check eerst óf het apparaat online is, wacht af; persoonsgebonden.
|
||||||
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
|
> **Detectie/verificatie:** de bridgestore en live acks zijn **geen** bezorgbewijs (bridge
|
||||||
> bestand × ontvanger/offline-klasse (ack 1-hang, ontvanger offline of app weigert
|
> fabriceert ze). Enige betrouwbare bronnen: het apparaat en `getInfo`
|
||||||
> render) is niet hetzelfde als de **staged-ghost-race** (zie RCA hierboven): daar is het
|
> (`message.getInfo()` → `read`/`readRemaining`). `dienst-media-test.js` (server,
|
||||||
> bestand nooit het probleem en **re-render (poppler) is een wassen neus** — het maskert
|
> `/home/aiaiaiaiagent/whatsapp-scripts/dienst-media-test.js`) bestaat nog maar meet acks;
|
||||||
> de race alleen. Check bij een "niet aangekomen bladzijde" eerst: was het de 2e pagina
|
> zijn `ack≥3`-criterium is daardoor onbetrouwbaar — bevestig altijd op het apparaat/read.
|
||||||
> 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
|
**Afbeeldingsformaat:** sinds `07b511e` leidt `sendImageMessage` de mimetype af uit de
|
||||||
extensie — `png`→`image/png`, `jpg`/`jpeg`→`image/jpeg` (onbekend→`image/png`). De bot is
|
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
|
dus voorbereid als de bron-repo overgaat naar JPEG; de `bestandsnaam` uit
|
||||||
`dienst-tijden.json` wordt al extensie-agnostisch ingelezen (`getPngFilePaths`).
|
`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)
|
### Script-parameters (voor gebruik)
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|
|
||||||
64
README.md
64
README.md
|
|
@ -121,7 +121,6 @@ src/
|
||||||
dailyStats.js # Dagelijkse aanvraag-teller
|
dailyStats.js # Dagelijkse aanvraag-teller
|
||||||
dienstService.js # JSON lookup en PNG bestandspaden
|
dienstService.js # JSON lookup en PNG bestandspaden
|
||||||
whatsappApi.js # WhatsApp API client (bericht + afbeelding)
|
whatsappApi.js # WhatsApp API client (bericht + afbeelding)
|
||||||
ackTracker.js # Ack-tracking: hangende media detecteren
|
|
||||||
telegramBot.js # Telegram watchdog (apart process, health checks)
|
telegramBot.js # Telegram watchdog (apart process, health checks)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -143,53 +142,14 @@ Bij elke interactie met de groep controleert de bot of het pinned bericht nog be
|
||||||
- **Restart-loop preventie**: systemd `Restart=always` met `RestartSec=5`
|
- **Restart-loop preventie**: systemd `Restart=always` met `RestartSec=5`
|
||||||
- **Webhook fout**: wordt gelogd in de logs
|
- **Webhook fout**: wordt gelogd in de logs
|
||||||
|
|
||||||
## Ack-tracking (ghost-send detectie, sinds 2026-09-24)
|
## Media-levering: acks & pacing (RCA 2026-09-26)
|
||||||
|
|
||||||
> **⚠️ 2026-09-26 — ack-tracking is voor media ongeldig gebleken.** Zie de RCA in
|
**Ack-tracking is VERWIJDERD (dwaalspoor).** De ack-tracker (`message_ack` → admin-melding
|
||||||
> AGENTS.md ("staged-ghost = race-conditie"): de bridge resolvet media-sends optimistisch
|
bij ack<2) is uit de bot gehaald: de bridge fabriceert acks (dienst 5219 blz 2 kreeg
|
||||||
> op lokale staging en fabriceert daarna óók de live `message_ack`-events (bewijs: dienst
|
`ack=2` voor een bericht dat nooit getransmitteerd was) — ze zijn geen bezorgbewijs en
|
||||||
> 5219 blz 2 kreeg `ack=2` voor een bericht dat nooit getransmitteerd was). Daardoor:
|
gaven verkeerde signalen. Enige betrouwbare bronnen: het apparaat en `getInfo`
|
||||||
> (1) de tracker stopt bij gefabriceerde ack ≥ 2 en mist de ghost; (2) "live ack" is géén
|
(`message.getInfo()` → `read`/`readRemaining`). De bridge-`delivery`/acs van de store zijn
|
||||||
> bezorgbewijs meer — enige waarheid is het apparaat en `getInfo` (`read` blijft leeg).
|
nooit leveringsbewijs.
|
||||||
> De klasse is een **race bij 2+ media achter elkaar** (altijd de 2e pagina), niet een
|
|
||||||
> bestandprobleem; `MEDIA_SEND_DELAY_MS` en re-render (poppler) lossen het niet op.
|
|
||||||
|
|
||||||
Sommige mediabestand-verzendingen keren `success` + message-ID terug maar worden nooit
|
|
||||||
**bezorgd**: ze blijven op één grijs vinkje hangen (ack 1) en de ontvanger ziet niets
|
|
||||||
aankomen. Dit is een bestand×ontvanger-interactie in WhatsApp's mediaverwerking (niet de
|
|
||||||
sessie — zie AGENTS.md). De bot detecteert dit via `message_ack`-events en waarschuwt.
|
|
||||||
|
|
||||||
### Hoe het werkt
|
|
||||||
|
|
||||||
1. **Vereiste:** wwebjs-api .env — `message_ack` uit `DISABLED_CALLBACKS` halen
|
|
||||||
(staat er sinds 2026-09-24 niet meer in) + container herstarten.
|
|
||||||
2. `sendImageMessage` registreert elke verzending (`src/ackTracker.js`) met message-ID,
|
|
||||||
chat en bestandsnaam; de webhook (`message_ack` in `messageHandler.js`) update de ack.
|
|
||||||
3. Bij **ack ≥ 2** (bezorgd op apparaat) wordt de tracking verwijderd.
|
|
||||||
4. Blijft een bericht **> `ACK_STUCK_TIMEOUT_MS` (5 min) op ack < 2** → éénmalige
|
|
||||||
admin-notificatie (WhatsApp → Telegram-fallback) met bestandsnaam + chat en het advies
|
|
||||||
"opnieuw renderen + bezorgtest". Er volgt **geen sessie-herstart** (die helpt niet bij
|
|
||||||
een ack-1-hang) en geen resend naar de aanvrager.
|
|
||||||
5. **Pruning:** entries worden na `ACK_PRUNED_AFTER_MS` (30 min) opgeruimd; de check-loop
|
|
||||||
is **activity-driven** (loopt alleen zolang er tracking open staat, geen 30s-polling
|
|
||||||
in rust).
|
|
||||||
|
|
||||||
### Ack-semantiek (wwebjs 1.34.7)
|
|
||||||
|
|
||||||
| ack | Betekenis |
|
|
||||||
|-----|-----------|
|
|
||||||
| 0 | onbekend/nete verstuurd |
|
|
||||||
| 1 | verstuurd naar server (één grijs vinkje) — **hangt hier** |
|
|
||||||
| 2 | bezorgd op apparaat (twee vinkjes) |
|
|
||||||
| 3 | geopend/gelezen (blauwe vinkjes) |
|
|
||||||
|
|
||||||
### Configuratie
|
|
||||||
|
|
||||||
| Variabele | Doel | Default |
|
|
||||||
|-----------|------|---------|
|
|
||||||
| `ACK_STUCK_TIMEOUT_MS` | Leeftijd zonder ack 2 = stuck | `300000` (5 min) |
|
|
||||||
| `ACK_PRUNED_AFTER_MS` | Stale tracking opruimen | `1800000` (30 min) |
|
|
||||||
| `ACK_CHECK_INTERVAL_MS` | Checkinterval (alleen actief bij pending) | `30000` |
|
|
||||||
|
|
||||||
> **🛡️ Media-pacing zit bridge-breed (2026-09-26), niet in de bot.** Staged-ghost-race:
|
> **🛡️ Media-pacing zit bridge-breed (2026-09-26), niet in de bot.** Staged-ghost-race:
|
||||||
> de wwebjs-media-pipeline is één tab; een direct-opvolgende media-send (van elke client)
|
> de wwebjs-media-pipeline is één tab; een direct-opvolgende media-send (van elke client)
|
||||||
|
|
@ -198,16 +158,6 @@ sessie — zie AGENTS.md). De bot detecteert dit via `message_ack`-events en waa
|
||||||
> ertussen. De bot pauzeert niet. Details/config/locatie: zie AGENTS.md en vault "Whatsapp
|
> ertussen. De bot pauzeert niet. Details/config/locatie: zie AGENTS.md en vault "Whatsapp
|
||||||
> rest-api.md". Tijdelijke mitigatie; permanente fix (fork) staat open.
|
> rest-api.md". Tijdelijke mitigatie; permanente fix (fork) staat open.
|
||||||
|
|
||||||
Opvallende logregels: `[ack] track …`, `[ack] delivered ack=…`, `[ack] pruned …`.
|
|
||||||
|
|
||||||
> **Let op — bridgestore is geen bezorgbewijs (2026-09-24):** de lokale store van wwebjs-api
|
|
||||||
> (`/chat/fetchMessages`-acks) kan ack 1/2/3 tonen voor berichten die de ontvanger nooit zag
|
|
||||||
> (ghost-sends). Trek hier géén leveringsconclusie uit. **En sinds 2026-09-26 geldt hetzelfde
|
|
||||||
> voor de live `message_ack`-progressie** (zie waarschuwing bovenin deze sectie): ook die
|
|
||||||
> kan gefabriceerd zijn bij een staged-ghost. Enig betrouwbaar bridgesignaal is `getInfo`
|
|
||||||
> (`message.getInfo()` → `read`/`readRemaining`); eindoordeel is altijd het apparaat van
|
|
||||||
> de ontvanger.
|
|
||||||
|
|
||||||
## Spam protectie
|
## Spam protectie
|
||||||
|
|
||||||
Elke gebruiker kan maximaal `MAX_REQUESTS_PER_DAY` (standaard 5) diensten per dag opvragen. Bij overschrijding krijgt de gebruiker een melding. De teller wordt dagelijks automatisch gereset (bij de eerste request na middernacht).
|
Elke gebruiker kan maximaal `MAX_REQUESTS_PER_DAY` (standaard 5) diensten per dag opvragen. Bij overschrijding krijgt de gebruiker een melding. De teller wordt dagelijks automatisch gereset (bij de eerste request na middernacht).
|
||||||
|
|
|
||||||
|
|
@ -1,106 +0,0 @@
|
||||||
// ackTracker.js
|
|
||||||
// Tracks delivery acks (message_ack) of outgoing media to detect sends that
|
|
||||||
// never reach the recipient: a message returns success + a message ID but
|
|
||||||
// stays below ack 2 (one grey tick).
|
|
||||||
//
|
|
||||||
// Findings (2026-09-24): an ack-1 hang is a bestand×ontvanger interaction, NOT
|
|
||||||
// the session. The same file hung (ack 1) for one recipient yet delivered
|
|
||||||
// (ack 3) to another in the same fresh session. A session restart does NOT fix
|
|
||||||
// an ack-1 hang and needlessly disrupts production — so we deliberately do NOT
|
|
||||||
// auto-restart here. We only detect + notify + name the file, so it can be
|
|
||||||
// re-rendered and re-tested.
|
|
||||||
//
|
|
||||||
// Activity-driven: the caller (index.js) is notified via the pending listener
|
|
||||||
// when the tracked set becomes non-empty, and starts a check loop only then.
|
|
||||||
// No polling when nothing is tracked.
|
|
||||||
//
|
|
||||||
// Requires the bridge (wwebjs-api) to emit message_ack: remove 'message_ack'
|
|
||||||
// from DISABLED_CALLBACKS in the wwebjs-api .env and restart the container.
|
|
||||||
|
|
||||||
const STUCK_TIMEOUT_MS = parseInt(process.env.ACK_STUCK_TIMEOUT_MS, 10) || 5 * 60 * 1000
|
|
||||||
const PRUNE_AFTER_MS = parseInt(process.env.ACK_PRUNED_AFTER_MS, 10) || 30 * 60 * 1000
|
|
||||||
|
|
||||||
// messageId -> { chatId, filename, sentAt, ack, notifiedStuck }
|
|
||||||
const sends = new Map()
|
|
||||||
let pendingListener = null
|
|
||||||
|
|
||||||
function setPendingListener(fn) {
|
|
||||||
pendingListener = typeof fn === 'function' ? fn : null
|
|
||||||
}
|
|
||||||
|
|
||||||
function notifyPendingChange() {
|
|
||||||
if (pendingListener) pendingListener(sends.size)
|
|
||||||
}
|
|
||||||
|
|
||||||
function registerSend(messageId, chatId, filename) {
|
|
||||||
if (!messageId) return
|
|
||||||
sends.set(messageId, {
|
|
||||||
chatId: chatId || '',
|
|
||||||
filename: filename || '',
|
|
||||||
sentAt: Date.now(),
|
|
||||||
ack: 0,
|
|
||||||
notifiedStuck: false
|
|
||||||
})
|
|
||||||
console.log(`[ack] track ${messageId} (${filename} → ${chatId})`)
|
|
||||||
notifyPendingChange()
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleAck(messageId, ack) {
|
|
||||||
if (!messageId) return
|
|
||||||
const entry = sends.get(messageId)
|
|
||||||
if (!entry) return
|
|
||||||
if (ack > entry.ack) entry.ack = ack
|
|
||||||
if (ack >= 2) {
|
|
||||||
sends.delete(messageId) // delivered; nothing left to track
|
|
||||||
console.log(`[ack] delivered ack=${ack} ${messageId} (${entry.filename})`)
|
|
||||||
notifyPendingChange()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove stale entries (older than PRUNE_AFTER_MS). Stuck entries were notified
|
|
||||||
// once around STUCK_TIMEOUT_MS; keeping them longer only leaks memory and keeps
|
|
||||||
// the check-loop alive, so they are dropped here.
|
|
||||||
function prune() {
|
|
||||||
const now = Date.now()
|
|
||||||
let removed = 0
|
|
||||||
for (const [id, entry] of sends) {
|
|
||||||
if (now - entry.sentAt > PRUNE_AFTER_MS) {
|
|
||||||
sends.delete(id)
|
|
||||||
removed++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (removed > 0) {
|
|
||||||
console.log(`[ack] pruned ${removed} stale entr${removed === 1 ? 'y' : 'ies'} (leeftijd > ${Math.round(PRUNE_AFTER_MS / 60000)} min)`)
|
|
||||||
notifyPendingChange()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function stuckEntries() {
|
|
||||||
const now = Date.now()
|
|
||||||
const stuck = []
|
|
||||||
for (const [id, entry] of sends) {
|
|
||||||
if (entry.ack < 2 && now - entry.sentAt > STUCK_TIMEOUT_MS) {
|
|
||||||
stuck.push({ id, ...entry })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return stuck
|
|
||||||
}
|
|
||||||
|
|
||||||
function markNotified(messageId) {
|
|
||||||
const entry = sends.get(messageId)
|
|
||||||
if (entry) entry.notifiedStuck = true
|
|
||||||
}
|
|
||||||
|
|
||||||
function pendingCount() {
|
|
||||||
return sends.size
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
registerSend,
|
|
||||||
handleAck,
|
|
||||||
stuckEntries,
|
|
||||||
prune,
|
|
||||||
markNotified,
|
|
||||||
pendingCount,
|
|
||||||
setPendingListener
|
|
||||||
}
|
|
||||||
37
src/index.js
37
src/index.js
|
|
@ -6,8 +6,7 @@ const axios = require('axios')
|
||||||
const { handleWebhook } = require('./messageHandler')
|
const { handleWebhook } = require('./messageHandler')
|
||||||
const { loadDiensten } = require('./dienstService')
|
const { loadDiensten } = require('./dienstService')
|
||||||
const { resetDay, getYesterdayTotal } = require('./dailyStats')
|
const { resetDay, getYesterdayTotal } = require('./dailyStats')
|
||||||
const { sendTextMessage, notifyAdmin } = require('./whatsappApi')
|
const { sendTextMessage } = require('./whatsappApi')
|
||||||
const ackTracker = require('./ackTracker')
|
|
||||||
|
|
||||||
async function notifyTelegramOnline() {
|
async function notifyTelegramOnline() {
|
||||||
const url = process.env.TELEGRAM_BOT_URL
|
const url = process.env.TELEGRAM_BOT_URL
|
||||||
|
|
@ -103,38 +102,4 @@ async function startServer() {
|
||||||
process.on('SIGINT', () => shutdown('SIGINT'))
|
process.on('SIGINT', () => shutdown('SIGINT'))
|
||||||
}
|
}
|
||||||
|
|
||||||
async function checkStuckAcks() {
|
|
||||||
ackTracker.prune()
|
|
||||||
const stuck = ackTracker.stuckEntries()
|
|
||||||
if (stuck.length === 0) return
|
|
||||||
for (const s of stuck) {
|
|
||||||
if (!s.notifiedStuck) {
|
|
||||||
ackTracker.markNotified(s.id)
|
|
||||||
notifyAdmin(
|
|
||||||
`[dienst-bot] hangend bericht: ${s.filename} → ${s.chatId} ` +
|
|
||||||
`(ack ${s.ack}, ${Math.round((Date.now() - s.sentAt) / 1000)}s oud). ` +
|
|
||||||
`Bestand opnieuw renderen + bezorgtest, geen sessie-herstart (die helpt hier niet).`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Lazy, activity-driven check loop: only runs while there is a tracked send
|
|
||||||
// pending, and stops as soon as the tracked set is empty. No 30s polling in
|
|
||||||
// idle. Re-arms itself after every tick while pendingCount() > 0.
|
|
||||||
const ackCheckIntervalMs = parseInt(process.env.ACK_CHECK_INTERVAL_MS, 10) || 30000
|
|
||||||
let ackTimer = null
|
|
||||||
|
|
||||||
function scheduleAckCheck() {
|
|
||||||
if (ackTimer) return
|
|
||||||
if (ackTracker.pendingCount() === 0) return
|
|
||||||
ackTimer = setTimeout(() => {
|
|
||||||
ackTimer = null
|
|
||||||
checkStuckAcks()
|
|
||||||
if (ackTracker.pendingCount() > 0) scheduleAckCheck()
|
|
||||||
}, ackCheckIntervalMs)
|
|
||||||
}
|
|
||||||
|
|
||||||
ackTracker.setPendingListener(() => scheduleAckCheck())
|
|
||||||
|
|
||||||
startServer()
|
startServer()
|
||||||
|
|
@ -2,7 +2,6 @@ const { findDienst, getPngFilePaths } = require('./dienstService')
|
||||||
const { sendTextMessage, sendImageMessage, notifyAdmin, sendChatSeen } = require('./whatsappApi')
|
const { sendTextMessage, sendImageMessage, notifyAdmin, sendChatSeen } = require('./whatsappApi')
|
||||||
const { checkLimit, increment, MAX_REQUESTS_PER_DAY } = require('./rateLimiter')
|
const { checkLimit, increment, MAX_REQUESTS_PER_DAY } = require('./rateLimiter')
|
||||||
const { increment: incrementDaily } = require('./dailyStats')
|
const { increment: incrementDaily } = require('./dailyStats')
|
||||||
const { handleAck } = require('./ackTracker')
|
|
||||||
|
|
||||||
const ALLOWED_GROUP = process.env.ALLOWED_GROUP
|
const ALLOWED_GROUP = process.env.ALLOWED_GROUP
|
||||||
const ALLOWED_GROUP_NAMES = (process.env.ALLOWED_GROUP_NAME || '')
|
const ALLOWED_GROUP_NAMES = (process.env.ALLOWED_GROUP_NAME || '')
|
||||||
|
|
@ -24,13 +23,6 @@ function extractDienstnummer(body) {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleWebhook(payload) {
|
async function handleWebhook(payload) {
|
||||||
if (payload.dataType === 'message_ack') {
|
|
||||||
if (payload.sessionId && payload.sessionId !== process.env.WHATSAPP_SESSION) return
|
|
||||||
const { message, ack } = payload.data || {}
|
|
||||||
const id = message && (message.id?.id || message._data?.id?.id)
|
|
||||||
if (id) handleAck(id, ack)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (payload.dataType !== 'message') return
|
if (payload.dataType !== 'message') return
|
||||||
if (payload.sessionId !== process.env.WHATSAPP_SESSION) return
|
if (payload.sessionId !== process.env.WHATSAPP_SESSION) return
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
const axios = require('axios')
|
const axios = require('axios')
|
||||||
const fs = require('fs')
|
const fs = require('fs')
|
||||||
const { registerSend } = require('./ackTracker')
|
|
||||||
|
|
||||||
const API_BASE = process.env.WHATSAPP_API_ENDPOINT
|
const API_BASE = process.env.WHATSAPP_API_ENDPOINT
|
||||||
const SESSION = process.env.WHATSAPP_SESSION
|
const SESSION = process.env.WHATSAPP_SESSION
|
||||||
|
|
@ -69,8 +68,6 @@ async function sendImageMessage(chatId, filePath, filename) {
|
||||||
filename: filename
|
filename: filename
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
const messageId = response.data?.message?._data?.id?.id
|
|
||||||
registerSend(messageId, chatId, filename)
|
|
||||||
return response.data
|
return response.data
|
||||||
}, `sendImageMessage ${filename} to ${chatId}`)
|
}, `sendImageMessage ${filename} to ${chatId}`)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue