Resolve Arr metadata before adding media
Magent CI/CD / verify (push) Successful in 11m12s
Magent CI/CD / deploy-prod (push) Skipped
Magent CI/CD / deploy-beta (push) Successful in 14s

This commit is contained in:
2026-08-30 16:32:14 +12:00
parent 3815dfea60
commit 9db32481bd
4 changed files with 126 additions and 10 deletions
+10
View File
@@ -9,6 +9,10 @@ class RadarrClient(ApiClient):
async def get_movie_by_tmdb_id(self, tmdb_id: int) -> Optional[Dict[str, Any]]:
return await self.get("/api/v3/movie", params={"tmdbId": tmdb_id})
async def lookup_movie_by_tmdb_id(self, tmdb_id: int) -> Optional[Dict[str, Any]]:
result = await self.get("/api/v3/movie/lookup/tmdb", params={"tmdbId": tmdb_id})
return result if isinstance(result, dict) else None
async def get_movie(self, movie_id: int) -> Optional[Dict[str, Any]]:
return await self.get(f"/api/v3/movie/{movie_id}")
@@ -42,9 +46,15 @@ class RadarrClient(ApiClient):
root_folder: str,
monitored: bool = True,
search_for_movie: bool = True,
title: Optional[str] = None,
) -> Optional[Dict[str, Any]]:
lookup = await self.lookup_movie_by_tmdb_id(tmdb_id)
resolved_title = str((lookup or {}).get("title") or "").strip() or (title or "").strip()
if not resolved_title:
raise ValueError("Radarr could not resolve a title for this TMDB ID")
payload = {
"tmdbId": tmdb_id,
"title": resolved_title,
"qualityProfileId": quality_profile_id,
"rootFolderPath": root_folder,
"monitored": monitored,
+19 -2
View File
@@ -9,6 +9,20 @@ class SonarrClient(ApiClient):
async def get_series_by_tvdb_id(self, tvdb_id: int) -> Optional[Dict[str, Any]]:
return await self.get("/api/v3/series", params={"tvdbId": tvdb_id})
async def lookup_series_by_tvdb_id(self, tvdb_id: int) -> Optional[Dict[str, Any]]:
result = await self.get("/api/v3/series/lookup", params={"term": f"tvdb:{tvdb_id}"})
if not isinstance(result, list):
return None
for item in result:
if not isinstance(item, dict):
continue
try:
if int(item.get("tvdbId")) == tvdb_id:
return item
except (TypeError, ValueError):
continue
return next((item for item in result if isinstance(item, dict)), None)
async def get_series(self, series_id: int) -> Optional[Dict[str, Any]]:
return await self.get(f"/api/v3/series/{series_id}")
@@ -49,16 +63,19 @@ class SonarrClient(ApiClient):
title: Optional[str] = None,
search_missing: bool = True,
) -> Optional[Dict[str, Any]]:
lookup = await self.lookup_series_by_tvdb_id(tvdb_id)
resolved_title = str((lookup or {}).get("title") or "").strip() or (title or "").strip()
if not resolved_title:
raise ValueError("Sonarr could not resolve a title for this TVDB ID")
payload = {
"tvdbId": tvdb_id,
"title": resolved_title,
"qualityProfileId": quality_profile_id,
"rootFolderPath": root_folder,
"monitored": monitored,
"seasonFolder": True,
"addOptions": {"searchForMissingEpisodes": search_missing},
}
if title:
payload["title"] = title
return await self.post("/api/v3/series", payload=payload)
async def update_series(self, payload: Dict[str, Any]) -> Optional[Dict[str, Any]]:
+36 -8
View File
@@ -337,17 +337,25 @@ def _extract_requested_by_id(request_data: Any) -> Optional[int]:
def _format_upstream_error(service: str, exc: httpx.HTTPStatusError) -> str:
response = exc.response
status = response.status_code if response is not None else "unknown"
body = ""
message = ""
if response is not None:
try:
payload = response.json()
body = json.dumps(payload, ensure_ascii=True)
if isinstance(payload, dict):
message = str(payload.get("message") or payload.get("error") or "").strip()
elif isinstance(payload, list):
validation_messages = [
str(item.get("errorMessage") or item.get("message") or "").strip()
for item in payload
if isinstance(item, dict)
]
message = "; ".join(item for item in validation_messages if item)
except ValueError:
body = response.text
body = body.strip() if body else ""
if body:
return f"{service} error {status}: {body}"
return f"{service} error {status}."
message = response.text.strip()
if message:
compact_message = " ".join(message.split())[:500]
return f"{service} could not complete the request ({status}): {compact_message}"
return f"{service} could not complete the request ({status})."
def _request_display_name(request_data: Any) -> Optional[str]:
@@ -2318,6 +2326,12 @@ async def action_readd(request_id: str, user: Dict[str, str] = Depends(get_curre
save_action, request_id, "readd_to_arr", "Re-add to Sonarr/Radarr", "failed", detail
)
raise HTTPException(status_code=502, detail=detail) from exc
except ValueError as exc:
detail = f"Sonarr could not add this series: {exc}"
await asyncio.to_thread(
save_action, request_id, "readd_to_arr", "Re-add to Sonarr/Radarr", "failed", detail
)
raise HTTPException(status_code=502, detail=detail) from exc
await asyncio.to_thread(
save_action,
request_id,
@@ -2353,9 +2367,17 @@ async def action_readd(request_id: str, user: Dict[str, str] = Depends(get_curre
)
return {"status": "ok", "message": message, "movieId": movie_id}
root_folder = await _resolve_root_folder_path(client, runtime.radarr_root_folder, "Radarr")
title = snapshot.title
if title in {None, "", "Unknown"}:
title = (
media.get("title")
or media.get("name")
or jelly.get("title")
or jelly.get("name")
)
try:
response = await client.add_movie(
int(tmdb_id), runtime.radarr_quality_profile_id, root_folder
int(tmdb_id), runtime.radarr_quality_profile_id, root_folder, title=title
)
except httpx.HTTPStatusError as exc:
detail = _format_upstream_error("Radarr", exc)
@@ -2363,6 +2385,12 @@ async def action_readd(request_id: str, user: Dict[str, str] = Depends(get_curre
save_action, request_id, "readd_to_arr", "Re-add to Sonarr/Radarr", "failed", detail
)
raise HTTPException(status_code=502, detail=detail) from exc
except ValueError as exc:
detail = f"Radarr could not add this movie: {exc}"
await asyncio.to_thread(
save_action, request_id, "readd_to_arr", "Re-add to Sonarr/Radarr", "failed", detail
)
raise HTTPException(status_code=502, detail=detail) from exc
await asyncio.to_thread(
save_action,
request_id,