This commit is contained in:
2026-01-31 23:22:30 -08:00
parent ed44d69ef1
commit 753cdfb196
6 changed files with 454 additions and 35 deletions

View File

@@ -4,6 +4,8 @@ import hashlib
import json
import sys
import time
import shutil
import tempfile
from pathlib import Path
from typing import Any, Dict, Iterable, List, Optional, Callable, Tuple
from urllib.parse import urlparse
@@ -14,7 +16,7 @@ from ProviderCore.base import Provider, SearchResult
from SYS.provider_helpers import TableProviderMixin
from SYS.utils import sanitize_filename
from SYS.logger import log, debug
from SYS.models import DownloadError
from SYS.models import DownloadError, PipeObject
_HOSTS_CACHE_TTL_SECONDS = 24 * 60 * 60
@@ -273,13 +275,22 @@ def _parse_alldebrid_magnet_id(target: str) -> Optional[int]:
candidate = str(target or "").strip()
if not candidate:
return None
if not candidate.lower().startswith(_ALD_MAGNET_PREFIX):
return None
try:
magnet_id_raw = candidate[len(_ALD_MAGNET_PREFIX):].strip()
return int(magnet_id_raw)
except Exception:
return None
# Handle various prefix variations: alldebrid:magnet: (standard), alldebrid🧲, or alldebrid:
id_part = None
if candidate.lower().startswith(_ALD_MAGNET_PREFIX):
id_part = candidate[len(_ALD_MAGNET_PREFIX):].strip()
elif candidate.startswith("alldebrid🧲"):
id_part = candidate[len("alldebrid🧲"):].strip()
elif candidate.lower().startswith("alldebrid:"):
id_part = candidate[len("alldebrid:"):].strip()
if id_part:
try:
return int(id_part)
except Exception:
return None
return None
def resolve_magnet_spec(target: str) -> Optional[str]:
@@ -302,6 +313,50 @@ def resolve_magnet_spec(target: str) -> Optional[str]:
return None
def _looks_like_torrent_source(candidate: str) -> bool:
value = str(candidate or "").strip()
if not value:
return False
def _clean_segment(text: str) -> str:
cleaned = text.split("#", 1)[0].split("?", 1)[0]
return cleaned.strip().lower()
paths: list[str] = []
try:
parsed = urlparse(value)
except Exception:
parsed = None
if parsed and parsed.path:
paths.append(_clean_segment(parsed.path))
paths.append(_clean_segment(value))
for path in paths:
if path and is_torrent_file(path):
return True
return False
def _extract_value(source: Any, field: str) -> Any:
if source is None:
return None
if isinstance(source, dict):
if field in source:
return source.get(field)
else:
try:
value = getattr(source, field)
except Exception:
value = None
if value is not None:
return value
extra = getattr(source, "extra", None)
if isinstance(extra, dict) and field in extra:
return extra.get(field)
return None
def _dispatch_alldebrid_magnet_search(
magnet_id: int,
config: Dict[str, Any],
@@ -581,7 +636,7 @@ class AllDebrid(TableProviderMixin, Provider):
# Magnet URIs should be routed through this provider.
TABLE_AUTO_STAGES = {"alldebrid": ["download-file"]}
AUTO_STAGE_USE_SELECTION_ARGS = True
URL = ("magnet:", "alldebrid:magnet:")
URL = ("magnet:", "alldebrid:magnet:", "alldebrid:", "alldebrid🧲")
URL_DOMAINS = ()
@classmethod
@@ -662,6 +717,8 @@ class AllDebrid(TableProviderMixin, Provider):
spec = resolve_magnet_spec(url)
if not spec:
if _looks_like_torrent_source(url):
log(f"[alldebrid] Torrent source ignored (unable to parse magnet/hash): {url}", file=sys.stderr)
return False, None
cfg = self.config if isinstance(self.config, dict) else {}
@@ -869,6 +926,106 @@ class AllDebrid(TableProviderMixin, Provider):
except Exception:
return None
@classmethod
def download_for_pipe_result(
cls,
result: Any,
pipe_obj: Optional[PipeObject],
config: Dict[str, Any],
) -> Tuple[Optional[Path], Optional[str], Optional[Path]]:
"""Download a remote provider result on behalf of add-file."""
download_url = (
_extract_value(result, "path")
or (getattr(pipe_obj, "url", None) if pipe_obj is not None else None)
)
download_url = str(download_url or "").strip()
if not download_url:
return None, None, None
metadata: Dict[str, Any] = {}
maybe_meta = _extract_value(result, "full_metadata") or _extract_value(result, "metadata")
if isinstance(maybe_meta, dict):
metadata.update(maybe_meta)
if pipe_obj is not None:
pipe_meta = getattr(pipe_obj, "metadata", None)
if isinstance(pipe_meta, dict):
metadata.update(pipe_meta)
table_name = str(
_extract_value(result, "table")
or (getattr(pipe_obj, "provider", None) if pipe_obj is not None else None)
or "alldebrid"
).strip()
if not table_name:
table_name = "alldebrid"
title = str(
_extract_value(result, "title")
or (getattr(pipe_obj, "title", None) if pipe_obj is not None else None)
or metadata.get("name")
or ""
).strip()
if not title:
fallback_name = None
parsed_url = None
try:
parsed_url = urlparse(download_url)
except Exception:
parsed_url = None
if parsed_url and parsed_url.path:
try:
fallback_name = Path(parsed_url.path).stem
except Exception:
fallback_name = None
title = fallback_name or "alldebrid"
media_kind = str(
_extract_value(result, "media_kind")
or metadata.get("media_kind")
or "file"
).strip() or "file"
search_result = SearchResult(
table=table_name,
title=title,
path=download_url,
detail=str(_extract_value(result, "detail") or ""),
annotations=[],
media_kind=media_kind,
tag=set(getattr(pipe_obj, "tag", []) or []),
columns=[],
full_metadata=metadata,
)
if not download_url.startswith(("http://", "https://")):
return None, None, None
download_dir = Path(tempfile.mkdtemp(prefix="add-file-alldebrid-"))
try:
provider = cls(config)
downloaded_path = provider.download(search_result, download_dir)
if not downloaded_path:
shutil.rmtree(download_dir, ignore_errors=True)
return None, None, None
if pipe_obj is not None:
pipe_obj.is_temp = True
hash_hint = (
_extract_value(result, "hash")
or _extract_value(result, "file_hash")
or (getattr(pipe_obj, "hash", None) if pipe_obj is not None else None)
)
if not hash_hint:
for candidate in ("hash", "file_hash", "info_hash"):
candidate_val = metadata.get(candidate)
if isinstance(candidate_val, str) and candidate_val.strip():
hash_hint = candidate_val.strip()
break
return downloaded_path, hash_hint, download_dir
except Exception as exc:
log(f"[alldebrid] add-file download failed: {exc}", file=sys.stderr)
shutil.rmtree(download_dir, ignore_errors=True)
return None, None, None
def download_items(
self,
result: SearchResult,
@@ -880,28 +1037,33 @@ class AllDebrid(TableProviderMixin, 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)
# Check if this is a direct magnet_id from the account (from selector or custom URL scheme)
path_id = _parse_alldebrid_magnet_id(getattr(result, "path", ""))
full_metadata = getattr(result, "full_metadata", None) or {}
magnet_id_direct = None
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}")
active_id = magnet_id_direct if magnet_id_direct is not None else path_id
if active_id is not None:
try:
magnet_id = int(active_id)
debug(f"[download_items] Found magnet_id {magnet_id}, 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: