170 lines
5.4 KiB
Rust
170 lines
5.4 KiB
Rust
use std::{error::Error, sync::Arc, time::Instant};
|
|
|
|
use bytes::Bytes;
|
|
use futures::TryStream;
|
|
use reqwest::{Body, multipart::Part};
|
|
use serde_json::json;
|
|
use uuid::Uuid;
|
|
|
|
use super::MinecraftSkinVariant;
|
|
use crate::{
|
|
ErrorKind,
|
|
data::Credentials,
|
|
state::{
|
|
MINECRAFT_SERVICES_USER_AGENT, MinecraftProfile, PROFILE_CACHE,
|
|
ProfileCacheEntry,
|
|
},
|
|
util::fetch::INSECURE_REQWEST_CLIENT,
|
|
util::mojang::{mojang_service_url, should_use_mojang_mirror},
|
|
};
|
|
|
|
/// Provides operations for interacting with capes on a Minecraft player profile.
|
|
pub struct MinecraftCapeOperation;
|
|
|
|
impl MinecraftCapeOperation {
|
|
pub async fn equip(
|
|
credentials: &Credentials,
|
|
cape_id: Uuid,
|
|
) -> crate::Result<()> {
|
|
let url = mojang_service_url(
|
|
"https://api.minecraftservices.com/minecraft/profile/capes/active",
|
|
should_use_mojang_mirror(),
|
|
);
|
|
update_profile_cache_from_response(
|
|
INSECURE_REQWEST_CLIENT
|
|
.put(url.as_ref())
|
|
.header("Content-Type", "application/json; charset=utf-8")
|
|
.header("Accept", "application/json")
|
|
.header("User-Agent", MINECRAFT_SERVICES_USER_AGENT)
|
|
.bearer_auth(&credentials.access_token)
|
|
.json(&json!({
|
|
"capeId": cape_id.hyphenated(),
|
|
}))
|
|
.send()
|
|
.await
|
|
.and_then(|response| response.error_for_status())?,
|
|
)
|
|
.await;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
pub async fn unequip_any(credentials: &Credentials) -> crate::Result<()> {
|
|
let url = mojang_service_url(
|
|
"https://api.minecraftservices.com/minecraft/profile/capes/active",
|
|
should_use_mojang_mirror(),
|
|
);
|
|
update_profile_cache_from_response(
|
|
INSECURE_REQWEST_CLIENT
|
|
.delete(url.as_ref())
|
|
.header("Accept", "application/json")
|
|
.header("User-Agent", MINECRAFT_SERVICES_USER_AGENT)
|
|
.bearer_auth(&credentials.access_token)
|
|
.send()
|
|
.await
|
|
.and_then(|response| response.error_for_status())?,
|
|
)
|
|
.await;
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
/// Provides operations for interacting with skins on a Minecraft player profile.
|
|
pub struct MinecraftSkinOperation;
|
|
|
|
impl MinecraftSkinOperation {
|
|
pub async fn equip<TextureStream>(
|
|
credentials: &Credentials,
|
|
texture: TextureStream,
|
|
variant: MinecraftSkinVariant,
|
|
) -> crate::Result<Option<Arc<MinecraftProfile>>>
|
|
where
|
|
TextureStream: TryStream + Send + 'static,
|
|
TextureStream::Error: Into<Box<dyn Error + Send + Sync>>,
|
|
Bytes: From<TextureStream::Ok>,
|
|
{
|
|
let form = reqwest::multipart::Form::new()
|
|
.text(
|
|
"variant",
|
|
match variant {
|
|
MinecraftSkinVariant::Slim => "slim",
|
|
MinecraftSkinVariant::Classic => "classic",
|
|
_ => {
|
|
return Err(ErrorKind::OtherError(
|
|
"Cannot equip skin of unknown model variant".into(),
|
|
)
|
|
.into());
|
|
}
|
|
},
|
|
)
|
|
.part(
|
|
"file",
|
|
Part::stream(Body::wrap_stream(texture))
|
|
.mime_str("image/png")?
|
|
.file_name("skin.png"),
|
|
);
|
|
|
|
let url = mojang_service_url(
|
|
"https://api.minecraftservices.com/minecraft/profile/skins",
|
|
should_use_mojang_mirror(),
|
|
);
|
|
let profile = update_profile_cache_from_response(
|
|
INSECURE_REQWEST_CLIENT
|
|
.post(url.as_ref())
|
|
.header("Accept", "application/json")
|
|
.header("User-Agent", MINECRAFT_SERVICES_USER_AGENT)
|
|
.bearer_auth(&credentials.access_token)
|
|
.multipart(form)
|
|
.send()
|
|
.await
|
|
.and_then(|response| response.error_for_status())?,
|
|
)
|
|
.await;
|
|
|
|
Ok(profile)
|
|
}
|
|
|
|
pub async fn unequip_any(credentials: &Credentials) -> crate::Result<()> {
|
|
let url = mojang_service_url(
|
|
"https://api.minecraftservices.com/minecraft/profile/skins/active",
|
|
should_use_mojang_mirror(),
|
|
);
|
|
update_profile_cache_from_response(
|
|
INSECURE_REQWEST_CLIENT
|
|
.delete(url.as_ref())
|
|
.header("Accept", "application/json")
|
|
.header("User-Agent", MINECRAFT_SERVICES_USER_AGENT)
|
|
.bearer_auth(&credentials.access_token)
|
|
.send()
|
|
.await
|
|
.and_then(|response| response.error_for_status())?,
|
|
)
|
|
.await;
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
async fn update_profile_cache_from_response(
|
|
response: reqwest::Response,
|
|
) -> Option<Arc<MinecraftProfile>> {
|
|
let Some(mut profile) = response.json::<MinecraftProfile>().await.ok()
|
|
else {
|
|
tracing::warn!(
|
|
"Failed to parse player profile from skin or cape operation response, not updating profile cache"
|
|
);
|
|
return None;
|
|
};
|
|
|
|
profile.fetch_time = Some(Instant::now());
|
|
let profile = Arc::new(profile);
|
|
|
|
PROFILE_CACHE
|
|
.lock()
|
|
.await
|
|
.insert(profile.id, ProfileCacheEntry::Hit(Arc::clone(&profile)));
|
|
|
|
Some(profile)
|
|
}
|