dev: bump to 0.1.84.0-dev Trakt Weiterschauen via watched/shows, Specials überspringen
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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" />
|
||||||
|
|||||||
@@ -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
|
||||||
# -------------------------------------------------------------------
|
# -------------------------------------------------------------------
|
||||||
|
|||||||
@@ -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,12 +4423,14 @@ def _play_episode(
|
|||||||
preferred_setter([selected_hoster])
|
preferred_setter([selected_hoster])
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
with _busy_dialog("Stream wird gesucht..."):
|
||||||
link = plugin.stream_link_for(title, season, episode)
|
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)
|
||||||
|
with _busy_dialog("Stream wird aufgelöst..."):
|
||||||
final_link = _resolve_stream_with_retry(plugin, link)
|
final_link = _resolve_stream_with_retry(plugin, link)
|
||||||
if not final_link:
|
if not final_link:
|
||||||
return
|
return
|
||||||
@@ -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()]
|
||||||
|
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:
|
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",
|
_add_directory_item(handle, title, "seasons",
|
||||||
{"plugin": plugin_name, "title": title, **_series_url_params(plugin, title)},
|
{"plugin": plugin_name, "title": title, **_series_url_params(plugin, title)},
|
||||||
is_folder=True)
|
is_folder=True, info_labels=info_labels, art=art, cast=cast)
|
||||||
if titles:
|
|
||||||
_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)
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|||||||
Reference in New Issue
Block a user