revert: bot-side media pacing weg — pacing zit nu bridge-breed

De seriële send-keten + MEDIA_SEND_DELAY_MS in de bot teruggedraaid; de
wwebjs-api bridge serialiseert + throttle de media-sends nu zelf (zie
build-p25). Bot verstuurt weer direct. Docs/.env.example bijgewerkt.
This commit is contained in:
Eddy de Vink 2026-09-26 22:44:38 +02:00
commit 5d3683a8a0
5 changed files with 11 additions and 36 deletions

View file

@ -25,7 +25,6 @@ MAX_REQUESTS_PER_DAY=5
ACK_STUCK_TIMEOUT_MS=300000 # 5 min zonder ack 2 = stuck 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_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) ACK_CHECK_INTERVAL_MS=30000 # checkinterval (alleen actief als er iets pending is)
MEDIA_SEND_DELAY_MS=500 # wacht ná élke media-send (media-pipeline is één tab; voorkomt overlappende sends)
# Admin notifications # Admin notifications
ADMIN_NUMBER=31612345678@c.us ADMIN_NUMBER=31612345678@c.us

View file

@ -181,13 +181,11 @@ Sommige mediabestand-verzendingen keren `success` + message-ID terug maar worden
- **Pruning** na `ACK_PRUNED_AFTER_MS` (30 min); de check-loop is **activity-driven** (draait alleen bij openstaande tracking, geen polling in rust). - **Pruning** na `ACK_PRUNED_AFTER_MS` (30 min); de check-loop is **activity-driven** (draait alleen bij openstaande tracking, geen polling in rust).
- Vereiste aan de bridge: `message_ack` staat sinds 2026-09-24 in wwebjs-api `.env` **niet** meer in `DISABLED_CALLBACKS`; container is daarvoor herstart. - Vereiste aan de bridge: `message_ack` staat sinds 2026-09-24 in wwebjs-api `.env` **niet** meer in `DISABLED_CALLBACKS`; container is daarvoor herstart.
- Env-vars: `ACK_STUCK_TIMEOUT_MS` (default 300000), `ACK_PRUNED_AFTER_MS` (default 1800000), `ACK_CHECK_INTERVAL_MS` (default 30000). - Env-vars: `ACK_STUCK_TIMEOUT_MS` (default 300000), `ACK_PRUNED_AFTER_MS` (default 1800000), `ACK_CHECK_INTERVAL_MS` (default 30000).
- **Media-spreiding:** `MEDIA_SEND_DELAY_MS` (default **500**; praktijktest op 500) - **Media-pacing zit bridge-breed** (wwebjs-api, niet de bot): de bridge serialiseert
pauzeert **na élke media-send** (in `sendImageMessage`), niet alleen tussen pagina's van media-sends en wacht `MEDIA_SEND_DELAY_MS` (bridge-.env) tussen twee sends — de
één dienst. De wwebjs-media-pipeline is EEN chromium-tab; opeenvolgende media-sends (van wélke client
wwebjs-media-pipeline is EEN chromium-tab; opeenvolgende media-sends — óók naar ook: dienst-bot, EBS, rss-forward) overlappen anders → staged-ghost-race. De bot zelf
verschillende ontvangers — overlappen anders en veroorzaken de staged-ghost-race. pauzeert niet meer.
0 = uitschakelen. (Tijdelijke mitigatie; permanente fix = fork: send óf écht verzenden
óf zichtbaar falen, zie RCA.)
Logregels: `[ack] track <id> (<bestand> → <chat>)`, `[ack] delivered ack=<n> <id>`, `[ack] pruned <n> stale entries`. Logregels: `[ack] track <id> (<bestand> → <chat>)`, `[ack] delivered ack=<n> <id>`, `[ack] pruned <n> stale entries`.

View file

