From 674d0df703f06c861d876d3f089dda159524060e Mon Sep 17 00:00:00 2001 From: Clarth Date: Fri, 13 Nov 2026 20:37:28 -0500 Subject: [PATCH] Add group_alerts setting for per-run summary notifications Allow choosing between one alert per item (default) and one grouped summary alert per run when any items need attention via general.group_alerts. --- README.md | 1 + config.example.toml | 3 +++ config.py | 2 ++ runner.py | 22 +++++++++++++++++++++- 4 files changed, 27 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index d0be837..1fd1c68 100644 --- a/README.md +++ b/README.md @@ -59,6 +59,7 @@ There are two config files. Both are ignored by git, so your values stay on your | --- | --- | --- | | `general.realert_hours` | `12` | Hours before an open item is alerted again. `0` turns repeats off. | | `general.notify_cleared` | `true` | Send a message when an item is cleared. | +| `general.group_alerts` | `false` | `false`: one alert per item. `true`: one summary alert per run when anything needs attention. | | `general.request_timeout` | `15` | Seconds to wait for each HTTP request. | | `general.state_file` | `state.db` | Where the alert history is stored. | | `general.notifiers` | `["fluxer"]` | Notifiers to use. | diff --git a/config.example.toml b/config.example.toml index 29c785e..c68d08c 100644 --- a/config.example.toml +++ b/config.example.toml @@ -9,6 +9,9 @@ realert_hours = 12 # Send a message when an item is no longer open (cleared in the admin UI). notify_cleared = true +# Send one alert per item (false) or one summary alert per run when anything needs attention (true). +group_alerts = false + # Timeout in seconds for each request to the Fluxer API or webhook. request_timeout = 15 diff --git a/config.py b/config.py index 348aac6..f6ae51b 100644 --- a/config.py +++ b/config.py @@ -38,6 +38,7 @@ class Settings: request_timeout: float realert: timedelta | None notify_cleared: bool + group_alerts: bool state_file: Path notifiers: tuple[str, ...] fluxer_webhook_url: str | None @@ -90,6 +91,7 @@ def load_settings(config_path: Path) -> Settings: request_timeout=float(general.get("request_timeout", 15)), realert=timedelta(hours=realert_hours) if realert_hours > 0 else None, notify_cleared=bool(general.get("notify_cleared", True)), + group_alerts=bool(general.get("group_alerts", False)), state_file=PROJECT_ROOT / str(general.get("state_file", "state.db")), notifiers=notifiers, fluxer_webhook_url=fluxer_webhook_url, diff --git a/runner.py b/runner.py index 6d72253..82ca3c4 100644 --- a/runner.py +++ b/runner.py @@ -30,6 +30,23 @@ def _to_message(notice: Notice, admin_ui_url: str) -> Message: ) +def _group_messages(notices: list[Notice], admin_ui_url: str) -> list[Message]: + if not notices: + return [] + lines: list[str] = [] + urgent = False + for notice in notices: + label = KIND_PREFIX[notice.kind] + detail = notice.detail.replace("\n", " / ") + lines.append(f"{label}: {notice.title} - {detail}") + urgent = urgent or notice.urgent + title = f"Admin alerts: {len(notices)} item{'s' if len(notices) != 1 else ''} need attention" + body = "\n".join(lines) + if len(body) > 4000: + body = body[:3997] + "..." + return [Message(title=title, body=body, link=admin_ui_url, urgent=urgent)] + + def run(settings: Settings, dry_run: bool = False) -> int: api = FluxerAPI(settings.api_url, settings.api_key, settings.request_timeout) now = datetime.now(timezone.utc) @@ -57,7 +74,10 @@ def run(settings: Settings, dry_run: bool = False) -> int: try: known = store.load() plan = decide(now, seen, known, checked, settings.realert, settings.notify_cleared) - messages = [_to_message(n, settings.admin_ui_url) for n in plan.notices] + if settings.group_alerts: + messages = _group_messages(plan.notices, settings.admin_ui_url) + else: + messages = [_to_message(n, settings.admin_ui_url) for n in plan.notices] if dry_run: for message in messages: