diff --git a/Cargo.lock b/Cargo.lock index e214ba7..2afe0a5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -118,7 +118,7 @@ checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" [[package]] name = "mcstatusface" -version = "1.2.0" +version = "1.1.1" dependencies = [ "chrono", "serde", diff --git a/Cargo.toml b/Cargo.toml index 9c5b648..7140036 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,10 +1,10 @@ [package] name = "mcstatusface" -authors = ["ari melody "] -repository = "https://codeberg.org/arimelody/mcstatusface" +authors = ["ari melody "] +repository = "https://git.arimelody.me/ari/mcstatusface" license = "MIT" keywords = ["minecraft", "server", "query", "web"] -version = "1.2.0" +version = "1.1.1" edition = "2024" [dependencies] diff --git a/src/dns.rs b/src/dns.rs deleted file mode 100644 index 8cfb46c..0000000 --- a/src/dns.rs +++ /dev/null @@ -1,128 +0,0 @@ -use std::{io::{Error, ErrorKind, Result}, net::UdpSocket}; - -const CLOUDFLARE_DNS: &str = "1.1.1.1"; - -const RECURSION_DESIRED: u16 = 1 << 8; - -const RECORD_TYPE_SRV: u16 = 33; -const RECORD_CLASS_IN: u16 = 1; - -pub fn create_dns_query(qname: &String, qtype: u16, qclass: u16) -> Result> { - let qname = match qname.is_ascii() { - true => qname, - false => return Err(Error::new(ErrorKind::InvalidInput, "domain is not valid ASCII")), - }; - - let txid = 1u16; - let flags = RECURSION_DESIRED; - let qdcount = 1u16; - let ancount = 0u16; - let nscount = 0u16; - let arcount = 0u16; - - let header: [u16; 6] = - [txid, flags, qdcount, ancount, nscount, arcount] - .map(|el| el.to_be()); - - // let mut qname_bytes = bitvec![u8, Msb0;]; - let mut query = Vec::::new(); - for mut label in qname.split(".") { - label = match label.len() > u8::MAX.into() { - true => label.split_at(u8::MAX.into()).0, - false => label - }; - let len: u8 = label.len().try_into().unwrap(); - query.push(len); - query.append(&mut label.as_bytes().to_vec()); - } - query.push(0); - query.append(&mut qtype.to_be_bytes().to_vec()); - query.append(&mut qclass.to_be_bytes().to_vec()); - - let mut request = Vec::::new(); - for el in header { - request.push(el as u8); - request.push((el >> 8) as u8); - } - request.append(&mut query); - - Ok(request) -} - -#[derive(Debug)] -pub struct DnsSrvResponse { - qref: u16, - rtype: u16, - class: u16, - ttl: u32, - priority: u16, - weight: u16, - port: u16, -} - -pub fn parse_srv_response(mut ptr: usize, recv: &[u8]) -> Option { - let mut data = DnsSrvResponse { - qref: 0, - rtype: 0, - class: 0, - ttl: 0, - priority: 0, - weight: 0, - port: 0, - }; - - fn read16(buf: &[u8], offset: usize) -> u16 { - let left = (buf[offset] as u16) << 8; - let right = buf[offset + 1] as u16; - left + right - } - - fn read32(buf: &[u8], offset: usize) -> u32 { - let left = (read16(buf, offset) as u32) << 16; - let right = read16(buf, offset + 2) as u32; - left + right - } - - data.qref = read16(recv, ptr); - ptr += 2; - data.rtype = read16(recv, ptr); - ptr += 2; - if data.rtype != RECORD_TYPE_SRV { - return None - } - data.class = read16(recv, ptr); - ptr += 2; - data.ttl = read32(recv, ptr); - ptr += 4; - let _rdata_len = read16(recv, ptr); - ptr += 2; - data.priority = read16(recv, ptr); - ptr += 2; - data.weight = read16(recv, ptr); - ptr += 2; - data.port = read16(recv, ptr); - // optional: read target? - - // print!("data: {:#?}\n", data); - - Some(data) -} - -pub fn resolve_srv_port(domain: &String) -> Option { - let request = create_dns_query( - &("_minecraft._tcp.".to_string() + domain), - RECORD_TYPE_SRV, - RECORD_CLASS_IN).unwrap(); - - let socket = UdpSocket::bind("0.0.0.0:0").expect("failed to bind to port!"); - socket.connect(CLOUDFLARE_DNS.to_string() + ":53").expect("failed to connect to dns server"); - socket.send(&request).expect("failed to send dns request"); - - let mut recv_buf: [u8; 1024] = [0; 1024]; - let _ = socket.recv_from(&mut recv_buf).unwrap(); - - match parse_srv_response(request.len(), &recv_buf) { - Some(data) => Some(data.port), - None => None, - } -} diff --git a/src/lib.rs b/src/lib.rs index 6139176..8a34bdd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,7 +3,6 @@ use std::{sync::{mpsc, Arc, Mutex}, thread}; pub mod leb128; pub mod status; pub mod http; -pub mod dns; pub use status::MinecraftStatus; pub struct ThreadPool { diff --git a/src/main.rs b/src/main.rs index 2dcd39d..a4309af 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,20 +5,15 @@ use std::{env}; use mcstatusface::http::{HttpServer, StatusCode}; use mcstatusface::{MinecraftStatus}; -use mcstatusface::dns::{resolve_srv_port}; #[derive(serde::Serialize)] struct MinecraftStatusResponse<'a> { version: &'a String, players: u32, max_players: u32, - enforces_secure_chat: bool, - favicon: Option<&'a String>, motd: String, } -const DEFAULT_PORT: u16 = 25565; - fn main() -> Result<()> { let args: Vec = env::args().collect(); if args.len() < 2 { @@ -36,15 +31,7 @@ env!("CARGO_PKG_VERSION")); if args[1] != "serve" { let mut address = String::from(args[1].as_str()); - if !address.contains(":") { - let port: u16 = match resolve_srv_port(&address) { - Some(port) => port, - None => DEFAULT_PORT, - }; - - address.push_str(":"); - address.push_str(port.to_string().as_str()); - } + if !address.contains(":") { address.push_str(":25565"); } let mut addrs_iter = address.to_socket_addrs().unwrap(); let address = addrs_iter.next().unwrap(); @@ -108,16 +95,7 @@ env!("CARGO_PKG_VERSION")); } Some(query_address) => { let mut address = query_address.to_string(); - address = address.replace("%3A", ":"); - if !address.contains(":") { - let port: u16 = match resolve_srv_port(&address) { - Some(port) => port, - None => DEFAULT_PORT, - }; - - address.push_str(":"); - address.push_str(port.to_string().as_str()); - } + if !address.contains(":") { address.push_str(":25565"); } match address.to_socket_addrs() { Err(_) => { response.status(StatusCode::BadRequest); @@ -146,8 +124,6 @@ env!("CARGO_PKG_VERSION")); version: &status.version.name, players: status.players.online, max_players: status.players.max, - enforces_secure_chat: status.enforces_secure_chat(), - favicon: status.favicon.as_ref(), motd: status.parse_description(), }; @@ -155,19 +131,14 @@ env!("CARGO_PKG_VERSION")); "

