1use std::fmt;
2use serde::{Deserialize, Serialize};
3use serde_json::Value;
4use crate::McError;
5use std::fs::File;
6use std::io::Write;
7use base64::{engine::general_purpose, Engine as _};
8
9#[derive(Debug, Serialize, Deserialize, Clone)]
10pub struct ServerStatus {
11 pub online: bool,
12 pub latency: f64,
13 pub data: ServerData,
14}
15
16impl fmt::Debug for JavaStatus {
17 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
18 f.debug_struct("JavaStatus")
19 .field("version", &self.version)
20 .field("players", &self.players)
21 .field("description", &self.description)
22 .field("favicon", &self.favicon.as_ref().map(|_| "[Favicon data]"))
23 .field("raw_data", &"[Value]")
24 .finish()
25 }
26}
27
28impl JavaStatus {
29 pub fn save_favicon(&self, filename: &str) -> Result<(), McError> {
30 if let Some(favicon) = &self.favicon {
31 let data = favicon.split(',').nth(1).unwrap_or(favicon);
32 let bytes = general_purpose::STANDARD.decode(data)
33 .map_err(|e| McError::Base64Error(e))?;
34
35 let mut file = File::create(filename)
36 .map_err(|e| McError::IoError(e))?;
37
38 file.write_all(&bytes)
39 .map_err(|e| McError::IoError(e))?;
40
41 Ok(())
42 } else {
43 Err(McError::InvalidResponse("No favicon available".to_string()))
44 }
45 }
46}
47
48#[derive(Debug, Serialize, Deserialize, Clone)]
49#[serde(untagged)]
50pub enum ServerData {
51 Java(JavaStatus),
52 Bedrock(BedrockStatus),
53}
54
55#[derive(Serialize, Deserialize, Clone)]
56pub struct JavaStatus {
57 pub version: JavaVersion,
58 pub players: JavaPlayers,
59 pub description: String,
60 #[serde(skip_serializing)]
61 pub favicon: Option<String>,
62 #[serde(skip)]
63 pub raw_data: Value,
64}
65
66#[derive(Debug, Serialize, Deserialize, Clone)]
67pub struct JavaVersion {
68 pub name: String,
69 pub protocol: i64,
70}
71
72#[derive(Debug, Serialize, Deserialize, Clone)]
73pub struct JavaPlayers {
74 pub online: i64,
75 pub max: i64,
76 pub sample: Option<Vec<JavaPlayer>>,
77}
78
79#[derive(Debug, Serialize, Deserialize, Clone)]
80pub struct JavaPlayer {
81 pub name: String,
82 pub id: 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 raw_data: String,
100}
101
102impl fmt::Debug for BedrockStatus {
103 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
104 f.debug_struct("BedrockStatus")
105 .field("edition", &self.edition)
106 .field("motd", &self.motd)
107 .field("protocol_version", &self.protocol_version)
108 .field("version", &self.version)
109 .field("online_players", &self.online_players)
110 .field("max_players", &self.max_players)
111 .field("server_uid", &self.server_uid)
112 .field("motd2", &self.motd2)
113 .field("game_mode", &self.game_mode)
114 .field("game_mode_numeric", &self.game_mode_numeric)
115 .field("port_ipv4", &self.port_ipv4)
116 .field("port_ipv6", &self.port_ipv6)
117 .field("raw_data", &"[String]")
118 .finish()
119 }
120}
121
122#[derive(Debug, Serialize, Deserialize, Clone)]
123pub struct ServerInfo {
124 pub address: String,
125 pub edition: ServerEdition,
126}
127
128#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq)]
129pub enum ServerEdition {
130 Java,
131 Bedrock,
132}
133
134impl std::str::FromStr for ServerEdition {
135 type Err = McError;
136
137 fn from_str(s: &str) -> Result<Self, Self::Err> {
138 match s.to_lowercase().as_str() {
139 "java" => Ok(ServerEdition::Java),
140 "bedrock" => Ok(ServerEdition::Bedrock),
141 _ => Err(McError::InvalidEdition(s.to_string())),
142 }
143 }
144}