dev: harden resolver bootstrap and simplify update settings

This commit is contained in:
2026-02-24 16:18:44 +01:00
parent 99b67a24f8
commit 16e4b5f261
8 changed files with 298 additions and 71 deletions

View File

@@ -954,12 +954,6 @@ def _add_directory_item(
xbmcplugin.addDirectoryItem(handle=handle, url=url, listitem=item, isFolder=is_folder)
def _plugin_version(plugin: BasisPlugin) -> str:
raw = getattr(plugin, "version", "0.0.0")
text = str(raw or "").strip()
return text or "0.0.0"
def _normalize_update_info_url(raw: str) -> str:
value = str(raw or "").strip()
default = "http://127.0.0.1:8080/repo/addons.xml"
@@ -976,6 +970,8 @@ UPDATE_CHANNEL_CUSTOM = 2
AUTO_UPDATE_INTERVAL_SEC = 6 * 60 * 60
UPDATE_HTTP_TIMEOUT_SEC = 8
UPDATE_ADDON_ID = "plugin.video.viewit"
RESOLVEURL_ADDON_ID = "script.module.resolveurl"
RESOLVEURL_AUTO_INSTALL_INTERVAL_SEC = 6 * 60 * 60
def _selected_update_channel() -> int:
@@ -1030,10 +1026,7 @@ def _resolve_update_info_url() -> str:
raw = _get_setting_string("update_repo_url")
else:
raw = _get_setting_string("update_repo_url_main")
info_url = _normalize_update_info_url(raw)
# Legacy-Setting beibehalten, damit bestehende Installationen und alte Builds weiterlaufen.
_set_setting_string("update_repo_url", info_url)
return info_url
return _normalize_update_info_url(raw)
def _read_text_url(url: str, *, timeout: int = UPDATE_HTTP_TIMEOUT_SEC) -> str:
@@ -1247,25 +1240,10 @@ def _install_addon_version(info_url: str, version: str) -> bool:
def _sync_update_channel_status_settings() -> None:
channel = _selected_update_channel()
channel_label = _channel_label(channel)
selected_info_url = _resolve_update_info_url()
main_info_url = _normalize_update_info_url(_get_setting_string("update_repo_url_main"))
nightly_info_url = _normalize_update_info_url(_get_setting_string("update_repo_url_nightly"))
available_main = _fetch_repo_addon_version(main_info_url)
available_nightly = _fetch_repo_addon_version(nightly_info_url)
if channel == UPDATE_CHANNEL_MAIN:
available_selected = available_main
elif channel == UPDATE_CHANNEL_NIGHTLY:
available_selected = available_nightly
else:
available_selected = _fetch_repo_addon_version(selected_info_url)
_set_setting_string("update_active_channel", channel_label)
available_selected = _fetch_repo_addon_version(selected_info_url)
_set_setting_string("update_active_channel", _channel_label(channel))
_set_setting_string("update_active_repo_url", selected_info_url)
_set_setting_string("update_available_main", available_main)
_set_setting_string("update_available_nightly", available_nightly)
_set_setting_string("update_available_selected", available_selected)
@@ -1306,11 +1284,6 @@ def _update_repository_source(info_url: str) -> bool:
return False
def _settings_key_for_plugin(name: str) -> str:
safe = re.sub(r"[^a-z0-9]+", "_", (name or "").strip().casefold()).strip("_")
return f"update_version_{safe}" if safe else "update_version_unknown"
def _installed_addon_version_from_disk() -> str:
if xbmcvfs is None:
return "0.0.0"
@@ -1328,6 +1301,96 @@ def _installed_addon_version_from_disk() -> str:
return "0.0.0"
def _is_addon_installed(addon_id: str) -> bool:
addon_id = str(addon_id or "").strip()
if not addon_id:
return False
has_addon = getattr(xbmc, "getCondVisibility", None)
if callable(has_addon):
try:
return bool(has_addon(f"System.HasAddon({addon_id})"))
except Exception:
pass
if xbmcvfs is None:
return False
try:
addon_xml = xbmcvfs.translatePath(f"special://home/addons/{addon_id}/addon.xml")
except Exception:
return False
return bool(addon_xml and os.path.exists(addon_xml))
def _sync_resolveurl_status_setting() -> None:
status = "Installiert" if _is_addon_installed(RESOLVEURL_ADDON_ID) else "Fehlt"
_set_setting_string("resolveurl_status", status)
def _install_kodi_addon(addon_id: str, *, wait_seconds: int) -> bool:
if _is_addon_installed(addon_id):
return True
builtin = getattr(xbmc, "executebuiltin", None)
if not callable(builtin):
return False
try:
builtin(f"InstallAddon({addon_id})")
builtin("UpdateLocalAddons")
except Exception as exc:
_log(f"InstallAddon fehlgeschlagen ({addon_id}): {exc}", xbmc.LOGWARNING)
return False
if wait_seconds <= 0:
return _is_addon_installed(addon_id)
deadline = time.time() + max(1, int(wait_seconds))
while time.time() < deadline:
if _is_addon_installed(addon_id):
return True
time.sleep(1)
return _is_addon_installed(addon_id)
def _ensure_resolveurl_installed(*, force: bool, silent: bool) -> bool:
if _is_addon_installed(RESOLVEURL_ADDON_ID):
_sync_resolveurl_status_setting()
return True
if not force and not _get_setting_bool("resolveurl_auto_install", default=True):
_sync_resolveurl_status_setting()
return False
now = int(time.time())
if not force:
last_try = _get_setting_int("resolveurl_last_ts", default=0)
if last_try > 0 and (now - last_try) < RESOLVEURL_AUTO_INSTALL_INTERVAL_SEC:
return False
_set_setting_string("resolveurl_last_ts", str(now))
wait_seconds = 20 if force else 0
ok = _install_kodi_addon(RESOLVEURL_ADDON_ID, wait_seconds=wait_seconds)
_sync_resolveurl_status_setting()
if not silent:
if ok:
xbmcgui.Dialog().notification(
"ResolveURL",
"script.module.resolveurl ist installiert.",
xbmcgui.NOTIFICATION_INFO,
4000,
)
else:
xbmcgui.Dialog().notification(
"ResolveURL",
"Installation fehlgeschlagen. Bitte Repository/Netzwerk pruefen.",
xbmcgui.NOTIFICATION_ERROR,
5000,
)
return ok
def _maybe_auto_install_resolveurl(action: str | None) -> None:
if (action or "").strip():
return
_ensure_resolveurl_installed(force=False, silent=True)
def _sync_update_version_settings() -> None:
addon_version = _installed_addon_version_from_disk()
if addon_version == "0.0.0":
@@ -1337,24 +1400,8 @@ def _sync_update_version_settings() -> None:
addon_version = str(addon.getAddonInfo("version") or "0.0.0")
except Exception:
addon_version = "0.0.0"
_set_setting_string("update_version_addon", addon_version)
_set_setting_string("update_installed_version", addon_version)
versions = {
"update_version_serienstream": "-",
"update_version_aniworld": "-",
"update_version_einschalten": "-",
"update_version_topstreamfilm": "-",
"update_version_filmpalast": "-",
"update_version_doku_streams": "-",
}
for plugin in _discover_plugins().values():
key = _settings_key_for_plugin(str(plugin.name))
if key in versions:
versions[key] = _plugin_version(plugin)
for key, value in versions.items():
_set_setting_string(key, value)
_sync_resolveurl_status_setting()
_sync_update_channel_status_settings()
@@ -3543,14 +3590,18 @@ def _show_version_selector() -> None:
except Exception:
pass
confirm_choice = xbmcgui.Dialog().select(
"Version installieren",
[
f"Installieren: {version}",
"Abbrechen",
],
)
if confirm_choice != 0:
dialog = xbmcgui.Dialog()
try:
confirmed = dialog.yesno(
"Version installieren",
f"Installiert: {installed}",
f"Ausgewaehlt: {version}",
yeslabel="Installieren",
nolabel="Abbrechen",
)
except TypeError:
confirmed = dialog.yesno("Version installieren", f"Installiert: {installed}", f"Ausgewaehlt: {version}")
if not confirmed:
return
xbmcgui.Dialog().notification("Updates", f"Installation gestartet: {version}", xbmcgui.NOTIFICATION_INFO, 2500)
@@ -3609,6 +3660,10 @@ def _is_cloudflare_challenge_error(message: str) -> bool:
return "cloudflare" in text or "challenge" in text or "attention required" in text
def _is_resolveurl_missing_error(message: str) -> bool:
return str(message or "").strip().casefold() == "resolveurl missing"
def _play_final_link(
link: str,
*,
@@ -3743,6 +3798,11 @@ def _play_episode(
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(
@@ -3853,6 +3913,11 @@ def _play_episode_url(
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(
@@ -3914,6 +3979,7 @@ def run() -> None:
action = params.get("action")
_log(f"Action: {action}", xbmc.LOGDEBUG)
_maybe_run_auto_update_check(action)
_maybe_auto_install_resolveurl(action)
if action == "search":
_show_search()
elif action == "plugin_menu":
@@ -3991,6 +4057,8 @@ def run() -> None:
_apply_update_channel()
elif action == "select_update_version":
_show_version_selector()
elif action == "install_resolveurl":
_ensure_resolveurl_installed(force=True, silent=False)
elif action == "seasons":
_show_seasons(params.get("plugin", ""), params.get("title", ""), params.get("series_url", ""))
elif action == "episodes":