tonhowtf/omniget · error

o arquivo baixado não parece o export do Goodreads

Error message

o arquivo baixado não parece o export do Goodreads

What it means

This error is thrown by trigger_and_wait in the Goodreads export tool after it downloads the CSV generated by Goodreads' import/export pipeline. The tool polls until the export is ready, fetches the file, and sanity-checks that the body contains the string 'title' (a required column header in a Goodreads library export). If the downloaded text does not look like a Goodreads CSV, it assumes something else came back (HTML error page, login page, empty export) and throws this anyhow error.

Solutions

  1. Check that the Goodreads session cookies are valid: re-capture cookies from goodreads.com in the browser extension and pass them via opts.session_netscape.
  2. Trigger the export manually at goodreads.com/review/import and download goodreads_library_export.csv, then pass its path via opts.csv_path so the download/poll path is skipped entirely.
  3. Increase opts.delay_ms / re-run to give Goodreads more time to finish generating the export before the fetch.
  4. Inspect what was actually downloaded (log the first bytes of csv) to see if it is an HTML page or an empty file and adjust accordingly.

Example fix

// before: rely solely on polling the generated export
let (csv, secs) = trigger_and_wait(&f, opts, &p).await?;
// after: prefer an existing local export when present
if let Some(path) = opts.csv_path.as_deref().map(str::trim) {
    let csv = std::fs::read_to_string(path)?;
    ...
} else {
    let (csv, secs) = trigger_and_wait(&f, opts, &p).await?;
    ...
}
Defensive patterns

Strategy: validation

Validate before calling

if let Some(path) = &opts.csv_path {
    let head = std::fs::read_to_string(path)?;
    anyhow::ensure!(head.to_lowercase().contains("title"), "arquivo não parece export do Goodreads");
}

Type guard

fn looks_like_goodreads_csv(body: &str) -> bool {
    body.to_lowercase().contains("title")
}

Try / catch

match tool::run(&opts, &p).await {
    Ok(res) => println!("export ok: {}", res.output_dir),
    Err(e) if e.to_string().contains("export do Goodreads") => {
        eprintln!("Goodreads devolveu algo inesperado; re-capture os cookies ou gere o CSV manualmente");
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Calling run without a local CSV path so trigger_and_wait waits for Goodreads to generate the export, then f.get_bytes(&csv_url) returns content whose lowercase form lacks 'title' — e.g. Goodreads returned an HTML/login/error page at the export URL, the export job produced an empty or partial file, or the poll loop timed out and grabbed a not-yet-ready resource.

Common situations: Goodreads export job never completed but the URL was fetched anyway; the session cookies expired so Goodreads served a sign-in page; Goodreads changed its export format or column names; network middleware (proxy/CAPTCHA page) intercepted the download.

Related errors


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

Appendix: source

Thrown at src-tauri/omniget-core/src/core/tools/lists/goodreads.rs:326

                "o Goodreads ainda não gerou o CSV depois de {}s. Deixe goodreads.com/review/import aberto, espere e rode de novo — o link fica salvo lá",
                limit.as_secs()
            ));
        }
        report(
            p,
            TOOL_ID,
            "progress",
            started.elapsed().as_secs(),
            Some(limit.as_secs()),
            Some("esperando o Goodreads gerar o CSV".into()),
        );
        tokio::time::sleep(Duration::from_secs(10)).await;
    }

    let (bytes, _) = f.get_bytes(&csv_url).await?;
    let csv = String::from_utf8_lossy(&bytes).to_string();
    if !csv.to_lowercase().contains("title") {
        return Err(anyhow!(
            "o arquivo baixado não parece o export do Goodreads"
        ));
    }
    Ok((csv, started.elapsed().as_secs()))
}

/// A página devolvida quando a sessão não vale: o Goodreads responde 200 com
/// a tela de entrada em vez de mandar para outro lugar.
pub fn is_signed_out(html: &str) -> bool {
    let has_form = html.contains("review_porter") || html.contains("js-LibraryExport");
    !has_form && (html.contains("/user/sign_in") || html.contains("/user/new"))
}

// ── Prateleira na web ───────────────────────────────────────────────────

pub fn shelf_url(user: &str, shelf: &str, page: u32) -> String {
    format!(
        "https://www.goodreads.com/review/list/{}?shelf={}&per_page=100&page={}&print=true",

View on GitHub (pinned to 8600b91f42)