feat(mii): add the mii route

This commit is contained in:
Andrea Toska 2025-04-27 11:04:57 +02:00
commit 4958074aa1
No known key found for this signature in database
GPG key ID: 5B3C83807CCBE9A2
3 changed files with 113 additions and 4 deletions

1
Cargo.lock generated
View file

@ -29,6 +29,7 @@ dependencies = [
"prost", "prost",
"quick-xml", "quick-xml",
"rand", "rand",
"reqwest",
"rocket", "rocket",
"serde", "serde",
"serde_json", "serde_json",

View file

@ -46,6 +46,7 @@ tonic = "0.12.3"
prost = "0.13.4" prost = "0.13.4"
lettre = "0.11.15" lettre = "0.11.15"
rand = "0.8.5" rand = "0.8.5"
reqwest = "0.12.12"

View file

@ -10,18 +10,30 @@ use rocket::{get, post, put, State};
use rocket::serde::{Deserialize, Serialize}; use rocket::serde::{Deserialize, Serialize};
use crate::account::account::{generate_password, Auth, User}; use crate::account::account::{generate_password, Auth, User};
use crate::dsresponse::Ds; use crate::dsresponse::Ds;
use crate::error::Errors; use crate::error::{Error, Errors};
use crate::nnid::pid_distribution::next_pid; use crate::nnid::pid_distribution::next_pid;
use crate::nnid::timezones::{OFFSET_FROM_TIMEZONE}; use crate::nnid::timezones::{OFFSET_FROM_TIMEZONE};
use crate::Pool; use crate::Pool;
use crate::xml::{Xml, YesNoVal}; use crate::xml::{Xml, YesNoVal};
use crate::email::send_verification_email; use crate::email::send_verification_email;
use rand::Rng; 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<Box<str>> = Lazy::new(|| static S3_URL_STRING: Lazy<Box<str>> = Lazy::new(||
env::var("S3_URL").expect("S3_URL not specified").into_boxed_str() 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<BaseUrl> = Lazy::new(|| static S3_URL: Lazy<BaseUrl> = Lazy::new(||
S3_URL_STRING.parse().unwrap() S3_URL_STRING.parse().unwrap()
@ -74,6 +86,17 @@ pub struct Email{
address: Box<str> address: Box<str>
} }
pub struct S3ClientState {
pub client: Arc<Client>,
}
#[derive(Deserialize)]
pub struct UpdateMiiData {
name: Box<str>,
primary: crate::xml::YesNoVal,
data: Box<str>,
}
#[derive(Deserialize, Serialize)] #[derive(Deserialize, Serialize)]
pub struct Mii{ pub struct Mii{
name: Box<str>, name: Box<str>,
@ -357,8 +380,92 @@ fn build_own_profile(user: User) -> Ds<Xml<GetOwnProfileData>> {
} }
#[put("/v1/api/people/@me/miis/@primary")] #[put("/v1/api/people/@me/miis/@primary", data = "<data>")]
pub fn change_mii() { pub async fn refresh_mii_images(
// stubbed(technically requires auth but this doesnt do anything so theres no harm in not doing auth here rn) database: &State<Pool>,
s3: &State<S3ClientState>,
auth: Auth<false>,
data: Xml<UpdateMiiData>,
) -> Result<(), Option<Errors<'static>>> {
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<Client>, 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();
}
}
}