your commit message

This commit is contained in:
2026-02-25 17:35:38 -08:00
parent 39a84b3274
commit 834be06ab9
12 changed files with 517 additions and 543 deletions

View File

@@ -87,10 +87,9 @@ def _load_root_modules() -> None:
def _load_helper_modules() -> None:
try:
import API.alldebrid as _alldebrid
except Exception:
pass
# Provider-specific module pre-loading removed; providers are loaded lazily
# through ProviderCore.registry when first referenced.
pass
def _register_native_commands() -> None:

View File

@@ -965,6 +965,48 @@ def normalize_hash(hash_hex: Optional[str]) -> Optional[str]:
return text
def resolve_hash_for_cmdlet(
raw_hash: Optional[str],
raw_path: Optional[str],
override_hash: Optional[str],
) -> Optional[str]:
"""Resolve a file hash for note/tag/file cmdlets.
Shared implementation used by add-note, delete-note, get-note, and similar
cmdlets that need to identify a file by its SHA-256 hash.
Resolution order:
1. ``override_hash`` — explicit hash provided via *-query* (highest priority)
2. ``raw_hash`` — positional hash argument
3. ``raw_path`` stem — if the filename stem is a 64-char hex string it is
treated directly as the hash (Hydrus-style naming convention)
4. SHA-256 computed from the file at ``raw_path``
Args:
raw_hash: Hash string from positional argument.
raw_path: Filesystem path to the file (may be None).
override_hash: Hash extracted from *-query* (takes precedence).
Returns:
Normalised 64-char lowercase hex hash, or ``None`` if unresolvable.
"""
resolved = normalize_hash(override_hash) if override_hash else normalize_hash(raw_hash)
if resolved:
return resolved
if raw_path:
try:
p = Path(str(raw_path))
stem = p.stem
if len(stem) == 64 and all(c in "0123456789abcdef" for c in stem.lower()):
return stem.lower()
if p.exists() and p.is_file():
from SYS.utils import sha256_file as _sha256_file
return _sha256_file(p)
except Exception:
return None
return None
def parse_hash_query(query: Optional[str]) -> List[str]:
"""Parse a unified query string for `hash:` into normalized SHA256 hashes.

View File

@@ -44,6 +44,13 @@ SUPPORTED_MEDIA_EXTENSIONS = ALL_SUPPORTED_EXTENSIONS
DEBUG_PIPE_NOTE_PREVIEW_LENGTH = 256
# Protocol schemes that identify a remote resource / not a local file path.
# Used by multiple methods in this file to guard against URL strings being
# treated as local file paths.
_REMOTE_URL_PREFIXES: tuple[str, ...] = (
"http://", "https://", "magnet:", "torrent:", "tidal:", "hydrus:",
)
def _truncate_debug_note_text(value: Any) -> str:
raw = str(value or "")
@@ -1203,7 +1210,7 @@ class Add_File(Cmdlet):
if candidate:
s = str(candidate).lower()
if s.startswith(("http://", "https://", "magnet:", "torrent:", "tidal:", "hydrus:")):
if s.startswith(_REMOTE_URL_PREFIXES):
log("add-file ingests local files only. Use download-file first.", file=sys.stderr)
return None, None, None
@@ -1427,7 +1434,7 @@ class Add_File(Cmdlet):
if not val:
return False
# Obvious schemes
if val.startswith(("http://", "https://", "magnet:", "torrent:", "tidal:", "hydrus:")):
if val.startswith(_REMOTE_URL_PREFIXES):
return True
# Domain-like patterns or local file paths (but we want URLs here)
if "://" in val:

View File

@@ -175,25 +175,9 @@ class Add_Note(Cmdlet):
self,
raw_hash: Optional[str],
raw_path: Optional[str],
override_hash: Optional[str]
override_hash: Optional[str],
) -> Optional[str]:
resolved = normalize_hash(override_hash
) if override_hash else normalize_hash(raw_hash)
if resolved:
return resolved
if raw_path:
try:
p = Path(str(raw_path))
stem = p.stem
if len(stem) == 64 and all(c in "0123456789abcdef"
for c in stem.lower()):
return stem.lower()
if p.exists() and p.is_file():
return sha256_file(p)
except Exception:
return None
return None
return sh.resolve_hash_for_cmdlet(raw_hash, raw_path, override_hash)
def run(self, result: Any, args: Sequence[str], config: Dict[str, Any]) -> int:
if should_show_help(args):

View File

@@ -54,24 +54,9 @@ class Delete_Note(Cmdlet):
self,
raw_hash: Optional[str],
raw_path: Optional[str],
override_hash: Optional[str]
override_hash: Optional[str],
) -> Optional[str]:
resolved = normalize_hash(override_hash
) if override_hash else normalize_hash(raw_hash)
if resolved:
return resolved
if raw_path:
try:
p = Path(str(raw_path))
stem = p.stem
if len(stem) == 64 and all(c in "0123456789abcdef"
for c in stem.lower()):
return stem.lower()
if p.exists() and p.is_file():
return sha256_file(p)
except Exception:
return None
return None
return sh.resolve_hash_for_cmdlet(raw_hash, raw_path, override_hash)
def run(self, result: Any, args: Sequence[str], config: Dict[str, Any]) -> int:
if should_show_help(args):

View File

@@ -54,6 +54,10 @@ resolve_target_dir = sh.resolve_target_dir
coerce_to_path = sh.coerce_to_path
build_pipeline_preview = sh.build_pipeline_preview
# URI scheme prefixes owned by AllDebrid (magic-link and emoji shorthand).
# Defined once here so every method in this file references the same constant.
_ALLDEBRID_PREFIXES: tuple[str, ...] = ("alldebrid:", "alldebrid🧲")
class Download_File(Cmdlet):
"""Class-based download-file cmdlet - direct HTTP downloads."""
@@ -652,9 +656,12 @@ class Download_File(Cmdlet):
notes: Optional[Dict[str, str]] = None
try:
if isinstance(full_metadata, dict):
subtitles = full_metadata.get("_tidal_lyrics_subtitles")
if isinstance(subtitles, str) and subtitles.strip():
notes = {"lyric": subtitles}
# Providers attach pre-built notes under the generic "_notes" key
# (e.g. Tidal sets {"lyric": subtitles} during download enrichment).
# This keeps provider-specific metadata handling inside the provider.
_provider_notes = full_metadata.get("_notes")
if isinstance(_provider_notes, dict) and _provider_notes:
notes = {str(k): str(v) for k, v in _provider_notes.items() if k and v}
except Exception:
notes = None
tag: List[str] = []
@@ -2787,7 +2794,9 @@ class Download_File(Cmdlet):
s_val = str(value or "").strip().lower()
except Exception:
return False
return s_val.startswith(("http://", "https://", "magnet:", "torrent:", "alldebrid:", "alldebrid🧲"))
return s_val.startswith(
("http://", "https://", "magnet:", "torrent:") + _ALLDEBRID_PREFIXES
)
def _extract_selection_args(item: Any) -> tuple[Optional[List[str]], Optional[str]]:
selection_args: Optional[List[str]] = None
@@ -2955,15 +2964,13 @@ class Download_File(Cmdlet):
and (not parsed.get("path"))):
candidate = str(raw_url[0] or "").strip()
low = candidate.lower()
looks_like_url = low.startswith((
"http://", "https://", "ftp://", "magnet:", "torrent:",
"alldebrid:", "alldebrid🧲"
))
looks_like_url = low.startswith(
("http://", "https://", "ftp://", "magnet:", "torrent:") + _ALLDEBRID_PREFIXES
)
looks_like_provider = (
":" in candidate and not candidate.startswith((
"http:", "https:", "ftp:", "ftps:", "file:",
"alldebrid:"
))
":" in candidate and not candidate.startswith(
("http:", "https:", "ftp:", "ftps:", "file:") + _ALLDEBRID_PREFIXES
)
)
looks_like_windows_path = (
(len(candidate) >= 2 and candidate[1] == ":")

View File

@@ -49,24 +49,9 @@ class Get_Note(Cmdlet):
self,
raw_hash: Optional[str],
raw_path: Optional[str],
override_hash: Optional[str]
override_hash: Optional[str],
) -> Optional[str]:
resolved = normalize_hash(override_hash
) if override_hash else normalize_hash(raw_hash)
if resolved:
return resolved
if raw_path:
try:
p = Path(str(raw_path))
stem = p.stem
if len(stem) == 64 and all(c in "0123456789abcdef"
for c in stem.lower()):
return stem.lower()
if p.exists() and p.is_file():
return sha256_file(p)
except Exception:
return None
return None
return sh.resolve_hash_for_cmdlet(raw_hash, raw_path, override_hash)
def run(self, result: Any, args: Sequence[str], config: Dict[str, Any]) -> int:
if should_show_help(args):