Plugin neuheiten: Bearer-Token-Pflicht der BGG-XML-API2 (Fix für HTTP 401)

BoardGameGeek verlangt für die XML API2 jetzt Bearer-Authentifizierung
(Thread 3602374); ohne Token antwortet die API mit 401.

- BggClient: neuer Parameter token, sendet Authorization: Bearer … auf
  allen Requests; Token kommt aus SPIELE_BGG_TOKEN (vor jedem Sync gelesen)
- HTTP 401 → klarer BggAuthFehler ('BGG hat die Anfrage abgelehnt (401) —
  bitte gültigen API-Token in SPIELE_BGG_TOKEN hinterlegen.') statt
  generischer Meldung, kein Retry
- Ohne konfigurierten Token wird der Sync übersprungen ('Kein BGG-API-Token
  konfiguriert (SPIELE_BGG_TOKEN) — Sync übersprungen.'), statt Requests zu
  feuern; Meldung erscheint im Sync-Ergebnis, UI-Banner und Protokoll
- SyncService: BggAuthFehler bricht den Lauf ab (weitere Suchbegriffe
  scheitern zwangsläufig gleich), Teilergebnisse bleiben erhalten
- Tests: Bearer-Header, 401-Fall (Client/Sync/UI), Skip ohne Token;
  bestehende Sync-UI-Tests setzen Test-Token
- README: neue Env-Variable SPIELE_BGG_TOKEN + Hinweis auf Token-Pflicht
This commit is contained in:
Flo Hartmann
2026-08-21 23:14:58 +00:00
parent 55f6f32f73
commit 60c4b6e818
8 changed files with 259 additions and 9 deletions

View File

@@ -17,7 +17,7 @@ from dataclasses import dataclass, field
from sqlalchemy import select
from sqlalchemy.orm import Session, sessionmaker
from .bgg import BggSpiel, BggClient
from .bgg import BggAuthFehler, BggSpiel, BggClient
from .models import STATUS_NEUHEIT, Neuheit
_logger = logging.getLogger("plugins.neuheiten")
@@ -42,8 +42,13 @@ class SyncErgebnis:
aktualisiert: int = 0
gefiltert: int = 0
fehler: list[str] = field(default_factory=list)
# Grund, warum der Lauf vorzeitig endete oder ganz übersprungen wurde
# (z. B. fehlender BGG-Token oder HTTP 401); None = normal gelaufen.
abbruch: str | None = None
def als_text(self) -> str:
if self.abbruch:
return self.abbruch
text = (
f"{self.neu} neu, {self.aktualisiert} aktualisiert, "
f"{self.gefiltert} gefiltert (Erweiterungen/Prototypen)"
@@ -101,6 +106,13 @@ class SyncService:
continue
try:
self._ein_suchbegriff(db, begriff, ergebnis)
except BggAuthFehler as exc:
# HTTP 401 trifft jede weitere Anfrage genauso — der Lauf
# wird abgebrochen, statt alle Begriffe einzeln scheitern
# zu lassen; bereits gespeicherte Treffer bleiben erhalten.
_logger.warning("BGG-Sync abgebrochen für %r: %s", begriff, exc)
ergebnis.abbruch = str(exc)
break
except Exception as exc: # ein Begriff darf den Lauf nicht abbrechen
_logger.warning("BGG-Sync fehlgeschlagen für %r: %s", begriff, exc)
ergebnis.fehler.append(f"{begriff}: {exc}")