feat: a bunch of things
This commit is contained in:
parent
2cd0311a20
commit
2e2b01990e
20 changed files with 16216 additions and 137 deletions
45
src/nnid/agreements.rs
Normal file
45
src/nnid/agreements.rs
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
use std::{env, fs, io};
|
||||
use rocket::fs::NamedFile;
|
||||
use rocket::get;
|
||||
use rocket::response::content::RawXml;
|
||||
use tokio::fs::try_exists;
|
||||
use crate::dsresponse::Ds;
|
||||
use crate::nnid::devices::Device;
|
||||
use crate::xml::Xml;
|
||||
|
||||
#[get("/v1/api/content/agreements/Nintendo-Network-EULA/<lang>/@latest")]
|
||||
pub async fn get_agreement(lang: &str) -> io::Result<Ds<RawXml<NamedFile>>>{
|
||||
let base_path = {
|
||||
// if this crashes then something is wrong with the server setup so crashing here is fine imo
|
||||
let mut path = env::current_dir().unwrap();
|
||||
|
||||
path.push("res");
|
||||
path.push("agreement");
|
||||
|
||||
path
|
||||
};
|
||||
|
||||
let requested_file_path = {
|
||||
let mut path = base_path.clone();
|
||||
|
||||
path.push(format!("{}.xml", lang));
|
||||
|
||||
path
|
||||
};
|
||||
|
||||
|
||||
|
||||
if try_exists(&requested_file_path).await.is_ok_and(|v| v == true){
|
||||
Ok(Ds(RawXml(NamedFile::open(&requested_file_path).await?)))
|
||||
} else {
|
||||
let fallback_path = {
|
||||
let mut path = base_path;
|
||||
|
||||
path.push("DEFAULT.xml");
|
||||
|
||||
path
|
||||
};
|
||||
|
||||
Ok(Ds(RawXml(NamedFile::open(&fallback_path).await?)))
|
||||
}
|
||||
}
|
||||
176
src/nnid/create_account.rs
Normal file
176
src/nnid/create_account.rs
Normal file
|
|
@ -0,0 +1,176 @@
|
|||
use chrono::NaiveDate;
|
||||
use rocket::{post, State};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use crate::account::account::{generate_password, User};
|
||||
use crate::error::Errors;
|
||||
use crate::Pool;
|
||||
use crate::xml::{Xml, YesNoVal};
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct Email{
|
||||
address: Box<str>
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
struct Mii{
|
||||
name: Box<str>,
|
||||
primary: YesNoVal,
|
||||
data: Box<str>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename(serialize = "person"))]
|
||||
struct AccountCreationData{
|
||||
birth_date: NaiveDate,
|
||||
user_id: Box<str>,
|
||||
password: Box<str>,
|
||||
country: Box<str>,
|
||||
language: Box<str>,
|
||||
tz_name: Box<str>,
|
||||
email: Email,
|
||||
gender: Box<str>,
|
||||
marketing_flag: YesNoVal,
|
||||
region: i32
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
#[serde(rename(serialize = "person"))]
|
||||
struct AccountCreationResponseData{
|
||||
pid: i32
|
||||
}
|
||||
|
||||
#[post("/v1/api/people", data="<data>")]
|
||||
async fn create_account(database: &State<Pool>, data: Xml<AccountCreationData>) -> Result<Xml<AccountCreationResponseData>, Errors>{
|
||||
let database = database.inner();
|
||||
|
||||
let AccountCreationData {
|
||||
user_id,
|
||||
password,
|
||||
birth_date,
|
||||
tz_name,
|
||||
language,
|
||||
email: Email{
|
||||
address
|
||||
},
|
||||
marketing_flag,
|
||||
gender,
|
||||
region,
|
||||
country,
|
||||
..
|
||||
} = *data;
|
||||
|
||||
|
||||
|
||||
let new_account = sqlx::query("
|
||||
INSERT INTO users.users (
|
||||
pid,
|
||||
username,
|
||||
password,
|
||||
birthdate,
|
||||
birthdate,
|
||||
timezone,
|
||||
email,
|
||||
country,
|
||||
language,
|
||||
marketing_allowed,
|
||||
off_device_allowed,
|
||||
region,
|
||||
mii_data
|
||||
) VALUES (
|
||||
?,?,?,?,?,?,?,?,?,?
|
||||
)
|
||||
");
|
||||
|
||||
|
||||
|
||||
let pid = connection.transaction::<_, diesel::result::Error, _>(|conn| Box::pin(async move{
|
||||
use crate::schema::users::dsl::*;
|
||||
|
||||
diesel::insert_into(users)
|
||||
.values(&new_account)
|
||||
.returning(User::as_returning())
|
||||
.get_result(conn)
|
||||
.await?;
|
||||
|
||||
|
||||
Ok(())
|
||||
})).await;
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test{
|
||||
use chrono::NaiveDate;
|
||||
use crate::nnid::create_account::AccountCreationData;
|
||||
|
||||
const TEST_XML: &str =
|
||||
"<?xml version=\"1.0\" encoding=\"utf-8\"?>
|
||||
<person>
|
||||
<birth_date>1991-02-03</birth_date>
|
||||
<user_id>testtest</user_id>
|
||||
<password>[PASSWORD]</password>
|
||||
<country>DE</country>
|
||||
<language>en</language>
|
||||
<tz_name>Europe/Berlin</tz_name>
|
||||
<agreement>
|
||||
<agreement_date>2025-02-24T19:42:45</agreement_date>
|
||||
<country>US</country>
|
||||
<location>https://account.spfn.cc/v1/api/content/agreements/Nintendo-Network-EULA/0300</location>
|
||||
<type>NINTENDO-NETWORK-EULA</type>
|
||||
<version>0300</version>
|
||||
</agreement>
|
||||
<email>
|
||||
<address>tvnebel@gmail.com</address>
|
||||
<owned>N</owned>
|
||||
<parent>N</parent>
|
||||
<primary>Y</primary>
|
||||
<validated>N</validated>
|
||||
<type>DEFAULT</type>
|
||||
</email>
|
||||
<mii>
|
||||
<name>y</name>
|
||||
<primary>Y</primary>
|
||||
<data>
|
||||
AwAAQDrPvmeBxJIQ3j+V8Ip4iCWDvgAAAEB5AAAAIABOAEEATQBFAAAAAAAAAEBAAAAhAQJoRBgm
|
||||
NEYUgRIXaA0AACkAUkhQAAAAAAAAAAAAAAAAAAAAAAAAAAAAANzO
|
||||
</data>
|
||||
</mii>
|
||||
<parental_consent>
|
||||
<scope>1</scope>
|
||||
<consent_date>2025-02-24T19:42:45</consent_date>
|
||||
<approval_id>0</approval_id>
|
||||
</parental_consent>
|
||||
<gender>M</gender>
|
||||
<region>1309343744</region>
|
||||
<marketing_flag>N</marketing_flag>
|
||||
<device_attributes>
|
||||
<device_attribute>
|
||||
<name>uuid_account</name>
|
||||
<value>55fdbad0-f2ab-11ef-b648-010144cdca06</value>
|
||||
</device_attribute>
|
||||
<device_attribute>
|
||||
<name>uuid_common</name>
|
||||
<value>898ed052-5e25-11ef-b648-010144cdca06</value>
|
||||
</device_attribute>
|
||||
<device_attribute>
|
||||
<name>persistent_id</name>
|
||||
<value>8000001d</value>
|
||||
</device_attribute>
|
||||
<device_attribute>
|
||||
<name>transferable_id_base</name>
|
||||
<value>0800000444cdca06</value>
|
||||
</device_attribute>
|
||||
<device_attribute>
|
||||
<name>transferable_id_base_common</name>
|
||||
<value>0640000444cdca06</value>
|
||||
</device_attribute>
|
||||
</device_attributes>
|
||||
<off_device_flag>N</off_device_flag>
|
||||
</person>";
|
||||
#[test]
|
||||
fn test(){
|
||||
let data: AccountCreationData = quick_xml::de::from_str(TEST_XML).unwrap();
|
||||
|
||||
assert_eq!(data.birth_date, NaiveDate::from_ymd_opt(1991,02,03).unwrap());
|
||||
assert_eq!(data.user_id.as_ref(), "testtest");
|
||||
}
|
||||
}
|
||||
28
src/nnid/devices.rs
Normal file
28
src/nnid/devices.rs
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
use rocket::get;
|
||||
use serde::Serialize;
|
||||
use crate::xml::Xml;
|
||||
|
||||
#[derive(Serialize)]
|
||||
#[serde(rename(serialize = "device"))]
|
||||
pub struct Device;
|
||||
|
||||
#[get("/v1/api/devices/@current/status")]
|
||||
pub fn current_device_status() -> Xml<Device>{
|
||||
Xml(Device)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::str::from_utf8;
|
||||
use crate::nnid::devices::Device;
|
||||
|
||||
#[test]
|
||||
fn test_device_data(){
|
||||
let text = crate::xml::serialize_with_version(&Device).unwrap();
|
||||
|
||||
|
||||
|
||||
println!("{}", text);
|
||||
|
||||
}
|
||||
}
|
||||
11
src/nnid/email.rs
Normal file
11
src/nnid/email.rs
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
use rocket::{post, FromForm};
|
||||
use rocket::form::Form;
|
||||
|
||||
#[derive(FromForm)]
|
||||
struct ValidateEmailInput{
|
||||
email: String,
|
||||
}
|
||||
#[post("/v1/api/support/validate/email", data="<data>")]
|
||||
pub fn validate(data: Form<ValidateEmailInput>){
|
||||
|
||||
}
|
||||
6
src/nnid/mod.rs
Normal file
6
src/nnid/mod.rs
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
pub mod devices;
|
||||
pub mod agreements;
|
||||
pub mod timezones;
|
||||
pub mod person_exists;
|
||||
pub mod email;
|
||||
mod create_account;
|
||||
58
src/nnid/person_exists.rs
Normal file
58
src/nnid/person_exists.rs
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
use rocket::{get, State};
|
||||
use sqlx::Row;
|
||||
use crate::error::{Error, Errors};
|
||||
use crate::Pool;
|
||||
use crate::xml::Xml;
|
||||
|
||||
#[get("/v1/api/people/<username>")]
|
||||
pub async fn person_exists(database: &State<Pool>, username: &str) -> Result<(), Errors<'static>>{
|
||||
let database = database.inner();
|
||||
|
||||
let exists: bool = sqlx::query_as!(
|
||||
bool,
|
||||
"SELECT EXISTS(SELECT 1 FROM users.users WHERE username = ? )",
|
||||
username
|
||||
).fetch_one(database)
|
||||
.await
|
||||
.unwrap_or(true);
|
||||
|
||||
if exists {
|
||||
Err(
|
||||
Errors{
|
||||
error: &[
|
||||
Error{
|
||||
code: "0100",
|
||||
message: "Account ID already exists"
|
||||
}
|
||||
],
|
||||
}
|
||||
)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test{
|
||||
use crate::error::{Error, Errors};
|
||||
use crate::xml::serialize_with_version;
|
||||
|
||||
#[test]
|
||||
fn test(){
|
||||
let val = Errors{
|
||||
error: &[
|
||||
Error{
|
||||
code: "0100",
|
||||
message: "Account ID already exists"
|
||||
}
|
||||
],
|
||||
};
|
||||
|
||||
let enc = serialize_with_version(&val).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
enc.as_ref(),
|
||||
"<?xml version=\"1.0\"?><errors><error><code>0100</code><message>Account ID already exists</message></error></errors>"
|
||||
)
|
||||
}
|
||||
}
|
||||
64
src/nnid/timezones.rs
Normal file
64
src/nnid/timezones.rs
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
use std::collections::HashMap;
|
||||
use std::{env, fs};
|
||||
use once_cell::sync::Lazy;
|
||||
use rocket::get;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::from_slice;
|
||||
use crate::xml::{serialize_with_version, Xml};
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(rename(serialize = "timezone"))]
|
||||
pub struct Timezone{
|
||||
area: String,
|
||||
language: String,
|
||||
name: String,
|
||||
utc_offset: String,
|
||||
order: String
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
#[serde(rename(serialize = "timezones"))]
|
||||
pub struct Timezones<'a>{
|
||||
pub timezone: &'a [Timezone],
|
||||
}
|
||||
|
||||
pub static TIMEZONES: Lazy<HashMap<String, HashMap<String, Vec<Timezone>>>> = Lazy::new(||{
|
||||
let path = {
|
||||
// if this crashes then something is wrong with the server setup so crashing here is fine imo
|
||||
let mut path = env::current_dir().unwrap();
|
||||
|
||||
path.push("res");
|
||||
path.push("timezones.json");
|
||||
|
||||
path
|
||||
};
|
||||
serde_json::from_str(&fs::read_to_string(path).unwrap()).unwrap()
|
||||
});
|
||||
|
||||
|
||||
#[get("/v1/api/content/time_zones/<zone>/<lang>")]
|
||||
pub fn get_timezone(zone: &str, lang: &str) -> Option<Xml<Timezones<'static>>>{
|
||||
let timezone = (&*TIMEZONES).get(zone)?.get(lang)?;
|
||||
let timezones = Timezones{ timezone };
|
||||
Some(Xml(timezones))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test{
|
||||
use crate::nnid::timezones::{Timezones, TIMEZONES};
|
||||
use crate::xml::serialize_with_version;
|
||||
|
||||
#[test]
|
||||
fn test(){
|
||||
let timezone = (&*TIMEZONES).get("DE").unwrap().get("en").unwrap();
|
||||
let timezones = Timezones{ timezone };
|
||||
let ser = serialize_with_version(&timezones).unwrap();
|
||||
|
||||
println!("{}", ser);
|
||||
|
||||
assert_eq!(
|
||||
ser.as_ref(),
|
||||
"<?xml version=\"1.0\"?><timezones><timezone><area>Europe/Berlin</area><language>en</language><name>Amsterdam, Berlin, Rome</name><utc_offset>3600</utc_offset><order>0</order></timezone></timezones>"
|
||||
)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue