{"record":{"id":"61f0be43b2e2c625","repo":"tonhowtf/omniget","slug":"no-url-available-legacy","errorCode":null,"errorMessage":"No URL available","messagePattern":"No URL available","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src-tauri/src/platforms/bilibili/legacy.rs","lineNumber":184,"sourceCode":"    progress: mpsc::Sender<ProgressUpdate>,\n) -> anyhow::Result<DownloadResult> {\n    let _ = progress.send(ProgressUpdate::percent(0.0)).await;\n\n    let ytdlp_path = match &opts.ytdlp_path {\n        Some(p) => p.clone(),\n        None => ytdlp::find_ytdlp_cached()\n            .await\n            .ok_or_else(|| anyhow!(\"yt-dlp not found\"))?,\n    };\n\n    let url = info\n        .available_qualities\n        .first()\n        .map(|q| q.url.as_str())\n        .unwrap_or(\"\");\n\n    if url.is_empty() {\n        return Err(anyhow!(\"No URL available\"));\n    }\n\n    if info.media_type == MediaType::Playlist {\n        return download_playlist(info, opts, progress, &ytdlp_path).await;\n    }\n\n    let quality_height = opts\n        .quality\n        .as_ref()\n        .and_then(|q| q.trim_end_matches('p').parse::<u32>().ok());\n\n    let extra = vec![\"--no-playlist\".to_string()];\n\n    ytdlp::download_video(\n        &ytdlp_path,\n        url,\n        &opts.output_dir,\n        quality_height,","sourceCodeStart":166,"sourceCodeEnd":202,"githubUrl":"https://github.com/tonhowtf/omniget/blob/8600b91f4246848bac346874daa9e61c1fc5677a/src-tauri/src/platforms/bilibili/legacy.rs#L166-L202","documentation":"`download` takes the first entry of `info.available_qualities` and treats its `url` as the media stream; if the qualities list is empty or the first quality's URL is an empty string it throws `No URL available`. This protects against launching yt-dlp with a blank target.","triggerScenarios":"Passing a MediaInfo produced by a failed/incomplete `get_media_info` call where `available_qualities` is empty, or where quality entries were built with empty `url` fields (e.g. extraction returned metadata but no stream URLs, or a hand-constructed MediaInfo).","commonSituations":"Chaining get_media_info → download without checking that qualities were populated; Bilibili returning metadata but stream extraction blocked (login/region); constructing MediaInfo manually in tests/fixtures with placeholder qualities.","solutions":["Check `info.available_qualities` is non-empty and the first URL is non-blank before calling download.","Re-run `get_media_info` to refresh stream URLs — they can expire and extraction may have failed silently.","Ensure the user is logged in / cookies are configured when streams require authentication.","Pick a specific quality explicitly rather than relying on `.first()` defaults."],"exampleFix":"// before\ndownload(info, opts, progress).await?; // panics if qualities empty\n// after\nif info.available_qualities.first().map(|q| q.url.is_empty()).unwrap_or(true) {\n    info = get_media_info(&source_url).await?;\n}\ndownload(info, opts, progress).await?;","handlingStrategy":"validation","validationCode":"fn has_stream(info: &MediaInfo) -> bool {\n    info.available_qualities.first().map(|q| !q.url.is_empty()).unwrap_or(false)\n}","typeGuard":null,"tryCatchPattern":"match download(info, opts, progress).await {\n    Err(e) if e.to_string().contains(\"No URL available\") => refresh_info_and_retry().await?,\n    Err(e) => return Err(e),\n    Ok(_) => {},\n}","preventionTips":["Check available_qualities non-empty and first URL non-blank before downloading.","Fetch MediaInfo immediately before download so stream URLs are fresh.","Ensure login/cookies are set for streams that require auth.","Avoid hand-constructing MediaInfo with placeholder URLs in production code."],"tags":["download","state","bilibili"],"backgroundTag":"empty-required-field","analyzedSha":"8600b91f4246848bac346874daa9e61c1fc5677a","analyzedAt":"2026-09-12T14:29:19.317Z","contentChangedAt":"2026-09-12T14:29:19.317Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}