This commit is contained in:
2026-01-07 11:01:13 -08:00
parent f0799191ff
commit 89ac3bb7e8
3 changed files with 394 additions and 7 deletions

View File

@@ -3,3 +3,9 @@
Concrete provider implementations live in this package.
The public entrypoint/registry is ProviderCore.registry.
"""
# Register providers with the strict ResultTable adapter system
try:
from . import alldebrid
except Exception:
pass

View File

@@ -544,6 +544,7 @@ def adjust_output_dir_for_alldebrid(
class AllDebrid(Provider):
# Magnet URIs should be routed through this provider.
TABLE_AUTO_STAGES = {"alldebrid": ["download-file"]}
AUTO_STAGE_USE_SELECTION_ARGS = True
URL = ("magnet:",)
URL_DOMAINS = ()
@@ -818,6 +819,29 @@ class AllDebrid(Provider):
path_from_result: Callable[[Any], Path],
config: Optional[Dict[str, Any]] = None,
) -> int:
# Check if this is a direct magnet_id from the account (e.g., from selector)
full_metadata = getattr(result, "full_metadata", None) or {}
if isinstance(full_metadata, dict):
magnet_id_direct = full_metadata.get("magnet_id")
if magnet_id_direct is not None:
try:
magnet_id = int(magnet_id_direct)
debug(f"[download_items] Found magnet_id {magnet_id} in metadata, downloading files directly")
cfg = config if isinstance(config, dict) else (self.config or {})
count = self._download_magnet_by_id(
magnet_id,
output_dir,
cfg,
emit,
progress,
quiet_mode,
path_from_result,
)
debug(f"[download_items] _download_magnet_by_id returned {count}")
return count
except Exception as e:
debug(f"[download_items] Failed to download by magnet_id: {e}")
spec = self._resolve_magnet_spec_from_result(result)
if not spec:
return 0
@@ -885,6 +909,97 @@ class AllDebrid(Provider):
enriched["_relpath"] = relpath
yield enriched
def _download_magnet_by_id(
self,
magnet_id: int,
output_dir: Path,
config: Dict[str, Any],
emit: Callable[[Path, str, str, Dict[str, Any]], None],
progress: Any,
quiet_mode: bool,
path_from_result: Callable[[Any], Path],
) -> int:
"""Download files from an existing magnet ID (already in account)."""
api_key = _get_debrid_api_key(config or {})
if not api_key:
log("AllDebrid API key not configured", file=sys.stderr)
return 0
try:
client = AllDebridClient(api_key)
except Exception as exc:
log(f"Failed to init AllDebrid client: {exc}", file=sys.stderr)
return 0
try:
files_result = client.magnet_links([magnet_id])
except Exception as exc:
log(f"Failed to list files for magnet {magnet_id}: {exc}", file=sys.stderr)
return 0
magnet_files = files_result.get(str(magnet_id), {}) if isinstance(files_result, dict) else {}
file_nodes = magnet_files.get("files") if isinstance(magnet_files, dict) else []
if not file_nodes:
log(f"AllDebrid magnet {magnet_id} has no files", file=sys.stderr)
return 0
downloaded = 0
for node in self._flatten_files(file_nodes):
locked_url = str(node.get("l") or node.get("link") or "").strip()
file_name = str(node.get("n") or node.get("name") or "").strip()
relpath = str(node.get("_relpath") or file_name or "").strip()
if not locked_url or not relpath:
continue
# Unlock the URL if it's restricted (contains /f/)
file_url = locked_url
if "/f/" in locked_url:
try:
unlocked = client.unlock_link(locked_url)
if unlocked:
file_url = unlocked
debug(f"[alldebrid] Unlocked restricted link for {file_name}")
else:
debug(f"[alldebrid] Failed to unlock {locked_url}, trying locked URL")
except Exception as exc:
debug(f"[alldebrid] unlock_link failed: {exc}, trying locked URL")
target_path = output_dir
rel_path_obj = Path(relpath)
if rel_path_obj.parent:
target_path = output_dir / rel_path_obj.parent
try:
target_path.mkdir(parents=True, exist_ok=True)
except Exception:
target_path = output_dir
try:
result_obj = _download_direct_file(
file_url,
target_path,
quiet=quiet_mode,
suggested_filename=rel_path_obj.name,
pipeline_progress=progress,
)
except Exception as exc:
debug(f"Failed to download {file_url}: {exc}")
continue
downloaded_path = path_from_result(result_obj)
metadata = {
"magnet_id": magnet_id,
"relpath": relpath,
"name": file_name,
}
emit(downloaded_path, file_url, relpath, metadata)
downloaded += 1
if downloaded == 0:
log(f"AllDebrid magnet {magnet_id} produced no downloads", file=sys.stderr)
return downloaded
def search(
self,
query: str,
@@ -1032,10 +1147,9 @@ class AllDebrid(Provider):
"file": file_node,
"provider": "alldebrid",
"provider_view": "files",
"_selection_args": ["-magnet-id", str(magnet_id)],
"_selection_action": ["download-file", "-provider", "alldebrid", "-magnet-id", str(magnet_id)],
}
if file_url:
metadata["_selection_args"] = ["-url", file_url]
metadata["_selection_action"] = ["download-file", "-url", file_url]
results.append(
SearchResult(
@@ -1147,6 +1261,9 @@ class AllDebrid(Provider):
"provider": "alldebrid",
"provider_view": "folders",
"magnet_name": magnet_name,
# Selection metadata: allow @N expansion to drive downloads directly
"_selection_args": ["-magnet-id", str(magnet_id)],
"_selection_action": ["download-file", "-provider", "alldebrid", "-magnet-id", str(magnet_id)],
},
)
)
@@ -1247,10 +1364,7 @@ class AllDebrid(Provider):
table.set_table_metadata({"provider": "alldebrid", "view": "files", "magnet_id": magnet_id})
except Exception:
pass
table.set_source_command(
"search-file",
["-provider", "alldebrid", "-open", str(magnet_id), "-query", "*"],
)
table.set_source_command("download-file", ["-provider", "alldebrid"])
results_payload: List[Dict[str, Any]] = []
for r in files or []:
@@ -1280,3 +1394,177 @@ class AllDebrid(Provider):
pass
return True
try:
from SYS.result_table_adapters import register_provider
from SYS.result_table_api import ColumnSpec, ResultModel, metadata_column, title_column
def _as_payload(item: Any) -> Dict[str, Any]:
if isinstance(item, dict):
return dict(item)
try:
if hasattr(item, "to_dict"):
result = item.to_dict() # type: ignore[attr-defined]
if isinstance(result, dict):
return result
except Exception:
pass
payload: Dict[str, Any] = {}
for attr in ("title", "path", "columns", "full_metadata", "table", "source", "size_bytes", "size", "ext"):
try:
val = getattr(item, attr, None)
except Exception:
val = None
if val is not None:
payload.setdefault(attr, val)
return payload
def _coerce_size(value: Any) -> Optional[int]:
if value is None:
return None
if isinstance(value, (int, float)):
try:
return int(value)
except Exception:
return None
try:
return int(float(str(value).strip()))
except Exception:
return None
def _normalize_columns(columns: Any, metadata: Dict[str, Any]) -> None:
if not isinstance(columns, list):
return
for entry in columns:
if not isinstance(entry, (list, tuple)) or len(entry) < 2:
continue
key, value = entry[0], entry[1]
if not key:
continue
normalized = str(key).replace(" ", "_").strip().lower()
if not normalized:
continue
metadata.setdefault(normalized, value)
def _convert_to_model(item: Any) -> ResultModel:
payload = _as_payload(item)
title = str(payload.get("title") or payload.get("name") or "").strip()
if not title:
candidate = payload.get("path") or payload.get("detail") or payload.get("magnet_name")
title = str(candidate or "").strip()
if not title:
title = "alldebrid"
path_val = payload.get("path")
if path_val is not None and not isinstance(path_val, str):
try:
path_val = str(path_val)
except Exception:
path_val = None
size_bytes = _coerce_size(payload.get("size_bytes") or payload.get("size") or payload.get("file_size"))
metadata: Dict[str, Any] = {}
full_metadata = payload.get("full_metadata")
if isinstance(full_metadata, dict):
metadata.update(full_metadata)
_normalize_columns(payload.get("columns"), metadata)
table_name = str(payload.get("table") or payload.get("source") or "alldebrid").strip().lower()
if table_name:
metadata.setdefault("table", table_name)
metadata.setdefault("source", table_name)
metadata.setdefault("provider", table_name)
ext = payload.get("ext")
if not ext and isinstance(path_val, str):
try:
suffix = Path(path_val).suffix
if suffix:
ext = suffix.lstrip(".")
except Exception:
ext = None
return ResultModel(
title=title,
path=path_val,
ext=str(ext) if ext is not None else None,
size_bytes=size_bytes,
metadata=metadata,
source="alldebrid",
)
def _adapter(items: Iterable[Any]) -> Iterable[ResultModel]:
for item in items or []:
try:
model = _convert_to_model(item)
except Exception:
continue
yield model
def _has_metadata(rows: List[ResultModel], key: str) -> bool:
for row in rows:
md = row.metadata or {}
if key in md:
val = md[key]
if val is None:
continue
if isinstance(val, str) and not val.strip():
continue
return True
return False
def _columns_factory(rows: List[ResultModel]) -> List[ColumnSpec]:
cols = [title_column()]
if _has_metadata(rows, "magnet_name"):
cols.append(metadata_column("magnet_name", "Magnet"))
if _has_metadata(rows, "magnet_id"):
cols.append(metadata_column("magnet_id", "Magnet ID"))
if _has_metadata(rows, "status"):
cols.append(metadata_column("status", "Status"))
if _has_metadata(rows, "ready"):
cols.append(metadata_column("ready", "Ready"))
if _has_metadata(rows, "relpath"):
cols.append(metadata_column("relpath", "Relpath"))
if _has_metadata(rows, "provider_view"):
cols.append(metadata_column("provider_view", "View"))
if _has_metadata(rows, "size"):
cols.append(metadata_column("size", "Size"))
return cols
def _selection_fn(row: ResultModel) -> List[str]:
metadata = row.metadata or {}
action = metadata.get("_selection_action") or metadata.get("selection_action")
if isinstance(action, (list, tuple)) and action:
return [str(x) for x in action if x is not None]
args = metadata.get("_selection_args") or metadata.get("selection_args")
if isinstance(args, (list, tuple)) and args:
return [str(x) for x in args if x is not None]
view = metadata.get("provider_view") or metadata.get("view") or ""
if view == "files":
if row.path:
return ["-url", row.path]
magnet_id = metadata.get("magnet_id")
if magnet_id is not None:
return ["-magnet-id", str(magnet_id)]
if row.path:
return ["-url", row.path]
return ["-title", row.title or ""]
register_provider(
"alldebrid",
_adapter,
columns=_columns_factory,
selection_fn=_selection_fn,
metadata={"description": "AllDebrid account provider"},
)
except Exception:
pass

View File

@@ -86,6 +86,11 @@ class Download_File(Cmdlet):
alias="a",
description="Download audio only (yt-dlp)",
),
CmdletArg(
name="-magnet-id",
type="string",
description="(internal) AllDebrid magnet id used by provider selection hooks",
),
CmdletArg(
name="format",
type="string",
@@ -3789,6 +3794,94 @@ class Download_File(Cmdlet):
registry = self._load_provider_registry()
downloaded_count = 0
# Special-case: support selection-inserted magnet-id arg to drive provider downloads
magnet_id_raw = parsed.get("magnet-id")
if magnet_id_raw:
try:
magnet_id = int(str(magnet_id_raw).strip())
except Exception:
log(f"[download-file] invalid magnet-id: {magnet_id_raw}", file=sys.stderr)
return 1
get_provider = registry.get("get_provider")
provider_name = str(parsed.get("provider") or "alldebrid").strip().lower()
provider_obj = None
if get_provider is not None:
try:
provider_obj = get_provider(provider_name, config)
except Exception:
provider_obj = None
if provider_obj is None:
log(f"[download-file] provider '{provider_name}' not available", file=sys.stderr)
return 1
SearchResult = registry.get("SearchResult")
try:
if SearchResult is not None:
sr = SearchResult(
table=provider_name,
title=f"magnet-{magnet_id}",
path=f"alldebrid:magnet:{magnet_id}",
full_metadata={
"magnet_id": magnet_id,
"provider": provider_name,
"provider_view": "files",
},
)
else:
sr = None
except Exception:
sr = None
def _on_emit(path: Path, file_url: str, relpath: str, metadata: Dict[str, Any]) -> None:
title_hint = metadata.get("name") or relpath or f"magnet-{magnet_id}"
self._emit_local_file(
downloaded_path=path,
source=file_url or f"alldebrid:magnet:{magnet_id}",
title_hint=title_hint,
tags_hint=None,
media_kind_hint="file",
full_metadata=metadata,
progress=progress,
config=config,
provider_hint=provider_name,
)
try:
downloaded_extra = provider_obj.download_items(
sr,
final_output_dir,
emit=_on_emit,
progress=progress,
quiet_mode=quiet_mode,
path_from_result=self._path_from_download_result,
config=config,
)
except TypeError:
downloaded_extra = provider_obj.download_items(
sr,
final_output_dir,
emit=_on_emit,
progress=progress,
quiet_mode=quiet_mode,
path_from_result=self._path_from_download_result,
)
except Exception as exc:
log(f"[download-file] failed to download magnet {magnet_id}: {exc}", file=sys.stderr)
return 1
if downloaded_extra:
debug(f"[download-file] AllDebrid magnet {magnet_id} emitted {downloaded_extra} files")
return 0
log(
f"[download-file] AllDebrid magnet {magnet_id} produced no downloads",
file=sys.stderr,
)
return 1
urls_downloaded, early_exit = self._process_explicit_urls(
raw_urls=raw_url,
final_output_dir=final_output_dir,