{"record":{"id":"8a43528c41b34f69","repo":"tonhowtf/omniget","slug":"age-restricted-content-twitter","errorCode":null,"errorMessage":"Age-restricted content","messagePattern":"Age-restricted content","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src-tauri/omniget-core/src/platforms/twitter.rs","lineNumber":464,"sourceCode":"\n                if reason == \"Protected\" {\n                    return Err(anyhow!(\"Post privado\"));\n                }\n\n                let tombstone_text = tweet_result\n                    .pointer(\"/tombstone/text/text\")\n                    .and_then(|v| v.as_str())\n                    .unwrap_or(\"\");\n\n                tracing::warn!(\n                    \"[twitter] graphql tombstone tweet_id={} reason='{}' tombstone_text='{}'\",\n                    tweet_id,\n                    reason,\n                    tombstone_text\n                );\n\n                if reason == \"NsfwLoggedOut\" || tombstone_text.starts_with(\"Age-restricted\") {\n                    return Err(anyhow!(\"Age-restricted content\"));\n                }\n\n                Err(anyhow!(\"Post not available\"))\n            }\n            \"Tweet\" | \"TweetWithVisibilityResults\" => {\n                let media = Self::media_arrays_from_tweet_result(tweet_result)\n                    .ok_or_else(|| anyhow!(\"No media found in tweet\"))?;\n                tracing::debug!(\n                    \"[twitter] graphql extracted {} media entries for tweet_id={}\",\n                    media.len(),\n                    tweet_id\n                );\n                Ok(media)\n            }\n            _ => Err(anyhow!(\"Post not available\")),\n        }\n    }\n","sourceCodeStart":446,"sourceCodeEnd":482,"githubUrl":"https://github.com/tonhowtf/omniget/blob/8600b91f4246848bac346874daa9e61c1fc5677a/src-tauri/omniget-core/src/platforms/twitter.rs#L446-L482","documentation":"Raised when the tweet result is a tombstone with reason \"NsfwLoggedOut\" or its tombstone text starts with \"Age-restricted\": the media is age-gated and the current (logged-out) session is not allowed to view it. The library deliberately blocks extraction here. It reflects X's sensitive-content policy for unauthenticated clients, not a parsing failure.","triggerScenarios":"extract_graphql_media encounters a tombstone whose reason is \"NsfwLoggedOut\" or whose /tombstone/text/text begins with \"Age-restricted\" — sensitive-media tweets fetched without an age-verified logged-in session.","commonSituations":"Scraping sensitive-media tweets with guest tokens only; X tightened logged-out access to NSFW content; region/age verification missing on the credentials used.","solutions":["Use cookies from a logged-in, age-verified X account for sensitive media.","Detect the NsfwLoggedOut reason up front and surface a clear 'age-restricted' message to end users.","If legitimate, route the request through an authenticated extraction path rather than the guest GraphQL one.","Treat this as a terminal condition — do not retry the same logged-out request."],"exampleFix":"// before\nif reason == \"NsfwLoggedOut\" || tombstone_text.starts_with(\"Age-restricted\") {\n    return Err(anyhow!(\"Age-restricted content\"));\n}\n// after\nif reason == \"NsfwLoggedOut\" || tombstone_text.starts_with(\"Age-restricted\") {\n    return Err(TwitterError::AgeRestricted { tweet_id });\n}","handlingStrategy":"try-catch","validationCode":"// Detect age-gated tombstones before media extraction:\nlet reason = tweet_result.pointer(\"/result/reason\").or_else(|| tweet_result.get(\"reason\"))\n    .and_then(|v| v.as_str()).unwrap_or(\"\");\nlet tombstone = tweet_result.pointer(\"/tombstone/text/text\").and_then(|v| v.as_str()).unwrap_or(\"\");\nif reason == \"NsfwLoggedOut\" || tombstone.starts_with(\"Age-restricted\") {\n    return Err(DisplayError::AgeRestricted);\n}","typeGuard":"fn is_age_restricted(tweet_result: &serde_json::Value) -> bool {\n    let reason = tweet_result.pointer(\"/result/reason\")\n        .or_else(|| tweet_result.get(\"reason\"))\n        .and_then(|v| v.as_str()).unwrap_or(\"\");\n    let text = tweet_result.pointer(\"/tombstone/text/text\")\n        .and_then(|v| v.as_str()).unwrap_or(\"\");\n    reason == \"NsfwLoggedOut\" || text.starts_with(\"Age-restricted\")\n}","tryCatchPattern":"match extractor.fetch(tweet_url) {\n    Err(e) if e.to_string() == \"Age-restricted content\" => {\n        ui.show(\"This media is age-restricted; sign in with an age-verified account.\");\n    }\n    other => other?,\n}","preventionTips":["Use cookies from a logged-in, age-verified X account when sensitive media is expected.","Never retry a NsfwLoggedOut request with the same guest credentials.","Inspect the reason/tombstone fields yourself to classify failures precisely.","Warn users up front that sensitive media requires authenticated extraction."],"tags":["twitter","age-restriction","nsfw","scraping","authentication"],"backgroundTag":"authentication-required","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"}