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

  1. Confirm the post actually contains images or video before attempting download.
  2. Check the full API response for `thread.$type === 'app.bsky.feed.defs#notFoundPost'` and surface a clearer message.
  3. 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'.
  4. 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

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


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)