Allow choosing between one alert per item (default) and one grouped summary alert per run when any items need attention via general.group_alerts.
103 lines
3.4 KiB
Python
103 lines
3.4 KiB
Python
from __future__ import annotations
|
|
|
|
import logging
|
|
from datetime import datetime, timezone
|
|
|
|
from config import Settings
|
|
from engine import Notice, decide
|
|
from fluxer_api import FluxerAPI, FluxerAPIError
|
|
from notifiers import NotifierError, Message, build_notifiers
|
|
from sources import SOURCES, Item
|
|
from state import StateStore
|
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
KIND_PREFIX = {
|
|
"new": "New",
|
|
"reminder": "Still open",
|
|
"repeat": "Reminder",
|
|
"cleared": "Cleared",
|
|
}
|
|
|
|
|
|
def _to_message(notice: Notice, admin_ui_url: str) -> Message:
|
|
link = f"{admin_ui_url}{notice.path}" if notice.path else admin_ui_url
|
|
return Message(
|
|
title=f"{KIND_PREFIX[notice.kind]}: {notice.title}",
|
|
body=notice.detail,
|
|
link=link,
|
|
urgent=notice.urgent,
|
|
)
|
|
|
|
|
|
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)
|
|
failures = 0
|
|
checked: set[str] = set()
|
|
seen: dict[str, tuple[str, Item]] = {}
|
|
|
|
for source in SOURCES:
|
|
cfg = settings.sources[source.name]
|
|
if not cfg.enabled:
|
|
log.debug("%s: disabled", source.name)
|
|
continue
|
|
try:
|
|
items = source.fetch(api, cfg)
|
|
except FluxerAPIError as exc:
|
|
log.error("%s: %s", source.name, exc)
|
|
failures += 1
|
|
continue
|
|
checked.add(source.name)
|
|
log.info("%s: %d open", source.name, len(items))
|
|
for item in items:
|
|
seen[item.key] = (source.name, item)
|
|
|
|
store = StateStore(settings.state_file)
|
|
try:
|
|
known = store.load()
|
|
plan = decide(now, seen, known, checked, settings.realert, settings.notify_cleared)
|
|
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:
|
|
log.info("would send: %s | %s | %s", message.title, message.body.replace("\n", " / "), message.link)
|
|
log.info("dry run: %d messages, %d state changes, nothing sent or saved", len(messages), len(plan.records))
|
|
return 1 if failures else 0
|
|
|
|
if messages:
|
|
try:
|
|
for notifier in build_notifiers(settings):
|
|
notifier.send(messages)
|
|
except NotifierError as exc:
|
|
log.error("%s; nothing was recorded, so these will be retried next run", exc)
|
|
return 1
|
|
log.info("sent %d messages", len(messages))
|
|
else:
|
|
log.info("nothing new to send")
|
|
|
|
store.save(plan.records)
|
|
finally:
|
|
store.close()
|
|
|
|
return 1 if failures else 0
|