@ -190,7 +190,10 @@ sessie — zie AGENTS.md). De bot detecteert dit via `message_ack`-events en waa
| `ACK_STUCK_TIMEOUT_MS` | Leeftijd zonder ack 2 = stuck | `300000` (5 min) | | `ACK_STUCK_TIMEOUT_MS` | Leeftijd zonder ack 2 = stuck | `300000` (5 min) |
| `ACK_PRUNED_AFTER_MS` | Stale tracking opruimen | `1800000` (30 min) | | `ACK_PRUNED_AFTER_MS` | Stale tracking opruimen | `1800000` (30 min) |
| `ACK_CHECK_INTERVAL_MS` | Checkinterval (alleen actief bij pending) | `30000` | | `ACK_CHECK_INTERVAL_MS` | Checkinterval (alleen actief bij pending) | `30000` |
| `MEDIA_SEND_DELAY_MS` | Wacht ná élke media-send (media-pipeline is één tab; voorkomt overlappende sends naar elk doel) | `500` |
> **Media-pacing:** zit bridge-breed (wwebjs-api), niet in de bot — zie AGENTS.md. De bot
> pauzeert niet tussen media-sends; de bridge serialiseert ze met `MEDIA_SEND_DELAY_MS`
> (bridge `.env`).
Opvallende logregels: `[ack] track …`, `[ack] delivered ack=…`, `[ack] pruned …`. Opvallende logregels: `[ack] track …`, `[ack] delivered ack=…`, `[ack] pruned …`.

View file

@ -68,8 +68,8 @@ async function handleWebhook(payload) {
return return
} }
// Pacing gebeurt nu centraal in sendImageMessage (na élke media-send), zodat óók // Media-pacing voor opeenvolgende sends gebeurt bridge-breed (wwebjs-api
// afbeeldingen naar verschillende ontvangers niet overlappen in de ene chromium-tab. // serialiseert + throttle de media-sends; MEDIA_SEND_DELAY_MS in bridge-.env).
const pngPaths = getPngFilePaths(dienst) const pngPaths = getPngFilePaths(dienst)
if (!pngPaths || pngPaths.length === 0) { if (!pngPaths || pngPaths.length === 0) {
await sendTextMessage(author, `Geen pagina's gevonden voor dienst ${dienstnummer}.`) await sendTextMessage(author, `Geen pagina's gevonden voor dienst ${dienstnummer}.`)

View file

@ -19,19 +19,6 @@ async function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms)) return new Promise(resolve => setTimeout(resolve, ms))
} }
// Media-send seriële keten: de wwebjs-media-pipeline is EÉN chromium-tab. Zonder
// serialisatie overlappen opeenvolgende media-sends — pagina's van één dienst óf
// afbeeldingen naar verschillende ontvangers bij gelijktijdige webhook-requests.
// Daarom loopt élke media-send door deze ene keten en eindigt elke send met
// MEDIA_SEND_DELAY_MS, zodat de tab tussen twee sends rust krijgt.
let mediaSendChain = Promise.resolve()
function enqueueSend(run) {
const job = mediaSendChain.then(run, run)
mediaSendChain = job.catch(() => {}) // keten levend houden, ook als een send faalt
return job
}
function isRetryableError(error) { function isRetryableError(error) {
if (error.response) { if (error.response) {
const status = error.response.status const status = error.response.status
@ -69,10 +56,6 @@ async function sendTextMessage(chatId, text) {
} }
async function sendImageMessage(chatId, filePath, filename) { async function sendImageMessage(chatId, filePath, filename) {
return enqueueSend(() => sendImageMessageInner(chatId, filePath, filename))
}
async function sendImageMessageInner(chatId, filePath, filename) {
return withRetry(async () => { return withRetry(async () => {
const fileBuffer = fs.readFileSync(filePath) const fileBuffer = fs.readFileSync(filePath)
const base64Data = fileBuffer.toString('base64') const base64Data = fileBuffer.toString('base64')
@ -88,14 +71,6 @@ async function sendImageMessageInner(chatId, filePath, filename) {
}) })
const messageId = response.data?.message?._data?.id?.id const messageId = response.data?.message?._data?.id?.id
registerSend(messageId, chatId, filename) registerSend(messageId, chatId, filename)
// Pacing ná élke media-send: de wwebjs-media-pipeline is EEN chromium-tab. Twee
// opeenvolgende media-sends (pagina's van één dienst óf afbeeldingen naar
// verschillende ontvangers) mogen niet overlappen — dat veroorzaakt de staged-
// ghost-race (2e send verliest). Wacht dus altijd MEDIA_SEND_DELAY_MS ná een send.
const mediaSendDelayMs = parseInt(process.env.MEDIA_SEND_DELAY_MS, 10) || 500
if (mediaSendDelayMs > 0) {
await sleep(mediaSendDelayMs)
}
return response.data return response.data
}, `sendImageMessage ${filename} to ${chatId}`) }, `sendImageMessage ${filename} to ${chatId}`)
} }