from __future__ import annotations from collections.abc import Callable from dataclasses import dataclass from datetime import datetime, timedelta, timezone from typing import Any from config import SourceSettings from fluxer_api import FluxerAPI PAGE_SIZE = 200 MAX_PAGES = 50 @dataclass(frozen=True) class Item: key: str title: str detail: str path: str | None = None urgent: bool = False opened_at: datetime | None = None reminder_after: timedelta | None = None FetchFunc = Callable[[FluxerAPI, SourceSettings], list[Item]] def _parse_time(value: Any) -> datetime | None: if not isinstance(value, str): return None try: parsed = datetime.fromisoformat(value.replace("Z", "+00:00")) except ValueError: return None if parsed.tzinfo is None: parsed = parsed.replace(tzinfo=timezone.utc) return parsed def fetch_pending_reports(api: FluxerAPI, cfg: SourceSettings) -> list[Item]: items: list[Item] = [] reminder_after = timedelta(hours=cfg.stale_after_hours) if cfg.stale_after_hours else None offset = 0 for _ in range(MAX_PAGES): data = api.get( "/admin/reports", {"status": "pending", "limit": PAGE_SIZE, "offset": offset}, ) reports = data.get("reports", []) for report in reports: report_id = str(report["report_id"]) reason = report.get("reason_label") or report.get("category") or "no reason given" items.append( Item( key=f"report:{report_id}", title="Report waiting for review", detail=f"Reason: {reason}", urgent=bool(report.get("reason_highest_priority")), opened_at=_parse_time(report.get("reported_at")), reminder_after=reminder_after, ) ) offset += len(reports) if not reports or offset >= int(data.get("total", 0)): break return items def fetch_pending_registrations(api: FluxerAPI, cfg: SourceSettings) -> list[Item]: data = api.get("/admin/instance/pending-registrations") items: list[Item] = [] for entry in data.get("pending_registrations", []): user_id = str(entry["user_id"]) name = entry.get("username") or user_id items.append( Item( key=f"registration:{user_id}", title="Account waiting for approval", detail=f"User: {name}", ) ) return items def fetch_discovery_applications(api: FluxerAPI, cfg: SourceSettings) -> list[Item]: data = api.get("/admin/discovery/applications") applications = data if isinstance(data, list) else data.get("applications", []) items: list[Item] = [] for entry in applications: guild_id = str(entry["guild_id"]) name = entry.get("guild_name") or guild_id items.append( Item( key=f"discovery:{guild_id}", title="Community waiting for discovery approval", detail=f"Community: {name}", ) ) return items def fetch_enforcement_review(api: FluxerAPI, cfg: SourceSettings) -> list[Item]: data = api.get("/admin/enforcement-decisions", {"review_queue": "true", "limit": 100}) items: list[Item] = [] for entry in data.get("decisions", []): decision_id = str(entry["decision_id"]) kind = entry.get("kind", "decision") subject = entry.get("subject_name") or entry.get("subject_id") or "unknown" items.append( Item( key=f"enforcement:{decision_id}", title="Automated enforcement needs review", detail=f"{kind} on {entry.get('subject_type', 'unknown')} {subject}", ) ) return items @dataclass(frozen=True) class Source: name: str fetch: FetchFunc SOURCES: tuple[Source, ...] = ( Source("pending_reports", fetch_pending_reports), Source("pending_registrations", fetch_pending_registrations), Source("discovery_applications", fetch_discovery_applications), Source("enforcement_review", fetch_enforcement_review), )