Server Details

- Favicon:
-
Version: {}
Players: {}/{}
- Enforces Secure Chat: {}
MOTD:

{}
", - sanitize_html(&minecraft_status.favicon.map_or("", |s| s).to_string()), sanitize_html(minecraft_status.version).to_string(), minecraft_status.players, minecraft_status.max_players, - if minecraft_status.enforces_secure_chat { "true" } else { "false" }, sanitize_html(&minecraft_status.motd).to_string(), ); } @@ -197,16 +168,7 @@ env!("CARGO_PKG_VERSION")); } Some(query_address) => { let mut address = query_address.to_string(); - address = address.replace("%3A", ":"); - if !address.contains(":") { - let port: u16 = match resolve_srv_port(&address) { - Some(port) => port, - None => DEFAULT_PORT, - }; - - address.push_str(":"); - address.push_str(port.to_string().as_str()); - } + if !address.contains(":") { address.push_str(":25565"); } match address.to_socket_addrs() { Err(_) => { response.status(StatusCode::BadRequest); @@ -225,8 +187,6 @@ env!("CARGO_PKG_VERSION")); version: &status.version.name, players: status.players.online, max_players: status.players.max, - enforces_secure_chat: status.enforces_secure_chat(), - favicon: status.favicon.as_ref(), motd: status.parse_description(), }; diff --git a/src/public/style/index.css b/src/public/style/index.css index ef8577a..59c28af 100644 --- a/src/public/style/index.css +++ b/src/public/style/index.css @@ -1,47 +1,10 @@ :root { - --bg-0: #101010; - --bg-1: #181818; - --bg-2: #282828; - --fg-0: #c0c0c0; - --fg-1: #d0d0d0; - --fg-2: #e0e0e0; - --accent: #c0ea76; - --on-accent: #43600f; - - --shadow-md: 0 2px 4px rgba(0,0,0,.5); - --shadow-sm: 0 1px 2px rgba(0,0,0,.3); -} - -@media (prefers-color-scheme: light) { - :root { - --bg-0: #e8e8e8; - --bg-1: #f0f0f0; - --bg-2: #fcfcfc; - --fg-0: #606060; - --fg-1: #303030; - --fg-2: #101010; - --accent: #6a9321; - --on-accent: #f6ffe6; - - --shadow-md: 0 2px 4px rgba(0,0,0,.25);; - --shadow-sm: 0 1px 2px rgba(0,0,0,.15);; - } + --accent: #7ca82f; } body { - width: 720px; - max-width: calc(100vw - 1em); - margin: auto; - - font-size: 20px; - font-family: 'Inter', 'Arial', sans-serif; - - background-color: var(--bg-0); - color: var(--fg-0); -} - -* { - transition: background-color .2s ease-out, color .2s ease-out; + font-size: 16px; + font-family: 'Inter', sans-serif; } a { @@ -54,45 +17,34 @@ a:hover { form { width: fit-content; - padding: 1em; - border-radius: 8px; - background-color: var(--bg-1); - color: var(--fg-1); - box-shadow: var(--shadow-md); + padding: .5em; + border: 1px solid black; + border-radius: 4px; } form input[type="text"] { width: fit-content; min-width: 16em; - padding: .2em .4em; - font-size: inherit; - border: none; - border-radius: 4px; - background-color: var(--bg-2); - color: var(--fg-2); - box-shadow: var(--shadow-sm); } form button { margin-top: .5em; - padding: .2em .4em; + padding: .2em .3em; font-family: inherit; font-size: inherit; - border: none; - border-radius: 8px; - color: var(--on-accent); + border: 1px solid black; + border-radius: 4px; + color: white; background-color: var(--accent); - box-shadow: var(--shadow-sm); } pre code { padding: .5em; - border-radius: 8px; - font-size: 16px; - - background-color: var(--fg-1); - color: var(--bg-1); - box-shadow: var(--shadow-sm); + border: 1px solid black; + border-radius: 4px; + font-size: .8em; + color: #e0e0e0; + background-color: #303030; } pre#motd { @@ -103,7 +55,6 @@ pre#motd code { display: block; width: fit-content; min-width: 440px; - box-shadow: var(--shadow-sm); } footer { diff --git a/src/status.rs b/src/status.rs index 65c6eb3..3af55f6 100644 --- a/src/status.rs +++ b/src/status.rs @@ -19,7 +19,7 @@ pub struct MinecraftPlayer { pub struct MinecraftPlayers { pub online: u32, pub max: u32, - pub sample: Option>, + pub sample: Vec, } #[derive(serde::Serialize, serde::Deserialize)] @@ -30,14 +30,7 @@ enum MinecraftDescriptionExtra { } #[derive(serde::Serialize, serde::Deserialize)] -#[serde(untagged)] -pub enum MinecraftDescription { - Rich(MinecraftRichDescription), - Plain(String) -} - -#[derive(serde::Serialize, serde::Deserialize)] -pub struct MinecraftRichDescription { +pub struct MinecraftDescription { text: String, extra: Option>, bold: Option, @@ -110,6 +103,7 @@ impl MinecraftStatus { } let msg = std::str::from_utf8(&data[offset..]).unwrap().trim(); let sanitised: String = msg.chars().filter(|&c| c >= '\u{20}' || c == '\n' || c == '\r' || c == '\t').collect(); + // println!("{sanitised}"); let status: MinecraftStatus = serde_json::from_slice(sanitised.as_bytes()).unwrap(); Ok(status) @@ -125,28 +119,21 @@ impl MinecraftStatus { } fn _description(description: &MinecraftDescription) -> String { - match description { - MinecraftDescription::Rich(description) => { - if description.extra.is_some() { - let mut extras = String::new(); - for extra in description.extra.as_ref().unwrap() { - match extra { - MinecraftDescriptionExtra::Extra(description) => { - extras += &_description(&description); - } - MinecraftDescriptionExtra::String(string) => { - extras += &string; - } - } + if description.extra.is_some() { + let mut extras = String::new(); + for extra in description.extra.as_ref().unwrap() { + match extra { + MinecraftDescriptionExtra::Extra(description) => { + extras += &_description(&description); + } + MinecraftDescriptionExtra::String(string) => { + extras += &string; } - return description.text.clone() + &extras; } - description.text.clone() - } - MinecraftDescription::Plain(description) => { - description.clone() } + return description.text.clone() + &extras; } + description.text.clone() } fn send_packet(stream: &mut TcpStream, data: &[u8]) -> Result<()> { diff --git a/src/views/index.html b/src/views/index.html index fcfc154..5bdd9d3 100644 --- a/src/views/index.html +++ b/src/views/index.html @@ -21,7 +21,7 @@

- This website retrieves query information from Minecraft servers! + You can use this website to retrieve query information from a Minecraft server!

For more information, see https://minecraft.wiki/w/Query. @@ -38,13 +38,13 @@ Alternatively, you can cURL this website to get a raw JSON response:

-
curl 'https://{{host}}?s=<server address>' | jq .
+
curl 'https://{{host}}?s=<server address>'
{{response}}