huge refactor of the entire codebase, with the goal of improving maintainability, readability, and extensibility. This commit includes changes to almost every file in the project, including:

This commit is contained in:
2026-04-19 00:41:09 -07:00
parent d9e736172a
commit bafd37fdfb
50 changed files with 3258 additions and 4177 deletions
+58 -26
View File
@@ -74,34 +74,9 @@ def ping_url(url: str, timeout: float = 3.0) -> tuple[bool, str]:
def provider_display_name(key: str) -> str:
label = (key or "").strip()
lower = label.lower()
if lower == "openlibrary":
return "OpenLibrary"
if lower == "alldebrid":
return "AllDebrid"
if lower == "youtube":
return "YouTube"
return label[:1].upper() + label[1:] if label else "Provider"
def default_provider_ping_targets(provider_key: str) -> list[str]:
provider = (provider_key or "").strip().lower()
if provider == "openlibrary":
return ["https://openlibrary.org"]
if provider == "youtube":
return ["https://www.youtube.com"]
if provider == "bandcamp":
return ["https://bandcamp.com"]
if provider == "libgen":
try:
from Provider.libgen import MIRRORS
return [str(url).rstrip("/") + "/json.php" for url in (MIRRORS or []) if str(url).strip()]
except ImportError:
return []
return []
def ping_first(urls: list[str]) -> tuple[bool, str]:
for url in urls:
ok, detail = ping_url(url)
@@ -109,4 +84,61 @@ def ping_first(urls: list[str]) -> tuple[bool, str]:
return True, detail
if urls:
return ping_url(urls[0])
return False, "No ping target"
return False, "No ping target"
def collect_plugin_startup_checks(config: dict) -> list[dict[str, Any]]:
provider_cfg = config.get("provider") if isinstance(config, dict) else None
if not isinstance(provider_cfg, dict) or not provider_cfg:
return []
try:
from ProviderCore.registry import get_plugin_class
except Exception:
return []
checks: list[dict[str, Any]] = []
for plugin_name in provider_cfg.keys():
plugin_key = str(plugin_name or "").strip().lower()
if not plugin_key:
continue
plugin_class = None
try:
plugin_class = get_plugin_class(plugin_key)
except Exception:
plugin_class = None
if plugin_class is None:
checks.append(
{
"status": "UNKNOWN",
"name": provider_display_name(plugin_key),
"plugin": plugin_key,
"detail": "Not registered",
}
)
continue
try:
plugin = plugin_class(config)
summary = plugin.status_summary()
except Exception as exc:
summary = {
"status": "DISABLED",
"name": provider_display_name(plugin_key),
"plugin": plugin_key,
"detail": str(exc),
}
checks.append(
{
"status": str(summary.get("status") or "UNKNOWN"),
"name": str(summary.get("name") or provider_display_name(plugin_key)),
"plugin": str(summary.get("plugin") or plugin_key),
"detail": str(summary.get("detail") or ""),
"files": summary.get("files"),
}
)
return checks
+49 -74
View File
@@ -15,6 +15,7 @@ from SYS.result_table import Table
from SYS.item_accessors import get_sha256_hex
from SYS.utils import extract_hydrus_hash_from_url
from SYS import pipeline as ctx
from ProviderCore.registry import get_plugin, get_plugin_for_url
from cmdnat._parsing import (
extract_arg_value,
extract_piped_value as _extract_piped_value,
@@ -29,6 +30,29 @@ _MATRIX_MENU_STATE_KEY = "matrix_menu_state"
_MATRIX_SELECTED_SETTING_KEY_KEY = "matrix_selected_setting_key"
def _get_matrix_provider(config: Dict[str, Any]) -> Any:
provider = get_plugin("matrix", config)
if provider is None:
raise RuntimeError("Matrix plugin is not registered")
return provider
def _resolve_plugin_url(url: str, config: Dict[str, Any]) -> str:
target = str(url or "").strip()
if not target:
return target
provider = get_plugin_for_url(target, config)
if provider is None:
return target
try:
resolved = provider.resolve_url(target)
except Exception:
return target
return str(resolved or target)
def _extract_set_value_arg(args: Sequence[str]) -> Optional[str]:
"""Extract the value from -set-value flag."""
return extract_arg_value(args, flags={"-set-value"})
@@ -212,35 +236,11 @@ def _resolve_room_identifier(value: str, config: Dict[str, Any]) -> Optional[str
conf_ids = _parse_config_room_filter_ids(config)
if conf_ids:
# Attempt to fetch names for the configured IDs
try:
from Provider.matrix import Matrix
# Avoid __init__ network failures by requiring homeserver+token to exist
block = config.get("provider", {}).get("matrix", {})
if block and block.get("homeserver") and block.get("access_token"):
try:
m = Matrix(config)
rooms = m.list_rooms(room_ids=conf_ids)
for room in rooms or []:
name = str(room.get("name") or "").strip()
rid = str(room.get("room_id") or "").strip()
if name and name.lower() == cand.lower():
return rid
if name and cand.lower() in name.lower():
return rid
except Exception:
# Best-effort; fallback below
pass
except Exception:
pass
# Last resort: attempt to ask the server for matching rooms (if possible)
try:
from Provider.matrix import Matrix
block = config.get("provider", {}).get("matrix", {})
if block and block.get("homeserver") and block.get("access_token"):
try:
m = Matrix(config)
rooms = m.list_rooms()
m = _get_matrix_provider(config)
rooms = m.list_rooms(room_ids=conf_ids)
for room in rooms or []:
name = str(room.get("name") or "").strip()
rid = str(room.get("room_id") or "").strip()
@@ -250,8 +250,22 @@ def _resolve_room_identifier(value: str, config: Dict[str, Any]) -> Optional[str
return rid
except Exception:
pass
except Exception:
pass
# Last resort: attempt to ask the server for matching rooms (if possible)
block = config.get("provider", {}).get("matrix", {})
if block and block.get("homeserver") and block.get("access_token"):
try:
m = _get_matrix_provider(config)
rooms = m.list_rooms()
for room in rooms or []:
name = str(room.get("name") or "").strip()
rid = str(room.get("room_id") or "").strip()
if name and name.lower() == cand.lower():
return rid
if name and cand.lower() in name.lower():
return rid
except Exception:
pass
return None
except Exception:
@@ -270,10 +284,8 @@ def _send_pending_to_rooms(config: Dict[str, Any], room_ids: List[str], args: Se
log("No pending items to upload (use: @N | .matrix)", file=sys.stderr)
return 1
from Provider.matrix import Matrix
try:
provider = Matrix(config)
provider = _get_matrix_provider(config)
except Exception as exc:
log(f"Matrix not available: {exc}", file=sys.stderr)
return 1
@@ -585,35 +597,6 @@ def _maybe_download_hydrus_file(item: Any,
return None
def _maybe_unlock_alldebrid_url(url: str, config: Dict[str, Any]) -> str:
try:
parsed = urlparse(url)
host = (parsed.netloc or "").lower()
if host != "alldebrid.com":
return url
if not (parsed.path or "").startswith("/f/"):
return url
try:
from Provider.alldebrid import _get_debrid_api_key # type: ignore
api_key = _get_debrid_api_key(config or {})
except Exception:
api_key = None
if not api_key:
return url
from API.alldebrid import AllDebridClient
client = AllDebridClient(str(api_key))
unlocked = client.unlock_link(url)
if isinstance(unlocked, str) and unlocked.strip():
return unlocked.strip()
except Exception:
pass
return url
def _resolve_upload_path(item: Any, config: Dict[str, Any]) -> Optional[str]:
"""Resolve a usable local file path for uploading.
@@ -645,7 +628,7 @@ def _resolve_upload_path(item: Any, config: Dict[str, Any]) -> Optional[str]:
return None
# Best-effort: unlock AllDebrid file links (they require auth and aren't directly downloadable).
url = _maybe_unlock_alldebrid_url(url, config)
url = _resolve_plugin_url(url, config)
try:
from API.HTTP import _download_direct_file
@@ -851,10 +834,8 @@ def _handle_settings_edit(result: Any, args: Sequence[str], config: Dict[str, An
def _handle_settings_test(config: Dict[str, Any]) -> int:
"""Test Matrix credentials and prompt for default rooms upon success."""
from Provider.matrix import Matrix
try:
provider = Matrix(config)
provider = _get_matrix_provider(config)
except Exception as exc:
log(f"Matrix test failed: {exc}", file=sys.stderr)
return 1
@@ -863,13 +844,11 @@ def _handle_settings_test(config: Dict[str, Any]) -> int:
return _show_default_room_picker(config, provider=provider)
def _show_default_room_picker(config: Dict[str, Any], *, provider: Optional["Matrix"] = None) -> int:
def _show_default_room_picker(config: Dict[str, Any], *, provider: Optional[Any] = None) -> int:
"""Display joined rooms so the user can select defaults for sharing."""
from Provider.matrix import Matrix
try:
if provider is None:
provider = Matrix(config)
provider = _get_matrix_provider(config)
except Exception as exc:
log(f"Matrix not available: {exc}", file=sys.stderr)
return 1
@@ -977,10 +956,8 @@ def _handle_settings_rooms(result: Any, args: Sequence[str], config: Dict[str, A
def _show_rooms_table(config: Dict[str, Any]) -> int:
"""Display rooms (refactored original behavior)."""
from Provider.matrix import Matrix
try:
provider = Matrix(config)
provider = _get_matrix_provider(config)
except Exception as exc:
log(f"Matrix not available: {exc}", file=sys.stderr)
return 1
@@ -1121,10 +1098,8 @@ def _run(result: Any, args: Sequence[str], config: Dict[str, Any]) -> int:
log("No pending items to upload (use: @N | .matrix)", file=sys.stderr)
return 1
from Provider.matrix import Matrix
try:
provider = Matrix(config)
provider = _get_matrix_provider(config)
except Exception as exc:
log(f"Matrix not available: {exc}", file=sys.stderr)
return 1
+75 -67
View File
@@ -10,17 +10,15 @@ from datetime import datetime, timedelta
from urllib.parse import urlparse, parse_qs
from pathlib import Path
from SYS.cmdlet_spec import Cmdlet, CmdletArg, parse_cmdlet_args
from Provider.tidal_manifest import resolve_tidal_manifest_path
from ProviderCore.registry import get_plugin_for_url
from SYS.logger import debug, get_thread_stream, is_debug_enabled, set_debug, set_thread_stream
from SYS.result_table import Table
from MPV.mpv_ipc import MPV
from SYS import pipeline as ctx
from SYS.models import PipeObject
from SYS.config import get_hydrus_access_key, get_hydrus_url
from SYS.config import get_hydrus_access_key, get_hydrus_url, resolve_cookies_path
_ALLDEBRID_UNLOCK_CACHE: Dict[str,
str] = {}
_NOTES_PREFETCH_INFLIGHT: set[str] = set()
_NOTES_PREFETCH_LOCK = threading.Lock()
_PLAYLIST_STORE_CACHE: Optional[Dict[str, Any]] = None
@@ -478,73 +476,85 @@ def _try_enable_mpv_file_logging(mpv_log_path: str, *, attempts: int = 3) -> boo
return bool(ok)
def _get_alldebrid_api_key(config: Optional[Dict[str, Any]]) -> Optional[str]:
def _iter_plugin_targets(item: Any) -> List[str]:
values: List[str] = []
seen: set[str] = set()
def _add(candidate: Any) -> None:
text = str(candidate or "").strip()
if not text or text in seen:
return
seen.add(text)
values.append(text)
try:
if not isinstance(config, dict):
return None
provider_cfg = config.get("provider")
if not isinstance(provider_cfg, dict):
return None
ad_cfg = provider_cfg.get("alldebrid")
if not isinstance(ad_cfg, dict):
return None
key = ad_cfg.get("api_key")
if not isinstance(key, str):
return None
key = key.strip()
return key or None
if isinstance(item, dict):
_add(item.get("path"))
_add(item.get("url"))
_add(item.get("source_url"))
_add(item.get("target"))
metadata = item.get("full_metadata") or item.get("metadata")
else:
_add(getattr(item, "path", None))
_add(getattr(item, "url", None))
_add(getattr(item, "source_url", None))
_add(getattr(item, "target", None))
metadata = getattr(item, "full_metadata", None) or getattr(item, "metadata", None)
if isinstance(metadata, dict):
_add(metadata.get("url"))
_add(metadata.get("webpage_url"))
_add(metadata.get("source_url"))
extra = item.get("extra") if isinstance(item, dict) else getattr(item, "extra", None)
if isinstance(extra, dict):
_add(extra.get("url"))
_add(extra.get("source_url"))
except Exception:
return None
return values
return values
def _is_alldebrid_protected_url(url: str) -> bool:
def _resolve_plugin_url(url: str, config: Optional[Dict[str, Any]]) -> str:
target = str(url or "").strip()
if not target:
return target
try:
if not isinstance(url, str):
return False
u = url.strip()
if not u.startswith(("http://", "https://")):
return False
p = urlparse(u)
host = (p.netloc or "").lower()
path = p.path or ""
# AllDebrid file page links (require auth; not directly streamable by mpv)
return host == "alldebrid.com" and path.startswith("/f/")
plugin = get_plugin_for_url(target, config or {})
except Exception:
return False
def _maybe_unlock_alldebrid_url(url: str, config: Optional[Dict[str, Any]]) -> str:
"""Convert AllDebrid protected file URLs into direct streamable links.
When AllDebrid returns `https://alldebrid.com/f/...`, that URL typically requires
authentication. MPV cannot access it without credentials. We transparently call
the AllDebrid API `link/unlock` (using the configured API key) to obtain a direct
URL that MPV can stream.
"""
if not _is_alldebrid_protected_url(url):
return url
cached = _ALLDEBRID_UNLOCK_CACHE.get(url)
if isinstance(cached, str) and cached:
return cached
api_key = _get_alldebrid_api_key(config)
if not api_key:
return url
plugin = None
if plugin is None:
return target
try:
from API.alldebrid import AllDebridClient
resolved = plugin.resolve_url(target)
except Exception as exc:
debug(f"Plugin URL resolution failed for {target}: {exc}", file=sys.stderr)
return target
client = AllDebridClient(api_key)
unlocked = client.unlock_link(url)
if isinstance(unlocked, str) and unlocked.strip():
unlocked = unlocked.strip()
_ALLDEBRID_UNLOCK_CACHE[url] = unlocked
return unlocked
except Exception as e:
debug(f"AllDebrid unlock failed for MPV target: {e}", file=sys.stderr)
return str(resolved or target)
return url
def _resolve_plugin_playback_path(item: Any, config: Optional[Dict[str, Any]]) -> Optional[str]:
for candidate in _iter_plugin_targets(item):
try:
plugin = get_plugin_for_url(candidate, config or {})
except Exception:
plugin = None
if plugin is None:
continue
try:
resolved = plugin.resolve_playback_path(item)
except Exception as exc:
debug(f"Plugin playback-path resolution failed for {candidate}: {exc}", file=sys.stderr)
continue
text = str(resolved or "").strip()
if text:
return text
return None
def _ensure_lyric_overlay(mpv: MPV) -> None:
@@ -1078,9 +1088,7 @@ def _ensure_ytdl_cookies(config: Optional[Dict[str, Any]] = None) -> None:
cookies_path = None
try:
from tool.ytdlp import YtDlpTool
cookiefile = YtDlpTool(config or {}).resolve_cookiefile()
cookiefile = resolve_cookies_path(config or {})
if cookiefile is not None:
cookies_path = str(cookiefile)
except Exception:
@@ -1326,7 +1334,7 @@ def _get_playable_path(
"none"}:
path = None
manifest_path = resolve_tidal_manifest_path(item)
manifest_path = _resolve_plugin_playback_path(item, config)
if manifest_path:
path = manifest_path
else:
@@ -1542,7 +1550,7 @@ def _queue_items(
# If the target is an AllDebrid protected file URL, unlock it to a direct link for MPV.
try:
if isinstance(target, str):
target = _maybe_unlock_alldebrid_url(target, config)
target = _resolve_plugin_url(target, config)
except Exception:
pass
@@ -2591,7 +2599,7 @@ def _start_mpv(
try:
needs_mpd_whitelist = False
for it in items or []:
mpd = resolve_tidal_manifest_path(it)
mpd = _resolve_plugin_playback_path(it, config)
candidate = mpd
if not candidate:
if isinstance(it, dict):
+12 -79
View File
@@ -4,18 +4,14 @@ import shutil
from typing import Any, Dict, List
from SYS.cmdlet_spec import Cmdlet
from SYS.config import resolve_cookies_path
from SYS import pipeline as ctx
from SYS.result_table import Table
from SYS.logger import set_debug, debug
from cmdnat._status_shared import (
add_startup_check as _add_startup_check,
default_provider_ping_targets as _default_provider_ping_targets,
has_provider as _has_provider,
collect_plugin_startup_checks as _collect_plugin_startup_checks,
has_store_subtype as _has_store_subtype,
has_tool as _has_tool,
ping_first as _ping_first,
ping_url as _ping_url,
provider_display_name as _provider_display_name,
)
CMDLET = Cmdlet(
@@ -95,82 +91,19 @@ def _run(result: Any, args: List[str], config: Dict[str, Any]) -> int:
detail = f"{uval} - {err or 'Unavailable'}"
_add_startup_check(startup_table, status, nkey, store="hydrusnetwork", files=files, detail=detail)
# Providers
pcfg = config.get("provider", {})
if isinstance(pcfg, dict) and pcfg:
from ProviderCore.registry import list_providers, list_search_providers, list_file_providers
from Provider.metadata_provider import list_metadata_providers
p_avail = list_providers(config) or {}
s_avail = list_search_providers(config) or {}
f_avail = list_file_providers(config) or {}
m_avail = list_metadata_providers(config) or {}
debug(f"Provider registries: providers={list(p_avail.keys())}, search={list(s_avail.keys())}, file={list(f_avail.keys())}, metadata={list(m_avail.keys())}")
already = {"matrix"}
for pname in pcfg.keys():
prov = str(pname).lower()
if prov in already: continue
display = _provider_display_name(prov)
if prov == "alldebrid":
try:
from Provider.alldebrid import _get_debrid_api_key
from API.alldebrid import AllDebridClient
api_key = _get_debrid_api_key(config)
debug(f"AllDebrid configured: api_key_present={bool(api_key)}")
if not api_key:
_add_startup_check(startup_table, "DISABLED", display, provider=prov, detail="Not configured")
else:
client = AllDebridClient(api_key)
_add_startup_check(startup_table, "ENABLED", display, provider=prov, detail=getattr(client, "base_url", "Connected"))
debug(f"AllDebrid client connected: base_url={getattr(client, 'base_url', 'unknown')}")
except Exception as exc:
_add_startup_check(startup_table, "DISABLED", display, provider=prov, detail=str(exc))
debug(f"AllDebrid check failed: {exc}")
already.add(prov)
continue
is_known = prov in p_avail or prov in s_avail or prov in f_avail or prov in m_avail
if not is_known:
_add_startup_check(startup_table, "UNKNOWN", display, provider=prov, detail="Not registered")
debug(f"Provider {prov} not registered")
else:
ok_val = p_avail.get(prov) or s_avail.get(prov) or f_avail.get(prov) or m_avail.get(prov)
detail = "Configured" if ok_val else "Not configured"
ping_targets = _default_provider_ping_targets(prov)
if ping_targets:
debug(f"Provider {prov} ping targets: {ping_targets}")
pok, pdet = _ping_first(ping_targets)
debug(f"Provider {prov} ping result: ok={pok}, detail={pdet}")
detail = pdet if ok_val else f"{detail} | {pdet}"
_add_startup_check(startup_table, "ENABLED" if ok_val else "DISABLED", display, provider=prov, detail=detail)
already.add(prov)
# Matrix
if _has_provider(config, "matrix"):
try:
from Provider.matrix import Matrix
m_prov = Matrix(config)
mcfg = config.get("provider", {}).get("matrix", {})
hs = str(mcfg.get("homeserver") or "").strip()
rid = str(mcfg.get("room_id") or "").strip()
detail = f"{hs} room:{rid}"
valid = False
try:
valid = bool(m_prov.validate())
except Exception as exc:
debug(f"Matrix validate failed: {exc}")
_add_startup_check(startup_table, "ENABLED" if valid else "DISABLED", "Matrix", provider="matrix", detail=detail)
debug(f"Matrix check: homeserver={hs}, room_id={rid}, validate={valid}")
except Exception as exc:
_add_startup_check(startup_table, "DISABLED", "Matrix", provider="matrix", detail=str(exc))
debug(f"Matrix instantiation failed: {exc}")
for check in _collect_plugin_startup_checks(config):
_add_startup_check(
startup_table,
str(check.get("status") or "UNKNOWN"),
str(check.get("name") or "Plugin"),
provider=str(check.get("plugin") or ""),
files=check.get("files"),
detail=str(check.get("detail") or ""),
)
# Cookies
try:
from tool.ytdlp import YtDlpTool
cf = YtDlpTool(config).resolve_cookiefile()
cf = resolve_cookies_path(config)
_add_startup_check(startup_table, "FOUND" if cf else "MISSING", "Cookies", detail=str(cf) if cf else "Not found")
debug(f"Cookies: resolved cookiefile={cf}")
except Exception as exc:
+9 -3
View File
@@ -8,10 +8,18 @@ from SYS.cmdlet_spec import Cmdlet, CmdletArg
from SYS.logger import log
from SYS.result_table import Table
from SYS import pipeline as ctx
from ProviderCore.registry import get_plugin
from cmdnat._parsing import has_flag as _has_flag, normalize_to_list as _normalize_to_list
_TELEGRAM_PENDING_ITEMS_KEY = "telegram_pending_items"
def _get_telegram_provider(config: Dict[str, Any]) -> Any:
provider = get_plugin("telegram", config)
if provider is None:
raise RuntimeError("Telegram plugin is not registered")
return provider
def _extract_chat_id(chat_obj: Any) -> Optional[int]:
try:
if isinstance(chat_obj, dict):
@@ -119,10 +127,8 @@ def _extract_file_path(item: Any) -> Optional[str]:
def _run(_result: Any, args: Sequence[str], config: Dict[str, Any]) -> int:
from Provider.telegram import Telegram
try:
provider = Telegram(config)
provider = _get_telegram_provider(config)
except Exception as exc:
log(f"Telegram not available: {exc}", file=sys.stderr)
return 1