Implement ViewIt Plugin System Documentation and Update Project Notes
- Added comprehensive documentation for the ViewIt Plugin System, detailing the plugin loading process, required methods, optional features, and community extension workflow. - Updated project notes to reflect the current structure, build process, search logic, and known issues. - Introduced new build scripts for installing the add-on and creating ZIP packages. - Added test scripts for TMDB API integration, including argument parsing and logging functionality. - Enhanced existing plugins with improved search logic and error handling.
This commit is contained in:
128
addon/plugin_helpers.py
Normal file
128
addon/plugin_helpers.py
Normal file
@@ -0,0 +1,128 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Shared helpers for ViewIt plugins.
|
||||
|
||||
Focus:
|
||||
- Kodi addon settings access (string/bool)
|
||||
- Optional URL notifications
|
||||
- Optional URL logging
|
||||
- Optional HTML response dumps
|
||||
|
||||
Designed to work both in Kodi and outside Kodi (for linting/tests).
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime
|
||||
import hashlib
|
||||
import os
|
||||
from typing import Optional
|
||||
|
||||
try: # pragma: no cover - Kodi runtime
|
||||
import xbmcaddon # type: ignore[import-not-found]
|
||||
import xbmcvfs # type: ignore[import-not-found]
|
||||
import xbmcgui # type: ignore[import-not-found]
|
||||
except ImportError: # pragma: no cover - allow importing outside Kodi
|
||||
xbmcaddon = None
|
||||
xbmcvfs = None
|
||||
xbmcgui = None
|
||||
|
||||
|
||||
def get_setting_string(addon_id: str, setting_id: str, *, default: str = "") -> str:
|
||||
if xbmcaddon is None:
|
||||
return default
|
||||
try:
|
||||
addon = xbmcaddon.Addon(addon_id)
|
||||
getter = getattr(addon, "getSettingString", None)
|
||||
if getter is not None:
|
||||
return str(getter(setting_id) or "").strip()
|
||||
return str(addon.getSetting(setting_id) or "").strip()
|
||||
except Exception:
|
||||
return default
|
||||
|
||||
|
||||
def get_setting_bool(addon_id: str, setting_id: str, *, default: bool = False) -> bool:
|
||||
if xbmcaddon is None:
|
||||
return default
|
||||
try:
|
||||
addon = xbmcaddon.Addon(addon_id)
|
||||
getter = getattr(addon, "getSettingBool", None)
|
||||
if getter is not None:
|
||||
return bool(getter(setting_id))
|
||||
raw = addon.getSetting(setting_id)
|
||||
return str(raw).strip().lower() in {"1", "true", "yes", "on"}
|
||||
except Exception:
|
||||
return default
|
||||
|
||||
|
||||
def notify_url(addon_id: str, *, heading: str, url: str, enabled_setting_id: str) -> None:
|
||||
if xbmcgui is None:
|
||||
return
|
||||
if not get_setting_bool(addon_id, enabled_setting_id, default=False):
|
||||
return
|
||||
try:
|
||||
xbmcgui.Dialog().notification(heading, url, xbmcgui.NOTIFICATION_INFO, 3000)
|
||||
except Exception:
|
||||
return
|
||||
|
||||
|
||||
def _profile_logs_dir(addon_id: str) -> Optional[str]:
|
||||
if xbmcaddon is None or xbmcvfs is None:
|
||||
return None
|
||||
try:
|
||||
addon = xbmcaddon.Addon(addon_id)
|
||||
profile = xbmcvfs.translatePath(addon.getAddonInfo("profile"))
|
||||
log_dir = os.path.join(profile, "logs")
|
||||
if not xbmcvfs.exists(log_dir):
|
||||
xbmcvfs.mkdirs(log_dir)
|
||||
return log_dir
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
|
||||
def _append_text_file(path: str, content: str) -> None:
|
||||
try:
|
||||
with open(path, "a", encoding="utf-8") as handle:
|
||||
handle.write(content)
|
||||
return
|
||||
except Exception:
|
||||
pass
|
||||
if xbmcvfs is None:
|
||||
return
|
||||
try:
|
||||
handle = xbmcvfs.File(path, "a")
|
||||
handle.write(content)
|
||||
handle.close()
|
||||
except Exception:
|
||||
return
|
||||
|
||||
|
||||
def log_url(addon_id: str, *, enabled_setting_id: str, log_filename: str, url: str, kind: str = "VISIT") -> None:
|
||||
if not get_setting_bool(addon_id, enabled_setting_id, default=False):
|
||||
return
|
||||
timestamp = datetime.utcnow().isoformat(timespec="seconds") + "Z"
|
||||
line = f"{timestamp}\t{kind}\t{url}\n"
|
||||
log_dir = _profile_logs_dir(addon_id)
|
||||
if log_dir:
|
||||
_append_text_file(os.path.join(log_dir, log_filename), line)
|
||||
return
|
||||
_append_text_file(os.path.join(os.path.dirname(__file__), log_filename), line)
|
||||
|
||||
|
||||
def dump_response_html(
|
||||
addon_id: str,
|
||||
*,
|
||||
enabled_setting_id: str,
|
||||
url: str,
|
||||
body: str,
|
||||
filename_prefix: str,
|
||||
) -> None:
|
||||
if not get_setting_bool(addon_id, enabled_setting_id, default=False):
|
||||
return
|
||||
timestamp = datetime.utcnow().strftime("%Y%m%d_%H%M%S_%f")
|
||||
digest = hashlib.md5(url.encode("utf-8")).hexdigest() # nosec - filename only
|
||||
filename = f"{filename_prefix}_{timestamp}_{digest}.html"
|
||||
log_dir = _profile_logs_dir(addon_id)
|
||||
path = os.path.join(log_dir, filename) if log_dir else os.path.join(os.path.dirname(__file__), filename)
|
||||
content = f"<!-- {url} -->\n{body or ''}"
|
||||
_append_text_file(path, content)
|
||||
|
||||
Reference in New Issue
Block a user