# How Bilistream Detects Twitch Stream Status: GraphQL Implementation Guide

> Discover how Bilistream detects Twitch stream status using a GraphQL query. Learn the implementation details for real-time stream monitoring.

- Repository: [InitCool/bilistream](https://github.com/limitcool/bilistream)
- Tags: how-to-guide
- Published: 2026-03-06

---

**Bilistream determines Twitch stream status by posting a GraphQL StreamMetadata query to `https://gql.twitch.tv/gql` and verifying that `res["data"]["user"]["stream"]["type"]` equals `"live"`.**

The `limitcool/bilistream` repository implements Twitch stream status detection through a dedicated plugin architecture that abstracts platform-specific logic behind a common trait. This approach allows the application to treat Twitch, YouTube, and other platforms uniformly while maintaining precise control over each service's unique API requirements.

## The Architecture Behind Twitch Stream Status Detection

The detection system relies on the **`Live`** trait defined in [`src/plugins/live.rs`](https://github.com/limitcool/bilistream/blob/main/src/plugins/live.rs), which establishes a standard contract for all streaming platforms. This abstraction enables polymorphic handling of different live-streaming services through a boxed trait object.

### The Live Trait Abstraction

In [`src/plugins/live.rs`](https://github.com/limitcool/bilistream/blob/main/src/plugins/live.rs), the `Live` trait declares the interface that every platform plugin must implement:

```rust
// src/plugins/live.rs
pub trait Live {
    async fn get_status(&self) -> Result<bool, Box<dyn Error>>;
    fn room(&self) -> &str;
}

```

The `select_live` function (lines 42‑53) acts as a factory that instantiates the appropriate concrete type based on the configuration's `platform` field. When `cfg.platform == "Twitch"`, it constructs a `Twitch` struct instance via `Twitch::new`, passing the channel name, a `reqwest` client with retry middleware, and the global configuration.

### Twitch Plugin Structure

The `Twitch` struct in [`src/plugins/twitch.rs`](https://github.com/limitcool/bilistream/blob/main/src/plugins/twitch.rs) encapsulates all state required for GraphQL communication:

- **`room`**: The target channel login name
- **`client`**: A `reqwest` HTTP client configured with retry policies
- **`config`**: Global application settings

This structure is instantiated during the platform selection phase and persists throughout the application's lifecycle, allowing efficient reuse of the HTTP connection pool.

## GraphQL Query Implementation

Bilistream bypasses Twitch's private REST API in favor of the public GraphQL endpoint, using a persisted query that requires no OAuth tokens.

### Endpoint and Authentication Headers

The `get_status` method constructs a POST request to **`https://gql.twitch.tv/gql`** with the following mandatory headers:

- **`Client-ID: kimne78kx3ncx6brgo4mv6wki5h1ko`** (Twitch's public web client ID)
- **Content-Type: application/json**

These headers authenticate the request as a standard Twitch web client, allowing access to public stream metadata without user credentials.

### The StreamMetadata Operation

The GraphQL payload queries the `StreamMetadata` operation with a specific SHA256 hash that identifies the persisted query:

```rust
// src/plugins/twitch.rs (lines 35‑46)
let j = json!({
    "operationName": "StreamMetadata",
    "variables": {
        "channelLogin": &self.room
    },
    "extensions": {
        "persistedQuery": {
            "version": 1,
            "sha256Hash": "1c719a40e481453e5c48d9bb585d971b8b372f8ebb105b17076722264dfa5b3e"
        }
    }
});

```

This operation returns stream metadata for the specified `channelLogin`, including the critical `type` field that indicates live status.

## Parsing the Live Status Response

After receiving the JSON response, bilistream deserializes it into `serde_json::Value` and inspects a specific nested path to determine status:

```rust
// src/plugins/twitch.rs (lines 50‑55)
if let Some(stream_type) = res["data"]["user"]["stream"]["type"].as_str() {
    Ok(stream_type == "live")
} else {
    Ok(false)
}

```

The logic returns **`Ok(true)`** only when the `type` field equals `"live"`. Any other value—including `"archive"`, `"highlight"`, or `null`—results in `Ok(false)`, accurately distinguishing between active broadcasts and offline or recorded content.

## Complete Usage Example

The following example demonstrates the complete workflow for checking a Twitch channel's status using bilistream's API:

```rust
use bilistream::config::load_config;
use bilistream::plugins::select_live;
use std::path::Path;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Load configuration containing platform = "Twitch" and room = "channel_name"
    let cfg = load_config(Path::new("config.yaml"))?;
    
    // Factory returns Box<dyn Live> configured for Twitch
    let live = select_live(cfg).await?;
    
    // Execute GraphQL query and parse response
    let is_live = live.get_status().await?;
    println!("Channel '{}' is live: {}", live.room(), is_live);
    
    Ok(())
}

```

This pattern delegates platform-specific details to the plugin layer while providing a unified interface for the main application logic.

## Summary

- **File location**: Twitch detection logic resides in [`src/plugins/twitch.rs`](https://github.com/limitcool/bilistream/blob/main/src/plugins/twitch.rs), implementing the `Live` trait from [`src/plugins/live.rs`](https://github.com/limitcool/bilistream/blob/main/src/plugins/live.rs).
- **API method**: Uses Twitch's public GraphQL endpoint at `https://gql.twitch.tv/gql` with client ID `kimne78kx3ncx6brgo4mv6wki5h1ko`.
- **Query details**: Sends a `StreamMetadata` persisted query (SHA256: `1c719a40e481453e5c48d9bb585d971b8b372f8ebb105b17076722264dfa5b3e`) with the channel login as a variable.
- **Status logic**: Returns `true` only when `data.user.stream.type` equals `"live"`, ensuring accurate detection of active broadcasts versus offline or recorded states.
- **Integration**: The `select_live` factory in [`src/plugins/live.rs`](https://github.com/limitcool/bilistream/blob/main/src/plugins/live.rs) instantiates the correct plugin based on configuration, enabling seamless multi-platform support.

## Frequently Asked Questions

### What GraphQL endpoint does bilistream use for Twitch?

Bilistream queries **`https://gql.twitch.tv/gql`**, Twitch's public GraphQL API. This endpoint serves the Twitch web interface and requires only a public client ID header, avoiding the need for OAuth tokens or private API keys.

### How does bilistream authenticate with Twitch's API?

The plugin sends the public client ID **`kimne78kx3ncx6brgo4mv6wki5h1ko`** in the request headers. This identifier matches Twitch's official web application and grants access to public stream metadata without user authentication.

### What specific field indicates a live stream in the response?

The code inspects **`res["data"]["user"]["stream"]["type"]`**. If this field exists and equals the string `"live"`, the channel is currently broadcasting. Any other value or a missing field indicates the channel is offline or hosting non-live content.

### Can bilistream check other platforms besides Twitch?

Yes. The `Live` trait abstraction in [`src/plugins/live.rs`](https://github.com/limitcool/bilistream/blob/main/src/plugins/live.rs) supports multiple platforms through the `select_live` factory. When configured for different platforms (such as YouTube), the factory instantiates the appropriate plugin that implements the same `get_status` interface, allowing bilistream to handle diverse streaming services through uniform code paths.