steam-client-rs 0.1.2

Steam client for Rust - Individual and Anonymous user account types
Documentation

steam-client-rs

Crates.io Docs.rs License: MIT

A comprehensive Steam client library for Rust, providing Individual and Anonymous user account types. This crate handles connection management, authentication, and communication with Steam's CM (Connection Manager) servers via WebSocket.

Features

  • Multiple Authentication Methods

    • Refresh token login (persistent sessions)
    • Password login with Steam Guard support (email, TOTP, mobile confirmation)
    • Anonymous login for public data access
  • Connection Management

    • WebSocket-based connections to Steam CM servers
    • Automatic reconnection with exponential backoff
    • Server load balancing and blacklisting
    • Heartbeat keep-alive
    • Smart Server Refresh: Self-healing mechanism that automatically updates the server list every 7 days from WebAPI, ensuring reliable connections even after long periods of inactivity.
  • Friends & Social

    • Friends list management
    • Persona states (Online, Away, Busy, Offline, etc.)
    • Friend nicknames and levels
    • Friend messages and typing indicators
  • Games & Apps

    • Set playing status (up to 32 games)
    • Support for Non-Steam games with custom names
    • Auto-Idler: Built-in utility for automatic game idling with randomized intervals
    • Rich presence data
    • App info and PICS (Package Info Cache System)
    • License/ownership information
    • Free license requests
  • Economy & Trading

    • Asset class info
    • Trade URLs
    • Trade restrictions
    • Emoticons and profile items
  • Content Delivery

    • Content server discovery
    • Depot decryption keys
    • Manifest parsing
  • Advanced Features

    • Game Coordinator (GC) messaging with request-response pattern
    • Family sharing management
    • Chat rooms and group chats
    • Notification system
    • Two-factor authentication management
    • Gift redemption
    • Store tag metadata and localization
    • App ownership tickets and encrypted app tickets
    • Persona cache with configurable TTL
    • Automatic cache population from events
    • Thread-safe with size limits
  • CS:GO/CS2 Integration

    • Game Coordinator communication
    • Party search
    • Lobby management (create, join, leave, update)
    • Rich presence / fake score
    • Player profiles

Installation

[dependencies]

steam-client-rs = "0.1"

tokio = { version = "1", features = ["full"] }

The crate is imported as steam_client in your code (the package name is steam-client-rs because steam-client is taken on crates.io).

Architecture

┌─────────────────────────────────────────────────────────────────────┐
│                           SteamClient                               │
├─────────────────────────────────────────────────────────────────────┤
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐  ┌────────────┐  │
│  │  Connection │  │    Jobs     │  │   GC Jobs   │  │ Reconnect  │  │
│  │   Manager   │  │   Manager   │  │   Manager   │  │  Manager   │  │
│  └─────────────┘  └─────────────┘  └─────────────┘  └────────────┘  │
├─────────────────────────────────────────────────────────────────────┤
│                          Cache Layer                                │
│  ┌─────────────────────────────────────────────────────────────┐    │
│  │                      PersonaCache                            │    │
│  └─────────────────────────────────────────────────────────────┘    │
├─────────────────────────────────────────────────────────────────────┤
│                         Services Layer                              │
│  ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐        │
│  │ Friends │ │  Apps   │ │   CDN   │ │  Chat   │ │  Econ   │        │
│  └─────────┘ └─────────┘ └─────────┘ └─────────┘ └─────────┘        │
│  ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌──────────┐ ┌─────────┐       │
│  │ChatRoom │ │ CS:GO   │ │TwoFactor│ │ Account  │ │ Trading │       │
│  └─────────┘ └─────────┘ └─────────┘ └──────────┘ └─────────┘       │
│  ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌──────────┐ ┌─────────┐       │
│  │  Gifts  │ │   GC    │ │  Store  │ │  Idler   │ │ PubFiles│       │
│  └─────────┘ └─────────┘ └─────────┘ └──────────┘ └─────────┘       │
└─────────────────────────────────────────────────────────────────────┘

Quick Start

Login with Refresh Token

