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 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) 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