update
This commit is contained in:
@@ -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",
|
||||
|
||||
@@ -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
@@ -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
@@ -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}"),
|
||||
|
||||
Reference in New Issue
Block a user