use steam_client::{SteamClient, LogOnDetails, EPersonaState};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let mut client = SteamClient::new(Default::default());
    
    let response = client.log_on(LogOnDetails {
        refresh_token: Some("your_refresh_token".to_string()),
        ..Default::default()
    }).await?;
    
    println!("Logged in as: {}", response.steam_id.steam3());
    println!("Public IP: {:?}", response.public_ip);
    println!("Cell ID: {}", response.cell_id);
    
    // Set online status
    client.set_persona(EPersonaState::Online, None).await?;
    
    Ok(())
}

Anonymous Login

use steam_client::{SteamClient, LogOnDetails};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let mut client = SteamClient::new(Default::default());
    
    // Anonymous login - useful for public data access (app info, player counts, etc.)
    let response = client.log_on(LogOnDetails::anonymous()).await?;
    println!("Connected as anonymous user: {}", response.steam_id.steam3());
    
    // Anonymous sessions can still query public data
    // but cannot access friend lists, chat, etc.
    
    Ok(())
}

Password Login with Steam Guard

use steam_client::{SteamClient, SteamError, EAuthSessionGuardType};

async fn login_with_password() -> Result<(), Box<dyn std::error::Error>> {
    let mut client = SteamClient::new(Default::default());
    
    match client.log_on_with_password("username", "password", None, None).await {
        Ok(response) => {
            println!("Logged in: {}", response.steam_id.steam3());
        }
        Err(SteamError::SteamGuardRequired { guard_type, email_domain }) => {
            match guard_type {
                EAuthSessionGuardType::KEAuthSessionGuardTypeEmailCode => {
                    println!("Check your email at {}!", email_domain.unwrap_or_default());
                    // Get code from user...
                    let code = "ABC123";
                    let response = client.submit_steam_guard_code(code).await?;
                    println!("Logged in: {}", response.steam_id.steam3());
                }
                EAuthSessionGuardType::KEAuthSessionGuardTypeDeviceCode => {
                    println!("Enter your Steam Mobile Authenticator code:");
                    // Get TOTP code from user or generate with steam-totp...
                    let code = "12345";
                    let response = client.submit_steam_guard_code(code).await?;
                    println!("Logged in: {}", response.steam_id.steam3());
                }
                _ => {
                    println!("Unsupported guard type: {:?}", guard_type);
                }
            }
        }
        Err(e) => return Err(e.into()),
    }
    
    Ok(())
}

Configuration

SteamOptions

use steam_client::{SteamClient, SteamOptions, ReconnectConfig, HeartbeatOptions};
use steam_client::EConnectionProtocol;
use std::time::Duration;

let options = SteamOptions {
    // Connection protocol (Auto, TCP, WebSocket)
    protocol: EConnectionProtocol::Auto,
    
    // Auto-reconnect on disconnect
    auto_relogin: true,
    
    // Data directory for persistent storage (machine auth tokens)
    data_directory: Some("./steam_data".to_string()),
    
    // Web compatibility mode (WebSocket only, port 443)
    web_compatibility_mode: false,
    
    // HTTP proxy URL
    http_proxy: None,
    
    // Automatically renew refresh tokens
    renew_refresh_tokens: false,
    
    // Machine name reported to Steam
    machine_name: Some("MyRustBot".to_string()),
    
    // Reconnection settings
    reconnect: ReconnectConfig {
        enabled: true,
        max_attempts: 5,
        initial_delay: Duration::from_secs(1),
        max_delay: Duration::from_secs(30),
        backoff_multiplier: 2.0,
    },
    
    // Heartbeat settings
    heartbeat: HeartbeatOptions {
        enabled: true,
        interval: Duration::from_secs(30),
    },
};

let client = SteamClient::new(options);

LogOnDetails

use steam_client::LogOnDetails;

// Refresh token login (recommended)
let details = LogOnDetails {
    refresh_token: Some("your_refresh_token".to_string()),
    ..Default::default()
};

// Anonymous login
let details = LogOnDetails::anonymous();

// Full login details
let details = LogOnDetails {
    anonymous: false,
    refresh_token: Some("token".to_string()),
    account_name: Some("username".to_string()),
    password: Some("password".to_string()),
    auth_code: None,                          // Email Steam Guard code
    two_factor_code: None,                    // TOTP code
    machine_auth_token: None,                 // Machine auth for Steam Guard
    logon_id: Some(12345),                    // Avoid "logged in elsewhere"
    machine_name: Some("MyBot".to_string()),  // Machine name
};

Event System

The client uses a categorized event system for type-safe event handling:

Event Categories

Category Description Events
Auth Authentication LoggedOn, LoggedOff, RefreshToken
Connection Connection lifecycle Connected, Disconnected, ReconnectAttempt, ReconnectFailed
Friends Social features FriendsList, PersonaState, FriendRelationship
Chat Messaging FriendMessage, FriendTyping
Apps Games/licenses LicenseList, ProductInfoResponse, PICSChanges, GCReceived
Content Content delivery RichPresence
System Debug/errors Debug, Error

Event Loop

use steam_client::{SteamClient, SteamEvent, AuthEvent, FriendsEvent, ChatEvent, ConnectionEvent, AppsEvent};
use std::time::Duration;

async fn event_loop(client: &mut SteamClient) -> Result<(), Box<dyn std::error::Error>> {
    loop {
        match client.poll_event_timeout(Duration::from_secs(30)).await? {
            Some(event) => match event {
                // Authentication events
                SteamEvent::Auth(AuthEvent::LoggedOn { steam_id }) => {
                    println!("✅ Logged in as {}", steam_id.steam3());
                }
                SteamEvent::Auth(AuthEvent::RefreshToken { token, account_name }) => {
                    // Save this token for future logins!
                    println!("💾 New refresh token for {}", account_name);
                    save_token(&account_name, &token);
                }
                SteamEvent::Auth(AuthEvent::LoggedOff { result }) => {
                    println!("👋 Logged off: {:?}", result);
                    break;
                }

                // Friends events
                SteamEvent::Friends(FriendsEvent::FriendsList { friends, .. }) => {
                    println!("👥 Friends list: {} friends", friends.len());
                }
                SteamEvent::Friends(FriendsEvent::PersonaState(persona)) => {
                    println!("👤 {} is {:?}", persona.player_name, persona.persona_state);
                }

                // Chat events
                SteamEvent::Chat(ChatEvent::FriendMessage { sender, message, .. }) => {
                    println!("💬 {}: {}", sender.steam3(), message);
                }
                SteamEvent::Chat(ChatEvent::FriendTyping { sender }) => {
                    println!("✏️ {} is typing...", sender.steam3());
                }

                // Apps events
                SteamEvent::Apps(AppsEvent::LicenseList { licenses }) => {
                    println!("📦 {} licenses owned", licenses.len());
                }

                // Connection events
                SteamEvent::Connection(ConnectionEvent::Disconnected { reason, will_reconnect }) => {
                    println!("🔌 Disconnected: {:?}", reason);
                    if !will_reconnect {
                        break;
                    }
                }
                SteamEvent::Connection(ConnectionEvent::ReconnectAttempt { attempt, max_attempts, delay }) => {
                    println!("🔄 Reconnecting {}/{} (delay: {:?})", attempt, max_attempts, delay);
                }

                _ => {}
            },
            None => {
                // Timeout - no event received
                println!("⏰ Heartbeat");
            }
        }
    }
    Ok(())
}

fn save_token(account: &str, token: &str) {
    // Implement your token storage here
}

Event Helpers

use steam_client::SteamEvent;

// Check event category
event.is_auth();        // Authentication event?
event.is_connection();  // Connection event?
event.is_friends();     // Friends event?
event.is_chat();        // Chat event?
event.is_apps();        // Apps event?
event.is_content();     // Content event?
event.is_system();      // System event?

// Get chat sender
if let Some(sender) = event.chat_sender() {
    println!("Message from: {}", sender.steam3());
}

Services

Friends Service

use steam_client::SteamClient;
use steamid::SteamID;
use steam_client::EPersonaState;

async fn friends_example(client: &mut SteamClient) -> Result<(), Box<dyn std::error::Error>> {
    // Set your persona state and name
    client.set_persona(EPersonaState::Online, Some("Playing Rust 🦀".to_string())).await?;
    
    // Get persona info for users
    let steam_ids = vec![SteamID::from_steam_id64(76561198000000000)];
    let personas = client.get_personas(&steam_ids).await?;
    
    // Add a friend
    let result = client.add_friend(steam_ids[0]).await?;
    println!("Add friend result: {:?}", result);
    
    // Get friend nicknames
    let nicknames = client.get_nicknames().await?;
    
    // Set a nickname
    client.set_nickname(steam_ids[0], "My Friend").await?;
    
    // Get Steam levels
    let levels = client.get_steam_levels(&steam_ids).await?;
    
    // Remove friend
    client.remove_friend(steam_ids[0]).await?;
    
    Ok(())
}

