Clarify Sonarr search download behavior
This commit is contained in:
@@ -165,6 +165,11 @@ Der erlaubte Schreibablauf ist eng auf fehlende Episoden begrenzt:
|
||||
das passende Ticket. Erst dann startet Sonarr eine `EpisodeSearch` über die
|
||||
dort konfigurierten Indexer.
|
||||
|
||||
`EpisodeSearch` ist keine bloße Ergebnisvorschau: Sonarr kann dabei sofort das
|
||||
beste akzeptierte Release pro Episode an den Download-Client übergeben. Das
|
||||
gilt auch für explizit ausgewählte, momentan nicht überwachte Episoden;
|
||||
Monitoring steuert vor allem die spätere automatische/RSS-Verarbeitung.
|
||||
|
||||
Der Ablauf ändert weder Serien- noch Staffel-Monitoring und erlaubt weder
|
||||
beliebige Commands noch direkte URL-Downloads. Tickets gelten zehn Minuten,
|
||||
sind einmalig und an genau die angezeigte Auswahl gebunden.
|
||||
@@ -176,6 +181,9 @@ bestimmten Releases. Wenn ein Benutzer etwa ausdrücklich ein FuN-Staffelpaket
|
||||
verlangt, gilt stattdessen dieser Ablauf:
|
||||
|
||||
1. `search_releases` sucht ausschließlich über Sonarrs konfigurierte Indexer.
|
||||
Für Gruppen- oder Staffelpaketfragen werden `release_group` und
|
||||
`season_pack_only=true` direkt gesetzt; vorhandene Bibliotheksdateien sind
|
||||
kein Beleg dafür, was aktuell auf den Indexern verfügbar ist.
|
||||
2. `preview_release_grab` bekommt Serien-ID, Staffel und die **exakte GUID** des
|
||||
ausgewählten Suchergebnisses. Sonarr wird erneut abgefragt; Titel, Größe,
|
||||
Indexer, Ablehnungsgründe und vorhandene Episodendateien werden angezeigt.
|
||||
|
||||
@@ -183,7 +183,7 @@ async def _find_series(client: Any, kwargs: dict[str, Any]) -> dict[str, Any]:
|
||||
str(item.get(field, "")) for field in ("title", "sortTitle", "originalTitle", "alternateTitles")
|
||||
).casefold()
|
||||
if all(word in haystack for word in words):
|
||||
matches.append(_compact_series(item, include_seasons=True))
|
||||
matches.append(_compact_series(item, include_seasons=False))
|
||||
return {
|
||||
"query": query,
|
||||
"matches": matches[:limit],
|
||||
@@ -245,6 +245,7 @@ async def _search_releases(client: Any, kwargs: dict[str, Any]) -> dict[str, Any
|
||||
episode_id = kwargs.get("episode_id")
|
||||
season_number = kwargs.get("season_number")
|
||||
release_group = str(kwargs.get("release_group", "")).strip()
|
||||
season_pack_only = kwargs.get("season_pack_only") is True
|
||||
if series_id is None and episode_id is None:
|
||||
raise ValueError("search_releases requires series_id or episode_id")
|
||||
query: dict[str, Any] = {}
|
||||
@@ -265,6 +266,22 @@ async def _search_releases(client: Any, kwargs: dict[str, Any]) -> dict[str, Any
|
||||
str(item.get("releaseGroup", "")) + " " + str(item.get("title", ""))
|
||||
).casefold()
|
||||
]
|
||||
if season_pack_only:
|
||||
if season_number is None:
|
||||
raise ValueError("season_pack_only=true requires season_number")
|
||||
season_token = rf"(?:^|[. _-])S0*{int(season_number)}(?:[. _-]|$)"
|
||||
episode_token = rf"S0*{int(season_number)}E\d+"
|
||||
raw = [
|
||||
item for item in raw
|
||||
if isinstance(item, dict)
|
||||
and (
|
||||
item.get("fullSeason") is True
|
||||
or (
|
||||
re.search(season_token, str(item.get("title", "")), re.IGNORECASE)
|
||||
and not re.search(episode_token, str(item.get("title", "")), re.IGNORECASE)
|
||||
)
|
||||
)
|
||||
] if isinstance(raw, list) else raw
|
||||
compact = _compact_result("get_release", raw)
|
||||
return {
|
||||
"task_complete": True,
|
||||
@@ -273,6 +290,7 @@ async def _search_releases(client: Any, kwargs: dict[str, Any]) -> dict[str, Any
|
||||
"episode_id": episode_id,
|
||||
"season_number": season_number,
|
||||
"release_group_filter": release_group or None,
|
||||
"season_pack_only": season_pack_only,
|
||||
},
|
||||
"monitoring_changed": False,
|
||||
"download_started": False,
|
||||
@@ -410,7 +428,13 @@ async def _start_episode_search(client: Any, kwargs: dict[str, Any]) -> dict[str
|
||||
"sonarr_command": _pick(command, ("id", "name", "status", "queued", "startedOn")) if isinstance(command, dict) else command,
|
||||
"scope": resolved["scope"],
|
||||
"monitoring_changed": False,
|
||||
"instruction": "Sonarr is now searching its configured indexers for the approved missing episodes.",
|
||||
"download_may_start_immediately": True,
|
||||
"instruction": (
|
||||
"Sonarr is now searching its configured indexers and may immediately grab/download "
|
||||
"the best acceptable release for every approved episode. EpisodeSearch is NOT a "
|
||||
"read-only manual-search preview and is NOT limited by the monitored flag. Check the "
|
||||
"queue before claiming that nothing was downloaded."
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
@@ -590,11 +614,11 @@ def register_sonarr_tools(mcp: FastMCP) -> None:
|
||||
@mcp.tool(tags={"sonarr"})
|
||||
async def sonarr_action(
|
||||
action: str = Field(
|
||||
description="Choose one Sonarr operation. Use find_series to resolve a TV-series name; get_season_summary for present/missing episodes; search_releases for Sonarr indexer results. If the user requests a SPECIFIC release, group, or complete season pack, NEVER use automatic episode search: use preview_release_grab with its exact GUID, wait for approval, then grab_release. Use preview_episode_search/start_episode_search only when any acceptable release for missing episodes is wanted. Use list_actions only for unusual reads."
|
||||
description="Choose one Sonarr operation. Use find_series only to resolve a TV-series name. For questions whether a release/group/season pack is AVAILABLE, immediately use search_releases with release_group and season_pack_only as requested; do not infer availability from existing library files or call get_season_summary first. If the user requests a SPECIFIC release, group, or complete season pack, NEVER use automatic episode search: use preview_release_grab with its exact GUID, wait for approval, then grab_release. preview_episode_search/start_episode_search automatically chooses releases and may immediately download them; use it only when the user accepts any suitable release."
|
||||
),
|
||||
params_json: str = Field(
|
||||
default="{}",
|
||||
description="JSON object encoded as a string. Common forms: find_series {\"query\":\"Title\"}; get_season_summary/search_releases {\"series_id\":123,\"season_number\":2}; preview_release_grab {\"series_id\":123,\"season_number\":2,\"guid\":\"exact-guid-from-search\"}; preview_episode_search {\"series_id\":123,\"season_number\":2,\"episode_numbers\":[2,3]}. For grab_release or start_episode_search reuse the exact preview scope and add confirm:true plus approval_ticket.",
|
||||
description="JSON object encoded as a string. Common forms: find_series {\"query\":\"Title\"}; get_season_summary {\"series_id\":123,\"season_number\":2}; search_releases {\"series_id\":123,\"season_number\":2,\"release_group\":\"FuN\",\"season_pack_only\":true}; preview_release_grab {\"series_id\":123,\"season_number\":2,\"guid\":\"exact-guid-from-search\"}; preview_episode_search {\"series_id\":123,\"season_number\":2,\"episode_numbers\":[2,3]}. For grab_release or start_episode_search reuse the exact preview scope and add confirm:true plus approval_ticket.",
|
||||
),
|
||||
) -> Any:
|
||||
"""USE ONLY for TV-series tasks managed by Sonarr: identify a series, inspect missing episodes, search configured indexers, grab one explicitly selected and approved release, or start an approved automatic missing-episode search. A specific season pack/release must use preview_release_grab then grab_release; automatic episode search is not equivalent. DO NOT use for movies (use Radarr), public-web research, media playback, filesystem copying, or direct URL downloads. Read-only by default; monitoring is never changed."""
|
||||
|
||||
Reference in New Issue
Block a user