dev: bump to 0.1.84.0-dev Trakt Weiterschauen via watched/shows, Specials überspringen

This commit is contained in:
2026-03-16 22:43:36 +01:00
parent 4e9ae348b9
commit 938f6c0e3d
4 changed files with 91 additions and 24 deletions

View File

@@ -1,3 +1,7 @@
## 0.1.83.5-dev - 2026-03-15
- dev: SerienStream Suche via /suche?term=, Staffel 0 als Filme, Katalog-Suche entfernt
## 0.1.83.0-dev - 2026-03-15 ## 0.1.83.0-dev - 2026-03-15
- dev: Trakt Performance, Suchfilter Phrase-Match, Debug-Settings Expert-Level - dev: Trakt Performance, Suchfilter Phrase-Match, Debug-Settings Expert-Level

View File

@@ -1,5 +1,5 @@
<?xml version='1.0' encoding='utf-8'?> <?xml version='1.0' encoding='utf-8'?>
<addon id="plugin.video.viewit" name="ViewIt" version="0.1.83.5-dev" provider-name="ViewIt"> <addon id="plugin.video.viewit" name="ViewIt" version="0.1.84.0-dev" provider-name="ViewIt">
<requires> <requires>
<import addon="xbmc.python" version="3.0.0" /> <import addon="xbmc.python" version="3.0.0" />
<import addon="script.module.requests" /> <import addon="script.module.requests" />

View File

@@ -370,6 +370,40 @@ class TraktClient:
return [] return []
return self._parse_history_items(payload) return self._parse_history_items(payload)
def get_watched_shows(self, token: str) -> list[TraktItem]:
"""GET /users/me/watched/shows alle Serien mit zuletzt gesehener Episode."""
status, payload = self._get("/users/me/watched/shows", token=token)
if status != 200 or not isinstance(payload, list):
self._do_log(f"get_watched_shows: status={status}")
return []
result: list[TraktItem] = []
for entry in payload:
if not isinstance(entry, dict):
continue
show = entry.get("show") or {}
ids = self._parse_ids((show.get("ids") or {}))
title = str(show.get("title", "") or "")
year = int(show.get("year", 0) or 0)
seasons = entry.get("seasons") or []
last_season = 0
last_episode = 0
for s in seasons:
snum = int((s.get("number") or 0))
if snum == 0: # Specials überspringen
continue
for ep in (s.get("episodes") or []):
enum = int((ep.get("number") or 0))
if snum > last_season or (snum == last_season and enum > last_episode):
last_season = snum
last_episode = enum
if title:
result.append(TraktItem(
title=title, year=year, media_type="episode",
ids=ids, season=last_season, episode=last_episode,
))
self._do_log(f"get_watched_shows: {len(result)} Serien")
return result
# ------------------------------------------------------------------- # -------------------------------------------------------------------
# Calendar # Calendar
# ------------------------------------------------------------------- # -------------------------------------------------------------------

View File