Chat Service

use steam_client::SteamClient;
use steamid::SteamID;

async fn chat_example(client: &mut SteamClient) -> Result<(), Box<dyn std::error::Error>> {
    let friend = SteamID::from_steam_id64(76561198000000000);
    
    // Send a message
    let result = client.send_friend_message(friend, "Hello from Rust! 🦀").await?;
    println!("Message sent, timestamp: {:?}", result.modified_timestamp);
    
    // Send typing indicator
    client.send_friend_typing(friend).await?;
    
    // Get chat history
    let history = client.get_recent_messages(friend, 50).await?;
    for msg in history {
        println!("[{}] {}: {}", msg.timestamp, msg.sender.steam3(), msg.message);
    }
    
    // Get active message sessions
    let sessions = client.get_active_message_sessions().await?;
    
    Ok(())
}

Games Service

use steam_client::SteamClient;
use std::collections::HashMap;

async fn games_example(client: &mut SteamClient) -> Result<(), Box<dyn std::error::Error>> {
    // Play a single game
    client.games_played(vec![440]).await?;  // TF2
    
    // Play multiple games (up to 32)
    client.games_played(vec![440, 730, 570]).await?;  // TF2, CS2, Dota 2
    
    // Stop playing
    client.games_played(vec![]).await?;
    
    // Set rich presence
    let mut presence = HashMap::new();
    presence.insert("status".to_string(), "In Queue".to_string());
    presence.insert("steam_display".to_string(), "#Status_InGame".to_string());
    client.upload_rich_presence(440, presence).await?;
    
    // Clear rich presence
    client.clear_rich_presence(440).await?;
    
    // Get rich presence for other users
    let steam_ids = vec![SteamID::from_steam_id64(76561198000000000)];
    let presence = client.request_rich_presence(440, &steam_ids).await?;
    
    Ok(())
}

Idler Service

The IdlerHandle provides a way to automatically refresh your playing status at random intervals (15-30 minutes) to avoid bot detection patterns.

use steam_client::{SteamClient, services::IdlerHandle};

async fn idler_example(client: &mut SteamClient) -> Result<(), Box<dyn std::error::Error>> {
    // idle CS:GO (730) and TF2 (440)
    let mut idler = IdlerHandle::new(vec![730, 440]);
    
    // Set initial Playing status
    client.games_played(idler.app_ids().to_vec()).await?;
    
    // In your main event loop
    loop {
        tokio::select! {
            _ = idler.tick() => {
                // Time to refresh playing status
                if client.is_logged_in() {
                    println!("Refreshing idler status...");
                    client.games_played(idler.app_ids().to_vec()).await?;
                }
            }
            // ... handle other events ...
        }
    }
}

Apps Service

use steam_client::{SteamClient, AppInfoRequest};

async fn apps_example(client: &mut SteamClient) -> Result<(), Box<dyn std::error::Error>> {
    // Get app info (metadata, launch options, etc.)
    let apps = vec![
        AppInfoRequest { appid: 440, access_token: 0 },   // TF2
        AppInfoRequest { appid: 730, access_token: 0 },   // CS2
    ];
    let info = client.request_product_info(&apps, &[]).await?;
    
    // Get owned apps
    let owned = client.get_owned_apps().await?;
    println!("You own {} apps", owned.len());
    
    // Get player count for a game
    let count = client.get_player_count(440).await?;
    println!("TF2 has {} players online", count);
    
    // Get PICS changes since a change number
    let changes = client.get_product_changes(0).await?;

    // Request free licenses (e.g. free-to-play games)
    let app_ids = vec![440, 730]; // TF2, CS2
    let response = client.request_free_license(app_ids).await?;
    println!("Granted apps: {:?}", response.granted_appids);
    
    // Auto-request free licenses from a list (with filtering and shuffling)
    // Useful for bulk activation of free packages
    let free_apps = vec![440, 730, 570];
    client.auto_request_free_license(free_apps, 10).await?;
    
    Ok(())
}

Store Service

use steam_client::SteamClient;

async fn store_example(client: &mut SteamClient) -> Result<(), Box<dyn std::error::Error>> {
    // Get localized names for store tags
    let tag_ids = vec![19, 21, 122]; // Action, Adventure, RPG
    let tags = client.get_store_tag_names("english", &tag_ids).await?;
    
    for tag in tags {
        println!("Tag {}: {}", tag.tagid, tag.name);
    }
    
    Ok(())
}

App Auth Service

use steam_client::SteamClient;

async fn auth_ticket_example(client: &mut SteamClient) -> Result<(), Box<dyn std::error::Error>> {
    let app_id = 440; // TF2
    
    // Get app ownership ticket (for game servers)
    let ticket = client.get_app_ownership_ticket(app_id).await?;
    println!("Got ownership ticket: {} bytes", ticket.len());
    
    // Create encrypted app ticket (for 3rd party backend validation)
    let encrypted_ticket = client.create_encrypted_app_ticket(app_id, None).await?;
    println!("Got encrypted ticket: {} bytes", encrypted_ticket.len());
    
    Ok(())
}

CDN Service

use steam_client::SteamClient;

async fn cdn_example(client: &mut SteamClient) -> Result<(), Box<dyn std::error::Error>> {
    let app_id = 440;
    let depot_id = 441;
    
    // Get content servers
    let servers = client.get_content_servers(Some(app_id)).await?;
    println!("Found {} content servers", servers.len());
    
    // Get depot decryption key
    let key = client.get_depot_decryption_key(depot_id, app_id).await?;
    
    // Get CDN auth token
    let token = client.get_cdn_auth_token(depot_id, &servers[0].host).await?;
    
    // Get manifest
    let manifest = client.get_manifest(app_id, depot_id, manifest_id).await?;
    
    Ok(())
}

Account Service

use steam_client::SteamClient;

async fn account_example(client: &mut SteamClient) -> Result<(), Box<dyn std::error::Error>> {
    // Get account info
    let info = client.get_account_info().await?;
    println!("Account: {}", info.persona_name);
    
    // Get Steam Guard details
    let guard = client.get_steam_guard_details().await?;
    println!("Steam Guard enabled: {}", guard.is_steamguard_enabled);
    
    // Get wallet info
    let wallet = client.get_wallet_details().await?;
    println!("Balance: {} {}", wallet.balance, wallet.currency);
    
    // Get privacy settings
    let privacy = client.get_privacy_settings().await?;
    
    // Get account limitations (cached)
    let limits = client.get_account_limitations()?;
    println!("Limited account: {}", limits.is_limited);
    
    // Get VAC bans (cached)
    let vac = client.get_vac_bans()?;
    println!("VAC bans: {} (apps: {:?})", vac.num_bans, vac.app_ids);

    // Get credential change timestamps
    let creds = client.get_credential_change_times().await?;
    println!("Password last changed: {:?}", creds.password_last_changed);

    // Get account auth secret
    let (secret_id, secret) = client.get_auth_secret().await?;
    println!("Auth secret ID: {}", secret_id);
    
    // Get email info
    let email = client.get_email_info().await?;
    println!("Email: {} (verified: {})", email.email, email.validated);
    
    Ok(())
}

Two-Factor Service

use steam_client::SteamClient;

async fn twofactor_example(client: &mut SteamClient) -> Result<(), Box<dyn std::error::Error>> {
    // Enable 2FA (mobile authenticator)
    let secrets = client.enable_two_factor().await?;
    println!("Shared secret: {}", secrets.shared_secret);
    println!("Revocation code: {}", secrets.revocation_code);
    // IMPORTANT: Save these securely!
    
    // Finalize with SMS code
    client.finalize_two_factor(&secrets, "123456").await?;
    
    // Remove 2FA (requires revocation code)
    client.remove_two_factor("R12345").await?;
    
    Ok(())
}

Game Coordinator Service

use steam_client::{SteamClient, GCSendOptions};

