Add saved skin and cape profiles wardrobe feature
This commit is contained in:
1 parent
c7aa563dac
commit
ebd9de1bee
5 files changed
+375
-2
No files matched your search
@@ -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]
|
||||
|
||||
@@ -59,6 +59,10 @@ pub fn run() {
|
||||
commands::set_skin_model,
|
||||
commands::delete_skin,
|
||||
commands::set_cape,
|
||||
commands::get_skin_profiles,
|
||||
commands::save_skin_profile,
|
||||
commands::delete_skin_profile,
|
||||
commands::apply_skin_profile,
|
||||
commands::set_username,
|
||||
commands::select_account,
|
||||
commands::remove_account,
|
||||
|
||||
Reference in new issue
Block a user