Skip to content

Rust ​

Rust applications interact with the local Riot Client session by executing the riotclient CLI with std::process::Command and deserializing stdout into typed structs using serde and serde_json. Data types can be generated from the JSON Schemas with quicktype. The whole example lives in examples/rust/ and compiles with cargo check.

Setup ​

  1. Install the CLI globally:
bash
npm install -g @valoranchi/riot-client
  1. Add serde and serde_json to your Cargo.toml:
toml
[dependencies]
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"

Read ​

Spawn riotclient commands and deserialize the standard output into typed Rust structs:

rust
pub fn read_example() -> Result<(), Box<dyn std::error::Error>> {
    // 1. Read player profile
    let whoami_out = Command::new("riotclient").arg("whoami").output()?;
    if whoami_out.status.success() {
        let player: Player = serde_json::from_slice(&whoami_out.stdout)?;
        println!("Player: {}#{} ({})", player.game_name, player.tag_line, player.region);
    }

    // 2. Read owned collection
    let collection_out = Command::new("riotclient")
        .args(["owned-items", "--language", "en-US"])
        .output()?;
    if collection_out.status.success() {
        let items: OwnedItems = serde_json::from_slice(&collection_out.stdout)?;
        println!("Collection generated at: {}", items.generated_at);
        for weapon in items.weapons {
            if let Some(first_skin) = weapon.skins.first() {
                println!("- {}: {}", weapon.name, first_skin.name);
            }
        }
    }

    Ok(())
}

Write with Validation ​

Every mutation defaults to a local dry run unless --yes is supplied. If validation fails (for example equipping a card the account does not own), the process exits with code 6 and returns error JSON on stderr:

rust
#[derive(Debug, Deserialize)]
pub struct ErrorDetail {
    pub code: String,
    pub reason: Option<String>,
    pub message: String,
}

#[derive(Debug, Deserialize)]
pub struct ErrorEnvelope {
    pub error: ErrorDetail,
}

pub fn equip_card(card_uuid: &str, apply: bool) -> Result<(), Box<dyn std::error::Error>> {
    let mut cmd = Command::new("riotclient");
    cmd.args(["equip", "--card", card_uuid]);
    if apply {
        cmd.arg("--yes");
    }

    let output = cmd.output()?;
    if output.status.success() {
        let stdout = String::from_utf8_lossy(&output.stdout);
        println!("Success: {}", stdout.trim());
    } else if output.status.code() == Some(6) {
        // Validation error
        let err: ErrorEnvelope = serde_json::from_slice(&output.stderr)?;
        eprintln!(
            "Validation failed [code {} / {:?}]: {}",
            err.error.code,
            err.error.reason.unwrap_or_default(),
            err.error.message
        );
    } else {
        eprintln!("Command failed: {}", String::from_utf8_lossy(&output.stderr));
    }

    Ok(())
}

Events ​

Stream real-time events from the local client by spawning riotclient watch with piped stdout and iterating lines with BufReader:

rust
pub fn stream_events() -> Result<(), Box<dyn std::error::Error>> {
    let mut child = Command::new("riotclient")
        .args(["watch", "--only", "friend:presence,message"])
        .stdout(Stdio::piped())
        .spawn()?;

    println!("Listening for real-time events. Press Ctrl+C to terminate.");
    let stdout = child.stdout.take().expect("Failed to capture stdout");
    let reader = BufReader::new(stdout);

    for line in reader.lines() {
        let line = line?;
        if line.trim().is_empty() {
            continue;
        }

        let val: Value = serde_json::from_str(&line)?;
        let event = val.get("event").and_then(|v| v.as_str()).unwrap_or_default();
        let data = val.get("data");

        match event {
            "friend:presence" => {
                let name = data.and_then(|d| d.pointer("/friend/gameName")).and_then(|v| v.as_str()).unwrap_or_default();
                let change = data.and_then(|d| d.get("change")).and_then(|v| v.as_str()).unwrap_or_default();
                let state = data.and_then(|d| d.pointer("/friend/presence/state")).and_then(|v| v.as_str()).unwrap_or_default();
                println!("Friend {} is now {} ({})", name, change, state);
            }
            "message" => {
                let from = data.and_then(|d| d.pointer("/from/gameName")).and_then(|v| v.as_str()).unwrap_or_default();
                let body = data.and_then(|d| d.get("body")).and_then(|v| v.as_str()).unwrap_or_default();
                println!("[{}]: {}", from, body);
            }
            _ => {}
        }
    }

    Ok(())
}

Types ​

The structs the example deserializes into:

rust
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Player {
    pub game_name: String,
    pub tag_line: String,
    pub region: String,
}

#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct OwnedSkin {
    pub name: String,
}

#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct OwnedWeapon {
    pub name: String,
    pub skins: Vec<OwnedSkin>,
}

#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct OwnedItems {
    pub generated_at: String,
    pub weapons: Vec<OwnedWeapon>,
}

pub fn types_example(stdout: &[u8]) -> Result<(), Box<dyn std::error::Error>> {
    let items: OwnedItems = serde_json::from_slice(stdout)?;
    for weapon in items.weapons {
        if let Some(skin) = weapon.skins.first() {
            println!("- {}: {}", weapon.name, skin.name);
        }
    }
    Ok(())
}

Generate complete serde structs for every model from the schemas with quicktype:

bash
npx quicktype schema/OwnedItems.json --src-lang schema -l rust -o owned_items.rs

Not affiliated with Riot Games