@@ -2095,8 +2095,17 @@ def _run_async(coro):
"""Fuehrt eine Coroutine aus, auch wenn Kodi bereits einen Event-Loop hat.""" """Fuehrt eine Coroutine aus, auch wenn Kodi bereits einen Event-Loop hat."""
_ensure_windows_selector_policy() _ensure_windows_selector_policy()
def _run_with_asyncio_run(): def _run_without_asyncio_run():
return asyncio.run(coro) # asyncio.run() wuerde cancel_all_tasks() aufrufen, was auf Android TV
# wegen eines kaputten _weakrefset.py-Builds zu NameError: 'len' fuehrt.
loop = asyncio.new_event_loop()
try:
return loop.run_until_complete(coro)
finally:
try:
loop.close()
except Exception:
pass
try: try:
running_loop = asyncio.get_running_loop() running_loop = asyncio.get_running_loop()
@@ -2109,7 +2118,7 @@ def _run_async(coro):
def _worker() -> None: def _worker() -> None:
try: try:
result_box["value"] = _run_with_asyncio_run() result_box["value"] = _run_without_asyncio_run()
except BaseException as exc: # pragma: no cover - defensive except BaseException as exc: # pragma: no cover - defensive
error_box["error"] = exc error_box["error"] = exc
@@ -2120,7 +2129,7 @@ def _run_async(coro):
raise error_box["error"] raise error_box["error"]
return result_box.get("value") return result_box.get("value")
return _run_with_asyncio_run() return _run_without_asyncio_run()
def _series_url_params(plugin: BasisPlugin, title: str) -> dict[str, str]: def _series_url_params(plugin: BasisPlugin, title: str) -> dict[str, str]:
@@ -4414,13 +4423,15 @@ def _play_episode(
preferred_setter([selected_hoster]) preferred_setter([selected_hoster])
try: try:
link = plugin.stream_link_for(title, season, episode) with _busy_dialog("Stream wird gesucht..."):
link = plugin.stream_link_for(title, season, episode)
if not link: if not link:
_log("Kein Stream gefunden.", xbmc.LOGWARNING) _log("Kein Stream gefunden.", xbmc.LOGWARNING)
xbmcgui.Dialog().notification("Wiedergabe", "Kein Stream gefunden.", xbmcgui.NOTIFICATION_INFO, 3000) xbmcgui.Dialog().notification("Wiedergabe", "Kein Stream gefunden.", xbmcgui.NOTIFICATION_INFO, 3000)
return return
_log(f"Stream-Link: {link}", xbmc.LOGDEBUG) _log(f"Stream-Link: {link}", xbmc.LOGDEBUG)
final_link = _resolve_stream_with_retry(plugin, link) with _busy_dialog("Stream wird aufgelöst..."):
final_link = _resolve_stream_with_retry(plugin, link)
if not final_link: if not final_link:
return return
finally: finally:
@@ -4815,11 +4826,33 @@ def _show_tag_titles_page(plugin_name: str, tag: str, page: int = 1) -> None:
xbmcplugin.endOfDirectory(handle) xbmcplugin.endOfDirectory(handle)
return return
titles = [str(t).strip() for t in titles if t and str(t).strip()] titles = [str(t).strip() for t in titles if t and str(t).strip()]
for title in titles:
_add_directory_item(handle, title, "seasons",
{"plugin": plugin_name, "title": title, **_series_url_params(plugin, title)},
is_folder=True)
if titles: if titles:
use_source, show_tmdb, prefer_source = _metadata_policy(
plugin_name, plugin, allow_tmdb=_tmdb_list_enabled()
)
plugin_meta = _collect_plugin_metadata(plugin, titles) if use_source else {}
show_plot = _get_setting_bool("tmdb_show_plot", default=True)
show_art = _get_setting_bool("tmdb_show_art", default=True)
tmdb_prefetched: dict[str, tuple[dict[str, str], dict[str, str], list[TmdbCastMember]]] = {}
tmdb_titles = list(titles) if show_tmdb else []
if show_tmdb and prefer_source and use_source:
tmdb_titles = [
t for t in titles
if _needs_tmdb((plugin_meta.get(t) or ({},))[0], (plugin_meta.get(t) or ({}, {}))[1],
want_plot=show_plot, want_art=show_art)
]
if show_tmdb and tmdb_titles:
with _busy_dialog("Schlagwort-Liste wird geladen..."):
tmdb_prefetched = _tmdb_labels_and_art_bulk(tmdb_titles)
for title in titles:
tmdb_info, tmdb_art, tmdb_cast = tmdb_prefetched.get(title, ({}, {}, [])) if show_tmdb else ({}, {}, [])
meta = plugin_meta.get(title)
info_labels, art, cast = _merge_metadata(title, tmdb_info, tmdb_art, tmdb_cast, meta)
info_labels = dict(info_labels or {})
info_labels.setdefault("mediatype", "tvshow")
_add_directory_item(handle, title, "seasons",
{"plugin": plugin_name, "title": title, **_series_url_params(plugin, title)},
is_folder=True, info_labels=info_labels, art=art, cast=cast)
_add_directory_item(handle, "Naechste Seite", "tag_titles_page", _add_directory_item(handle, "Naechste Seite", "tag_titles_page",
{"plugin": plugin_name, "tag": tag, "page": str(page + 1)}, is_folder=True) {"plugin": plugin_name, "tag": tag, "page": str(page + 1)}, is_folder=True)
xbmcplugin.endOfDirectory(handle) xbmcplugin.endOfDirectory(handle)
@@ -4929,7 +4962,7 @@ def _show_trakt_watchlist(media_type: str = "") -> None:
_add_directory_item(handle, label, "search", {"query": item.title}, is_folder=True, info_labels=info_labels, art=art) _add_directory_item(handle, label, "search", {"query": item.title}, is_folder=True, info_labels=info_labels, art=art)
if not items: if not items:
xbmcgui.Dialog().notification("Trakt", "Watchlist ist leer.", xbmcgui.NOTIFICATION_INFO, 3000) xbmcgui.Dialog().notification("Trakt", "Watchlist ist leer.", xbmcgui.NOTIFICATION_INFO, 3000)
xbmcplugin.endOfDirectory(handle) xbmcplugin.endOfDirectory(handle, cacheToDisc=False)
def _show_trakt_history(page: int = 1) -> None: def _show_trakt_history(page: int = 1) -> None:
@@ -4999,7 +5032,7 @@ def _show_trakt_history(page: int = 1) -> None:
_add_directory_item(handle, "Naechste Seite >>", "trakt_history", {"page": str(page + 1)}, is_folder=True) _add_directory_item(handle, "Naechste Seite >>", "trakt_history", {"page": str(page + 1)}, is_folder=True)
if not items and page == 1: if not items and page == 1:
xbmcgui.Dialog().notification("Trakt", "Keine History vorhanden.", xbmcgui.NOTIFICATION_INFO, 3000) xbmcgui.Dialog().notification("Trakt", "Keine History vorhanden.", xbmcgui.NOTIFICATION_INFO, 3000)
xbmcplugin.endOfDirectory(handle) xbmcplugin.endOfDirectory(handle, cacheToDisc=False)
def _show_trakt_upcoming() -> None: def _show_trakt_upcoming() -> None:
@@ -5110,7 +5143,7 @@ def _show_trakt_upcoming() -> None:
_add_directory_item(handle, label, action, params, is_folder=True, info_labels=info_labels, art=art) _add_directory_item(handle, label, action, params, is_folder=True, info_labels=info_labels, art=art)
xbmcplugin.endOfDirectory(handle) xbmcplugin.endOfDirectory(handle, cacheToDisc=False)
def _show_trakt_continue_watching() -> None: def _show_trakt_continue_watching() -> None:
@@ -5127,21 +5160,17 @@ def _show_trakt_continue_watching() -> None:
_set_content(handle, "episodes") _set_content(handle, "episodes")
try: try:
history = client.get_history(token, media_type="episodes", limit=100) watched = client.get_watched_shows(token)
except Exception as exc: except Exception as exc:
_log(f"Trakt History fehlgeschlagen: {exc}", xbmc.LOGWARNING) _log(f"Trakt Watched fehlgeschlagen: {exc}", xbmc.LOGWARNING)
xbmcgui.Dialog().notification("Trakt", "History konnte nicht geladen werden.", xbmcgui.NOTIFICATION_INFO, 3000) xbmcgui.Dialog().notification("Trakt", "Watched-Liste konnte nicht geladen werden.", xbmcgui.NOTIFICATION_INFO, 3000)
xbmcplugin.endOfDirectory(handle) xbmcplugin.endOfDirectory(handle)
return return
# Pro Serie nur den zuletzt gesehenen Eintrag behalten (History ist absteigend sortiert) seen: dict[str, object] = {item.title: item for item in watched if item.title}
seen: dict[str, object] = {}
for item in history:
if item.title and item.title not in seen:
seen[item.title] = item
if not seen: if not seen:
xbmcgui.Dialog().notification("Trakt", "Keine History vorhanden.", xbmcgui.NOTIFICATION_INFO, 3000) xbmcgui.Dialog().notification("Trakt", "Keine gesehenen Serien vorhanden.", xbmcgui.NOTIFICATION_INFO, 3000)
xbmcplugin.endOfDirectory(handle) xbmcplugin.endOfDirectory(handle)
return return
@@ -5169,7 +5198,7 @@ def _show_trakt_continue_watching() -> None:
_, art, _ = tmdb_prefetched.get(last.title, ({}, {}, [])) _, art, _ = tmdb_prefetched.get(last.title, ({}, {}, []))
_add_directory_item(handle, display_label, "search", {"query": last.title}, is_folder=True, info_labels=info_labels, art=art) _add_directory_item(handle, display_label, "search", {"query": last.title}, is_folder=True, info_labels=info_labels, art=art)
xbmcplugin.endOfDirectory(handle) xbmcplugin.endOfDirectory(handle, cacheToDisc=False)
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------