tonhowtf/omniget · error · anyhow::Error
Post does not contain media
Error message
Post does not contain media
What it means
In `native_get_media_info` (src-tauri/src/platforms/bluesky/mod.rs:39), the JSON pointer `/thread/post/embed` was absent from the fetched post record, meaning the Bluesky AppView returned a post with no embed at all. Only posts with attached media (or external embeds) carry this field.
Solutions
- Confirm the post actually contains images or video before attempting download.
- Check the full API response for `thread.$type === 'app.bsky.feed.defs#notFoundPost'` and surface a clearer message.
- Use a null-tolerant path like `pointer("/thread/post/embed")` combined with an explicit not-found check on `thread` to distinguish 'no media' from 'post missing'.
- Fall back to the generic ytdlp path for posts without native embeds.
Example fix
// before
let embed = json.pointer("/thread/post/embed")
.ok_or_else(|| anyhow!("Post does not contain media"))?;
// after
if json.pointer("/thread/$type").is_some_and(|t| t.as_str().unwrap_or_default().contains("notFoundPost")) {
anyhow::bail!("Post not found or deleted");
}
let embed = json.pointer("/thread/post/embed")
.ok_or_else(|| anyhow!("Post does not contain media"))?; Defensive patterns
Strategy: type-guard
Type guard
fn has_embed(thread: &serde_json::Value) -> bool {
thread.pointer("/post/embed").is_some()
} Try / catch
let Some(embed) = json.pointer("/thread/post/embed") else {
anyhow::bail!("Post has no media embed (is it a text-only post?)");
}; Prevention
- Check for the notFoundPost thread type before assuming a live post.
- Inform users that only posts with attached images or video are supported.
- Validate the embed path exists before pointer navigation.
When it happens
Trigger: Fetching a text-only post, a deleted or gated post that returns a skeleton without embed, or a reply/repost whose thread shape differs, then calling `.pointer("/thread/post/embed")` which yields None.
Common situations: User shares a Bluesky post that has no images or video; post was deleted between copying the link and downloading; post from a blocked/moderated account returns a truncated thread object.
Related errors
- No downloadable media found for this tweet (it may be…
- Token sem signature
- Token sem value
- Could not extract user and post_id from URL
- Unsupported media type
AI-assisted analysis of tonhowtf/omniget@8600b91f42 (2026-09-12).
Data as JSON: /api/errors/39984c9110680792.
Report an issue: GitHub.
Appendix: source
Thrown at src-tauri/src/platforms/bluesky/mod.rs:39
}
}
impl BlueskyDownloader {
async fn fallback_ytdlp(&self, url: &str) -> anyhow::Result<MediaInfo> {
let ytdlp_path = crate::core::ytdlp::ensure_ytdlp().await?;
let json = crate::core::ytdlp::get_video_info(&ytdlp_path, url, &[]).await?;
crate::platforms::generic_ytdlp::GenericYtdlpDownloader::parse_video_info(&json)
}
async fn native_get_media_info(&self, url: &str) -> anyhow::Result<MediaInfo> {
let (user, post_id) = Self::extract_user_and_post(url)
.ok_or_else(|| anyhow!("Could not extract user and post_id from URL"))?;
let json = self.fetch_post(&user, &post_id).await?;
let embed = json
.pointer("/thread/post/embed")
.ok_or_else(|| anyhow!("Post does not contain media"))?;
let media = extract_media(embed).ok_or_else(|| anyhow!("Unsupported media type"))?;
let filename_base = format!("bluesky_{}_{}", sanitize_filename::sanitize(&user), post_id);
match media {
BlueskyMedia::Video { hls_url } => Ok(MediaInfo {
title: filename_base,
author: user,
platform: "bluesky".to_string(),
duration_seconds: None,
thumbnail_url: None,
available_qualities: vec![VideoQuality {
label: "best".to_string(),
width: 0,
height: 0,
url: hls_url,
format: "hls".to_string(),View on GitHub (pinned to 8600b91f42)