This commit is contained in:
2026-01-03 03:37:48 -08:00
parent 6e9a0c28ff
commit 73f3005393
23 changed files with 1791 additions and 442 deletions

View File

@@ -55,10 +55,40 @@ class Provider(ABC):
URL: Sequence[str] = ()
# Optional provider-driven defaults for what to do when a user selects @N from a
# provider table. The CLI uses this to auto-insert stages (e.g. download-file)
# without hardcoding table names.
#
# Example:
# TABLE_AUTO_STAGES = {"youtube": ["download-file"]}
# TABLE_AUTO_PREFIXES = {"hifi": ["download-file"]} # matches hifi.*
TABLE_AUTO_STAGES: Dict[str, Sequence[str]] = {}
TABLE_AUTO_PREFIXES: Dict[str, Sequence[str]] = {}
AUTO_STAGE_USE_SELECTION_ARGS: bool = False
# Optional provider-declared configuration keys.
# Used for dynamically generating config panels (e.g., missing credentials).
REQUIRED_CONFIG_KEYS: Sequence[str] = ()
def __init__(self, config: Optional[Dict[str, Any]] = None):
self.config = config or {}
self.name = self.__class__.__name__.lower()
@classmethod
def required_config_keys(cls) -> List[str]:
keys = getattr(cls, "REQUIRED_CONFIG_KEYS", None)
if not keys:
return []
out: List[str] = []
try:
for k in list(keys):
s = str(k or "").strip()
if s:
out.append(s)
except Exception:
return []
return out
# Standard lifecycle/auth hook.
def login(self, **_kwargs: Any) -> bool:
return True
@@ -109,6 +139,56 @@ class Provider(ABC):
_ = stage_is_last
return False
@classmethod
def selection_auto_stage(
cls,
table_type: str,
stage_args: Optional[Sequence[str]] = None,
) -> Optional[List[str]]:
"""Return a stage to auto-run after selecting from `table_type`.
This is used by the CLI to auto-insert default stages for provider tables
(e.g. select a YouTube row -> auto-run download-file).
Providers can implement this via class attributes (TABLE_AUTO_STAGES /
TABLE_AUTO_PREFIXES) or by overriding this method.
"""
t = str(table_type or "").strip().lower()
if not t:
return None
stage: Optional[Sequence[str]] = None
try:
stage = cls.TABLE_AUTO_STAGES.get(t)
except Exception:
stage = None
if stage is None:
try:
for prefix, cmd in (cls.TABLE_AUTO_PREFIXES or {}).items():
p = str(prefix or "").strip().lower()
if not p:
continue
if t == p or t.startswith(p + ".") or t.startswith(p):
stage = cmd
break
except Exception:
stage = None
if not stage:
return None
out = [str(x) for x in stage if str(x or "").strip()]
if not out:
return None
if cls.AUTO_STAGE_USE_SELECTION_ARGS and stage_args:
try:
out.extend([str(x) for x in stage_args if str(x or "").strip()])
except Exception:
pass
return out
@classmethod
def url_patterns(cls) -> Tuple[str, ...]:
"""Return normalized URL patterns that this provider handles."""

View File

@@ -49,6 +49,38 @@ _PROVIDERS: Dict[str,
}
def get_provider_class(name: str) -> Optional[Type[Provider]]:
"""Return the provider class for a registered provider name, if any."""
key = str(name or "").strip().lower()
return _PROVIDERS.get(key)
def selection_auto_stage_for_table(
table_type: str,
stage_args: Optional[Sequence[str]] = None,
) -> Optional[list[str]]:
"""Return the provider-suggested stage to auto-run for a selected table.
This is used by the CLI to avoid hardcoding table names and behaviors.
"""
t = str(table_type or "").strip().lower()
if not t:
return None
# Provider tables are usually either:
# - "youtube" (no dot)
# - "hifi.tracks" (prefix = provider name)
provider_key = t.split(".", 1)[0] if "." in t else t
provider_class = get_provider_class(provider_key) or get_provider_class(t)
if provider_class is None:
return None
try:
return provider_class.selection_auto_stage(t, stage_args)
except Exception:
return None
def is_known_provider_name(name: str) -> bool:
"""Return True if `name` matches a registered provider key.
@@ -251,4 +283,6 @@ __all__ = [
"match_provider_name_for_url",
"get_provider_for_url",
"download_soulseek_file",
"get_provider_class",
"selection_auto_stage_for_table",
]