Compare commits
No commits in common. "main" and "v1.0.0" have entirely different histories.
8 changed files with 63 additions and 359 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -118,7 +118,7 @@ checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mcstatusface"
|
name = "mcstatusface"
|
||||||
version = "1.1.1"
|
version = "1.0.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"chrono",
|
"chrono",
|
||||||
"serde",
|
"serde",
|
||||||
|
|
|
@ -4,7 +4,7 @@ authors = ["ari melody <ari@arimelody.me>"]
|
||||||
repository = "https://git.arimelody.me/ari/mcstatusface"
|
repository = "https://git.arimelody.me/ari/mcstatusface"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
keywords = ["minecraft", "server", "query", "web"]
|
keywords = ["minecraft", "server", "query", "web"]
|
||||||
version = "1.1.1"
|
version = "1.0.0"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
|
15
README.md
15
README.md
|
@ -3,18 +3,11 @@
|
||||||
A light application that serves Minecraft server query information in a
|
A light application that serves Minecraft server query information in a
|
||||||
convenient format!
|
convenient format!
|
||||||
|
|
||||||
For more information, see https://minecraft.wiki/w/Query
|
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
McStatusFace can simply be run with `mcstatusface <address:[port]>`.
|
McStatusFace can be run as a web server with `./mcstatusface serve`. This will
|
||||||
|
provide server information in JSON format to requests on `GET /?s=<address[:port]>`.
|
||||||
|
(e.g. `curl -sS "127.0.0.1:8080?s=127.0.0.1:25565" | jq .`)
|
||||||
|
|
||||||
Alternatively, you can start a web interface with
|
Alternatively, you can simply run `./mcstatusface <address[:port]>`, and the
|
||||||
`mcstatusface serve [address:[port]]`.
|
|
||||||
|
|
||||||
This provides a web interface to query server information via a frontend, or
|
|
||||||
in JSON format if the request is cURLed.
|
|
||||||
|
|
||||||
The server queries Minecraft servers when a `GET /?s=<address[:port]>` request
|
|
||||||
is received. (e.g. `curl -sS "127.0.0.1:8080?s=127.0.0.1:25565" | jq .`)
|
|
||||||
tool will provide server details in plain-text format.
|
tool will provide server details in plain-text format.
|
||||||
|
|
63
src/http.rs
63
src/http.rs
|
@ -1,10 +1,4 @@
|
||||||
use std::{
|
use std::{collections::HashMap, io::{BufRead, BufReader, Result, Write}, net::{SocketAddr, TcpListener, TcpStream}};
|
||||||
collections::HashMap,
|
|
||||||
io::{BufRead, BufReader, Error, ErrorKind, Result, Write},
|
|
||||||
net::{IpAddr, SocketAddr, TcpListener, TcpStream},
|
|
||||||
str::FromStr,
|
|
||||||
sync::Arc,
|
|
||||||
};
|
|
||||||
|
|
||||||
use chrono::Local;
|
use chrono::Local;
|
||||||
|
|
||||||
|
@ -48,27 +42,22 @@ pub struct Request<'a> {
|
||||||
path: &'a str,
|
path: &'a str,
|
||||||
method: &'a str,
|
method: &'a str,
|
||||||
version: &'a str,
|
version: &'a str,
|
||||||
headers: HashMap<String, &'a str>,
|
headers: HashMap<&'a str, &'a str>,
|
||||||
query: HashMap<&'a str, &'a str>,
|
query: HashMap<&'a str, &'a str>,
|
||||||
body: Option<String>,
|
body: Option<String>,
|
||||||
real_address: IpAddr,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Request<'a> {
|
impl<'a> Request<'a> {
|
||||||
pub fn new(stream: &'a TcpStream, lines: &'a Vec<String>, trusted_proxies: Vec<IpAddr>) -> Result<Request<'a>> {
|
pub fn new(stream: &'a TcpStream, lines: &'a Vec<String>) -> Result<Request<'a>> {
|
||||||
let request_line = lines[0].as_str();
|
let request_line = lines[0].as_str();
|
||||||
let request_line_split: Vec<&str> = request_line.split(" ").collect();
|
let request_line_split: Vec<&str> = request_line.split(" ").collect();
|
||||||
if request_line_split.len() < 3 {
|
|
||||||
return Err(Error::new(ErrorKind::Other, "invalid request start-line"));
|
|
||||||
}
|
|
||||||
let method = request_line_split[0];
|
let method = request_line_split[0];
|
||||||
let mut path = request_line_split[1];
|
let path = request_line_split[1];
|
||||||
let version = request_line_split[2];
|
let version = request_line_split[2];
|
||||||
|
|
||||||
let mut query: HashMap<&'a str, &'a str> = HashMap::new();
|
let mut query: HashMap<&'a str, &'a str> = HashMap::new();
|
||||||
match request_line_split[1].split_once("?") {
|
match path.split_once("?") {
|
||||||
Some((path_without_query, query_string)) => {
|
Some((_, query_string)) => {
|
||||||
path = path_without_query;
|
|
||||||
let query_splits: Vec<&'a str> = query_string.split("&").collect();
|
let query_splits: Vec<&'a str> = query_string.split("&").collect();
|
||||||
for pair in query_splits {
|
for pair in query_splits {
|
||||||
match pair.split_once("=") {
|
match pair.split_once("=") {
|
||||||
|
@ -82,7 +71,7 @@ impl<'a> Request<'a> {
|
||||||
None => {},
|
None => {},
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut headers: HashMap<String, &'a str> = HashMap::new();
|
let mut headers: HashMap<&'a str, &'a str> = HashMap::new();
|
||||||
if lines.len() > 1 {
|
if lines.len() > 1 {
|
||||||
let mut i: usize = 1;
|
let mut i: usize = 1;
|
||||||
loop {
|
loop {
|
||||||
|
@ -90,27 +79,11 @@ impl<'a> Request<'a> {
|
||||||
let line = &lines[i];
|
let line = &lines[i];
|
||||||
if line.len() == 0 || !line.contains(":") { break; }
|
if line.len() == 0 || !line.contains(":") { break; }
|
||||||
let (name, value) = line.split_once(":").unwrap();
|
let (name, value) = line.split_once(":").unwrap();
|
||||||
headers.insert(name.to_lowercase(), value.trim());
|
headers.insert(name, value.trim());
|
||||||
i += 1;
|
i += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut real_address = IpAddr::from(stream.peer_addr().unwrap().ip());
|
|
||||||
|
|
||||||
headers.get("X-Forwarded-For").inspect(|address| {
|
|
||||||
match IpAddr::from_str(address) {
|
|
||||||
Ok(address) => {
|
|
||||||
for proxy in trusted_proxies {
|
|
||||||
if real_address == proxy {
|
|
||||||
real_address = address;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(_) => {}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
let mut body: Option<String> = None;
|
let mut body: Option<String> = None;
|
||||||
if lines.len() > headers.len() + 2 &&
|
if lines.len() > headers.len() + 2 &&
|
||||||
(method == "POST" || method == "PUT") &&
|
(method == "POST" || method == "PUT") &&
|
||||||
|
@ -127,16 +100,12 @@ impl<'a> Request<'a> {
|
||||||
headers,
|
headers,
|
||||||
query,
|
query,
|
||||||
body,
|
body,
|
||||||
real_address,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn address(&self) -> Result<SocketAddr> {
|
pub fn address(&self) -> Result<SocketAddr> {
|
||||||
self.stream.peer_addr()
|
self.stream.peer_addr()
|
||||||
}
|
}
|
||||||
pub fn real_address(&self) -> &IpAddr {
|
|
||||||
&self.real_address
|
|
||||||
}
|
|
||||||
pub fn path(&self) -> &'a str {
|
pub fn path(&self) -> &'a str {
|
||||||
self.path
|
self.path
|
||||||
}
|
}
|
||||||
|
@ -149,7 +118,7 @@ impl<'a> Request<'a> {
|
||||||
pub fn body(&self) -> &Option<String> {
|
pub fn body(&self) -> &Option<String> {
|
||||||
&self.body
|
&self.body
|
||||||
}
|
}
|
||||||
pub fn headers(&self) -> &HashMap<String, &'a str> {
|
pub fn headers(&self) -> &HashMap<&'a str, &'a str> {
|
||||||
&self.headers
|
&self.headers
|
||||||
}
|
}
|
||||||
pub fn query(&self) -> &HashMap<&'a str, &'a str> {
|
pub fn query(&self) -> &HashMap<&'a str, &'a str> {
|
||||||
|
@ -214,12 +183,11 @@ impl<'a> Response<'a> {
|
||||||
pub struct HttpServer {
|
pub struct HttpServer {
|
||||||
address: String,
|
address: String,
|
||||||
port: u16,
|
port: u16,
|
||||||
trusted_proxies: Arc<Vec<IpAddr>>,
|
|
||||||
max_connections: usize,
|
max_connections: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl HttpServer {
|
impl HttpServer {
|
||||||
pub fn new(address: String, max_connections: usize, trusted_proxies: Vec<IpAddr>) -> HttpServer {
|
pub fn new(address: String, max_connections: usize) -> HttpServer {
|
||||||
let mut _address = address.clone();
|
let mut _address = address.clone();
|
||||||
let mut _port: u16 = 8080;
|
let mut _port: u16 = 8080;
|
||||||
match address.split_once(":") {
|
match address.split_once(":") {
|
||||||
|
@ -232,7 +200,6 @@ impl HttpServer {
|
||||||
HttpServer {
|
HttpServer {
|
||||||
address: _address,
|
address: _address,
|
||||||
port: _port,
|
port: _port,
|
||||||
trusted_proxies: Arc::new(trusted_proxies),
|
|
||||||
max_connections,
|
max_connections,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -246,9 +213,8 @@ impl HttpServer {
|
||||||
for stream in listener.incoming() {
|
for stream in listener.incoming() {
|
||||||
match stream {
|
match stream {
|
||||||
Ok(stream) => {
|
Ok(stream) => {
|
||||||
let trusted_proxies = self.trusted_proxies.clone();
|
|
||||||
pool.execute(move || {
|
pool.execute(move || {
|
||||||
HttpServer::handle_client(&stream, handler, trusted_proxies);
|
HttpServer::handle_client(&stream, handler);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
|
@ -260,7 +226,7 @@ impl HttpServer {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_client(stream: &TcpStream, handler: HttpHandlerFunc, trusted_proxies: Arc<Vec<IpAddr>>) {
|
fn handle_client(stream: &TcpStream, handler: HttpHandlerFunc) {
|
||||||
let buf_reader = BufReader::new(stream);
|
let buf_reader = BufReader::new(stream);
|
||||||
let http_request: Vec<String> = buf_reader
|
let http_request: Vec<String> = buf_reader
|
||||||
.lines()
|
.lines()
|
||||||
|
@ -268,7 +234,7 @@ impl HttpServer {
|
||||||
.take_while(|line| !line.is_empty())
|
.take_while(|line| !line.is_empty())
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let request = Request::new(stream, &http_request, trusted_proxies.to_vec());
|
let request = Request::new(stream, &http_request);
|
||||||
if request.is_err() {
|
if request.is_err() {
|
||||||
eprintln!("Failed to process request: {}", request.err().unwrap());
|
eprintln!("Failed to process request: {}", request.err().unwrap());
|
||||||
return;
|
return;
|
||||||
|
@ -281,7 +247,6 @@ impl HttpServer {
|
||||||
match handler(&request, response) {
|
match handler(&request, response) {
|
||||||
Ok(status) => {
|
Ok(status) => {
|
||||||
let end_date = Local::now();
|
let end_date = Local::now();
|
||||||
|
|
||||||
println!(
|
println!(
|
||||||
"[{}] {} {} {} - {} {} - {}ms - {} ({})",
|
"[{}] {} {} {} - {} {} - {}ms - {} ({})",
|
||||||
start_date.format("%Y-%m-%d %H:%M:%S"),
|
start_date.format("%Y-%m-%d %H:%M:%S"),
|
||||||
|
@ -296,7 +261,7 @@ impl HttpServer {
|
||||||
(end_date - start_date).num_milliseconds(),
|
(end_date - start_date).num_milliseconds(),
|
||||||
|
|
||||||
request.headers().get("User-Agent").map_or("[]", |v| v),
|
request.headers().get("User-Agent").map_or("[]", |v| v),
|
||||||
request.real_address().to_string(),
|
request.address().unwrap().ip(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
|
|
224
src/main.rs
224
src/main.rs
|
@ -1,32 +1,15 @@
|
||||||
use std::io::{Result};
|
use std::io::{Result};
|
||||||
use std::net::{IpAddr, ToSocketAddrs};
|
use std::net::{ToSocketAddrs};
|
||||||
use std::str::FromStr;
|
use std::env;
|
||||||
use std::{env};
|
|
||||||
|
|
||||||
use mcstatusface::http::{HttpServer, StatusCode};
|
use mcstatusface::http::{HttpServer, StatusCode};
|
||||||
use mcstatusface::{MinecraftStatus};
|
use mcstatusface::{MinecraftStatus};
|
||||||
|
|
||||||
#[derive(serde::Serialize)]
|
|
||||||
struct MinecraftStatusResponse<'a> {
|
|
||||||
version: &'a String,
|
|
||||||
players: u32,
|
|
||||||
max_players: u32,
|
|
||||||
motd: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() -> Result<()> {
|
fn main() -> Result<()> {
|
||||||
let args: Vec<String> = env::args().collect();
|
let args: Vec<String> = env::args().collect();
|
||||||
if args.len() < 2 {
|
if args.len() < 2 {
|
||||||
println!(
|
eprintln!("Usage: {} [serve] <address[:port]>", args[0]);
|
||||||
r#"Crafty McStatusFace, v{} - made with <3 by ari melody
|
std::process::exit(1);
|
||||||
|
|
||||||
Host a web API:
|
|
||||||
$ mcstatusface serve [address[:port]]
|
|
||||||
|
|
||||||
Query a server:
|
|
||||||
$ mcstatusface <address[:port]>"#,
|
|
||||||
env!("CARGO_PKG_VERSION"));
|
|
||||||
std::process::exit(0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if args[1] != "serve" {
|
if args[1] != "serve" {
|
||||||
|
@ -47,24 +30,9 @@ env!("CARGO_PKG_VERSION"));
|
||||||
|
|
||||||
let mut address = "0.0.0.0:8080".to_string();
|
let mut address = "0.0.0.0:8080".to_string();
|
||||||
if args.len() > 2 { address = args[2].to_string() }
|
if args.len() > 2 { address = args[2].to_string() }
|
||||||
let trusted_proxies: Vec<IpAddr> =
|
|
||||||
match env::var("MCSTATUSFACE_TRUSTED_PROXIES") {
|
|
||||||
Err(_) => { vec![] }
|
|
||||||
Ok(envar) => {
|
|
||||||
let mut trusted_proxies: Vec<IpAddr> = Vec::new();
|
|
||||||
for addr in envar.split(",") {
|
|
||||||
match IpAddr::from_str(addr) {
|
|
||||||
Err(_) => {}
|
|
||||||
Ok(addr) => { trusted_proxies.push(addr); }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
trusted_proxies
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
HttpServer::new(address, 64, trusted_proxies).start(|request, mut response| {
|
HttpServer::new(address, 64).start(|request, mut response| {
|
||||||
response.status(StatusCode::OK);
|
response.status(StatusCode::OK);
|
||||||
response.set_header("Content-Type", "text/plain".to_string());
|
|
||||||
response.set_header("x-powered-by", "GIRL FUEL".to_string());
|
response.set_header("x-powered-by", "GIRL FUEL".to_string());
|
||||||
|
|
||||||
if request.method() != "GET" {
|
if request.method() != "GET" {
|
||||||
|
@ -72,159 +40,51 @@ env!("CARGO_PKG_VERSION"));
|
||||||
return response.send()
|
return response.send()
|
||||||
}
|
}
|
||||||
|
|
||||||
if request.path() == "/style/index.css" {
|
if !request.query().contains_key("s") {
|
||||||
let content = include_str!("public/style/index.css");
|
response.status(StatusCode::BadRequest);
|
||||||
response.set_header("Content-Type", "text/css".to_string());
|
// TODO: nice index landing page for browsers
|
||||||
response.status(StatusCode::OK);
|
response.body("?s=<server address>\n".to_string());
|
||||||
response.body(content.to_string());
|
return response.send()
|
||||||
return response.send();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if request.path() == "/" {
|
let mut address = request.query().get("s").unwrap().to_string();
|
||||||
if request.headers().get("accept").is_some_and(
|
if !address.contains(":") { address.push_str(":25565"); }
|
||||||
|accept| accept.contains("text/html")
|
let mut addrs_iter = address.to_socket_addrs().unwrap();
|
||||||
) {
|
let address = addrs_iter.next().unwrap();
|
||||||
// HTML response
|
|
||||||
let content = include_str!("views/index.html");
|
let status = MinecraftStatus::fetch(address).unwrap();
|
||||||
response.set_header("Content-Type", "text/html".to_string());
|
|
||||||
|
#[derive(serde::Serialize)]
|
||||||
|
struct MinecraftStatusResponse<'a> {
|
||||||
|
version: &'a String,
|
||||||
|
players: u32,
|
||||||
|
max_players: u32,
|
||||||
|
motd: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
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.status(StatusCode::OK);
|
||||||
let query_response: String;
|
response.set_header("Content-Type", "application/json".to_string());
|
||||||
match request.query().get("s") {
|
response.body(json);
|
||||||
None => {
|
|
||||||
query_response = String::from("");
|
|
||||||
}
|
|
||||||
Some(query_address) => {
|
|
||||||
let mut address = query_address.to_string();
|
|
||||||
if !address.contains(":") { address.push_str(":25565"); }
|
|
||||||
match address.to_socket_addrs() {
|
|
||||||
Err(_) => {
|
|
||||||
response.status(StatusCode::BadRequest);
|
|
||||||
query_response = format!(
|
|
||||||
"<hr/>
|
|
||||||
<h2>Server Details</h2>
|
|
||||||
<pre><code>Invalid server address: {}.</pre></code>",
|
|
||||||
sanitize_html(&address.to_string()),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
Ok(mut addrs_iter) => {
|
|
||||||
let address = addrs_iter.next().unwrap();
|
|
||||||
|
|
||||||
match MinecraftStatus::fetch(address) {
|
|
||||||
Err(_) => {
|
|
||||||
response.status(StatusCode::InternalServerError);
|
|
||||||
query_response = format!(
|
|
||||||
"<hr/>
|
|
||||||
<h2>Server Details</h2>
|
|
||||||
<pre><code>Failed to connect to {}.</pre></code>",
|
|
||||||
sanitize_html(&address.to_string()),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
Ok(status) => {
|
|
||||||
let minecraft_status = MinecraftStatusResponse{
|
|
||||||
version: &status.version.name,
|
|
||||||
players: status.players.online,
|
|
||||||
max_players: status.players.max,
|
|
||||||
motd: status.parse_description(),
|
|
||||||
};
|
|
||||||
|
|
||||||
query_response = format!(
|
|
||||||
"<hr/>
|
|
||||||
<h2>Server Details</h2>
|
|
||||||
<p>
|
|
||||||
<strong>Version:</strong> <code>{}</code><br/>
|
|
||||||
<strong>Players:</strong> <code>{}/{}</code><br/>
|
|
||||||
<strong>MOTD:</strong>
|
|
||||||
</p>
|
|
||||||
<pre id=\"motd\"><code>{}</code></pre>",
|
|
||||||
sanitize_html(minecraft_status.version).to_string(),
|
|
||||||
minecraft_status.players,
|
|
||||||
minecraft_status.max_players,
|
|
||||||
sanitize_html(&minecraft_status.motd).to_string(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let response_content = content
|
|
||||||
.replace("{{response}}", &query_response)
|
|
||||||
.replace("{{host}}", match request.headers().get("host") {
|
|
||||||
None => { "mcq.bliss.town" }
|
|
||||||
Some(host) => { host }
|
|
||||||
});
|
|
||||||
response.set_header("Content-Type", "text/html".to_string());
|
|
||||||
response.body(response_content.to_string());
|
|
||||||
return response.send();
|
|
||||||
}
|
}
|
||||||
|
Err(e) => {
|
||||||
// JSON response
|
eprintln!("Request to {address} failed: {e}");
|
||||||
match request.query().get("s") {
|
response.status(StatusCode::InternalServerError);
|
||||||
None => {
|
response.set_header("Content-Type", "text/plain".to_string());
|
||||||
response.status(StatusCode::OK);
|
response.body("Unable to reach the requested server.\n".to_string());
|
||||||
response.body("?s=<server address>\n".to_string());
|
|
||||||
return response.send();
|
|
||||||
}
|
|
||||||
Some(query_address) => {
|
|
||||||
let mut address = query_address.to_string();
|
|
||||||
if !address.contains(":") { address.push_str(":25565"); }
|
|
||||||
match address.to_socket_addrs() {
|
|
||||||
Err(_) => {
|
|
||||||
response.status(StatusCode::BadRequest);
|
|
||||||
response.body("Invalid server address.\n".to_string());
|
|
||||||
}
|
|
||||||
Ok(mut addrs_iter) => {
|
|
||||||
let address = addrs_iter.next().unwrap();
|
|
||||||
|
|
||||||
match MinecraftStatus::fetch(address) {
|
|
||||||
Err(_) => {
|
|
||||||
response.status(StatusCode::InternalServerError);
|
|
||||||
response.body(format!("Failed to connect to {address}.\n"));
|
|
||||||
}
|
|
||||||
Ok(status) => {
|
|
||||||
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) {
|
|
||||||
Err(e) => {
|
|
||||||
eprintln!("Failed to format response from {} to JSON: {}", address, e);
|
|
||||||
response.status(StatusCode::InternalServerError);
|
|
||||||
response.body("Internal Server Error\n".to_string());
|
|
||||||
}
|
|
||||||
Ok(json) => {
|
|
||||||
response.status(StatusCode::OK);
|
|
||||||
response.set_header("Content-Type", "application/json".to_string());
|
|
||||||
response.body(json + "\n");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return response.send()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
response.status(StatusCode::NotFound);
|
response.send()
|
||||||
response.body("Not Found".to_string());
|
|
||||||
return response.send();
|
|
||||||
}).unwrap();
|
}).unwrap();
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn sanitize_html(input: &String) -> String {
|
|
||||||
input
|
|
||||||
.replace("&", "&")
|
|
||||||
.replace("<", "<")
|
|
||||||
.replace(">", ">")
|
|
||||||
.replace("\"", """)
|
|
||||||
.replace("'", "'")
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,62 +0,0 @@
|
||||||
:root {
|
|
||||||
--accent: #7ca82f;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
font-size: 16px;
|
|
||||||
font-family: 'Inter', sans-serif;
|
|
||||||
}
|
|
||||||
|
|
||||||
a {
|
|
||||||
color: var(--accent);
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
a:hover {
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
|
|
||||||
form {
|
|
||||||
width: fit-content;
|
|
||||||
padding: .5em;
|
|
||||||
border: 1px solid black;
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
form input[type="text"] {
|
|
||||||
width: fit-content;
|
|
||||||
min-width: 16em;
|
|
||||||
}
|
|
||||||
|
|
||||||
form button {
|
|
||||||
margin-top: .5em;
|
|
||||||
padding: .2em .3em;
|
|
||||||
font-family: inherit;
|
|
||||||
font-size: inherit;
|
|
||||||
border: 1px solid black;
|
|
||||||
border-radius: 4px;
|
|
||||||
color: white;
|
|
||||||
background-color: var(--accent);
|
|
||||||
}
|
|
||||||
|
|
||||||
pre code {
|
|
||||||
padding: .5em;
|
|
||||||
border: 1px solid black;
|
|
||||||
border-radius: 4px;
|
|
||||||
font-size: .8em;
|
|
||||||
color: #e0e0e0;
|
|
||||||
background-color: #303030;
|
|
||||||
}
|
|
||||||
|
|
||||||
pre#motd {
|
|
||||||
margin-top: -1em
|
|
||||||
}
|
|
||||||
|
|
||||||
pre#motd code {
|
|
||||||
display: block;
|
|
||||||
width: fit-content;
|
|
||||||
min-width: 440px;
|
|
||||||
}
|
|
||||||
|
|
||||||
footer {
|
|
||||||
font-family: monospace;
|
|
||||||
}
|
|
|
@ -51,10 +51,8 @@ impl MinecraftStatus {
|
||||||
pub fn fetch(address: SocketAddr) -> Result<MinecraftStatus> {
|
pub fn fetch(address: SocketAddr) -> Result<MinecraftStatus> {
|
||||||
// println!("Connecting to {address}...");
|
// println!("Connecting to {address}...");
|
||||||
|
|
||||||
let stream = TcpStream::connect(address.to_string());
|
let mut stream = TcpStream::connect(address.to_string()).unwrap();
|
||||||
if stream.is_err() { return Err(stream.unwrap_err()); }
|
|
||||||
// println!("Connected!");
|
// println!("Connected!");
|
||||||
let mut stream = stream.unwrap();
|
|
||||||
|
|
||||||
let mut send_buffer: Vec<u8> = Vec::new();
|
let mut send_buffer: Vec<u8> = Vec::new();
|
||||||
|
|
||||||
|
|
|
@ -1,50 +0,0 @@
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
||||||
<title>Crafty McStatusFace</title>
|
|
||||||
<link href="style/index.css" rel="stylesheet">
|
|
||||||
|
|
||||||
<meta name="description" content="Query Minecraft servers!">
|
|
||||||
|
|
||||||
<meta property="og:title" content="Crafty McStatusFace">
|
|
||||||
<meta property="og:type" content="website">
|
|
||||||
<meta property="og:url" content="https://{{host}}">
|
|
||||||
<meta property="og:site_name" content="Crafty McStatusFace">
|
|
||||||
<meta property="og:description" content="Query Minecraft servers!">
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<header>
|
|
||||||
<h1><a href="/">Crafty McStatusFace</a></h1>
|
|
||||||
</header>
|
|
||||||
<hr/>
|
|
||||||
<main>
|
|
||||||
<p>
|
|
||||||
You can use this website to retrieve query information from a Minecraft server!
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
For more information, see <a href="https://minecraft.wiki/w/Query">https://minecraft.wiki/w/Query</a>.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<form action="/" method="GET">
|
|
||||||
<label for="s">Server Address</label>
|
|
||||||
<input type="text" name="s" value="" placeholder="e.g. mc.bliss.town or 104.17.17.42">
|
|
||||||
<br/>
|
|
||||||
<button type="submit">Check Status</button>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
Alternatively, you can cURL this website to get a raw JSON response:
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<pre><code>curl 'https://{{host}}?s=<server address>'</code></pre>
|
|
||||||
|
|
||||||
{{response}}
|
|
||||||
</main>
|
|
||||||
<hr/>
|
|
||||||
<footer>
|
|
||||||
<em>made with <span aria-label="love">♥</span> by ari, 2025. <a href="https://git.arimelody.me/ari/mcstatusface" target="_blank">source</a></em>
|
|
||||||
</footer>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
Loading…
Add table
Add a link
Reference in a new issue