Fix shared request access and Jellyfin-ready pipeline status
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
from typing import Any, Dict, List, Optional
|
||||
import asyncio
|
||||
import logging
|
||||
import re
|
||||
from datetime import datetime, timezone
|
||||
from urllib.parse import quote
|
||||
import httpx
|
||||
@@ -57,6 +58,100 @@ def _pick_first(value: Any) -> Optional[Dict[str, Any]]:
|
||||
return None
|
||||
|
||||
|
||||
def _normalize_media_title(value: Any) -> Optional[str]:
|
||||
if not isinstance(value, str):
|
||||
return None
|
||||
normalized = re.sub(r"[^a-z0-9]+", " ", value.lower()).strip()
|
||||
return normalized or None
|
||||
|
||||
|
||||
def _canonical_provider_key(value: str) -> str:
|
||||
normalized = value.strip().lower()
|
||||
if normalized.endswith("id"):
|
||||
normalized = normalized[:-2]
|
||||
return normalized
|
||||
|
||||
|
||||
def extract_request_provider_ids(payload: Any) -> Dict[str, str]:
|
||||
provider_ids: Dict[str, str] = {}
|
||||
candidates: List[Any] = []
|
||||
if isinstance(payload, dict):
|
||||
candidates.append(payload)
|
||||
media = payload.get("media")
|
||||
if isinstance(media, dict):
|
||||
candidates.append(media)
|
||||
for candidate in candidates:
|
||||
if not isinstance(candidate, dict):
|
||||
continue
|
||||
embedded = candidate.get("ProviderIds") or candidate.get("providerIds")
|
||||
if isinstance(embedded, dict):
|
||||
for key, value in embedded.items():
|
||||
if value is None:
|
||||
continue
|
||||
text = str(value).strip()
|
||||
if text:
|
||||
provider_ids[_canonical_provider_key(str(key))] = text
|
||||
for key in ("tmdbId", "tvdbId", "imdbId", "tmdb_id", "tvdb_id", "imdb_id"):
|
||||
value = candidate.get(key)
|
||||
if value is None:
|
||||
continue
|
||||
text = str(value).strip()
|
||||
if text:
|
||||
provider_ids[_canonical_provider_key(key)] = text
|
||||
return provider_ids
|
||||
|
||||
|
||||
def jellyfin_item_matches_request(
|
||||
item: Dict[str, Any],
|
||||
*,
|
||||
title: Optional[str],
|
||||
year: Optional[int],
|
||||
request_type: RequestType,
|
||||
request_payload: Optional[Dict[str, Any]] = None,
|
||||
) -> bool:
|
||||
request_provider_ids = extract_request_provider_ids(request_payload or {})
|
||||
item_provider_ids = extract_request_provider_ids(item)
|
||||
|
||||
provider_priority = ("tmdb", "tvdb", "imdb")
|
||||
for key in provider_priority:
|
||||
request_id = request_provider_ids.get(key)
|
||||
item_id = item_provider_ids.get(key)
|
||||
if request_id and item_id and request_id == item_id:
|
||||
return True
|
||||
|
||||
request_title = _normalize_media_title(title)
|
||||
if not request_title:
|
||||
return False
|
||||
|
||||
item_titles = [
|
||||
_normalize_media_title(item.get("Name")),
|
||||
_normalize_media_title(item.get("OriginalTitle")),
|
||||
_normalize_media_title(item.get("SortName")),
|
||||
_normalize_media_title(item.get("SeriesName")),
|
||||
_normalize_media_title(item.get("title")),
|
||||
]
|
||||
item_titles = [candidate for candidate in item_titles if candidate]
|
||||
|
||||
item_year = item.get("ProductionYear") or item.get("Year")
|
||||
try:
|
||||
item_year_value = int(item_year) if item_year is not None else None
|
||||
except (TypeError, ValueError):
|
||||
item_year_value = None
|
||||
|
||||
if year and item_year_value and int(year) != item_year_value:
|
||||
return False
|
||||
|
||||
if request_title in item_titles:
|
||||
return True
|
||||
|
||||
if request_type == RequestType.tv:
|
||||
for candidate in item_titles:
|
||||
if candidate and (candidate.startswith(request_title) or request_title.startswith(candidate)):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def _extract_http_error_message(exc: httpx.HTTPStatusError) -> Optional[str]:
|
||||
response = exc.response
|
||||
if response is None:
|
||||
@@ -513,7 +608,7 @@ async def build_snapshot(request_id: str) -> Snapshot:
|
||||
if jellyfin.configured() and snapshot.title:
|
||||
types = ["Movie"] if snapshot.request_type == RequestType.movie else ["Series"]
|
||||
try:
|
||||
search = await jellyfin.search_items(snapshot.title, types)
|
||||
search = await jellyfin.search_items(snapshot.title, types, limit=50)
|
||||
except Exception:
|
||||
search = None
|
||||
if isinstance(search, dict):
|
||||
@@ -521,11 +616,13 @@ async def build_snapshot(request_id: str) -> Snapshot:
|
||||
for item in items:
|
||||
if not isinstance(item, dict):
|
||||
continue
|
||||
name = item.get("Name") or item.get("title")
|
||||
year = item.get("ProductionYear") or item.get("Year")
|
||||
if name and name.strip().lower() == (snapshot.title or "").strip().lower():
|
||||
if snapshot.year and year and int(year) != int(snapshot.year):
|
||||
continue
|
||||
if jellyfin_item_matches_request(
|
||||
item,
|
||||
title=snapshot.title,
|
||||
year=snapshot.year,
|
||||
request_type=snapshot.request_type,
|
||||
request_payload=jelly_request,
|
||||
):
|
||||
jellyfin_available = True
|
||||
jellyfin_item = item
|
||||
break
|
||||
@@ -646,12 +743,22 @@ async def build_snapshot(request_id: str) -> Snapshot:
|
||||
snapshot.state = NormalizedState.added_to_arr
|
||||
snapshot.state_reason = "Item is present in Sonarr/Radarr"
|
||||
|
||||
if jellyfin_available and snapshot.state not in {
|
||||
NormalizedState.downloading,
|
||||
NormalizedState.importing,
|
||||
}:
|
||||
snapshot.state = NormalizedState.completed
|
||||
snapshot.state_reason = "Ready to watch in Jellyfin."
|
||||
if jellyfin_available:
|
||||
missing_episodes = arr_details.get("missingEpisodes")
|
||||
if snapshot.request_type == RequestType.tv and isinstance(missing_episodes, dict) and missing_episodes:
|
||||
snapshot.state = NormalizedState.importing
|
||||
snapshot.state_reason = "Some episodes are available in Jellyfin, but the request is still incomplete."
|
||||
for hop in timeline:
|
||||
if hop.service == "Seerr":
|
||||
hop.status = "Partially ready"
|
||||
else:
|
||||
snapshot.state = NormalizedState.completed
|
||||
snapshot.state_reason = "Ready to watch in Jellyfin."
|
||||
for hop in timeline:
|
||||
if hop.service == "Seerr":
|
||||
hop.status = "Available"
|
||||
elif hop.service == "Sonarr/Radarr" and hop.status not in {"error"}:
|
||||
hop.status = "available"
|
||||
|
||||
snapshot.timeline = timeline
|
||||
actions: List[ActionOption] = []
|
||||
|
||||
Reference in New Issue
Block a user