- Python 100%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
- Strip de filter terug tot: (1) sterk signaal in de titel -> door; (2) score van unieke semantische groepen >= 3 met titel-signaal -> door; (3) anders weg. Geen whitelist (82 termen), geen blacklist, geen span-merge, geen dienststoring-titelanker meer; woordgrenzen/lookbehinds weren de bekende botsingen structureel (schoolbus/VW-bus/stationsstraat/stationair/ treinstraat/treinweg). - Lijsten 49 families + 82 whitelist + 2 blacklist = 133 -> 16 sterk + 14 ambigu = 30 groepen; filterzone 524 -> ~292 regels; match-methodes 4 -> 2. - Vijf historische uitzonderings-families geschrapt (busongeluk, sein, breng, hermes, inchecken, kaartje, rails, staking, e.d.) — die woorden leveren nu 0 signalen op en de bekende false positives blijven geweerd. - Completheids-audit ving de Maaslijn/Nedersaksenlijn/Kamperlijn/waterbus- regressie op (hersteld); resterende zero-hit-kandidaten geen wijziging. - validate_filter.py: invarianten 1-3 + 113 regressie-cases (90 onveranderd groen + 23 nieuw); prompt-matrix 27/27. - Gedragswijziging op 45 productiedagen: uitsluitend false positives extra geweerd (buitenlandse veerboot-rampen, Zwitsers busongeluk, delftopzondag tram+halte, Cox/RET, tram-blokkade); 0 recalls verloren. - README/AGENTS.md/vault bijgewerkt. |
||
| buzzrss | ||
| .gitignore | ||
| AGENTS.md | ||
| base-config.yaml | ||
| maubot.yaml | ||
| README.md | ||
| validate_filter.py | ||
| WHATSAPP_DOORSTUREN_PLAN.md | ||
buzz-rss — RSS/Atom → Matrix, gefilterd op OV-relevantie
Maubot-plugin nl.eddydevink.rss (fork van xyz.maubot.rss)
die nieuwsfeeds pollt en alleen openbaar-vervoer-gerelateerde berichten doorplaatst
naar Matrix-rooms (optioneel ook alleen de link naar een WhatsApp-groep).
De filterlogica is bewust vast in code (niet via config) en volgt één businessregel:
Precision boven recall — liever een relevant OV-artikel missen dan één niet-OV-artikel ten onrechte doorlaten. (Gebruikersbesluit 2026-09-10)
Functionaliteit
- Abonneren op feeds per room, met per-feed meldingssjabloon en
m.notice-optie. - Conditional fetch (
ETag/Last-Modified) en backoff bij faalende feeds. - HTML-scrape-modus: bronnen zonder RSS/Atom/JSON worden als pagina gepolled via
per-feed
feed.scrape_config(ziebuzzrss/html_scrape.py; eerste productiefeed: CNV). - Paywall-uitsluiting (
exclude_paywalled) en titel-dedup over feeds (title_dedup_days: zelfde titel van een andere site van dezelfde uitgever = niet dubbel posten). - Per-run logfile met aantallen (
run_log_file). - WhatsApp-forward: alleen
entry.linkwordt gepost (vooruit-links, zonder titel), met dedup-check tegen de doelgroep. Uit zolangwhatsapp_api/whatsapp_chat_idleeg zijn.
Commando's
Prefix uit config (command_prefix, default rss), eventueel zonder !:
| Commando | Aliassen | Werking |
|---|---|---|
!rss subscribe <url> |
s, sub |
Deze room op een feed abonneren (nieuwe feed wordt direct geïndexeerd) |
!rss unsubscribe <feed-id> |
u, unsub |
Abonnement van deze room opzeggen |
!rss subscriptions |
ls, list, subs |
Abonnementen in deze room + huidige filter |
!rss template <feed-id> [sjabloon] |
t, tpl |
Meldingssjabloon bekijken/wijzigen (variabelen: $title, $link, $summary, $feed_title, …) |
!rss notice <feed-id> <true|false> |
n |
Als m.notice versturen i.p.v. m.text |
!rss filter |
f, set-filter, show-filter, get-filter |
Toont het vaste OV-matchfilter |
Bestuurders: admins uit de config, of gebruikers met een powerlevel ≥ dat van het RSS-state-event in die room (default 50 als geen level is ingesteld). Bij een room-tombstone worden abonnementen automatisch naar de vervangende room verplaatst.
Het OV-matchfilter (v1.15)
De volledige classificatie + de werkwijze voor nieuwe woorden staat in
buzzrss/bot.py, boven MATCH_FILTER_STRONG — dat blok is leidend bij elke
wijziging. Samengevat, in drie regels:
1. staat er een STERK signaal in de TITEL? -> PLAATSEN
2. score = som unieke groeps-gewichten ≥ 3 én de titel bevat een signaal? -> PLAATSEN
3. anders -> NIET PLAATSEN
- Sterk (2 punten, titel = zelfstandig): vervoerders (NS, ProRail, Arriva, RET, GVB, Qbuzz, Deutsche Bahn, TESO, ...), spoorjargon (spoorstoring, spoorlijn, wisselstoring, treinstoring, ertms, perron, ...), bus-samenstellingen (bushalte, buslijn, busbaan, buschauffeur), tramverkeer/tramlijn, metrolijn, intercity/streekvervoer/veerdienst/buurtbus, conducteur, ov-chipkaart/ovpay, openbaar vervoer, lightrail, 'lijn 12', ICE-frasen, rangeer... én de dienststoring-zin ('sprinters rijden niet', 'metro rijdt niet').
- Ambugue (1 punt; halte/tram/metro/spoorbrug 2) — contextafhankelijk,
nooit zelfstandig voldoende, alleen als combinatie:
bus,trein,station(s),reiziger/passagier,vervoer(der),sprinter,pendelbus/shuttlebus,dienstregeling,ontsporen,spoor. - Semantische groepen tellen één keer per artikel: 'bus' + 'buslijn' + 'busvervoer' + 'busstation' is één bus-signaal (bus-samenstelling, bus, station — nooit vier onafhankelijke signalen).
- Geen whitelist, geen blacklist: woordgrenzen en koppelteken-lookbehinds weren structureel 'schoolbus'/'VW-bus', 'stationsstraat'/'stationair'/ 'stationcar'/'treinstraat'/'treinweg'. Bekende false positives blijven geweerd: krant 'De Metro', café 'De Tram', atleet-'sprinter', werkwoord 'brengen', idioom 'goed met elkaar overweg kunnen', 'een wijk opwaarderen', brandweerjargon 'sein brandmeester', figuratief 'weer op de rails', achternaam 'Hermes', buitenlandse veerboot-/busrampen, 'restaurant/woning naast busstation'.
Tests
python3 validate_filter.py
Vaste invarianten + regressie (momenteel 110 cases):
- gewichten geldig (sterk 2; ambigu in {1, 2}), drempel 3;
- trefwoorden matchen hooguit één groep per categorie (geen overlappende regexes);
- semantische varianten ('bus' + 'buslijn' + 'busvervoer', 'station'-reeks) leveren samen één signaal op;
- regressie: bekende positive/negative/combinatie-cases (precision-first).
Verplicht vóór elke filterwijziging (zie AGENTS.md): het classificatiebeleid in
buzzrss/bot.py lezen, validate_filter.py groen, en de compleetheids-audit:
OV-gap-scan over de afgewezen artikelen van de specialist-feeds (SpoorPro,
Treinreiziger, Mobiliteit; query op entry in PostgreSQL-schema mbp_rss),
nieuwe woordkandidaten klasseer je volgens het beleid (minimaal één extra
taalmodel; enige stem 'TWIJFEL'/'NIET-OV' ⇒ zwak).
Configuratie
Zie base-config.yaml (wordt in het gebouwde .mbp meegeleverd):
| Sleutel | Default | Werking |
|---|---|---|
update_interval |
60 | Poll-interval in minuten |
max_backoff |
7200 | Maximale backoff (minuten) bij faalende feeds |
poll_parallelism_limit |
10 | Maximaal aantal feeds tegelijk pollend |
spam_sleep |
2 | Pauze tussen berichten bij broadcast (‑1 = parallel) |
command_prefix |
rss |
Commando-prefix |
notification_template |
New post in $feed_title: [$title]($link) |
Standaard meldingssjabloon |
run_log_file |
/data/buzzrss-runs.log |
Per-run samenvattingsregel (leeg = uit) |
whatsapp_api / whatsapp_session / whatsapp_chat_id |
– | WhatsApp-forward (leeg adapter = uit) |
exclude_paywalled |
true | Betaalmuur-artikelen overslaan |
title_dedup_days |
30 | Cross-feed titel-dedup-venster (0 = uit) |
admins |
– | Gebruikers die room-permissies mogen omzeilen |
Deploy
Productie draait het geïnstalleerde .mbp (maubot), niet deze werkdirectory —
een commit is pas actief na build + upload + restart. Exacte recepten staan in de
maubot-beheerskill (skill://maubot) en de Obsidian-vault (Matrix Synapse en MAS.md).
In het kort:
1. mbc build uit de VOLLEDIGE bron (buzzrss/ + maubot.yaml + base-config.yaml —
zonder base-config.yaml faalt de instance met BaseMissingError)
2. POST /_matrix/maubot/v1/plugins/upload?allow_override=true (admin-login)
3. docker restart matrix-bot-maubot
NOOIT maubot reload gebruiken — die laat zombie-commandhandlers achter
(één commando → N antwoorden). Alleen een proces-herstart wist de handlers.
Architectuur in het kort
- Poll-loop → per feed conditional-fetch of HTML-scrape → nieuwe entries
(
feed_id+idPK-dedup) → cross-feed titel-dedup →_filter_passes→ broadcast naar alle subscriptions + optioneel WhatsApp (_send_whatsapp). - Persistente tabel
whatsapp_postedvoor WhatsApp-dedup. - DB: PostgreSQL-schema
mbp_rss(feed,entry,subscription,whatsapp_posted); migraties via maubot'sUpgradeTable(buzzrss/migrations.py).
Licentie
AGPL-3.0-or-later (upstream: maubot/rss, © Tulir Asokan).