{"record":{"id":"ad902273c393d977","repo":"tonhowtf/omniget","slug":"n-o-achei-o-token-anti-csrf-do-goodreads-recapture-os","errorCode":null,"errorMessage":"não achei o token anti-CSRF do Goodreads; recapture os cookies e tente de novo","messagePattern":"não achei o token anti-CSRF do Goodreads; recapture os cookies e tente de novo","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src-tauri/omniget-core/src/core/tools/lists/goodreads.rs","lineNumber":264,"sourceCode":"async fn trigger_and_wait(f: &Fetcher, opts: &Options, p: &ProgressFn) -> Result<(String, u64)> {\n    let page = f.get_text(IMPORT_URL).await?;\n    if is_signed_out(&page) {\n        return Err(anyhow!(\n            \"a sessão do Goodreads não está válida: capture os cookies de goodreads.com na extensão\"\n        ));\n    }\n    let uid = user_id(&page).ok_or_else(|| {\n        anyhow!(\"não achei o id da sua conta na página de importação do Goodreads\")\n    })?;\n    let csv_url = export_csv_url(&uid);\n\n    // Se já existe um export pronto, aproveita: o Goodreads só deixa gerar um\n    // a cada poucos dias, e gerar um novo apaga o anterior.\n    let ready = !opts.force && export_link(&page).is_some();\n    let started = Instant::now();\n    if !ready {\n        let token = csrf_token(&page).ok_or_else(|| {\n            anyhow!(\n                \"não achei o token anti-CSRF do Goodreads; recapture os cookies e tente de novo\"\n            )\n        })?;\n        f.pace().await;\n        let resp = f\n            .client()\n            .post(export_post_url(&uid))\n            .header(reqwest::header::REFERER, IMPORT_URL)\n            .header(reqwest::header::ORIGIN, \"https://www.goodreads.com\")\n            .header(reqwest::header::ACCEPT, \"*/*\")\n            .header(\"X-Requested-With\", \"XMLHttpRequest\")\n            .header(\"X-CSRF-Token\", token)\n            .header(\n                reqwest::header::CONTENT_TYPE,\n                \"application/x-www-form-urlencoded\",\n            )\n            .body(\"format=json\")\n            .send()","sourceCodeStart":246,"sourceCodeEnd":282,"githubUrl":"https://github.com/tonhowtf/omniget/blob/8600b91f4246848bac346874daa9e61c1fc5677a/src-tauri/omniget-core/src/core/tools/lists/goodreads.rs#L246-L282","documentation":"When no ready export link exists on the page (or opts.force is set), trigger_and_wait must POST to /review_porter/export/<id>, which requires the anti-CSRF token embedded in the import page. If csrf_token() fails to extract it, the export cannot be triggered and this error is returned, advising a cookie recapture.","triggerScenarios":"csrf_token() returning None on the fetched page: the token's markup attribute changed, the page variant served doesn't include the token, or the session is semi-valid (signed in but page rendered without form token).","commonSituations":"Goodreads rotating or renaming the CSRF input/meta element; stale cookies producing a degraded page; locale-specific page variants omitting the token field; forced re-export on a page variant that omits the form.","solutions":["Recapture goodreads.com cookies with the extension while logged in, then rerun.","Retry shortly after — if Goodreads served an A/B or degraded page once, a fresh fetch usually includes the token.","If persistent, update the csrf_token() extraction pattern to match Goodreads' current markup (check the import page's HTML for the token input/meta).","Skip the forced export if a ready export link exists (run without force) — the ready path does not need the CSRF token."],"exampleFix":"// before\nlet opts = Options { force: true, .. };\nrun(&fetcher, &opts).await?; // csrf token missing\n\n// after: reuse existing export when possible\nlet opts = Options { force: false, .. };\nrun(&fetcher, &opts).await?;","handlingStrategy":"fallback","validationCode":"let page = fetcher.get_text(IMPORT_URL).await?;\nif csrf_token(&page).is_none() && export_link(&page).is_none() {\n    prompt_cookie_recapture(); // neither a token nor an existing export is usable\n}","typeGuard":null,"tryCatchPattern":"match run(&fetcher, &opts).await {\n    Err(e) if e.to_string().contains(\"anti-CSRF\") => {\n        // fall back to reusing existing export instead of forcing a new one\n        run(&fetcher, &Options { force: false, ..opts }).await?;\n    }\n    r => r?,\n}","preventionTips":["Avoid force=true unless an export is genuinely stale","Recapture cookies when the token extraction fails","Prefer the ready-export path whenever a link exists on the page"],"tags":["csrf","goodreads","scraping","authentication"],"backgroundTag":"unexpected-response-shape","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"}