tonhowtf/omniget · error · anyhow::Error

a resposta da API do TikTok não era JSON

Error message

a resposta da API do TikTok não era JSON

What it means

After a non-empty response, list_private() parses the body as serde_json::Value; when parsing fails (HTML error page, garbled bytes, JSONP wrapper) it raises this anyhow error. TikTok normally returns strict JSON, so non-JSON content signals interception or an API change.

Solutions

  1. Log the first ~200 bytes of the body to see if it is HTML (login/captcha page) — refresh cookies if so.
  2. Ensure the reqwest client is not disabling gzip/brotli handling that could yield undecoded bytes.
  3. Retry later or from a different IP to rule out a transient anti-bot challenge.
  4. If TikTok changed the response envelope, update the request/parse code in this module.
Defensive patterns

Strategy: retry

Type guard

fn is_json(body: &str) -> bool { serde_json::from_str::<serde_json::Value>(body).is_ok() }

Try / catch

match run(opts).await {
    Ok(entries) => render(entries),
    Err(e) if e.to_string().contains("não era JSON") => {
        eprintln!("Resposta não-JSON (anti-bot?) — tente novamente com cookies novos");
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: The item-list endpoint returns 2xx non-empty content that is not valid JSON — an HTML login/bot-check page, a compressed body decoded incorrectly, or TikTok switching to a JSONP/signed envelope.

Common situations: Anti-bot systems returning an HTML challenge with 200; region-redirect page; a middlebox mangling the response; library outdated after a TikTok API format change.

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/3d6edb0e8b8a77ad. Report an issue: GitHub.

Appendix: source

Thrown at src-tauri/omniget-core/src/core/tools/tiktok/favorites.rs:399

        opts.limit
    };
    for _ in 0..200 {
        let url = item_list_url(&opts.source, &sec_uid, &cursor, 30)
            .ok_or_else(|| anyhow!("fonte desconhecida: {}", opts.source))?;
        pacer.wait().await;
        let resp = client.get(&url).header("Referer", &profile).send().await?;
        if !resp.status().is_success() {
            return Err(anyhow!("a API do TikTok respondeu HTTP {}", resp.status()));
        }
        let body = resp.text().await?;
        if body.trim().is_empty() {
            return Err(anyhow!(
                "a API do TikTok devolveu resposta vazia — normalmente é a sessão expirada ou a \
                 assinatura da requisição que o site passou a exigir"
            ));
        }
        let v: Value = serde_json::from_str(&body)
            .map_err(|_| anyhow!("a resposta da API do TikTok não era JSON"))?;
        let (page, next, has_more) = entries_from_item_list(&v);
        let vazio = page.is_empty();
        for e in page {
            if out.len() as u32 >= teto {
                break;
            }
            if !out.iter().any(|x| x.id == e.id) {
                out.push(e);
            }
        }
        report(
            progress,
            ID,
            "progress",
            out.len() as u64,
            None,
            Some(format!("{} itens", out.len())),
        );

View on GitHub (pinned to 8600b91f42)