async fn gc_example(client: &mut SteamClient) -> Result<(), Box<dyn std::error::Error>> {
    let app_id = 730; // CS2
    
    // Send a GC message
    let msg_type = 4006; // ClientHello
    let payload = vec![]; // Your protobuf-encoded message
    
    client.send_gc_message(app_id, msg_type, &payload, GCSendOptions::default()).await?;
    
    // GC responses come as AppsEvent::GCReceived events in the event loop
    
    Ok(())
}

CS:GO/CS2 Service

use steam_client::{SteamClient, LobbyConfig};
use steam_client::services::csgo::CSGOClient;
use steamid::SteamID;

async fn csgo_example(client: &mut SteamClient) -> Result<(), Box<dyn std::error::Error>> {
    let mut csgo = CSGOClient::new(client);
    
    // Send hello to GC
    csgo.send_hello().await?;
    
    // Party search
    let parties = csgo.party_search(true, 0).await?;
    for party in parties {
        println!("Party {} has {} members", party.id, party.members.len());
    }
    
    // Create a lobby
    let lobby_id = csgo.create_lobby(10, 0).await?;
    println!("Created lobby: {}", lobby_id);
    
    // Update lobby settings
    let config = LobbyConfig::new()
        .max_members(5)
        .lobby_type(1);
    csgo.update_lobby(lobby_id, &config).await?;
    
    // Invite a friend
    let friend_id = SteamID::from_steam_id64(76561198000000000);
    csgo.invite_to_lobby(lobby_id, friend_id).await?;
    
    // Join an existing lobby
    let lobby_info = csgo.join_lobby(lobby_id).await?;
    println!("Lobby owner: {:?}", lobby_info.owner_id);
    
    // Get lobby data
    let data = csgo.get_lobby_data(lobby_id).await?;
    println!("Members: {:?}", data.members);
    
    // Leave lobby
    csgo.leave_lobby(lobby_id).await?;
    
    Ok(())
}

Gift Service

use steam_client::SteamClient;

async fn gift_example(client: &SteamClient, cookies: &str) -> Result<(), Box<dyn std::error::Error>> {
    let gift_id = "1234567890";
    
    // Get gift details before redeeming
    let details = client.get_gift_details(gift_id, cookies).await?;
    println!("Gift: {} for {}", details.gift_name, details.app_name);
    
    if details.owned {
        println!("Warning: You already own this game!");
    } else {
        // Redeem the gift
        client.redeem_gift(gift_id, cookies).await?;
        println!("Gift redeemed successfully!");
    }
    
    Ok(())
}

Persona Cache

use steam_client::{SteamClient, cache::PersonaCache, cache::PersonaCacheConfig};
use steamid::SteamID;
use std::time::Duration;

async fn cache_example(client: &mut SteamClient) -> Result<(), Box<dyn std::error::Error>> {
    let steam_ids = vec![
        SteamID::from_steam_id64(76561198000000000),
        SteamID::from_steam_id64(76561198000000001),
    ];
    
    // Get personas with caching (uses internal cache)
    // Only queries Steam for missing/expired entries
    let personas = client.get_personas_cached(steam_ids.clone(), false).await?;
    println!("Got {} personas", personas.len());
    
    // Force refresh (bypasses cache)
    let personas = client.get_personas_cached(steam_ids, true).await?;
    
    // Direct cache access for advanced usage
    let config = PersonaCacheConfig {
        ttl: Duration::from_secs(300),    // 5 minute TTL
        max_entries: 1000,
    };
    let cache = PersonaCache::new(config);
    
    // Bulk lookup
    let (found, missing) = cache.get_many(&steam_ids);
    println!("Found: {}, Missing: {}", found.len(), missing.len());
    
    // Clear expired entries
    cache.evict_expired();
    
    Ok(())
}

Error Handling

use steam_client::{SteamClient, SteamError, EResult};

