use serde::{de, Deserialize, Deserializer, Serialize};
use crate::mojang_api::{client::get, error::ApiError};
fn deserialize_textures_entry<'de, D>(ty: D) -> Result<TexturesEntry, D::Error>
where
D: Deserializer<'de>,
{
let mut buf = [0u8; 768];
let str = String::deserialize(ty)?;
let len =
base64::decode_config_slice(&str, base64::STANDARD, &mut buf).map_err(de::Error::custom)?;
serde_json::from_slice(&buf[..len]).map_err(de::Error::custom)
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub struct Profile {
pub id: String,
pub name: String,
pub properties: [ProfileProperty; 1],
#[serde(default)]
pub legacy: bool,
}
impl Profile {
pub fn fetch(uuid: &str) -> Result<Self, ApiError> {
let url = format!(
"https://sessionserver.mojang.com/session/minecraft/profile/{}",
uuid
);
Ok(get(url)?.json()?)
}
pub fn textures(&self) -> &Textures {
&self.properties[0].value.textures
}
pub fn slim_model(&self) -> bool {
let is_slim = self
.textures()
.skin
.metadata
.as_ref()
.map(|m| m.model == "slim");
matches!(is_slim, Some(true))
}
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub struct ProfileProperty {
pub name: String,
#[serde(deserialize_with = "deserialize_textures_entry")]
pub value: TexturesEntry,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub struct TexturesEntry {
pub timestamp: i64,
#[serde(rename = "profileId")]
pub profile_id: String,
#[serde(rename = "profileName")]
pub profile_name: String,
pub textures: Textures,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub struct Textures {
#[serde(rename = "SKIN")]
pub skin: SkinData,
#[serde(rename = "CAPE")]
pub cape: Option<CapeData>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub struct SkinData {
pub url: String,
pub metadata: Option<SkinMetadata>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub struct CapeData {
pub url: String,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub struct SkinMetadata {
pub model: String,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub struct UsernameEntry {
pub name: String,
#[serde(rename = "changedToAt")]
pub changed_to_at: Option<u64>,
}
pub fn get_username_history(uuid: &str) -> Result<Vec<UsernameEntry>, ApiError> {
let url = format!("https://api.mojang.com/user/profiles/{}/names", uuid);
Ok(get(url)?.json()?)
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_parse() {
let json = r#"{
"id" : "7a8084cd1f444a159bb1eef8d5b535a1",
"name" : "brecert",
"properties" : [ {
"name" : "textures",
"value" : "ewogICJ0aW1lc3RhbXAiIDogMTY0MDMyNjE1MTg1OSwKICAicHJvZmlsZUlkIiA6ICI3YTgwODRjZDFmNDQ0YTE1OWJiMWVlZjhkNWI1MzVhMSIsCiAgInByb2ZpbGVOYW1lIiA6ICJicmVjZXJ0IiwKICAidGV4dHVyZXMiIDogewogICAgIlNLSU4iIDogewogICAgICAidXJsIiA6ICJodHRwOi8vdGV4dHVyZXMubWluZWNyYWZ0Lm5ldC90ZXh0dXJlL2I4MTMwMjgyYjgwY2MwODg3MmJmYzg1ODk3NTM1MGFiM2YzZmNkNGIxZDE4NzE3YmZiNWI3YjgzOGZjZTRlYWEiLAogICAgICAibWV0YWRhdGEiIDogewogICAgICAgICJtb2RlbCIgOiAic2xpbSIKICAgICAgfQogICAgfQogIH0KfQ=="
} ]
}"#;
let profile = serde_json::from_str::<Profile>(&json).unwrap();
assert_eq!(profile.textures().skin.url, "http://textures.minecraft.net/texture/b8130282b80cc08872bfc858975350ab3f3fcd4b1d18717bfb5b7b838fce4eaa")
}
#[test]
fn test_username_history() {
let history = get_username_history("7a8084cd1f444a159bb1eef8d5b535a1").unwrap();
assert_eq!(
history,
vec![
UsernameEntry {
name: String::from("brecert"),
changed_to_at: None,
},
UsernameEntry {
name: String::from("Brecert"),
changed_to_at: Some(1424118240000,),
},
UsernameEntry {
name: String::from("brecert"),
changed_to_at: Some(1636689908888,),
},
],
);
}
}