Files
Medios-Macina/Provider/alldebrid.py
2026-01-01 20:37:27 -08:00

965 lines
34 KiB
Python

from __future__ import annotations
import hashlib
import sys
import time
from pathlib import Path
from typing import Any, Dict, Iterable, List, Optional, Callable, Tuple
from urllib.parse import urlparse
from API.HTTP import HTTPClient
from API.alldebrid import AllDebridClient, parse_magnet_or_hash, is_magnet_link, is_torrent_file
from ProviderCore.base import Provider, SearchResult
from ProviderCore.download import sanitize_filename
from SYS.download import _download_direct_file
from SYS.logger import log
def _get_debrid_api_key(config: Dict[str, Any]) -> Optional[str]:
"""Read AllDebrid API key from config.
Preferred formats:
- config.conf provider block:
[provider=alldebrid]
api_key=...
-> config["provider"]["alldebrid"]["api_key"]
- store-style debrid block:
config["store"]["debrid"]["all-debrid"]["api_key"]
Falls back to some legacy keys if present.
"""
# 1) provider block: [provider=alldebrid]
provider = config.get("provider")
if isinstance(provider, dict):
entry = provider.get("alldebrid")
if isinstance(entry, dict):
for k in ("api_key", "apikey", "API_KEY", "APIKEY"):
val = entry.get(k)
if isinstance(val, str) and val.strip():
return val.strip()
if isinstance(entry, str) and entry.strip():
return entry.strip()
# 2) store.debrid block (canonical for debrid store configuration)
try:
from SYS.config import get_debrid_api_key
key = get_debrid_api_key(config, service="All-debrid")
return key.strip() if key else None
except Exception:
pass
# Legacy fallback (kept permissive so older configs still work)
for legacy_key in ("alldebrid_api_key", "AllDebrid", "all_debrid_api_key"):
val = config.get(legacy_key)
if isinstance(val, str) and val.strip():
return val.strip()
return None
def _consume_bencoded_value(data: bytes, pos: int) -> int:
if pos >= len(data):
raise ValueError("Unexpected end of bencode")
token = data[pos:pos + 1]
if token == b"i":
end = data.find(b"e", pos + 1)
if end == -1:
raise ValueError("Unterminated integer")
return end + 1
if token == b"l" or token == b"d":
cursor = pos + 1
while cursor < len(data):
if data[cursor:cursor + 1] == b"e":
return cursor + 1
cursor = _consume_bencoded_value(data, cursor)
raise ValueError("Unterminated list/dict")
if token and b"0" <= token <= b"9":
colon = data.find(b":", pos)
if colon == -1:
raise ValueError("Invalid string length")
length = int(data[pos:colon])
return colon + 1 + length
raise ValueError("Unknown bencode token")
def _info_hash_from_torrent_bytes(data: bytes) -> Optional[str]:
needle = b"4:info"
idx = data.find(needle)
if idx == -1:
return None
start = idx + len(needle)
try:
end = _consume_bencoded_value(data, start)
except ValueError:
return None
info_bytes = data[start:end]
try:
return hashlib.sha1(info_bytes).hexdigest()
except Exception:
return None
def _fetch_torrent_bytes(target: str) -> Optional[bytes]:
path_obj = Path(str(target))
try:
if path_obj.exists() and path_obj.is_file():
return path_obj.read_bytes()
except Exception:
pass
try:
parsed = urlparse(target)
except Exception:
parsed = None
if parsed is None or not parsed.scheme or parsed.scheme.lower() not in {"http", "https"}:
return None
if not target.lower().endswith(".torrent"):
return None
try:
with HTTPClient(timeout=30.0) as client:
response = client.get(target)
return response.content
except Exception as exc:
log(f"Failed to download .torrent from {target}: {exc}", file=sys.stderr)
return None
def resolve_magnet_spec(target: str) -> Optional[str]:
"""Resolve a magnet/hash/torrent URL into a magnet/hash string."""
candidate = str(target or "").strip()
if not candidate:
return None
parsed = parse_magnet_or_hash(candidate)
if parsed:
return parsed
if is_torrent_file(candidate):
torrent_bytes = _fetch_torrent_bytes(candidate)
if not torrent_bytes:
return None
hash_value = _info_hash_from_torrent_bytes(torrent_bytes)
if hash_value:
return hash_value
return None
def _dispatch_alldebrid_magnet_search(
magnet_id: int,
config: Dict[str, Any],
) -> None:
try:
from cmdlet.search_file import CMDLET as _SEARCH_FILE_CMDLET
exec_fn = getattr(_SEARCH_FILE_CMDLET, "exec", None)
if callable(exec_fn):
exec_fn(
None,
["-provider", "alldebrid", f"ID={magnet_id}"],
config,
)
except Exception:
pass
log(f"[alldebrid] Sent magnet {magnet_id} to AllDebrid for download", file=sys.stderr)
def prepare_magnet(
magnet_spec: str,
config: Dict[str, Any],
) -> tuple[Optional[AllDebridClient], Optional[int]]:
api_key = _get_debrid_api_key(config or {})
if not api_key:
try:
from ProviderCore.registry import show_provider_config_panel
show_provider_config_panel("alldebrid", ["api_key"])
except Exception:
pass
log("AllDebrid API key not configured (provider.alldebrid.api_key)", file=sys.stderr)
return None, None
try:
client = AllDebridClient(api_key)
except Exception as exc:
log(f"Failed to initialize AllDebrid client: {exc}", file=sys.stderr)
return None, None
try:
magnet_info = client.magnet_add(magnet_spec)
magnet_id = int(magnet_info.get("id", 0))
if magnet_id <= 0:
log(f"AllDebrid magnet submission failed: {magnet_info}", file=sys.stderr)
return None, None
except Exception as exc:
log(f"Failed to submit magnet to AllDebrid: {exc}", file=sys.stderr)
return None, None
_dispatch_alldebrid_magnet_search(magnet_id, config)
return client, magnet_id
def _flatten_files_with_relpath(items: Any) -> Iterable[Dict[str, Any]]:
for node in AllDebrid._flatten_files(items):
enriched = dict(node)
rel = node.get("_relpath") or node.get("relpath")
if not rel:
name = node.get("n") or node.get("name")
rel = str(name or "").strip()
enriched["relpath"] = rel
yield enriched
def download_magnet(
magnet_spec: str,
original_url: str,
final_output_dir: Path,
config: Dict[str, Any],
progress: Any,
quiet_mode: bool,
path_from_result: Callable[[Any], Path],
on_emit: Callable[[Path, str, str, Dict[str, Any]], None],
) -> tuple[int, Optional[int]]:
client, magnet_id = prepare_magnet(magnet_spec, config)
if client is None or magnet_id is None:
return 0, None
wait_timeout = 300
try:
streaming_config = config.get("streaming", {}) if isinstance(config, dict) else {}
wait_timeout = int(streaming_config.get("wait_timeout", 300))
except Exception:
wait_timeout = 300
elapsed = 0
while elapsed < wait_timeout:
try:
status = client.magnet_status(magnet_id)
except Exception as exc:
log(f"Failed to read magnet status {magnet_id}: {exc}", file=sys.stderr)
return 0, magnet_id
ready = bool(status.get("ready")) or status.get("statusCode") == 4
if ready:
break
time.sleep(5)
elapsed += 5
else:
log(f"AllDebrid magnet {magnet_id} timed out after {wait_timeout}s", file=sys.stderr)
return 0, magnet_id
try:
files_result = client.magnet_links([magnet_id])
except Exception as exc:
log(f"Failed to list AllDebrid magnet files: {exc}", file=sys.stderr)
return 0, magnet_id
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} produced no files", file=sys.stderr)
return 0, magnet_id
downloaded = 0
for node in _flatten_files_with_relpath(file_nodes):
file_url = str(node.get("link") or "").strip()
file_name = str(node.get("name") or "").strip()
relpath = str(node.get("relpath") or file_name).strip()
if not file_url or not relpath:
continue
target_path = final_output_dir
rel_path_obj = Path(relpath)
output_dir = target_path
if rel_path_obj.parent:
output_dir = target_path / rel_path_obj.parent
try:
output_dir.mkdir(parents=True, exist_ok=True)
except Exception:
output_dir = target_path
try:
result_obj = _download_direct_file(
file_url,
output_dir,
quiet=quiet_mode,
suggested_filename=rel_path_obj.name,
pipeline_progress=progress,
)
except Exception as exc:
log(f"Failed to download AllDebrid file {file_url}: {exc}", file=sys.stderr)
continue
downloaded_path = path_from_result(result_obj)
metadata = {
"magnet_id": magnet_id,
"relpath": relpath,
"name": file_name,
}
on_emit(downloaded_path, file_url or original_url, relpath, metadata)
downloaded += 1
return downloaded, magnet_id
def expand_folder_item(
item: Any,
get_search_provider: Optional[Callable[[str, Dict[str, Any]], Any]],
config: Dict[str, Any],
) -> Tuple[List[Any], Optional[str]]:
table = getattr(item, "table", None) if not isinstance(item, dict) else item.get("table")
media_kind = getattr(item, "media_kind", None) if not isinstance(item, dict) else item.get("media_kind")
full_metadata = getattr(item, "full_metadata", None) if not isinstance(item, dict) else item.get("full_metadata")
target = None
if isinstance(item, dict):
target = item.get("path") or item.get("url")
else:
target = getattr(item, "path", None) or getattr(item, "url", None)
if (str(table or "").lower() != "alldebrid") or (str(media_kind or "").lower() != "folder"):
return [], None
magnet_id = None
if isinstance(full_metadata, dict):
magnet_id = full_metadata.get("magnet_id")
if magnet_id is None and isinstance(target, str) and target.lower().startswith("alldebrid:magnet:"):
try:
magnet_id = int(target.split(":")[-1])
except Exception:
magnet_id = None
if magnet_id is None or get_search_provider is None:
return [], None
provider = get_search_provider("alldebrid", config) if get_search_provider else None
if provider is None:
return [], None
try:
files = provider.search("*", limit=10_000, filters={"view": "files", "magnet_id": int(magnet_id)})
except Exception:
files = []
if files and len(files) == 1 and getattr(files[0], "media_kind", "") == "folder":
detail = getattr(files[0], "detail", "")
return [], str(detail or "unknown")
expanded: List[Any] = []
for sr in files:
expanded.append(sr.to_dict() if hasattr(sr, "to_dict") else sr)
return expanded, None
def adjust_output_dir_for_alldebrid(
base_output_dir: Path,
full_metadata: Optional[Dict[str, Any]],
item: Any,
) -> Path:
from ProviderCore.download import sanitize_filename as _sf
output_dir = base_output_dir
md = full_metadata if isinstance(full_metadata, dict) else {}
magnet_name = md.get("magnet_name") or md.get("folder")
if not magnet_name:
try:
detail_val = getattr(item, "detail", None) if not isinstance(item, dict) else item.get("detail")
magnet_name = str(detail_val or "").strip() or None
except Exception:
magnet_name = None
magnet_dir_name = _sf(str(magnet_name)) if magnet_name else ""
try:
base_tail = str(Path(output_dir).name or "")
except Exception:
base_tail = ""
base_tail_norm = _sf(base_tail).lower() if base_tail.strip() else ""
magnet_dir_norm = magnet_dir_name.lower() if magnet_dir_name else ""
if magnet_dir_name and (not base_tail_norm or base_tail_norm != magnet_dir_norm):
output_dir = Path(output_dir) / magnet_dir_name
relpath = md.get("relpath") if isinstance(md, dict) else None
if (not relpath) and isinstance(md.get("file"), dict):
relpath = md["file"].get("_relpath")
if relpath:
parts = [p for p in str(relpath).replace("\\", "/").split("/") if p and p not in {".", ".."}]
if magnet_dir_name and parts:
try:
if _sf(parts[0]).lower() == magnet_dir_norm:
parts = parts[1:]
except Exception:
pass
for part in parts[:-1]:
output_dir = Path(output_dir) / _sf(part)
try:
Path(output_dir).mkdir(parents=True, exist_ok=True)
except Exception:
output_dir = base_output_dir
return output_dir
class AllDebrid(Provider):
# Magnet URIs should be routed through this provider.
URL = ("magnet:",)
"""Search provider for AllDebrid account content.
This provider lists and searches the files/magnets already present in the
user's AllDebrid account.
Query behavior:
- "*" / "all" / "list": list recent files from ready magnets
- otherwise: substring match on file name OR magnet name, or exact magnet id
"""
def validate(self) -> bool:
# Consider "available" when configured; actual API connectivity can vary.
return bool(_get_debrid_api_key(self.config or {}))
def download(self, result: SearchResult, output_dir: Path) -> Optional[Path]:
"""Download an AllDebrid SearchResult into output_dir.
AllDebrid magnet file listings often provide links that require an API
"unlock" step to produce a true direct-download URL. Without unlocking,
callers may download a small HTML/redirect page instead of file bytes.
This is used by the download-file cmdlet when a provider item is piped.
"""
try:
api_key = _get_debrid_api_key(self.config or {})
if not api_key:
return None
target = str(getattr(result, "path", "") or "").strip()
if not target.startswith(("http://", "https://")):
return None
try:
from API.alldebrid import AllDebridClient
client = AllDebridClient(api_key)
except Exception as exc:
log(f"[alldebrid] Failed to init client: {exc}", file=sys.stderr)
return None
# Quiet mode when download-file is mid-pipeline.
quiet = (
bool(self.config.get("_quiet_background_output"))
if isinstance(self.config,
dict) else False
)
unlocked_url = target
try:
unlocked = client.unlock_link(target)
if isinstance(unlocked,
str) and unlocked.strip().startswith(("http://",
"https://")):
unlocked_url = unlocked.strip()
except Exception as exc:
# Fall back to the raw link, but warn.
log(f"[alldebrid] Failed to unlock link: {exc}", file=sys.stderr)
# Prefer provider title as the output filename.
suggested = sanitize_filename(
str(getattr(result,
"title",
"") or "").strip()
)
suggested_name = suggested if suggested else None
try:
from SYS.download import _download_direct_file
pipe_progress = None
try:
if isinstance(self.config, dict):
pipe_progress = self.config.get("_pipeline_progress")
except Exception:
pipe_progress = None
dl_res = _download_direct_file(
unlocked_url,
Path(output_dir),
quiet=quiet,
suggested_filename=suggested_name,
pipeline_progress=pipe_progress,
)
downloaded_path = getattr(dl_res, "path", None)
if downloaded_path is None:
return None
downloaded_path = Path(str(downloaded_path))
# Guard: if we got an HTML error/redirect page, treat as failure.
try:
if downloaded_path.exists():
size = downloaded_path.stat().st_size
if (size > 0 and size <= 250_000
and downloaded_path.suffix.lower() not in (".html",
".htm")):
head = downloaded_path.read_bytes()[:512]
try:
text = head.decode("utf-8", errors="ignore").lower()
except Exception:
text = ""
if "<html" in text or "<!doctype html" in text:
try:
downloaded_path.unlink()
except Exception:
pass
log(
"[alldebrid] Download returned HTML page (not file bytes). Try again or check AllDebrid link status.",
file=sys.stderr,
)
return None
except Exception:
pass
return downloaded_path if downloaded_path.exists() else None
except Exception as exc:
log(f"[alldebrid] Download failed: {exc}", file=sys.stderr)
return None
except Exception:
return None
@staticmethod
def _flatten_files(items: Any,
*,
_prefix: Optional[List[str]] = None) -> Iterable[Dict[str,
Any]]:
"""Flatten AllDebrid magnet file tree into file dicts, preserving relative paths.
API commonly returns:
- file: {n: name, s: size, l: link}
- folder: {n: name, e: [sub_items]}
This flattener attaches a best-effort relative path to each yielded file node
as `_relpath` using POSIX separators (e.g., "Season 1/E01.mkv").
Some call sites in this repo also expect {name, size, link}, so we accept both.
"""
prefix = list(_prefix or [])
if not items:
return
if isinstance(items, dict):
items = [items]
if not isinstance(items, list):
return
for node in items:
if not isinstance(node, dict):
continue
children = node.get("e") or node.get("children")
if isinstance(children, list):
folder_name = node.get("n") or node.get("name")
next_prefix = prefix
if isinstance(folder_name, str) and folder_name.strip():
next_prefix = prefix + [folder_name.strip()]
yield from AllDebrid._flatten_files(children, _prefix=next_prefix)
continue
name = node.get("n") or node.get("name")
link = node.get("l") or node.get("link")
if isinstance(name,
str) and name.strip() and isinstance(link,
str) and link.strip():
rel_parts = prefix + [name.strip()]
relpath = "/".join([p for p in rel_parts if p])
enriched = dict(node)
enriched["_relpath"] = relpath
yield enriched
def search(
self,
query: str,
limit: int = 50,
filters: Optional[Dict[str,
Any]] = None,
**kwargs: Any,
) -> List[SearchResult]:
q = (query or "").strip()
if not q:
return []
api_key = _get_debrid_api_key(self.config or {})
if not api_key:
return []
view = None
if isinstance(filters, dict):
view = str(filters.get("view") or "").strip().lower() or None
view = view or "folders"
try:
from API.alldebrid import AllDebridClient
client = AllDebridClient(api_key)
except Exception as exc:
log(f"[alldebrid] Failed to init client: {exc}", file=sys.stderr)
return []
q_lower = q.lower()
needle = "" if q_lower in {"*",
"all",
"list"} else q_lower
# Second-stage: list files for a specific magnet id.
if view == "files":
magnet_id_val = None
if isinstance(filters, dict):
magnet_id_val = filters.get("magnet_id")
if magnet_id_val is None:
magnet_id_val = kwargs.get("magnet_id")
try:
magnet_id = int(magnet_id_val)
except Exception:
return []
magnet_status: Dict[str,
Any] = {}
try:
magnet_status = client.magnet_status(magnet_id)
except Exception:
magnet_status = {}
magnet_name = str(
magnet_status.get("filename") or magnet_status.get("name")
or magnet_status.get("hash") or f"magnet-{magnet_id}"
)
status_code = magnet_status.get("statusCode")
status_text = str(magnet_status.get("status") or "").strip() or "unknown"
ready = status_code == 4 or bool(magnet_status.get("ready"))
if not ready:
return [
SearchResult(
table="alldebrid",
title=magnet_name,
path=f"alldebrid:magnet:{magnet_id}",
detail=status_text,
annotations=["folder",
"not-ready"],
media_kind="folder",
tag={"alldebrid",
"folder",
str(magnet_id),
"not-ready"},
columns=[
("Folder",
magnet_name),
("ID",
str(magnet_id)),
("Status",
status_text),
("Ready",
"no"),
],
full_metadata={
"magnet": magnet_status,
"magnet_id": magnet_id,
"provider": "alldebrid",
"provider_view": "files",
"magnet_name": magnet_name,
},
)
]
try:
files_result = client.magnet_links([magnet_id])
magnet_files = (
files_result.get(str(magnet_id),
{}) if isinstance(files_result,
dict) else {}
)
file_tree = magnet_files.get("files",
[]) if isinstance(magnet_files,
dict) else []
except Exception as exc:
log(
f"[alldebrid] Failed to list files for magnet {magnet_id}: {exc}",
file=sys.stderr,
)
file_tree = []
results: List[SearchResult] = []
for file_node in self._flatten_files(file_tree):
file_name = str(file_node.get("n") or file_node.get("name")
or "").strip()
file_url = str(file_node.get("l") or file_node.get("link")
or "").strip()
relpath = str(file_node.get("_relpath") or file_name or "").strip()
file_size = file_node.get("s") or file_node.get("size")
if not file_name or not file_url:
continue
if needle and needle not in file_name.lower():
continue
size_bytes: Optional[int] = None
try:
if isinstance(file_size, (int, float)):
size_bytes = int(file_size)
elif isinstance(file_size, str) and file_size.isdigit():
size_bytes = int(file_size)
except Exception:
size_bytes = None
results.append(
SearchResult(
table="alldebrid",
title=file_name,
path=file_url,
detail=magnet_name,
annotations=["file"],
media_kind="file",
size_bytes=size_bytes,
tag={"alldebrid",
"file",
str(magnet_id)},
columns=[
("File",
file_name),
("Folder",
magnet_name),
("ID",
str(magnet_id)),
],
full_metadata={
"magnet": magnet_status,
"magnet_id": magnet_id,
"magnet_name": magnet_name,
"relpath": relpath,
"file": file_node,
"provider": "alldebrid",
"provider_view": "files",
},
)
)
if len(results) >= max(1, limit):
break
return results
# Default: folders view (magnets)
try:
magnets = client.magnet_list() or []
except Exception as exc:
log(f"[alldebrid] Failed to list account magnets: {exc}", file=sys.stderr)
return []
wanted_id: Optional[int] = None
if needle.isdigit():
try:
wanted_id = int(needle)
except Exception:
wanted_id = None
results: List[SearchResult] = []
for magnet in magnets:
if not isinstance(magnet, dict):
continue
try:
magnet_id = int(magnet.get("id"))
except Exception:
continue
magnet_name = str(
magnet.get("filename") or magnet.get("name") or magnet.get("hash")
or f"magnet-{magnet_id}"
)
magnet_name_lower = magnet_name.lower()
status_text = str(magnet.get("status") or "").strip() or "unknown"
status_code = magnet.get("statusCode")
ready = status_code == 4 or bool(magnet.get("ready"))
if wanted_id is not None:
if magnet_id != wanted_id:
continue
elif needle and (needle not in magnet_name_lower):
continue
size_bytes: Optional[int] = None
try:
size_val = magnet.get("size")
if isinstance(size_val, (int, float)):
size_bytes = int(size_val)
elif isinstance(size_val, str) and size_val.isdigit():
size_bytes = int(size_val)
except Exception:
size_bytes = None
results.append(
SearchResult(
table="alldebrid",
title=magnet_name,
path=f"alldebrid:magnet:{magnet_id}",
detail=status_text,
annotations=["folder"],
media_kind="folder",
size_bytes=size_bytes,
tag={"alldebrid",
"folder",
str(magnet_id)}
| ({"ready"} if ready else {"not-ready"}),
columns=[
("Folder",
magnet_name),
("ID",
str(magnet_id)),
("Status",
status_text),
("Ready",
"yes" if ready else "no"),
],
full_metadata={
"magnet": magnet,
"magnet_id": magnet_id,
"provider": "alldebrid",
"provider_view": "folders",
"magnet_name": magnet_name,
},
)
)
if len(results) >= max(1, limit):
break
return results
def selector(
self,
selected_items: List[Any],
*,
ctx: Any,
stage_is_last: bool = True,
**_kwargs: Any,
) -> bool:
"""Handle AllDebrid `@N` selection by drilling into magnet files."""
if not stage_is_last:
return False
def _as_payload(item: Any) -> Dict[str, Any]:
if isinstance(item, dict):
return dict(item)
try:
if hasattr(item, "to_dict"):
maybe = item.to_dict() # type: ignore[attr-defined]
if isinstance(maybe, dict):
return maybe
except Exception:
pass
payload: Dict[str, Any] = {}
try:
payload = {
"title": getattr(item, "title", None),
"path": getattr(item, "path", None),
"table": getattr(item, "table", None),
"annotations": getattr(item, "annotations", None),
"media_kind": getattr(item, "media_kind", None),
"full_metadata": getattr(item, "full_metadata", None),
}
except Exception:
payload = {}
return payload
chosen: List[Dict[str, Any]] = []
for item in selected_items or []:
payload = _as_payload(item)
meta = payload.get("full_metadata") or payload.get("metadata") or {}
if not isinstance(meta, dict):
meta = {}
ann_set: set[str] = set()
for ann_source in (payload.get("annotations"), meta.get("annotations")):
if isinstance(ann_source, (list, tuple, set)):
for ann in ann_source:
ann_text = str(ann or "").strip().lower()
if ann_text:
ann_set.add(ann_text)
media_kind = str(payload.get("media_kind") or meta.get("media_kind") or "").strip().lower()
is_folder = (media_kind == "folder") or ("folder" in ann_set)
magnet_id = meta.get("magnet_id")
if magnet_id is None or (not is_folder):
continue
title = str(payload.get("title") or meta.get("magnet_name") or meta.get("name") or "").strip()
if not title:
title = f"magnet-{magnet_id}"
chosen.append({
"magnet_id": magnet_id,
"title": title,
})
if not chosen:
return False
target = chosen[0]
magnet_id = target.get("magnet_id")
title = target.get("title") or f"magnet-{magnet_id}"
try:
files = self.search("*", limit=200, filters={"view": "files", "magnet_id": magnet_id})
except Exception as exc:
print(f"alldebrid selector failed: {exc}\n")
return True
try:
from SYS.result_table import ResultTable
from SYS.rich_display import stdout_console
except Exception:
return True
table = ResultTable(f"AllDebrid Files: {title}").set_preserve_order(True)
table.set_table("alldebrid")
try:
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", "*"],
)
results_payload: List[Dict[str, Any]] = []
for r in files or []:
table.add_result(r)
try:
results_payload.append(r.to_dict())
except Exception:
results_payload.append(
{
"table": getattr(r, "table", "alldebrid"),
"title": getattr(r, "title", ""),
"path": getattr(r, "path", ""),
"full_metadata": getattr(r, "full_metadata", None),
}
)
try:
ctx.set_last_result_table(table, results_payload)
ctx.set_current_stage_table(table)
except Exception:
pass
try:
stdout_console().print()
stdout_console().print(table)
except Exception:
pass
return True