Persist Seerr media failure suppression and reduce sync error noise
This commit is contained in:
@@ -3,6 +3,7 @@ import asyncio
|
||||
import logging
|
||||
from datetime import datetime, timezone
|
||||
from urllib.parse import quote
|
||||
import httpx
|
||||
|
||||
from ..clients.jellyseerr import JellyseerrClient
|
||||
from ..clients.jellyfin import JellyfinClient
|
||||
@@ -18,6 +19,9 @@ from ..db import (
|
||||
get_recent_snapshots,
|
||||
get_setting,
|
||||
set_setting,
|
||||
is_seerr_media_failure_suppressed,
|
||||
record_seerr_media_failure,
|
||||
clear_seerr_media_failure,
|
||||
)
|
||||
from ..models import ActionOption, NormalizedState, RequestType, Snapshot, TimelineHop
|
||||
|
||||
@@ -53,6 +57,59 @@ def _pick_first(value: Any) -> Optional[Dict[str, Any]]:
|
||||
return None
|
||||
|
||||
|
||||
def _extract_http_error_message(exc: httpx.HTTPStatusError) -> Optional[str]:
|
||||
response = exc.response
|
||||
if response is None:
|
||||
return None
|
||||
try:
|
||||
payload = response.json()
|
||||
except ValueError:
|
||||
payload = response.text
|
||||
if isinstance(payload, dict):
|
||||
message = payload.get("message") or payload.get("error")
|
||||
return str(message).strip() if message else str(payload)
|
||||
if isinstance(payload, str):
|
||||
trimmed = payload.strip()
|
||||
return trimmed or None
|
||||
return str(payload)
|
||||
|
||||
|
||||
def _should_persist_seerr_media_failure(exc: httpx.HTTPStatusError) -> bool:
|
||||
response = exc.response
|
||||
if response is None:
|
||||
return False
|
||||
return response.status_code == 404 or response.status_code >= 500
|
||||
|
||||
|
||||
async def _get_seerr_media_details(
|
||||
jellyseerr: JellyseerrClient, request_type: RequestType, tmdb_id: int
|
||||
) -> Optional[Dict[str, Any]]:
|
||||
media_type = request_type.value
|
||||
if media_type not in {"movie", "tv"}:
|
||||
return None
|
||||
if is_seerr_media_failure_suppressed(media_type, tmdb_id):
|
||||
logger.debug("Seerr snapshot hydration suppressed: media_type=%s tmdb_id=%s", media_type, tmdb_id)
|
||||
return None
|
||||
try:
|
||||
if request_type == RequestType.movie:
|
||||
details = await jellyseerr.get_movie(int(tmdb_id))
|
||||
else:
|
||||
details = await jellyseerr.get_tv(int(tmdb_id))
|
||||
except httpx.HTTPStatusError as exc:
|
||||
if _should_persist_seerr_media_failure(exc):
|
||||
record_seerr_media_failure(
|
||||
media_type,
|
||||
int(tmdb_id),
|
||||
status_code=exc.response.status_code if exc.response is not None else None,
|
||||
error_message=_extract_http_error_message(exc),
|
||||
)
|
||||
return None
|
||||
if isinstance(details, dict):
|
||||
clear_seerr_media_failure(media_type, int(tmdb_id))
|
||||
return details
|
||||
return None
|
||||
|
||||
|
||||
async def _maybe_refresh_jellyfin(snapshot: Snapshot) -> None:
|
||||
if snapshot.state not in {NormalizedState.available, NormalizedState.completed}:
|
||||
return
|
||||
@@ -300,33 +357,22 @@ async def build_snapshot(request_id: str) -> Snapshot:
|
||||
if snapshot.title in {None, "", "Unknown"} and allow_remote:
|
||||
tmdb_id = jelly_request.get("media", {}).get("tmdbId")
|
||||
if tmdb_id:
|
||||
try:
|
||||
details = await _get_seerr_media_details(jellyseerr, snapshot.request_type, int(tmdb_id))
|
||||
if isinstance(details, dict):
|
||||
if snapshot.request_type == RequestType.movie:
|
||||
details = await jellyseerr.get_movie(int(tmdb_id))
|
||||
if isinstance(details, dict):
|
||||
snapshot.title = details.get("title") or snapshot.title
|
||||
release_date = details.get("releaseDate")
|
||||
snapshot.year = int(release_date[:4]) if release_date else snapshot.year
|
||||
poster_path = poster_path or details.get("posterPath") or details.get("poster_path")
|
||||
backdrop_path = (
|
||||
backdrop_path
|
||||
or details.get("backdropPath")
|
||||
or details.get("backdrop_path")
|
||||
)
|
||||
snapshot.title = details.get("title") or snapshot.title
|
||||
release_date = details.get("releaseDate")
|
||||
snapshot.year = int(release_date[:4]) if release_date else snapshot.year
|
||||
elif snapshot.request_type == RequestType.tv:
|
||||
details = await jellyseerr.get_tv(int(tmdb_id))
|
||||
if isinstance(details, dict):
|
||||
snapshot.title = details.get("name") or details.get("title") or snapshot.title
|
||||
first_air = details.get("firstAirDate")
|
||||
snapshot.year = int(first_air[:4]) if first_air else snapshot.year
|
||||
poster_path = poster_path or details.get("posterPath") or details.get("poster_path")
|
||||
backdrop_path = (
|
||||
backdrop_path
|
||||
or details.get("backdropPath")
|
||||
or details.get("backdrop_path")
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
snapshot.title = details.get("name") or details.get("title") or snapshot.title
|
||||
first_air = details.get("firstAirDate")
|
||||
snapshot.year = int(first_air[:4]) if first_air else snapshot.year
|
||||
poster_path = poster_path or details.get("posterPath") or details.get("poster_path")
|
||||
backdrop_path = (
|
||||
backdrop_path
|
||||
or details.get("backdropPath")
|
||||
or details.get("backdrop_path")
|
||||
)
|
||||
|
||||
cache_mode = (runtime.artwork_cache_mode or "remote").lower()
|
||||
snapshot.artwork = {
|
||||
|
||||
Reference in New Issue
Block a user