use std::io::{Result}; use std::net::{ToSocketAddrs}; use std::{env}; use mcstatusface::http::{HttpServer, StatusCode}; use mcstatusface::{MinecraftStatus}; #[derive(serde::Serialize)] struct MinecraftStatusResponse<'a> { version: &'a String, players: u32, max_players: u32, motd: String, } fn main() -> Result<()> { let args: Vec = env::args().collect(); if args.len() < 2 { println!( r#"Crafty McStatusFace, v{} - made with <3 by ari melody Host a web API: $ mcstatusface serve [address[:port]] Query a server: $ mcstatusface "#, env!("CARGO_PKG_VERSION")); std::process::exit(0); } if args[1] != "serve" { let mut address = String::from(args[1].as_str()); if !address.contains(":") { address.push_str(":25565"); } let mut addrs_iter = address.to_socket_addrs().unwrap(); let address = addrs_iter.next().unwrap(); let status = MinecraftStatus::fetch(address).unwrap(); println!("Version: {} ({})", status.version.name, status.version.protocol); println!("Players: {}/{}", status.players.online, status.players.max); println!("MOTD:"); println!("{}", status.parse_description()); return Ok(()); } let mut address = "0.0.0.0:8080".to_string(); if args.len() > 2 { address = args[2].to_string() } HttpServer::new(address, 64).start(|request, mut response| { response.status(StatusCode::OK); response.set_header("Content-Type", "text/plain".to_string()); response.set_header("x-powered-by", "GIRL FUEL".to_string()); if request.method() != "GET" { response.status(StatusCode::NotFound); return response.send() } if request.path() == "/style/index.css" { let content = include_str!("public/style/index.css"); response.set_header("Content-Type", "text/css".to_string()); response.status(StatusCode::OK); response.body(content.to_string()); return response.send(); } if request.path() == "/" { if request.headers().get("Accept").is_some_and( |accept| accept.contains("text/html") ) { // HTML response let content = include_str!("views/index.html"); response.set_header("Content-Type", "text/html".to_string()); response.status(StatusCode::OK); let query_response: String; match request.query().get("s") { Some(query_address) => { let mut address = query_address.to_string(); if !address.contains(":") { address.push_str(":25565"); } match address.to_socket_addrs() { Err(_) => { response.set_header("Content-Type", "text/html".to_string()); response.status(StatusCode::BadRequest); response.body("Server address is invalid or unreachable.\n".to_string()); return response.send(); } Ok(mut addrs_iter) => { let address = addrs_iter.next().unwrap(); let status = MinecraftStatus::fetch(address).unwrap(); let minecraft_status = MinecraftStatusResponse{ version: &status.version.name, players: status.players.online, max_players: status.players.max, motd: status.parse_description(), }; query_response = format!( "

Server Details

Version: {}
Players: {}/{}
MOTD:

{}
", sanitize_html(minecraft_status.version).to_string(), minecraft_status.players, minecraft_status.max_players, sanitize_html(&minecraft_status.motd).to_string(), ); } } } None => { query_response = String::from(""); } } let response_content = content .replace("{{response}}", &query_response) .replace("{{host}}", match request.headers().get("Host") { Some(host) => { host } None => { "mcq.bliss.town" } }); response.body(response_content.to_string()); return response.send(); } // JSON response match request.query().get("s") { None => { response.status(StatusCode::BadRequest); response.body("?s=\n".to_string()); return response.send(); } Some(query_address) => { let mut address = query_address.to_string(); if !address.contains(":") { address.push_str(":25565"); } let mut addrs_iter = address.to_socket_addrs().unwrap(); let address = addrs_iter.next().unwrap(); let status = MinecraftStatus::fetch(address).unwrap(); let minecraft_status = MinecraftStatusResponse{ version: &status.version.name, players: status.players.online, max_players: status.players.max, motd: status.parse_description(), }; match serde_json::to_string(&minecraft_status) { Ok(json) => { response.status(StatusCode::OK); response.set_header("Content-Type", "application/json".to_string()); response.body(json + "\n"); } Err(e) => { eprintln!("Request to {address} failed: {e}"); response.status(StatusCode::InternalServerError); response.body("Unable to reach the requested server.\n".to_string()); } } return response.send() } } } response.status(StatusCode::NotFound); response.body("Not Found".to_string()); return response.send(); }).unwrap(); Ok(()) } fn sanitize_html(input: &String) -> String { input .replace("&", "&") .replace("<", "<") .replace(">", ">") .replace("\"", """) .replace("'", "'") }