Created
July 7, 2025 01:22
-
-
Save Stankye/afb77591abaffd8f9367c73216c88e1b to your computer and use it in GitHub Desktop.
ChatGPT Genned
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/////////////////////// | |
// SERVER (Axum API) // | |
/////////////////////// | |
// File: server/src/main.rs | |
use axum::{extract::State, http::StatusCode, routing::post, Router}; | |
use qbittorrent_rust::{Credentials, QbitApi}; | |
use std::{net::SocketAddr, sync::Arc}; | |
use tokio::sync::Mutex; | |
use tracing::{error, info}; | |
use tracing_subscriber; | |
#[tokio::main] | |
async fn main() { | |
tracing_subscriber::fmt::init(); | |
let creds = Credentials::new("admin", "adminadmin"); | |
let api = match QbitApi::new("http://localhost:8080", creds).await { | |
Ok(api) => Arc::new(Mutex::new(api)), | |
Err(e) => { | |
error!(?e, "Failed to initialize qBittorrent API"); | |
std::process::exit(1); | |
} | |
}; | |
let app = Router::new() | |
.route("/control", post(control_handler)) | |
.with_state(api); | |
let addr = SocketAddr::from(([0, 0, 0, 0], 5000)); | |
info!("Listening on {}", addr); | |
axum::Server::bind(&addr) | |
.serve(app.into_make_service()) | |
.await | |
.unwrap(); | |
} | |
async fn control_handler( | |
State(api): State<Arc<Mutex<QbitApi>>>, | |
body: String, | |
) -> StatusCode { | |
let mut api = api.lock().await; | |
match body.as_str() { | |
"restrict" => { | |
if api.transfer().set_global_upload_limit(50 * 1024).await.is_ok() | |
&& api.transfer().set_global_download_limit(100 * 1024).await.is_ok() | |
{ | |
StatusCode::NO_CONTENT | |
} else { | |
error!("Failed to restrict bandwidth"); | |
StatusCode::INTERNAL_SERVER_ERROR | |
} | |
} | |
"unrestrict" => { | |
if api.transfer().set_global_upload_limit(0).await.is_ok() | |
&& api.transfer().set_global_download_limit(0).await.is_ok() | |
{ | |
StatusCode::NO_CONTENT | |
} else { | |
error!("Failed to unrestrict bandwidth"); | |
StatusCode::INTERNAL_SERVER_ERROR | |
} | |
} | |
_ => StatusCode::BAD_REQUEST, | |
} | |
} | |
/////////////////////// | |
// Dockerfile (server) | |
/////////////////////// | |
# File: server/Dockerfile | |
FROM rust:1.77 as builder | |
WORKDIR /usr/src/app | |
COPY . . | |
RUN cargo build --release | |
FROM debian:buster-slim | |
WORKDIR /app | |
COPY --from=builder /usr/src/app/target/release/server /app/server | |
CMD ["/app/server"] | |
////////////////////////// | |
// Client (Windows Rust) // | |
////////////////////////// | |
// File: client/src/main.rs | |
use reqwest::blocking::Client; | |
use std::{thread, time::Duration}; | |
use sysinfo::{ProcessExt, System, SystemExt}; | |
use tracing::{info, error}; | |
use tracing_subscriber; | |
const GAMES: &[&str] = &["eldenring.exe", "witcher3.exe", "cs2.exe"]; | |
const SERVER_URL: &str = "http://192.168.1.100:5000/control"; | |
const POLL_INTERVAL: Duration = Duration::from_secs(10); | |
fn is_game_running(sys: &System) -> bool { | |
for process in sys.processes().values() { | |
let name = process.name().to_lowercase(); | |
if GAMES.iter().any(|g| name.contains(g)) { | |
return true; | |
} | |
} | |
false | |
} | |
fn main() { | |
tracing_subscriber::fmt().init(); | |
let client = Client::new(); | |
let mut sys = System::new_all(); | |
let mut game_was_running = false; | |
loop { | |
sys.refresh_processes(); | |
let game_running = is_game_running(&sys); | |
if game_running && !game_was_running { | |
info!("Game detected, sending restrict"); | |
if client.post(SERVER_URL).body("restrict").send().is_err() { | |
error!("Failed to send restrict"); | |
} | |
game_was_running = true; | |
} else if !game_running && game_was_running { | |
info!("No game running, sending unrestrict"); | |
if client.post(SERVER_URL).body("unrestrict").send().is_err() { | |
error!("Failed to send unrestrict"); | |
} | |
game_was_running = false; | |
} | |
thread::sleep(POLL_INTERVAL); | |
} | |
} | |
///////////////////// | |
// Cargo.toml Guide | |
///////////////////// | |
# File: Cargo.toml (shared for both) | |
[workspace] | |
members = ["server", "client"] | |
# In server/Cargo.toml: | |
[package] | |
name = "server" | |
version = "0.1.0" | |
edition = "2021" | |
[dependencies] | |
axum = "0.7" | |
tokio = { version = "1", features = ["full"] } | |
qbittorrent-rust = "0.1.3" | |
tracing = "0.1" | |
tracing-subscriber = "0.3" | |
# In client/Cargo.toml: | |
[package] | |
name = "client" | |
version = "0.1.0" | |
edition = "2021" | |
[dependencies] | |
reqwest = { version = "0.12", features = ["blocking"] } | |
sysinfo = "0.30" | |
tracing = "0.1" | |
tracing-subscriber = "0.3" | |
///////////////////// | |
// .cargo/config.toml | |
///////////////////// | |
# Optimize client for small binary size | |
# File: client/.cargo/config.toml | |
[profile.release] | |
opt-level = "z" | |
strip = true | |
lto = true | |
codegen-units = 1 | |
panic = "abort" | |
[target.'cfg(windows)'] | |
rustflags = ["-C", "target-feature=+crt-static"] | |
///////////////////////// | |
// Background Setup (Win) | |
///////////////////////// | |
// Use NSSM (Non-Sucking Service Manager) or Windows Task Scheduler: | |
// nssm install GameQoSDetector "C:\path\to\client.exe" | |
// Or run via Task Scheduler with "Run whether user is logged in or not" | |
// and trigger at logon or on startup. |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment