A Rust client for the Rust+ companion app protocol. This library provides a native, asynchronous interface for interacting with Rust game servers.
- Asynchronous API: Built on
tokioandtokio-tungstenite. - Full Protocol Coverage: Support for server info, map markers, team chat, and entity control.
- Camera Support: Native decoding of camera rays into PNG frames using the
imagecrate. - Automatic Rate Limiting: Transparently manages token buckets for IP and Player ID limits.
- Strongly Typed: Automatically generated Protobuf bindings via
prost.
To connect to a server, you must provide a valid player_token generated by the Rust server. The only automated way to retrieve this token dynamically (e.g., when a user clicks "Pair" in-game) is to register an Android FCM/GCM device and listen for the pairing push notification.
If you are building a Discord bot or application that requires users to pair servers dynamically, you will need to use our companion crate: push-receiver
push-receiver acts as a mock Android device to catch the pairing notifications and extract the tokens, which you can then pass into rustplus.
use push_receiver::PushReceiver;
use rustplus::RustPlusClient;
use serde_json::Value;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// 1. Connect to FCM using the Rust+ Sender ID
let (_receiver, mut rx) = PushReceiver::builder("1056586548777")
.connect()
.await?;
println!("Waiting for pairing notification (Click 'Pair' in Rust)...");
// 2. Listen for incoming push messages
while let Some(msg) = rx.recv().await {
for item in msg.app_data {
let val = item.value.trim();
if !val.starts_with('{') { continue; }
// 3. Parse the pairing payload
if let Ok(body) = serde_json::from_str::<Value>(val) {
if let (Some(ip), Some(port), Some(token), Some(steam_id)) = (
body["ip"].as_str(),
body["port"].as_u64(),
body["playerToken"].as_i64(),
body["playerId"].as_u64(),
) {
println!("Received pairing info for {}:{}", ip, port);
// 4. Use the extracted token to connect the RustPlusClient
let mut rp_client = RustPlusClient::new(
ip, port as u16, steam_id, token as i32, false
);
rp_client.connect().await?;
let info = rp_client.get_info().await?;
if let Some(resp) = info.response {
println!("Connected successfully to: {:?}", resp.get_info);
}
return Ok(());
}
}
}
}
Ok(())
}use rustplus::RustPlusClient;
#[tokio::main]
async fn main() -> rustplus::Result<()> {
let mut client = RustPlusClient::new(
"127.0.0.1", // Server IP
28082, // App Port
7656119..., // Steam ID
-12345678, // Player Token
false, // Use Facepunch Proxy
);
client.connect().await?;
let info = client.get_info().await?;
if let Some(resp) = info.response {
if let Some(get_info) = resp.get_info {
println!("Server Name: {}", get_info.name);
}
}
client.send_team_message("Hello from Rust!").await?;
Ok(())
}let mut camera = client.get_camera("DOME1");
camera.subscribe().await?;
let mut frames = camera.subscribe_frames();
while let Ok(png_bytes) = frames.recv().await {
// png_bytes contains the rendered frame
}The client respects the following default server limits:
- IP Limit: 50 tokens, 15/sec replenishment.
- Player Limit: 25 tokens, 3/sec replenishment.