use std::fmt;
use std::fs::File;
use std::io::Write;
use base64::{engine::general_purpose, Engine as _};
use serde::{Deserialize, Serialize};
use serde_json::Value;
use smallvec::SmallVec;
use crate::error::McError;
#[derive(Serialize, Deserialize, Clone)]
pub struct JavaStatus {
pub version: JavaVersion,
pub players: JavaPlayers,
pub description: String,
#[serde(skip_serializing)]
pub favicon: Option<String>,
pub map: Option<String>,
pub gamemode: Option<String>,
pub software: Option<String>,
pub plugins: Option<SmallVec<[JavaPlugin; 8]>>,
pub mods: Option<SmallVec<[JavaMod; 8]>>,
#[serde(skip)]
pub raw_data: Value,
}
impl JavaStatus {
pub fn save_favicon(&self, filename: &str) -> Result<(), McError> {
let favicon = self.favicon.as_deref()
.ok_or_else(|| McError::invalid_response("No favicon available"))?;
let b64 = favicon.split(',').next_back().unwrap_or(favicon);
let bytes = general_purpose::STANDARD
.decode(b64)
.map_err(|e| McError::from(crate::error::ProtocolError::Base64(e)))?;
let mut file = File::create(filename).map_err(McError::Io)?;
file.write_all(&bytes).map_err(McError::Io)?;
Ok(())
}
#[doc(hidden)]
pub fn from_json_test(v: serde_json::Value) -> Result<Self, crate::McError> {
Self::from_json(v)
}
pub(crate) fn from_json(v: Value) -> Result<Self, McError> {
let desc = match &v["description"] {
Value::String(s) => s.clone(),
o if o.is_object() => o["text"].as_str().unwrap_or("").to_string(),
_ => String::new(),
};
Ok(JavaStatus {
version: serde_json::from_value(v["version"].clone())
.map_err(|e| McError::from(crate::error::ProtocolError::Json(e)))?,
players: serde_json::from_value(v["players"].clone())
.map_err(|e| McError::from(crate::error::ProtocolError::Json(e)))?,
description: desc,
favicon: serde_json::from_value(v["favicon"].clone()).ok(),
map: v["map"].as_str().map(str::to_string),
gamemode: v["gamemode"].as_str().map(str::to_string),
software: v["software"].as_str().map(str::to_string),
plugins: serde_json::from_value(v["plugins"].clone()).ok(),
mods: serde_json::from_value(v["mods"].clone()).ok(),
raw_data: v,
})
}
}
impl fmt::Debug for JavaStatus {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("JavaStatus")
.field("version", &self.version)
.field("players", &self.players)
.field("description", &self.description)
.field("map", &self.map)
.field("gamemode", &self.gamemode)
.field("software", &self.software)
.field("plugins", &self.plugins.as_ref().map(|p| p.len()))
.field("mods", &self.mods.as_ref().map(|m| m.len()))
.field("favicon", &self.favicon.as_ref().map(|_| "[Favicon data]"))
.field("raw_data", &"[Value]")
.finish()
}
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct JavaVersion {
pub name: String,
pub protocol: i64,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct JavaPlayers {
pub online: i64,
pub max: i64,
pub sample: Option<SmallVec<[JavaPlayer; 12]>>,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct JavaPlayer {
pub name: String,
pub id: String,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct JavaPlugin {
pub name: String,
pub version: Option<String>,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct JavaMod {
pub modid: String,
pub version: Option<String>,
}