steam-client-rs

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
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);
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());
let response = client.log_on(LogOnDetails::anonymous()).await?;
println!("Connected as anonymous user: {}", response.steam_id.steam3());
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());
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:");
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 {
protocol: EConnectionProtocol::Auto,
auto_relogin: true,
data_directory: Some("./steam_data".to_string()),
web_compatibility_mode: false,
http_proxy: None,
renew_refresh_tokens: false,
machine_name: Some("MyRustBot".to_string()),
reconnect: ReconnectConfig {
enabled: true,
max_attempts: 5,
initial_delay: Duration::from_secs(1),
max_delay: Duration::from_secs(30),
backoff_multiplier: 2.0,
},
heartbeat: HeartbeatOptions {
enabled: true,
interval: Duration::from_secs(30),
},
};
let client = SteamClient::new(options);
LogOnDetails
use steam_client::LogOnDetails;
let details = LogOnDetails {
refresh_token: Some("your_refresh_token".to_string()),
..Default::default()
};
let details = LogOnDetails::anonymous();
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, two_factor_code: None, machine_auth_token: None, logon_id: Some(12345), machine_name: Some("MyBot".to_string()), };
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 {
SteamEvent::Auth(AuthEvent::LoggedOn { steam_id }) => {
println!("✅ Logged in as {}", steam_id.steam3());
}
SteamEvent::Auth(AuthEvent::RefreshToken { token, account_name }) => {
println!("💾 New refresh token for {}", account_name);
save_token(&account_name, &token);
}
SteamEvent::Auth(AuthEvent::LoggedOff { result }) => {
println!("👋 Logged off: {:?}", result);
break;
}
SteamEvent::Friends(FriendsEvent::FriendsList { friends, .. }) => {
println!("👥 Friends list: {} friends", friends.len());
}
SteamEvent::Friends(FriendsEvent::PersonaState(persona)) => {
println!("👤 {} is {:?}", persona.player_name, persona.persona_state);
}
SteamEvent::Chat(ChatEvent::FriendMessage { sender, message, .. }) => {
println!("💬 {}: {}", sender.steam3(), message);
}
SteamEvent::Chat(ChatEvent::FriendTyping { sender }) => {
println!("✏️ {} is typing...", sender.steam3());
}
SteamEvent::Apps(AppsEvent::LicenseList { licenses }) => {
println!("📦 {} licenses owned", licenses.len());
}
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 => {
println!("⏰ Heartbeat");
}
}
}
Ok(())
}
fn save_token(account: &str, token: &str) {
}
Event Helpers
use steam_client::SteamEvent;
event.is_auth(); event.is_connection(); event.is_friends(); event.is_chat(); event.is_apps(); event.is_content(); event.is_system();
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>> {
client.set_persona(EPersonaState::Online, Some("Playing Rust 🦀".to_string())).await?;
let steam_ids = vec![SteamID::from_steam_id64(76561198000000000)];
let personas = client.get_personas(&steam_ids).await?;
let result = client.add_friend(steam_ids[0]).await?;
println!("Add friend result: {:?}", result);
let nicknames = client.get_nicknames().await?;
client.set_nickname(steam_ids[0], "My Friend").await?;
let levels = client.get_steam_levels(&steam_ids).await?;
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);
let result = client.send_friend_message(friend, "Hello from Rust! 🦀").await?;
println!("Message sent, timestamp: {:?}", result.modified_timestamp);
client.send_friend_typing(friend).await?;
let history = client.get_recent_messages(friend, 50).await?;
for msg in history {
println!("[{}] {}: {}", msg.timestamp, msg.sender.steam3(), msg.message);
}
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>> {
client.games_played(vec![440]).await?;
client.games_played(vec![440, 730, 570]).await?;
client.games_played(vec![]).await?;
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?;
client.clear_rich_presence(440).await?;
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>> {
let mut idler = IdlerHandle::new(vec![730, 440]);
client.games_played(idler.app_ids().to_vec()).await?;
loop {
tokio::select! {
_ = idler.tick() => {
if client.is_logged_in() {
println!("Refreshing idler status...");
client.games_played(idler.app_ids().to_vec()).await?;
}
}
}
}
}
Apps Service
use steam_client::{SteamClient, AppInfoRequest};
async fn apps_example(client: &mut SteamClient) -> Result<(), Box<dyn std::error::Error>> {
let apps = vec![
AppInfoRequest { appid: 440, access_token: 0 }, AppInfoRequest { appid: 730, access_token: 0 }, ];
let info = client.request_product_info(&apps, &[]).await?;
let owned = client.get_owned_apps().await?;
println!("You own {} apps", owned.len());
let count = client.get_player_count(440).await?;
println!("TF2 has {} players online", count);
let changes = client.get_product_changes(0).await?;
let app_ids = vec![440, 730]; let response = client.request_free_license(app_ids).await?;
println!("Granted apps: {:?}", response.granted_appids);
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>> {
let tag_ids = vec![19, 21, 122]; 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;
let ticket = client.get_app_ownership_ticket(app_id).await?;
println!("Got ownership ticket: {} bytes", ticket.len());
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;
let servers = client.get_content_servers(Some(app_id)).await?;
println!("Found {} content servers", servers.len());
let key = client.get_depot_decryption_key(depot_id, app_id).await?;
let token = client.get_cdn_auth_token(depot_id, &servers[0].host).await?;
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>> {
let info = client.get_account_info().await?;
println!("Account: {}", info.persona_name);
let guard = client.get_steam_guard_details().await?;
println!("Steam Guard enabled: {}", guard.is_steamguard_enabled);
let wallet = client.get_wallet_details().await?;
println!("Balance: {} {}", wallet.balance, wallet.currency);
let privacy = client.get_privacy_settings().await?;
let limits = client.get_account_limitations()?;
println!("Limited account: {}", limits.is_limited);
let vac = client.get_vac_bans()?;
println!("VAC bans: {} (apps: {:?})", vac.num_bans, vac.app_ids);
let creds = client.get_credential_change_times().await?;
println!("Password last changed: {:?}", creds.password_last_changed);
let (secret_id, secret) = client.get_auth_secret().await?;
println!("Auth secret ID: {}", secret_id);
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>> {
let secrets = client.enable_two_factor().await?;
println!("Shared secret: {}", secrets.shared_secret);
println!("Revocation code: {}", secrets.revocation_code);
client.finalize_two_factor(&secrets, "123456").await?;
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;
let msg_type = 4006; let payload = vec![];
client.send_gc_message(app_id, msg_type, &payload, GCSendOptions::default()).await?;
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);
csgo.send_hello().await?;
let parties = csgo.party_search(true, 0).await?;
for party in parties {
println!("Party {} has {} members", party.id, party.members.len());
}
let lobby_id = csgo.create_lobby(10, 0).await?;
println!("Created lobby: {}", lobby_id);
let config = LobbyConfig::new()
.max_members(5)
.lobby_type(1);
csgo.update_lobby(lobby_id, &config).await?;
let friend_id = SteamID::from_steam_id64(76561198000000000);
csgo.invite_to_lobby(lobby_id, friend_id).await?;
let lobby_info = csgo.join_lobby(lobby_id).await?;
println!("Lobby owner: {:?}", lobby_info.owner_id);
let data = csgo.get_lobby_data(lobby_id).await?;
println!("Members: {:?}", data.members);
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";
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 {
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),
];
let personas = client.get_personas_cached(steam_ids.clone(), false).await?;
println!("Got {} personas", personas.len());
let personas = client.get_personas_cached(steam_ids, true).await?;
let config = PersonaCacheConfig {
ttl: Duration::from_secs(300), max_entries: 1000,
};
let cache = PersonaCache::new(config);
let (found, missing) = cache.get_many(&steam_ids);
println!("Found: {}, Missing: {}", found.len(), missing.len());
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)) => {
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, pub steam_id: SteamID, pub public_ip: Option<String>, pub cell_id: u32, pub vanity_url: Option<String>, pub email_domain: Option<String>, pub steam_guard_required: bool, }
Examples
Run the examples with:
REFRESH_TOKEN="your_token" cargo run --example basic_bot
REFRESH_TOKEN="your_token" cargo run --example chat_bot
REFRESH_TOKEN="your_token" cargo run --example friends_list
cargo run --example anonymous_login
cargo run --example password_login
REFRESH_TOKEN="your_token" cargo run --example licenses_dumper
REFRESH_TOKEN="your_token" cargo run --example rich_presence
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
Testing
The crate includes comprehensive testing infrastructure:
use steam_client::{SteamClientBuilder, MockClock, MockHttpClient, MockRng};
let (client, mocks) = SteamClient::builder()
.with_options(Default::default())
.build_with_mocks();
if let Some(clock) = mocks.clock {
clock.advance(Duration::from_secs(10));
}
License
MIT