From 4958074aa1df5bb57149fa1c12a6e3344ddaa813 Mon Sep 17 00:00:00 2001 From: Andrea Toska Date: Sun, 27 Apr 2025 11:04:57 +0200 Subject: [PATCH] feat(mii): add the mii route --- Cargo.lock | 1 + Cargo.toml | 1 + src/nnid/people.rs | 115 +++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 113 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 31fa8ea..5cc2276 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -29,6 +29,7 @@ dependencies = [ "prost", "quick-xml", "rand", + "reqwest", "rocket", "serde", "serde_json", diff --git a/Cargo.toml b/Cargo.toml index 7252c2d..fb981ac 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -46,6 +46,7 @@ tonic = "0.12.3" prost = "0.13.4" lettre = "0.11.15" rand = "0.8.5" +reqwest = "0.12.12" diff --git a/src/nnid/people.rs b/src/nnid/people.rs index c84910a..c1ffd00 100644 --- a/src/nnid/people.rs +++ b/src/nnid/people.rs @@ -10,18 +10,30 @@ use rocket::{get, post, put, State}; use rocket::serde::{Deserialize, Serialize}; use crate::account::account::{generate_password, Auth, User}; use crate::dsresponse::Ds; -use crate::error::Errors; +use crate::error::{Error, Errors}; use crate::nnid::pid_distribution::next_pid; use crate::nnid::timezones::{OFFSET_FROM_TIMEZONE}; use crate::Pool; use crate::xml::{Xml, YesNoVal}; use crate::email::send_verification_email; use rand::Rng; +use mii::{get_image_png, get_image_tga}; +use minio::s3::client::Client; +use minio::s3::args::PutObjectArgs; +use std::sync::Arc; static S3_URL_STRING: Lazy> = Lazy::new(|| env::var("S3_URL").expect("S3_URL not specified").into_boxed_str() ); +const DATABASE_ERROR: Errors = Errors{ + error: &[ + Error{ + code: "9999", + message: "Internal server error" + } + ] +}; static S3_URL: Lazy = Lazy::new(|| S3_URL_STRING.parse().unwrap() @@ -74,6 +86,17 @@ pub struct Email{ address: Box } +pub struct S3ClientState { + pub client: Arc, +} + +#[derive(Deserialize)] +pub struct UpdateMiiData { + name: Box, + primary: crate::xml::YesNoVal, + data: Box, +} + #[derive(Deserialize, Serialize)] pub struct Mii{ name: Box, @@ -357,8 +380,92 @@ fn build_own_profile(user: User) -> Ds> { } -#[put("/v1/api/people/@me/miis/@primary")] -pub fn change_mii() { - // stubbed(technically requires auth but this doesnt do anything so theres no harm in not doing auth here rn) +#[put("/v1/api/people/@me/miis/@primary", data = "")] +pub async fn refresh_mii_images( + database: &State, + s3: &State, + auth: Auth, + data: Xml, +) -> Result<(), Option>> { + let db = database.inner(); + let pid = auth.pid; + + let mii_data = data.data.as_ref(); + + let result = sqlx::query!( + "UPDATE users SET mii_data = $1 WHERE pid = $2", + mii_data, + pid + ) + .execute(db) + .await; + + if result.is_err() { + return Err(Some(DATABASE_ERROR)); + } + + generate_mii_images(s3.client.clone(), "pn-cdn", pid, mii_data).await; + + Ok(()) } +pub async fn generate_mii_images(client: Arc, bucket: &str, pid: i32, mii_data: &str) { + let user_mii_key = format!("mii/{}", pid); + + // Upload normal face images + if let Some(png_data) = get_image_png(mii_data).await { + let object_content = ObjectContent::from(png_data.clone()); + let _ = client.put_object_content( + bucket, + &format!("{}/normal_face.png", user_mii_key), + object_content + ).send().await.ok(); + } + + if let Some(tga_data) = get_image_tga(mii_data).await { + let object_content = ObjectContent::from(tga_data.clone()); + let _ = client.put_object_content( + bucket, + &format!("{}/standard.tga", user_mii_key), + object_content + ).send().await.ok(); + } + + // Upload expressions + let expressions = [ + "frustrated", + "smile_open_mouth", + "wink_left", + "sorrow", + "surprise_open_mouth" + ]; + + for expression in expressions.iter() { + let url = format!("https://mii-unsecure.ariankordi.net/miis/image.png?data={}&expression={}&type=face&width=128&instance_count=1", mii_data, expression); + + if let Ok(resp) = reqwest::get(&url).await { + if let Ok(bytes) = resp.bytes().await { + let object_content = ObjectContent::from(bytes.to_vec()); + let _ = client.put_object_content( + bucket, + &format!("{}/{}.png", user_mii_key, expression), + object_content + ).send().await.ok(); + } + } + } + + // Upload body + let body_url = format!("https://mii-unsecure.ariankordi.net/miis/image.png?data={}&type=all_body&width=270&instance_count=1", mii_data); + + if let Ok(resp) = reqwest::get(&body_url).await { + if let Ok(bytes) = resp.bytes().await { + let object_content = ObjectContent::from(bytes.to_vec()); + let _ = client.put_object_content( + bucket, + &format!("{}/body.png", user_mii_key), + object_content + ).send().await.ok(); + } + } +}