dev: bump to 0.1.72-dev – Autoplay-Setting, Moflix Hoster-Dialog, Update-Hinweis im Hauptmenue
This commit is contained in:
@@ -218,6 +218,8 @@ class MoflixPlugin(BasisPlugin):
|
||||
self._season_api_ids: dict[tuple[str, int], str] = {}
|
||||
# (title, season_nr) → Liste der Episode-Labels
|
||||
self._episode_labels: dict[tuple[str, int], list[str]] = {}
|
||||
# bevorzugte Hoster für Hoster-Dialog
|
||||
self._preferred_hosters: list[str] = []
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Verfügbarkeit
|
||||
@@ -510,57 +512,46 @@ class MoflixPlugin(BasisPlugin):
|
||||
# Stream
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def stream_link_for(self, title: str, season: str, episode: str) -> Optional[str]:
|
||||
def _videos_for(self, title: str, season: str, episode: str) -> list[dict]:
|
||||
"""Gibt die rohe videos[]-Liste für einen Titel/Staffel/Episode zurück."""
|
||||
title = (title or "").strip()
|
||||
season = (season or "").strip()
|
||||
|
||||
if season == "Film":
|
||||
return self._stream_link_for_movie(title)
|
||||
url = self._ensure_title_url(title)
|
||||
if not url:
|
||||
self._resolve_title(title)
|
||||
url = self._ensure_title_url(title)
|
||||
if not url:
|
||||
return []
|
||||
data = self._get_json(url)
|
||||
if not isinstance(data, dict):
|
||||
return []
|
||||
return (data.get("title") or {}).get("videos") or []
|
||||
|
||||
season_nr = _extract_first_number(season)
|
||||
episode_nr = _extract_first_number(episode)
|
||||
if season_nr is None or episode_nr is None:
|
||||
return None
|
||||
return []
|
||||
|
||||
# Season-API-ID ermitteln (mit Cache-Miss-Fallback)
|
||||
api_id = self._season_api_ids.get((title, season_nr), "")
|
||||
if not api_id:
|
||||
self.seasons_for(title)
|
||||
api_id = self._season_api_ids.get((title, season_nr), "")
|
||||
if not api_id:
|
||||
return None
|
||||
return []
|
||||
|
||||
# Episoden-Detail laden – enthält videos[] mit src-URLs
|
||||
url = _URL_EPISODE.format(id=api_id, s=season_nr, e=episode_nr)
|
||||
data = self._get_json(url)
|
||||
if not isinstance(data, dict):
|
||||
return None
|
||||
videos = (data.get("episode") or {}).get("videos") or []
|
||||
return self._best_src_from_videos(videos)
|
||||
return []
|
||||
return (data.get("episode") or {}).get("videos") or []
|
||||
|
||||
def _stream_link_for_movie(self, title: str) -> Optional[str]:
|
||||
"""Wählt den besten src-Link eines Films aus den API-Videos."""
|
||||
url = self._ensure_title_url(title)
|
||||
if not url:
|
||||
self._resolve_title(title)
|
||||
url = self._ensure_title_url(title)
|
||||
if not url:
|
||||
return None
|
||||
data = self._get_json(url)
|
||||
if not isinstance(data, dict):
|
||||
return None
|
||||
videos = (data.get("title") or {}).get("videos") or []
|
||||
return self._best_src_from_videos(videos)
|
||||
|
||||
def _best_src_from_videos(self, videos: object) -> Optional[str]:
|
||||
"""Wählt die beste src-URL aus einer videos[]-Liste.
|
||||
|
||||
Priorisiert bekannte auflösbare Hoster (vidara.to),
|
||||
überspringt Domains die erfahrungsgemäß 403 liefern.
|
||||
"""
|
||||
preferred: list[str] = []
|
||||
fallback: list[str] = []
|
||||
for v in (videos if isinstance(videos, list) else []):
|
||||
def _hosters_from_videos(self, videos: list) -> dict[str, str]:
|
||||
"""Konvertiert videos[] zu {Hoster-Name → src-URL}, mit Skip/Prefer-Logik."""
|
||||
hosters: dict[str, str] = {}
|
||||
seen: set[str] = set()
|
||||
for v in videos:
|
||||
if not isinstance(v, dict):
|
||||
continue
|
||||
src = _safe_str(v.get("src"))
|
||||
@@ -569,12 +560,44 @@ class MoflixPlugin(BasisPlugin):
|
||||
domain = urlparse(src).netloc.lstrip("www.")
|
||||
if domain in _VIDEO_SKIP_DOMAINS:
|
||||
continue
|
||||
name = _normalize_video_name(_safe_str(v.get("name")), src)
|
||||
if not name:
|
||||
name = domain
|
||||
base_name = name
|
||||
i = 2
|
||||
while name in seen:
|
||||
name = f"{base_name} {i}"
|
||||
i += 1
|
||||
seen.add(name)
|
||||
hosters[name] = src
|
||||
return hosters
|
||||
|
||||
def available_hosters_for(self, title: str, season: str, episode: str) -> List[str]:
|
||||
videos = self._videos_for(title, season, episode)
|
||||
return list(self._hosters_from_videos(videos).keys())
|
||||
|
||||
def set_preferred_hosters(self, hosters: List[str]) -> None:
|
||||
self._preferred_hosters = [h for h in hosters if h]
|
||||
|
||||
def stream_link_for(self, title: str, season: str, episode: str) -> Optional[str]:
|
||||
videos = self._videos_for(title, season, episode)
|
||||
if not videos:
|
||||
return None
|
||||
hosters = self._hosters_from_videos(videos)
|
||||
if not hosters:
|
||||
return None
|
||||
# Bevorzugten Hoster nutzen falls gesetzt
|
||||
for preferred in self._preferred_hosters:
|
||||
key = preferred.casefold()
|
||||
for name, url in hosters.items():
|
||||
if key in name.casefold() or key in url.casefold():
|
||||
return url
|
||||
# Fallback: Prefer-Domains zuerst, dann Rest
|
||||
for url in hosters.values():
|
||||
domain = urlparse(url).netloc.lstrip("www.")
|
||||
if domain in _VIDEO_PREFER_DOMAINS:
|
||||
preferred.append(src)
|
||||
else:
|
||||
fallback.append(src)
|
||||
candidates = preferred + fallback
|
||||
return candidates[0] if candidates else None
|
||||
return url
|
||||
return next(iter(hosters.values()))
|
||||
|
||||
def _resolve_vidara(self, filecode: str) -> Optional[str]:
|
||||
"""Löst einen vidara.to-Filecode über die vidara-API auf → HLS-URL."""
|
||||
@@ -727,7 +750,10 @@ class MoflixPlugin(BasisPlugin):
|
||||
def popular_series(self) -> List[str]:
|
||||
return self._titles_from_channel("series")
|
||||
|
||||
def latest_titles(self, page: int = 1) -> List[str]:
|
||||
def new_titles(self) -> List[str]:
|
||||
return self._titles_from_channel("now-playing")
|
||||
|
||||
def new_titles_page(self, page: int = 1) -> List[str]:
|
||||
return self._titles_from_channel("now-playing", page=page)
|
||||
|
||||
def genres(self) -> List[str]:
|
||||
@@ -752,4 +778,4 @@ class MoflixPlugin(BasisPlugin):
|
||||
return self._titles_from_channel(slug, page=page)
|
||||
|
||||
def capabilities(self) -> set[str]:
|
||||
return {"popular_series", "latest_titles", "collections", "genres"}
|
||||
return {"popular_series", "new_titles", "collections", "genres"}
|
||||
|
||||
Reference in New Issue
Block a user