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:
@@ -7,12 +7,17 @@ Umfang:
|
||||
- Manueller „Jetzt synchronisieren“-Button für Admins/Redakteure.
|
||||
- Sortier-/filterbare Neuheitenliste mit Suche (deutsche UI).
|
||||
|
||||
Konfiguration über Umgebungsvariablen (gelesen beim Plugin-Start):
|
||||
Konfiguration über Umgebungsvariablen (gelesen beim Plugin-Start;
|
||||
SPIELE_BGG_TOKEN wird dagegen vor jedem Sync gelesen):
|
||||
- SPIELE_BGG_SYNC_AKTIV „1“ (Standard) = Hintergrund-Job an
|
||||
- SPIELE_BGG_SYNC_INTERVALL_STUNDEN Intervall in Stunden (Standard 24)
|
||||
- SPIELE_BGG_SUCHBEGRIFFE Komma-getrennte Suchbegriffe
|
||||
(Standard: „brettspiel“)
|
||||
- SPIELE_BGG_MAX_TREFFER_PRO_SUCHE Obergrenze Treffer/Suchbegriff (25)
|
||||
- SPIELE_BGG_TOKEN API-Token für die BGG-XML-API2
|
||||
(Pflicht seit BGG-Umstellung, wird als
|
||||
„Authorization: Bearer …“ gesendet);
|
||||
ohne Token wird der Sync übersprungen
|
||||
|
||||
Keine Fachlogik im Kern — alles hier im Plugin gemäß Plugin-Vertrag.
|
||||
"""
|
||||
@@ -33,10 +38,14 @@ from redaktionskern.contracts import BasePlugin, Migration, NavEntry, PluginCont
|
||||
|
||||
from .bgg import BggClient, BggFehler
|
||||
from .models import Neuheit
|
||||
from .sync import SyncService
|
||||
from .sync import SyncErgebnis, SyncService
|
||||
|
||||
_logger = logging.getLogger("plugins.neuheiten")
|
||||
|
||||
MELDUNG_OHNE_TOKEN = (
|
||||
"Kein BGG-API-Token konfiguriert (SPIELE_BGG_TOKEN) — Sync übersprungen."
|
||||
)
|
||||
|
||||
SORTIERBAR = {
|
||||
"titel": Neuheit.titel,
|
||||
"verlag": Neuheit.verlag,
|
||||
@@ -151,7 +160,11 @@ class NeuheitenPlugin(BasePlugin):
|
||||
_logger.warning("Manueller BGG-Sync fehlgeschlagen: %s", exc)
|
||||
ziel = f"/neuheiten?meldung={quote(f'Sync fehlgeschlagen: {exc}')}"
|
||||
return RedirectResponse(ziel, status_code=303)
|
||||
ziel = f"/neuheiten?meldung={quote(f'Sync abgeschlossen: {ergebnis.als_text()}')}"
|
||||
if ergebnis.abbruch:
|
||||
# z. B. fehlender Token oder HTTP 401 — klar benennbare Ursache
|
||||
ziel = f"/neuheiten?fehler={quote(ergebnis.abbruch)}"
|
||||
else:
|
||||
ziel = f"/neuheiten?meldung={quote(f'Sync abgeschlossen: {ergebnis.als_text()}')}"
|
||||
return RedirectResponse(ziel, status_code=303)
|
||||
|
||||
# ---------- Plugin-Vertrag ----------
|
||||
@@ -188,11 +201,26 @@ class NeuheitenPlugin(BasePlugin):
|
||||
|
||||
# ---------- Internas ----------
|
||||
|
||||
def _aktives_bgg_token(self) -> str | None:
|
||||
"""Liest den BGG-API-Token aus der Umgebung (leer = nicht gesetzt).
|
||||
|
||||
Bewusst pro Sync gelesen, damit ein rotierter Token ohne Neustart
|
||||
greift.
|
||||
"""
|
||||
import os
|
||||
|
||||
return (os.environ.get("SPIELE_BGG_TOKEN") or "").strip() or None
|
||||
|
||||
def _neuer_client(self) -> BggClient:
|
||||
"""Fabrik für den BGG-Client; von Tests überschreibbar."""
|
||||
return BggClient()
|
||||
return BggClient(token=self._aktives_bgg_token())
|
||||
|
||||
def _sync_ausfuehren(self, suchbegriffe: list[str]):
|
||||
if self._aktives_bgg_token() is None:
|
||||
# Ohne Token lehnt die BGG-API jeden Request mit 401 ab —
|
||||
# den Sync gar nicht erst starten.
|
||||
_logger.warning(MELDUNG_OHNE_TOKEN)
|
||||
return SyncErgebnis(abbruch=MELDUNG_OHNE_TOKEN)
|
||||
client = self._neuer_client()
|
||||
try:
|
||||
service = SyncService(
|
||||
@@ -211,7 +239,10 @@ class NeuheitenPlugin(BasePlugin):
|
||||
return
|
||||
try:
|
||||
ergebnis = self._sync_ausfuehren(self._suchbegriffe)
|
||||
_logger.info("BGG-Sync abgeschlossen: %s", ergebnis.als_text())
|
||||
if ergebnis.abbruch:
|
||||
_logger.warning("BGG-Sync nicht ausgeführt: %s", ergebnis.abbruch)
|
||||
else:
|
||||
_logger.info("BGG-Sync abgeschlossen: %s", ergebnis.als_text())
|
||||
except Exception as exc:
|
||||
_logger.warning("BGG-Sync fehlgeschlagen: %s", exc)
|
||||
finally:
|
||||
|
||||
Reference in New Issue
Block a user