v1.3.1: filter-score (titel 2x + summary 1x, drempel 2)

- losse 1-hit in summary volstaat niet meer: 'ns-trein' in een
  rechtszaak-artikel (stenengooier) telt als 1 cluster en wordt geblokkeerd
- cluster-aware teller: aaneengesloten matches (NS-trein, openbaar vervoer,
  pdf) samenvoegen
- bus(?:sen)? suffix: 'Meer bussen naar TU' komt terug
- root-cause config-corruptie: re.sub-replacement de-escaped \\b naar \b
  (YAML-backspace); nu regel-bewerking zonder escape-processing
This commit is contained in:
eddy 2026-09-05 20:32:20 +02:00
commit dff304ba56
3 changed files with 23 additions and 5 deletions

View file

@ -20,7 +20,7 @@ exclude_paywalled: true
# Global match filter (whitelist): applies to ALL feeds in this instance. # Global match filter (whitelist): applies to ALL feeds in this instance.
# A post is ONLY sent when the regex matches the title OR the (HTML-stripped) summary. # A post is ONLY sent when the regex matches the title OR the (HTML-stripped) summary.
# Set it live with `!rss set-filter <regex>`. Empty = send everything. # Set it live with `!rss set-filter <regex>`. Empty = send everything.
match_filter: "(?i)\\b(?:ns|arriva|qbuzz|connexxion|cxx|transdev|keolis|ret|gvb|ebs|htm|syntus|breng|u-ov|allgo|flixbus|blauwnet|r-net|valleilijn|waterbus|eurostar|thalys|randstadrail|gvu|ov)\\b|\\b(?:bus|tram|metro)(?:halte|haltes|lijn|lijnen|station|stations|verkeer|net|staking)?\\b|\\btrein(?:en|verkeer|stel(?:len)?|staking|ongeluk|reizigers?|station|stations)?\\b|\\b(?:spoorweg|spoorbaan|spoorlijn|spoorverkeer|spoorwegovergang|spoorstoring|perron|perrons|overweg|overwegen|seinstoring|wisselstoring|sein|ontsporing|ontspoord|spoorstaking)\\b|\\b(?:openbaar vervoer|dienstregeling|reizigers?|inchecken|uitchecken|pont|ponten|lightrail|pendelbus|shuttlebus)\\b|\\b(?:ov-chipkaart|chipkaart|ovpay|ov-pay|reisproduct|studentenreisproduct|vervoersbewijs|vervoerbewijs|opwaarderen)\\b|\\b(?:ov-staking|conducteursstaking)\\b" match_filter: "(?i)\\b(?:ns|arriva|qbuzz|connexxion|cxx|transdev|keolis|ret|gvb|ebs|htm|syntus|breng|u-ov|allgo|flixbus|blauwnet|r-net|valleilijn|waterbus|eurostar|thalys|randstadrail|gvu|ov)\\b|\\b(?:bus|tram|metro)(?:halte|haltes|lijn|lijnen|station|stations|verkeer|net|staking|sen)?\\b|\\btrein(?:en|verkeer|stel(?:len)?|staking|ongeluk|reizigers?|station|stations)?\\b|\\b(?:spoorweg|spoorbaan|spoorlijn|spoorverkeer|spoorwegovergang|spoorstoring|perron|perrons|overweg|overwegen|seinstoring|wisselstoring|sein|ontsporing|ontspoord|spoorstaking)\\b|\\b(?:openbaar vervoer|dienstregeling|reizigers?|inchecken|uitchecken|pont|ponten|lightrail|pendelbus|shuttlebus)\\b|\\b(?:ov-chipkaart|chipkaart|ovpay|ov-pay|reisproduct|studentenreisproduct|vervoersbewijs|vervoerbewijs|opwaarderen)\\b|\\b(?:ov-staking|conducteursstaking)\\b"
# Users who can bypass room permission checks # Users who can bypass room permission checks
admins: admins:
- "@user:example.com" - "@user:example.com"

View file

