tonhowtf/omniget · error

Post does not contain media

Error message

Post does not contain media

What it means

After fetching the post JSON, native_get_media_info() reads the JSON pointer /thread/post/embed. If the pointer is absent the post has no embedded media (plain text post, or embed in an unexpected place) and this error is returned. It distinguishes a successfully fetched but non-media post from a network/parsing failure.

Solutions

  1. Confirm the post actually contains an image or video (not text-only).
  2. Handle record_with_media: also check thread.post.embed.record.embed / embeds array for nested media.
  3. Return a clearer message distinguishing text-only posts from unsupported embed types.
  4. Verify against the current AppView schema if Bluesky changed embed nesting.

Example fix

// before
let embed = json.pointer("/thread/post/embed").ok_or_else(|| anyhow!("Post does not contain media"))?;
// after
let embed = json.pointer("/thread/post/embed")
    .or_else(|| json.pointer("/thread/post/embed/record/embed"))
    .or_else(|| json.pointer("/thread/post/embeds/0"))
    .ok_or_else(|| anyhow!("Post does not contain media (text-only or unsupported embed type)"))?;
Defensive patterns

Strategy: try-catch

Type guard

fn embed_of(post_json: &serde_json::Value) -> Option<&serde_json::Value> {
    post_json.pointer("/thread/post/embed")
        .or_else(|| post_json.pointer("/thread/post/embed/record/embed"))
        .or_else(|| post_json.pointer("/thread/post/embeds/0"))
}

Try / catch

match get_media_info(url).await {
    Err(e) if e.to_string().contains("Post does not contain media") => {
        eprintln!("this post is text-only or uses an unsupported embed type");
        Err(e)
    }
    other => other,
}

Prevention

When it happens

Trigger: get_media_info() -> native_get_media_info() where fetch_post() succeeds but the returned AppView JSON has no thread.post.embed object — i.e. a text-only post, a repost, a deleted embed, or an embed-variant (e.g. record_with_media) at a different JSON path.

Common situations: User passes a text-only skeet URL; post is a repost of media living under a different pointer; Bluesky AppView changed the embed shape (recordWithMedia nesting) so the fixed pointer misses.

Related errors


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

Appendix: source

Thrown at src-tauri/omniget-core/src/platforms/bluesky.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)