update
This commit is contained in:
@@ -505,14 +505,8 @@ class CmdletIntrospection:
|
||||
if normalized_arg == "plugin":
|
||||
canonical_cmd = (cmd_name or "").replace("_", "-").lower()
|
||||
try:
|
||||
from PluginCore.registry import (
|
||||
list_configured_plugin_names_with_capability,
|
||||
list_plugin_names_with_capability,
|
||||
list_plugin_names_for_cmdlet,
|
||||
)
|
||||
from PluginCore.registry import list_plugin_names_for_cmdlet
|
||||
except Exception:
|
||||
list_configured_plugin_names_with_capability = None # type: ignore
|
||||
list_plugin_names_with_capability = None # type: ignore
|
||||
list_plugin_names_for_cmdlet = None # type: ignore
|
||||
|
||||
plugin_choices: List[str] = []
|
||||
@@ -569,10 +563,6 @@ class CmdletIntrospection:
|
||||
) or []
|
||||
# Prefer configured plugins first, but still show valid plugin options.
|
||||
plugin_choices = _merge_choice_groups(configured, available)
|
||||
elif canonical_cmd in {"add-file"} and list_configured_plugin_names_with_capability is not None:
|
||||
plugin_choices = list_configured_plugin_names_with_capability("upload", config) or []
|
||||
elif list_configured_plugin_names_with_capability is not None:
|
||||
plugin_choices = list_configured_plugin_names_with_capability("search", config) or []
|
||||
|
||||
if plugin_choices:
|
||||
return plugin_choices
|
||||
|
||||
+39
-174
@@ -626,100 +626,6 @@ def is_known_plugin_name(name: str) -> bool:
|
||||
return REGISTRY.has_name(name)
|
||||
|
||||
|
||||
def _supports_search(provider: Provider) -> bool:
|
||||
return _class_supports_method(provider.__class__, "search", Provider.search)
|
||||
|
||||
|
||||
def _supports_upload(provider: Provider) -> bool:
|
||||
try:
|
||||
exposed = bool(getattr(provider.__class__, "EXPOSE_AS_FILE_PROVIDER", True))
|
||||
except Exception:
|
||||
exposed = True
|
||||
return exposed and _class_supports_method(provider.__class__, "upload", Provider.upload)
|
||||
|
||||
|
||||
def _supports_download(provider: Provider) -> bool:
|
||||
return (
|
||||
_class_supports_method(provider.__class__, "handle_url", Provider.handle_url)
|
||||
or _class_supports_method(provider.__class__, "download_url", Provider.download_url)
|
||||
or _class_supports_method(provider.__class__, "download", Provider.download)
|
||||
)
|
||||
|
||||
|
||||
def _supports_pipe_result_download(provider: Provider) -> bool:
|
||||
return _class_supports_method(
|
||||
provider.__class__,
|
||||
"resolve_pipe_result_download",
|
||||
Provider.resolve_pipe_result_download,
|
||||
)
|
||||
|
||||
|
||||
def _supports_delete_file(provider: Provider) -> bool:
|
||||
method = getattr(provider.__class__, "delete_file", None)
|
||||
base_method = getattr(Provider, "delete_file", None)
|
||||
return callable(method) and method is not base_method
|
||||
|
||||
|
||||
def _supports_capability(provider: Provider, capability: str) -> bool:
|
||||
capability_key = str(capability or "").strip().lower()
|
||||
if capability_key == "search":
|
||||
return _supports_search(provider)
|
||||
if capability_key in {"upload", "file", "file-provider"}:
|
||||
return _supports_upload(provider)
|
||||
if capability_key in {"download", "download-file", "download_file"}:
|
||||
return _supports_download(provider)
|
||||
if capability_key in {"pipe-download", "pipe_result_download", "pipe-result-download"}:
|
||||
return _supports_pipe_result_download(provider)
|
||||
if capability_key in {"delete-file", "delete_file", "delete"}:
|
||||
return _supports_delete_file(provider)
|
||||
if capability_key in {"pipe-item-context", "pipe-context"}:
|
||||
return _class_supports_method(
|
||||
provider.__class__,
|
||||
"resolve_pipe_item_context",
|
||||
Provider.resolve_pipe_item_context,
|
||||
)
|
||||
if capability_key in {"playlist-store", "playback-store"}:
|
||||
return _class_supports_method(
|
||||
provider.__class__,
|
||||
"infer_playlist_store",
|
||||
Provider.infer_playlist_store,
|
||||
)
|
||||
return False
|
||||
|
||||
|
||||
def _info_supports_capability(info: PluginInfo, capability: str) -> bool:
|
||||
capability_key = str(capability or "").strip().lower()
|
||||
if capability_key == "search":
|
||||
return bool(info.supports_search)
|
||||
if capability_key in {"upload", "file", "file-provider"}:
|
||||
return bool(info.supports_upload)
|
||||
if capability_key in {"download", "download-file", "download_file"}:
|
||||
return bool(info.supports_download)
|
||||
if capability_key in {"pipe-download", "pipe_result_download", "pipe-result-download"}:
|
||||
return _class_supports_method(
|
||||
info.plugin_class,
|
||||
"resolve_pipe_result_download",
|
||||
Provider.resolve_pipe_result_download,
|
||||
)
|
||||
if capability_key in {"delete-file", "delete_file", "delete"}:
|
||||
method = getattr(info.plugin_class, "delete_file", None)
|
||||
base_method = getattr(Provider, "delete_file", None)
|
||||
return callable(method) and method is not base_method
|
||||
if capability_key in {"pipe-item-context", "pipe-context"}:
|
||||
return _class_supports_method(
|
||||
info.plugin_class,
|
||||
"resolve_pipe_item_context",
|
||||
Provider.resolve_pipe_item_context,
|
||||
)
|
||||
if capability_key in {"playlist-store", "playback-store"}:
|
||||
return _class_supports_method(
|
||||
info.plugin_class,
|
||||
"infer_playlist_store",
|
||||
Provider.infer_playlist_store,
|
||||
)
|
||||
return False
|
||||
|
||||
|
||||
def get_plugin(name: str, config: Optional[Dict[str, Any]] = None) -> Optional[Provider]:
|
||||
info = REGISTRY.get(name)
|
||||
if info is None:
|
||||
@@ -760,77 +666,60 @@ def list_plugins(config: Optional[Dict[str, Any]] = None) -> Dict[str, bool]:
|
||||
return availability
|
||||
|
||||
|
||||
def get_plugin_with_capability(
|
||||
def get_plugin_for_cmdlet(
|
||||
name: str,
|
||||
capability: str,
|
||||
cmdlet_name: str,
|
||||
config: Optional[Dict[str, Any]] = None,
|
||||
) -> Optional[Provider]:
|
||||
plugin = get_plugin(name, config)
|
||||
if plugin is None:
|
||||
info = REGISTRY.get(name)
|
||||
if info is None:
|
||||
debug(f"[plugin] Unknown plugin: {name}")
|
||||
return None
|
||||
if not _supports_capability(plugin, capability):
|
||||
debug(f"[plugin] Plugin '{name}' does not support capability '{capability}'")
|
||||
|
||||
cmd = str(cmdlet_name or "").strip().lower()
|
||||
if not cmd or cmd not in info.supported_cmdlets:
|
||||
debug(f"[plugin] Plugin '{name}' does not declare cmdlet '{cmdlet_name}'")
|
||||
return None
|
||||
return plugin
|
||||
|
||||
return get_plugin(name, config)
|
||||
|
||||
|
||||
def list_plugins_with_capability(
|
||||
capability: str,
|
||||
def list_plugins_for_cmdlet(
|
||||
cmdlet_name: str,
|
||||
config: Optional[Dict[str, Any]] = None,
|
||||
) -> Dict[str, bool]:
|
||||
availability: Dict[str, bool] = {}
|
||||
for info in REGISTRY.iter_plugins():
|
||||
for info in REGISTRY.get_plugins_for_cmdlet(cmdlet_name):
|
||||
try:
|
||||
plugin = info.plugin_class(config)
|
||||
availability[info.canonical_name] = bool(
|
||||
plugin.validate() and _supports_capability(plugin, capability)
|
||||
)
|
||||
availability[info.canonical_name] = plugin.validate()
|
||||
except Exception:
|
||||
availability[info.canonical_name] = False
|
||||
return availability
|
||||
|
||||
|
||||
def list_plugin_names_with_capability(capability: str) -> List[str]:
|
||||
return sorted(
|
||||
info.canonical_name
|
||||
for info in REGISTRY.iter_plugins()
|
||||
if _info_supports_capability(info, capability)
|
||||
def _info_has_configured_plugin_entry(
|
||||
info: PluginInfo,
|
||||
cfg: Optional[Dict[str, Any]] = None,
|
||||
plugin_section: Optional[Dict[str, Any]] = None,
|
||||
) -> bool:
|
||||
config_dict = cfg or {}
|
||||
section: Dict[str, Any] = (
|
||||
plugin_section if isinstance(plugin_section, dict)
|
||||
else (config_dict.get("plugin") or {}) # type: ignore[assignment]
|
||||
)
|
||||
|
||||
|
||||
def list_configured_plugin_names_with_capability(
|
||||
capability: str,
|
||||
config: Optional[Dict[str, Any]] = None,
|
||||
) -> List[str]:
|
||||
"""Return plugin names that support `capability` AND have configuration present.
|
||||
|
||||
For MULTI_INSTANCE plugins (e.g. hydrusnetwork, ftp) the plugin must have at
|
||||
least one configured instance. For single-instance plugins the plugin's section
|
||||
must exist under config["plugin"].
|
||||
"""
|
||||
cfg = config or {}
|
||||
plugin_section: Dict[str, Any] = cfg.get("plugin") or {} # type: ignore[assignment]
|
||||
|
||||
result: List[str] = []
|
||||
for info in REGISTRY.iter_plugins():
|
||||
if not _info_supports_capability(info, capability):
|
||||
continue
|
||||
name = info.canonical_name
|
||||
if info.is_multi_instance:
|
||||
try:
|
||||
plugin_obj = info.plugin_class(cfg)
|
||||
plugin_obj = info.plugin_class(config_dict)
|
||||
instances = plugin_obj.configured_instances()
|
||||
# Treat explicit multi-instance names as configured, but also allow
|
||||
# a default/single config block for multi-instance plugins.
|
||||
if instances or bool(plugin_obj.plugin_config_root()):
|
||||
result.append(name)
|
||||
return bool(instances or plugin_obj.plugin_config_root())
|
||||
except Exception:
|
||||
pass
|
||||
else:
|
||||
pname = name.lower()
|
||||
if isinstance(plugin_section.get(pname), dict):
|
||||
result.append(name)
|
||||
return sorted(result)
|
||||
return False
|
||||
|
||||
return isinstance(section.get(info.canonical_name.lower()), dict)
|
||||
|
||||
|
||||
def list_plugin_names_for_cmdlet(
|
||||
@@ -839,46 +728,24 @@ def list_plugin_names_for_cmdlet(
|
||||
*,
|
||||
configured_only: bool = False,
|
||||
) -> List[str]:
|
||||
"""Return plugin names suitable for a cmdlet.
|
||||
|
||||
Priority:
|
||||
1) Plugins that explicitly declare the cmdlet in SUPPORTED_CMDLETS.
|
||||
2) Capability fallback for legacy plugins that do not yet declare cmdlets.
|
||||
"""
|
||||
"""Return plugin names that explicitly declare support for a cmdlet."""
|
||||
cmd = str(cmdlet_name or "").strip().lower()
|
||||
if not cmd:
|
||||
return []
|
||||
|
||||
supported = {
|
||||
info.canonical_name for info in REGISTRY.get_plugins_for_cmdlet(cmd)
|
||||
}
|
||||
|
||||
fallback_capability = {
|
||||
"search-file": "search",
|
||||
"add-file": "upload",
|
||||
"download-file": "download",
|
||||
"delete-file": "delete-file",
|
||||
}.get(cmd)
|
||||
|
||||
if fallback_capability:
|
||||
supported.update(list_plugin_names_with_capability(fallback_capability))
|
||||
supported_infos = list(REGISTRY.get_plugins_for_cmdlet(cmd))
|
||||
supported = {info.canonical_name for info in supported_infos}
|
||||
|
||||
if not configured_only:
|
||||
return sorted(supported)
|
||||
|
||||
cfg = config or {}
|
||||
configured: set[str] = set()
|
||||
if fallback_capability:
|
||||
configured.update(list_configured_plugin_names_with_capability(fallback_capability, cfg))
|
||||
|
||||
# Keep cmdlet-declared plugins if they appear configured in the plugin section.
|
||||
plugin_section: Dict[str, Any] = cfg.get("plugin") or {} # type: ignore[assignment]
|
||||
for name in supported:
|
||||
key = str(name or "").strip().lower()
|
||||
if isinstance(plugin_section.get(key), dict):
|
||||
configured.add(name)
|
||||
|
||||
return sorted(configured)
|
||||
return sorted(
|
||||
info.canonical_name
|
||||
for info in supported_infos
|
||||
if _info_has_configured_plugin_entry(info, cfg, plugin_section)
|
||||
)
|
||||
|
||||
|
||||
def match_plugin_name_for_url(url: str) -> Optional[str]:
|
||||
@@ -1051,11 +918,9 @@ __all__ = [
|
||||
"register_plugin",
|
||||
"get_plugin",
|
||||
"list_plugins",
|
||||
"get_plugin_with_capability",
|
||||
"list_plugins_with_capability",
|
||||
"list_plugin_names_with_capability",
|
||||
"get_plugin_for_cmdlet",
|
||||
"list_plugins_for_cmdlet",
|
||||
"list_plugin_names_for_cmdlet",
|
||||
"list_configured_plugin_names_with_capability",
|
||||
"match_plugin_name_for_url",
|
||||
"get_plugin_for_url",
|
||||
"list_selection_url_prefixes",
|
||||
|
||||
+32
-27
@@ -75,19 +75,19 @@ class _CommandDependencies:
|
||||
self._plugins[norm_name] = plugin
|
||||
return plugin
|
||||
|
||||
def get_plugin_with_capability(self, name: str, capability: str) -> Optional[Any]:
|
||||
"""Cached plugin lookup with capability check."""
|
||||
from PluginCore.registry import get_plugin_with_capability
|
||||
def get_plugin_for_cmdlet(self, name: str, cmdlet_name: str) -> Optional[Any]:
|
||||
"""Cached plugin lookup with explicit cmdlet support check."""
|
||||
from PluginCore.registry import get_plugin_for_cmdlet
|
||||
|
||||
norm_name = str(name or "").strip().lower()
|
||||
if not norm_name:
|
||||
return None
|
||||
|
||||
cache_key = f"{norm_name}#{capability}"
|
||||
cache_key = f"{norm_name}#{cmdlet_name}"
|
||||
if cache_key in self._plugins:
|
||||
return self._plugins[cache_key]
|
||||
|
||||
plugin = get_plugin_with_capability(norm_name, capability, self.config)
|
||||
plugin = get_plugin_for_cmdlet(norm_name, cmdlet_name, self.config)
|
||||
self._plugins[cache_key] = plugin
|
||||
return plugin
|
||||
|
||||
@@ -1512,6 +1512,8 @@ class Add_File(Cmdlet):
|
||||
args: Sequence[str],
|
||||
config: Optional[Dict[str, Any]] = None,
|
||||
) -> Optional[str]:
|
||||
from PluginCore.registry import PLUGIN_REGISTRY
|
||||
|
||||
cfg = config if isinstance(config, dict) else {}
|
||||
|
||||
if Add_File._uses_legacy_path_flag(args):
|
||||
@@ -1556,17 +1558,20 @@ class Add_File(Cmdlet):
|
||||
|
||||
normalized_plugin_name = Add_File._normalize_provider_key(plugin_name)
|
||||
if normalized_plugin_name:
|
||||
upload_plugin = deps.get_plugin_with_capability(normalized_plugin_name, "upload")
|
||||
upload_plugin = deps.get_plugin_for_cmdlet(normalized_plugin_name, "add-file")
|
||||
if upload_plugin is None:
|
||||
plugin_exists = deps.get_plugin(normalized_plugin_name) is not None
|
||||
if plugin_exists:
|
||||
if normalized_plugin_name == "loc":
|
||||
plugin_info = PLUGIN_REGISTRY.get(normalized_plugin_name)
|
||||
if plugin_info is not None:
|
||||
canonical_plugin_name = str(plugin_info.canonical_name or normalized_plugin_name).strip().lower()
|
||||
if canonical_plugin_name == "loc":
|
||||
return (
|
||||
"Pipeline error: plugin 'loc' does not support add-file/upload. "
|
||||
"Pipeline error: plugin 'loc' does not support add-file. "
|
||||
"Use -plugin local -instance <name|path> for local export."
|
||||
)
|
||||
return f"Pipeline error: plugin '{normalized_plugin_name}' does not support add-file/upload."
|
||||
return f"Pipeline error: unknown upload plugin '{plugin_name}'."
|
||||
if "add-file" not in plugin_info.supported_cmdlets:
|
||||
return f"Pipeline error: plugin '{canonical_plugin_name}' does not support add-file."
|
||||
return f"Pipeline error: plugin '{canonical_plugin_name}' is not configured or not available for add-file."
|
||||
return f"Pipeline error: unknown add-file plugin '{plugin_name}'."
|
||||
|
||||
if normalized_plugin_name == "local":
|
||||
requested_local = str(plugin_instance or location or "").strip() or "<default>"
|
||||
@@ -1600,7 +1605,7 @@ class Add_File(Cmdlet):
|
||||
if deps is None:
|
||||
deps = _CommandDependencies(config)
|
||||
|
||||
file_provider = deps.get_plugin_with_capability(plugin_key, "upload")
|
||||
file_provider = deps.get_plugin_for_cmdlet(plugin_key, "add-file")
|
||||
if file_provider is None:
|
||||
return None
|
||||
|
||||
@@ -1656,7 +1661,7 @@ class Add_File(Cmdlet):
|
||||
if deps is None:
|
||||
deps = _CommandDependencies(config)
|
||||
|
||||
file_provider = deps.get_plugin_with_capability("local", "upload")
|
||||
file_provider = deps.get_plugin_for_cmdlet("local", "add-file")
|
||||
if file_provider is None:
|
||||
return None, None
|
||||
|
||||
@@ -2477,27 +2482,27 @@ class Add_File(Cmdlet):
|
||||
Any],
|
||||
delete_after: bool,
|
||||
) -> int:
|
||||
"""Handle uploading via an upload plugin (e.g. 0x0)."""
|
||||
"""Handle uploading via an add-file plugin (e.g. 0x0)."""
|
||||
from PluginCore.registry import (
|
||||
get_plugin_with_capability,
|
||||
list_plugin_names_with_capability,
|
||||
list_plugins_with_capability,
|
||||
PLUGIN_REGISTRY,
|
||||
get_plugin_for_cmdlet,
|
||||
list_plugins_for_cmdlet,
|
||||
)
|
||||
|
||||
try:
|
||||
file_provider = get_plugin_with_capability(plugin_name, "upload", config)
|
||||
file_provider = get_plugin_for_cmdlet(plugin_name, "add-file", config)
|
||||
if not file_provider:
|
||||
available_map = list_plugins_with_capability("upload", config)
|
||||
known_upload_plugins = set(list_plugin_names_with_capability("upload"))
|
||||
available_uploads = [name for name, enabled in available_map.items() if enabled and name in known_upload_plugins]
|
||||
available_map = list_plugins_for_cmdlet("add-file", config)
|
||||
available_add_plugins = [name for name, enabled in available_map.items() if enabled]
|
||||
requested_plugin_info = PLUGIN_REGISTRY.get(plugin_name)
|
||||
|
||||
if str(plugin_name or "").strip().lower() in known_upload_plugins:
|
||||
show_plugin_config_panel([plugin_name])
|
||||
if requested_plugin_info is not None and "add-file" in requested_plugin_info.supported_cmdlets:
|
||||
show_plugin_config_panel([requested_plugin_info.canonical_name])
|
||||
else:
|
||||
log(f"Upload plugin '{plugin_name}' is not available or does not support upload", file=sys.stderr)
|
||||
log(f"Add-file plugin '{plugin_name}' is not available or does not support add-file", file=sys.stderr)
|
||||
|
||||
if available_uploads:
|
||||
show_available_plugins_panel(sorted(available_uploads))
|
||||
if available_add_plugins:
|
||||
show_available_plugins_panel(sorted(available_add_plugins))
|
||||
return 1
|
||||
|
||||
upload_kwargs: Dict[str, Any] = {
|
||||
|
||||
@@ -15,7 +15,7 @@ from urllib.parse import urlparse, parse_qs, unquote, urljoin
|
||||
|
||||
from SYS.logger import log, debug, debug_panel
|
||||
from SYS.payload_builders import build_file_result_payload, normalize_file_extension
|
||||
from PluginCore.registry import get_plugin_with_capability, list_plugins_with_capability
|
||||
from PluginCore.registry import get_plugin_for_cmdlet, list_plugins_for_cmdlet
|
||||
from SYS.rich_display import (
|
||||
show_plugin_config_panel,
|
||||
show_store_config_panel,
|
||||
@@ -1478,7 +1478,7 @@ class search_file(Cmdlet):
|
||||
log("Error: search-file -plugin requires both plugin and query", file=sys.stderr)
|
||||
log(f"Usage: {self.usage}", file=sys.stderr)
|
||||
|
||||
providers_map = list_plugins_with_capability("search", config)
|
||||
providers_map = list_plugins_for_cmdlet("search-file", config)
|
||||
available = [n for n, a in providers_map.items() if a]
|
||||
unconfigured = [n for n, a in providers_map.items() if not a]
|
||||
|
||||
@@ -1499,8 +1499,10 @@ class search_file(Cmdlet):
|
||||
if hasattr(ctx_mod, "get_pipeline_state"):
|
||||
progress = ctx_mod.get_pipeline_state().live_progress
|
||||
|
||||
provider = get_plugin_with_capability(plugin_name, "search", config)
|
||||
if not provider:
|
||||
providers_map = list_plugins_for_cmdlet("search-file", config)
|
||||
provider = get_plugin_for_cmdlet(plugin_name, "search-file", config)
|
||||
resolved_plugin_name = str(getattr(provider, "name", "") or plugin_name).strip().lower()
|
||||
if not provider or not providers_map.get(resolved_plugin_name, False):
|
||||
if progress:
|
||||
try:
|
||||
progress.stop()
|
||||
@@ -1509,7 +1511,6 @@ class search_file(Cmdlet):
|
||||
|
||||
show_plugin_config_panel([plugin_name])
|
||||
|
||||
providers_map = list_plugins_with_capability("search", config)
|
||||
available = [n for n, a in providers_map.items() if a]
|
||||
if available:
|
||||
show_available_plugins_panel(available)
|
||||
|
||||
@@ -658,6 +658,7 @@ class AllDebrid(TablePluginMixin, Provider):
|
||||
AUTO_STAGE_USE_SELECTION_ARGS = True
|
||||
URL = ("magnet:", "alldebrid:magnet:", "alldebrid:", "alldebrid🧲", "alldebrid.com")
|
||||
URL_DOMAINS = ()
|
||||
SUPPORTED_CMDLETS = frozenset({"download-file", "search-file"})
|
||||
|
||||
def extract_query_arguments(self, query: str) -> Tuple[str, Dict[str, Any]]:
|
||||
normalized = str(query or "").strip()
|
||||
|
||||
@@ -13,6 +13,7 @@ from plugins.playwright import PlaywrightTool
|
||||
class Bandcamp(Provider):
|
||||
"""Search provider for Bandcamp."""
|
||||
|
||||
SUPPORTED_CMDLETS = frozenset({"search-file"})
|
||||
TABLE_AUTO_STAGES = {
|
||||
"bandcamp": ["download-file"],
|
||||
}
|
||||
|
||||
@@ -51,6 +51,7 @@ def _extract_key(payload: Any) -> Optional[str]:
|
||||
class FileIO(Provider):
|
||||
"""File provider for file.io."""
|
||||
PLUGIN_NAME = "file.io"
|
||||
SUPPORTED_CMDLETS = frozenset({"add-file"})
|
||||
|
||||
@classmethod
|
||||
def config_schema(cls) -> List[Dict[str, Any]]:
|
||||
|
||||
@@ -25,6 +25,7 @@ class HelloProvider(Provider):
|
||||
"""
|
||||
|
||||
PLUGIN_NAME = "hello"
|
||||
SUPPORTED_CMDLETS = frozenset({"download-file", "search-file"})
|
||||
URL = ("hello:",)
|
||||
URL_DOMAINS = ()
|
||||
|
||||
|
||||
@@ -65,6 +65,7 @@ def _format_total_seconds(seconds: Any) -> str:
|
||||
|
||||
class HIFI(Provider):
|
||||
PLUGIN_NAME = "hifi"
|
||||
SUPPORTED_CMDLETS = frozenset({"download-file", "search-file"})
|
||||
|
||||
TABLE_AUTO_STAGES = {
|
||||
"hifi.track": ["download-file"],
|
||||
|
||||
@@ -593,6 +593,7 @@ class InternetArchive(Provider):
|
||||
- add-file -plugin internetarchive (uploads)
|
||||
"""
|
||||
URL = ("archive.org",)
|
||||
SUPPORTED_CMDLETS = frozenset({"add-file", "download-file", "search-file"})
|
||||
|
||||
def get_table_type(self, query: str, filters: Optional[Dict[str, Any]] = None) -> str:
|
||||
return "internetarchive.folder"
|
||||
|
||||
@@ -656,6 +656,7 @@ def _libgen_metadata_to_tags(meta: Dict[str, Any]) -> List[str]:
|
||||
|
||||
class Libgen(Provider):
|
||||
|
||||
SUPPORTED_CMDLETS = frozenset({"download-file", "search-file"})
|
||||
TABLE_AUTO_STAGES = {
|
||||
"libgen": ["download-file"],
|
||||
}
|
||||
|
||||
@@ -14,6 +14,8 @@ class LOC(Provider):
|
||||
Currently implements Chronicling America collection search via the LoC JSON API.
|
||||
"""
|
||||
|
||||
SUPPORTED_CMDLETS = frozenset({"search-file"})
|
||||
|
||||
@property
|
||||
def preserve_order(self) -> bool:
|
||||
return True
|
||||
|
||||
@@ -302,7 +302,7 @@ class Matrix(TablePluginMixin, Provider):
|
||||
|
||||
EXPOSE_AS_FILE_PROVIDER = False
|
||||
MULTI_INSTANCE = True
|
||||
SUPPORTED_CMDLETS = frozenset({"add-file"})
|
||||
SUPPORTED_CMDLETS = frozenset({"add-file", "search-file"})
|
||||
|
||||
@classmethod
|
||||
def config_schema(cls) -> List[Dict[str, Any]]:
|
||||
|
||||
+14
-16
@@ -10,7 +10,7 @@ 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 PluginCore.registry import get_plugin, get_plugin_for_url, list_plugin_names_with_capability
|
||||
from PluginCore.registry import get_plugin, 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 plugins.mpv.mpv_ipc import MPV
|
||||
@@ -566,34 +566,32 @@ def _iter_provider_hook_candidates(
|
||||
providers: List[Any] = []
|
||||
seen: set[str] = set()
|
||||
|
||||
for target in targets or ():
|
||||
try:
|
||||
provider = get_plugin_for_url(str(target or ""), config or {})
|
||||
except Exception:
|
||||
provider = None
|
||||
def _append_provider(provider: Any) -> None:
|
||||
if provider is None:
|
||||
continue
|
||||
return
|
||||
name = str(getattr(provider, "name", "") or "").strip().lower()
|
||||
if name and name not in seen:
|
||||
seen.add(name)
|
||||
providers.append(provider)
|
||||
|
||||
for target in targets or ():
|
||||
try:
|
||||
provider_names = list_plugin_names_with_capability(capability)
|
||||
provider = get_plugin_for_url(str(target or ""), config or {})
|
||||
except Exception:
|
||||
provider_names = []
|
||||
provider = None
|
||||
_append_provider(provider)
|
||||
|
||||
for provider_name in provider_names:
|
||||
fallback_provider_names = {
|
||||
"pipe-item-context": ("hydrusnetwork",),
|
||||
"playlist-store": ("hydrusnetwork",),
|
||||
}.get(str(capability or "").strip().lower(), ())
|
||||
|
||||
for provider_name in fallback_provider_names:
|
||||
try:
|
||||
provider = get_plugin(provider_name, config or {})
|
||||
except Exception:
|
||||
provider = None
|
||||
if provider is None:
|
||||
continue
|
||||
name = str(getattr(provider, "name", provider_name) or provider_name).strip().lower()
|
||||
if name and name not in seen:
|
||||
seen.add(name)
|
||||
providers.append(provider)
|
||||
_append_provider(provider)
|
||||
|
||||
return providers
|
||||
|
||||
|
||||
@@ -608,6 +608,7 @@ def title_hint_from_url_slug(u: str) -> str:
|
||||
|
||||
class OpenLibrary(Provider):
|
||||
|
||||
SUPPORTED_CMDLETS = frozenset({"download-file", "search-file"})
|
||||
TABLE_AUTO_STAGES = {
|
||||
"openlibrary.edition": ["download-file"],
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ def _get_podcastindex_credentials(config: Dict[str, Any]) -> Tuple[str, str]:
|
||||
class PodcastIndex(Provider):
|
||||
"""Search provider for PodcastIndex.org."""
|
||||
|
||||
SUPPORTED_CMDLETS = frozenset({"search-file"})
|
||||
TABLE_AUTO_STAGES = {
|
||||
"podcastindex": ["download-file"],
|
||||
"podcastindex.episodes": ["download-file"],
|
||||
|
||||
@@ -74,6 +74,7 @@ def _unique_path(path: Path) -> Path:
|
||||
class SCP(Provider):
|
||||
PLUGIN_NAME = "scp"
|
||||
URL = ("scp://", "sftp://")
|
||||
SUPPORTED_CMDLETS = frozenset({"add-file", "download-file", "search-file"})
|
||||
|
||||
@property
|
||||
def label(self) -> str:
|
||||
|
||||
@@ -266,6 +266,7 @@ def _suppress_aioslsk_noise() -> Any:
|
||||
|
||||
class Soulseek(Provider):
|
||||
|
||||
SUPPORTED_CMDLETS = frozenset({"download-file", "search-file"})
|
||||
TABLE_AUTO_STAGES = {
|
||||
"soulseek": ["download-file", "-plugin", "soulseek"],
|
||||
}
|
||||
|
||||
@@ -151,6 +151,7 @@ class Telegram(Provider):
|
||||
bot_token=
|
||||
"""
|
||||
URL = ("t.me", "telegram.me")
|
||||
SUPPORTED_CMDLETS = frozenset({"download-file"})
|
||||
|
||||
@classmethod
|
||||
def config_schema(cls) -> List[Dict[str, Any]]:
|
||||
|
||||
@@ -65,6 +65,7 @@ def _format_total_seconds(seconds: Any) -> str:
|
||||
|
||||
class Tidal(Provider):
|
||||
PLUGIN_NAME = "tidal"
|
||||
SUPPORTED_CMDLETS = frozenset({"download-file", "search-file"})
|
||||
|
||||
TABLE_AUTO_STAGES = {
|
||||
"tidal.track": ["download-file"],
|
||||
|
||||
@@ -361,6 +361,7 @@ class ApiBayScraper(Scraper):
|
||||
|
||||
class Torrent(Provider):
|
||||
TABLE_AUTO_STAGES = {"torrent": ["download-file"]}
|
||||
SUPPORTED_CMDLETS = frozenset({"search-file"})
|
||||
|
||||
@property
|
||||
def preserve_order(self) -> bool:
|
||||
|
||||
@@ -52,6 +52,7 @@ class Vimm(TablePluginMixin, Provider):
|
||||
The code below implements these choices (and contains inline comments
|
||||
explaining specific decisions)."""
|
||||
|
||||
SUPPORTED_CMDLETS = frozenset({"download-file", "search-file"})
|
||||
URL = ("https://vimm.net/vault/",)
|
||||
URL_DOMAINS = ("vimm.net",)
|
||||
|
||||
|
||||
@@ -508,6 +508,7 @@ class ytdlp(TablePluginMixin, Provider):
|
||||
PLUGIN_NAME = "ytdlp"
|
||||
PLUGIN_ALIASES = ("youtube",)
|
||||
SEARCH_QUERY_KEYS = ("search", "q")
|
||||
SUPPORTED_CMDLETS = frozenset({"download-file", "search-file"})
|
||||
|
||||
@staticmethod
|
||||
def config_schema() -> List[Dict[str, Any]]:
|
||||
|
||||
@@ -13,6 +13,7 @@ class ZeroXZero(Provider):
|
||||
|
||||
PLUGIN_NAME = "0x0"
|
||||
PLUGIN_ALIASES = ("zeroxzero",)
|
||||
SUPPORTED_CMDLETS = frozenset({"add-file"})
|
||||
|
||||
def upload(self, file_path: str, **kwargs: Any) -> str:
|
||||
from API.HTTP import HTTPClient
|
||||
|
||||
Reference in New Issue
Block a user