tonhowtf/omniget · error

VOD não encontrado (ou já expirou)

Error message

VOD não encontrado (ou já expirou): {}

What it means

video() looks up a VOD by id via GraphQL and throws when data.video is missing or null. Twitch returns null both for unknown ids and for VODs that expired or were deleted, so the message explicitly mentions expiry — a frequent cause given Twitch's 14/30/60-day retention limits.

Solutions

  1. Open https://www.twitch.tv/videos/<id> to confirm the VOD still exists
  2. Check whether the VOD expired per the channel's retention window — it cannot be recovered
  3. Correct the VOD id (must be the numeric id, not a title/slug)
  4. For sub-only VODs, ensure proper authentication cookies are supplied if supported

Example fix

// before
let v = gql.video("1234567890").await?; // expired VOD
// after
let v = match gql.video("1234567890").await {
    Ok(v) => v,
    Err(e) if e.to_string().contains("expirou") => {
        eprintln!("VOD unavailable/expired; pick another");
        return Err(e);
    }
    Err(e) => return Err(e),
};
Defensive patterns

Strategy: validation

Validate before calling

let id = input.trim();
if !id.chars().all(|c| c.is_ascii_digit()) {
    eprintln!("VOD id must be numeric, got: {id}");
    return Ok(());
}
// Optionally: check twitch.tv/videos/{id} is still live before calling.

Try / catch

match gql.video(id).await {
    Err(e) if e.to_string().contains("expirou") => {
        eprintln!("VOD {id} is gone (expired/deleted); cannot recover");
        Ok(())
    }
    other => other,
}

Prevention

When it happens

Trigger: Calling gql.video(id) (directly or via clip_video / resolve_chat_target) with an id whose VOD no longer exists: expired after the retention window, deleted by the broadcaster, removed by DMCA, or a mistyped id.

Common situations: Trying to download chat for an old VOD past its subscriber retention period; DMCA-deleted VODs; wrong id (using a slug or timestamp instead of the numeric VOD id); turbo/sub-only VODs accessed without auth.

Understand the failure class

Background: "Not found" and "does not exist" errors: why "Task not found", "No such folder", and "Can't find" fire when a lookup comes back empty — this error's family across 14 libraries.

Related errors


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

Appendix: source

Thrown at src-tauri/omniget-core/src/core/tools/twitch/gql.rs:222

        let data = self.query(&q).await?;
        let u = data.get("user").filter(|v| !v.is_null());
        let u = u.ok_or_else(|| anyhow!("canal não encontrado: {}", login))?;
        Ok(Channel {
            id: str_at(u, "id"),
            login: str_at(u, "login"),
            display_name: str_at(u, "displayName"),
            avatar: str_at(u, "profileImageURL"),
        })
    }

    pub async fn video(&self, id: &str) -> anyhow::Result<VideoInfo> {
        let q = format!(
            r#"{{ video(id: "{}") {{ id title lengthSeconds createdAt owner {{ login displayName }} }} }}"#,
            escape(id)
        );
        let data = self.query(&q).await?;
        let v = data.get("video").filter(|v| !v.is_null());
        let v = v.ok_or_else(|| anyhow!("VOD não encontrado (ou já expirou): {}", id))?;
        Ok(VideoInfo {
            id: str_at(v, "id"),
            title: str_at(v, "title"),
            channel: v
                .pointer("/owner/login")
                .and_then(|x| x.as_str())
                .unwrap_or_default()
                .to_string(),
            channel_display: v
                .pointer("/owner/displayName")
                .and_then(|x| x.as_str())
                .unwrap_or_default()
                .to_string(),
            duration_seconds: v.get("lengthSeconds").and_then(num).unwrap_or_default(),
            created_at: str_at(v, "createdAt"),
            clip_offset: None,
            clip_duration: None,
        })

View on GitHub (pinned to 8600b91f42)