mc_server_status/
models.rs

1// Copyright (c) 2025 pynickle. This is a fork of Original Crate. Original copyright: Copyright (c) 2025 NameOfShadow
2
3use std::fmt;
4use std::fs::File;
5use std::io::Write;
6
7use base64::{Engine as _, engine::general_purpose};
8use serde::{Deserialize, Serialize};
9use serde_json::Value;
10
11use crate::McError;
12
13#[derive(Debug, Serialize, Deserialize, Clone)]
14pub struct ServerStatus {
15    pub online: bool,
16    pub ip: String,
17    pub port: u16,
18    pub hostname: String,
19    pub latency: f64,
20    pub dns: Option<DnsInfo>,
21    pub data: ServerData,
22}
23
24#[derive(Debug, Serialize, Deserialize, Clone)]
25#[serde(untagged)]
26pub enum ServerData {
27    Java(JavaStatus),
28    Bedrock(BedrockStatus),
29}
30
31#[derive(Debug, Serialize, Deserialize, Clone)]
32pub struct DnsInfo {
33    pub a_records: Vec<String>,
34    pub cname: Option<String>,
35    pub ttl: u32,
36}
37
38#[derive(Serialize, Deserialize, Clone)]
39pub struct JavaStatus {
40    pub version: JavaVersion,
41    pub players: JavaPlayers,
42    pub description: String,
43    #[serde(skip_serializing)]
44    pub favicon: Option<String>,
45    pub map: Option<String>,
46    pub gamemode: Option<String>,
47    pub software: Option<String>,
48    pub plugins: Option<Vec<JavaPlugin>>,
49    pub mods: Option<Vec<JavaMod>>,
50    #[serde(skip)]
51    pub raw_data: Value,
52}
53
54#[derive(Debug, Serialize, Deserialize, Clone)]
55pub struct JavaVersion {
56    pub name: String,
57    pub protocol: i64,
58}
59
60#[derive(Debug, Serialize, Deserialize, Clone)]
61pub struct JavaPlayers {
62    pub online: i64,
63    pub max: i64,
64    pub sample: Option<Vec<JavaPlayer>>,
65}
66
67#[derive(Debug, Serialize, Deserialize, Clone)]
68pub struct JavaPlayer {
69    pub name: String,
70    pub id: String,
71}
72
73#[derive(Debug, Serialize, Deserialize, Clone)]
74pub struct JavaPlugin {
75    pub name: String,
76    pub version: Option<String>,
77}
78
79#[derive(Debug, Serialize, Deserialize, Clone)]
80pub struct JavaMod {
81    pub modid: String,
82    pub version: Option<String>,
83}
84
85#[derive(Serialize, Deserialize, Clone)]
86pub struct BedrockStatus {
87    pub edition: String,
88    pub motd: String,
89    pub protocol_version: String,
90    pub version: String,
91    pub online_players: String,
92    pub max_players: String,
93    pub server_uid: String,
94    pub motd2: String,
95    pub game_mode: String,
96    pub game_mode_numeric: String,
97    pub port_ipv4: String,
98    pub port_ipv6: String,
99    pub map: Option<String>,
100    pub software: Option<String>,
101    pub raw_data: String,
102}
103
104#[derive(Debug, Serialize, Deserialize, Clone)]
105pub struct ServerInfo {
106    pub address: String,
107    pub edition: ServerEdition,
108}
109
110#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq)]
111pub enum ServerEdition {
112    Java,
113    Bedrock,
114}
115
116impl fmt::Debug for JavaStatus {
117    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
118        f.debug_struct("JavaStatus")
119            .field("version", &self.version)
120            .field("players", &self.players)
121            .field("description", &self.description)
122            .field("map", &self.map)
123            .field("gamemode", &self.gamemode)
124            .field("software", &self.software)
125            .field("plugins", &self.plugins.as_ref().map(|p| p.len()))
126            .field("mods", &self.mods.as_ref().map(|m| m.len()))
127            .field("favicon", &self.favicon.as_ref().map(|_| "[Favicon data]"))
128            .field("raw_data", &"[Value]")
129            .finish()
130    }
131}
132
133impl JavaStatus {
134    pub fn save_favicon(&self, filename: &str) -> Result<(), McError> {
135        if let Some(favicon) = &self.favicon {
136            let data = favicon.split(',').nth(1).unwrap_or(favicon);
137            let bytes = general_purpose::STANDARD
138                .decode(data)
139                .map_err(McError::Base64Error)?;
140
141            let mut file = File::create(filename).map_err(McError::IoError)?;
142            file.write_all(&bytes).map_err(McError::IoError)?;
143
144            Ok(())
145        } else {
146            Err(McError::InvalidResponse("No favicon available".to_string()))
147        }
148    }
149}
150
151impl fmt::Debug for BedrockStatus {
152    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
153        f.debug_struct("BedrockStatus")
154            .field("edition", &self.edition)
155            .field("motd", &self.motd)
156            .field("protocol_version", &self.protocol_version)
157            .field("version", &self.version)
158            .field("online_players", &self.online_players)
159            .field("max_players", &self.max_players)
160            .field("server_uid", &self.server_uid)
161            .field("motd2", &self.motd2)
162            .field("game_mode", &self.game_mode)
163            .field("game_mode_numeric", &self.game_mode_numeric)
164            .field("port_ipv4", &self.port_ipv4)
165            .field("port_ipv6", &self.port_ipv6)
166            .field("map", &self.map)
167            .field("software", &self.software)
168            .field("raw_data", &self.raw_data.len())
169            .finish()
170    }
171}
172
173impl std::str::FromStr for ServerEdition {
174    type Err = McError;
175
176    fn from_str(s: &str) -> Result<Self, Self::Err> {
177        match s.to_lowercase().as_str() {
178            "java" => Ok(ServerEdition::Java),
179            "bedrock" => Ok(ServerEdition::Bedrock),
180            _ => Err(McError::InvalidEdition(s.to_string())),
181        }
182    }
183}