dev: bump to 0.1.72-dev – Autoplay-Setting, Moflix Hoster-Dialog, Update-Hinweis im Hauptmenue
This commit is contained in:
@@ -93,6 +93,8 @@ class KKistePlugin(BasisPlugin):
|
||||
self._is_series: dict[str, bool] = {}
|
||||
# title → Staffelnummer (aus "Staffel N" extrahiert)
|
||||
self._season_nr: dict[str, int] = {}
|
||||
# bevorzugte Hoster für Hoster-Dialog
|
||||
self._preferred_hosters: list[str] = []
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Verfügbarkeit
|
||||
@@ -165,6 +167,24 @@ class KKistePlugin(BasisPlugin):
|
||||
self._title_meta[title] = (plot, poster, fanart)
|
||||
return title
|
||||
|
||||
def _ensure_watch_url(self, title: str) -> str:
|
||||
"""Gibt die Watch-URL zurück – lädt bei leerem Cache alle Titel nach."""
|
||||
url = self._title_to_watch_url.get(title, "")
|
||||
if url:
|
||||
return url
|
||||
# Fallback: alle Titel laden und exact-match suchen
|
||||
search_url = _URL_SEARCH.format(lang=_LANG)
|
||||
data = self._get_json(search_url)
|
||||
if isinstance(data, dict):
|
||||
q_lower = title.lower()
|
||||
for movie in (data.get("movies") or []):
|
||||
if isinstance(movie, dict):
|
||||
raw = str(movie.get("title") or "").strip()
|
||||
if raw.lower() == q_lower:
|
||||
self._cache_entry(movie)
|
||||
return self._title_to_watch_url.get(title, "")
|
||||
return ""
|
||||
|
||||
def _browse(self, content_type: str, order: str = "Trending") -> List[str]:
|
||||
url = _URL_BROWSE.format(lang=_LANG, type=content_type, order=order, page=1)
|
||||
data = self._get_json(url)
|
||||
@@ -175,6 +195,55 @@ class KKistePlugin(BasisPlugin):
|
||||
if isinstance(movie, dict) and (t := self._cache_entry(movie))
|
||||
]
|
||||
|
||||
def _hosters_for(self, title: str, season: str, episode: str) -> dict[str, str]:
|
||||
"""Gibt {Hoster-Name → URL} für Titel/Staffel/Episode zurück."""
|
||||
watch_url = self._ensure_watch_url(title)
|
||||
if not watch_url:
|
||||
return {}
|
||||
data = self._get_json(watch_url)
|
||||
if not isinstance(data, dict):
|
||||
return {}
|
||||
|
||||
streams = data.get("streams") or []
|
||||
hosters: dict[str, str] = {}
|
||||
seen: set[str] = set()
|
||||
|
||||
# Film vs Serie: relevante Streams filtern
|
||||
if season == "Film":
|
||||
target_streams = [s for s in streams if isinstance(s, dict)]
|
||||
else:
|
||||
m = re.search(r"\d+", episode or "")
|
||||
ep_nr = int(m.group()) if m else None
|
||||
if ep_nr is None:
|
||||
return {}
|
||||
target_streams = [
|
||||
s for s in streams
|
||||
if isinstance(s, dict) and s.get("e") == ep_nr
|
||||
]
|
||||
|
||||
for stream in target_streams:
|
||||
src = str(stream.get("stream") or "").strip()
|
||||
if not src:
|
||||
continue
|
||||
# Hoster-Name aus der Stream-URL extrahieren (nicht aus "source" – das ist die Aggregator-Quelle)
|
||||
try:
|
||||
from urllib.parse import urlparse
|
||||
host = urlparse(src).hostname or "Hoster"
|
||||
# Domain-Prefix entfernen (www.)
|
||||
if host.startswith("www."):
|
||||
host = host[4:]
|
||||
except Exception:
|
||||
host = "Hoster"
|
||||
name = host
|
||||
base_name = name
|
||||
i = 2
|
||||
while name in seen:
|
||||
name = f"{base_name} {i}"
|
||||
i += 1
|
||||
seen.add(name)
|
||||
hosters[name] = src
|
||||
return hosters
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Pflicht-Methoden
|
||||
# ------------------------------------------------------------------
|
||||
@@ -211,10 +280,14 @@ class KKistePlugin(BasisPlugin):
|
||||
return []
|
||||
|
||||
is_series = self._is_series.get(title)
|
||||
if is_series is None:
|
||||
# Cache leer (neue Instanz) – nachfüllen
|
||||
self._ensure_watch_url(title)
|
||||
is_series = self._is_series.get(title)
|
||||
|
||||
if is_series:
|
||||
season_nr = self._season_nr.get(title, 1)
|
||||
return [f"Staffel {season_nr}"]
|
||||
# Film (oder unbekannt → Film-Fallback)
|
||||
return ["Film"]
|
||||
|
||||
def episodes_for(self, title: str, season: str) -> List[str]:
|
||||
@@ -222,12 +295,11 @@ class KKistePlugin(BasisPlugin):
|
||||
if not title:
|
||||
return []
|
||||
|
||||
# Film
|
||||
if season == "Film":
|
||||
return [title]
|
||||
|
||||
# Serie: Episodenliste aus /data/watch/ laden
|
||||
watch_url = self._title_to_watch_url.get(title, "")
|
||||
watch_url = self._ensure_watch_url(title)
|
||||
if not watch_url:
|
||||
return []
|
||||
|
||||
@@ -247,7 +319,6 @@ class KKistePlugin(BasisPlugin):
|
||||
pass
|
||||
|
||||
if not episode_nrs:
|
||||
# Keine Episoden-Nummern → als Film behandeln
|
||||
return [title]
|
||||
|
||||
return [f"Episode {nr}" for nr in sorted(episode_nrs)]
|
||||
@@ -256,48 +327,25 @@ class KKistePlugin(BasisPlugin):
|
||||
# Stream
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def available_hosters_for(self, title: str, season: str, episode: str) -> List[str]:
|
||||
return list(self._hosters_for(title, season, episode).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]:
|
||||
title = (title or "").strip()
|
||||
watch_url = self._title_to_watch_url.get(title, "")
|
||||
if not watch_url:
|
||||
hosters = self._hosters_for(title, season, episode)
|
||||
if not hosters:
|
||||
return None
|
||||
|
||||
data = self._get_json(watch_url)
|
||||
if not isinstance(data, dict):
|
||||
return None
|
||||
|
||||
streams = data.get("streams") or []
|
||||
|
||||
if season == "Film":
|
||||
# Film: Stream ohne Episode-Nummer bevorzugen
|
||||
for stream in streams:
|
||||
if isinstance(stream, dict) and stream.get("e") is None:
|
||||
src = str(stream.get("stream") or "").strip()
|
||||
if src:
|
||||
return src
|
||||
# Fallback: irgendeinen Stream
|
||||
for stream in streams:
|
||||
if isinstance(stream, dict):
|
||||
src = str(stream.get("stream") or "").strip()
|
||||
if src:
|
||||
return src
|
||||
else:
|
||||
# Serie: Episodennummer extrahieren und matchen
|
||||
m = re.search(r"\d+", episode or "")
|
||||
if not m:
|
||||
return None
|
||||
ep_nr = int(m.group())
|
||||
for stream in streams:
|
||||
if not isinstance(stream, dict):
|
||||
continue
|
||||
try:
|
||||
if int(stream.get("e") or -1) == ep_nr:
|
||||
src = str(stream.get("stream") or "").strip()
|
||||
if src:
|
||||
return src
|
||||
except (ValueError, TypeError):
|
||||
pass
|
||||
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: erster Hoster
|
||||
return next(iter(hosters.values()))
|
||||
|
||||
def resolve_stream_link(self, link: str) -> Optional[str]:
|
||||
link = (link or "").strip()
|
||||
@@ -341,12 +389,23 @@ class KKistePlugin(BasisPlugin):
|
||||
# Browsing
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def new_titles(self) -> List[str]:
|
||||
return self._browse("movies", "new")
|
||||
|
||||
def new_titles_page(self, page: int = 1) -> List[str]:
|
||||
page = max(1, int(page or 1))
|
||||
url = _URL_BROWSE.format(lang=_LANG, type="movies", order="new", page=page)
|
||||
data = self._get_json(url)
|
||||
if not isinstance(data, dict):
|
||||
return []
|
||||
return [
|
||||
t for movie in (data.get("movies") or [])
|
||||
if isinstance(movie, dict) and (t := self._cache_entry(movie))
|
||||
]
|
||||
|
||||
def popular_series(self) -> List[str]:
|
||||
return self._browse("tvseries", "views")
|
||||
|
||||
def latest_titles(self, page: int = 1) -> List[str]:
|
||||
return self._browse("movies", "new")
|
||||
|
||||
def genres(self) -> List[str]:
|
||||
return sorted(GENRE_SLUGS.keys())
|
||||
|
||||
@@ -364,4 +423,4 @@ class KKistePlugin(BasisPlugin):
|
||||
]
|
||||
|
||||
def capabilities(self) -> set[str]:
|
||||
return {"popular_series", "latest_titles", "genres"}
|
||||
return {"popular_series", "new_titles", "genres"}
|
||||
|
||||
Reference in New Issue
Block a user