async fn handle_errors(client: &mut SteamClient) {
    match client.log_on(details).await {
        Ok(response) => {
            println!("Success!");
        }
        Err(SteamError::SteamResult(result)) => {
            // Steam returned an error code
            match result {
                EResult::InvalidPassword => println!("Wrong password"),
                EResult::AccountDisabled => println!("Account is disabled"),
                EResult::RateLimitExceeded => println!("Too many attempts"),
                _ => println!("Steam error: {:?}", result),
            }
        }
        Err(SteamError::SteamGuardRequired { guard_type, email_domain }) => {
            println!("Need Steam Guard code: {:?}", guard_type);
        }
        Err(SteamError::ConnectionError(msg)) => {
            println!("Connection failed: {}", msg);
        }
        Err(SteamError::Timeout) => {
            println!("Operation timed out");
        }
        Err(SteamError::NotConnected) => {
            println!("Not connected to Steam");
        }
        Err(e) => {
            println!("Other error: {:?}", e);
        }
    }
}

LogOnResponse

After successful login, you receive:

pub struct LogOnResponse {
    pub eresult: EResult,           // Result code (OK on success)
    pub steam_id: SteamID,          // Your SteamID
    pub public_ip: Option<String>,  // Your public IP as seen by Steam
    pub cell_id: u32,               // Cell ID for content servers
    pub vanity_url: Option<String>, // Your vanity URL
    pub email_domain: Option<String>, // Email domain hint
    pub steam_guard_required: bool, // Whether Steam Guard is enabled
}

Examples

Run the examples with:

# Basic bot - login, set status, play game

REFRESH_TOKEN="your_token" cargo run --example basic_bot


# Chat bot - respond to messages

REFRESH_TOKEN="your_token" cargo run --example chat_bot


# Friends list - dump all friends info

REFRESH_TOKEN="your_token" cargo run --example friends_list


# Anonymous login - no credentials needed

cargo run --example anonymous_login


# Password login - interactive with Steam Guard

cargo run --example password_login


# Licenses dumper - show owned games/packages

REFRESH_TOKEN="your_token" cargo run --example licenses_dumper


# Rich presence - set custom game status

REFRESH_TOKEN="your_token" cargo run --example rich_presence


# Token dumper - get refresh token from password login

cargo run --example token_dumper

Services Summary

Service Module Key Types
Account services::account AccountInfo, AccountLimitations, WalletInfo, VacBans, PrivacySettings
App Auth services::appauth AuthSessionTicket, AuthSessionResult
Apps services::apps AppInfo, PackageInfo, OwnedApp
CDN services::cdn ContentServer, DepotManifest, ManifestFile, CdnAuthToken
Chat services::chat ChatMessage, HistoryMessage, SendMessageResult
Chat Rooms services::chatroom ChatRoom, ChatRoomGroup, ChatRoomMember, ChatRole
CS:GO/CS2 services::csgo CSGOClient, LobbyConfig, LobbyData, JoinLobbyResult, LobbyMetadata
Economy services::econ AssetClass, AssetClassInfo, TradeUrl, Emoticon, ProfileItem
Family Sharing services::familysharing AuthorizedBorrower, AuthorizedDevice
Friends services::friends Friend, FriendsGroup, AddFriendResult
Game Coordinator services::gc GCMessage, GCProtoHeader, GCSendOptions, GCJobManager
Game Servers services::gameservers GameServer
Gifts services::gifts GiftDetails
Idler services::idler IdlerService
Notifications services::notifications Notification, NotificationType
Persona Cache cache::persona PersonaCache, PersonaCacheConfig, CachedPersona
Published Files services::pubfiles PublishedFileDetails, VoteData
Rich Presence services::rich_presence RichPresenceData
Store services::store StoreTag
Trading services::trading TradeRestrictions
Two-Factor services::twofactor TwoFactorSecrets

Related Crates

Crate Description
steamid-rs Steam ID parsing and formatting
steam-enums Steam protocol enumerations
steam-protos Protobuf message definitions
steam-crypto-rs Cryptographic utilities (AES, RSA)
steam-auth-rs Authentication session management
steam-totp-rs TOTP code generation for Steam Guard
steam-cm-provider CM server discovery
steam-friend-code CS2 friend code encoding/decoding

Testing

The crate includes comprehensive testing infrastructure:

use steam_client::{SteamClientBuilder, MockClock, MockHttpClient, MockRng};

// Create client with mock dependencies for testing
let (client, mocks) = SteamClient::builder()
    .with_options(Default::default())
    .build_with_mocks();

// Control mocks in tests
if let Some(clock) = mocks.clock {
    clock.advance(Duration::from_secs(10));
}

License

MIT