rust_mc_status/
models.rs

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