dev: bump to 0.1.71-dev – Trakt History direkt abspielen, Metadaten + Plugin-Bugfixes
- Trakt History: Episoden starten direkt (kein Staffel-Dialog mehr) - Trakt History: Episodentitel, Plot und Artwork bereits in der Übersicht - TraktItem um episode_title, episode_overview, episode_thumb, show_poster, show_fanart erweitert - get_history() nutzt jetzt ?extended=full,images - Slash-Commands /check und /deploy angelegt - build_install_addon.sh deployt jetzt auch nach ~/.kodi/addons/ - filmpalast_plugin: return-Tuple-Bug gefixt (return "", "", "") - dokustreams_plugin: Regex-Escaping für clean_name() korrigiert - aniworld_plugin: raise_for_status() in resolve_redirect() ergänzt - serienstream_plugin: Toter Code und unnötigen Regex-Backslash entfernt
This commit is contained in:
@@ -54,12 +54,26 @@ class TraktMediaIds:
|
||||
class TraktItem:
|
||||
title: str
|
||||
year: int
|
||||
media_type: str # "movie" oder "show"
|
||||
media_type: str # "movie", "show" oder "episode"
|
||||
ids: TraktMediaIds = field(default_factory=TraktMediaIds)
|
||||
season: int = 0
|
||||
episode: int = 0
|
||||
watched_at: str = ""
|
||||
poster: str = ""
|
||||
episode_title: str = "" # Episodentitel (extended=full)
|
||||
episode_overview: str = "" # Episoden-Inhaltsangabe (extended=full)
|
||||
episode_thumb: str = "" # Screenshot-URL (extended=images)
|
||||
show_poster: str = "" # Serien-Poster-URL (extended=images)
|
||||
show_fanart: str = "" # Serien-Fanart-URL (extended=images)
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class TraktEpisodeMeta:
|
||||
"""Metadaten einer einzelnen Episode (aus extended=full,images)."""
|
||||
title: str
|
||||
overview: str
|
||||
runtime_minutes: int
|
||||
thumb: str # Screenshot-URL (https://)
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
@@ -71,9 +85,27 @@ class TraktCalendarItem:
|
||||
season: int
|
||||
episode: int
|
||||
episode_title: str
|
||||
episode_overview: str # Episoden-Inhaltsangabe (extended=full)
|
||||
episode_thumb: str # Screenshot-URL (https://)
|
||||
show_poster: str # Poster-URL (https://)
|
||||
show_fanart: str # Fanart-URL (https://)
|
||||
first_aired: str # ISO-8601, z.B. "2026-03-02T02:00:00.000Z"
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Helpers
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def _trakt_image_url(raw: str) -> str:
|
||||
"""Stellt https:// vor relative Trakt-Bild-URLs."""
|
||||
if not raw:
|
||||
return ""
|
||||
raw = raw.strip()
|
||||
if raw.startswith("http"):
|
||||
return raw
|
||||
return f"https://{raw}"
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Client
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -332,7 +364,7 @@ class TraktClient:
|
||||
path = "/users/me/history"
|
||||
if media_type in ("movies", "shows", "episodes"):
|
||||
path = f"{path}/{media_type}"
|
||||
path = f"{path}?page={page}&limit={limit}"
|
||||
path = f"{path}?page={page}&limit={limit}&extended=full,images"
|
||||
status, payload = self._get(path, token=token)
|
||||
if status != 200 or not isinstance(payload, list):
|
||||
return []
|
||||
@@ -351,7 +383,7 @@ class TraktClient:
|
||||
if not start_date:
|
||||
from datetime import date
|
||||
start_date = date.today().strftime("%Y-%m-%d")
|
||||
path = f"/calendars/my/shows/{start_date}/{days}"
|
||||
path = f"/calendars/my/shows/{start_date}/{days}?extended=full,images"
|
||||
status, payload = self._get(path, token=token)
|
||||
if status != 200 or not isinstance(payload, list):
|
||||
return []
|
||||
@@ -362,6 +394,13 @@ class TraktClient:
|
||||
show = entry.get("show") or {}
|
||||
ep = entry.get("episode") or {}
|
||||
show_ids = self._parse_ids(show.get("ids") or {})
|
||||
ep_images = ep.get("images") or {}
|
||||
show_images = show.get("images") or {}
|
||||
|
||||
def _first(img_dict: dict, key: str) -> str:
|
||||
imgs = img_dict.get(key) or []
|
||||
return _trakt_image_url(imgs[0]) if imgs else ""
|
||||
|
||||
items.append(TraktCalendarItem(
|
||||
show_title=str(show.get("title", "") or ""),
|
||||
show_year=int(show.get("year", 0) or 0),
|
||||
@@ -369,10 +408,75 @@ class TraktClient:
|
||||
season=int(ep.get("season", 0) or 0),
|
||||
episode=int(ep.get("number", 0) or 0),
|
||||
episode_title=str(ep.get("title", "") or ""),
|
||||
episode_overview=str(ep.get("overview", "") or ""),
|
||||
episode_thumb=_first(ep_images, "screenshot"),
|
||||
show_poster=_first(show_images, "poster"),
|
||||
show_fanart=_first(show_images, "fanart"),
|
||||
first_aired=str(entry.get("first_aired", "") or ""),
|
||||
))
|
||||
return items
|
||||
|
||||
def search_show(self, query: str) -> str:
|
||||
"""GET /search/show?query=... – gibt slug des ersten Treffers zurück, sonst ''."""
|
||||
from urllib.parse import urlencode
|
||||
path = f"/search/show?{urlencode({'query': query, 'limit': 1})}"
|
||||
status, payload = self._get(path)
|
||||
if status != 200 or not isinstance(payload, list) or not payload:
|
||||
return ""
|
||||
show = (payload[0] or {}).get("show") or {}
|
||||
ids = show.get("ids") or {}
|
||||
return str(ids.get("slug") or ids.get("trakt") or "")
|
||||
|
||||
def lookup_tv_season(
|
||||
self,
|
||||
show_id_or_slug: "str | int",
|
||||
season_number: int,
|
||||
*,
|
||||
token: str = "",
|
||||
) -> "dict[int, TraktEpisodeMeta] | None":
|
||||
"""GET /shows/{id}/seasons/{n}/episodes?extended=full,images
|
||||
Gibt episode_number -> TraktEpisodeMeta zurück, oder None bei Fehler.
|
||||
"""
|
||||
path = f"/shows/{show_id_or_slug}/seasons/{season_number}/episodes?extended=full,images"
|
||||
status, payload = self._get(path, token=token)
|
||||
if status != 200 or not isinstance(payload, list):
|
||||
return None
|
||||
result: "dict[int, TraktEpisodeMeta]" = {}
|
||||
for entry in payload:
|
||||
try:
|
||||
ep_no = int(entry.get("number") or 0)
|
||||
except Exception:
|
||||
continue
|
||||
if not ep_no:
|
||||
continue
|
||||
images = entry.get("images") or {}
|
||||
screenshots = images.get("screenshot") or []
|
||||
thumb = _trakt_image_url(screenshots[0]) if screenshots else ""
|
||||
result[ep_no] = TraktEpisodeMeta(
|
||||
title=str(entry.get("title") or "").strip(),
|
||||
overview=str(entry.get("overview") or "").strip(),
|
||||
runtime_minutes=int(entry.get("runtime") or 0),
|
||||
thumb=thumb,
|
||||
)
|
||||
return result or None
|
||||
|
||||
def get_episode_translation(
|
||||
self,
|
||||
show_id_or_slug: "str | int",
|
||||
season: int,
|
||||
episode: int,
|
||||
language: str = "de",
|
||||
) -> "tuple[str, str]":
|
||||
"""GET /shows/{id}/seasons/{s}/episodes/{e}/translations/{lang}
|
||||
Gibt (title, overview) in der Zielsprache zurück, oder ('', '') bei Fehler.
|
||||
"""
|
||||
path = f"/shows/{show_id_or_slug}/seasons/{season}/episodes/{episode}/translations/{language}"
|
||||
status, payload = self._get(path)
|
||||
if status != 200 or not isinstance(payload, list) or not payload:
|
||||
return "", ""
|
||||
first = payload[0] if payload else {}
|
||||
return str(first.get("title") or ""), str(first.get("overview") or "")
|
||||
|
||||
# -------------------------------------------------------------------
|
||||
# Parser
|
||||
# -------------------------------------------------------------------
|
||||
@@ -417,6 +521,13 @@ class TraktClient:
|
||||
show = entry.get("show") or {}
|
||||
ep = entry.get("episode") or {}
|
||||
ids = self._parse_ids((show.get("ids") or {}))
|
||||
ep_images = ep.get("images") or {}
|
||||
show_images = show.get("images") or {}
|
||||
|
||||
def _first_img(img_dict: dict, key: str) -> str:
|
||||
imgs = img_dict.get(key) or []
|
||||
return _trakt_image_url(imgs[0]) if imgs else ""
|
||||
|
||||
result.append(TraktItem(
|
||||
title=str(show.get("title", "") or ""),
|
||||
year=int(show.get("year", 0) or 0),
|
||||
@@ -425,6 +536,11 @@ class TraktClient:
|
||||
season=int(ep.get("season", 0) or 0),
|
||||
episode=int(ep.get("number", 0) or 0),
|
||||
watched_at=watched_at,
|
||||
episode_title=str(ep.get("title", "") or ""),
|
||||
episode_overview=str(ep.get("overview", "") or ""),
|
||||
episode_thumb=_first_img(ep_images, "screenshot"),
|
||||
show_poster=_first_img(show_images, "poster"),
|
||||
show_fanart=_first_img(show_images, "fanart"),
|
||||
))
|
||||
else:
|
||||
media = entry.get("movie") or entry.get("show") or {}
|
||||
|
||||
Reference in New Issue
Block a user