dev: bump to 0.1.74-dev – BurningSeries entfernt, Paging-Fix Neuste Titel
This commit is contained in:
261
addon/default.py
261
addon/default.py
@@ -1203,6 +1203,7 @@ def _normalize_update_info_url(raw: str) -> str:
|
||||
UPDATE_CHANNEL_MAIN = 0
|
||||
UPDATE_CHANNEL_NIGHTLY = 1
|
||||
UPDATE_CHANNEL_CUSTOM = 2
|
||||
UPDATE_CHANNEL_DEV = 3
|
||||
_AUTO_UPDATE_INTERVALS = [1 * 60 * 60, 6 * 60 * 60, 24 * 60 * 60] # 1h, 6h, 24h
|
||||
UPDATE_HTTP_TIMEOUT_SEC = 8
|
||||
UPDATE_ADDON_ID = "plugin.video.viewit"
|
||||
@@ -1212,7 +1213,7 @@ RESOLVEURL_AUTO_INSTALL_INTERVAL_SEC = 6 * 60 * 60
|
||||
|
||||
def _selected_update_channel() -> int:
|
||||
channel = _get_setting_int("update_channel", default=UPDATE_CHANNEL_MAIN)
|
||||
if channel not in {UPDATE_CHANNEL_MAIN, UPDATE_CHANNEL_NIGHTLY, UPDATE_CHANNEL_CUSTOM}:
|
||||
if channel not in {UPDATE_CHANNEL_MAIN, UPDATE_CHANNEL_NIGHTLY, UPDATE_CHANNEL_CUSTOM, UPDATE_CHANNEL_DEV}:
|
||||
return UPDATE_CHANNEL_MAIN
|
||||
return channel
|
||||
|
||||
@@ -1222,6 +1223,8 @@ def _channel_label(channel: int) -> str:
|
||||
return "Nightly"
|
||||
if channel == UPDATE_CHANNEL_CUSTOM:
|
||||
return "Custom"
|
||||
if channel == UPDATE_CHANNEL_DEV:
|
||||
return "Dev"
|
||||
return "Main"
|
||||
|
||||
|
||||
@@ -1246,11 +1249,17 @@ def _is_nightly_version(version: str) -> bool:
|
||||
return bool(re.match(r"^\d+\.\d+\.\d+-nightly$", str(version or "").strip()))
|
||||
|
||||
|
||||
def _is_dev_version(version: str) -> bool:
|
||||
return bool(re.match(r"^\d+\.\d+\.\d+-dev$", str(version or "").strip()))
|
||||
|
||||
|
||||
def _filter_versions_for_channel(channel: int, versions: list[str]) -> list[str]:
|
||||
if channel == UPDATE_CHANNEL_MAIN:
|
||||
return [v for v in versions if _is_stable_version(v)]
|
||||
if channel == UPDATE_CHANNEL_NIGHTLY:
|
||||
return [v for v in versions if _is_nightly_version(v)]
|
||||
if channel == UPDATE_CHANNEL_DEV:
|
||||
return [v for v in versions if _is_dev_version(v)]
|
||||
return list(versions)
|
||||
|
||||
|
||||
@@ -1260,6 +1269,8 @@ def _resolve_update_info_url() -> str:
|
||||
raw = _get_setting_string("update_repo_url_nightly")
|
||||
elif channel == UPDATE_CHANNEL_CUSTOM:
|
||||
raw = _get_setting_string("update_repo_url")
|
||||
elif channel == UPDATE_CHANNEL_DEV:
|
||||
raw = _get_setting_string("update_repo_url_dev")
|
||||
else:
|
||||
raw = _get_setting_string("update_repo_url_main")
|
||||
return _normalize_update_info_url(raw)
|
||||
@@ -1661,24 +1672,6 @@ def _show_root_menu() -> None:
|
||||
for plugin_name in sorted(plugins.keys(), key=lambda value: value.casefold()):
|
||||
_add_directory_item(handle, plugin_name, "plugin_menu", {"plugin": plugin_name}, is_folder=True)
|
||||
|
||||
# Katalog-Caches im Hintergrund vorwaermen (fire-and-forget)
|
||||
for _pname, _plugin in plugins.items():
|
||||
_warmer = getattr(_plugin, "warm_catalog_cache", None)
|
||||
if callable(_warmer):
|
||||
def _warm_and_notify(_fn=_warmer, _name=_pname):
|
||||
try:
|
||||
loaded = _fn()
|
||||
except Exception:
|
||||
loaded = False
|
||||
if loaded:
|
||||
# executebuiltin ist thread-sicher; xbmcgui.Dialog() darf nicht
|
||||
# aus Daemon-Threads aufgerufen werden (Kodi-Absturzgefahr).
|
||||
safe_name = _name.replace('"', "")
|
||||
xbmc.executebuiltin(
|
||||
f'Notification("{safe_name}", "Suchindex geladen", 3000, "")'
|
||||
)
|
||||
threading.Thread(target=_warm_and_notify, daemon=True, name=f"viewit-warmup-{_pname}").start()
|
||||
|
||||
# Trakt-Menue (nur wenn aktiviert)
|
||||
if _get_setting_bool("trakt_enabled", default=False):
|
||||
if _trakt_load_token():
|
||||
@@ -1704,7 +1697,8 @@ def _show_plugin_menu(plugin_name: str) -> None:
|
||||
|
||||
xbmcplugin.setPluginCategory(handle, plugin_name)
|
||||
|
||||
_add_directory_item(handle, "Suche", "plugin_search", {"plugin": plugin_name}, is_folder=True)
|
||||
if callable(getattr(plugin, "search_titles", None)):
|
||||
_add_directory_item(handle, "Suche", "plugin_search", {"plugin": plugin_name}, is_folder=True)
|
||||
|
||||
if _plugin_has_capability(plugin, "new_titles") or _plugin_has_capability(plugin, "latest_episodes"):
|
||||
_add_directory_item(handle, LATEST_MENU_LABEL, "latest_titles", {"plugin": plugin_name, "page": "1"}, is_folder=True)
|
||||
@@ -2527,34 +2521,6 @@ def _show_episodes(plugin_name: str, title: str, season: str, series_url: str =
|
||||
xbmcplugin.endOfDirectory(handle)
|
||||
|
||||
|
||||
def _show_genre_sources() -> None:
|
||||
handle = _get_handle()
|
||||
_log("Genre-Quellen laden.")
|
||||
plugins = _discover_plugins()
|
||||
sources: list[tuple[str, BasisPlugin]] = []
|
||||
for plugin_name, plugin in plugins.items():
|
||||
if plugin.__class__.genres is BasisPlugin.genres:
|
||||
continue
|
||||
if plugin.__class__.titles_for_genre is BasisPlugin.titles_for_genre:
|
||||
continue
|
||||
sources.append((plugin_name, plugin))
|
||||
|
||||
if not sources:
|
||||
xbmcgui.Dialog().notification("Genres", "Keine Quellen mit Genres gefunden.", xbmcgui.NOTIFICATION_INFO, 3000)
|
||||
xbmcplugin.endOfDirectory(handle)
|
||||
return
|
||||
|
||||
for plugin_name, plugin in sources:
|
||||
_add_directory_item(
|
||||
handle,
|
||||
f"Genres [{plugin_name}]",
|
||||
"genres",
|
||||
{"plugin": plugin_name},
|
||||
is_folder=True,
|
||||
)
|
||||
xbmcplugin.endOfDirectory(handle)
|
||||
|
||||
|
||||
def _show_genres(plugin_name: str) -> None:
|
||||
handle = _get_handle()
|
||||
_log(f"Genres laden: {plugin_name}")
|
||||
@@ -2596,44 +2562,6 @@ def _show_genres(plugin_name: str) -> None:
|
||||
xbmcplugin.endOfDirectory(handle)
|
||||
|
||||
|
||||
def _show_categories(plugin_name: str) -> None:
|
||||
handle = _get_handle()
|
||||
_log(f"Kategorien laden: {plugin_name}")
|
||||
plugin = _discover_plugins().get(plugin_name)
|
||||
if plugin is None:
|
||||
xbmcgui.Dialog().notification("Kategorien", "Quelle nicht gefunden.", xbmcgui.NOTIFICATION_INFO, 3000)
|
||||
xbmcplugin.endOfDirectory(handle)
|
||||
return
|
||||
getter = getattr(plugin, "categories", None)
|
||||
if not callable(getter):
|
||||
xbmcgui.Dialog().notification("Kategorien", "Kategorien nicht verfuegbar.", xbmcgui.NOTIFICATION_INFO, 3000)
|
||||
xbmcplugin.endOfDirectory(handle)
|
||||
return
|
||||
try:
|
||||
categories = _run_with_progress(
|
||||
"Kategorien",
|
||||
f"{plugin_name}: Kategorien werden geladen...",
|
||||
lambda: list(getter() or []),
|
||||
)
|
||||
except Exception as exc:
|
||||
_log(f"Kategorien konnten nicht geladen werden ({plugin_name}): {exc}", xbmc.LOGWARNING)
|
||||
xbmcgui.Dialog().notification("Kategorien", "Kategorien konnten nicht geladen werden.", xbmcgui.NOTIFICATION_INFO, 3000)
|
||||
xbmcplugin.endOfDirectory(handle)
|
||||
return
|
||||
for category in categories:
|
||||
category = str(category).strip()
|
||||
if not category:
|
||||
continue
|
||||
_add_directory_item(
|
||||
handle,
|
||||
category,
|
||||
"category_titles_page",
|
||||
{"plugin": plugin_name, "category": category, "page": "1"},
|
||||
is_folder=True,
|
||||
)
|
||||
xbmcplugin.endOfDirectory(handle)
|
||||
|
||||
|
||||
def _show_paged_title_list(
|
||||
plugin_name: str,
|
||||
filter_value: str,
|
||||
@@ -2767,18 +2695,6 @@ def _show_paged_title_list(
|
||||
xbmcplugin.endOfDirectory(handle)
|
||||
|
||||
|
||||
def _show_category_titles_page(plugin_name: str, category: str, page: int = 1) -> None:
|
||||
_show_paged_title_list(
|
||||
plugin_name, category, page,
|
||||
dialog_label="Kategorien",
|
||||
page_action="category_titles_page",
|
||||
filter_param="category",
|
||||
paging_method="titles_for_genre_page",
|
||||
count_method="genre_page_count",
|
||||
has_more_method="genre_has_more",
|
||||
)
|
||||
|
||||
|
||||
def _show_genre_titles_page(plugin_name: str, genre: str, page: int = 1) -> None:
|
||||
_show_paged_title_list(
|
||||
plugin_name, genre, page,
|
||||
@@ -3351,6 +3267,8 @@ def _show_new_titles(plugin_name: str, page: int = 1, *, action_name: str = "new
|
||||
show_next = bool(has_more_getter(page))
|
||||
except Exception:
|
||||
show_next = False
|
||||
elif callable(paging_getter) and page_items:
|
||||
show_next = True
|
||||
elif "total_pages" in locals():
|
||||
show_next = bool(total_pages > 1 and page < total_pages) # type: ignore[name-defined]
|
||||
|
||||
@@ -3445,7 +3363,12 @@ def _show_latest_episodes(plugin_name: str, page: int = 1) -> None:
|
||||
info_labels=info_labels,
|
||||
)
|
||||
|
||||
if entries:
|
||||
has_more_fn = getattr(plugin, "latest_episodes_has_more", None)
|
||||
if callable(has_more_fn):
|
||||
show_next = bool(has_more_fn(page))
|
||||
else:
|
||||
show_next = False
|
||||
if show_next:
|
||||
_add_directory_item(handle, "Naechste Seite", "latest_titles",
|
||||
{"plugin": plugin_name, "page": str(page + 1)}, is_folder=True)
|
||||
|
||||
@@ -3842,6 +3765,51 @@ def _is_resolveurl_missing_error(message: str) -> bool:
|
||||
return str(message or "").strip().casefold() == "resolveurl missing"
|
||||
|
||||
|
||||
def _resolve_stream_with_retry(plugin: BasisPlugin, link: str) -> str | None:
|
||||
"""Löst einen Stream-Link auf mit ResolveURL-Auto-Install und CF-Check.
|
||||
|
||||
Gibt den finalen Link zurück oder None (mit Kodi-Notification bei Fehler).
|
||||
"""
|
||||
resolved_link = plugin.resolve_stream_link(link)
|
||||
if not resolved_link:
|
||||
err = _resolveurl_last_error()
|
||||
if _is_resolveurl_missing_error(err):
|
||||
_log("ResolveURL fehlt: versuche Auto-Installation.", xbmc.LOGWARNING)
|
||||
_ensure_resolveurl_installed(force=True, silent=True)
|
||||
resolved_link = plugin.resolve_stream_link(link)
|
||||
err = _resolveurl_last_error()
|
||||
if _is_cloudflare_challenge_error(err):
|
||||
_log(f"ResolveURL Cloudflare-Challenge: {err}", xbmc.LOGWARNING)
|
||||
xbmcgui.Dialog().notification(
|
||||
"Wiedergabe",
|
||||
"Hoster durch Cloudflare geschuetzt. Bitte spaeter erneut probieren.",
|
||||
xbmcgui.NOTIFICATION_INFO,
|
||||
4500,
|
||||
)
|
||||
return None
|
||||
if not resolved_link:
|
||||
_log("Stream konnte nicht aufgeloest werden.", xbmc.LOGWARNING)
|
||||
xbmcgui.Dialog().notification(
|
||||
"Wiedergabe",
|
||||
"Stream konnte nicht aufgeloest werden.",
|
||||
xbmcgui.NOTIFICATION_INFO,
|
||||
3000,
|
||||
)
|
||||
return None
|
||||
final_link = normalize_resolved_stream_url(resolved_link, source_url=link)
|
||||
err = _resolveurl_last_error()
|
||||
if _is_cloudflare_challenge_error(err) and final_link.strip() == link.strip():
|
||||
_log(f"ResolveURL Cloudflare-Challenge (unresolved): {err}", xbmc.LOGWARNING)
|
||||
xbmcgui.Dialog().notification(
|
||||
"Wiedergabe",
|
||||
"Hoster durch Cloudflare geschuetzt. Bitte spaeter erneut probieren.",
|
||||
xbmcgui.NOTIFICATION_INFO,
|
||||
4500,
|
||||
)
|
||||
return None
|
||||
return final_link
|
||||
|
||||
|
||||
def _play_final_link(
|
||||
link: str,
|
||||
*,
|
||||
@@ -4086,43 +4054,8 @@ def _play_episode(
|
||||
xbmcgui.Dialog().notification("Wiedergabe", "Kein Stream gefunden.", xbmcgui.NOTIFICATION_INFO, 3000)
|
||||
return
|
||||
_log(f"Stream-Link: {link}", xbmc.LOGDEBUG)
|
||||
resolved_link = plugin.resolve_stream_link(link)
|
||||
if not resolved_link:
|
||||
err = _resolveurl_last_error()
|
||||
if _is_resolveurl_missing_error(err):
|
||||
_log("ResolveURL fehlt: versuche Auto-Installation.", xbmc.LOGWARNING)
|
||||
_ensure_resolveurl_installed(force=True, silent=True)
|
||||
resolved_link = plugin.resolve_stream_link(link)
|
||||
err = _resolveurl_last_error()
|
||||
if _is_cloudflare_challenge_error(err):
|
||||
_log(f"ResolveURL Cloudflare-Challenge: {err}", xbmc.LOGWARNING)
|
||||
xbmcgui.Dialog().notification(
|
||||
"Wiedergabe",
|
||||
"Hoster durch Cloudflare geschuetzt. Bitte spaeter erneut probieren.",
|
||||
xbmcgui.NOTIFICATION_INFO,
|
||||
4500,
|
||||
)
|
||||
return
|
||||
if not resolved_link:
|
||||
_log("Stream konnte nicht aufgeloest werden.", xbmc.LOGWARNING)
|
||||
xbmcgui.Dialog().notification(
|
||||
"Wiedergabe",
|
||||
"Stream konnte nicht aufgeloest werden.",
|
||||
xbmcgui.NOTIFICATION_INFO,
|
||||
3000,
|
||||
)
|
||||
return
|
||||
final_link = resolved_link
|
||||
final_link = normalize_resolved_stream_url(final_link, source_url=link)
|
||||
err = _resolveurl_last_error()
|
||||
if _is_cloudflare_challenge_error(err) and final_link.strip() == link.strip():
|
||||
_log(f"ResolveURL Cloudflare-Challenge (unresolved): {err}", xbmc.LOGWARNING)
|
||||
xbmcgui.Dialog().notification(
|
||||
"Wiedergabe",
|
||||
"Hoster durch Cloudflare geschuetzt. Bitte spaeter erneut probieren.",
|
||||
xbmcgui.NOTIFICATION_INFO,
|
||||
4500,
|
||||
)
|
||||
final_link = _resolve_stream_with_retry(plugin, link)
|
||||
if not final_link:
|
||||
return
|
||||
finally:
|
||||
if restore_hosters is not None and callable(preferred_setter):
|
||||
@@ -4237,43 +4170,8 @@ def _play_episode_url(
|
||||
xbmcgui.Dialog().notification("Wiedergabe", "Kein Stream gefunden.", xbmcgui.NOTIFICATION_INFO, 3000)
|
||||
return
|
||||
_log(f"Stream-Link: {link}", xbmc.LOGDEBUG)
|
||||
resolved_link = plugin.resolve_stream_link(link)
|
||||
if not resolved_link:
|
||||
err = _resolveurl_last_error()
|
||||
if _is_resolveurl_missing_error(err):
|
||||
_log("ResolveURL fehlt: versuche Auto-Installation.", xbmc.LOGWARNING)
|
||||
_ensure_resolveurl_installed(force=True, silent=True)
|
||||
resolved_link = plugin.resolve_stream_link(link)
|
||||
err = _resolveurl_last_error()
|
||||
if _is_cloudflare_challenge_error(err):
|
||||
_log(f"ResolveURL Cloudflare-Challenge: {err}", xbmc.LOGWARNING)
|
||||
xbmcgui.Dialog().notification(
|
||||
"Wiedergabe",
|
||||
"Hoster durch Cloudflare geschuetzt. Bitte spaeter erneut probieren.",
|
||||
xbmcgui.NOTIFICATION_INFO,
|
||||
4500,
|
||||
)
|
||||
return
|
||||
if not resolved_link:
|
||||
_log("Stream konnte nicht aufgeloest werden.", xbmc.LOGWARNING)
|
||||
xbmcgui.Dialog().notification(
|
||||
"Wiedergabe",
|
||||
"Stream konnte nicht aufgeloest werden.",
|
||||
xbmcgui.NOTIFICATION_INFO,
|
||||
3000,
|
||||
)
|
||||
return
|
||||
final_link = resolved_link
|
||||
final_link = normalize_resolved_stream_url(final_link, source_url=link)
|
||||
err = _resolveurl_last_error()
|
||||
if _is_cloudflare_challenge_error(err) and final_link.strip() == link.strip():
|
||||
_log(f"ResolveURL Cloudflare-Challenge (unresolved): {err}", xbmc.LOGWARNING)
|
||||
xbmcgui.Dialog().notification(
|
||||
"Wiedergabe",
|
||||
"Hoster durch Cloudflare geschuetzt. Bitte spaeter erneut probieren.",
|
||||
xbmcgui.NOTIFICATION_INFO,
|
||||
4500,
|
||||
)
|
||||
final_link = _resolve_stream_with_retry(plugin, link)
|
||||
if not final_link:
|
||||
return
|
||||
finally:
|
||||
if restore_hosters is not None and callable(preferred_setter):
|
||||
@@ -4945,21 +4843,11 @@ def _route_plugin_search(params: dict[str, str]) -> None:
|
||||
_show_plugin_search(params.get("plugin", ""))
|
||||
|
||||
|
||||
@_router.route("genre_sources")
|
||||
def _route_genre_sources(params: dict[str, str]) -> None:
|
||||
_show_genre_sources()
|
||||
|
||||
|
||||
@_router.route("genres")
|
||||
def _route_genres(params: dict[str, str]) -> None:
|
||||
_show_genres(params.get("plugin", ""))
|
||||
|
||||
|
||||
@_router.route("categories")
|
||||
def _route_categories(params: dict[str, str]) -> None:
|
||||
_show_categories(params.get("plugin", ""))
|
||||
|
||||
|
||||
@_router.route("latest_titles")
|
||||
def _route_latest_titles(params: dict[str, str]) -> None:
|
||||
_show_latest_titles(params.get("plugin", ""), _parse_positive_int(params.get("page", "1"), default=1))
|
||||
@@ -4988,13 +4876,6 @@ def _route_genre_titles_page(params: dict[str, str]) -> None:
|
||||
)
|
||||
|
||||
|
||||
@_router.route("category_titles_page")
|
||||
def _route_category_titles_page(params: dict[str, str]) -> None:
|
||||
_show_category_titles_page(
|
||||
params.get("plugin", ""), params.get("category", ""),
|
||||
_parse_positive_int(params.get("page", "1"), default=1),
|
||||
)
|
||||
|
||||
|
||||
@_router.route("alpha_index")
|
||||
def _route_alpha_index(params: dict[str, str]) -> None:
|
||||
|
||||
Reference in New Issue
Block a user