This commit is contained in:
2026-05-16 15:03:33 -07:00
parent 717cb13dda
commit 5048729b0c
10 changed files with 1646 additions and 241 deletions
+5 -5
View File
@@ -92,7 +92,7 @@
"(hitfile\\.net/[a-z0-9A-Z]{4,9})"
],
"regexp": "(hitf\\.(to|cc)/([a-z0-9A-Z]{4,9}))|(htfl\\.(net|to|cc)/([a-z0-9A-Z]{4,9}))|(hitfile\\.(net)/download/free/([a-z0-9A-Z]{4,9}))|((hitfile\\.net/[a-z0-9A-Z]{4,9}))",
"status": true
"status": false
},
"mega": {
"name": "mega",
@@ -463,7 +463,7 @@
"isra\\.cloud/\\?op=report_file&id=([0-9a-zA-Z]{12})"
],
"regexp": "((isra\\.cloud/[0-9a-zA-Z]{12}))|(isra\\.cloud/\\?op=report_file&id=([0-9a-zA-Z]{12}))",
"status": true,
"status": false,
"hardRedirect": [
"isra\\.cloud/([0-9a-zA-Z]{12})"
]
@@ -478,11 +478,11 @@
"katfile.vip"
],
"regexps": [
"katfile\\.(cloud|online|vip|ws)/([0-9a-zA-Z]{12})",
"katfile\\.(cloud|online|vip|ws|space)/([0-9a-zA-Z]{12})",
"(katfile\\.com/[0-9a-zA-Z]{12})"
],
"regexp": "(katfile\\.(cloud|online|vip|ws)/([0-9a-zA-Z]{12}))|((katfile\\.com/[0-9a-zA-Z]{12}))",
"status": true
"regexp": "(katfile\\.(cloud|online|vip|ws|space)/([0-9a-zA-Z]{12}))|((katfile\\.com/[0-9a-zA-Z]{12}))",
"status": false
},
"mediafire": {
"name": "mediafire",
+2
View File
@@ -6535,6 +6535,7 @@ mp.register_script_message('medios-load-url-event', function(json)
if not M._reset_uosc_input_state('load-url-submit') then
_lua_log('[LOAD-URL] UOSC not loaded, cannot close menu')
end
M._schedule_uosc_cursor_resync('load-url-submit')
end
-- Close the URL prompt immediately once the user submits. Playback may still
@@ -6602,6 +6603,7 @@ mp.register_script_message('medios-load-url-event', function(json)
_lua_log('[LOAD-URL] URL is yt-dlp compatible, prefetching formats in background')
mp.add_timeout(0.5, function()
_prefetch_formats_for_url(url)
M._schedule_uosc_cursor_resync('file-loaded-web')
end)
end
end)
+175 -5
View File
@@ -895,7 +895,7 @@ def _get_playlist(silent: bool = False) -> Optional[List[Dict[str, Any]]]:
def _extract_title_from_item(item: Dict[str, Any]) -> str:
"""Extract a clean title from an MPV playlist item, handling memory:// M3U hacks."""
title = item.get("title")
filename = item.get("filename") or ""
filename = item.get("filename") or item.get("playlist-path") or ""
# Special handling for memory:// M3U playlists (used to pass titles via IPC)
if "memory://" in filename and "#EXTINF:" in filename:
@@ -923,6 +923,163 @@ def _extract_title_from_item(item: Dict[str, Any]) -> str:
return title or filename or "Unknown"
def _looks_like_raw_playlist_title(
title: Optional[str],
target: Optional[str],
) -> bool:
text = str(title or "").strip()
if not text or text == "Unknown":
return True
target_text = str(target or "").strip()
if target_text and text == target_text:
return True
lower = text.lower()
if lower.startswith(("http://", "https://", "hydrus://", "file://", "memory://")):
return True
if _WINDOWS_PATH_RE.match(text) or text.startswith("\\\\"):
return True
return False
def _resolve_hydrus_playlist_title(
target: str,
*,
store_name: Optional[str],
file_hash: Optional[str],
config: Optional[Dict[str, Any]],
) -> Optional[str]:
raw_target = str(target or "").strip()
if not raw_target:
return None
resolved_store = str(store_name or "").strip() or None
resolved_hash = str(file_hash or "").strip().lower() or None
looks_hydrus = bool(resolved_store) or bool(
_SHA256_FULL_RE.fullmatch(raw_target.lower())
) or raw_target.lower().startswith("hydrus://") or _is_hydrus_path(raw_target, None)
if not looks_hydrus:
return None
try:
hydrus_plugin = get_plugin("hydrusnetwork", config or {})
except Exception:
hydrus_plugin = None
if hydrus_plugin is None:
return None
try:
parsed_store, parsed_hash = hydrus_plugin.parse_hydrus_url(raw_target)
except Exception:
parsed_store, parsed_hash = None, ""
if not resolved_store and parsed_store:
resolved_store = str(parsed_store).strip() or None
if not resolved_hash and parsed_hash:
resolved_hash = str(parsed_hash).strip().lower() or None
if not resolved_store:
try:
inferred_store = hydrus_plugin.infer_playlist_store(
None,
target=raw_target,
file_storage=None,
)
except Exception:
inferred_store = None
if inferred_store:
resolved_store = str(inferred_store).strip() or None
if not resolved_hash:
try:
hashes = hydrus_plugin.find_hashes_by_url(
raw_target,
store_name=resolved_store,
)
except TypeError:
try:
hashes = hydrus_plugin.find_hashes_by_url(raw_target)
except Exception:
hashes = []
except Exception:
hashes = []
if isinstance(hashes, list) and hashes:
resolved_hash = str(hashes[0] or "").strip().lower() or None
if not resolved_hash:
return None
try:
resolved_title = hydrus_plugin.get_title(
resolved_hash,
store_name=resolved_store,
)
except Exception:
resolved_title = ""
title_text = str(resolved_title or "").strip()
if not title_text:
return None
if title_text.lower() in {resolved_hash, resolved_hash[:16] + "..."}:
return None
return title_text
def _resolve_playlist_display_title(
item: Dict[str, Any],
*,
config: Optional[Dict[str, Any]] = None,
file_storage: Optional[Any] = None,
store_name: Optional[str] = None,
file_hash: Optional[str] = None,
title_cache: Optional[Dict[tuple[str, str, str], Optional[str]]] = None,
) -> str:
title = _extract_title_from_item(item)
filename = item.get("filename") or item.get("playlist-path") or ""
real_path = _extract_target_from_memory_uri(filename) or filename
if not _looks_like_raw_playlist_title(title, real_path):
return title
resolved_store = str(store_name or "").strip() or None
resolved_hash = str(file_hash or "").strip().lower() or None
if not resolved_store or not resolved_hash:
extracted_store, extracted_hash = _extract_store_and_hash(
{
"store": resolved_store,
"hash": resolved_hash,
"path": real_path,
"filename": filename,
"title": title,
},
config=config,
)
if not resolved_store and extracted_store:
resolved_store = str(extracted_store).strip() or None
if not resolved_hash and extracted_hash:
resolved_hash = str(extracted_hash).strip().lower() or None
cache_key = (
str(real_path or "").strip().lower(),
str(resolved_store or "").strip().lower(),
str(resolved_hash or "").strip().lower(),
)
if title_cache is not None and cache_key in title_cache:
cached_title = title_cache[cache_key]
return cached_title or title
resolved_title = _resolve_hydrus_playlist_title(
real_path,
store_name=resolved_store,
file_hash=resolved_hash,
config=config,
)
if title_cache is not None:
title_cache[cache_key] = resolved_title
return resolved_title or title
def _extract_target_from_memory_uri(text: str) -> Optional[str]:
"""Extract the real target URL/path from a memory:// M3U payload."""
if not isinstance(text, str) or not text.startswith("memory://"):
@@ -1927,7 +2084,7 @@ def _run(result: Any, args: Sequence[str], config: Dict[str, Any]) -> int:
return 1
# Build result object with file info
title = _extract_title_from_item(current_item)
title = _resolve_playlist_display_title(current_item, config=config)
filename = current_item.get("filename", "")
# Emit the current item to pipeline
@@ -2340,7 +2497,11 @@ def _run(result: Any, args: Sequence[str], config: Dict[str, Any]) -> int:
return 1
item = items[idx]
title = _extract_title_from_item(item)
title = _resolve_playlist_display_title(
item,
config=config,
file_storage=file_storage,
)
filename = item.get("filename", "") if isinstance(item, dict) else ""
hydrus_header = _build_hydrus_header(config or {})
hydrus_url = None
@@ -2446,9 +2607,9 @@ def _run(result: Any, args: Sequence[str], config: Dict[str, Any]) -> int:
# Convert MPV items to PipeObjects with proper hash and store
pipe_objects = []
title_cache: Dict[tuple[str, str, str], Optional[str]] = {}
for i, item in enumerate(items):
is_current = item.get("current", False)
title = _extract_title_from_item(item)
filename = item.get("filename", "")
# Extract the real path/URL from memory:// wrapper if present
@@ -2458,7 +2619,7 @@ def _run(result: Any, args: Sequence[str], config: Dict[str, Any]) -> int:
store_name, file_hash = _extract_store_and_hash(
{
"path": real_path,
"title": title,
"filename": filename,
},
config=config,
)
@@ -2480,6 +2641,15 @@ def _run(result: Any, args: Sequence[str], config: Dict[str, Any]) -> int:
config=config,
)
title = _resolve_playlist_display_title(
item,
config=config,
file_storage=file_storage,
store_name=store_name,
file_hash=file_hash,
title_cache=title_cache,
)
# Build PipeObject with proper metadata
pipe_obj = PipeObject(
hash=file_hash or "unknown",
+76 -18
View File
@@ -544,6 +544,81 @@ class ytdlp(TableProviderMixin, Provider):
}
AUTO_STAGE_USE_SELECTION_ARGS = True
@staticmethod
def _playlist_entry_to_url(entry: Any, *, extractor_name: str) -> Optional[str]:
if not isinstance(entry, dict):
return None
for key in ("webpage_url", "original_url", "url"):
value = entry.get(key)
if isinstance(value, str) and value.strip():
cleaned = value.strip()
try:
if urlparse(cleaned).scheme in {"http", "https"}:
return cleaned
except Exception:
return cleaned
entry_id = entry.get("id")
if isinstance(entry_id, str) and entry_id.strip() and "youtube" in extractor_name:
return f"https://www.youtube.com/watch?v={entry_id.strip()}"
return None
def resolve_preflight_items(self, url: str, **kwargs: Any) -> Optional[List[Dict[str, Any]]]:
url_str = str(url or "").strip()
if not url_str or not is_url_supported_by_ytdlp(url_str):
return None
parsed = kwargs.get("parsed") if isinstance(kwargs.get("parsed"), dict) else {}
query_spec = parsed.get("query")
query_keyed = _parse_query_keyed_spec(str(query_spec) if query_spec is not None else None)
playlist_items = str(parsed.get("item")) if parsed.get("item") else None
item_values: List[str] = []
if isinstance(query_keyed, dict):
item_values.extend(query_keyed.get("item", []) or [])
if item_values and not playlist_items:
playlist_items = ",".join([value for value in item_values if value])
ytdlp_tool = YtDlpTool(self.config)
try:
probe = probe_url(
url_str,
no_playlist=False,
playlist_items=playlist_items,
timeout_seconds=15,
cookiefile=_cookiefile_str(ytdlp_tool),
)
except Exception:
probe = None
if not isinstance(probe, dict):
return None
entries = probe.get("entries")
if not isinstance(entries, list) or not entries:
return None
extractor_name = str(probe.get("extractor") or probe.get("extractor_key") or "").strip().lower()
items: List[Dict[str, Any]] = []
for idx, entry in enumerate(entries, 1):
entry_url = self._playlist_entry_to_url(entry, extractor_name=extractor_name)
if not entry_url:
continue
playlist_index = None
if isinstance(entry, dict):
playlist_index = entry.get("playlist_index")
try:
playlist_index_value = int(playlist_index)
except Exception:
playlist_index_value = idx
items.append(
{
"url": entry_url,
"playlist_index": playlist_index_value,
}
)
return items or None
def extract_query_arguments(self, query: str) -> Tuple[str, Dict[str, Any]]:
normalized_query, inline_args = parse_inline_query_arguments(query)
search_parts: List[str] = []
@@ -745,23 +820,6 @@ class ytdlp(TableProviderMixin, Provider):
elif "youtube" in extractor_name:
table_type = "youtube"
def _entry_to_url(entry: Any) -> Optional[str]:
if not isinstance(entry, dict):
return None
for key in ("webpage_url", "original_url", "url"):
value = entry.get(key)
if isinstance(value, str) and value.strip():
cleaned = value.strip()
try:
if urlparse(cleaned).scheme in {"http", "https"}:
return cleaned
except Exception:
return cleaned
entry_id = entry.get("id")
if isinstance(entry_id, str) and entry_id.strip() and "youtube" in extractor_name:
return f"https://www.youtube.com/watch?v={entry_id.strip()}"
return None
table = Table(preserve_order=True)
safe_url = str(url or "").strip()
table.title = f'download-file -url "{safe_url}"' if safe_url else "download-file"
@@ -781,7 +839,7 @@ class ytdlp(TableProviderMixin, Provider):
title = entry.get("title") if isinstance(entry, dict) else None
uploader = entry.get("uploader") if isinstance(entry, dict) else None
duration = entry.get("duration") if isinstance(entry, dict) else None
entry_url = _entry_to_url(entry)
entry_url = self._playlist_entry_to_url(entry, extractor_name=extractor_name)
row = build_table_result_payload(
table="download-file",
title=str(title or f"Item {idx}"),