fluxer-admin-alerts/fluxer_admin_alerts/sources.py

131 lines
4.2 KiB
Python

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