dev: umfangreiches Refactoring, Trakt-Integration und Code-Review-Fixes (0.1.69-dev)
Core & Architektur: - Neues Verzeichnis addon/core/ mit router.py, trakt.py, metadata.py, gui.py, playstate.py, plugin_manager.py, updater.py - Tests-Verzeichnis hinzugefügt (24 Tests, pytest + Coverage) Trakt-Integration: - OAuth Device Flow, Scrobbling, Watchlist, History, Calendar - Upcoming Episodes, Weiterschauen (Continue Watching) - Watched-Status in Episodenlisten - _trakt_find_in_plugins() mit 5-Min-Cache Serienstream-Suche: - API-Ergebnisse werden immer mit Katalog-Cache ergänzt (serverseitiges 10-Treffer-Limit) - Katalog-Cache wird beim Addon-Start im Daemon-Thread vorgewärmt - Notification nach Cache-Load via xbmc.executebuiltin() (thread-sicher) Bugfixes (Code-Review): - Race Condition auf _TRAKT_WATCHED_CACHE: _TRAKT_WATCHED_CACHE_LOCK hinzugefügt - GUI-Dialog aus Daemon-Thread: xbmcgui -> xbmc.executebuiltin() - ValueError in Trakt-Watchlist-Routen abgesichert - Token expires_at==0 Check korrigiert - get_setting_bool() Kontrollfluss in gui.py bereinigt - topstreamfilm_plugin: try-finally um xbmcvfs.File.close() Cleanup: - default.py.bak und refactor_router.py entfernt - .gitignore: /tests/ Eintrag entfernt - Type-Hints vereinheitlicht (Dict/List/Tuple -> dict/list/tuple)
This commit is contained in:
@@ -218,8 +218,10 @@ class TopstreamfilmPlugin(BasisPlugin):
|
||||
if directory and not xbmcvfs.exists(directory):
|
||||
xbmcvfs.mkdirs(directory)
|
||||
handle = xbmcvfs.File(path, "w")
|
||||
handle.write(payload)
|
||||
handle.close()
|
||||
try:
|
||||
handle.write(payload)
|
||||
finally:
|
||||
handle.close()
|
||||
else:
|
||||
with open(path, "w", encoding="utf-8") as handle:
|
||||
handle.write(payload)
|
||||
@@ -283,8 +285,10 @@ class TopstreamfilmPlugin(BasisPlugin):
|
||||
if directory and not xbmcvfs.exists(directory):
|
||||
xbmcvfs.mkdirs(directory)
|
||||
handle = xbmcvfs.File(path, "w")
|
||||
handle.write(payload)
|
||||
handle.close()
|
||||
try:
|
||||
handle.write(payload)
|
||||
finally:
|
||||
handle.close()
|
||||
else:
|
||||
with open(path, "w", encoding="utf-8") as handle:
|
||||
handle.write(payload)
|
||||
@@ -371,9 +375,6 @@ class TopstreamfilmPlugin(BasisPlugin):
|
||||
message=message,
|
||||
)
|
||||
|
||||
def capabilities(self) -> set[str]:
|
||||
return {"genres", "popular_series"}
|
||||
|
||||
def _popular_url(self) -> str:
|
||||
return self._absolute_url("/beliebte-filme-online.html")
|
||||
|
||||
@@ -1162,14 +1163,80 @@ class TopstreamfilmPlugin(BasisPlugin):
|
||||
return hosters.get(first_name)
|
||||
|
||||
def resolve_stream_link(self, link: str) -> Optional[str]:
|
||||
from plugin_helpers import resolve_via_resolveurl
|
||||
return resolve_via_resolveurl(link, fallback_to_link=True)
|
||||
|
||||
def capabilities(self) -> set[str]:
|
||||
return {"genres", "popular_series", "year_filter", "latest_titles"}
|
||||
|
||||
def years_available(self) -> List[str]:
|
||||
"""Liefert verfügbare Erscheinungsjahre (aktuelles Jahr bis 1980)."""
|
||||
import datetime
|
||||
current_year = datetime.date.today().year
|
||||
return [str(y) for y in range(current_year, 1979, -1)]
|
||||
|
||||
def titles_for_year(self, year: str, page: int = 1) -> List[str]:
|
||||
"""Liefert Titel für ein bestimmtes Erscheinungsjahr.
|
||||
|
||||
URL-Muster: /xfsearch/{year}/ oder /xfsearch/{year}/page/{n}/
|
||||
"""
|
||||
year = (year or "").strip()
|
||||
if not year or not REQUESTS_AVAILABLE or BeautifulSoup is None:
|
||||
return []
|
||||
page = max(1, int(page or 1))
|
||||
base = self._get_base_url()
|
||||
if page == 1:
|
||||
url = f"{base}/xfsearch/{year}/"
|
||||
else:
|
||||
url = f"{base}/xfsearch/{year}/page/{page}/"
|
||||
try:
|
||||
from resolveurl_backend import resolve as resolve_with_resolveurl
|
||||
soup = self._get_soup(url)
|
||||
except Exception:
|
||||
resolve_with_resolveurl = None
|
||||
if callable(resolve_with_resolveurl):
|
||||
resolved = resolve_with_resolveurl(link)
|
||||
return resolved or link
|
||||
return link
|
||||
return []
|
||||
hits = self._parse_listing_titles(soup)
|
||||
titles: List[str] = []
|
||||
seen: set[str] = set()
|
||||
for hit in hits:
|
||||
if hit.title in seen:
|
||||
continue
|
||||
seen.add(hit.title)
|
||||
self._title_to_url[hit.title] = hit.url
|
||||
self._store_title_meta(hit.title, plot=hit.description, poster=hit.poster)
|
||||
titles.append(hit.title)
|
||||
if titles:
|
||||
self._save_title_url_cache()
|
||||
return titles
|
||||
|
||||
def latest_titles(self, page: int = 1) -> List[str]:
|
||||
"""Liefert neu hinzugefügte Filme.
|
||||
|
||||
URL-Muster: /neueste-filme/ oder /neueste-filme/page/{n}/
|
||||
"""
|
||||
if not REQUESTS_AVAILABLE or BeautifulSoup is None:
|
||||
return []
|
||||
page = max(1, int(page or 1))
|
||||
base = self._get_base_url()
|
||||
if page == 1:
|
||||
url = f"{base}/neueste-filme/"
|
||||
else:
|
||||
url = f"{base}/neueste-filme/page/{page}/"
|
||||
try:
|
||||
soup = self._get_soup(url)
|
||||
except Exception:
|
||||
return []
|
||||
hits = self._parse_listing_titles(soup)
|
||||
titles: List[str] = []
|
||||
seen: set[str] = set()
|
||||
for hit in hits:
|
||||
if hit.title in seen:
|
||||
continue
|
||||
seen.add(hit.title)
|
||||
self._title_to_url[hit.title] = hit.url
|
||||
self._store_title_meta(hit.title, plot=hit.description, poster=hit.poster)
|
||||
titles.append(hit.title)
|
||||
if titles:
|
||||
self._save_title_url_cache()
|
||||
return titles
|
||||
|
||||
|
||||
# Alias für die automatische Plugin-Erkennung.
|
||||
|
||||
Reference in New Issue
Block a user