Add saved skin and cape profiles wardrobe feature

This commit is contained in:
SCOPEDD committed 2026-09-29 02:15:38 -04:00
1 parent c7aa563dac
commit ebd9de1bee
5 files changed
+375 -2

No files matched your search

+104 -1
View File
@@ -6,7 +6,7 @@ use crate::settings::Settings;
use crate::state::{build, AppState};
use crate::updater;
use scopenet_core::ping::ServerStatus;
use scopenet_shared::{LauncherManifest, PlayerProfile};
use scopenet_shared::{LauncherManifest, PlayerProfile, SkinProfile};
use serde::Serialize;
use tauri::{AppHandle, Manager, State};
use tauri_plugin_opener::OpenerExt;
@@ -220,6 +220,109 @@ pub async fn set_cape(state: State<'_, AppState>, cape_id: Option<i64>) -> Res<P
profile_response(req.json(&serde_json::json!({ "cape_id": cape_id })).send().await.map_err(err)?).await
}
fn load_skin_profiles(data_dir: &std::path::Path) -> Vec<SkinProfile> {
let path = data_dir.join("skin_profiles.json");
std::fs::read(&path)
.ok()
.and_then(|b| serde_json::from_slice(&b).ok())
.unwrap_or_default()
}
fn write_skin_profiles(data_dir: &std::path::Path, profiles: &[SkinProfile]) -> anyhow::Result<()> {
let path = data_dir.join("skin_profiles.json");
let json = serde_json::to_vec_pretty(profiles)?;
std::fs::write(path, json)?;
Ok(())
}
#[tauri::command]
pub fn get_skin_profiles(state: State<'_, AppState>) -> Res<Vec<SkinProfile>> {
Ok(load_skin_profiles(&state.data_dir))
}
#[tauri::command]
pub async fn save_skin_profile(state: State<'_, AppState>, mut profile: SkinProfile) -> Res<Vec<SkinProfile>> {
use base64::Engine;
if let Some(ref mut data) = profile.skin_data {
if data.starts_with('/') {
if let Some(panel) = state.panel_url() {
*data = format!("{panel}{data}");
}
}
if data.starts_with("http://") || data.starts_with("https://") {
if let Ok(resp) = state.http.get(&*data).send().await {
if resp.status().is_success() {
if let Ok(bytes) = resp.bytes().await {
let b64 = base64::engine::general_purpose::STANDARD.encode(&bytes);
*data = format!("data:image/png;base64,{b64}");
}
}
}
} else if !data.starts_with("data:") && !data.is_empty() {
*data = format!("data:image/png;base64,{data}");
}
}
if profile.created_at.is_empty() {
let ts = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_secs().to_string())
.unwrap_or_default();
profile.created_at = ts;
}
let mut profiles = load_skin_profiles(&state.data_dir);
if let Some(idx) = profiles.iter().position(|x| x.id == profile.id) {
profiles[idx] = profile;
} else {
profiles.push(profile);
}
write_skin_profiles(&state.data_dir, &profiles).map_err(aerr)?;
Ok(profiles)
}
#[tauri::command]
pub fn delete_skin_profile(state: State<'_, AppState>, id: String) -> Res<Vec<SkinProfile>> {
let mut profiles = load_skin_profiles(&state.data_dir);
profiles.retain(|x| x.id != id);
write_skin_profiles(&state.data_dir, &profiles).map_err(aerr)?;
Ok(profiles)
}
#[tauri::command]
pub async fn apply_skin_profile(state: State<'_, AppState>, id: String) -> Res<PlayerProfile> {
use base64::Engine;
let profiles = load_skin_profiles(&state.data_dir);
let p = profiles.into_iter().find(|x| x.id == id).ok_or_else(|| "profile not found".to_string())?;
if let Some(skin_data) = p.skin_data {
if !skin_data.is_empty() {
let bytes = if skin_data.starts_with("data:") {
let comma = skin_data.find(',').ok_or_else(|| "invalid data uri".to_string())?;
base64::engine::general_purpose::STANDARD.decode(skin_data[comma + 1..].trim()).map_err(err)?
} else if skin_data.starts_with("http://") || skin_data.starts_with("https://") {
state.http.get(&skin_data).send().await.map_err(err)?.bytes().await.map_err(err)?.to_vec()
} else {
base64::engine::general_purpose::STANDARD.decode(skin_data.trim()).map_err(err)?
};
let form = reqwest::multipart::Form::new()
.text("model", p.skin_model.clone())
.part("file", reqwest::multipart::Part::bytes(bytes).file_name("skin.png").mime_str("image/png").map_err(err)?);
let req = account_api(&state, reqwest::Method::POST, "/account/skin").await?;
profile_response(req.multipart(form).send().await.map_err(err)?).await?;
} else {
let req = account_api(&state, reqwest::Method::PUT, "/account/skin/model").await?;
profile_response(req.json(&serde_json::json!({ "model": p.skin_model })).send().await.map_err(err)?).await?;
}
} else {
let req = account_api(&state, reqwest::Method::PUT, "/account/skin/model").await?;
profile_response(req.json(&serde_json::json!({ "model": p.skin_model })).send().await.map_err(err)?).await?;
}
let req = account_api(&state, reqwest::Method::PUT, "/account/cape").await?;
profile_response(req.json(&serde_json::json!({ "cape_id": p.cape_id })).send().await.map_err(err)?).await?;
account_profile(state).await
}
// ---- game ----
#[tauri::command]