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 {}
|
||||
|
||||
172
addon/default.py
172
addon/default.py
@@ -131,6 +131,7 @@ _MEDIA_TYPE_CACHE: dict[str, str] = {}
|
||||
_TMDB_SEASON_CACHE: dict[tuple[int, int, str, str], dict[int, tuple[dict[str, str], dict[str, str]]]] = {}
|
||||
_TMDB_SEASON_SUMMARY_CACHE: dict[tuple[int, int, str, str], tuple[dict[str, str], dict[str, str]]] = {}
|
||||
_TMDB_EPISODE_CAST_CACHE: dict[tuple[int, int, int, str], list[TmdbCastMember]] = {}
|
||||
_TRAKT_SEASON_META_CACHE: dict = {}
|
||||
_TMDB_LOG_PATH: str | None = None
|
||||
_GENRE_TITLES_CACHE: dict[tuple[str, str], list[str]] = {}
|
||||
_ADDON_INSTANCE = None
|
||||
@@ -987,7 +988,9 @@ def _tmdb_episode_labels_and_art(*, title: str, season_label: str, episode_label
|
||||
_tmdb_labels_and_art(title)
|
||||
tmdb_id = _tmdb_cache_get(_TMDB_ID_CACHE, title_key)
|
||||
if not tmdb_id:
|
||||
return {"title": episode_label}, {}
|
||||
return _trakt_episode_labels_and_art(
|
||||
title=title, season_label=season_label, episode_label=episode_label
|
||||
)
|
||||
|
||||
season_number = _extract_first_int(season_label)
|
||||
episode_number = _extract_first_int(episode_label)
|
||||
@@ -1003,7 +1006,9 @@ def _tmdb_episode_labels_and_art(*, title: str, season_label: str, episode_label
|
||||
if cached_season is None:
|
||||
api_key = _get_setting_string("tmdb_api_key").strip()
|
||||
if not api_key or api_key == "None":
|
||||
return {"title": episode_label}, {}
|
||||
return _trakt_episode_labels_and_art(
|
||||
title=title, season_label=season_label, episode_label=episode_label
|
||||
)
|
||||
log_requests = _get_setting_bool("tmdb_log_requests", default=False)
|
||||
log_responses = _get_setting_bool("tmdb_log_responses", default=False)
|
||||
log_fn = _tmdb_file_log if (log_requests or log_responses) else None
|
||||
@@ -1038,6 +1043,66 @@ def _tmdb_episode_labels_and_art(*, title: str, season_label: str, episode_label
|
||||
return cached_season.get(episode_number, ({"title": episode_label}, {}))
|
||||
|
||||
|
||||
def _trakt_episode_labels_and_art(
|
||||
*, title: str, season_label: str, episode_label: str
|
||||
) -> tuple[dict[str, str], dict[str, str]]:
|
||||
"""Trakt-Fallback für Episoden-Metadaten wenn TMDB nicht verfügbar.
|
||||
Lädt Staffel-Episodendaten per Batch (extended=full,images) und optionale
|
||||
deutsche Übersetzung per Episode (Translations-Endpunkt).
|
||||
"""
|
||||
client = _trakt_get_client()
|
||||
if not client:
|
||||
return {"title": episode_label}, {}
|
||||
season_number = _extract_first_int(season_label)
|
||||
episode_number = _extract_first_int(episode_label)
|
||||
if season_number is None or episode_number is None:
|
||||
return {"title": episode_label}, {}
|
||||
|
||||
cache_key = (title.strip().casefold(), season_number)
|
||||
cached = _tmdb_cache_get(_TRAKT_SEASON_META_CACHE, cache_key)
|
||||
if cached is None:
|
||||
slug = client.search_show(title)
|
||||
if not slug:
|
||||
_tmdb_cache_set(_TRAKT_SEASON_META_CACHE, cache_key, {})
|
||||
return {"title": episode_label}, {}
|
||||
meta = client.lookup_tv_season(slug, season_number)
|
||||
_tmdb_cache_set(_TRAKT_SEASON_META_CACHE, cache_key, {"slug": slug, "episodes": meta or {}})
|
||||
cached = _tmdb_cache_get(_TRAKT_SEASON_META_CACHE, cache_key)
|
||||
|
||||
slug = (cached or {}).get("slug", "")
|
||||
episodes: dict = (cached or {}).get("episodes", {})
|
||||
ep = episodes.get(episode_number)
|
||||
if not ep:
|
||||
return {"title": episode_label}, {}
|
||||
|
||||
ep_title = ep.title or episode_label
|
||||
ep_overview = ep.overview
|
||||
|
||||
language = _get_setting_string("tmdb_language").strip() or "de-DE"
|
||||
lang_code = language[:2]
|
||||
if slug and lang_code and lang_code != "en":
|
||||
trans_key = (cache_key, episode_number, lang_code)
|
||||
trans_cached = _tmdb_cache_get(_TRAKT_SEASON_META_CACHE, trans_key)
|
||||
if trans_cached is None:
|
||||
t_title, t_overview = client.get_episode_translation(slug, season_number, episode_number, lang_code)
|
||||
trans_cached = {"title": t_title, "overview": t_overview}
|
||||
_tmdb_cache_set(_TRAKT_SEASON_META_CACHE, trans_key, trans_cached)
|
||||
if trans_cached.get("title"):
|
||||
ep_title = trans_cached["title"]
|
||||
if trans_cached.get("overview"):
|
||||
ep_overview = trans_cached["overview"]
|
||||
|
||||
info: dict[str, str] = {"title": ep_title}
|
||||
if ep_overview:
|
||||
info["plot"] = ep_overview
|
||||
if ep.runtime_minutes:
|
||||
info["duration"] = str(ep.runtime_minutes * 60)
|
||||
art: dict[str, str] = {}
|
||||
if ep.thumb:
|
||||
art["thumb"] = ep.thumb
|
||||
return info, art
|
||||
|
||||
|
||||
def _tmdb_episode_cast(*, title: str, season_label: str, episode_label: str) -> list[TmdbCastMember]:
|
||||
if not _tmdb_enabled():
|
||||
return []
|
||||
@@ -4526,12 +4591,29 @@ def _show_trakt_watchlist(media_type: str = "") -> None:
|
||||
xbmcplugin.endOfDirectory(handle)
|
||||
return
|
||||
|
||||
_set_content(handle, "tvshows")
|
||||
items = client.get_watchlist(token, media_type=media_type)
|
||||
for item in items:
|
||||
label = f"{item.title}"
|
||||
label = f"{item.title} ({item.year})" if item.year else item.title
|
||||
|
||||
tmdb_info, art, _ = _tmdb_labels_and_art(item.title)
|
||||
info_labels: dict[str, object] = dict(tmdb_info)
|
||||
info_labels["title"] = label
|
||||
info_labels["tvshowtitle"] = item.title
|
||||
if item.year:
|
||||
label = f"{item.title} ({item.year})"
|
||||
_add_directory_item(handle, label, "search", {"query": item.title}, is_folder=True)
|
||||
info_labels["year"] = item.year
|
||||
info_labels["mediatype"] = "tvshow"
|
||||
|
||||
match = _trakt_find_in_plugins(item.title)
|
||||
if match:
|
||||
plugin_name, matched_title = match
|
||||
action = "seasons"
|
||||
params: dict[str, str] = {"plugin": plugin_name, "title": matched_title}
|
||||
else:
|
||||
action = "search"
|
||||
params = {"query": item.title}
|
||||
|
||||
_add_directory_item(handle, label, action, params, is_folder=True, info_labels=info_labels, art=art)
|
||||
if not items:
|
||||
xbmcgui.Dialog().notification("Trakt", "Watchlist ist leer.", xbmcgui.NOTIFICATION_INFO, 3000)
|
||||
xbmcplugin.endOfDirectory(handle)
|
||||
@@ -4546,14 +4628,66 @@ def _show_trakt_history(page: int = 1) -> None:
|
||||
xbmcplugin.endOfDirectory(handle)
|
||||
return
|
||||
|
||||
xbmcplugin.setPluginCategory(handle, "Trakt: Zuletzt gesehen")
|
||||
_set_content(handle, "episodes")
|
||||
|
||||
items = client.get_history(token, page=page, limit=LIST_PAGE_SIZE)
|
||||
for item in items:
|
||||
label = item.title
|
||||
if item.media_type == "episode" and item.season and item.episode:
|
||||
label = f"{item.title} - S{item.season:02d}E{item.episode:02d}"
|
||||
is_episode = item.media_type == "episode" and item.season and item.episode
|
||||
|
||||
# Label mit Episodentitel wenn vorhanden
|
||||
if is_episode:
|
||||
ep_title = item.episode_title or f"Episode {item.episode}"
|
||||
label = f"{item.title} – S{item.season:02d}E{item.episode:02d}: {ep_title}"
|
||||
elif item.year:
|
||||
label = f"{item.title} ({item.year})"
|
||||
_add_directory_item(handle, label, "search", {"query": item.title}, is_folder=True)
|
||||
else:
|
||||
label = item.title
|
||||
|
||||
# Artwork: Trakt-Bilder als Basis, TMDB ergänzt fehlende Keys
|
||||
art: dict[str, str] = {}
|
||||
if item.episode_thumb:
|
||||
art["thumb"] = item.episode_thumb
|
||||
if item.show_fanart:
|
||||
art["fanart"] = item.show_fanart
|
||||
if item.show_poster:
|
||||
art["poster"] = item.show_poster
|
||||
_, tmdb_art, _ = _tmdb_labels_and_art(item.title)
|
||||
for _k, _v in tmdb_art.items():
|
||||
art.setdefault(_k, _v)
|
||||
|
||||
# Info-Labels
|
||||
info_labels: dict[str, object] = {}
|
||||
info_labels["title"] = item.episode_title if is_episode and item.episode_title else label
|
||||
info_labels["tvshowtitle"] = item.title
|
||||
if item.year:
|
||||
info_labels["year"] = item.year
|
||||
if is_episode:
|
||||
info_labels["season"] = item.season
|
||||
info_labels["episode"] = item.episode
|
||||
if item.episode_overview:
|
||||
info_labels["plot"] = item.episode_overview
|
||||
info_labels["mediatype"] = "episode" if is_episode else "tvshow"
|
||||
|
||||
# Navigation: Episoden direkt abspielen, Serien zur Staffelauswahl
|
||||
match = _trakt_find_in_plugins(item.title)
|
||||
if match:
|
||||
plugin_name, matched_title = match
|
||||
if is_episode:
|
||||
action = "play_episode"
|
||||
params: dict[str, str] = {
|
||||
"plugin": plugin_name,
|
||||
"title": matched_title,
|
||||
"season": f"Staffel {item.season}",
|
||||
"episode": f"Episode {item.episode}",
|
||||
}
|
||||
_add_directory_item(handle, label, action, params, is_folder=False, info_labels=info_labels, art=art)
|
||||
else:
|
||||
action = "seasons"
|
||||
params = {"plugin": plugin_name, "title": matched_title}
|
||||
_add_directory_item(handle, label, action, params, is_folder=True, info_labels=info_labels, art=art)
|
||||
else:
|
||||
_add_directory_item(handle, label, "search", {"query": item.title}, is_folder=True, info_labels=info_labels, art=art)
|
||||
|
||||
if len(items) >= LIST_PAGE_SIZE:
|
||||
_add_directory_item(handle, "Naechste Seite >>", "trakt_history", {"page": str(page + 1)}, is_folder=True)
|
||||
@@ -4613,8 +4747,20 @@ def _show_trakt_upcoming() -> None:
|
||||
}
|
||||
if item.show_year:
|
||||
info_labels["year"] = item.show_year
|
||||
if item.episode_overview:
|
||||
info_labels["plot"] = item.episode_overview
|
||||
|
||||
_, art, _ = _tmdb_labels_and_art(item.show_title)
|
||||
# Artwork: Trakt-Bilder als Basis, TMDB ergänzt fehlende Keys
|
||||
art: dict[str, str] = {}
|
||||
if item.episode_thumb:
|
||||
art["thumb"] = item.episode_thumb
|
||||
if item.show_fanart:
|
||||
art["fanart"] = item.show_fanart
|
||||
if item.show_poster:
|
||||
art["poster"] = item.show_poster
|
||||
_, tmdb_art, _ = _tmdb_labels_and_art(item.show_title)
|
||||
for _k, _v in tmdb_art.items():
|
||||
art.setdefault(_k, _v)
|
||||
|
||||
match = _trakt_find_in_plugins(item.show_title)
|
||||
if match:
|
||||
@@ -4736,7 +4882,11 @@ def _show_trakt_continue_watching() -> None:
|
||||
|
||||
@_router.route("search")
|
||||
def _route_search(params: dict[str, str]) -> None:
|
||||
_show_search()
|
||||
query = params.get("query", "").strip()
|
||||
if query:
|
||||
_show_search_results(query)
|
||||
else:
|
||||
_show_search()
|
||||
|
||||
|
||||
@_router.route("plugin_menu")
|
||||
|
||||
@@ -593,6 +593,7 @@ def resolve_redirect(target_url: str) -> Optional[str]:
|
||||
response = None
|
||||
try:
|
||||
response = session.get(normalized_url, headers=HEADERS, timeout=DEFAULT_TIMEOUT, allow_redirects=True)
|
||||
response.raise_for_status()
|
||||
if response.url:
|
||||
_log_url(response.url, kind="RESOLVED")
|
||||
return response.url if response.url else None
|
||||
|
||||
@@ -304,7 +304,7 @@ class DokuStreamsPlugin(BasisPlugin):
|
||||
|
||||
def clean_name(value: str) -> str:
|
||||
value = (value or "").strip()
|
||||
return re.sub(r"\\s*\\(\\d+\\)\\s*$", "", value).strip()
|
||||
return re.sub(r"\s*\(\d+\)\s*$", "", value).strip()
|
||||
|
||||
def walk(ul, parents: List[str]) -> None:
|
||||
for li in ul.find_all("li", recursive=False):
|
||||
|
||||
@@ -728,7 +728,7 @@ class FilmpalastPlugin(BasisPlugin):
|
||||
|
||||
def _extract_detail_metadata(self, soup: BeautifulSoupT) -> tuple[str, str, str]:
|
||||
if not soup:
|
||||
return "", ""
|
||||
return "", "", ""
|
||||
root = soup.select_one("div#content[role='main']") or soup
|
||||
detail = root.select_one("article.detail") or root
|
||||
plot = ""
|
||||
|
||||
@@ -865,7 +865,7 @@ def _extract_episodes(soup: BeautifulSoupT) -> list[EpisodeInfo]:
|
||||
onclick = (row.get("onclick") or "").strip()
|
||||
url = ""
|
||||
if onclick:
|
||||
match = re.search(r"location=['\\\"]([^'\\\"]+)['\\\"]", onclick)
|
||||
match = re.search(r"location=['\"]([^'\"]+)['\"]", onclick)
|
||||
if match:
|
||||
url = _absolute_url(match.group(1))
|
||||
if not url:
|
||||
@@ -923,8 +923,6 @@ def _extract_episodes(soup: BeautifulSoupT) -> list[EpisodeInfo]:
|
||||
hosters=hosters,
|
||||
)
|
||||
)
|
||||
if episodes:
|
||||
return episodes
|
||||
return episodes
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user