tonhowtf/omniget · error

Post privado

Error message

Post privado

What it means

Raised when the tweet result's reason field equals "Protected": the target account is private/protected, so its posts are only visible to approved followers. The library is (typically) making a logged-out request and intentionally refuses with the Spanish-language message 'Post privado' instead of attempting extraction. This is an expected, deliberate error for protected content.

Solutions

  1. Inform the user the post belongs to a protected account and cannot be fetched without follower access.
  2. Use authenticated cookies belonging to an account that follows the protected user.
  3. Check the account's visibility (protected flag) before attempting extraction to fail fast with a clearer message.
  4. Map this reason to a typed error so callers can distinguish it from generic unavailability.

Example fix

// before
if reason == "Protected" {
    return Err(anyhow!("Post privado"));
}
// after
if reason == "Protected" {
    return Err(TwitterError::ProtectedPost { tweet_id });
}
Defensive patterns

Strategy: validation

Validate before calling

// Before extraction, check account visibility via the user API:
let is_protected = user.get("protected").and_then(|v| v.as_bool()).unwrap_or(false);
if is_protected {
    return Err(DisplayError::ProtectedPost);
}

Type guard

fn is_protected_reason(tweet_result: &serde_json::Value) -> bool {
    tweet_result.pointer("/result/reason").or_else(|| tweet_result.get("reason"))
        .and_then(|v| v.as_str()) == Some("Protected")
}

Try / catch

match extractor.fetch(tweet_url) {
    Err(e) if e.to_string() == "Post privado" => {
        ui.show("This post is from a protected account and cannot be downloaded.");
    }
    other => other?,
}

Prevention

When it happens

Trigger: extract_graphql_media resolves a tweet result whose __typename is TweetTombstone (or has /result/reason) and reason == "Protected" — i.e. the tweet belongs to a protected X account.

Common situations: User pasted a link to a private account's tweet; the account was switched to protected after the link was shared; scraper runs without follower credentials, so any protected tweet fails.

Understand the failure class

Background: Permission denied / not authorized / 403 Forbidden: access-control rejections when the caller lacks the required role, grant, or ownership — this error's family across 18 libraries.

Related errors


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

Appendix: source

Thrown at src-tauri/omniget-core/src/platforms/twitter.rs:448

            .get("__typename")
            .and_then(|v| v.as_str())
            .unwrap_or("");
        tracing::debug!(
            "[twitter] graphql media typename={} tweet_id={}",
            typename,
            tweet_id
        );

        match typename {
            "TweetUnavailable" | "TweetTombstone" => {
                let reason = tweet_result
                    .pointer("/result/reason")
                    .or_else(|| tweet_result.get("reason"))
                    .and_then(|v| v.as_str())
                    .unwrap_or("");

                if reason == "Protected" {
                    return Err(anyhow!("Post privado"));
                }

                let tombstone_text = tweet_result
                    .pointer("/tombstone/text/text")
                    .and_then(|v| v.as_str())
                    .unwrap_or("");

                tracing::warn!(
                    "[twitter] graphql tombstone tweet_id={} reason='{}' tombstone_text='{}'",
                    tweet_id,
                    reason,
                    tombstone_text
                );

                if reason == "NsfwLoggedOut" || tombstone_text.starts_with("Age-restricted") {
                    return Err(anyhow!("Age-restricted content"));
                }

View on GitHub (pinned to 8600b91f42)