@ -51,7 +51,7 @@ rss_change_level = EventType.find("xyz.maubot.rss", t_class=EventType.Class.STAT
DEFAULT_MATCH_FILTER = ( DEFAULT_MATCH_FILTER = (
r"(?i)" r"(?i)"
r"\b(?:ns|arriva|qbuzz|connexxion|cxx|transdev|keolis|ret|gvb|ebs|htm|syntus|breng|u-ov|allgo|flixbus|blauwnet|r-net|valleilijn|waterbus|eurostar|thalys|randstadrail|gvu|ov)\b" r"\b(?:ns|arriva|qbuzz|connexxion|cxx|transdev|keolis|ret|gvb|ebs|htm|syntus|breng|u-ov|allgo|flixbus|blauwnet|r-net|valleilijn|waterbus|eurostar|thalys|randstadrail|gvu|ov)\b"
r"|\b(?:bus|tram|metro)(?:halte|haltes|lijn|lijnen|station|stations|verkeer|net|staking)?\b" r"|\b(?:bus|tram|metro)(?:halte|haltes|lijn|lijnen|station|stations|verkeer|net|staking|sen)?\b"
r"|\btrein(?:en|verkeer|stel(?:len)?|staking|ongeluk|reizigers?|station|stations)?\b" r"|\btrein(?:en|verkeer|stel(?:len)?|staking|ongeluk|reizigers?|station|stations)?\b"
r"|\b(?:spoorweg|spoorbaan|spoorlijn|spoorverkeer|spoorwegovergang|spoorstoring|perron|perrons|overweg|overwegen|seinstoring|wisselstoring|sein|ontsporing|ontspoord|spoorstaking)\b" r"|\b(?:spoorweg|spoorbaan|spoorlijn|spoorverkeer|spoorwegovergang|spoorstoring|perron|perrons|overweg|overwegen|seinstoring|wisselstoring|sein|ontsporing|ontspoord|spoorstaking)\b"
r"|\b(?:openbaar vervoer|dienstregeling|reizigers?|inchecken|uitchecken|pont|ponten|lightrail|pendelbus|shuttlebus)\b" r"|\b(?:openbaar vervoer|dienstregeling|reizigers?|inchecken|uitchecken|pont|ponten|lightrail|pendelbus|shuttlebus)\b"
@ -134,11 +134,29 @@ class RSSBot(Plugin):
except Exception: except Exception:
self.log.exception("Fatal error while polling feeds") self.log.exception("Fatal error while polling feeds")
@staticmethod
def _filter_score(title: str, summary: str, match_filter: str) -> int:
"""Score = 2× hits in titel + 1× hits in summary. Aaneengesloten matches
(bv 'NS-trein', 'openbaar vervoer') tellen als 1 cluster."""
def count(text: str) -> int:
spans = sorted((m.start(), m.end()) for m in re.finditer(match_filter, text))
if not spans:
return 0
clusters, prev_end = 1, spans[0][1]
for s, e in spans[1:]:
if s - prev_end <= 1:
prev_end = max(prev_end, e)
else:
clusters += 1
prev_end = e
return clusters
return 2 * count(title) + count(summary)
async def _send(self, feed: Feed, entry: Entry, sub: Subscription) -> EventID | None: async def _send(self, feed: Feed, entry: Entry, sub: Subscription) -> EventID | None:
match_filter = self.config["match_filter"] match_filter = self.config["match_filter"]
if match_filter: if match_filter:
haystack = f"{entry.title}\n{html.unescape(re.sub(r'<[^>]+>', '', entry.summary))}" summary = html.unescape(re.sub(r"<[^>]+>", "", entry.summary))
if not re.search(match_filter, haystack): if self._filter_score(entry.title, summary, match_filter) < 2:
return None return None
message = sub.notification_template.safe_substitute( message = sub.notification_template.safe_substitute(

View file

@ -1,5 +1,5 @@
id: nl.eddydevink.rss id: nl.eddydevink.rss
version: 1.3.0 version: 1.3.1
modules: modules:
- buzzrss - buzzrss
main_class: RSSBot main_class: RSSBot