tonhowtf/omniget · error · anyhow::Error

Failed to parse content

Error message

Failed to parse content: {}

What it means

After URL detection succeeds, `api_engine_download` calls `parser::parse(&client, &kind)` to fetch and interpret content metadata via the Bilibili API; any parser error's i18n key is wrapped in `anyhow!("Failed to parse content: {}")`. This is the top-level wrapper for all upstream API/parse failures.

Solutions

  1. Read the wrapped i18n key to identify the underlying cause (auth, not-found, rate-limit) and address it specifically.
  2. Refresh login credentials/cookies for the active account.
  3. Retry after a delay if rate-limited; use a realistic User-Agent via `opts.user_agent`.
  4. Update the app/parsers if Bilibili's API format changed.
  5. Confirm the content still exists and is publicly playable in a browser.

Example fix

// before
let parsed = parser::parse(&client, &kind).await.map_err(|e| anyhow!("Failed to parse content: {}", e.i18n_key()))?;
// after
let parsed = match parser::parse(&client, &kind).await {
    Ok(p) => p,
    Err(e) if is_auth_error(&e) => { refresh_cookies(&client).await?; parser::parse(&client, &kind).await? }
    Err(e) => return Err(anyhow!("Failed to parse content: {}", e.i18n_key())),
};
Defensive patterns

Strategy: try-catch

Validate before calling

// pre-flight: ensure account/cookies are configured before parsing
if active_account_slug().is_none() && content_requires_login(&kind) {
    return Err(anyhow!("login required"));
}

Try / catch

match download(info, opts, progress).await {
    Err(e) if e.to_string().contains("Failed to parse content") => {
        log_parse_error(&e); // inspect i18n key: auth vs not-found vs rate-limit
        maybe_refresh_credentials_and_retry().await?;
    }
    Err(e) => return Err(e),
    Ok(_) => {},
}

Prevention

When it happens

Trigger: Calling `download` where the Bilibili API request inside `parser::parse` fails — HTTP errors, rate limiting, login-required/charge-only content, deleted or private videos, API response shape changes breaking the parser, or invalid account credentials/cookies.

Common situations: Expired Bilibili cookies causing auth-required responses; region-restricted content returning unavailable; Bilibili changing its API JSON structure after an app update gap; network failures or 412/429 anti-bot responses from Bilibili.

Understand the failure class

Related errors


AI-assisted analysis of tonhowtf/omniget@8600b91f42 (2026-09-12). Data as JSON: /api/errors/98dce1cc8edf11bd. Report an issue: GitHub.

Appendix: source

Thrown at src-tauri/src/platforms/bilibili/mod.rs:125

    if url.is_empty() {
        return Err(anyhow!("No URL available"));
    }

    let slug = active_account_slug();
    let client = build_api_client(slug.as_deref(), opts.user_agent.as_deref())?;

    let mut effective_url = url.to_string();
    if url_kind::is_b23_short(&effective_url) {
        if let Ok(resolved) = url_kind::resolve_b23(&client, &effective_url).await {
            effective_url = resolved;
        }
    }

    let kind = url_kind::detect(&effective_url)
        .map_err(|e| anyhow!("Failed to detect URL kind: {}", e.i18n_key()))?;
    let parsed = parser::parse(&client, &kind)
        .await
        .map_err(|e| anyhow!("Failed to parse content: {}", e.i18n_key()))?;

    let settings = crate::storage::config::load_settings_standalone();
    let container = mux::container_from_setting(&settings.download.bilibili_container);
    let danmaku_format = danmaku_format_from_setting(&settings.download.bilibili_danmaku_format);
    let cover_format = cover::CoverFormat::from_str(&settings.download.bilibili_cover_format);
    let template_set = naming::TemplateSet {
        video: settings.download.bilibili_naming_video.clone(),
        multi_part: settings.download.bilibili_naming_multi_part.clone(),
        bangumi: settings.download.bilibili_naming_bangumi.clone(),
        cheese: settings.download.bilibili_naming_cheese.clone(),
        collection: settings.download.bilibili_naming_collection.clone(),
    };
    let first_item_owned = parsed.items.first().cloned().unwrap_or_default();
    let naming_kind = naming::classify(&kind, &first_item_owned);
    let naming_inputs = naming::NamingInputs {
        item: &first_item_owned,
        metadata: &parsed.metadata,
        parsed_title: &parsed.title,

View on GitHub (pinned to 8600b91f42)