d
This commit is contained in:
296
Provider/alldebrid.py
Normal file
296
Provider/alldebrid.py
Normal file
@@ -0,0 +1,296 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from typing import Any, Dict, Iterable, List, Optional
|
||||
|
||||
from ProviderCore.base import SearchProvider, SearchResult
|
||||
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 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
|
||||
|
||||
|
||||
class AllDebrid(SearchProvider):
|
||||
"""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 {}))
|
||||
|
||||
@staticmethod
|
||||
def _flatten_files(items: Any) -> Iterable[Dict[str, Any]]:
|
||||
"""Flatten AllDebrid magnet file tree into file dicts.
|
||||
|
||||
API commonly returns:
|
||||
- file: {n: name, s: size, l: link}
|
||||
- folder: {n: name, e: [sub_items]}
|
||||
|
||||
Some call sites in this repo also expect {name, size, link}, so we accept both.
|
||||
"""
|
||||
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):
|
||||
yield from AllDebrid._flatten_files(children)
|
||||
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():
|
||||
yield node
|
||||
|
||||
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},
|
||||
)
|
||||
]
|
||||
|
||||
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()
|
||||
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, "file": file_node},
|
||||
)
|
||||
)
|
||||
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},
|
||||
)
|
||||
)
|
||||
|
||||
if len(results) >= max(1, limit):
|
||||
break
|
||||
|
||||
return results
|
||||
Reference in New Issue
Block a user