{"record":{"id":"fec6de254bc6cfef","repo":"tonhowtf/omniget","slug":"bluesky-api-retornou-http","errorCode":null,"errorMessage":"Bluesky API retornou HTTP {}","messagePattern":"Bluesky API retornou HTTP (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src-tauri/omniget-core/src/platforms/bluesky.rs","lineNumber":139,"sourceCode":"        let segments: Vec<&str> = parsed.path().split('/').filter(|s| !s.is_empty()).collect();\n        if segments.len() >= 4 && segments[0] == \"profile\" && segments[2] == \"post\" {\n            return Some((segments[1].to_string(), segments[3].to_string()));\n        }\n        None\n    }\n\n    async fn fetch_post(&self, user: &str, post_id: &str) -> anyhow::Result<serde_json::Value> {\n        let uri = format!(\"at://{}/app.bsky.feed.post/{}\", user, post_id);\n        let url = format!(\n            \"{}?depth=0&parentHeight=0&uri={}\",\n            API_BASE,\n            urlencoding::encode(&uri)\n        );\n\n        let response = self.client.get(&url).send().await?;\n\n        if !response.status().is_success() {\n            return Err(anyhow!(\"Bluesky API retornou HTTP {}\", response.status()));\n        }\n\n        let json: serde_json::Value = response.json().await?;\n\n        if let Some(error) = json.get(\"error\").and_then(|e| e.as_str()) {\n            return match error {\n                \"NotFound\" | \"InternalServerError\" => Err(anyhow!(\"Post not available\")),\n                \"InvalidRequest\" => Err(anyhow!(\"Unsupported link\")),\n                _ => Err(anyhow!(\"Erro da API: {}\", error)),\n            };\n        }\n\n        Ok(json)\n    }\n}\n\nenum BlueskyMedia {\n    Video { hls_url: String },","sourceCodeStart":121,"sourceCodeEnd":157,"githubUrl":"https://github.com/tonhowtf/omniget/blob/8600b91f4246848bac346874daa9e61c1fc5677a/src-tauri/omniget-core/src/platforms/bluesky.rs#L121-L157","documentation":"Thrown in fetch_post when the public Bluesky getPostThread endpoint returns a non-2xx HTTP status. The error embeds the status code so the developer can see whether it was a 400, 404, 5xx, etc. It guards before JSON parsing since the body will not be a valid thread.","triggerScenarios":"GET to https://public.api.bsky.app/xrpc/app.bsky.feed.getPostThread returns non-success: malformed at:// URI, deleted/private post, rate limiting (429), or AppView outage (5xx).","commonSituations":"Deleted or blocked post IDs, wrong PDS/handle in the URL, rate limits from scraping many posts, transient Bluesky API outages, or network proxies mangling the request.","solutions":["Inspect the logged HTTP status: 404/400 means the post link is invalid or deleted; retry is pointless.","For 429, back off and retry later respecting rate limits.","For 5xx, retry with backoff or rely on the yt-dlp fallback path in get_media_info.","Verify the URL matches bsky.app/<handle>/post/<postId> format before calling."],"exampleFix":"let response = self.client.get(&url).send().await?;\n// after: handle retries for transient statuses\nif response.status() == reqwest::StatusCode::TOO_MANY_REQUESTS {\n    tokio::time::sleep(std::time::Duration::from_secs(5)).await;\n    return Box::pin(self.fetch_post(user, post_id)).await;\n}\nif !response.status().is_success() {\n    return Err(anyhow!(\"Bluesky API retornou HTTP {}\", response.status()));\n}","handlingStrategy":"retry","validationCode":"let parsed = url::Url::parse(url)?;\nlet segs: Vec<&str> = parsed.path().split('/').filter(|s| !s.is_empty()).collect();\nif !(segs.len() >= 4 && segs[0] == \"profile\" && segs[2] == \"post\") {\n    return Err(anyhow!(\"not a bsky post URL\"));\n}","typeGuard":null,"tryCatchPattern":"for attempt in 0..3 {\n    match downloader.get_media_info(url).await {\n        Ok(info) => break Ok(info),\n        Err(e) if e.to_string().contains(\"HTTP 5\") || e.to_string().contains(\"HTTP 429\") => {\n            tokio::time::sleep(Duration::from_secs(2u64 << attempt)).await;\n        }\n        Err(e) => break Err(e), // 4xx: don't retry\n    }\n}","preventionTips":["Validate the bsky.app post URL shape (profile/<handle>/post/<id>) before calling","Distinguish retryable statuses (429, 5xx) from permanent ones (400, 404) in your catch logic","Respect Bluesky public API rate limits; throttle batch downloads"],"tags":["bluesky","http","api"],"backgroundTag":"http-error-response","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"}