dev: bump to 0.1.72-dev – Autoplay-Setting, Moflix Hoster-Dialog, Update-Hinweis im Hauptmenue
This commit is contained in:
@@ -38,7 +38,7 @@ HEADERS = {
|
||||
"Connection": "keep-alive",
|
||||
}
|
||||
|
||||
_URL_SEARCH = BASE_URL + "/?s={query}"
|
||||
_URL_SEARCH = BASE_URL + "/?do=search&subaction=search&story={query}"
|
||||
_URL_NEW = BASE_URL + "/kinofilme-online/"
|
||||
_URL_SERIES = BASE_URL + "/serienstream-deutsch/"
|
||||
|
||||
@@ -68,7 +68,7 @@ GENRE_SLUGS: dict[str, str] = {
|
||||
}
|
||||
|
||||
# Hoster die übersprungen werden (kein Stream / nur Trailer)
|
||||
_SKIP_LINK_KEYWORDS = ("youtube.com", "youtu.be")
|
||||
_SKIP_LINK_KEYWORDS = ("youtube.com", "youtu.be", "hdfilme-tv.cc")
|
||||
|
||||
ProgressCallback = Optional[Callable[[str, Optional[int]], Any]]
|
||||
|
||||
@@ -122,6 +122,7 @@ class HdfilmePlugin(BasisPlugin):
|
||||
self._is_series: dict[str, bool] = {}
|
||||
self._title_meta: dict[str, tuple[str, str]] = {} # title → (plot, poster)
|
||||
self._episode_cache: dict[str, list[str]] = {} # detail_url → episode labels
|
||||
self._preferred_hosters: list[str] = []
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Verfügbarkeit
|
||||
@@ -182,40 +183,64 @@ class HdfilmePlugin(BasisPlugin):
|
||||
titles.append(title)
|
||||
return titles
|
||||
|
||||
def _ensure_detail_url(self, title: str) -> str:
|
||||
"""Gibt die Detail-URL für einen Titel zurück.
|
||||
|
||||
Sucht zuerst im Cache, dann live über die Suchfunktion.
|
||||
"""
|
||||
url = self._title_to_url.get(title, "")
|
||||
if url:
|
||||
return url
|
||||
# Fallback: Live-Suche (nötig wenn Plugin-Instanz neu, Cache leer)
|
||||
search_url = _URL_SEARCH.format(query=quote_plus(title.strip()))
|
||||
soup = _get_soup(search_url)
|
||||
if soup:
|
||||
self._parse_entries(soup)
|
||||
url = self._title_to_url.get(title, "")
|
||||
return url
|
||||
|
||||
def _get_detail_soup(self, title: str) -> Any:
|
||||
"""Lädt die Detailseite eines Titels."""
|
||||
url = self._title_to_url.get(title, "")
|
||||
url = self._ensure_detail_url(title)
|
||||
if not url:
|
||||
return None
|
||||
return _get_soup(url)
|
||||
|
||||
def _extract_hoster_links(self, soup: Any, episode_id: str = "") -> list[str]:
|
||||
def _extract_hoster_links(self, soup: Any, episode_id: str = "") -> dict[str, str]:
|
||||
"""Extrahiert Hoster-Links aus einer Detailseite.
|
||||
|
||||
Gibt dict {Hoster-Name → URL} zurück.
|
||||
episode_id: wenn gesetzt, nur Links aus dem `<li id="{episode_id}">` Block.
|
||||
"""
|
||||
if soup is None:
|
||||
return []
|
||||
links: list[str] = []
|
||||
return {}
|
||||
hosters: dict[str, str] = {}
|
||||
|
||||
if episode_id:
|
||||
# Serien-Episode: Links aus dem spezifischen Episode-Container
|
||||
container = soup.select_one(f"li#{episode_id}")
|
||||
if container is None:
|
||||
return []
|
||||
return {}
|
||||
candidates = container.select("a[data-link]")
|
||||
else:
|
||||
# Film: Links aus .mirrors
|
||||
candidates = soup.select(".mirrors [data-link]")
|
||||
|
||||
seen_names: set[str] = set()
|
||||
for el in candidates:
|
||||
href = _absolute_url((el.get("data-link") or "").strip())
|
||||
if not href:
|
||||
continue
|
||||
if any(kw in href for kw in _SKIP_LINK_KEYWORDS):
|
||||
continue
|
||||
links.append(href)
|
||||
return links
|
||||
name = el.get_text(strip=True) or "Hoster"
|
||||
# Eindeutiger Name bei Duplikaten
|
||||
base_name = name
|
||||
i = 2
|
||||
while name in seen_names:
|
||||
name = f"{base_name} {i}"
|
||||
i += 1
|
||||
seen_names.add(name)
|
||||
hosters[name] = href
|
||||
return hosters
|
||||
|
||||
def _staffel_nr(self, season: str) -> int:
|
||||
"""Extrahiert die Staffelnummer aus einem Label wie 'Staffel 2'."""
|
||||
@@ -270,7 +295,7 @@ class HdfilmePlugin(BasisPlugin):
|
||||
if season == "Film":
|
||||
return [title]
|
||||
|
||||
detail_url = self._title_to_url.get(title, "")
|
||||
detail_url = self._ensure_detail_url(title)
|
||||
cached = self._episode_cache.get(detail_url)
|
||||
if cached is not None:
|
||||
return cached
|
||||
@@ -304,27 +329,40 @@ class HdfilmePlugin(BasisPlugin):
|
||||
self._episode_cache[detail_url] = result
|
||||
return result
|
||||
|
||||
def _hosters_for(self, title: str, season: str, episode: str) -> dict[str, str]:
|
||||
"""Gibt alle verfügbaren Hoster {Name → URL} für Titel/Staffel/Episode zurück."""
|
||||
soup = self._get_detail_soup(title)
|
||||
if soup is None:
|
||||
return {}
|
||||
if season == "Film" or not self._is_series.get(title, False):
|
||||
return self._extract_hoster_links(soup)
|
||||
staffel_nr = self._staffel_nr(season)
|
||||
ep_idx = self._ep_index(episode)
|
||||
episode_id = f"serie-{staffel_nr}_{ep_idx}"
|
||||
return self._extract_hoster_links(soup, episode_id)
|
||||
|
||||
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()
|
||||
season = (season or "").strip()
|
||||
if not title:
|
||||
return None
|
||||
|
||||
soup = self._get_detail_soup(title)
|
||||
if soup is None:
|
||||
hosters = self._hosters_for(title, season, episode)
|
||||
if not hosters:
|
||||
return None
|
||||
|
||||
if season == "Film" or not self._is_series.get(title, False):
|
||||
# Film: .mirrors [data-link]
|
||||
links = self._extract_hoster_links(soup)
|
||||
else:
|
||||
# Serie: Episode-Container
|
||||
staffel_nr = self._staffel_nr(season)
|
||||
ep_idx = self._ep_index(episode)
|
||||
episode_id = f"serie-{staffel_nr}_{ep_idx}"
|
||||
links = self._extract_hoster_links(soup, episode_id)
|
||||
|
||||
return links[0] if links else 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()
|
||||
@@ -390,7 +428,12 @@ class HdfilmePlugin(BasisPlugin):
|
||||
# Browsing
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def latest_titles(self, page: int = 1) -> List[str]:
|
||||
def new_titles(self) -> List[str]:
|
||||
if not REQUESTS_AVAILABLE:
|
||||
return []
|
||||
return self._parse_entries(_get_soup(_URL_NEW))
|
||||
|
||||
def new_titles_page(self, page: int = 1) -> List[str]:
|
||||
if not REQUESTS_AVAILABLE:
|
||||
return []
|
||||
page = max(1, int(page or 1))
|
||||
@@ -417,4 +460,4 @@ class HdfilmePlugin(BasisPlugin):
|
||||
return self._parse_entries(_get_soup(url))
|
||||
|
||||
def capabilities(self) -> set[str]:
|
||||
return {"latest_titles", "popular_series", "genres"}
|
||||
return {"new_titles", "popular_series", "genres"}
|
||||
|
||||
Reference in New Issue
Block a user