tonhowtf/omniget · error · anyhow::Error

o servidor respondeu algo que não é JSON

Error message

o servidor respondeu algo que não é JSON ({}): {}

What it means

get_json() strips Medium's anti-hijack JSON prefix (])}while(1);</x>) then parses with serde_json. If parsing still fails, the body was not the expected JSON (HTML error page, empty body, login redirect page), and it reports the serde error plus the URL.

Solutions

  1. Re-capture medium.com cookies — an HTML login page usually means the session expired
  2. Open the URL in the error in a browser to see what the server actually returns
  3. Retry later if the site is serving a transient error page
  4. Update parsing if Medium changed its JSON envelope; check strip_json_prefix handling
Defensive patterns

Strategy: try-catch

Try / catch

match fetcher.get_json(url).await {
    Err(e) if e.to_string().contains("não é JSON") => {
        // likely HTML login/error page: refresh session then retry
        refresh_cookies();
        return fetcher.get_json(url).await;
    }
    other => other,
}

Prevention

When it happens

Trigger: A GET via get_json returns HTTP 200 but the body after strip_json_prefix is not valid JSON — e.g. serde_json::from_str fails — thrown to all callers (from_session, discover, export_publication, fetch_post, expand).

Common situations: Expired session causing Medium to return an HTML login/error page with 200 status; a captcha or consent page; endpoint changed its response format; truncated response body from a proxy.

Understand the failure class

Background: "Invalid JSON response" and "Failed to parse response" errors: when an API answers 200 but the body isn't the JSON your library expected — this error's family across 28 libraries.

Related errors


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

Appendix: source

Thrown at src-tauri/omniget-core/src/core/tools/blogs/mod.rs:204

                }
                Ok(r) => return Err(anyhow!("HTTP {} em {}", r.status(), url)),
                Err(e) if attempt < TRIES => {
                    tokio::time::sleep(wait).await;
                    wait *= 2;
                    let _ = e;
                }
                Err(e) => return Err(e.into()),
            }
        }
        Err(anyhow!("não foi possível ler {}", url))
    }

    /// GET com JSON de volta. Tolera o prefixo anti-sequestro do Medium.
    pub async fn get_json(&self, url: &str) -> Result<serde_json::Value> {
        let text = self.get_text(url).await?;
        let body = strip_json_prefix(&text);
        serde_json::from_str(body)
            .map_err(|e| anyhow!("o servidor respondeu algo que não é JSON ({}): {}", e, url))
    }
}

/// O Medium serve JSON prefixado com `])}while(1);</x>` para que ninguém
/// consiga incluir a resposta como `<script>`. É lixo antes do primeiro `{`
/// ou `[`; cortar é obrigatório antes de parsear.
pub fn strip_json_prefix(text: &str) -> &str {
    let t = text.trim_start();
    if t.starts_with('{') || t.starts_with('[') {
        return t;
    }
    match t.find(['{', '[']) {
        Some(i) => &t[i..],
        None => t,
    }
}

// ── HTML → Markdown ────────────────────────────────────────────────────

View on GitHub (pinned to 8600b91f42)