diff --git a/Cargo.toml b/Cargo.toml index d0312a4..19c5fdc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,9 +5,10 @@ license = "AGPL-3" edition = "2021" [dependencies] -tokio = {version = "1.27.0", features = ["rt", "macros", "rt-multi-thread"]} -serde = {version = "1.0.159", features = ["derive"]} -serde_json = "1.0.95" +tokio = {version = "1.28.1", features = ["rt", "macros", "rt-multi-thread", "full"]} +serde = {version = "1.0.162", features = ["derive"]} +serde_json = { version = "1.0.96", features = ["raw_value"] } +serde_repr = "0.1.12" reqwest = {version = "0.11.16", features = ["multipart"]} url = "2.3.1" chrono = {version = "0.4.24", features = ["serde"]} @@ -17,6 +18,18 @@ native-tls = "0.2.11" tokio-tungstenite = {version = "0.18.0", features = ["native-tls"]} futures-util = "0.3.28" http = "0.2.9" +openssl = "0.10.52" +base64 = "0.21.2" +hostname = "0.3.1" +bitflags = { version = "2.2.1", features = ["serde"] } +atomic = "0.5.3" +bigdecimal = "0.3.1" +num-bigint = "0.4.3" +lazy_static = "1.4.0" +poem = { version = "1.3.55", optional = true } +sqlx = { version = "0.6.3", features = ["mysql", "sqlite", "json", "chrono", "ipnetwork", "runtime-tokio-native-tls", "any"], optional = true } +thiserror = "1.0.40" +jsonwebtoken = "8.3.0" [dev-dependencies] -lazy_static = "1.4.0" \ No newline at end of file +lazy_static = "1.4.0" diff --git a/src/api/auth/login.rs b/src/api/auth/login.rs index 453ff1b..8589b39 100644 --- a/src/api/auth/login.rs +++ b/src/api/auth/login.rs @@ -6,17 +6,16 @@ pub mod login { use serde_json::{from_str, json}; use crate::api::limits::LimitType; - use crate::api::schemas::LoginSchema; - use crate::api::types::{ErrorResponse, LoginResult}; use crate::errors::InstanceServerError; - use crate::instance::Instance; + use crate::instance::{Instance, UserMeta}; use crate::limit::LimitedRequester; + use crate::types::{ErrorResponse, LoginResult, LoginSchema}; impl Instance { pub async fn login_account( &mut self, login_schema: &LoginSchema, - ) -> Result { + ) -> Result { let mut requester = LimitedRequester::new().await; let json_schema = json!(login_schema); let client = Client::new(); @@ -59,7 +58,7 @@ pub mod login { .get_user(login_result.token.clone(), None) .await .unwrap(); - let user = crate::api::types::User::new( + let user = UserMeta::new( Rc::new(RefCell::new(self.clone())), login_result.token, cloned_limits, diff --git a/src/api/auth/register.rs b/src/api/auth/register.rs index edafc5d..dc5bf61 100644 --- a/src/api/auth/register.rs +++ b/src/api/auth/register.rs @@ -5,10 +5,11 @@ pub mod register { use serde_json::{from_str, json}; use crate::{ - api::{limits::LimitType, schemas::RegisterSchema, types::ErrorResponse, Token}, + api::limits::LimitType, errors::InstanceServerError, - instance::Instance, + instance::{Instance, Token, UserMeta}, limit::LimitedRequester, + types::{ErrorResponse, RegisterSchema}, }; impl Instance { @@ -22,7 +23,7 @@ pub mod register { pub async fn register_account( &mut self, register_schema: &RegisterSchema, - ) -> Result { + ) -> Result { let json_schema = json!(register_schema); let mut limited_requester = LimitedRequester::new().await; let client = Client::new(); @@ -62,14 +63,11 @@ pub mod register { return Err(InstanceServerError::InvalidFormBodyError { error_type, error }); } let user_object = self.get_user(token.clone(), None).await.unwrap(); - let settings = crate::api::types::User::get_settings( - &token, - &self.urls.get_api().to_string(), - &mut self.limits, - ) - .await - .unwrap(); - let user: crate::api::types::User = crate::api::types::User::new( + let settings = + UserMeta::get_settings(&token, &self.urls.get_api().to_string(), &mut self.limits) + .await + .unwrap(); + let user: UserMeta = UserMeta::new( Rc::new(RefCell::new(self.clone())), token.clone(), cloned_limits, @@ -83,9 +81,9 @@ pub mod register { #[cfg(test)] mod test { - use crate::api::schemas::{AuthUsername, RegisterSchema}; use crate::instance::Instance; use crate::limit::LimitedRequester; + use crate::types::RegisterSchema; use crate::URLBundle; #[tokio::test] @@ -95,10 +93,10 @@ mod test { "http://localhost:3001".to_string(), "http://localhost:3001".to_string(), ); - let limited_requester = LimitedRequester::new().await; + let _limited_requester = LimitedRequester::new().await; let mut test_instance = Instance::new(urls.clone()).await.unwrap(); let reg = RegisterSchema::new( - AuthUsername::new("Hiiii".to_string()).unwrap(), + "Hiiii".to_string(), None, true, None, diff --git a/src/api/channels/channels.rs b/src/api/channels/channels.rs index 51552a1..aba4725 100644 --- a/src/api/channels/channels.rs +++ b/src/api/channels/channels.rs @@ -2,19 +2,17 @@ use reqwest::Client; use serde_json::from_str; use crate::{ - api::{limits::Limits, types}, - errors::InstanceServerError, - limit::LimitedRequester, + api::limits::Limits, errors::InstanceServerError, limit::LimitedRequester, types::Channel, }; -impl types::Channel { +impl Channel { pub async fn get( token: &str, url_api: &str, channel_id: &str, limits_user: &mut Limits, limits_instance: &mut Limits, - ) -> Result { + ) -> Result { let request = Client::new() .get(format!("{}/channels/{}/", url_api, channel_id)) .bearer_auth(token); @@ -32,7 +30,7 @@ impl types::Channel { Err(e) => return Err(e), }; let result_text = result.text().await.unwrap(); - match from_str::(&result_text) { + match from_str::(&result_text) { Ok(object) => Ok(object), Err(e) => Err(InstanceServerError::RequestErrorError { url: format!("{}/channels/{}/", url_api, channel_id), diff --git a/src/api/channels/messages.rs b/src/api/channels/messages.rs index 0aaf8d1..9a68eaa 100644 --- a/src/api/channels/messages.rs +++ b/src/api/channels/messages.rs @@ -5,8 +5,9 @@ pub mod messages { use serde_json::to_string; use crate::api::limits::Limits; - use crate::api::types::{Message, PartialDiscordFileAttachment, User}; + use crate::instance::UserMeta; use crate::limit::LimitedRequester; + use crate::types::{Message, MessageSendSchema, PartialDiscordFileAttachment}; impl Message { /** @@ -23,7 +24,7 @@ pub mod messages { pub async fn send<'a>( url_api: String, channel_id: String, - message: &mut crate::api::schemas::MessageSendSchema, + message: &mut MessageSendSchema, files: Option>, token: String, limits_user: &mut Limits, @@ -65,7 +66,7 @@ pub mod messages { let mut header_map = HeaderMap::new(); header_map.insert(CONTENT_DISPOSITION, content_disposition.parse().unwrap()); - let mut part = multipart::Part::bytes(attachment_content) + let part = multipart::Part::bytes(attachment_content) .file_name(attachment_filename) .headers(header_map); @@ -91,10 +92,10 @@ pub mod messages { } } - impl User { + impl UserMeta { pub async fn send_message( &mut self, - message: &mut crate::api::schemas::MessageSendSchema, + message: &mut MessageSendSchema, channel_id: String, files: Option>, ) -> Result { @@ -116,11 +117,9 @@ pub mod messages { #[cfg(test)] mod test { - use crate::{ - api::{AuthUsername, LoginSchema}, - instance::Instance, - limit::LimitedRequester, - }; + use crate::instance::UserMeta; + use crate::types::{LoginSchema, MessageSendSchema, PartialDiscordFileAttachment}; + use crate::{instance::Instance}; use std::io::Read; use std::{cell::RefCell, fs::File}; @@ -129,7 +128,7 @@ mod test { #[tokio::test] async fn send_message() { let channel_id = "1106954414356168802".to_string(); - let mut message = crate::api::schemas::MessageSendSchema::new( + let mut message = MessageSendSchema::new( None, Some("A Message!".to_string()), None, @@ -149,7 +148,7 @@ mod test { .await .unwrap(); let login_schema: LoginSchema = LoginSchema::new( - AuthUsername::new("user@test.xyz".to_string()).unwrap(), + "user@test.xyz".to_string(), "transrights".to_string(), None, None, @@ -162,7 +161,7 @@ mod test { println!("TOKEN: {}", token); let settings = login_result.settings; let limits = instance.limits.clone(); - let mut user = crate::api::types::User::new( + let mut user = UserMeta::new( Rc::new(RefCell::new(instance)), token, limits, @@ -185,7 +184,7 @@ mod test { reader.read_to_end(&mut buffer).unwrap(); - let attachment = crate::api::types::PartialDiscordFileAttachment { + let attachment = PartialDiscordFileAttachment { id: None, filename: "README.md".to_string(), description: None, @@ -201,7 +200,7 @@ mod test { content: buffer, }; - let mut message = crate::api::schemas::MessageSendSchema::new( + let mut message = MessageSendSchema::new( None, Some("trans rights now".to_string()), None, @@ -221,7 +220,7 @@ mod test { .await .unwrap(); let login_schema: LoginSchema = LoginSchema::new( - AuthUsername::new("user@test.xyz".to_string()).unwrap(), + "user@test.xyz".to_string(), "transrights".to_string(), None, None, @@ -233,7 +232,7 @@ mod test { let token = login_result.token; let settings = login_result.settings; let limits = instance.limits.clone(); - let mut user = crate::api::types::User::new( + let mut user = UserMeta::new( Rc::new(RefCell::new(instance)), token, limits, diff --git a/src/api/guilds/guilds.rs b/src/api/guilds/guilds.rs index 21140f1..d1f03f0 100644 --- a/src/api/guilds/guilds.rs +++ b/src/api/guilds/guilds.rs @@ -3,12 +3,12 @@ use serde_json::from_str; use serde_json::to_string; use crate::api::limits::Limits; -use crate::api::schemas; -use crate::api::types; use crate::errors::InstanceServerError; +use crate::instance::UserMeta; use crate::limit::LimitedRequester; +use crate::types::{Channel, ChannelCreateSchema, Guild, GuildCreateResponse, GuildCreateSchema}; -impl types::Guild { +impl Guild { /// Creates a new guild with the given parameters. /// /// # Arguments @@ -38,9 +38,9 @@ impl types::Guild { /// } /// ``` pub async fn create( - user: &mut types::User, + user: &mut UserMeta, url_api: &str, - guild_create_schema: schemas::GuildCreateSchema, + guild_create_schema: GuildCreateSchema, ) -> Result { let url = format!("{}/guilds/", url_api); let limits_user = user.limits.get_as_mut(); @@ -62,7 +62,7 @@ impl types::Guild { Ok(result) => result, Err(e) => return Err(e), }; - let id: types::GuildCreateResponse = from_str(&result.text().await.unwrap()).unwrap(); + let id: GuildCreateResponse = from_str(&result.text().await.unwrap()).unwrap(); Ok(id.id) } @@ -91,7 +91,7 @@ impl types::Guild { /// } /// ``` pub async fn delete( - user: &mut types::User, + user: &mut UserMeta, url_api: &str, guild_id: String, ) -> Option { @@ -134,14 +134,14 @@ impl types::Guild { &self, url_api: &str, token: &str, - schema: schemas::ChannelCreateSchema, + schema: ChannelCreateSchema, limits_user: &mut Limits, limits_instance: &mut Limits, - ) -> Result { - types::Channel::create( + ) -> Result { + Channel::create( token, url_api, - &self.id, + &self.id.to_string(), schema, limits_user, limits_instance, @@ -150,7 +150,7 @@ impl types::Guild { } } -impl types::Channel { +impl Channel { /// Sends a request to create a new channel in a guild. /// /// # Arguments @@ -169,10 +169,10 @@ impl types::Channel { token: &str, url_api: &str, guild_id: &str, - schema: schemas::ChannelCreateSchema, + schema: ChannelCreateSchema, limits_user: &mut Limits, limits_instance: &mut Limits, - ) -> Result { + ) -> Result { let request = Client::new() .post(format!("{}/guilds/{}/channels/", url_api, guild_id)) .bearer_auth(token) @@ -190,7 +190,7 @@ impl types::Channel { Ok(result) => result, Err(e) => return Err(e), }; - match from_str::(&result.text().await.unwrap()) { + match from_str::(&result.text().await.unwrap()) { Ok(object) => Ok(object), Err(e) => Err(InstanceServerError::RequestErrorError { url: format!("{}/guilds/{}/channels/", url_api, guild_id), diff --git a/src/api/mod.rs b/src/api/mod.rs index 37abc50..0bac755 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -2,13 +2,9 @@ pub mod auth; pub mod channels; pub mod guilds; pub mod policies; -pub mod schemas; -pub mod types; pub mod users; pub use channels::messages::*; pub use guilds::*; pub use policies::instance::instance::*; pub use policies::instance::limits::*; -pub use schemas::*; -pub use types::*; diff --git a/src/api/policies/instance/instance.rs b/src/api/policies/instance/instance.rs index 2196b80..20d7d6f 100644 --- a/src/api/policies/instance/instance.rs +++ b/src/api/policies/instance/instance.rs @@ -2,7 +2,8 @@ use reqwest::Client; use serde_json::from_str; use crate::errors::InstanceServerError; -use crate::{api::types::InstancePolicies, instance::Instance}; +use crate::instance::Instance; +use crate::types::GeneralConfiguration; impl Instance { /** @@ -10,7 +11,9 @@ impl Instance { # Errors [`InstanceServerError`] - If the request fails. */ - pub async fn instance_policies_schema(&self) -> Result { + pub async fn general_configuration_schema( + &self, + ) -> Result { let client = Client::new(); let endpoint_url = self.urls.get_api().to_string() + "/policies/instance/"; let request = match client.get(&endpoint_url).send().await { @@ -30,7 +33,7 @@ impl Instance { } let body = request.text().await.unwrap(); - let instance_policies_schema: InstancePolicies = from_str(&body).unwrap(); + let instance_policies_schema: GeneralConfiguration = from_str(&body).unwrap(); Ok(instance_policies_schema) } } @@ -46,9 +49,9 @@ mod instance_policies_schema_test { "http://localhost:3001".to_string(), "http://localhost:3001".to_string(), ); - let limited_requester = LimitedRequester::new().await; + let _limited_requester = LimitedRequester::new().await; let test_instance = Instance::new(urls.clone()).await.unwrap(); - let _schema = test_instance.instance_policies_schema().await.unwrap(); + let _schema = test_instance.general_configuration_schema().await.unwrap(); } } diff --git a/src/api/schemas.rs b/src/api/schemas.rs deleted file mode 100644 index fb1a14f..0000000 --- a/src/api/schemas.rs +++ /dev/null @@ -1,432 +0,0 @@ -use regex::Regex; -use serde::{Deserialize, Serialize}; - -use crate::errors::FieldFormatError; - -use super::{Channel, Embed}; - -/** -A struct that represents a well-formed email address. - */ -#[derive(Clone, PartialEq, Eq, Debug)] -pub struct AuthEmail { - pub email: String, -} - -impl AuthEmail { - /** - Returns a new [`Result`]. - ## Arguments - The email address you want to validate. - ## Errors - You will receive a [`FieldFormatError`], if: - - The email address is not in a valid format. - - */ - pub fn new(email: String) -> Result { - let regex = Regex::new(r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$").unwrap(); - if !regex.is_match(email.as_str()) { - return Err(FieldFormatError::EmailError); - } - Ok(AuthEmail { email }) - } -} - -/** -A struct that represents a well-formed username. -## Arguments -Please use new() to create a new instance of this struct. -## Errors -You will receive a [`FieldFormatError`], if: -- The username is not between 2 and 32 characters. - */ -#[derive(Clone, PartialEq, Eq, Debug)] -pub struct AuthUsername { - pub username: String, -} - -impl AuthUsername { - /** - Returns a new [`Result`]. - ## Arguments - The username you want to validate. - ## Errors - You will receive a [`FieldFormatError`], if: - - The username is not between 2 and 32 characters. - */ - pub fn new(username: String) -> Result { - if username.len() < 2 || username.len() > 32 { - Err(FieldFormatError::UsernameError) - } else { - Ok(AuthUsername { username }) - } - } -} - -/** -A struct that represents a well-formed password. -## Arguments -Please use new() to create a new instance of this struct. -## Errors -You will receive a [`FieldFormatError`], if: -- The password is not between 1 and 72 characters. - */ -#[derive(Clone, PartialEq, Eq, Debug)] -pub struct AuthPassword { - pub password: String, -} - -impl AuthPassword { - /** - Returns a new [`Result`]. - ## Arguments - The password you want to validate. - ## Errors - You will receive a [`FieldFormatError`], if: - - The password is not between 1 and 72 characters. - */ - pub fn new(password: String) -> Result { - if password.is_empty() || password.len() > 72 { - Err(FieldFormatError::PasswordError) - } else { - Ok(AuthPassword { password }) - } - } -} - -/** -A struct that represents a well-formed register request. -## Arguments -Please use new() to create a new instance of this struct. -## Errors -You will receive a [`FieldFormatError`], if: -- The username is not between 2 and 32 characters. -- The password is not between 1 and 72 characters. - */ - -#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] -#[serde(rename_all = "snake_case")] -pub struct RegisterSchema { - username: String, - password: Option, - consent: bool, - email: Option, - fingerprint: Option, - invite: Option, - date_of_birth: Option, - gift_code_sku_id: Option, - captcha_key: Option, - promotional_email_opt_in: Option, -} - -impl RegisterSchema { - /** - Returns a new [`Result`]. - ## Arguments - All but "String::username" and "bool::consent" are optional. - - ## Errors - You will receive a [`FieldFormatError`], if: - - The username is less than 2 or more than 32 characters in length - - You supply a `password` which is less than 1 or more than 72 characters in length. - - These constraints have been defined [in the Spacebar-API](https://docs.spacebar.chat/routes/) - */ - pub fn new( - username: AuthUsername, - password: Option, - consent: bool, - email: Option, - fingerprint: Option, - invite: Option, - date_of_birth: Option, - gift_code_sku_id: Option, - captcha_key: Option, - promotional_email_opt_in: Option, - ) -> Result { - let username = username.username; - - let email_addr; - if email.is_some() { - email_addr = Some(email.unwrap().email); - } else { - email_addr = None; - } - - let has_password; - if password.is_some() { - has_password = Some(password.unwrap().password); - } else { - has_password = None; - } - - if !consent { - return Err(FieldFormatError::ConsentError); - } - - Ok(RegisterSchema { - username, - password: has_password, - consent, - email: email_addr, - fingerprint, - invite, - date_of_birth, - gift_code_sku_id, - captcha_key, - promotional_email_opt_in, - }) - } -} - -/** -A struct that represents a well-formed login request. -## Arguments -Please use new() to create a new instance of this struct. -## Errors -You will receive a [`FieldFormatError`], if: -- The username is not between 2 and 32 characters. -- The password is not between 1 and 72 characters. - */ -#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] -#[serde(rename_all = "snake_case")] -pub struct LoginSchema { - login: String, - password: String, - undelete: Option, - captcha_key: Option, - login_source: Option, - gift_code_sku_id: Option, -} - -impl LoginSchema { - /** - Returns a new [`Result`]. - ## Arguments - login: The username you want to login with. - password: The password you want to login with. - undelete: Honestly no idea what this is for. - captcha_key: The captcha key you want to login with. - login_source: The login source. - gift_code_sku_id: The gift code sku id. - ## Errors - You will receive a [`FieldFormatError`], if: - - The username is less than 2 or more than 32 characters in length - */ - pub fn new( - login: AuthUsername, - password: String, - undelete: Option, - captcha_key: Option, - login_source: Option, - gift_code_sku_id: Option, - ) -> Result { - let login = login.username; - Ok(LoginSchema { - login, - password, - undelete, - captcha_key, - login_source, - gift_code_sku_id, - }) - } -} - -#[derive(Debug, Serialize, Deserialize)] -#[serde(rename_all = "snake_case")] -pub struct TotpSchema { - code: String, - ticket: String, - gift_code_sku_id: Option, - login_source: Option, -} - -#[derive(Debug, Deserialize, Serialize)] -#[serde(rename_all = "snake_case")] -pub struct MessageSendSchema { - #[serde(rename = "type")] - message_type: Option, - content: Option, - nonce: Option, - tts: Option, - embeds: Option>, - allowed_mentions: Option, - message_reference: Option, - components: Option>, - sticker_ids: Option>, - pub attachments: Option>, -} - -// make a new() method for MessageSendSchema -impl MessageSendSchema { - pub fn new( - message_type: Option, - content: Option, - nonce: Option, - tts: Option, - embeds: Option>, - allowed_mentions: Option, - message_reference: Option, - components: Option>, - sticker_ids: Option>, - attachments: Option>, - ) -> MessageSendSchema { - MessageSendSchema { - message_type, - content, - nonce, - tts, - embeds, - allowed_mentions, - message_reference, - components, - sticker_ids, - attachments, - } - } -} - -#[derive(Debug, Deserialize, Serialize)] -#[serde(rename_all = "snake_case")] -pub struct GuildCreateSchema { - pub name: Option, - pub region: Option, - pub icon: Option, - pub channels: Option>, - pub guild_template_code: Option, - pub system_channel_id: Option, - pub rules_channel_id: Option, -} - -#[derive(Debug, Deserialize, Serialize)] -#[serde(rename_all = "snake_case")] -pub struct UserModifySchema { - pub username: Option, - pub avatar: Option, - pub bio: Option, - pub accent_color: Option, - pub banner: Option, - pub current_password: Option, - pub new_password: Option, - pub code: Option, - pub email: Option, - pub discriminator: Option, -} - -#[derive(Debug, Deserialize, Serialize)] -#[serde(rename_all = "snake_case")] -// TODO: Implement in polyphony/types -pub struct ChannelCreateSchema { - pub name: String, - #[serde(rename = "type")] - pub channel_type: Option, - pub topic: Option, - pub icon: Option, - pub bitrate: Option, - pub user_limit: Option, - pub rate_limit_per_user: Option, - pub position: Option, - pub permission_overwrites: Option>, - pub parent_id: Option, - pub id: Option, - pub nsfw: Option, - pub rtc_region: Option, - pub default_auto_archive_duration: Option, - pub default_reaction_emoji: Option, - pub flags: Option, - pub default_thread_rate_limit_per_user: Option, - pub video_quality_mode: Option, -} - -// I know that some of these tests are... really really basic and unneccessary, but sometimes, I -// just feel like writing tests, so there you go :) -@bitfl0wer -#[cfg(test)] -mod schemas_tests { - use super::*; - use crate::errors::FieldFormatError; - - #[test] - fn password_too_short() { - assert_eq!( - AuthPassword::new("".to_string()), - Err(FieldFormatError::PasswordError) - ); - } - - #[test] - fn password_too_long() { - let mut long_pw = String::new(); - for _ in 0..73 { - long_pw += "a"; - } - assert_eq!( - AuthPassword::new(long_pw), - Err(FieldFormatError::PasswordError) - ); - } - - #[test] - fn username_too_short() { - assert_eq!( - AuthUsername::new("T".to_string()), - Err(FieldFormatError::UsernameError) - ); - } - - #[test] - fn username_too_long() { - let mut long_un = String::new(); - for _ in 0..33 { - long_un += "a"; - } - assert_eq!( - AuthUsername::new(long_un), - Err(FieldFormatError::UsernameError) - ); - } - - #[test] - fn consent_false() { - assert_eq!( - RegisterSchema::new( - AuthUsername::new("Test".to_string()).unwrap(), - None, - false, - None, - None, - None, - None, - None, - None, - None, - ), - Err(FieldFormatError::ConsentError) - ); - } - - #[test] - fn invalid_email() { - assert_eq!( - AuthEmail::new("p@p.p".to_string()), - Err(FieldFormatError::EmailError) - ) - } - - #[test] - fn valid_email() { - let reg = RegisterSchema::new( - AuthUsername::new("Testy".to_string()).unwrap(), - None, - true, - Some(AuthEmail::new("me@mail.de".to_string()).unwrap()), - None, - None, - None, - None, - None, - None, - ); - assert_ne!(reg, Err(FieldFormatError::EmailError)); - } -} diff --git a/src/api/types.rs b/src/api/types.rs deleted file mode 100644 index f040dd2..0000000 --- a/src/api/types.rs +++ /dev/null @@ -1,1482 +0,0 @@ -/* -To learn more about the types implemented here, please visit -https://discord.com/developers/docs . -I do not feel like re-documenting all of this, as everything is already perfectly explained there. -*/ - -use std::{cell::RefCell, rc::Rc}; - -use chrono::{DateTime, Utc}; -use serde::{Deserialize, Serialize}; -use serde_json::from_value; - -use crate::{api::limits::Limits, instance::Instance}; - -pub trait WebSocketEvent {} - -#[derive(Debug, Serialize, Deserialize)] -pub struct LoginResult { - pub token: String, - pub settings: UserSettings, -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct UserSettings { - afk_timeout: i32, - allow_accessibility_detection: bool, - animate_emoji: bool, - animate_stickers: i32, - contact_sync_enabled: bool, - convert_emoticons: bool, - custom_status: Option, - default_guilds_restricted: bool, - detect_platform_accounts: bool, - developer_mode: bool, - disable_games_tab: bool, - enable_tts_command: bool, - explicit_content_filter: i32, - friend_source_flags: FriendSourceFlags, - friend_discovery_flags: Option, - gateway_connected: bool, - gif_auto_play: bool, - guild_folders: Vec, - guild_positions: Vec, - inline_attachment_media: bool, - inline_embed_media: bool, - locale: String, - message_display_compact: bool, - native_phone_integration_enabled: bool, - render_embeds: bool, - render_reactions: bool, - restricted_guilds: Vec, - show_current_game: bool, - status: String, - stream_notifications_enabled: bool, - theme: String, - timezone_offset: i32, - //view_nsfw_guilds: bool, -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct FriendSourceFlags { - all: Option, - mutual_friends: Option, - mutual_guilds: Option, -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct GuildFolder { - id: String, - guild_ids: Vec, - name: String, -} - -/** -Represents the result you get from GET: /api/instance/policies/. -*/ -#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] -#[serde(rename_all = "camelCase")] -pub struct InstancePolicies { - instance_name: String, - instance_description: Option, - front_page: Option, - tos_page: Option, - correspondence_email: Option, - correspondence_user_id: Option, - image: Option, - instance_id: Option, -} - -impl InstancePolicies { - pub fn new( - instance_name: String, - instance_description: Option, - front_page: Option, - tos_page: Option, - correspondence_email: Option, - correspondence_user_id: Option, - image: Option, - instance_id: Option, - ) -> Self { - InstancePolicies { - instance_name, - instance_description, - front_page, - tos_page, - correspondence_email, - correspondence_user_id, - image, - instance_id, - } - } -} - -#[derive(Serialize, Deserialize, Debug)] -pub struct ErrorResponse { - pub code: i32, - pub message: String, - pub errors: IntermittentError, -} - -#[derive(Serialize, Deserialize, Debug)] -pub struct IntermittentError { - #[serde(flatten)] - pub errors: std::collections::HashMap, -} - -#[derive(Serialize, Deserialize, Debug, Default)] -pub struct ErrorField { - #[serde(default)] - pub _errors: Vec, -} - -#[derive(Serialize, Deserialize, Debug)] -pub struct Error { - pub message: String, - pub code: String, -} - -#[derive(Serialize, Deserialize, Debug, Default)] -pub struct UnavailableGuild { - id: String, - unavailable: bool, -} - -/// See https://discord.com/developers/docs/resources/guild -#[derive(Serialize, Deserialize, Debug, Default, Clone)] -pub struct Guild { - pub id: String, - pub name: String, - pub icon: Option, - pub icon_hash: Option, - pub splash: Option, - pub discovery_splash: Option, - pub owner: Option, - pub owner_id: Option, - pub permissions: Option, - pub afk_channel_id: Option, - pub afk_timeout: Option, - pub widget_enabled: Option, - pub widget_channel_id: Option, - pub widget_channel: Option, - pub verification_level: Option, - pub default_message_notifications: Option, - pub explicit_content_filter: Option, - pub roles: Vec, - pub emojis: Vec, - pub features: Vec, - pub application_id: Option, - pub system_channel_id: Option, - pub system_channel_flags: Option, - pub rules_channel_id: Option, - pub rules_channel: Option, - pub max_presences: Option, - pub max_members: Option, - pub vanity_url_code: Option, - pub description: Option, - pub banner: Option, - pub premium_tier: Option, - pub premium_subscription_count: Option, - pub preferred_locale: Option, - pub public_updates_channel_id: Option, - pub public_updates_channel: Option, - pub max_video_channel_users: Option, - pub max_stage_video_channel_users: Option, - pub approximate_member_count: Option, - pub approximate_presence_count: Option, - pub member_count: Option, - pub presence_count: Option, - pub welcome_screen: Option, - pub nsfw_level: u8, - pub nsfw: bool, - pub stickers: Option>, - pub premium_progress_bar_enabled: Option, - pub joined_at: String, - pub afk_channel: Option, - pub bans: Option>, - pub primary_category_id: Option, - pub large: Option, - pub channels: Option>, - pub template_id: Option, - pub template: Option, - pub invites: Option>, - pub voice_states: Option>, - pub webhooks: Option>, - pub mfa_level: Option, - pub region: Option, - pub unavailable: bool, - pub parent: Option, -} - -/// See https://docs.spacebar.chat/routes/#get-/guilds/-guild_id-/bans/-user- -#[derive(Serialize, Deserialize, Debug, Default, Clone)] -pub struct GuildBan { - pub id: String, - pub user_id: String, - pub guild_id: String, - pub executor_id: String, - pub reason: Option, -} - -/// See https://discord.com/developers/docs/topics/gateway-events#guild-create-guild-create-extra-fields -/// This is like [Guild], expect it has extra fields -#[derive(Serialize, Deserialize, Debug, Default, Clone)] -pub struct GuildCreateGuild { - pub id: String, - pub name: String, - pub icon: Option, - pub icon_hash: Option, - pub splash: Option, - pub discovery_splash: Option, - pub owner: Option, - pub owner_id: String, - pub permissions: Option, - pub afk_channel_id: Option, - pub afk_timeout: u8, - pub widget_enabled: Option, - pub widget_channel_id: Option, - pub verification_level: u8, - pub default_message_notifications: u8, - pub explicit_content_filter: u8, - pub roles: Vec, - pub emojis: Vec, - pub features: Vec, - pub mfa_level: u8, - pub application_id: Option, - pub system_channel_id: Option, - pub system_channel_flags: u8, - pub rules_channel_id: Option, - pub max_presences: Option, - pub max_members: Option, - pub vanity_url_code: Option, - pub description: Option, - pub banner: Option, - pub premium_tier: u8, - pub premium_subscription_count: Option, - pub preferred_locale: String, - pub public_updates_channel_id: Option, - pub max_video_channel_users: Option, - pub max_stage_video_channel_users: Option, - pub approximate_member_count: Option, - pub approximate_presence_count: Option, - pub welcome_screen: Option, - pub nsfw_level: u8, - pub stickers: Option>, - pub premium_progress_bar_enabled: bool, - // ------ Extra Fields ------ - pub joined_at: DateTime, - pub large: bool, - pub unavailable: Option, - pub member_count: u64, - // to:do implement voice states - //pub voice_states: Vec, - pub members: Vec, - pub channels: Vec, - pub threads: Vec, - pub presences: Vec, - // to:do add stage instances - //pub stage_instances: Vec, - // to:do add guild schedules events - //pub guild_scheduled_events: Vec -} - -impl GuildCreateGuild { - /// Converts self to a [Guild], discarding the extra fields - pub fn to_guild(&self) -> Guild { - let as_value = serde_json::to_value(&self).unwrap(); - return from_value(as_value).unwrap(); - } -} - -#[derive(Serialize, Deserialize, Debug, Default, Clone)] -pub struct WelcomeScreenObject { - pub description: Option, - pub welcome_channels: Vec, -} - -#[derive(Serialize, Deserialize, Debug, Default, Clone)] -pub struct WelcomeScreenChannel { - pub channel_id: String, - pub description: String, - pub emoji_id: Option, - pub emoji_name: Option, -} - -#[derive(Serialize, Deserialize, Debug, Default, Clone)] -/// See https://discord.com/developers/docs/topics/permissions#role-object -pub struct RoleObject { - pub id: String, - pub name: String, - pub color: f64, - pub hoist: bool, - pub icon: Option, - pub unicode_emoji: Option, - pub position: u16, - pub permissions: String, - pub managed: bool, - pub mentionable: bool, - // to:do add role tags https://discord.com/developers/docs/topics/permissions#role-object-role-tags-structure - //pub tags: Option -} - -#[derive(Serialize, Deserialize, Debug, Default, Clone, Eq, PartialEq)] -pub struct UserObject { - pub id: String, - username: String, - discriminator: String, - avatar: Option, - bot: bool, - system: Option, - mfa_enabled: Option, - accent_color: Option, - locale: Option, - verified: Option, - email: Option, - flags: String, - premium_since: Option, - premium_type: i8, - pronouns: Option, - public_flags: Option, - banner: Option, - bio: String, - theme_colors: Option>, - phone: Option, - nsfw_allowed: bool, - premium: bool, - purchased_flags: i32, - premium_usage_flags: i32, - disabled: Option, -} - -#[derive(Debug)] -pub struct User { - pub belongs_to: Rc>, - pub token: String, - pub limits: Limits, - pub settings: UserSettings, - pub object: Option, -} - -impl User { - pub fn token(&self) -> String { - self.token.clone() - } - - pub fn set_token(&mut self, token: String) { - self.token = token; - } - - pub fn new( - belongs_to: Rc>, - token: String, - limits: Limits, - settings: UserSettings, - object: Option, - ) -> User { - User { - belongs_to, - token, - limits, - settings, - object, - } - } -} - -#[derive(Debug, Serialize, Deserialize, Default)] -pub struct Message { - id: String, - pub channel_id: String, - author: UserObject, - content: String, - timestamp: String, - edited_timestamp: Option, - tts: bool, - mention_everyone: bool, - mentions: Vec, - mention_roles: Vec, - mention_channels: Option>, - pub attachments: Vec, - embeds: Vec, - reactions: Option>, - nonce: Option, - pinned: bool, - webhook_id: Option, - #[serde(rename = "type")] - message_type: i32, - activity: Option, - application: Option, - application_id: Option, - message_reference: Option, - flags: Option, - referenced_message: Option>, - interaction: Option, - thread: Option, - components: Option>, - sticker_items: Option>, - stickers: Option>, - position: Option, - role_subscription_data: Option, -} - -#[derive(Debug, Serialize, Deserialize, Default)] -pub struct MessageCreate { - #[serde(flatten)] - message: Message, - guild_id: Option, - member: Option, - mentions: Vec<(UserObject, GuildMember)>, // Not sure if this is correct: https://discord.com/developers/docs/topics/gateway-events#message-create -} - -impl WebSocketEvent for MessageCreate {} - -#[derive(Debug, Serialize, Deserialize, Default)] -struct PartialMessage { - id: Option, - channel_id: Option, - author: Option, - content: Option, - timestamp: Option, - edited_timestamp: Option, - tts: Option, - mention_everyone: Option, - mentions: Option>, - mention_roles: Option>, - mention_channels: Option>, - attachments: Option>, - embeds: Option>, - reactions: Option>, - nonce: Option, - pinned: Option, - webhook_id: Option, - #[serde(rename = "type")] - message_type: Option, - activity: Option, - application: Option, - application_id: Option, - message_reference: Option, - flags: Option, - referenced_message: Option>, - interaction: Option, - thread: Option, - components: Option>, - sticker_items: Option>, - stickers: Option>, - position: Option, - role_subscription_data: Option, - guild_id: Option, - member: Option, -} - -#[derive(Debug, Serialize, Deserialize, Default)] -pub struct MessageUpdate { - #[serde(flatten)] - message: PartialMessage, - guild_id: Option, - member: Option, - mentions: Vec<(UserObject, GuildMember)>, // Not sure if this is correct: https://discord.com/developers/docs/topics/gateway-events#message-create -} - -impl WebSocketEvent for MessageUpdate {} - -#[derive(Debug, Serialize, Deserialize, Default)] -pub struct MessageDelete { - id: String, - channel_id: String, - guild_id: Option, -} - -impl WebSocketEvent for MessageDelete {} - -#[derive(Debug, Serialize, Deserialize, Default)] -pub struct MessageDeleteBulk { - ids: Vec, - channel_id: String, - guild_id: Option, -} - -impl WebSocketEvent for MessageDeleteBulk {} - -#[derive(Debug, Serialize, Deserialize, Default)] -pub struct MessageReactionAdd { - user_id: String, - channel_id: String, - message_id: String, - guild_id: Option, - member: Option, - emoji: Emoji, -} - -impl WebSocketEvent for MessageReactionAdd {} - -#[derive(Debug, Serialize, Deserialize, Default)] -pub struct MessageReactionRemove { - user_id: String, - channel_id: String, - message_id: String, - guild_id: Option, - emoji: Emoji, -} - -impl WebSocketEvent for MessageReactionRemove {} - -#[derive(Debug, Serialize, Deserialize, Default)] -pub struct MessageReactionRemoveAll { - channel_id: String, - message_id: String, - guild_id: Option, -} - -impl WebSocketEvent for MessageReactionRemoveAll {} - -#[derive(Debug, Serialize, Deserialize, Default)] -pub struct MessageReactionRemoveEmoji { - channel_id: String, - message_id: String, - guild_id: Option, - emoji: Emoji, -} - -impl WebSocketEvent for MessageReactionRemoveEmoji {} - -#[derive(Debug, Serialize, Deserialize)] -struct ChannelMention { - id: String, - guild_id: String, - #[serde(rename = "type")] - channel_type: i32, - name: String, -} - -#[derive(Debug, Serialize, Deserialize)] -/** -Represents an Embed. [See the Discord Documentation](https://discord.com/developers/docs/resources/channel#embed-object). - */ -pub struct Embed { - title: Option, - #[serde(rename = "type")] - embed_type: Option, - description: Option, - url: Option, - timestamp: Option, - color: Option, - footer: Option, - image: Option, - thumbnail: Option, - video: Option, - provider: Option, - author: Option, - fields: Option>, -} - -#[derive(Debug, Serialize, Deserialize)] -struct EmbedFooter { - text: String, - icon_url: Option, - proxy_icon_url: Option, -} - -#[derive(Debug, Serialize, Deserialize)] -struct EmbedImage { - url: String, - proxy_url: String, - height: Option, - width: Option, -} - -#[derive(Debug, Serialize, Deserialize)] -struct EmbedThumbnail { - url: String, - proxy_url: Option, - height: Option, - width: Option, -} - -#[derive(Debug, Serialize, Deserialize)] -struct EmbedVideo { - url: Option, - proxy_url: Option, - height: Option, - width: Option, -} - -#[derive(Debug, Serialize, Deserialize)] -struct EmbedProvider { - name: Option, - url: Option, -} - -#[derive(Debug, Serialize, Deserialize)] -struct EmbedAuthor { - name: String, - url: Option, - icon_url: Option, - proxy_icon_url: Option, -} - -#[derive(Debug, Serialize, Deserialize)] - -struct EmbedField { - name: String, - value: String, - inline: Option, -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct Reaction { - pub count: i32, - pub me: bool, - pub emoji: Emoji, -} - -#[derive(Debug, Deserialize, Serialize, Default, Clone)] -pub struct Emoji { - pub id: Option, - pub name: Option, - pub roles: Option>, - pub user: Option, - pub require_colons: Option, - pub managed: Option, - pub animated: Option, - pub available: Option, -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct MessageActivity { - #[serde(rename = "type")] - pub activity_type: i64, - pub party_id: Option, -} - -#[derive(Debug, Deserialize, Serialize, Clone)] -pub struct Application { - pub id: String, - pub name: String, - pub icon: Option, - pub description: String, - pub rpc_origins: Option>, - pub bot_public: bool, - pub bot_require_code_grant: bool, - pub terms_of_service_url: Option, - pub privacy_policy_url: Option, - pub owner: Option, - pub summary: String, - pub verify_key: String, - pub team: Option, - pub guild_id: Option, - pub primary_sku_id: Option, - pub slug: Option, - pub cover_image: Option, - pub flags: Option, - pub tags: Option>, - pub install_params: Option, - pub custom_install_url: Option, - pub role_connections_verification_url: Option, -} - -#[derive(Debug, Deserialize, Serialize, Clone)] -pub struct Team { - pub icon: Option, - pub id: u64, - pub members: Vec, - pub name: String, - pub owner_user_id: u64, -} - -#[derive(Debug, Deserialize, Serialize, Clone)] -pub struct TeamMember { - pub membership_state: u8, - pub permissions: Vec, - pub team_id: u64, - pub user: UserObject, -} - -#[derive(Debug, Deserialize, Serialize)] -#[serde(rename_all = "SCREAMING_SNAKE_CASE")] -pub enum MembershipState { - Invited = 1, - Accepted = 2, -} - -#[derive(Debug, Serialize, Deserialize, Clone)] -pub struct InstallParams { - pub scopes: Vec, - pub permissions: String, -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct MessageReference { - pub message_id: String, - pub channel_id: String, - pub guild_id: Option, - pub fail_if_not_exists: Option, -} - -#[derive(Debug, Deserialize, Serialize)] -pub struct MessageInteraction { - pub id: u64, - #[serde(rename = "type")] - pub interaction_type: u8, - pub name: String, - pub user: UserObject, - pub member: Option, -} - -#[derive(Debug, Deserialize, Serialize, Clone, Eq, PartialEq)] -pub struct GuildMember { - pub user: Option, - pub nick: Option, - pub avatar: Option, - pub roles: Vec, - pub joined_at: String, - pub premium_since: Option, - pub deaf: bool, - pub mute: bool, - pub flags: i32, - pub pending: Option, - pub permissions: Option, - pub communication_disabled_until: Option, -} - -#[derive(Default, Debug, Serialize, Deserialize, Clone, Eq, PartialEq)] -pub struct Channel { - pub id: String, - #[serde(rename = "type")] - pub channel_type: i32, - pub guild_id: Option, - pub position: Option, - pub permission_overwrites: Option>, - pub name: Option, - pub topic: Option, - pub nsfw: Option, - pub last_message_id: Option, - pub bitrate: Option, - pub user_limit: Option, - pub rate_limit_per_user: Option, - pub recipients: Option>, - pub icon: Option, - pub owner_id: Option, - pub application_id: Option, - pub parent_id: Option, - pub last_pin_timestamp: Option, - pub rtc_region: Option, - pub video_quality_mode: Option, - pub message_count: Option, - pub member_count: Option, - pub thread_metadata: Option, - pub member: Option, - pub default_auto_archive_duration: Option, - pub permissions: Option, - pub flags: Option, - pub total_message_sent: Option, - pub available_tags: Option>, - pub applied_tags: Option>, - pub default_reaction_emoji: Option, - pub default_thread_rate_limit_per_user: Option, - pub default_sort_order: Option, - pub default_forum_layout: Option, -} - -#[derive(Debug, Deserialize, Serialize, Clone, Eq, PartialEq)] -pub struct Tag { - pub id: u64, - pub name: String, - pub moderated: bool, - pub emoji_id: Option, - pub emoji_name: Option, -} - -#[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq)] -pub struct PermissionOverwrite { - pub id: String, - #[serde(rename = "type")] - pub overwrite_type: u8, - pub allow: String, - pub deny: String, -} - -#[derive(Debug, Deserialize, Serialize, Clone, Eq, PartialEq)] -pub struct ThreadMetadata { - pub archived: bool, - pub auto_archive_duration: i32, - pub archive_timestamp: String, - pub locked: bool, - pub invitable: Option, - pub create_timestamp: Option, -} - -#[derive(Default, Debug, Deserialize, Serialize, Clone, Eq, PartialEq)] -pub struct ThreadMember { - pub id: Option, - pub user_id: Option, - pub join_timestamp: Option, - pub flags: Option, - pub member: Option, -} - -#[derive(Default, Debug, Deserialize, Serialize, Clone)] -/// See https://discord.com/developers/docs/resources/guild#integration-object-integration-structure -pub struct Integration { - pub id: String, - pub name: String, - #[serde(rename = "type")] - pub integration_type: String, - pub enabled: bool, - pub syncing: Option, - pub role_id: Option, - pub enabled_emoticons: Option, - pub expire_behaviour: Option, - pub expire_grace_period: Option, - pub user: Option, - pub account: IntegrationAccount, - pub synced_at: Option>, - pub subscriber_count: Option, - pub revoked: Option, - pub application: Option, - pub scopes: Option>, -} - -#[derive(Default, Debug, Deserialize, Serialize, Clone)] -/// See https://discord.com/developers/docs/resources/guild#integration-account-object-integration-account-structure -pub struct IntegrationAccount { - pub id: String, - pub name: String, -} - -#[derive(Default, Debug, Deserialize, Serialize, Clone)] -/// See https://discord.com/developers/docs/resources/stage-instance#stage-instance-object -pub struct StageInstance { - pub id: String, - pub guild_id: String, - pub channel_id: String, - pub topic: String, - pub privacy_level: u8, - pub discoverable_disabled: bool, - pub guild_scheduled_event_id: Option, -} - -#[derive(Debug, Deserialize, Serialize, Clone, Eq, PartialEq)] -pub struct DefaultReaction { - pub emoji_id: Option, - pub emoji_name: Option, -} - -#[derive(Debug, Serialize, Deserialize)] -pub enum Component { - ActionRow = 1, - Button = 2, - StringSelect = 3, - TextInput = 4, - UserSelect = 5, - RoleSelect = 6, - MentionableSelect = 7, - ChannelSelect = 8, -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct StickerItem { - pub id: u64, - pub name: String, - pub format_type: u8, -} - -#[derive(Debug, Serialize, Deserialize, Clone)] -pub struct Sticker { - pub id: u64, - pub pack_id: Option, - pub name: String, - pub description: Option, - pub tags: String, - pub asset: Option, - #[serde(rename = "type")] - pub sticker_type: u8, - pub format_type: u8, - pub available: Option, - pub guild_id: Option, - pub user: Option, - pub sort_value: Option, -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct RoleSubscriptionData { - pub role_subscription_listing_id: u64, - pub tier_name: String, - pub total_months_subscribed: u32, - pub is_renewal: bool, -} - -#[derive(Debug, Deserialize, Serialize, Default)] -pub struct TypingStartEvent { - pub channel_id: String, - pub guild_id: Option, - pub user_id: String, - pub timestamp: i64, - pub member: Option, -} - -impl WebSocketEvent for TypingStartEvent {} - -#[derive(Debug, Deserialize, Serialize, Default)] -pub struct GatewayIdentifyPayload { - pub token: String, - pub properties: GatewayIdentifyConnectionProps, - pub compress: Option, - pub large_threshold: Option, //default: 50 - pub shard: Option>, - pub presence: Option, - pub intents: i32, -} - -impl WebSocketEvent for GatewayIdentifyPayload {} - -#[derive(Debug, Deserialize, Serialize, Default)] -pub struct GatewayIdentifyConnectionProps { - pub os: String, - pub browser: String, - pub device: String, -} - -#[derive(Debug, Deserialize, Serialize, Default, Clone)] -/// See https://discord.com/developers/docs/topics/gateway-events#presence-update-presence-update-event-fields -pub struct PresenceUpdate { - pub user: UserObject, - pub guild_id: String, - pub status: String, - pub activities: Vec, - pub client_status: ClientStatusObject, -} - -#[derive(Debug, Deserialize, Serialize, Default, Clone)] -/// See https://discord.com/developers/docs/topics/gateway-events#client-status-object -pub struct ClientStatusObject { - pub desktop: Option, - pub mobile: Option, - pub web: Option, -} - -impl WebSocketEvent for PresenceUpdate {} - -#[derive(Debug, Deserialize, Serialize, Clone)] -pub struct Activity { - name: String, - #[serde(rename = "type")] - activity_type: i32, - url: Option, - created_at: i64, - timestamps: Option, - application_id: Option, - details: Option, - state: Option, - emoji: Option, - party: Option, - assets: Option, - secrets: Option, - instance: Option, - flags: Option, - buttons: Option>, -} - -#[derive(Debug, Deserialize, Serialize, Clone)] -struct ActivityTimestamps { - start: Option, - end: Option, -} - -#[derive(Debug, Deserialize, Serialize, Clone)] -struct ActivityParty { - id: Option, - size: Option>, -} - -#[derive(Debug, Deserialize, Serialize, Clone)] -struct ActivityAssets { - large_image: Option, - large_text: Option, - small_image: Option, - small_text: Option, -} - -#[derive(Debug, Deserialize, Serialize, Clone)] -struct ActivitySecrets { - join: Option, - spectate: Option, - #[serde(rename = "match")] - match_string: Option, -} - -#[derive(Debug, Deserialize, Serialize, Clone)] -struct ActivityButton { - label: String, - url: String, -} - -#[derive(Debug, Deserialize, Serialize, Default)] -pub struct GatewayResume { - pub token: String, - pub session_id: String, - pub seq: String, -} - -impl WebSocketEvent for GatewayResume {} - -#[derive(Debug, Deserialize, Serialize, Default)] -pub struct GatewayReady { - pub v: u8, - pub user: UserObject, - pub guilds: Vec, - pub session_id: String, - pub resume_gateway_url: Option, - pub shard: Option<(u64, u64)>, -} - -impl WebSocketEvent for GatewayReady {} - -#[derive(Debug, Deserialize, Serialize, Default)] -/// See https://discord.com/developers/docs/topics/gateway-events#request-guild-members-request-guild-members-structure -pub struct GatewayRequestGuildMembers { - pub guild_id: String, - pub query: Option, - pub limit: u64, - pub presence: Option, - pub user_ids: Option, - pub nonce: Option, -} - -impl WebSocketEvent for GatewayRequestGuildMembers {} - -#[derive(Debug, Deserialize, Serialize, Default)] -/// See https://discord.com/developers/docs/topics/gateway-events#update-voice-state-gateway-voice-state-update-structure -pub struct GatewayVoiceStateUpdate { - pub guild_id: String, - pub channel_id: Option, - pub self_mute: bool, - pub self_deaf: bool, -} - -impl WebSocketEvent for GatewayVoiceStateUpdate {} - -#[derive(Debug, Default, Deserialize, Serialize)] -pub struct GatewayHello { - pub op: i32, - pub d: HelloData, -} - -impl WebSocketEvent for GatewayHello {} - -#[derive(Debug, Default, Deserialize, Serialize)] -pub struct HelloData { - pub heartbeat_interval: u128, -} - -impl WebSocketEvent for HelloData {} - -#[derive(Debug, Default, Deserialize, Serialize)] -pub struct GatewayHeartbeat { - pub op: u8, - pub d: Option, -} - -impl WebSocketEvent for GatewayHeartbeat {} - -#[derive(Debug, Default, Deserialize, Serialize)] -pub struct GatewayHeartbeatAck { - pub op: i32, -} - -impl WebSocketEvent for GatewayHeartbeatAck {} - -#[derive(Debug, Default, Deserialize, Serialize)] -/// See https://discord.com/developers/docs/topics/gateway-events#channel-pins-update -pub struct ChannelPinsUpdate { - pub guild_id: Option, - pub channel_id: String, - pub last_pin_timestamp: Option>, -} - -impl WebSocketEvent for ChannelPinsUpdate {} - -#[derive(Debug, Default, Deserialize, Serialize)] -/// See https://discord.com/developers/docs/topics/gateway-events#guild-ban-add-guild-ban-add-event-fields -pub struct GuildBanAdd { - pub guild_id: String, - pub user: UserObject, -} - -impl WebSocketEvent for GuildBanAdd {} - -#[derive(Debug, Default, Deserialize, Serialize)] -/// See https://discord.com/developers/docs/topics/gateway-events#guild-ban-remove -pub struct GuildBanRemove { - pub guild_id: String, - pub user: UserObject, -} - -impl WebSocketEvent for GuildBanRemove {} - -#[derive(Debug, Default, Deserialize, Serialize)] -/// See https://discord.com/developers/docs/topics/gateway-events#user-update -/// Not directly serialized, as the inner payload is the user object -pub struct UserUpdate { - pub user: UserObject, -} - -impl WebSocketEvent for UserUpdate {} - -#[derive(Debug, Default, Deserialize, Serialize)] -/// See https://discord.com/developers/docs/topics/gateway-events#channel-create -/// Not directly serialized, as the inner payload is a channel object -pub struct ChannelCreate { - pub channel: Channel, -} - -impl WebSocketEvent for ChannelCreate {} - -#[derive(Debug, Default, Deserialize, Serialize)] -/// See https://discord.com/developers/docs/topics/gateway-events#channel-update -/// Not directly serialized, as the inner payload is a channel object -pub struct ChannelUpdate { - pub channel: Channel, -} - -impl WebSocketEvent for ChannelUpdate {} - -#[derive(Debug, Default, Deserialize, Serialize)] -/// See https://discord.com/developers/docs/topics/gateway-events#channel-delete -/// Not directly serialized, as the inner payload is a channel object -pub struct ChannelDelete { - pub channel: Channel, -} - -impl WebSocketEvent for ChannelDelete {} - -#[derive(Debug, Default, Deserialize, Serialize)] -/// See https://discord.com/developers/docs/topics/gateway-events#thread-create -/// Not directly serialized, as the inner payload is a channel object -pub struct ThreadCreate { - pub thread: Channel, -} - -impl WebSocketEvent for ThreadCreate {} - -#[derive(Debug, Default, Deserialize, Serialize)] -/// See https://discord.com/developers/docs/topics/gateway-events#thread-update -/// Not directly serialized, as the inner payload is a channel object -pub struct ThreadUpdate { - pub thread: Channel, -} - -impl WebSocketEvent for ThreadUpdate {} - -#[derive(Debug, Default, Deserialize, Serialize)] -/// See https://discord.com/developers/docs/topics/gateway-events#thread-delete -/// Not directly serialized, as the inner payload is a channel object -pub struct ThreadDelete { - pub thread: Channel, -} - -impl WebSocketEvent for ThreadDelete {} - -#[derive(Debug, Default, Deserialize, Serialize)] -/// See https://discord.com/developers/docs/topics/gateway-events#thread-list-sync -pub struct ThreadListSync { - pub guild_id: String, - pub channel_ids: Option>, - pub threads: Vec, - pub members: Vec, -} - -impl WebSocketEvent for ThreadListSync {} - -#[derive(Debug, Default, Deserialize, Serialize)] -/// See https://discord.com/developers/docs/topics/gateway-events#thread-member-update -/// The inner payload is a thread member object with an extra field. -/// The extra field is a bit painful, because we can't just serialize a thread member object -pub struct ThreadMemberUpdate { - pub id: Option, - pub user_id: Option, - pub join_timestamp: Option, - pub flags: Option, - pub member: Option, - pub guild_id: String, -} - -impl ThreadMemberUpdate { - /// Convert self to a thread member, losing the added guild_id field - pub fn to_thread_member(&self) -> ThreadMember { - ThreadMember { - id: self.id, - user_id: self.user_id, - join_timestamp: self.join_timestamp.clone(), - flags: self.flags, - member: self.member.clone(), - } - } -} - -impl WebSocketEvent for ThreadMemberUpdate {} - -#[derive(Debug, Default, Deserialize, Serialize)] -/// See https://discord.com/developers/docs/topics/gateway-events#thread-members-update -pub struct ThreadMembersUpdate { - pub id: String, - pub guild_id: String, - /// Capped at 50 - pub member_count: u8, - pub added_members: Option>, - pub removed_members: Option>, -} - -impl WebSocketEvent for ThreadMembersUpdate {} - -#[derive(Debug, Deserialize, Serialize, Default)] -/// See https://discord.com/developers/docs/topics/gateway-events#guild-create -/// This one is particularly painful, it can be a Guild object with extra field or an unavailbile guild object -pub struct GuildCreate { - pub d: GuildCreateDataOption, -} - -#[derive(Debug, Deserialize, Serialize)] -pub enum GuildCreateDataOption { - UnavailableGuild(UnavailableGuild), - Guild(Guild), -} - -impl Default for GuildCreateDataOption { - fn default() -> Self { - GuildCreateDataOption::UnavailableGuild(UnavailableGuild::default()) - } -} -impl WebSocketEvent for GuildCreate {} - -#[derive(Debug, Default, Deserialize, Serialize)] -pub struct GatewayPayload { - pub op: u8, - pub d: Option, - pub s: Option, - pub t: Option, -} - -impl WebSocketEvent for GatewayPayload {} - -#[derive(Debug, Serialize, Deserialize)] -pub struct DiscordFileAttachment { - pub id: i16, - pub filename: String, - description: Option, - content_type: Option, - size: i64, - url: String, - proxy_url: String, - height: Option, - width: Option, - ephemeral: Option, - duration_secs: Option, - waveform: Option, - #[serde(skip_serializing)] - content: Vec, -} - -#[derive(Debug, Serialize, Deserialize, Clone)] - -pub struct PartialDiscordFileAttachment { - pub id: Option, - pub filename: String, - pub description: Option, - pub content_type: Option, - pub size: Option, - pub url: Option, - pub proxy_url: Option, - pub height: Option, - pub width: Option, - pub ephemeral: Option, - pub duration_secs: Option, - pub waveform: Option, - #[serde(skip_serializing)] - pub content: Vec, -} - -impl PartialDiscordFileAttachment { - /** - Moves `self.content` out of `self` and returns it. - # Returns - Vec - */ - pub fn move_content(self) -> (Vec, PartialDiscordFileAttachment) { - let content = self.content; - let updated_struct = PartialDiscordFileAttachment { - id: self.id, - filename: self.filename, - description: self.description, - content_type: self.content_type, - size: self.size, - url: self.url, - proxy_url: self.proxy_url, - height: self.height, - width: self.width, - ephemeral: self.ephemeral, - duration_secs: self.duration_secs, - waveform: self.waveform, - content: Vec::new(), - }; - (content, updated_struct) - } - - pub fn move_filename(self) -> (String, PartialDiscordFileAttachment) { - let filename = self.filename; - let updated_struct = PartialDiscordFileAttachment { - id: self.id, - filename: String::new(), - description: self.description, - content_type: self.content_type, - size: self.size, - url: self.url, - proxy_url: self.proxy_url, - height: self.height, - width: self.width, - - ephemeral: self.ephemeral, - duration_secs: self.duration_secs, - waveform: self.waveform, - content: self.content, - }; - (filename, updated_struct) - } - - pub fn move_content_type(self) -> (Option, PartialDiscordFileAttachment) { - let content_type = self.content_type; - let updated_struct = PartialDiscordFileAttachment { - id: self.id, - filename: self.filename, - description: self.description, - content_type: None, - size: self.size, - url: self.url, - proxy_url: self.proxy_url, - height: self.height, - width: self.width, - ephemeral: self.ephemeral, - duration_secs: self.duration_secs, - waveform: self.waveform, - content: self.content, - }; - (content_type, updated_struct) - } - - pub fn set_id(&mut self, id: i16) { - self.id = Some(id); - } -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct AllowedMention { - parse: Vec, - roles: Vec, - users: Vec, - replied_user: bool, -} - -#[derive(Debug, Serialize, Deserialize)] -#[serde(rename_all = "snake_case")] -pub enum AllowedMentionType { - Roles, - Users, - Everyone, -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct Token { - pub token: String, -} - -/// See https://docs.spacebar.chat/routes/#cmp--schemas-template -#[derive(Serialize, Deserialize, Debug, Default, Clone)] -pub struct GuildTemplate { - pub code: String, - pub name: String, - pub description: Option, - pub usage_count: Option, - pub creator_id: String, - pub creator: UserObject, - pub created_at: DateTime, - pub updated_at: DateTime, - pub source_guild_id: String, - pub source_guild: Vec, // Unsure how a {recursive: Guild} looks like, might be a Vec? - pub serialized_source_guild: Vec, - id: String, -} - -/// See https://docs.spacebar.chat/routes/#cmp--schemas-invite -#[derive(Serialize, Deserialize, Debug, Default, Clone)] -pub struct GuildInvite { - pub code: String, - pub temporary: Option, - pub uses: Option, - pub max_uses: Option, - pub max_age: Option, - pub created_at: DateTime, - pub expires_at: Option>, - pub guild_id: String, - pub guild: Option, - pub channel_id: String, - pub channel: Option, - pub inviter_id: Option, - pub inviter: Option, - pub target_user_id: Option, - pub target_user: Option, - pub target_user_type: Option, - pub vanity_url: Option, -} - -/// See https://docs.spacebar.chat/routes/#cmp--schemas-voicestate -#[derive(Serialize, Deserialize, Debug, Default, Clone)] -pub struct VoiceState { - pub guild_id: String, - pub guild: Option, - pub channel_id: String, - pub channel: Option, - pub user_id: String, - pub user: Option, - pub member: Option, - pub session_id: String, - pub token: String, - pub deaf: bool, - pub mute: bool, - pub self_deaf: bool, - pub self_mute: bool, - pub self_stream: Option, - pub self_video: bool, - pub suppress: bool, - pub request_to_speak_timestamp: Option>, - pub id: String, -} - -/// See https://docs.spacebar.chat/routes/#cmp--schemas-webhook -#[derive(Serialize, Deserialize, Debug, Default, Clone)] -pub struct Webhook { - #[serde(rename = "type")] - pub webhook_type: i32, - pub name: String, - pub avatar: String, - pub token: String, - pub guild_id: String, - #[serde(skip_serializing_if = "Option::is_none")] - pub guild: Option, - pub channel_id: String, - #[serde(skip_serializing_if = "Option::is_none")] - pub channel: Option, - pub application_id: String, - #[serde(skip_serializing_if = "Option::is_none")] - pub application: Option, - pub user_id: String, - #[serde(skip_serializing_if = "Option::is_none")] - pub user: Option, - pub source_guild_id: String, - #[serde(skip_serializing_if = "Option::is_none")] - pub source_guild: Option, - pub id: String, -} - -#[derive(Serialize, Deserialize, Debug, Default, Clone)] -pub struct GuildCreateResponse { - pub id: String, -} diff --git a/src/api/users/users.rs b/src/api/users/users.rs index cf052aa..4113c6b 100644 --- a/src/api/users/users.rs +++ b/src/api/users/users.rs @@ -2,17 +2,14 @@ use reqwest::Client; use serde_json::{from_str, to_string}; use crate::{ - api::{ - limits::Limits, - types::{User, UserObject}, - UserModifySchema, UserSettings, - }, + api::limits::Limits, errors::InstanceServerError, - instance::Instance, + instance::{Instance, UserMeta}, limit::LimitedRequester, + types::{User, UserModifySchema, UserSettings}, }; -impl User { +impl UserMeta { /** Get a user object by id, or get the current user. # Arguments @@ -28,7 +25,7 @@ impl User { url_api: &String, id: Option<&String>, instance_limits: &mut Limits, - ) -> Result { + ) -> Result { let url: String; if id.is_none() { url = format!("{}/users/@me/", url_api); @@ -49,7 +46,7 @@ impl User { { Ok(result) => { let result_text = result.text().await.unwrap(); - Ok(serde_json::from_str::(&result_text).unwrap()) + Ok(serde_json::from_str::(&result_text).unwrap()) } Err(e) => Err(e), } @@ -91,7 +88,7 @@ impl User { pub async fn modify( &mut self, modify_schema: UserModifySchema, - ) -> Result { + ) -> Result { if modify_schema.new_password.is_some() || modify_schema.email.is_some() || modify_schema.code.is_some() @@ -118,7 +115,7 @@ impl User { Ok(response) => response, Err(e) => return Err(e), }; - let user_updated: UserObject = from_str(&result.text().await.unwrap()).unwrap(); + let user_updated: User = from_str(&result.text().await.unwrap()).unwrap(); let _ = std::mem::replace( &mut self.object.as_mut().unwrap(), &mut user_updated.clone(), @@ -171,8 +168,8 @@ impl Instance { &mut self, token: String, id: Option<&String>, - ) -> Result { - User::get( + ) -> Result { + UserMeta::get( &token, &self.urls.get_api().to_string(), id, diff --git a/src/gateway.rs b/src/gateway.rs index c79004f..a122d9b 100644 --- a/src/gateway.rs +++ b/src/gateway.rs @@ -1,12 +1,11 @@ -use std::sync::Arc; -use crate::api::types::*; -use crate::api::WebSocketEvent; use crate::errors::ObserverError; use crate::gateway::events::Events; +use crate::types; +use futures_util::stream::SplitSink; use futures_util::SinkExt; use futures_util::StreamExt; -use futures_util::stream::SplitSink; use native_tls::TlsConnector; +use std::sync::Arc; use tokio::net::TcpStream; use tokio::sync::mpsc; use tokio::sync::mpsc::error::TryRecvError; @@ -16,7 +15,7 @@ use tokio::task; use tokio::time; use tokio::time::Instant; use tokio_tungstenite::MaybeTlsStream; -use tokio_tungstenite::{WebSocketStream, Connector, connect_async_tls_with_config}; +use tokio_tungstenite::{connect_async_tls_with_config, Connector, WebSocketStream}; #[derive(Debug)] /** @@ -28,14 +27,25 @@ Using this handle you can also send Gateway Events directly. pub struct GatewayHandle { pub url: String, pub events: Arc>, - pub websocket_tx: Arc>, tokio_tungstenite::tungstenite::Message>>>, + pub websocket_tx: Arc< + Mutex< + SplitSink< + WebSocketStream>, + tokio_tungstenite::tungstenite::Message, + >, + >, + >, } impl GatewayHandle { /// Sends json to the gateway with an opcode async fn send_json_event(&self, op: u8, to_send: serde_json::Value) { - - let gateway_payload = GatewayPayload { op, d: Some(to_send), s: None, t: None }; + let gateway_payload = types::GatewayPayload { + op, + d: Some(to_send), + s: None, + t: None, + }; let payload_json = serde_json::to_string(&gateway_payload).unwrap(); @@ -45,8 +55,7 @@ impl GatewayHandle { } /// Sends an identify event to the gateway - pub async fn send_identify(&self, to_send: GatewayIdentifyPayload) { - + pub async fn send_identify(&self, to_send: types::GatewayIdentifyPayload) { let to_send_value = serde_json::to_value(&to_send).unwrap(); println!("GW: Sending Identify.."); @@ -55,8 +64,7 @@ impl GatewayHandle { } /// Sends a resume event to the gateway - pub async fn send_resume(&self, to_send: GatewayResume) { - + pub async fn send_resume(&self, to_send: types::GatewayResume) { let to_send_value = serde_json::to_value(&to_send).unwrap(); println!("GW: Sending Resume.."); @@ -65,8 +73,7 @@ impl GatewayHandle { } /// Sends an update presence event to the gateway - pub async fn send_update_presence(&self, to_send: PresenceUpdate) { - + pub async fn send_update_presence(&self, to_send: types::PresenceUpdate) { let to_send_value = serde_json::to_value(&to_send).unwrap(); println!("GW: Sending Presence Update.."); @@ -75,8 +82,7 @@ impl GatewayHandle { } /// Sends a Request Guild Members to the server - pub async fn send_request_guild_members(&self, to_send: GatewayRequestGuildMembers) { - + pub async fn send_request_guild_members(&self, to_send: types::GatewayRequestGuildMembers) { let to_send_value = serde_json::to_value(&to_send).unwrap(); println!("GW: Sending Request Guild Members.."); @@ -85,8 +91,7 @@ impl GatewayHandle { } /// Sends a Request Guild Members to the server - pub async fn send_update_voice_state(&self, to_send: GatewayVoiceStateUpdate) { - + pub async fn send_update_voice_state(&self, to_send: types::GatewayVoiceStateUpdate) { let to_send_value = serde_json::to_value(&to_send).unwrap(); println!("GW: Sending Voice State Update.."); @@ -98,14 +103,20 @@ impl GatewayHandle { pub struct Gateway { pub events: Arc>, heartbeat_handler: Option, - pub websocket_tx: Arc>, tokio_tungstenite::tungstenite::Message>>> + pub websocket_tx: Arc< + Mutex< + SplitSink< + WebSocketStream>, + tokio_tungstenite::tungstenite::Message, + >, + >, + >, } impl Gateway { pub async fn new( websocket_url: String, ) -> Result { - let (ws_stream, _) = match connect_async_tls_with_config( &websocket_url, None, @@ -123,24 +134,37 @@ impl Gateway { let shared_tx = Arc::new(Mutex::new(ws_tx)); - let mut gateway = Gateway { events: Arc::new(Mutex::new(Events::default())), heartbeat_handler: None, websocket_tx: shared_tx.clone() }; + let mut gateway = Gateway { + events: Arc::new(Mutex::new(Events::default())), + heartbeat_handler: None, + websocket_tx: shared_tx.clone(), + }; let shared_events = gateway.events.clone(); // Wait for the first hello and then spawn both tasks so we avoid nested tasks // This automatically spawns the heartbeat task, but from the main thread let msg = ws_rx.next().await.unwrap().unwrap(); - let gateway_payload: GatewayPayload = serde_json::from_str(msg.to_text().unwrap()).unwrap(); + let gateway_payload: types::GatewayPayload = + serde_json::from_str(msg.to_text().unwrap()).unwrap(); if gateway_payload.op != 10 { println!("Recieved non hello on gateway init, what is happening?"); - return Err(tokio_tungstenite::tungstenite::Error::Protocol(tokio_tungstenite::tungstenite::error::ProtocolError::InvalidOpcode(gateway_payload.op))) + return Err(tokio_tungstenite::tungstenite::Error::Protocol( + tokio_tungstenite::tungstenite::error::ProtocolError::InvalidOpcode( + gateway_payload.op, + ), + )); } println!("GW: Received Hello"); - let gateway_hello: HelloData = serde_json::from_value(gateway_payload.d.unwrap()).unwrap(); - gateway.heartbeat_handler = Some(HeartbeatHandler::new(gateway_hello.heartbeat_interval, shared_tx.clone())); + let gateway_hello: types::HelloData = + serde_json::from_value(gateway_payload.d.unwrap()).unwrap(); + gateway.heartbeat_handler = Some(HeartbeatHandler::new( + gateway_hello.heartbeat_interval, + shared_tx.clone(), + )); // Now we can continously check for messages in a different task, since we aren't going to receive another hello task::spawn(async move { @@ -162,14 +186,13 @@ impl Gateway { /// This handles a message as a websocket event and updates its events along with the events' observers pub async fn handle_event(&mut self, msg: tokio_tungstenite::tungstenite::Message) { - if msg.to_string() == String::new() { return; } let msg_string = msg.to_string(); - let gateway_payload: GatewayPayload = serde_json::from_str(&msg_string).unwrap(); + let gateway_payload: types::GatewayPayload = serde_json::from_str(&msg_string).unwrap(); // See https://discord.com/developers/docs/topics/opcodes-and-status-codes#gateway-gateway-opcodes match gateway_payload.op { @@ -183,7 +206,8 @@ impl Gateway { // See https://discord.com/developers/docs/topics/gateway-events#receive-events match gateway_payload_t.as_str() { "READY" => { - let _data: GatewayReady = serde_json::from_value(gateway_payload.d.unwrap()).unwrap(); + let _data: types::GatewayReady = + serde_json::from_value(gateway_payload.d.unwrap()).unwrap(); } "RESUMED" => {} "APPLICATION_COMMAND_PERMISSIONS_UPDATE" => {} @@ -192,65 +216,145 @@ impl Gateway { "AUTO_MODERATION_RULE_DELETE" => {} "AUTO_MODERATION_ACTION_EXECUTION" => {} "CHANNEL_CREATE" => { - let channel: Channel = serde_json::from_value(gateway_payload.d.unwrap()).unwrap(); - let new_data = ChannelCreate {channel}; - self.events.lock().await.channel.create.update_data(new_data).await; + let channel: types::Channel = + serde_json::from_value(gateway_payload.d.unwrap()).unwrap(); + let new_data = types::ChannelCreate { channel }; + self.events + .lock() + .await + .channel + .create + .update_data(new_data) + .await; } "CHANNEL_UPDATE" => { - let channel: Channel = serde_json::from_value(gateway_payload.d.unwrap()).unwrap(); - let new_data = ChannelUpdate {channel}; - self.events.lock().await.channel.update.update_data(new_data).await; + let channel: types::Channel = + serde_json::from_value(gateway_payload.d.unwrap()).unwrap(); + let new_data = types::ChannelUpdate { channel }; + self.events + .lock() + .await + .channel + .update + .update_data(new_data) + .await; } "CHANNEL_DELETE" => { - let channel: Channel = serde_json::from_value(gateway_payload.d.unwrap()).unwrap(); - let new_data = ChannelDelete {channel}; - self.events.lock().await.channel.delete.update_data(new_data).await; + let channel: types::Channel = + serde_json::from_value(gateway_payload.d.unwrap()).unwrap(); + let new_data = types::ChannelDelete { channel }; + self.events + .lock() + .await + .channel + .delete + .update_data(new_data) + .await; } "CHANNEL_PINS_UPDATE" => { - let new_data: ChannelPinsUpdate = serde_json::from_value(gateway_payload.d.unwrap()).unwrap(); - self.events.lock().await.channel.pins_update.update_data(new_data).await; + let new_data: types::ChannelPinsUpdate = + serde_json::from_value(gateway_payload.d.unwrap()).unwrap(); + self.events + .lock() + .await + .channel + .pins_update + .update_data(new_data) + .await; } "THREAD_CREATE" => { - let thread: Channel = serde_json::from_value(gateway_payload.d.unwrap()).unwrap(); - let new_data = ThreadCreate {thread}; - self.events.lock().await.thread.create.update_data(new_data).await; + let thread: types::Channel = + serde_json::from_value(gateway_payload.d.unwrap()).unwrap(); + let new_data = types::ThreadCreate { thread }; + self.events + .lock() + .await + .thread + .create + .update_data(new_data) + .await; } "THREAD_UPDATE" => { - let thread: Channel = serde_json::from_value(gateway_payload.d.unwrap()).unwrap(); - let new_data = ThreadUpdate {thread}; - self.events.lock().await.thread.update.update_data(new_data).await; + let thread: types::Channel = + serde_json::from_value(gateway_payload.d.unwrap()).unwrap(); + let new_data = types::ThreadUpdate { thread }; + self.events + .lock() + .await + .thread + .update + .update_data(new_data) + .await; } "THREAD_DELETE" => { - let thread: Channel = serde_json::from_value(gateway_payload.d.unwrap()).unwrap(); - let new_data = ThreadDelete {thread}; - self.events.lock().await.thread.delete.update_data(new_data).await; + let thread: types::Channel = + serde_json::from_value(gateway_payload.d.unwrap()).unwrap(); + let new_data = types::ThreadDelete { thread }; + self.events + .lock() + .await + .thread + .delete + .update_data(new_data) + .await; } "THREAD_LIST_SYNC" => { - let new_data: ThreadListSync = serde_json::from_value(gateway_payload.d.unwrap()).unwrap(); - self.events.lock().await.thread.list_sync.update_data(new_data).await; + let new_data: types::ThreadListSync = + serde_json::from_value(gateway_payload.d.unwrap()).unwrap(); + self.events + .lock() + .await + .thread + .list_sync + .update_data(new_data) + .await; } "THREAD_MEMBER_UPDATE" => { - let new_data: ThreadMemberUpdate = serde_json::from_value(gateway_payload.d.unwrap()).unwrap(); - self.events.lock().await.thread.member_update.update_data(new_data).await; + let new_data: types::ThreadMemberUpdate = + serde_json::from_value(gateway_payload.d.unwrap()).unwrap(); + self.events + .lock() + .await + .thread + .member_update + .update_data(new_data) + .await; } "THREAD_MEMBERS_UPDATE" => { - let new_data: ThreadMembersUpdate = serde_json::from_value(gateway_payload.d.unwrap()).unwrap(); - self.events.lock().await.thread.members_update.update_data(new_data).await; + let new_data: types::ThreadMembersUpdate = + serde_json::from_value(gateway_payload.d.unwrap()).unwrap(); + self.events + .lock() + .await + .thread + .members_update + .update_data(new_data) + .await; } "GUILD_CREATE" => { - let new_data: GuildCreate = serde_json::from_str(&msg_string).unwrap(); - self.events.lock().await.guild.create.update_data(new_data).await; + let new_data: types::GuildCreate = + serde_json::from_str(&msg_string).unwrap(); + self.events + .lock() + .await + .guild + .create + .update_data(new_data) + .await; } "GUILD_UPDATE" => {} "GUILD_DELETE" => { - let _new_data: UnavailableGuild = serde_json::from_value(gateway_payload.d.unwrap()).unwrap(); + let _new_data: types::UnavailableGuild = + serde_json::from_value(gateway_payload.d.unwrap()).unwrap(); } "GUILD_AUDIT_LOG_ENTRY_CREATE" => {} "GUILD_BAN_ADD" => { - let _new_data: GuildBanAdd = serde_json::from_value(gateway_payload.d.unwrap()).unwrap(); + let _new_data: types::GuildBanAdd = + serde_json::from_value(gateway_payload.d.unwrap()).unwrap(); } "GUILD_BAN_REMOVE" => { - let _new_data: GuildBanRemove = serde_json::from_value(gateway_payload.d.unwrap()).unwrap(); + let _new_data: types::GuildBanRemove = + serde_json::from_value(gateway_payload.d.unwrap()).unwrap(); } "GUILD_EMOJIS_UPDATE" => {} "GUILD_STICKERS_UPDATE" => {} @@ -274,40 +378,103 @@ impl Gateway { "INVITE_CREATE" => {} "INVITE_DELETE" => {} "MESSAGE_CREATE" => { - let new_data: MessageCreate = serde_json::from_value(gateway_payload.d.unwrap()).unwrap(); - self.events.lock().await.message.create.update_data(new_data).await; + let new_data: types::MessageCreate = + serde_json::from_value(gateway_payload.d.unwrap()).unwrap(); + self.events + .lock() + .await + .message + .create + .update_data(new_data) + .await; } "MESSAGE_UPDATE" => { - let new_data: MessageUpdate = serde_json::from_value(gateway_payload.d.unwrap()).unwrap(); - self.events.lock().await.message.update.update_data(new_data).await; + let new_data: types::MessageUpdate = + serde_json::from_value(gateway_payload.d.unwrap()).unwrap(); + self.events + .lock() + .await + .message + .update + .update_data(new_data) + .await; } "MESSAGE_DELETE" => { - let new_data: MessageDelete = serde_json::from_value(gateway_payload.d.unwrap()).unwrap(); - self.events.lock().await.message.delete.update_data(new_data).await; + let new_data: types::MessageDelete = + serde_json::from_value(gateway_payload.d.unwrap()).unwrap(); + self.events + .lock() + .await + .message + .delete + .update_data(new_data) + .await; } "MESSAGE_DELETE_BULK" => { - let new_data: MessageDeleteBulk = serde_json::from_value(gateway_payload.d.unwrap()).unwrap(); - self.events.lock().await.message.delete_bulk.update_data(new_data).await; + let new_data: types::MessageDeleteBulk = + serde_json::from_value(gateway_payload.d.unwrap()).unwrap(); + self.events + .lock() + .await + .message + .delete_bulk + .update_data(new_data) + .await; } "MESSAGE_REACTION_ADD" => { - let new_data: MessageReactionAdd = serde_json::from_value(gateway_payload.d.unwrap()).unwrap(); - self.events.lock().await.message.reaction_add.update_data(new_data).await; + let new_data: types::MessageReactionAdd = + serde_json::from_value(gateway_payload.d.unwrap()).unwrap(); + self.events + .lock() + .await + .message + .reaction_add + .update_data(new_data) + .await; } "MESSAGE_REACTION_REMOVE" => { - let new_data: MessageReactionRemove = serde_json::from_value(gateway_payload.d.unwrap()).unwrap(); - self.events.lock().await.message.reaction_remove.update_data(new_data).await; + let new_data: types::MessageReactionRemove = + serde_json::from_value(gateway_payload.d.unwrap()).unwrap(); + self.events + .lock() + .await + .message + .reaction_remove + .update_data(new_data) + .await; } "MESSAGE_REACTION_REMOVE_ALL" => { - let new_data: MessageReactionRemoveAll = serde_json::from_value(gateway_payload.d.unwrap()).unwrap(); - self.events.lock().await.message.reaction_remove_all.update_data(new_data).await; + let new_data: types::MessageReactionRemoveAll = + serde_json::from_value(gateway_payload.d.unwrap()).unwrap(); + self.events + .lock() + .await + .message + .reaction_remove_all + .update_data(new_data) + .await; } "MESSAGE_REACTION_REMOVE_EMOJI" => { - let new_data: MessageReactionRemoveEmoji= serde_json::from_value(gateway_payload.d.unwrap()).unwrap(); - self.events.lock().await.message.reaction_remove_emoji.update_data(new_data).await; + let new_data: types::MessageReactionRemoveEmoji = + serde_json::from_value(gateway_payload.d.unwrap()).unwrap(); + self.events + .lock() + .await + .message + .reaction_remove_emoji + .update_data(new_data) + .await; } "PRESENCE_UPDATE" => { - let new_data: PresenceUpdate = serde_json::from_value(gateway_payload.d.unwrap()).unwrap(); - self.events.lock().await.user.presence_update.update_data(new_data).await; + let new_data: types::PresenceUpdate = + serde_json::from_value(gateway_payload.d.unwrap()).unwrap(); + self.events + .lock() + .await + .user + .presence_update + .update_data(new_data) + .await; } "STAGE_INSTANCE_CREATE" => {} "STAGE_INSTANCE_UPDATE" => {} @@ -315,27 +482,47 @@ impl Gateway { // Not documented in discord docs, I assume this isnt for bots / apps but is for users? "SESSIONS_REPLACE" => {} "TYPING_START" => { - let new_data: TypingStartEvent = serde_json::from_value(gateway_payload.d.unwrap()).unwrap(); - self.events.lock().await.user.typing_start_event.update_data(new_data).await; + let new_data: types::TypingStartEvent = + serde_json::from_value(gateway_payload.d.unwrap()).unwrap(); + self.events + .lock() + .await + .user + .typing_start_event + .update_data(new_data) + .await; } "USER_UPDATE" => { - let user: UserObject = serde_json::from_value(gateway_payload.d.unwrap()).unwrap(); - let new_data = UserUpdate {user}; - self.events.lock().await.user.update.update_data(new_data).await; + let user: types::User = + serde_json::from_value(gateway_payload.d.unwrap()).unwrap(); + let new_data = types::UserUpdate { user }; + self.events + .lock() + .await + .user + .update + .update_data(new_data) + .await; } "VOICE_STATE_UPDATE" => {} "VOICE_SERVER_UPDATE" => {} "WEBHOOKS_UPDATE" => {} - _ => {panic!("Invalid gateway event ({})", &gateway_payload_t)} + _ => { + panic!("Invalid gateway event ({})", &gateway_payload_t) + } } } // Heartbeat // We received a heartbeat from the server 1 => {} // Reconnect - 7 => {todo!()} + 7 => { + todo!() + } // Invalid Session - 9 => {todo!()} + 9 => { + todo!() + } // Hello // Starts our heartbeat // We should have already handled this in gateway init @@ -346,17 +533,32 @@ impl Gateway { 11 => { println!("GW: Received Heartbeat ACK"); } - 2 | 3 | 4 | 6 | 8 => {panic!("Received Gateway op code that's meant to be sent, not received ({})", gateway_payload.op)} - _ => {panic!("Received Invalid Gateway op code ({})", gateway_payload.op)} + 2 | 3 | 4 | 6 | 8 => { + panic!( + "Received Gateway op code that's meant to be sent, not received ({})", + gateway_payload.op + ) + } + _ => { + panic!("Received Invalid Gateway op code ({})", gateway_payload.op) + } } // If we have an active heartbeat thread and we received a seq number we should let it know if gateway_payload.s.is_some() { if self.heartbeat_handler.is_some() { + let heartbeat_communication = HeartbeatThreadCommunication { + op: gateway_payload.op, + d: gateway_payload.s.unwrap(), + }; - let heartbeat_communication = HeartbeatThreadCommunication { op: gateway_payload.op, d: gateway_payload.s.unwrap() }; - - self.heartbeat_handler.as_mut().unwrap().tx.send(heartbeat_communication).await.unwrap(); + self.heartbeat_handler + .as_mut() + .unwrap() + .tx + .send(heartbeat_communication) + .await + .unwrap(); } } } @@ -372,7 +574,17 @@ struct HeartbeatHandler { } impl HeartbeatHandler { - pub fn new(heartbeat_interval: u128, websocket_tx: Arc>, tokio_tungstenite::tungstenite::Message>>>) -> HeartbeatHandler { + pub fn new( + heartbeat_interval: u128, + websocket_tx: Arc< + Mutex< + SplitSink< + WebSocketStream>, + tokio_tungstenite::tungstenite::Message, + >, + >, + >, + ) -> HeartbeatHandler { let (tx, mut rx) = mpsc::channel(32); task::spawn(async move { @@ -380,37 +592,36 @@ impl HeartbeatHandler { let mut last_seq_number: Option = None; loop { - // If we received a seq number update, use that as the last seq number - let hb_communication: Result = rx.try_recv(); + let hb_communication: Result = + rx.try_recv(); if hb_communication.is_ok() { last_seq_number = Some(hb_communication.unwrap().d); } if last_heartbeat.elapsed().as_millis() > heartbeat_interval { - println!("GW: Sending Heartbeat.."); - let heartbeat = GatewayHeartbeat { + let heartbeat = types::GatewayHeartbeat { op: 1, - d: last_seq_number + d: last_seq_number, }; let heartbeat_json = serde_json::to_string(&heartbeat).unwrap(); let msg = tokio_tungstenite::tungstenite::Message::text(heartbeat_json); - websocket_tx.lock().await - .send(msg) - .await - .unwrap(); + websocket_tx.lock().await.send(msg).await.unwrap(); last_heartbeat = time::Instant::now(); } } }); - Self { heartbeat_interval, tx } + Self { + heartbeat_interval, + tx, + } } } @@ -423,7 +634,7 @@ struct HeartbeatThreadCommunication { /// An opcode for the communication we received op: u8, /// The sequence number we got from discord - d: u64 + d: u64, } /** @@ -431,7 +642,7 @@ Trait which defines the behaviour of an Observer. An Observer is an object which an Observable. The Observer is notified when the Observable's data changes. In this case, the Observable is a [`GatewayEvent`], which is a wrapper around a WebSocketEvent. */ -pub trait Observer: std::fmt::Debug { +pub trait Observer: std::fmt::Debug { fn update(&self, data: &T); } @@ -440,13 +651,13 @@ change in the WebSocketEvent. GatewayEvents are observable. */ #[derive(Default, Debug)] -pub struct GatewayEvent { +pub struct GatewayEvent { observers: Vec + Sync + Send>>>, pub event_data: T, pub is_observed: bool, } -impl GatewayEvent { +impl GatewayEvent { fn new(event_data: T) -> Self { Self { is_observed: false, @@ -469,7 +680,10 @@ impl GatewayEvent { Returns an error if the GatewayEvent is already observed. Error type: [`ObserverError::AlreadySubscribedError`] */ - pub fn subscribe(&mut self, observable: Arc + Sync + Send>>) -> Option { + pub fn subscribe( + &mut self, + observable: Arc + Sync + Send>>, + ) -> Option { if self.is_observed { return Some(ObserverError::AlreadySubscribedError); } @@ -486,7 +700,8 @@ impl GatewayEvent { // pointer value than observable. // The usage of the debug format to compare the generic T of observers is quite stupid, but the only thing to compare between them is T and if T == T they are the same // anddd there is no way to do that without using format - self.observers.retain(|obs| !(format!("{:?}", obs) == format!("{:?}", &observable))); + self.observers + .retain(|obs| !(format!("{:?}", obs) == format!("{:?}", &observable))); self.is_observed = !self.observers.is_empty(); } @@ -517,50 +732,50 @@ mod events { pub channel: Channel, pub thread: Thread, pub guild: Guild, - pub gateway_identify_payload: GatewayEvent, - pub gateway_resume: GatewayEvent, + pub gateway_identify_payload: GatewayEvent, + pub gateway_resume: GatewayEvent, } #[derive(Default, Debug)] pub struct Message { - pub create: GatewayEvent, - pub update: GatewayEvent, - pub delete: GatewayEvent, - pub delete_bulk: GatewayEvent, - pub reaction_add: GatewayEvent, - pub reaction_remove: GatewayEvent, - pub reaction_remove_all: GatewayEvent, - pub reaction_remove_emoji: GatewayEvent, + pub create: GatewayEvent, + pub update: GatewayEvent, + pub delete: GatewayEvent, + pub delete_bulk: GatewayEvent, + pub reaction_add: GatewayEvent, + pub reaction_remove: GatewayEvent, + pub reaction_remove_all: GatewayEvent, + pub reaction_remove_emoji: GatewayEvent, } #[derive(Default, Debug)] pub struct User { - pub update: GatewayEvent, - pub presence_update: GatewayEvent, - pub typing_start_event: GatewayEvent, + pub update: GatewayEvent, + pub presence_update: GatewayEvent, + pub typing_start_event: GatewayEvent, } #[derive(Default, Debug)] pub struct Channel { - pub create: GatewayEvent, - pub update: GatewayEvent, - pub delete: GatewayEvent, - pub pins_update: GatewayEvent + pub create: GatewayEvent, + pub update: GatewayEvent, + pub delete: GatewayEvent, + pub pins_update: GatewayEvent, } #[derive(Default, Debug)] pub struct Thread { - pub create: GatewayEvent, - pub update: GatewayEvent, - pub delete: GatewayEvent, - pub list_sync: GatewayEvent, - pub member_update: GatewayEvent, - pub members_update: GatewayEvent, + pub create: GatewayEvent, + pub update: GatewayEvent, + pub delete: GatewayEvent, + pub list_sync: GatewayEvent, + pub member_update: GatewayEvent, + pub members_update: GatewayEvent, } #[derive(Default, Debug)] pub struct Guild { - pub create: GatewayEvent, + pub create: GatewayEvent, /*pub update: GatewayEvent, pub delete: GatewayEvent, pub audit_log_entry_create: GatewayEvent, @@ -587,25 +802,24 @@ mod events { #[cfg(test)] mod example { use super::*; - use crate::api::types::GatewayResume; #[derive(Debug)] struct Consumer; - impl Observer for Consumer { - fn update(&self, data: &GatewayResume) { + impl Observer for Consumer { + fn update(&self, data: &types::GatewayResume) { println!("{}", data.token) } } #[tokio::test] async fn test_observer_behaviour() { - let mut event = GatewayEvent::new(GatewayResume { + let mut event = GatewayEvent::new(types::GatewayResume { token: "start".to_string(), session_id: "start".to_string(), seq: "start".to_string(), }); - let new_data = GatewayResume { + let new_data = types::GatewayResume { token: "token_3276ha37am3".to_string(), session_id: "89346671230".to_string(), seq: "3".to_string(), @@ -634,7 +848,6 @@ mod example { None => assert!(true), Some(_) => assert!(false), } - } #[tokio::test] diff --git a/src/instance.rs b/src/instance.rs index ab4b4b2..ce8849d 100644 --- a/src/instance.rs +++ b/src/instance.rs @@ -1,9 +1,13 @@ +use serde::{Deserialize, Serialize}; + use crate::api::limits::Limits; -use crate::api::types::InstancePolicies; use crate::errors::{FieldFormatError, InstanceServerError}; +use crate::types::{GeneralConfiguration, User, UserSettings}; use crate::URLBundle; +use std::cell::RefCell; use std::fmt; +use std::rc::Rc; #[derive(Debug, Clone)] /** @@ -11,7 +15,7 @@ The [`Instance`] what you will be using to perform all sorts of actions on the S */ pub struct Instance { pub urls: URLBundle, - pub instance_info: InstancePolicies, + pub instance_info: GeneralConfiguration, pub limits: Limits, } @@ -25,7 +29,7 @@ impl Instance { pub async fn new(urls: URLBundle) -> Result { let mut instance = Instance { urls: urls.clone(), - instance_info: InstancePolicies::new( + instance_info: GeneralConfiguration::new( // This is okay, because the instance_info will be overwritten by the instance_policies_schema() function. "".to_string(), None, @@ -38,7 +42,7 @@ impl Instance { ), limits: Limits::check_limits(urls.api).await, }; - instance.instance_info = match instance.instance_policies_schema().await { + instance.instance_info = match instance.general_configuration_schema().await { Ok(schema) => schema, Err(e) => { return Err(InstanceServerError::CantGetInfoError { @@ -50,7 +54,7 @@ impl Instance { } } -#[derive(Debug, PartialEq, Eq)] +#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct Token { pub token: String, } @@ -79,3 +83,38 @@ impl Username { Ok(Username { username }) } } + +#[derive(Debug)] +pub struct UserMeta { + pub belongs_to: Rc>, + pub token: String, + pub limits: Limits, + pub settings: UserSettings, + pub object: Option, +} + +impl UserMeta { + pub fn token(&self) -> String { + self.token.clone() + } + + pub fn set_token(&mut self, token: String) { + self.token = token; + } + + pub fn new( + belongs_to: Rc>, + token: String, + limits: Limits, + settings: UserSettings, + object: Option, + ) -> UserMeta { + UserMeta { + belongs_to, + token, + limits, + settings, + object, + } + } +} diff --git a/src/lib.rs b/src/lib.rs index b69e360..aa73302 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,6 +3,7 @@ pub mod errors; pub mod gateway; pub mod instance; pub mod limit; +pub mod types; pub mod voice; use url::{ParseError, Url}; diff --git a/src/types/config/mod.rs b/src/types/config/mod.rs new file mode 100644 index 0000000..521d303 --- /dev/null +++ b/src/types/config/mod.rs @@ -0,0 +1,187 @@ +use serde::{Deserialize, Serialize}; +use serde_json::{Map, Value}; + +pub use crate::{ + types::config::types::{ + api_configuration::ApiConfiguration, cdn_configuration::CdnConfiguration, + defaults_configuration::DefaultsConfiguration, email_configuration::EmailConfiguration, + endpoint_configuration::EndpointConfiguration, + external_tokens_configuration::ExternalTokensConfiguration, + general_configuration::GeneralConfiguration, gif_configuration::GifConfiguration, + guild_configuration::GuildConfiguration, kafka_configuration::KafkaConfiguration, + limit_configuration::LimitsConfiguration, login_configuration::LoginConfiguration, + metrics_configuration::MetricsConfiguration, + password_reset_configuration::PasswordResetConfiguration, + rabbit_mq_configuration::RabbitMQConfiguration, region_configuration::RegionConfiguration, + register_configuration::RegisterConfiguration, + security_configuration::SecurityConfiguration, sentry_configuration::SentryConfiguration, + template_configuration::TemplateConfiguration, + }, + types::entities::ConfigEntity, +}; + +pub mod types; + +#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ConfigValue { + pub gateway: EndpointConfiguration, + pub cdn: CdnConfiguration, + pub api: ApiConfiguration, + pub general: GeneralConfiguration, + pub limits: LimitsConfiguration, + pub security: SecurityConfiguration, + pub login: LoginConfiguration, + pub register: RegisterConfiguration, + pub regions: RegionConfiguration, + pub guild: GuildConfiguration, + pub gif: GifConfiguration, + pub rabbitmq: RabbitMQConfiguration, + pub kafka: KafkaConfiguration, + pub templates: TemplateConfiguration, + pub metrics: MetricsConfiguration, + pub sentry: SentryConfiguration, + pub defaults: DefaultsConfiguration, + pub external: ExternalTokensConfiguration, + pub email: EmailConfiguration, + pub password_reset: PasswordResetConfiguration, +} + +impl ConfigValue { + pub fn to_pairs(&self) -> Vec { + let v = serde_json::json!(self); + + generate_pairs(&v, "") + } + + pub fn from_pairs(pairs: Vec) -> Self { + pairs_to_config(pairs) + } +} + +fn generate_pairs(obj: &Value, key: &str) -> Vec { + let mut pairs = Vec::new(); + match obj { + Value::Object(map) => { + for (k, v) in map { + let new_key = if key.is_empty() { + k.to_string() + } else { + format!("{}_{}", key, k) + }; + pairs.extend(generate_pairs(v, &new_key)); + } + } + Value::Array(arr) => { + for (i, v) in arr.iter().enumerate() { + let new_key = format!("{}_{}", key, i); + pairs.extend(generate_pairs(v, &new_key)); + } + } + _ => pairs.push(ConfigEntity { + key: key.to_string(), + value: Some(obj.clone()), + }), + } + pairs +} + +fn pairs_to_config(pairs: Vec) -> ConfigValue { + let mut value = Value::Object(Map::new()); + + for p in pairs { + let keys: Vec<&str> = p.key.split('_').collect(); + let mut path = vec![]; + + for (i, &key) in keys.iter().enumerate() { + path.push(key); + + if i == keys.len() - 1 { + insert_into(&mut value, &path, p.value.clone().unwrap_or(Value::Null)); + } else if keys[i + 1].parse::().is_ok() { + if !path_exists(&value, &path) { + insert_into(&mut value, &path, Value::Array(Vec::new())); + } + } else if !path_exists(&value, &path) { + insert_into(&mut value, &path, Value::Object(Map::new())); + } + } + } + + serde_json::from_value(value).unwrap() +} + +fn path_exists(value: &Value, path: &[&str]) -> bool { + let mut current = value; + + for &key in path { + match current { + Value::Object(map) => { + if let Some(v) = map.get(key) { + current = v; + } else { + return false; + } + } + Value::Array(arr) => { + if let Ok(index) = key.parse::() { + if let Some(v) = arr.get(index) { + current = v; + } else { + return false; + } + } else { + return false; + } + } + _ => return false, + } + } + + true +} + +fn insert_into(value: &mut Value, path: &[&str], new_value: Value) { + let last_key = path.last().unwrap(); + let parent_path = &path[0..path.len() - 1]; + + let mut current = value; + + for &key in parent_path { + current = match current { + Value::Object(map) => map.get_mut(key).unwrap(), + Value::Array(arr) => arr.get_mut(key.parse::().unwrap()).unwrap(), + _ => unreachable!(), + }; + } + + match current { + Value::Object(map) => { + map.insert((*last_key).to_string(), new_value); + } + Value::Array(arr) => { + let index = last_key.parse::().unwrap(); + if index >= arr.len() { + arr.resize(index + 1, Value::Null); + } + arr[index] = new_value; + } + _ => unreachable!(), + }; +} + +#[cfg(test)] +mod test { + use crate::types::config::{generate_pairs, pairs_to_config, ConfigValue}; + + #[test] + fn test_pairs() { + let c = ConfigValue::default(); + let v = serde_json::json!(&c); + + let pairs = generate_pairs(&v, ""); + + let cfg = pairs_to_config(pairs); + assert_eq!(cfg, c) + } +} diff --git a/src/types/config/types/api_configuration.rs b/src/types/config/types/api_configuration.rs new file mode 100644 index 0000000..2d617fe --- /dev/null +++ b/src/types/config/types/api_configuration.rs @@ -0,0 +1,24 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub struct ApiConfiguration { + pub default_version: String, + pub active_versions: Vec, + pub endpoint_public: Option, +} + +impl Default for ApiConfiguration { + fn default() -> Self { + Self { + default_version: String::from("9"), + active_versions: vec![ + String::from("6"), + String::from("7"), + String::from("8"), + String::from("9"), + ], + endpoint_public: None, + } + } +} diff --git a/src/types/config/types/cdn_configuration.rs b/src/types/config/types/cdn_configuration.rs new file mode 100644 index 0000000..5c76273 --- /dev/null +++ b/src/types/config/types/cdn_configuration.rs @@ -0,0 +1,25 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CdnConfiguration { + pub resize_height_max: u64, + pub resize_width_max: u64, + pub imagor_server_url: Option, + + pub endpoint_public: Option, + pub endpoint_private: Option, +} + +impl Default for CdnConfiguration { + fn default() -> Self { + Self { + resize_height_max: 1000, + resize_width_max: 1000, + imagor_server_url: None, + + endpoint_private: None, + endpoint_public: None, + } + } +} diff --git a/src/types/config/types/defaults_configuration.rs b/src/types/config/types/defaults_configuration.rs new file mode 100644 index 0000000..c2b67c2 --- /dev/null +++ b/src/types/config/types/defaults_configuration.rs @@ -0,0 +1,10 @@ +use serde::{Deserialize, Serialize}; + +use crate::types::config::types::subconfigs::defaults::{guild::GuildDefaults, user::UserDefaults}; + +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct DefaultsConfiguration { + pub guild: GuildDefaults, + pub user: UserDefaults, +} diff --git a/src/types/config/types/email_configuration.rs b/src/types/config/types/email_configuration.rs new file mode 100644 index 0000000..954f4de --- /dev/null +++ b/src/types/config/types/email_configuration.rs @@ -0,0 +1,25 @@ +use serde::{Deserialize, Serialize}; + +use crate::types::config::types::subconfigs::email::{ + mailgun::MailGunConfiguration, mailjet::MailJetConfiguration, sendgrid::SendGridConfiguration, + smtp::SMTPConfiguration, +}; + +#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)] +pub enum EmailProvider { + Smtp, + MailGun, + MailJet, + SendGrid, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +#[derive(Default)] +pub struct EmailConfiguration { + pub provider: Option, + pub smtp: SMTPConfiguration, + pub mailgun: MailGunConfiguration, + pub mailjet: MailJetConfiguration, + pub sendgrid: SendGridConfiguration, +} diff --git a/src/types/config/types/endpoint_configuration.rs b/src/types/config/types/endpoint_configuration.rs new file mode 100644 index 0000000..b484791 --- /dev/null +++ b/src/types/config/types/endpoint_configuration.rs @@ -0,0 +1,9 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct EndpointConfiguration { + pub endpoint_client: Option, + pub endpoint_private: Option, + pub endpoint_public: Option, +} diff --git a/src/types/config/types/external_tokens_configuration.rs b/src/types/config/types/external_tokens_configuration.rs new file mode 100644 index 0000000..f417b2f --- /dev/null +++ b/src/types/config/types/external_tokens_configuration.rs @@ -0,0 +1,7 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ExternalTokensConfiguration { + pub twitter: Option, +} diff --git a/src/types/config/types/general_configuration.rs b/src/types/config/types/general_configuration.rs new file mode 100644 index 0000000..a3c8f65 --- /dev/null +++ b/src/types/config/types/general_configuration.rs @@ -0,0 +1,57 @@ +use serde::{Deserialize, Serialize}; + +use crate::types::utils::Snowflake; + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct GeneralConfiguration { + pub instance_name: String, + pub instance_description: Option, + pub front_page: Option, + pub tos_page: Option, + pub correspondence_email: Option, + pub correspondence_user_id: Option, + pub image: Option, + pub instance_id: Option, +} + +impl Default for GeneralConfiguration { + fn default() -> Self { + Self { + instance_name: String::from("Spacebar Instance"), + instance_description: Some(String::from( + "This is a Spacebar instance made in the pre-release days", + )), + front_page: None, + tos_page: None, + correspondence_email: None, + correspondence_user_id: None, + image: None, + instance_id: Some(Snowflake::generate()), + } + } +} + +impl GeneralConfiguration { + pub fn new( + instance_name: String, + instance_description: Option, + front_page: Option, + tos_page: Option, + correspondence_email: Option, + correspondence_user_id: Option, + image: Option, + instance_id: Option, + ) -> Self { + Self { + instance_name, + instance_description, + front_page, + tos_page, + correspondence_email, + correspondence_user_id, + image, + instance_id, + } + } +} diff --git a/src/types/config/types/gif_configuration.rs b/src/types/config/types/gif_configuration.rs new file mode 100644 index 0000000..8644fb4 --- /dev/null +++ b/src/types/config/types/gif_configuration.rs @@ -0,0 +1,26 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "lowercase")] +pub enum GifProvider { + #[default] + Tenor, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct GifConfiguration { + pub enabled: bool, + pub provider: GifProvider, + pub api_key: Option, +} + +impl Default for GifConfiguration { + fn default() -> Self { + Self { + enabled: true, + provider: GifProvider::Tenor, + api_key: Some(String::from("LIVDSRZULELA")), + } + } +} diff --git a/src/types/config/types/guild_configuration.rs b/src/types/config/types/guild_configuration.rs new file mode 100644 index 0000000..e98c6c5 --- /dev/null +++ b/src/types/config/types/guild_configuration.rs @@ -0,0 +1,126 @@ +use serde::{Deserialize, Serialize}; + +use crate::types::config::types::subconfigs::guild::{ + autojoin::AutoJoinConfiguration, discovery::DiscoverConfiguration, +}; + +#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] +pub enum GuildFeatures { + ActivitiesAlpha, + ActivitiesEmployee, + ActivitiesInternalDev, + AnimatedBanner, + AnimatedIcon, + ApplicationCommandPermissionsV2, + AutoModeration, + AutoModTriggerKeywordFilter, + AutoModTriggerMLSpamFilter, + AutoModTriggerSpamLinkFilter, + AutoModTriggerUserProfile, + Banner, + BFG, + BoostingTiersExperimentMediumGuild, + BoostingTiersExperimentSmallGuild, + BotDeveloperEarlyAccess, + BurstReactions, + CommunityCanary, + CommunityExpLargeGated, + CommunityExpLargeUngated, + CommunityExpMedium, + ChannelEmojisGenerated, + ChannelHighlights, + ChannelHighlightsDisabled, + ClydeEnabled, + ClydeExperimentEnabled, + ClydeDisabled, + Community, + CreatorAcceptedNewTerms, + CreatorMonetizable, + CreatorMonetizableDisabled, + CreatorMonetizablePendingNewOwnerOnboarding, + CreatorMonetizableProvisional, + CreatorMonetizableRestricted, + CreatorMonetizableWhiteglove, + CreatorMonetizableApplicationAllowlist, + CreateStorePage, + DeveloperSupportServer, + DiscoverableDisabled, + Discoverable, + EnabledDiscoverableBefore, + ExposedToActivitiesWTPExperiment, + GuestsEnabled, + GuildAutomodDefaultList, + GuildCommunicationDisabledGuilds, + GuildHomeDeprecationOverride, + GuildHomeOverride, + GuildHomeTest, + GuildMemberVerificationExperiment, + GuildOnboarding, + GuildOnboardingAdminOnly, + GuildOnboardingEverEnabled, + GuildOnboardingHasPrompts, + GuildRoleSubscription, + GuildRoleSubscriptionPurchaseFeedbackLoop, + GuildRoleSubscriptionTrials, + GuildServerGuide, + GuildWebPageVanityURL, + HadEarlyActivitiesAccess, + HasDirectoryEntry, + HideFromExperimentUI, + Hub, + IncreasedThreadLimit, + InternalEmployeeOnly, + InviteSplash, + InvitesDisabled, + LinkedToHub, + MarketplacesConnectionRoles, + MemberProfiles, + MemberVerificationGateEnabled, + MemberVerificationManualApproval, + MobileWebRoleSubscriptionPurchasePage, + MonetizationEnabled, + MoreEmoji, + MoreStickers, + News, + NewThreadPermissions, + Partnered, + PremiumTier3Override, + PreviewEnabled, + RaidAlertsDisabled, + RelayEnabled, + RestrictSpamRiskGuild, + RoleIcons, + RoleSubscriptionsAvailableForPurchase, + RoleSubscriptionsEnabled, + RoleSubscriptionsEnabledForPurchase, + Shard, + SharedCanvasFriendsAndFamilyTest, + Soundboard, + SummariesEnabled, + SummariesEnabledGA, + SummariesDisabledByUser, + SummariesEnabledByUser, + TextInStageEnabled, + TextInVoiceEnabled, + ThreadsEnabledTesting, + ThreadsEnabled, + ThreadDefaultAutoArchiveDuration, + ThreadsOnlyChannel, + TicketedEventsEnabled, + TicketingEnabled, + VanityUrls, + Verified, + VIPRegions, + VoiceChannelEffects, + WelcomeScreenEnabled, +} + +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct GuildConfiguration { + pub discovery: DiscoverConfiguration, + pub auto_join: AutoJoinConfiguration, + #[serde(default)] + pub default_features: Vec, +} diff --git a/src/types/config/types/kafka_configuration.rs b/src/types/config/types/kafka_configuration.rs new file mode 100644 index 0000000..46d10b6 --- /dev/null +++ b/src/types/config/types/kafka_configuration.rs @@ -0,0 +1,10 @@ +use serde::{Deserialize, Serialize}; + +use crate::types::config::types::subconfigs::kafka::KafkaBroker; + +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct KafkaConfiguration { + #[serde(default)] + pub brokers: Option>, +} diff --git a/src/types/config/types/limit_configuration.rs b/src/types/config/types/limit_configuration.rs new file mode 100644 index 0000000..44f888a --- /dev/null +++ b/src/types/config/types/limit_configuration.rs @@ -0,0 +1,17 @@ +use serde::{Deserialize, Serialize}; + +use crate::types::config::types::subconfigs::limits::{ + channel::ChannelLimits, global::GlobalRateLimits, guild::GuildLimits, message::MessageLimits, + rates::RateLimits, user::UserLimits, +}; + +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct LimitsConfiguration { + pub user: UserLimits, + pub guild: GuildLimits, + pub message: MessageLimits, + pub channel: ChannelLimits, + pub rate: RateLimits, + pub absolute_rate: GlobalRateLimits, +} diff --git a/src/types/config/types/login_configuration.rs b/src/types/config/types/login_configuration.rs new file mode 100644 index 0000000..a2b1039 --- /dev/null +++ b/src/types/config/types/login_configuration.rs @@ -0,0 +1,8 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct LoginConfiguration { + pub require_captcha: bool, + pub require_verification: bool, +} diff --git a/src/types/config/types/metrics_configuration.rs b/src/types/config/types/metrics_configuration.rs new file mode 100644 index 0000000..336ac84 --- /dev/null +++ b/src/types/config/types/metrics_configuration.rs @@ -0,0 +1,13 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct MetricsConfiguration { + pub timeout: u64, +} + +impl Default for MetricsConfiguration { + fn default() -> Self { + Self { timeout: 30000 } + } +} diff --git a/src/types/config/types/mod.rs b/src/types/config/types/mod.rs new file mode 100644 index 0000000..dce4eb0 --- /dev/null +++ b/src/types/config/types/mod.rs @@ -0,0 +1,21 @@ +pub mod api_configuration; +pub mod cdn_configuration; +pub mod defaults_configuration; +pub mod email_configuration; +pub mod endpoint_configuration; +pub mod external_tokens_configuration; +pub mod general_configuration; +pub mod gif_configuration; +pub mod guild_configuration; +pub mod kafka_configuration; +pub mod limit_configuration; +pub mod login_configuration; +pub mod metrics_configuration; +pub mod password_reset_configuration; +pub mod rabbit_mq_configuration; +pub mod region_configuration; +pub mod register_configuration; +pub mod security_configuration; +pub mod sentry_configuration; +pub mod subconfigs; +pub mod template_configuration; diff --git a/src/types/config/types/password_reset_configuration.rs b/src/types/config/types/password_reset_configuration.rs new file mode 100644 index 0000000..4dddae9 --- /dev/null +++ b/src/types/config/types/password_reset_configuration.rs @@ -0,0 +1,7 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct PasswordResetConfiguration { + pub require_captcha: bool, +} diff --git a/src/types/config/types/rabbit_mq_configuration.rs b/src/types/config/types/rabbit_mq_configuration.rs new file mode 100644 index 0000000..2437055 --- /dev/null +++ b/src/types/config/types/rabbit_mq_configuration.rs @@ -0,0 +1,7 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RabbitMQConfiguration { + pub host: Option, +} diff --git a/src/types/config/types/region_configuration.rs b/src/types/config/types/region_configuration.rs new file mode 100644 index 0000000..078fa03 --- /dev/null +++ b/src/types/config/types/region_configuration.rs @@ -0,0 +1,29 @@ +use serde::{Deserialize, Serialize}; + +use crate::types::config::types::subconfigs::region::Region; + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RegionConfiguration { + pub default: String, + pub use_default_as_optimal: bool, + pub available: Vec, +} + +impl Default for RegionConfiguration { + fn default() -> Self { + Self { + default: String::from("spacebar"), + use_default_as_optimal: true, + available: vec![Region { + id: String::from("spacebar"), + name: String::from("spacebar"), + endpoint: String::from("127.0.0.1:3004"), + location: None, + vip: false, + custom: false, + deprecated: false, + }], + } + } +} diff --git a/src/types/config/types/register_configuration.rs b/src/types/config/types/register_configuration.rs new file mode 100644 index 0000000..4a20824 --- /dev/null +++ b/src/types/config/types/register_configuration.rs @@ -0,0 +1,41 @@ +use serde::{Deserialize, Serialize}; + +use crate::types::config::types::subconfigs::register::{ + DateOfBirthConfiguration, PasswordConfiguration, RegistrationEmailConfiguration, +}; + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RegisterConfiguration { + pub email: RegistrationEmailConfiguration, + pub date_of_birth: DateOfBirthConfiguration, + pub password: PasswordConfiguration, + pub disabled: bool, + pub require_captcha: bool, + pub require_invite: bool, + pub guests_require_invite: bool, + pub allow_new_registration: bool, + pub allow_multiple_accounts: bool, + pub block_proxies: bool, + pub incrementing_discriminators: bool, + pub default_rights: String, +} + +impl Default for RegisterConfiguration { + fn default() -> Self { + Self { + email: RegistrationEmailConfiguration::default(), + date_of_birth: DateOfBirthConfiguration::default(), + password: PasswordConfiguration::default(), + disabled: false, + require_captcha: true, + require_invite: false, + guests_require_invite: true, + allow_new_registration: true, + allow_multiple_accounts: true, + block_proxies: true, + incrementing_discriminators: false, + default_rights: String::from("875069521787904"), + } + } +} diff --git a/src/types/config/types/security_configuration.rs b/src/types/config/types/security_configuration.rs new file mode 100644 index 0000000..d025a4b --- /dev/null +++ b/src/types/config/types/security_configuration.rs @@ -0,0 +1,44 @@ +use base64::Engine; +use serde::{Deserialize, Serialize}; + +use crate::types::config::types::subconfigs::security::{ + CaptchaConfiguration, TwoFactorConfiguration, +}; + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SecurityConfiguration { + pub captcha: CaptchaConfiguration, + pub two_factor: TwoFactorConfiguration, + pub auto_update: bool, + pub request_signature: String, + pub jwt_secret: String, + pub forwarded_for: Option, + pub ipdata_api_key: Option, + pub mfa_backup_code_count: u8, + pub stats_world_readable: bool, + pub default_registration_token_expiration: u64, +} + +impl Default for SecurityConfiguration { + fn default() -> Self { + let mut req_sig: [u8; 32] = [0; 32]; + let _ = openssl::rand::rand_bytes(&mut req_sig); + let mut jwt_secret: [u8; 256] = [0; 256]; + let _ = openssl::rand::rand_bytes(&mut jwt_secret); + Self { + captcha: Default::default(), + two_factor: Default::default(), + auto_update: true, + request_signature: base64::engine::general_purpose::STANDARD.encode(req_sig), + jwt_secret: base64::engine::general_purpose::STANDARD.encode(jwt_secret), + forwarded_for: None, + ipdata_api_key: Some(String::from( + "eca677b284b3bac29eb72f5e496aa9047f26543605efe99ff2ce35c9", + )), + mfa_backup_code_count: 10, + stats_world_readable: true, + default_registration_token_expiration: 1000 * 60 * 60 * 24 * 7, + } + } +} diff --git a/src/types/config/types/sentry_configuration.rs b/src/types/config/types/sentry_configuration.rs new file mode 100644 index 0000000..256bab9 --- /dev/null +++ b/src/types/config/types/sentry_configuration.rs @@ -0,0 +1,28 @@ +use std::ffi::OsString; + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SentryConfiguration { + pub enabled: bool, + pub endpoint: String, + pub trace_sample_rate: f64, + pub environment: String, +} + +impl Default for SentryConfiguration { + fn default() -> Self { + Self { + enabled: false, + endpoint: String::from( + "https://241c6fb08adb469da1bb82522b25c99f@sentry.quartzinc.space/3", + ), + trace_sample_rate: 1.0, + environment: hostname::get() + .unwrap_or_else(|_| OsString::new()) + .to_string_lossy() + .to_string(), + } + } +} diff --git a/src/types/config/types/subconfigs/client/mod.rs b/src/types/config/types/subconfigs/client/mod.rs new file mode 100644 index 0000000..5d3d304 --- /dev/null +++ b/src/types/config/types/subconfigs/client/mod.rs @@ -0,0 +1,17 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ClientReleaseConfiguration { + pub use_local_release: bool, + pub upstream_version: String, +} + +impl Default for ClientReleaseConfiguration { + fn default() -> Self { + Self { + use_local_release: true, + upstream_version: String::from("0.0.264"), + } + } +} diff --git a/src/types/config/types/subconfigs/defaults/guild.rs b/src/types/config/types/subconfigs/defaults/guild.rs new file mode 100644 index 0000000..966c7af --- /dev/null +++ b/src/types/config/types/subconfigs/defaults/guild.rs @@ -0,0 +1,23 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct GuildDefaults { + pub max_presences: u64, + pub max_video_channel_users: u16, + pub afk_timeout: u16, + pub default_message_notifications: u8, + pub explicit_content_filter: u8, +} + +impl Default for GuildDefaults { + fn default() -> Self { + Self { + max_presences: 250_000, + max_video_channel_users: 200, + afk_timeout: 300, + default_message_notifications: 1, + explicit_content_filter: 0, + } + } +} diff --git a/src/types/config/types/subconfigs/defaults/mod.rs b/src/types/config/types/subconfigs/defaults/mod.rs new file mode 100644 index 0000000..56d877f --- /dev/null +++ b/src/types/config/types/subconfigs/defaults/mod.rs @@ -0,0 +1,2 @@ +pub mod guild; +pub mod user; diff --git a/src/types/config/types/subconfigs/defaults/user.rs b/src/types/config/types/subconfigs/defaults/user.rs new file mode 100644 index 0000000..635d6d4 --- /dev/null +++ b/src/types/config/types/subconfigs/defaults/user.rs @@ -0,0 +1,19 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct UserDefaults { + pub premium: bool, + pub premium_type: u8, + pub verified: bool, +} + +impl Default for UserDefaults { + fn default() -> Self { + Self { + premium: true, + premium_type: 2, + verified: true, + } + } +} diff --git a/src/types/config/types/subconfigs/email/mailgun.rs b/src/types/config/types/subconfigs/email/mailgun.rs new file mode 100644 index 0000000..636e462 --- /dev/null +++ b/src/types/config/types/subconfigs/email/mailgun.rs @@ -0,0 +1,8 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct MailGunConfiguration { + pub api_key: Option, + pub domain: Option, +} diff --git a/src/types/config/types/subconfigs/email/mailjet.rs b/src/types/config/types/subconfigs/email/mailjet.rs new file mode 100644 index 0000000..4e505c1 --- /dev/null +++ b/src/types/config/types/subconfigs/email/mailjet.rs @@ -0,0 +1,8 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct MailJetConfiguration { + pub api_key: Option, + pub api_secret: Option, +} diff --git a/src/types/config/types/subconfigs/email/mod.rs b/src/types/config/types/subconfigs/email/mod.rs new file mode 100644 index 0000000..21253fd --- /dev/null +++ b/src/types/config/types/subconfigs/email/mod.rs @@ -0,0 +1,4 @@ +pub mod mailgun; +pub mod mailjet; +pub mod sendgrid; +pub mod smtp; diff --git a/src/types/config/types/subconfigs/email/sendgrid.rs b/src/types/config/types/subconfigs/email/sendgrid.rs new file mode 100644 index 0000000..879c719 --- /dev/null +++ b/src/types/config/types/subconfigs/email/sendgrid.rs @@ -0,0 +1,7 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SendGridConfiguration { + pub api_key: Option, +} diff --git a/src/types/config/types/subconfigs/email/smtp.rs b/src/types/config/types/subconfigs/email/smtp.rs new file mode 100644 index 0000000..a02c66f --- /dev/null +++ b/src/types/config/types/subconfigs/email/smtp.rs @@ -0,0 +1,10 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub struct SMTPConfiguration { + pub host: Option, + pub port: Option, + pub secure: bool, + pub username: Option, + pub password: Option, +} diff --git a/src/types/config/types/subconfigs/guild/autojoin.rs b/src/types/config/types/subconfigs/guild/autojoin.rs new file mode 100644 index 0000000..fe72c6c --- /dev/null +++ b/src/types/config/types/subconfigs/guild/autojoin.rs @@ -0,0 +1,22 @@ +use serde::{Deserialize, Serialize}; + +use crate::types::utils::Snowflake; + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct AutoJoinConfiguration { + pub enabled: bool, + #[serde(skip_serializing_if = "Option::is_none")] + pub guilds: Option>, + pub can_leave: bool, +} + +impl Default for AutoJoinConfiguration { + fn default() -> Self { + Self { + enabled: true, + guilds: None, + can_leave: true, + } + } +} diff --git a/src/types/config/types/subconfigs/guild/discovery.rs b/src/types/config/types/subconfigs/guild/discovery.rs new file mode 100644 index 0000000..1e283b0 --- /dev/null +++ b/src/types/config/types/subconfigs/guild/discovery.rs @@ -0,0 +1,21 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct DiscoverConfiguration { + pub show_all_guilds: bool, + pub use_recommendation: bool, + pub offset: u16, + pub limit: u16, +} + +impl Default for DiscoverConfiguration { + fn default() -> Self { + Self { + show_all_guilds: false, + use_recommendation: false, + offset: 0, + limit: 24, + } + } +} diff --git a/src/types/config/types/subconfigs/guild/mod.rs b/src/types/config/types/subconfigs/guild/mod.rs new file mode 100644 index 0000000..e4d7dcf --- /dev/null +++ b/src/types/config/types/subconfigs/guild/mod.rs @@ -0,0 +1,2 @@ +pub mod autojoin; +pub mod discovery; diff --git a/src/types/config/types/subconfigs/kafka/mod.rs b/src/types/config/types/subconfigs/kafka/mod.rs new file mode 100644 index 0000000..1ee4015 --- /dev/null +++ b/src/types/config/types/subconfigs/kafka/mod.rs @@ -0,0 +1,7 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct KafkaBroker { + pub ip: String, + pub port: u16, +} diff --git a/src/types/config/types/subconfigs/limits/channel.rs b/src/types/config/types/subconfigs/limits/channel.rs new file mode 100644 index 0000000..03e46e5 --- /dev/null +++ b/src/types/config/types/subconfigs/limits/channel.rs @@ -0,0 +1,19 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ChannelLimits { + pub max_pins: u16, + pub max_topic: u16, + pub max_webhooks: u16, +} + +impl Default for ChannelLimits { + fn default() -> Self { + Self { + max_pins: 500, + max_topic: 1024, + max_webhooks: 100, + } + } +} diff --git a/src/types/config/types/subconfigs/limits/global.rs b/src/types/config/types/subconfigs/limits/global.rs new file mode 100644 index 0000000..87f9e1c --- /dev/null +++ b/src/types/config/types/subconfigs/limits/global.rs @@ -0,0 +1,41 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct GlobalRateLimit { + pub limit: u16, + pub window: u64, + pub enabled: bool, +} + +impl Default for GlobalRateLimit { + fn default() -> Self { + Self { + limit: 100, + window: 60 * 60 * 1000, + enabled: true, + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct GlobalRateLimits { + pub register: GlobalRateLimit, + pub send_message: GlobalRateLimit, +} + +impl Default for GlobalRateLimits { + fn default() -> Self { + Self { + register: GlobalRateLimit { + limit: 25, + ..Default::default() + }, + send_message: GlobalRateLimit { + limit: 200, + window: 60 * 1000, + ..Default::default() + }, + } + } +} diff --git a/src/types/config/types/subconfigs/limits/guild.rs b/src/types/config/types/subconfigs/limits/guild.rs new file mode 100644 index 0000000..6def5a0 --- /dev/null +++ b/src/types/config/types/subconfigs/limits/guild.rs @@ -0,0 +1,23 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct GuildLimits { + pub max_roles: u16, + pub max_emojis: u16, + pub max_members: u64, + pub max_channels: u32, + pub max_channels_in_category: u32, +} + +impl Default for GuildLimits { + fn default() -> Self { + Self { + max_roles: 1000, + max_emojis: 20_000, + max_members: 25_000_000, + max_channels: 65_535, + max_channels_in_category: 65_535, + } + } +} diff --git a/src/types/config/types/subconfigs/limits/message.rs b/src/types/config/types/subconfigs/limits/message.rs new file mode 100644 index 0000000..9d368b9 --- /dev/null +++ b/src/types/config/types/subconfigs/limits/message.rs @@ -0,0 +1,26 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct MessageLimits { + pub max_characters: u32, + #[serde(default)] + pub max_tts_characters: u32, + pub max_reactions: u32, + pub max_attachment_size: u64, + pub max_bulk_delete: u32, + pub max_embed_download_size: u64, +} + +impl Default for MessageLimits { + fn default() -> Self { + Self { + max_characters: 1048576, + max_tts_characters: 160, + max_reactions: 2048, + max_attachment_size: 1024 * 1024 * 1024, + max_bulk_delete: 1000, + max_embed_download_size: 1024 * 1024 * 5, + } + } +} diff --git a/src/types/config/types/subconfigs/limits/mod.rs b/src/types/config/types/subconfigs/limits/mod.rs new file mode 100644 index 0000000..4dbc2fa --- /dev/null +++ b/src/types/config/types/subconfigs/limits/mod.rs @@ -0,0 +1,7 @@ +pub mod channel; +pub mod global; +pub mod guild; +pub mod message; +pub mod ratelimits; +pub mod rates; +pub mod user; diff --git a/src/types/config/types/subconfigs/limits/ratelimits/auth.rs b/src/types/config/types/subconfigs/limits/ratelimits/auth.rs new file mode 100644 index 0000000..f5abb0f --- /dev/null +++ b/src/types/config/types/subconfigs/limits/ratelimits/auth.rs @@ -0,0 +1,28 @@ +use serde::{Deserialize, Serialize}; + +use crate::types::config::types::subconfigs::limits::ratelimits::RateLimitOptions; + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct AuthRateLimit { + pub login: RateLimitOptions, + pub register: RateLimitOptions, +} + +impl Default for AuthRateLimit { + fn default() -> Self { + Self { + login: RateLimitOptions { + bot: None, + count: 5, + window: 60, + only_ip: false, + }, + register: RateLimitOptions { + bot: None, + count: 2, + window: 60 * 60 * 12, + only_ip: false, + }, + } + } +} diff --git a/src/types/config/types/subconfigs/limits/ratelimits/mod.rs b/src/types/config/types/subconfigs/limits/ratelimits/mod.rs new file mode 100644 index 0000000..66a2b78 --- /dev/null +++ b/src/types/config/types/subconfigs/limits/ratelimits/mod.rs @@ -0,0 +1,14 @@ +use serde::{Deserialize, Serialize}; + +pub mod auth; +pub mod route; + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RateLimitOptions { + pub bot: Option, + pub count: u64, + pub window: u64, + #[serde(default)] + pub only_ip: bool, +} diff --git a/src/types/config/types/subconfigs/limits/ratelimits/route.rs b/src/types/config/types/subconfigs/limits/ratelimits/route.rs new file mode 100644 index 0000000..5529af3 --- /dev/null +++ b/src/types/config/types/subconfigs/limits/ratelimits/route.rs @@ -0,0 +1,39 @@ +use serde::{Deserialize, Serialize}; + +use crate::types::config::types::subconfigs::limits::ratelimits::{ + auth::AuthRateLimit, RateLimitOptions, +}; + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct RouteRateLimit { + pub guild: RateLimitOptions, + pub webhook: RateLimitOptions, + pub channel: RateLimitOptions, + pub auth: AuthRateLimit, +} + +impl Default for RouteRateLimit { + fn default() -> Self { + Self { + guild: RateLimitOptions { + bot: None, + count: 5, + window: 5, + only_ip: false, + }, + webhook: RateLimitOptions { + bot: None, + count: 10, + window: 5, + only_ip: false, + }, + channel: RateLimitOptions { + bot: None, + count: 10, + window: 5, + only_ip: false, + }, + auth: AuthRateLimit::default(), + } + } +} diff --git a/src/types/config/types/subconfigs/limits/rates.rs b/src/types/config/types/subconfigs/limits/rates.rs new file mode 100644 index 0000000..9d0cab1 --- /dev/null +++ b/src/types/config/types/subconfigs/limits/rates.rs @@ -0,0 +1,41 @@ +use serde::{Deserialize, Serialize}; + +use crate::types::config::types::subconfigs::limits::ratelimits::{ + route::RouteRateLimit, RateLimitOptions, +}; + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct RateLimits { + pub enabled: bool, + pub ip: RateLimitOptions, + pub global: RateLimitOptions, + pub error: RateLimitOptions, + pub routes: RouteRateLimit, +} + +impl Default for RateLimits { + fn default() -> Self { + Self { + enabled: false, + ip: RateLimitOptions { + bot: None, + count: 500, + window: 5, + only_ip: false, + }, + global: RateLimitOptions { + bot: None, + count: 250, + window: 5, + only_ip: false, + }, + error: RateLimitOptions { + bot: None, + count: 10, + window: 5, + only_ip: false, + }, + routes: RouteRateLimit::default(), + } + } +} diff --git a/src/types/config/types/subconfigs/limits/user.rs b/src/types/config/types/subconfigs/limits/user.rs new file mode 100644 index 0000000..e43b746 --- /dev/null +++ b/src/types/config/types/subconfigs/limits/user.rs @@ -0,0 +1,19 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct UserLimits { + pub max_guilds: u64, + pub max_username: u16, + pub max_friends: u64, +} + +impl Default for UserLimits { + fn default() -> Self { + Self { + max_guilds: 1048576, + max_username: 32, + max_friends: 5000, + } + } +} diff --git a/src/types/config/types/subconfigs/mod.rs b/src/types/config/types/subconfigs/mod.rs new file mode 100644 index 0000000..4c85096 --- /dev/null +++ b/src/types/config/types/subconfigs/mod.rs @@ -0,0 +1,9 @@ +pub mod client; +pub mod defaults; +pub mod email; +pub mod guild; +pub mod kafka; +pub mod limits; +pub mod region; +pub mod register; +pub mod security; diff --git a/src/types/config/types/subconfigs/region/mod.rs b/src/types/config/types/subconfigs/region/mod.rs new file mode 100644 index 0000000..e0b1800 --- /dev/null +++ b/src/types/config/types/subconfigs/region/mod.rs @@ -0,0 +1,21 @@ +use serde::{Deserialize, Serialize}; + + + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct LatLong { + pub latitude: f64, + pub longitude: f64, +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct Region { + pub id: String, + pub name: String, + pub endpoint: String, + pub location: Option, + pub vip: bool, + pub custom: bool, + #[serde(default)] + pub deprecated: bool, +} diff --git a/src/types/config/types/subconfigs/register/date_of_birth.rs b/src/types/config/types/subconfigs/register/date_of_birth.rs new file mode 100644 index 0000000..9c1bec1 --- /dev/null +++ b/src/types/config/types/subconfigs/register/date_of_birth.rs @@ -0,0 +1,16 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct DateOfBirthConfiguration { + pub required: bool, + pub minimum: u8, +} + +impl Default for DateOfBirthConfiguration { + fn default() -> Self { + Self { + required: true, + minimum: 13, + } + } +} diff --git a/src/types/config/types/subconfigs/register/email.rs b/src/types/config/types/subconfigs/register/email.rs new file mode 100644 index 0000000..ac99bfc --- /dev/null +++ b/src/types/config/types/subconfigs/register/email.rs @@ -0,0 +1,21 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct RegistrationEmailConfiguration { + pub required: bool, + pub allowlist: bool, + #[serde(default)] + pub blacklist: bool, + #[serde(default)] + pub domains: Vec, +} +impl Default for RegistrationEmailConfiguration { + fn default() -> Self { + Self { + required: false, + allowlist: false, + blacklist: true, + domains: Vec::new(), + } + } +} diff --git a/src/types/config/types/subconfigs/register/mod.rs b/src/types/config/types/subconfigs/register/mod.rs new file mode 100644 index 0000000..ad92571 --- /dev/null +++ b/src/types/config/types/subconfigs/register/mod.rs @@ -0,0 +1,7 @@ +mod date_of_birth; +mod email; +mod password; + +pub use date_of_birth::DateOfBirthConfiguration; +pub use email::RegistrationEmailConfiguration; +pub use password::PasswordConfiguration; diff --git a/src/types/config/types/subconfigs/register/password.rs b/src/types/config/types/subconfigs/register/password.rs new file mode 100644 index 0000000..9247f7d --- /dev/null +++ b/src/types/config/types/subconfigs/register/password.rs @@ -0,0 +1,23 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct PasswordConfiguration { + pub required: bool, + pub min_length: u8, + pub min_numbers: u8, + pub min_upper_case: u8, + pub min_symbols: u8, +} + +impl Default for PasswordConfiguration { + fn default() -> Self { + Self { + required: false, + min_length: 8, + min_numbers: 2, + min_upper_case: 2, + min_symbols: 0, + } + } +} diff --git a/src/types/config/types/subconfigs/security/captcha.rs b/src/types/config/types/subconfigs/security/captcha.rs new file mode 100644 index 0000000..82bb517 --- /dev/null +++ b/src/types/config/types/subconfigs/security/captcha.rs @@ -0,0 +1,27 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "lowercase")] +pub enum CaptchaService { + Recaptcha, + HCaptcha, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct CaptchaConfiguration { + pub enabled: bool, + pub service: CaptchaService, + pub sitekey: Option, + pub secret: Option, +} + +impl Default for CaptchaConfiguration { + fn default() -> Self { + Self { + enabled: false, + service: CaptchaService::HCaptcha, + sitekey: None, + secret: None, + } + } +} diff --git a/src/types/config/types/subconfigs/security/mod.rs b/src/types/config/types/subconfigs/security/mod.rs new file mode 100644 index 0000000..ceeb0d3 --- /dev/null +++ b/src/types/config/types/subconfigs/security/mod.rs @@ -0,0 +1,5 @@ +mod captcha; +mod twofactor; + +pub use captcha::{CaptchaConfiguration, CaptchaService}; +pub use twofactor::TwoFactorConfiguration; diff --git a/src/types/config/types/subconfigs/security/twofactor.rs b/src/types/config/types/subconfigs/security/twofactor.rs new file mode 100644 index 0000000..39a0373 --- /dev/null +++ b/src/types/config/types/subconfigs/security/twofactor.rs @@ -0,0 +1,15 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct TwoFactorConfiguration { + pub generate_backup_codes: bool, +} + +impl Default for TwoFactorConfiguration { + fn default() -> Self { + Self { + generate_backup_codes: true, + } + } +} diff --git a/src/types/config/types/template_configuration.rs b/src/types/config/types/template_configuration.rs new file mode 100644 index 0000000..932670e --- /dev/null +++ b/src/types/config/types/template_configuration.rs @@ -0,0 +1,21 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct TemplateConfiguration { + pub enabled: bool, + pub allow_template_creation: bool, + pub allow_discord_templates: bool, + pub allow_raws: bool, +} + +impl Default for TemplateConfiguration { + fn default() -> Self { + Self { + enabled: true, + allow_template_creation: true, + allow_discord_templates: true, + allow_raws: true, + } + } +} diff --git a/src/types/entities/application.rs b/src/types/entities/application.rs new file mode 100644 index 0000000..2ab3f99 --- /dev/null +++ b/src/types/entities/application.rs @@ -0,0 +1,138 @@ +use crate::types::utils::Snowflake; +use bitflags::{bitflags, Flags}; +use serde::{Deserialize, Serialize}; +use serde_json::Value; +#[cfg(feature = "sqlx")] +use sqlx::FromRow; + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[cfg_attr(feature = "sqlx", derive(FromRow))] +pub struct Application { + pub id: Snowflake, + pub name: String, + pub icon: Option, + pub description: Option, + pub summary: Option, + #[cfg(feature = "sqlx")] + pub r#type: Option>, + #[cfg(not(feature = "sqlx"))] + pub r#type: Option, + pub hook: bool, + pub bot_public: bool, + pub bot_require_code_grant: bool, + pub verify_key: String, + pub owner_id: Snowflake, + pub flags: u64, + #[cfg(feature = "sqlx")] + pub redirect_uris: Option>>, + #[cfg(not(feature = "sqlx"))] + pub redirect_uris: Option>, + pub rpc_application_state: i64, + pub store_application_state: i64, + pub verification_state: i64, + pub interactions_endpoint_url: Option, + pub integration_public: bool, + pub integration_require_code_grant: bool, + pub discoverability_state: i64, + pub discovery_eligibility_flags: i64, + pub bot_user_id: Snowflake, + #[cfg(feature = "sqlx")] + pub tags: Option>>, + #[cfg(not(feature = "sqlx"))] + pub tags: Option>, + pub cover_image: Option, + #[cfg(feature = "sqlx")] + pub install_params: Option>, + #[cfg(not(feature = "sqlx"))] + pub install_params: Option, + pub terms_of_service_url: Option, + pub privacy_policy_url: Option, + pub team_id: Option, +} + +impl Application { + pub fn flags(&self) -> ApplicationFlags { + ApplicationFlags::from_bits(self.flags.to_owned()).unwrap() + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct InstallParams { + pub scopes: Vec, + pub permissions: String, +} + +bitflags! { + #[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)] + pub struct ApplicationFlags: u64 { + const APPLICATION_AUTO_MODERATION_RULE_CREATE_BADGE = 1 << 6; + const GATEWAY_PRESENCE = 1 << 12; + const GATEWAY_PRESENCE_LIMITED = 1 << 13; + const GATEWAY_GUILD_MEMBERS = 1 << 14; + const GATEWAY_GUILD_MEMBERS_LIMITED = 1 << 15; + const VERIFICATION_PENDING_GUILD_LIMIT = 1 << 16; + const EMBEDDED = 1 << 17; + const GATEWAY_MESSAGE_CONTENT = 1 << 18; + const GATEWAY_MESSAGE_CONTENT_LIMITED = 1 << 19; + const APPLICATION_COMMAND_BADGE = 1 << 23; + } +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct ApplicationCommand { + pub id: Snowflake, + pub application_id: Snowflake, + pub name: String, + pub description: String, + pub options: Vec, +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct ApplicationCommandOption { + pub r#type: ApplicationCommandOptionType, + pub name: String, + pub description: String, + pub required: bool, + pub choices: Vec, + pub options: Vec, +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct ApplicationCommandOptionChoice { + pub name: String, + pub value: Value, +} + +#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)] +pub enum ApplicationCommandOptionType { + #[serde(rename = "SUB_COMMAND")] + SubCommand = 1, + #[serde(rename = "SUB_COMMAND_GROUP")] + SubCommandGroup = 2, + #[serde(rename = "STRING")] + String = 3, + #[serde(rename = "INTEGER")] + Integer = 4, + #[serde(rename = "BOOLEAN")] + Boolean = 5, + #[serde(rename = "USER")] + User = 6, + #[serde(rename = "CHANNEL")] + Channel = 7, + #[serde(rename = "ROLE")] + Role = 8, +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct ApplicationCommandInteractionData { + pub id: Snowflake, + pub name: String, + pub options: Vec, +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct ApplicationCommandInteractionDataOption { + pub name: String, + pub value: Value, + pub options: Vec, +} diff --git a/src/types/entities/attachment.rs b/src/types/entities/attachment.rs new file mode 100644 index 0000000..c93ab8f --- /dev/null +++ b/src/types/entities/attachment.rs @@ -0,0 +1,113 @@ +use serde::{Deserialize, Serialize}; + +use crate::types::utils::Snowflake; + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct Attachment { + pub id: Snowflake, + pub filename: String, + pub description: Option, + pub content_type: Option, + pub size: u64, + pub url: String, + pub proxy_url: String, + pub height: Option, + pub width: Option, + pub message_id: Snowflake, + pub ephemeral: Option, + pub duration_secs: Option, + pub waveform: Option, + #[serde(skip_serializing)] + pub content: Vec, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] + +pub struct PartialDiscordFileAttachment { + pub id: Option, + pub filename: String, + pub description: Option, + pub content_type: Option, + pub size: Option, + pub url: Option, + pub proxy_url: Option, + pub height: Option, + pub width: Option, + pub ephemeral: Option, + pub duration_secs: Option, + pub waveform: Option, + #[serde(skip_serializing)] + pub content: Vec, +} + +impl PartialDiscordFileAttachment { + /** + Moves `self.content` out of `self` and returns it. + # Returns + Vec + */ + pub fn move_content(self) -> (Vec, PartialDiscordFileAttachment) { + let content = self.content; + let updated_struct = PartialDiscordFileAttachment { + id: self.id, + filename: self.filename, + description: self.description, + content_type: self.content_type, + size: self.size, + url: self.url, + proxy_url: self.proxy_url, + height: self.height, + width: self.width, + ephemeral: self.ephemeral, + duration_secs: self.duration_secs, + waveform: self.waveform, + content: Vec::new(), + }; + (content, updated_struct) + } + + pub fn move_filename(self) -> (String, PartialDiscordFileAttachment) { + let filename = self.filename; + let updated_struct = PartialDiscordFileAttachment { + id: self.id, + filename: String::new(), + description: self.description, + content_type: self.content_type, + size: self.size, + url: self.url, + proxy_url: self.proxy_url, + height: self.height, + width: self.width, + + ephemeral: self.ephemeral, + duration_secs: self.duration_secs, + waveform: self.waveform, + content: self.content, + }; + (filename, updated_struct) + } + + pub fn move_content_type(self) -> (Option, PartialDiscordFileAttachment) { + let content_type = self.content_type; + let updated_struct = PartialDiscordFileAttachment { + id: self.id, + filename: self.filename, + description: self.description, + content_type: None, + size: self.size, + url: self.url, + proxy_url: self.proxy_url, + height: self.height, + width: self.width, + ephemeral: self.ephemeral, + duration_secs: self.duration_secs, + waveform: self.waveform, + content: self.content, + }; + (content_type, updated_struct) + } + + pub fn set_id(&mut self, id: i16) { + self.id = Some(id); + } +} diff --git a/src/types/entities/channel.rs b/src/types/entities/channel.rs new file mode 100644 index 0000000..e408968 --- /dev/null +++ b/src/types/entities/channel.rs @@ -0,0 +1,117 @@ +use serde::{Deserialize, Serialize}; +use serde_repr::{Deserialize_repr, Serialize_repr}; + +use crate::types::{ + entities::{GuildMember, User}, + utils::Snowflake, +}; + +#[derive(Default, Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] +pub struct Channel { + pub id: Snowflake, + #[serde(rename = "type")] + pub channel_type: ChannelType, + pub guild_id: Option, + pub position: Option, + pub permission_overwrites: Option>, + pub name: Option, + pub topic: Option, + pub nsfw: Option, + pub last_message_id: Option, + pub bitrate: Option, + pub user_limit: Option, + pub rate_limit_per_user: Option, + pub recipients: Option>, + pub icon: Option, + pub owner_id: Option, + pub application_id: Option, + pub parent_id: Option, + pub last_pin_timestamp: Option, + pub rtc_region: Option, + pub video_quality_mode: Option, + pub message_count: Option, + pub member_count: Option, + pub thread_metadata: Option, + pub member: Option, + pub default_auto_archive_duration: Option, + pub permissions: Option, + pub flags: Option, + pub total_message_sent: Option, + pub available_tags: Option>, + pub applied_tags: Option>, + pub default_reaction_emoji: Option, + pub default_thread_rate_limit_per_user: Option, + pub default_sort_order: Option, + pub default_forum_layout: Option, +} + +#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)] +pub struct Tag { + pub id: u64, + pub name: String, + pub moderated: bool, + pub emoji_id: Option, + pub emoji_name: Option, +} + +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] +pub struct PermissionOverwrite { + pub id: String, + #[serde(rename = "type")] + pub overwrite_type: u8, + pub allow: String, + pub deny: String, +} + +#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)] +pub struct ThreadMetadata { + pub archived: bool, + pub auto_archive_duration: i32, + pub archive_timestamp: String, + pub locked: bool, + pub invitable: Option, + pub create_timestamp: Option, +} + +#[derive(Default, Debug, Deserialize, Serialize, Clone, PartialEq, Eq)] +pub struct ThreadMember { + pub id: Option, + pub user_id: Option, + pub join_timestamp: Option, + pub flags: Option, + pub member: Option, +} + +#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)] +pub struct DefaultReaction { + pub emoji_id: Option, + pub emoji_name: Option, +} + +#[derive(Default, Clone, Copy, Debug, Serialize_repr, Deserialize_repr, PartialEq, Eq)] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] +#[repr(i32)] +pub enum ChannelType { + #[default] + GuildText = 0, + Dm = 1, + GuildVoice = 2, + GroupDm = 3, + GuildCategory = 4, + GuildNews = 5, + GuildStore = 6, + Encrypted = 7, + EncryptedThreads = 8, + Transactional = 9, + GuildNewsThread = 10, + GuildPublicThread = 11, + GuildPrivateThread = 12, + GuildStageVoice = 13, + Directory = 14, + GuildForum = 15, + TicketTracker = 33, + Kanban = 34, + VoicelessWhiteboard = 35, + CustomStart = 64, + Unhandled = 255, +} diff --git a/src/types/entities/config.rs b/src/types/entities/config.rs new file mode 100644 index 0000000..25b1ef1 --- /dev/null +++ b/src/types/entities/config.rs @@ -0,0 +1,34 @@ +use serde::{Deserialize, Serialize}; +use serde_json::Value; +#[cfg(feature = "sqlx")] +use sqlx::FromRow; + +#[derive(Debug, Serialize, Deserialize)] +#[cfg_attr(feature = "sqlx", derive(FromRow))] +pub struct ConfigEntity { + pub key: String, + pub value: Option, +} + +impl ConfigEntity { + pub fn as_string(&self) -> Option { + let Some(v) = self.value.as_ref() else { + return None; + }; + Some(v.as_str().expect("value is not a string").to_string()) + } + + pub fn as_bool(&self) -> Option { + let Some(v) = self.value.as_ref() else { + return None; + }; + Some(v.as_bool().expect("value is not a boolean")) + } + + pub fn as_int(&self) -> Option { + let Some(v) = self.value.as_ref() else { + return None; + }; + Some(v.as_i64().expect("value is not a number")) + } +} diff --git a/src/types/entities/emoji.rs b/src/types/entities/emoji.rs new file mode 100644 index 0000000..cf56f1f --- /dev/null +++ b/src/types/entities/emoji.rs @@ -0,0 +1,15 @@ +use serde::{Deserialize, Serialize}; + +use crate::types::entities::User; + +#[derive(Debug, Deserialize, Serialize, Default, Clone)] +pub struct Emoji { + pub id: Option, + pub name: Option, + pub roles: Option>, + pub user: Option, + pub require_colons: Option, + pub managed: Option, + pub animated: Option, + pub available: Option, +} diff --git a/src/types/entities/guild.rs b/src/types/entities/guild.rs new file mode 100644 index 0000000..b2da5c1 --- /dev/null +++ b/src/types/entities/guild.rs @@ -0,0 +1,117 @@ +use chrono::{DateTime, Utc}; +use serde::{Deserialize, Serialize}; + +use crate::types::{ + entities::{Channel, Emoji, GuildTemplate, RoleObject, Sticker, User, VoiceState, Webhook}, + interfaces::WelcomeScreenObject, + utils::Snowflake, +}; + +/// See https://discord.com/developers/docs/resources/guild +#[derive(Serialize, Deserialize, Debug, Default, Clone)] +pub struct Guild { + pub id: Snowflake, + pub name: String, + pub icon: Option, + pub icon_hash: Option, + pub splash: Option, + pub discovery_splash: Option, + pub owner: Option, + pub owner_id: Option, + pub permissions: Option, + pub afk_channel_id: Option, + pub afk_timeout: Option, + pub widget_enabled: Option, + pub widget_channel_id: Option, + pub widget_channel: Option, + pub verification_level: Option, + pub default_message_notifications: Option, + pub explicit_content_filter: Option, + pub roles: Vec, + pub emojis: Vec, + pub features: Vec, + pub application_id: Option, + pub system_channel_id: Option, + pub system_channel_flags: Option, + pub rules_channel_id: Option, + pub rules_channel: Option, + pub max_presences: Option, + pub max_members: Option, + pub vanity_url_code: Option, + pub description: Option, + pub banner: Option, + pub premium_tier: Option, + pub premium_subscription_count: Option, + pub preferred_locale: Option, + pub public_updates_channel_id: Option, + pub public_updates_channel: Option, + pub max_video_channel_users: Option, + pub max_stage_video_channel_users: Option, + pub approximate_member_count: Option, + pub approximate_presence_count: Option, + pub member_count: Option, + pub presence_count: Option, + pub welcome_screen: Option, + pub nsfw_level: u8, + pub nsfw: bool, + pub stickers: Option>, + pub premium_progress_bar_enabled: Option, + pub joined_at: String, + pub afk_channel: Option, + pub bans: Option>, + pub primary_category_id: Option, + pub large: Option, + pub channels: Option>, + pub template_id: Option, + pub template: Option, + pub invites: Option>, + pub voice_states: Option>, + pub webhooks: Option>, + pub mfa_level: Option, + pub region: Option, + pub unavailable: bool, + pub parent: Option, +} + +/// See https://docs.spacebar.chat/routes/#get-/guilds/-guild_id-/bans/-user- +#[derive(Serialize, Deserialize, Debug, Default, Clone)] +pub struct GuildBan { + pub id: Snowflake, + pub user_id: Snowflake, + pub guild_id: Snowflake, + pub executor_id: Snowflake, + pub reason: Option, +} + +/// See https://docs.spacebar.chat/routes/#cmp--schemas-invite +#[derive(Serialize, Deserialize, Debug, Default, Clone)] +pub struct GuildInvite { + pub code: String, + pub temporary: Option, + pub uses: Option, + pub max_uses: Option, + pub max_age: Option, + pub created_at: DateTime, + pub expires_at: Option>, + pub guild_id: String, + pub guild: Option, + pub channel_id: String, + pub channel: Option, + pub inviter_id: Option, + pub inviter: Option, + pub target_user_id: Option, + pub target_user: Option, + pub target_user_type: Option, + pub vanity_url: Option, +} + +#[derive(Serialize, Deserialize, Debug, Default)] +pub struct UnavailableGuild { + id: String, + unavailable: bool, +} + +#[derive(Serialize, Deserialize, Debug, Default, Clone)] +pub struct GuildCreateResponse { + pub id: String, +} diff --git a/src/types/entities/guild_member.rs b/src/types/entities/guild_member.rs new file mode 100644 index 0000000..50d75e4 --- /dev/null +++ b/src/types/entities/guild_member.rs @@ -0,0 +1,19 @@ +use serde::{Deserialize, Serialize}; + +use crate::types::entities::User; + +#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)] +pub struct GuildMember { + pub user: Option, + pub nick: Option, + pub avatar: Option, + pub roles: Vec, + pub joined_at: String, + pub premium_since: Option, + pub deaf: bool, + pub mute: bool, + pub flags: i32, + pub pending: Option, + pub permissions: Option, + pub communication_disabled_until: Option, +} diff --git a/src/types/entities/integration.rs b/src/types/entities/integration.rs new file mode 100644 index 0000000..d4ed911 --- /dev/null +++ b/src/types/entities/integration.rs @@ -0,0 +1,36 @@ +use chrono::{DateTime, Utc}; +use serde::{Deserialize, Serialize}; + +use crate::types::{ + entities::{Application, User}, + utils::Snowflake, +}; + +#[derive(Default, Debug, Deserialize, Serialize, Clone)] +/// See https://discord.com/developers/docs/resources/guild#integration-object-integration-structure +pub struct Integration { + pub id: Snowflake, + pub name: String, + #[serde(rename = "type")] + pub integration_type: String, + pub enabled: bool, + pub syncing: Option, + pub role_id: Option, + pub enabled_emoticons: Option, + pub expire_behaviour: Option, + pub expire_grace_period: Option, + pub user: Option, + pub account: IntegrationAccount, + pub synced_at: Option>, + pub subscriber_count: Option, + pub revoked: Option, + pub application: Option, + pub scopes: Option>, +} + +#[derive(Default, Debug, Deserialize, Serialize, Clone)] +/// See https://discord.com/developers/docs/resources/guild#integration-account-object-integration-account-structure +pub struct IntegrationAccount { + pub id: String, + pub name: String, +} diff --git a/src/types/entities/message.rs b/src/types/entities/message.rs new file mode 100644 index 0000000..e384e61 --- /dev/null +++ b/src/types/entities/message.rs @@ -0,0 +1,185 @@ +use serde::{Deserialize, Serialize}; + +use crate::types::{ + entities::{ + Application, Attachment, Channel, Emoji, GuildMember, RoleSubscriptionData, Sticker, + StickerItem, User, + }, + utils::Snowflake, +}; + +#[derive(Debug, Serialize, Deserialize, Default)] +pub struct Message { + id: Snowflake, + pub channel_id: Snowflake, + author: User, + content: String, + timestamp: String, + edited_timestamp: Option, + tts: bool, + mention_everyone: bool, + mentions: Vec, + mention_roles: Vec, + mention_channels: Option>, + pub attachments: Vec, + embeds: Vec, + reactions: Option>, + nonce: Option, + pinned: bool, + webhook_id: Option, + #[serde(rename = "type")] + message_type: i32, + activity: Option, + application: Option, + application_id: Option, + message_reference: Option, + flags: Option, + referenced_message: Option>, + interaction: Option, + thread: Option, + components: Option>, + sticker_items: Option>, + stickers: Option>, + position: Option, + role_subscription_data: Option, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct MessageReference { + pub message_id: Snowflake, + pub channel_id: Snowflake, + pub guild_id: Option, + pub fail_if_not_exists: Option, +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct MessageInteraction { + pub id: Snowflake, + #[serde(rename = "type")] + pub interaction_type: u8, + pub name: String, + pub user: User, + pub member: Option, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct AllowedMention { + parse: Vec, + roles: Vec, + users: Vec, + replied_user: bool, +} + +#[derive(Debug, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +pub enum AllowedMentionType { + Roles, + Users, + Everyone, +} + +#[derive(Debug, Serialize, Deserialize)] +struct ChannelMention { + id: Snowflake, + guild_id: Snowflake, + #[serde(rename = "type")] + channel_type: i32, + name: String, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct Embed { + title: Option, + #[serde(rename = "type")] + embed_type: Option, + description: Option, + url: Option, + timestamp: Option, + color: Option, + footer: Option, + image: Option, + thumbnail: Option, + video: Option, + provider: Option, + author: Option, + fields: Option>, +} + +#[derive(Debug, Serialize, Deserialize)] +struct EmbedFooter { + text: String, + icon_url: Option, + proxy_icon_url: Option, +} + +#[derive(Debug, Serialize, Deserialize)] +struct EmbedImage { + url: String, + proxy_url: String, + height: Option, + width: Option, +} + +#[derive(Debug, Serialize, Deserialize)] +struct EmbedThumbnail { + url: String, + proxy_url: Option, + height: Option, + width: Option, +} + +#[derive(Debug, Serialize, Deserialize)] +struct EmbedVideo { + url: Option, + proxy_url: Option, + height: Option, + width: Option, +} + +#[derive(Debug, Serialize, Deserialize)] +struct EmbedProvider { + name: Option, + url: Option, +} + +#[derive(Debug, Serialize, Deserialize)] +struct EmbedAuthor { + name: String, + url: Option, + icon_url: Option, + proxy_icon_url: Option, +} + +#[derive(Debug, Serialize, Deserialize)] + +struct EmbedField { + name: String, + value: String, + inline: Option, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct Reaction { + pub count: i32, + pub me: bool, + pub emoji: Emoji, +} + +#[derive(Debug, Serialize, Deserialize)] +pub enum Component { + ActionRow = 1, + Button = 2, + StringSelect = 3, + TextInput = 4, + UserSelect = 5, + RoleSelect = 6, + MentionableSelect = 7, + ChannelSelect = 8, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct MessageActivity { + #[serde(rename = "type")] + pub activity_type: i64, + pub party_id: Option, +} diff --git a/src/types/entities/mod.rs b/src/types/entities/mod.rs new file mode 100644 index 0000000..006aac1 --- /dev/null +++ b/src/types/entities/mod.rs @@ -0,0 +1,37 @@ +mod application; +mod attachment; +mod channel; +mod config; +mod emoji; +mod guild; +mod guild_member; +mod integration; +mod message; +mod role; +mod security_key; +mod sticker; +mod team; +mod template; +mod user; +mod user_settings; +mod voice_state; +mod webhook; + +pub use application::*; +pub use attachment::*; +pub use channel::*; +pub use config::*; +pub use emoji::*; +pub use guild::*; +pub use guild_member::*; +pub use integration::*; +pub use message::*; +pub use role::*; +pub use security_key::*; +pub use sticker::*; +pub use team::*; +pub use template::*; +pub use user::*; +pub use user_settings::*; +pub use voice_state::*; +pub use webhook::*; diff --git a/src/types/entities/role.rs b/src/types/entities/role.rs new file mode 100644 index 0000000..708be00 --- /dev/null +++ b/src/types/entities/role.rs @@ -0,0 +1,28 @@ +use serde::{Deserialize, Serialize}; + +use crate::types::utils::Snowflake; + +#[derive(Serialize, Deserialize, Debug, Default, Clone)] +/// See https://discord.com/developers/docs/topics/permissions#role-object +pub struct RoleObject { + pub id: Snowflake, + pub name: String, + pub color: f64, + pub hoist: bool, + pub icon: Option, + pub unicode_emoji: Option, + pub position: u16, + pub permissions: String, + pub managed: bool, + pub mentionable: bool, + // to:do add role tags https://discord.com/developers/docs/topics/permissions#role-object-role-tags-structure + //pub tags: Option +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct RoleSubscriptionData { + pub role_subscription_listing_id: Snowflake, + pub tier_name: String, + pub total_months_subscribed: u32, + pub is_renewal: bool, +} diff --git a/src/types/entities/security_key.rs b/src/types/entities/security_key.rs new file mode 100644 index 0000000..09112cc --- /dev/null +++ b/src/types/entities/security_key.rs @@ -0,0 +1,26 @@ +use serde::{Deserialize, Serialize}; + +use crate::types::utils::Snowflake; + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct SecurityKey { + pub id: String, + pub user_id: String, + pub key_id: String, + pub public_key: String, + pub counter: u64, + pub name: String, +} + +impl Default for SecurityKey { + fn default() -> Self { + Self { + id: Snowflake::generate().to_string(), + user_id: String::new(), + key_id: String::new(), + public_key: String::new(), + counter: 0, + name: String::new(), + } + } +} diff --git a/src/types/entities/sticker.rs b/src/types/entities/sticker.rs new file mode 100644 index 0000000..d8f2803 --- /dev/null +++ b/src/types/entities/sticker.rs @@ -0,0 +1,27 @@ +use serde::{Deserialize, Serialize}; + +use crate::types::{entities::User, utils::Snowflake}; + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct Sticker { + pub id: Snowflake, + pub pack_id: Option, + pub name: String, + pub description: Option, + pub tags: String, + pub asset: Option, + #[serde(rename = "type")] + pub sticker_type: u8, + pub format_type: u8, + pub available: Option, + pub guild_id: Option, + pub user: Option, + pub sort_value: Option, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct StickerItem { + pub id: Snowflake, + pub name: String, + pub format_type: u8, +} diff --git a/src/types/entities/team.rs b/src/types/entities/team.rs new file mode 100644 index 0000000..7c381a6 --- /dev/null +++ b/src/types/entities/team.rs @@ -0,0 +1,20 @@ +use serde::{Deserialize, Serialize}; + +use crate::types::entities::User; + +#[derive(Debug, Deserialize, Serialize, Clone)] +pub struct Team { + pub icon: Option, + pub id: u64, + pub members: Vec, + pub name: String, + pub owner_user_id: u64, +} + +#[derive(Debug, Deserialize, Serialize, Clone)] +pub struct TeamMember { + pub membership_state: u8, + pub permissions: Vec, + pub team_id: u64, + pub user: User, +} diff --git a/src/types/entities/template.rs b/src/types/entities/template.rs new file mode 100644 index 0000000..dfbd98a --- /dev/null +++ b/src/types/entities/template.rs @@ -0,0 +1,24 @@ +use chrono::{DateTime, Utc}; +use serde::{Deserialize, Serialize}; + +use crate::types::{ + entities::{Guild, User}, + utils::Snowflake, +}; + +/// See https://docs.spacebar.chat/routes/#cmp--schemas-template +#[derive(Serialize, Deserialize, Debug, Default, Clone)] +pub struct GuildTemplate { + pub code: String, + pub name: String, + pub description: Option, + pub usage_count: Option, + pub creator_id: Snowflake, + pub creator: User, + pub created_at: DateTime, + pub updated_at: DateTime, + pub source_guild_id: String, + pub source_guild: Vec, // Unsure how a {recursive: Guild} looks like, might be a Vec? + pub serialized_source_guild: Vec, + id: Snowflake, +} diff --git a/src/types/entities/user.rs b/src/types/entities/user.rs new file mode 100644 index 0000000..1c8b102 --- /dev/null +++ b/src/types/entities/user.rs @@ -0,0 +1,118 @@ +use chrono::{DateTime, Utc}; +use serde::{Deserialize, Serialize}; +use serde_json::{Map, Value}; +#[cfg(feature = "sqlx")] +use sqlx::{FromRow, Type}; + +use crate::types::{ + errors::Error, + utils::Snowflake, //util::{email::adjust_email, entities::user_setting::UserSettings}, +}; + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)] +#[cfg_attr(feature = "sqlx", derive(Type))] +pub struct UserData { + pub valid_tokens_since: DateTime, + pub hash: Option, +} + +impl User { + pub fn to_public_user(self) -> PublicUser { + PublicUser::from(self) + } +} + +#[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq, Eq)] +pub struct User { + pub id: Snowflake, + username: String, + discriminator: String, + avatar: Option, + bot: bool, + system: Option, + mfa_enabled: Option, + accent_color: Option, + locale: Option, + verified: Option, + email: Option, + flags: String, + premium_since: Option>, + premium_type: u8, + pronouns: Option, + public_flags: Option, + banner: Option, + bio: String, + theme_colors: Option>, + phone: Option, + nsfw_allowed: bool, + premium: bool, + purchased_flags: i32, + premium_usage_flags: i32, + disabled: Option, +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct PublicUser { + pub id: Snowflake, + pub username: String, + pub discriminator: String, + pub avatar: Option, + pub accent_color: Option, + pub banner: Option, + pub theme_colors: Option>, + pub pronouns: Option, + pub bot: bool, + pub bio: String, + pub premium_type: u8, + pub premium_since: Option>, + pub public_flags: Option, +} + +impl From for PublicUser { + fn from(value: User) -> Self { + Self { + id: value.id, + username: value.username, + discriminator: value.discriminator, + avatar: value.avatar, + accent_color: value.accent_color, + banner: value.banner, + theme_colors: value.theme_colors, + pronouns: value.pronouns, + bot: value.bot, + bio: value.bio, + premium_type: value.premium_type, + premium_since: value.premium_since, + public_flags: value.public_flags, + } + } +} + +const CUSTOM_USER_FLAG_OFFSET: u64 = 1 << 32; + +bitflags::bitflags! { + #[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)] + #[cfg_attr(feature = "sqlx", derive(Type))] + pub struct UserFlags: u64 { + const DISCORD_EMPLOYEE = 1 << 0; + const PARTNERED_SERVER_OWNER = 1 << 1; + const HYPESQUAD_EVENTS = 1 << 2; + const BUGHUNTER_LEVEL_1 =1 << 3; + const MFA_SMS = 1 << 4; + const PREMIUM_PROMO_DISMISSED = 1 << 5; + const HOUSE_BRAVERY = 1 << 6; + const HOUSE_BRILLIANCE = 1 << 7; + const HOUSE_BALANCE = 1 << 8; + const EARLY_SUPPORTER = 1 << 9; + const TEAM_USER = 1 << 10; + const TRUST_AND_SAFETY = 1 << 11; + const SYSTEM = 1 << 12; + const HAS_UNREAD_URGENT_MESSAGES = 1 << 13; + const BUGHUNTER_LEVEL_2 = 1 << 14; + const UNDERAGE_DELETED = 1 << 15; + const VERIFIED_BOT = 1 << 16; + const EARLY_VERIFIED_BOT_DEVELOPER = 1 << 17; + const CERTIFIED_MODERATOR = 1 << 18; + const BOT_HTTP_INTERACTIONS = 1 << 19; + } +} diff --git a/src/types/entities/user_settings.rs b/src/types/entities/user_settings.rs new file mode 100644 index 0000000..6f3ac53 --- /dev/null +++ b/src/types/entities/user_settings.rs @@ -0,0 +1,130 @@ +use chrono::{serde::ts_milliseconds_option, Utc}; +use serde::{Deserialize, Serialize}; + +use crate::types::utils::Snowflake; + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)] +#[serde(rename_all = "lowercase")] +pub enum UserStatus { + #[default] + Online, + Offline, + Dnd, + Idle, + Invisible, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)] +#[serde(rename_all = "lowercase")] +pub enum UserTheme { + #[default] + Dark, + Light, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct UserSettings { + pub afk_timeout: u16, + pub allow_accessibility_detection: bool, + pub animate_emoji: bool, + pub animate_stickers: u8, + pub contact_sync_enabled: bool, + pub convert_emoticons: bool, + pub custom_status: Option, + pub default_guilds_restricted: bool, + pub detect_platform_accounts: bool, + pub developer_mode: bool, + pub disable_games_tab: bool, + pub enable_tts_command: bool, + pub explicit_content_filter: u8, + pub friend_source_flags: FriendSourceFlags, + pub gateway_connected: bool, + pub gif_auto_play: bool, + pub guild_folders: Vec, + pub guild_positions: Vec, + pub inline_attachment_media: bool, + pub inline_embed_media: bool, + pub locale: String, + pub message_display_compact: bool, + pub native_phone_integration_enabled: bool, + pub render_embeds: bool, + pub render_reactions: bool, + pub restricted_guilds: Vec, + pub show_current_game: bool, + pub status: UserStatus, + pub stream_notifications_enabled: bool, + pub theme: UserTheme, + pub timezone_offset: i16, +} + +impl Default for UserSettings { + fn default() -> Self { + Self { + afk_timeout: 3600, + allow_accessibility_detection: true, + animate_emoji: true, + animate_stickers: 0, + contact_sync_enabled: false, + convert_emoticons: false, + custom_status: None, + default_guilds_restricted: false, + detect_platform_accounts: false, + developer_mode: true, + disable_games_tab: true, + enable_tts_command: false, + explicit_content_filter: 0, + friend_source_flags: FriendSourceFlags::default(), + gateway_connected: false, + gif_auto_play: false, + guild_folders: Vec::new(), + guild_positions: Vec::new(), + inline_attachment_media: true, + inline_embed_media: true, + locale: "en-US".to_string(), + message_display_compact: false, + native_phone_integration_enabled: true, + render_embeds: true, + render_reactions: true, + restricted_guilds: Vec::new(), + show_current_game: true, + status: UserStatus::Online, + stream_notifications_enabled: false, + theme: UserTheme::Dark, + timezone_offset: 0, + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct CustomStatus { + pub emoji_id: Option, + pub emoji_name: Option, + #[serde(with = "ts_milliseconds_option")] + pub expires_at: Option>, + pub text: Option, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct FriendSourceFlags { + pub all: bool, +} + +impl Default for FriendSourceFlags { + fn default() -> Self { + Self { all: true } + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct GuildFolder { + pub color: u32, + pub guild_ids: Vec, + pub id: u16, + pub name: String, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct LoginResult { + pub token: String, + pub settings: UserSettings, +} diff --git a/src/types/entities/voice_state.rs b/src/types/entities/voice_state.rs new file mode 100644 index 0000000..68e2051 --- /dev/null +++ b/src/types/entities/voice_state.rs @@ -0,0 +1,30 @@ +use chrono::{DateTime, Utc}; +use serde::{Deserialize, Serialize}; + +use crate::types::{ + entities::{Channel, Guild, GuildMember, User}, + utils::Snowflake, +}; + +/// See https://docs.spacebar.chat/routes/#cmp--schemas-voicestate +#[derive(Serialize, Deserialize, Debug, Default, Clone)] +pub struct VoiceState { + pub guild_id: Snowflake, + pub guild: Option, + pub channel_id: Snowflake, + pub channel: Option, + pub user_id: Snowflake, + pub user: Option, + pub member: Option, + pub session_id: Snowflake, + pub token: String, + pub deaf: bool, + pub mute: bool, + pub self_deaf: bool, + pub self_mute: bool, + pub self_stream: Option, + pub self_video: bool, + pub suppress: bool, + pub request_to_speak_timestamp: Option>, + pub id: Snowflake, +} diff --git a/src/types/entities/webhook.rs b/src/types/entities/webhook.rs new file mode 100644 index 0000000..26fa81f --- /dev/null +++ b/src/types/entities/webhook.rs @@ -0,0 +1,32 @@ +use serde::{Deserialize, Serialize}; + +use crate::types::{ + entities::{Application, Channel, Guild, User}, + utils::Snowflake, +}; + +/// See https://docs.spacebar.chat/routes/#cmp--schemas-webhook +#[derive(Serialize, Deserialize, Debug, Default, Clone)] +pub struct Webhook { + #[serde(rename = "type")] + pub webhook_type: i32, + pub name: String, + pub avatar: String, + pub token: String, + pub guild_id: Snowflake, + #[serde(skip_serializing_if = "Option::is_none")] + pub guild: Option, + pub channel_id: Snowflake, + #[serde(skip_serializing_if = "Option::is_none")] + pub channel: Option, + pub application_id: Snowflake, + #[serde(skip_serializing_if = "Option::is_none")] + pub application: Option, + pub user_id: Snowflake, + #[serde(skip_serializing_if = "Option::is_none")] + pub user: Option, + pub source_guild_id: Snowflake, + #[serde(skip_serializing_if = "Option::is_none")] + pub source_guild: Option, + pub id: Snowflake, +} diff --git a/src/types/errors.rs b/src/types/errors.rs new file mode 100644 index 0000000..63f6ceb --- /dev/null +++ b/src/types/errors.rs @@ -0,0 +1,54 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[cfg(feature = "sqlx")] + #[error("SQLX error: {0}")] + SQLX(#[from] sqlx::Error), + + #[error("serde: {0}")] + Serde(#[from] serde_json::Error), + + #[error(transparent)] + IO(#[from] std::io::Error), + + #[error(transparent)] + FieldFormat(#[from] FieldFormatError), +} + +#[derive(Debug, PartialEq, Eq, thiserror::Error)] +pub enum FieldFormatError { + #[error("Password must be between 1 and 72 characters.")] + PasswordError, + #[error("Username must be between 2 and 32 characters.")] + UsernameError, + #[error("Consent must be 'true' to register.")] + ConsentError, + #[error("The provided email address is in an invalid format.")] + EmailError, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct ErrorResponse { + pub code: i32, + pub message: String, + pub errors: IntermittentError, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct IntermittentError { + #[serde(flatten)] + pub errors: std::collections::HashMap, +} + +#[derive(Serialize, Deserialize, Debug, Default)] +pub struct ErrorField { + #[serde(default)] + pub _errors: Vec, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct APIError { + pub message: String, + pub code: String, +} diff --git a/src/types/events/channel.rs b/src/types/events/channel.rs new file mode 100644 index 0000000..a5db906 --- /dev/null +++ b/src/types/events/channel.rs @@ -0,0 +1,41 @@ +use crate::types::entities::Channel; +use crate::types::events::WebSocketEvent; +use chrono::{DateTime, Utc}; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Default, Deserialize, Serialize)] +/// See https://discord.com/developers/docs/topics/gateway-events#channel-pins-update +pub struct ChannelPinsUpdate { + pub guild_id: Option, + pub channel_id: String, + pub last_pin_timestamp: Option>, +} + +impl WebSocketEvent for ChannelPinsUpdate {} + +#[derive(Debug, Default, Deserialize, Serialize)] +/// See https://discord.com/developers/docs/topics/gateway-events#channel-create +/// Not directly serialized, as the inner payload is a channel object +pub struct ChannelCreate { + pub channel: Channel, +} + +impl WebSocketEvent for ChannelCreate {} + +#[derive(Debug, Default, Deserialize, Serialize)] +/// See https://discord.com/developers/docs/topics/gateway-events#channel-update +/// Not directly serialized, as the inner payload is a channel object +pub struct ChannelUpdate { + pub channel: Channel, +} + +impl WebSocketEvent for ChannelUpdate {} + +#[derive(Debug, Default, Deserialize, Serialize)] +/// See https://discord.com/developers/docs/topics/gateway-events#channel-delete +/// Not directly serialized, as the inner payload is a channel object +pub struct ChannelDelete { + pub channel: Channel, +} + +impl WebSocketEvent for ChannelDelete {} diff --git a/src/types/events/guild.rs b/src/types/events/guild.rs new file mode 100644 index 0000000..840fb8e --- /dev/null +++ b/src/types/events/guild.rs @@ -0,0 +1,41 @@ +use crate::types::entities::{Guild, UnavailableGuild, User}; +use crate::types::events::WebSocketEvent; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Deserialize, Serialize, Default)] +/// See https://discord.com/developers/docs/topics/gateway-events#guild-create +/// This one is particularly painful, it can be a Guild object with extra field or an unavailbile guild object +pub struct GuildCreate { + pub d: GuildCreateDataOption, +} + +#[derive(Debug, Deserialize, Serialize)] +pub enum GuildCreateDataOption { + UnavailableGuild(UnavailableGuild), + Guild(Guild), +} + +impl Default for GuildCreateDataOption { + fn default() -> Self { + GuildCreateDataOption::UnavailableGuild(UnavailableGuild::default()) + } +} +impl WebSocketEvent for GuildCreate {} + +#[derive(Debug, Default, Deserialize, Serialize)] +/// See https://discord.com/developers/docs/topics/gateway-events#guild-ban-add-guild-ban-add-event-fields +pub struct GuildBanAdd { + pub guild_id: String, + pub user: User, +} + +impl WebSocketEvent for GuildBanAdd {} + +#[derive(Debug, Default, Deserialize, Serialize)] +/// See https://discord.com/developers/docs/topics/gateway-events#guild-ban-remove +pub struct GuildBanRemove { + pub guild_id: String, + pub user: User, +} + +impl WebSocketEvent for GuildBanRemove {} diff --git a/src/types/events/heartbeat.rs b/src/types/events/heartbeat.rs new file mode 100644 index 0000000..be9e3f8 --- /dev/null +++ b/src/types/events/heartbeat.rs @@ -0,0 +1,17 @@ +use crate::types::events::WebSocketEvent; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Default, Deserialize, Serialize)] +pub struct GatewayHeartbeat { + pub op: u8, + pub d: Option, +} + +impl WebSocketEvent for GatewayHeartbeat {} + +#[derive(Debug, Default, Deserialize, Serialize)] +pub struct GatewayHeartbeatAck { + pub op: i32, +} + +impl WebSocketEvent for GatewayHeartbeatAck {} diff --git a/src/types/events/hello.rs b/src/types/events/hello.rs new file mode 100644 index 0000000..214f211 --- /dev/null +++ b/src/types/events/hello.rs @@ -0,0 +1,17 @@ +use crate::types::events::WebSocketEvent; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Default, Deserialize, Serialize)] +pub struct GatewayHello { + pub op: i32, + pub d: HelloData, +} + +impl WebSocketEvent for GatewayHello {} + +#[derive(Debug, Default, Deserialize, Serialize)] +pub struct HelloData { + pub heartbeat_interval: u128, +} + +impl WebSocketEvent for HelloData {} diff --git a/src/types/events/identify.rs b/src/types/events/identify.rs new file mode 100644 index 0000000..b9cacee --- /dev/null +++ b/src/types/events/identify.rs @@ -0,0 +1,22 @@ +use crate::types::events::{PresenceUpdate, WebSocketEvent}; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Deserialize, Serialize, Default)] +pub struct GatewayIdentifyPayload { + pub token: String, + pub properties: GatewayIdentifyConnectionProps, + pub compress: Option, + pub large_threshold: Option, //default: 50 + pub shard: Option>, + pub presence: Option, + pub intents: i32, +} + +impl WebSocketEvent for GatewayIdentifyPayload {} + +#[derive(Debug, Deserialize, Serialize, Default)] +pub struct GatewayIdentifyConnectionProps { + pub os: String, + pub browser: String, + pub device: String, +} diff --git a/src/types/events/message.rs b/src/types/events/message.rs new file mode 100644 index 0000000..8cd6429 --- /dev/null +++ b/src/types/events/message.rs @@ -0,0 +1,101 @@ +use serde::{Deserialize, Serialize}; + +use crate::types::{ + entities::{Emoji, GuildMember, Message, User}, + utils::Snowflake, +}; + +use super::WebSocketEvent; + +#[derive(Debug, Deserialize, Serialize, Default)] +pub struct TypingStartEvent { + pub channel_id: String, + pub guild_id: Option, + pub user_id: String, + pub timestamp: i64, + pub member: Option, +} + +impl WebSocketEvent for TypingStartEvent {} + +#[derive(Debug, Serialize, Deserialize, Default)] +pub struct MessageCreate { + #[serde(flatten)] + message: Message, + guild_id: Option, + member: Option, + mentions: Vec<(User, GuildMember)>, // Not sure if this is correct: https://discord.com/developers/docs/topics/gateway-events#message-create +} + +impl WebSocketEvent for MessageCreate {} + +#[derive(Debug, Serialize, Deserialize, Default)] +pub struct MessageUpdate { + #[serde(flatten)] + message: Message, + guild_id: Option, + member: Option, + mentions: Vec<(User, GuildMember)>, // Not sure if this is correct: https://discord.com/developers/docs/topics/gateway-events#message-create +} + +impl WebSocketEvent for MessageUpdate {} + +#[derive(Debug, Serialize, Deserialize, Default)] +pub struct MessageDelete { + id: String, + channel_id: String, + guild_id: Option, +} + +impl WebSocketEvent for MessageDelete {} + +#[derive(Debug, Serialize, Deserialize, Default)] +pub struct MessageDeleteBulk { + ids: Vec, + channel_id: String, + guild_id: Option, +} + +impl WebSocketEvent for MessageDeleteBulk {} + +#[derive(Debug, Serialize, Deserialize, Default)] +pub struct MessageReactionAdd { + user_id: String, + channel_id: String, + message_id: String, + guild_id: Option, + member: Option, + emoji: Emoji, +} + +impl WebSocketEvent for MessageReactionAdd {} + +#[derive(Debug, Serialize, Deserialize, Default)] +pub struct MessageReactionRemove { + user_id: String, + channel_id: String, + message_id: String, + guild_id: Option, + emoji: Emoji, +} + +impl WebSocketEvent for MessageReactionRemove {} + +#[derive(Debug, Serialize, Deserialize, Default)] +pub struct MessageReactionRemoveAll { + channel_id: String, + message_id: String, + guild_id: Option, +} + +impl WebSocketEvent for MessageReactionRemoveAll {} + +#[derive(Debug, Serialize, Deserialize, Default)] +pub struct MessageReactionRemoveEmoji { + channel_id: String, + message_id: String, + guild_id: Option, + emoji: Emoji, +} + +impl WebSocketEvent for MessageReactionRemoveEmoji {} diff --git a/src/types/events/mod.rs b/src/types/events/mod.rs new file mode 100644 index 0000000..418cd31 --- /dev/null +++ b/src/types/events/mod.rs @@ -0,0 +1,41 @@ +use serde::{Deserialize, Serialize}; + +mod channel; +mod guild; +mod heartbeat; +mod hello; +mod identify; +mod message; +mod presence; +mod ready; +mod request_members; +mod resume; +mod thread; +mod user; +mod voice_status; + +pub use channel::*; +pub use guild::*; +pub use heartbeat::*; +pub use hello::*; +pub use identify::*; +pub use message::*; +pub use presence::*; +pub use ready::*; +pub use request_members::*; +pub use resume::*; +pub use thread::*; +pub use user::*; +pub use voice_status::*; + +pub trait WebSocketEvent {} + +#[derive(Debug, Default, Deserialize, Serialize)] +pub struct GatewayPayload { + pub op: u8, + pub d: Option, + pub s: Option, + pub t: Option, +} + +impl WebSocketEvent for GatewayPayload {} diff --git a/src/types/events/presence.rs b/src/types/events/presence.rs new file mode 100644 index 0000000..f8fea4c --- /dev/null +++ b/src/types/events/presence.rs @@ -0,0 +1,24 @@ +use crate::types::entities::User; +use crate::types::events::WebSocketEvent; +use crate::types::interfaces::Activity; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Deserialize, Serialize, Default, Clone)] +/// See https://discord.com/developers/docs/topics/gateway-events#presence-update-presence-update-event-fields +pub struct PresenceUpdate { + pub user: User, + pub guild_id: String, + pub status: String, + pub activities: Vec, + pub client_status: ClientStatusObject, +} + +#[derive(Debug, Deserialize, Serialize, Default, Clone)] +/// See https://discord.com/developers/docs/topics/gateway-events#client-status-object +pub struct ClientStatusObject { + pub desktop: Option, + pub mobile: Option, + pub web: Option, +} + +impl WebSocketEvent for PresenceUpdate {} diff --git a/src/types/events/ready.rs b/src/types/events/ready.rs new file mode 100644 index 0000000..ff933c8 --- /dev/null +++ b/src/types/events/ready.rs @@ -0,0 +1,15 @@ +use crate::types::entities::{UnavailableGuild, User}; +use crate::types::events::WebSocketEvent; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Deserialize, Serialize, Default)] +pub struct GatewayReady { + pub v: u8, + pub user: User, + pub guilds: Vec, + pub session_id: String, + pub resume_gateway_url: Option, + pub shard: Option<(u64, u64)>, +} + +impl WebSocketEvent for GatewayReady {} diff --git a/src/types/events/request_members.rs b/src/types/events/request_members.rs new file mode 100644 index 0000000..baf6d4a --- /dev/null +++ b/src/types/events/request_members.rs @@ -0,0 +1,15 @@ +use crate::types::events::WebSocketEvent; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Deserialize, Serialize, Default)] +/// See https://discord.com/developers/docs/topics/gateway-events#request-guild-members-request-guild-members-structure +pub struct GatewayRequestGuildMembers { + pub guild_id: String, + pub query: Option, + pub limit: u64, + pub presence: Option, + pub user_ids: Option, + pub nonce: Option, +} + +impl WebSocketEvent for GatewayRequestGuildMembers {} diff --git a/src/types/events/resume.rs b/src/types/events/resume.rs new file mode 100644 index 0000000..362de98 --- /dev/null +++ b/src/types/events/resume.rs @@ -0,0 +1,11 @@ +use crate::types::events::WebSocketEvent; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Deserialize, Serialize, Default)] +pub struct GatewayResume { + pub token: String, + pub session_id: String, + pub seq: String, +} + +impl WebSocketEvent for GatewayResume {} diff --git a/src/types/events/thread.rs b/src/types/events/thread.rs new file mode 100644 index 0000000..a9bf851 --- /dev/null +++ b/src/types/events/thread.rs @@ -0,0 +1,82 @@ +use crate::types::entities::{Channel, GuildMember, ThreadMember}; +use crate::types::events::WebSocketEvent; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Default, Deserialize, Serialize)] +/// See https://discord.com/developers/docs/topics/gateway-events#thread-create +/// Not directly serialized, as the inner payload is a channel object +pub struct ThreadCreate { + pub thread: Channel, +} + +impl WebSocketEvent for ThreadCreate {} + +#[derive(Debug, Default, Deserialize, Serialize)] +/// See https://discord.com/developers/docs/topics/gateway-events#thread-update +/// Not directly serialized, as the inner payload is a channel object +pub struct ThreadUpdate { + pub thread: Channel, +} + +impl WebSocketEvent for ThreadUpdate {} + +#[derive(Debug, Default, Deserialize, Serialize)] +/// See https://discord.com/developers/docs/topics/gateway-events#thread-delete +/// Not directly serialized, as the inner payload is a channel object +pub struct ThreadDelete { + pub thread: Channel, +} + +impl WebSocketEvent for ThreadDelete {} + +#[derive(Debug, Default, Deserialize, Serialize)] +/// See https://discord.com/developers/docs/topics/gateway-events#thread-list-sync +pub struct ThreadListSync { + pub guild_id: String, + pub channel_ids: Option>, + pub threads: Vec, + pub members: Vec, +} + +impl WebSocketEvent for ThreadListSync {} + +#[derive(Debug, Default, Deserialize, Serialize)] +/// See https://discord.com/developers/docs/topics/gateway-events#thread-member-update +/// The inner payload is a thread member object with an extra field. +/// The extra field is a bit painful, because we can't just serialize a thread member object +pub struct ThreadMemberUpdate { + pub id: Option, + pub user_id: Option, + pub join_timestamp: Option, + pub flags: Option, + pub member: Option, + pub guild_id: String, +} + +impl ThreadMemberUpdate { + /// Convert self to a thread member, losing the added guild_id field + pub fn to_thread_member(self) -> ThreadMember { + ThreadMember { + id: self.id, + user_id: self.user_id, + join_timestamp: self.join_timestamp.clone(), + flags: self.flags, + member: self.member, + } + } +} + +impl WebSocketEvent for ThreadMemberUpdate {} + +#[derive(Debug, Default, Deserialize, Serialize)] +/// See https://discord.com/developers/docs/topics/gateway-events#thread-members-update +pub struct ThreadMembersUpdate { + pub id: String, + pub guild_id: String, + /// Capped at 50 + pub member_count: u8, + pub added_members: Option>, + pub removed_members: Option>, +} + +impl WebSocketEvent for ThreadMembersUpdate {} diff --git a/src/types/events/user.rs b/src/types/events/user.rs new file mode 100644 index 0000000..4762121 --- /dev/null +++ b/src/types/events/user.rs @@ -0,0 +1,12 @@ +use crate::types::entities::User; +use crate::types::events::WebSocketEvent; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Default, Deserialize, Serialize)] +/// See https://discord.com/developers/docs/topics/gateway-events#user-update +/// Not directly serialized, as the inner payload is the user object +pub struct UserUpdate { + pub user: User, +} + +impl WebSocketEvent for UserUpdate {} diff --git a/src/types/events/voice_status.rs b/src/types/events/voice_status.rs new file mode 100644 index 0000000..aea7e0c --- /dev/null +++ b/src/types/events/voice_status.rs @@ -0,0 +1,13 @@ +use crate::types::events::WebSocketEvent; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Deserialize, Serialize, Default)] +/// See https://discord.com/developers/docs/topics/gateway-events#update-voice-state-gateway-voice-state-update-structure +pub struct GatewayVoiceStateUpdate { + pub guild_id: String, + pub channel_id: Option, + pub self_mute: bool, + pub self_deaf: bool, +} + +impl WebSocketEvent for GatewayVoiceStateUpdate {} diff --git a/src/types/interfaces/activity.rs b/src/types/interfaces/activity.rs new file mode 100644 index 0000000..42ecc43 --- /dev/null +++ b/src/types/interfaces/activity.rs @@ -0,0 +1,56 @@ +use crate::types::entities::Emoji; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Deserialize, Serialize, Clone)] +pub struct Activity { + name: String, + #[serde(rename = "type")] + activity_type: i32, + url: Option, + created_at: i64, + timestamps: Option, + application_id: Option, + details: Option, + state: Option, + emoji: Option, + party: Option, + assets: Option, + secrets: Option, + instance: Option, + flags: Option, + buttons: Option>, +} + +#[derive(Debug, Deserialize, Serialize, Clone)] +struct ActivityTimestamps { + start: Option, + end: Option, +} + +#[derive(Debug, Deserialize, Serialize, Clone)] +struct ActivityParty { + id: Option, + size: Option>, +} + +#[derive(Debug, Deserialize, Serialize, Clone)] +struct ActivityAssets { + large_image: Option, + large_text: Option, + small_image: Option, + small_text: Option, +} + +#[derive(Debug, Deserialize, Serialize, Clone)] +struct ActivitySecrets { + join: Option, + spectate: Option, + #[serde(rename = "match")] + match_string: Option, +} + +#[derive(Debug, Deserialize, Serialize, Clone)] +struct ActivityButton { + label: String, + url: String, +} diff --git a/src/types/interfaces/connected_account.rs b/src/types/interfaces/connected_account.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/types/interfaces/guild_welcome_screen.rs b/src/types/interfaces/guild_welcome_screen.rs new file mode 100644 index 0000000..f799b1d --- /dev/null +++ b/src/types/interfaces/guild_welcome_screen.rs @@ -0,0 +1,17 @@ +use crate::types::utils::Snowflake; +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Debug, Default, Clone)] +pub struct WelcomeScreenObject { + pub enabled: bool, + pub description: Option, + pub welcome_channels: Vec, +} + +#[derive(Serialize, Deserialize, Debug, Default, Clone)] +pub struct WelcomeScreenChannel { + pub channel_id: Snowflake, + pub description: String, + pub emoji_id: Option, + pub emoji_name: Option, +} diff --git a/src/types/interfaces/interaction.rs b/src/types/interfaces/interaction.rs new file mode 100644 index 0000000..76b0361 --- /dev/null +++ b/src/types/interfaces/interaction.rs @@ -0,0 +1,39 @@ +use crate::types::entities::{AllowedMention, Embed}; +use crate::types::utils::Snowflake; +use serde::{Deserialize, Serialize}; +use serde_json::Value; + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct Interaction { + pub id: Snowflake, + pub r#type: InteractionType, + pub data: Value, + pub guild_id: Snowflake, + pub channel_id: Snowflake, + pub member_id: Snowflake, + pub token: String, + pub version: i32, +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub enum InteractionType { + SelfCommand = 0, + Ping = 1, + ApplicationCommand = 2, +} + +pub enum InteractionResponseType { + SelfCommandResponse = 0, + Pong = 1, + Acknowledge = 2, + ChannelMessage = 3, + ChannelMessageWithSource = 4, + AcknowledgeWithSource = 5, +} + +pub struct InteractionApplicationCommandCallbackData { + pub tts: bool, + pub content: String, + pub embeds: Vec, + pub allowed_mentions: AllowedMention, +} diff --git a/src/types/interfaces/mod.rs b/src/types/interfaces/mod.rs new file mode 100644 index 0000000..6e0a7df --- /dev/null +++ b/src/types/interfaces/mod.rs @@ -0,0 +1,11 @@ +mod activity; +mod connected_account; +mod guild_welcome_screen; +mod interaction; +mod status; + +pub use activity::*; +pub use connected_account::*; +pub use guild_welcome_screen::*; +pub use interaction::*; +pub use status::*; \ No newline at end of file diff --git a/src/types/interfaces/status.rs b/src/types/interfaces/status.rs new file mode 100644 index 0000000..105dd87 --- /dev/null +++ b/src/types/interfaces/status.rs @@ -0,0 +1,9 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Deserialize, Serialize, Default, Clone)] +/// See https://discord.com/developers/docs/topics/gateway-events#client-status-object +pub struct ClientStatusObject { + pub desktop: Option, + pub mobile: Option, + pub web: Option, +} \ No newline at end of file diff --git a/src/types/mod.rs b/src/types/mod.rs new file mode 100644 index 0000000..8554011 --- /dev/null +++ b/src/types/mod.rs @@ -0,0 +1,15 @@ +pub use config::*; +pub use entities::*; +pub use errors::*; +pub use events::*; +pub use interfaces::*; +pub use schema::*; +pub use utils::*; + +mod config; +mod entities; +mod errors; +mod events; +mod interfaces; +mod schema; +mod utils; diff --git a/src/types/schema/apierror.rs b/src/types/schema/apierror.rs new file mode 100644 index 0000000..95e72a5 --- /dev/null +++ b/src/types/schema/apierror.rs @@ -0,0 +1,72 @@ +#[cfg(feature = "poem")] +use poem::{http::StatusCode, IntoResponse, Response}; +use serde_json::{json, Value}; + +#[derive(Debug, thiserror::Error)] +pub enum APIError { + #[error(transparent)] + Auth(#[from] AuthError), +} + +impl APIError { + pub fn error_payload(&self) -> Value { + match self { + APIError::Auth(auth_err) => auth_err.error_payload(), + } + } +} + +#[derive(Debug, thiserror::Error)] +pub enum AuthError { + #[error("INVALID_LOGIN")] + InvalidLogin, + #[error("INVALID_CAPTCHA")] + InvalidCaptcha, +} + +impl AuthError { + pub fn error_code(&self) -> &str { + match self { + AuthError::InvalidLogin => "INVALID_LOGIN", + AuthError::InvalidCaptcha => "INVALID_CATPCA", + } + } + + pub fn error_payload(&self) -> Value { + match self { + AuthError::InvalidLogin => json!({ + "login": { + "message": "auth:login.INVALID_LOGIN", + "code": self.error_code() + } + }), + AuthError::InvalidCaptcha => json!([json!({ + "captcha_key": "TODO", + "captcha_sitekey": "TODO", + "captcha_service": "TODO" + })]), + } + } +} + +#[cfg(feature = "poem")] +impl poem::error::ResponseError for APIError { + fn status(&self) -> StatusCode { + match self { + APIError::Auth(auth_err) => match auth_err { + AuthError::InvalidLogin => StatusCode::UNAUTHORIZED, + AuthError::InvalidCaptcha => StatusCode::BAD_REQUEST, + }, + } + } + + fn as_response(&self) -> Response + where + Self: std::error::Error + Send + Sync + 'static, + { + Response::builder() + .status(self.status()) + .body(self.error_payload().to_string()) + .into_response() + } +} diff --git a/src/types/schema/auth.rs b/src/types/schema/auth.rs new file mode 100644 index 0000000..073d8d4 --- /dev/null +++ b/src/types/schema/auth.rs @@ -0,0 +1,240 @@ +use crate::errors::FieldFormatError; +use regex::Regex; +use serde::{Deserialize, Serialize}; + +/** +A struct that represents a well-formed email address. + */ +#[derive(Clone, PartialEq, Eq, Debug)] +pub struct AuthEmail { + pub email: String, +} + +impl AuthEmail { + /** + Returns a new [`Result`]. + ## Arguments + The email address you want to validate. + ## Errors + You will receive a [`FieldFormatError`], if: + - The email address is not in a valid format. + + */ + pub fn new(email: String) -> Result { + let regex = Regex::new(r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$").unwrap(); + if !regex.is_match(email.as_str()) { + return Err(FieldFormatError::EmailError); + } + Ok(AuthEmail { email }) + } +} + +/** +A struct that represents a well-formed username. +## Arguments +Please use new() to create a new instance of this struct. +## Errors +You will receive a [`FieldFormatError`], if: +- The username is not between 2 and 32 characters. + */ +#[derive(Clone, PartialEq, Eq, Debug)] +pub struct AuthUsername { + pub username: String, +} + +impl AuthUsername { + /** + Returns a new [`Result`]. + ## Arguments + The username you want to validate. + ## Errors + You will receive a [`FieldFormatError`], if: + - The username is not between 2 and 32 characters. + */ + pub fn new(username: String) -> Result { + if username.len() < 2 || username.len() > 32 { + Err(FieldFormatError::UsernameError) + } else { + Ok(AuthUsername { username }) + } + } +} + +/** +A struct that represents a well-formed password. +## Arguments +Please use new() to create a new instance of this struct. +## Errors +You will receive a [`FieldFormatError`], if: +- The password is not between 1 and 72 characters. + */ +#[derive(Clone, PartialEq, Eq, Debug)] +pub struct AuthPassword { + pub password: String, +} + +impl AuthPassword { + /** + Returns a new [`Result`]. + ## Arguments + The password you want to validate. + ## Errors + You will receive a [`FieldFormatError`], if: + - The password is not between 1 and 72 characters. + */ + pub fn new(password: String) -> Result { + if password.is_empty() || password.len() > 72 { + Err(FieldFormatError::PasswordError) + } else { + Ok(AuthPassword { password }) + } + } +} + +/** +A struct that represents a well-formed register request. +## Arguments +Please use new() to create a new instance of this struct. +## Errors +You will receive a [`FieldFormatError`], if: +- The username is not between 2 and 32 characters. +- The password is not between 1 and 72 characters. + */ + +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] +#[serde(rename_all = "snake_case")] +pub struct RegisterSchema { + username: String, + password: Option, + consent: bool, + email: Option, + fingerprint: Option, + invite: Option, + date_of_birth: Option, + gift_code_sku_id: Option, + captcha_key: Option, + promotional_email_opt_in: Option, +} + +impl RegisterSchema { + /** + Returns a new [`Result`]. + ## Arguments + All but "String::username" and "bool::consent" are optional. + + ## Errors + You will receive a [`FieldFormatError`], if: + - The username is less than 2 or more than 32 characters in length + - You supply a `password` which is less than 1 or more than 72 characters in length. + + These constraints have been defined [in the Spacebar-API](https://docs.spacebar.chat/routes/) + */ + pub fn new( + username: String, + password: Option, + consent: bool, + email: Option, + fingerprint: Option, + invite: Option, + date_of_birth: Option, + gift_code_sku_id: Option, + captcha_key: Option, + promotional_email_opt_in: Option, + ) -> Result { + let username = AuthUsername::new(username)?.username; + + let email = if let Some(email) = email { + Some(AuthEmail::new(email)?.email) + } else { + None + }; + + let password = if let Some(password) = password { + Some(AuthPassword::new(password)?.password) + } else { + None + }; + + if !consent { + return Err(FieldFormatError::ConsentError); + } + + Ok(RegisterSchema { + username, + password, + consent, + email, + fingerprint, + invite, + date_of_birth, + gift_code_sku_id, + captcha_key, + promotional_email_opt_in, + }) + } +} + +/** +A struct that represents a well-formed login request. +## Arguments +Please use new() to create a new instance of this struct. +## Errors +You will receive a [`FieldFormatError`], if: +- The username is not between 2 and 32 characters. +- The password is not between 1 and 72 characters. + */ +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] +#[serde(rename_all = "snake_case")] +pub struct LoginSchema { + pub login: String, + pub password: String, + pub undelete: Option, + pub captcha_key: Option, + pub login_source: Option, + pub gift_code_sku_id: Option, +} + +impl LoginSchema { + /** + Returns a new [`Result`]. + ## Arguments + login: The username you want to login with. + password: The password you want to login with. + undelete: Honestly no idea what this is for. + captcha_key: The captcha key you want to login with. + login_source: The login source. + gift_code_sku_id: The gift code sku id. + ## Errors + You will receive a [`FieldFormatError`], if: + - The username is less than 2 or more than 32 characters in length + */ + pub fn new( + login: String, + password: String, + undelete: Option, + captcha_key: Option, + login_source: Option, + gift_code_sku_id: Option, + ) -> Result { + let login = AuthUsername::new(login)?.username; + let password = AuthPassword::new(password)?.password; + + Ok(LoginSchema { + login, + password, + undelete, + captcha_key, + login_source, + gift_code_sku_id, + }) + } +} + +#[derive(Debug, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +pub struct TotpSchema { + code: String, + ticket: String, + gift_code_sku_id: Option, + login_source: Option, +} diff --git a/src/types/schema/channel.rs b/src/types/schema/channel.rs new file mode 100644 index 0000000..458c33c --- /dev/null +++ b/src/types/schema/channel.rs @@ -0,0 +1,27 @@ +use serde::{Deserialize, Serialize}; + +use crate::types::entities::PermissionOverwrite; + +#[derive(Debug, Deserialize, Serialize)] +#[serde(rename_all = "snake_case")] +pub struct ChannelCreateSchema { + pub name: String, + #[serde(rename = "type")] + pub channel_type: Option, + pub topic: Option, + pub icon: Option, + pub bitrate: Option, + pub user_limit: Option, + pub rate_limit_per_user: Option, + pub position: Option, + pub permission_overwrites: Option>, + pub parent_id: Option, + pub id: Option, + pub nsfw: Option, + pub rtc_region: Option, + pub default_auto_archive_duration: Option, + pub default_reaction_emoji: Option, + pub flags: Option, + pub default_thread_rate_limit_per_user: Option, + pub video_quality_mode: Option, +} diff --git a/src/types/schema/guild.rs b/src/types/schema/guild.rs new file mode 100644 index 0000000..4b8b524 --- /dev/null +++ b/src/types/schema/guild.rs @@ -0,0 +1,14 @@ +use crate::types::entities::Channel; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Deserialize, Serialize)] +#[serde(rename_all = "snake_case")] +pub struct GuildCreateSchema { + pub name: Option, + pub region: Option, + pub icon: Option, + pub channels: Option>, + pub guild_template_code: Option, + pub system_channel_id: Option, + pub rules_channel_id: Option, +} diff --git a/src/types/schema/message.rs b/src/types/schema/message.rs new file mode 100644 index 0000000..6121f5f --- /dev/null +++ b/src/types/schema/message.rs @@ -0,0 +1,49 @@ +use crate::types::entities::{ + AllowedMention, Component, Embed, MessageReference, PartialDiscordFileAttachment, +}; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Deserialize, Serialize)] +#[serde(rename_all = "snake_case")] +pub struct MessageSendSchema { + #[serde(rename = "type")] + message_type: Option, + content: Option, + nonce: Option, + tts: Option, + embeds: Option>, + allowed_mentions: Option, + message_reference: Option, + components: Option>, + sticker_ids: Option>, + pub attachments: Option>, +} + +// make a new() method for MessageSendSchema +impl MessageSendSchema { + pub fn new( + message_type: Option, + content: Option, + nonce: Option, + tts: Option, + embeds: Option>, + allowed_mentions: Option, + message_reference: Option, + components: Option>, + sticker_ids: Option>, + attachments: Option>, + ) -> MessageSendSchema { + MessageSendSchema { + message_type, + content, + nonce, + tts, + embeds, + allowed_mentions, + message_reference, + components, + sticker_ids, + attachments, + } + } +} diff --git a/src/types/schema/mod.rs b/src/types/schema/mod.rs new file mode 100644 index 0000000..6fe3e37 --- /dev/null +++ b/src/types/schema/mod.rs @@ -0,0 +1,103 @@ +mod apierror; +mod auth; +mod channel; +mod guild; +mod message; +mod user; + +pub use apierror::*; +pub use auth::*; +pub use channel::*; +pub use guild::*; +pub use message::*; +pub use user::*; + +#[cfg(test)] +mod schemas_tests { + use super::*; + use crate::errors::FieldFormatError; + + #[test] + fn password_too_short() { + assert_eq!( + AuthPassword::new("".to_string()), + Err(FieldFormatError::PasswordError) + ); + } + + #[test] + fn password_too_long() { + let mut long_pw = String::new(); + for _ in 0..73 { + long_pw += "a"; + } + assert_eq!( + AuthPassword::new(long_pw), + Err(FieldFormatError::PasswordError) + ); + } + + #[test] + fn username_too_short() { + assert_eq!( + AuthUsername::new("T".to_string()), + Err(FieldFormatError::UsernameError) + ); + } + + #[test] + fn username_too_long() { + let mut long_un = String::new(); + for _ in 0..33 { + long_un += "a"; + } + assert_eq!( + AuthUsername::new(long_un), + Err(FieldFormatError::UsernameError) + ); + } + + #[test] + fn consent_false() { + assert_eq!( + RegisterSchema::new( + "Test".to_string(), + None, + false, + None, + None, + None, + None, + None, + None, + None, + ), + Err(FieldFormatError::ConsentError) + ); + } + + #[test] + fn invalid_email() { + assert_eq!( + AuthEmail::new("p@p.p".to_string()), + Err(FieldFormatError::EmailError) + ) + } + + #[test] + fn valid_email() { + let reg = RegisterSchema::new( + "Testy".to_string(), + None, + true, + Some("me@mail.de".to_string()), + None, + None, + None, + None, + None, + None, + ); + assert_ne!(reg, Err(FieldFormatError::EmailError)); + } +} diff --git a/src/types/schema/user.rs b/src/types/schema/user.rs new file mode 100644 index 0000000..00f4962 --- /dev/null +++ b/src/types/schema/user.rs @@ -0,0 +1,16 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Deserialize, Serialize)] +#[serde(rename_all = "snake_case")] +pub struct UserModifySchema { + pub username: Option, + pub avatar: Option, + pub bio: Option, + pub accent_color: Option, + pub banner: Option, + pub current_password: Option, + pub new_password: Option, + pub code: Option, + pub email: Option, + pub discriminator: Option, +} \ No newline at end of file diff --git a/src/types/utils/jwt.rs b/src/types/utils/jwt.rs new file mode 100644 index 0000000..c9f1aa5 --- /dev/null +++ b/src/types/utils/jwt.rs @@ -0,0 +1,44 @@ +use jsonwebtoken::{encode, EncodingKey, Header}; +use serde::{Deserialize, Serialize}; + +use crate::types::utils::Snowflake; + +pub fn generate_token(id: &Snowflake, email: String, jwt_key: &str) -> String { + let claims = Claims::new(&email, id); + + build_token(&claims, jwt_key).unwrap() +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct Claims { + pub exp: i64, + pub iat: i64, + pub email: String, + pub id: String, +} + +impl Claims { + pub fn new(user: &str, id: &Snowflake) -> Self { + let unix = chrono::Utc::now().timestamp(); + Self { + exp: unix + (60 * 60 * 24), + id: id.to_string(), + iat: unix, + email: user.to_string(), + } + } +} + +pub fn build_token(claims: &Claims, jwt_key: &str) -> Result { + encode( + &Header::default(), + claims, + &EncodingKey::from_secret(jwt_key.as_bytes()), + ) +} + +/*pub fn decode_token(token: &str) -> Result, Error> { + let mut validation = Validation::new(Algorithm::HS256); + validation.sub = Some("quartzauth".to_string()); + decode(token, &DecodingKey::from_secret(JWT_SECRET), &validation) +}*/ diff --git a/src/types/utils/mod.rs b/src/types/utils/mod.rs new file mode 100644 index 0000000..ec2fd4a --- /dev/null +++ b/src/types/utils/mod.rs @@ -0,0 +1,8 @@ +pub mod jwt; +mod regexes; +mod rights; +mod snowflake; + +pub use regexes::*; +pub use rights::Rights; +pub use snowflake::{DeconstructedSnowflake, Snowflake}; diff --git a/src/types/utils/regexes.rs b/src/types/utils/regexes.rs new file mode 100644 index 0000000..0f160eb --- /dev/null +++ b/src/types/utils/regexes.rs @@ -0,0 +1,12 @@ +use lazy_static::lazy_static; +use regex::Regex; + +lazy_static! { + static ref DOUBLE_WHITE_SPACE_RE: Regex = Regex::new(r"\s\s+").unwrap(); + static ref SPECIAL_CHAR: Regex = Regex::new(r"@#`:\r\n\t\f\v\p{C}").unwrap(); + static ref CHANNEL_MENTION: Regex = Regex::new(r"<#(\d+)>").unwrap(); + static ref USER_MENTION: Regex = Regex::new(r"<@!?(\d+)>").unwrap(); + static ref ROLE_MENTION: Regex = Regex::new(r"<@&(\d+)>").unwrap(); + static ref EVERYONE_MENTION: Regex = Regex::new(r"@everyone").unwrap(); + static ref HERE_MENTION: Regex = Regex::new(r"@here").unwrap(); +} diff --git a/src/types/utils/rights.rs b/src/types/utils/rights.rs new file mode 100644 index 0000000..0198af6 --- /dev/null +++ b/src/types/utils/rights.rs @@ -0,0 +1,127 @@ +use bitflags::bitflags; + +bitflags! { + pub struct Rights: u64 { + const OPERATOR = 1 << 0; + const MANAGE_APPLICATIONS = 1 << 1; + const MANAGE_GUILDS = 1 << 2; + const MANAGE_MESSAGES = 1 << 3; + const MANAGE_RATE_LIMITS = 1 << 4; + const MANAGE_ROUTING = 1 << 5; + const MANAGE_TICKETS = 1 << 6; + const MANAGE_USERS = 1 << 7; + const ADD_MEMBERS = 1 << 8; + const BYPASS_RATE_LIMITS = 1 << 9; + const CREATE_APPLICATIONS = 1 << 10; + const CREATE_CHANNELS = 1 << 11; + const CREATE_DMS = 1 << 12; + const CREATE_DM_GROUPS = 1 << 13; + const CREATE_GUILDS = 1 << 14; + const CREATE_INVITES = 1 << 15; + const CREATE_ROLES = 1 << 16; + const CREATE_TEMPLATES = 1 << 17; + const CREATE_WEBHOOKS = 1 << 18; + const JOIN_GUILDS = 1 << 19; + const PIN_MESSAGES = 1 << 20; + const SELF_ADD_REACTIONS = 1 << 21; + const SELF_DELETE_MESSAGES = 1 << 22; + const SELF_EDIT_MESSAGES = 1 << 23; + const SELF_EDIT_NAME = 1 << 24; + const SEND_MESSAGES = 1 << 25; + const USE_ACTIVITIES = 1 << 26; + const USE_VIDEO = 1 << 27; + const USE_VOICE = 1 << 28; + const INVITE_USERS = 1 << 29; + const SELF_DELETE_DISABLE = 1 << 30; + const DEBTABLE = 1 << 31; + const CREDITABLE = 1 << 32; + const KICK_BAN_MEMBERS = 1 << 33; + const SELF_LEAVE_GROUPS = 1 << 34; + const PRESENCE = 1 << 35; + const SELF_ADD_DISCOVERABLE = 1 << 36; + const MANAGE_GUILD_DIRECTORY = 1 << 37; + const POGGERS = 1 << 38; + const USE_ACHIEVEMENTS = 1 << 39; + const INITIATE_INTERACTIONS = 1 << 40; + const RESPOND_TO_INTERACTIONS = 1 << 41; + const SEND_BACKDATED_EVENTS = 1 << 42; + const USE_MASS_INVITES = 1 << 43; + const ACCEPT_INVITES = 1 << 44; + const SELF_EDIT_FLAGS = 1 << 45; + const EDIT_FLAGS = 1 << 46; + const MANAGE_GROUPS = 1 << 47; + const VIEW_SERVER_STATS = 1 << 48; + const RESEND_VERIFICATION_EMAIL = 1 << 49; + } +} + +impl Rights { + pub fn any(&self, permission: Rights, check_operator: bool) -> bool { + (check_operator && self.contains(Rights::OPERATOR)) || self.contains(permission) + } + + pub fn has(&self, permission: Rights, check_operator: bool) -> bool { + (check_operator && self.contains(Rights::OPERATOR)) || self.contains(permission) + } + + pub fn has_throw(&self, permission: Rights) -> Result { + if self.has(permission, true) { + Ok(true) + } else { + Err("You are missing the following rights") + } + } +} + +fn all_rights() -> Rights { + Rights::OPERATOR + | Rights::MANAGE_APPLICATIONS + | Rights::MANAGE_GUILDS + | Rights::MANAGE_MESSAGES + | Rights::MANAGE_RATE_LIMITS + | Rights::MANAGE_ROUTING + | Rights::MANAGE_TICKETS + | Rights::MANAGE_USERS + | Rights::ADD_MEMBERS + | Rights::BYPASS_RATE_LIMITS + | Rights::CREATE_APPLICATIONS + | Rights::CREATE_CHANNELS + | Rights::CREATE_DMS + | Rights::CREATE_DM_GROUPS + | Rights::CREATE_GUILDS + | Rights::CREATE_INVITES + | Rights::CREATE_ROLES + | Rights::CREATE_TEMPLATES + | Rights::CREATE_WEBHOOKS + | Rights::JOIN_GUILDS + | Rights::PIN_MESSAGES + | Rights::SELF_ADD_REACTIONS + | Rights::SELF_DELETE_MESSAGES + | Rights::SELF_EDIT_MESSAGES + | Rights::SELF_EDIT_NAME + | Rights::SEND_MESSAGES + | Rights::USE_ACTIVITIES + | Rights::USE_VIDEO + | Rights::USE_VOICE + | Rights::INVITE_USERS + | Rights::SELF_DELETE_DISABLE + | Rights::DEBTABLE + | Rights::CREDITABLE + | Rights::KICK_BAN_MEMBERS + | Rights::SELF_LEAVE_GROUPS + | Rights::PRESENCE + | Rights::SELF_ADD_DISCOVERABLE + | Rights::MANAGE_GUILD_DIRECTORY + | Rights::POGGERS + | Rights::USE_ACHIEVEMENTS + | Rights::INITIATE_INTERACTIONS + | Rights::RESPOND_TO_INTERACTIONS + | Rights::SEND_BACKDATED_EVENTS + | Rights::USE_MASS_INVITES + | Rights::ACCEPT_INVITES + | Rights::SELF_EDIT_FLAGS + | Rights::EDIT_FLAGS + | Rights::MANAGE_GROUPS + | Rights::VIEW_SERVER_STATS + | Rights::RESEND_VERIFICATION_EMAIL +} diff --git a/src/types/utils/snowflake.rs b/src/types/utils/snowflake.rs new file mode 100644 index 0000000..7c756fa --- /dev/null +++ b/src/types/utils/snowflake.rs @@ -0,0 +1,160 @@ +use std::fmt::Display; + +use atomic::Atomic; +use bigdecimal::{Num, ToPrimitive, Zero}; +use num_bigint::{BigInt, ToBigInt}; +use serde::{Deserialize, Serialize}; +#[cfg(feature = "sqlx")] +use sqlx::Type; + +const EPOCH: i64 = 1420070400000; +static WORKER_ID: u128 = 0; +static PROCESS_ID: u128 = 1; +lazy_static::lazy_static! { + static ref INCREMENT: Atomic = Atomic::default(); +} +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[cfg_attr(feature = "sqlx", derive(Type))] +#[cfg_attr(feature = "sqlx", sqlx(transparent))] +pub struct Snowflake(String); + +impl Default for Snowflake { + fn default() -> Self { + Self::generate() + } +} + +impl Display for Snowflake { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.0) + } +} + +impl Snowflake { + pub fn to_binary(&self) -> String { + let self_len = self.0.len(); + let high = self.0[..self_len - 10].parse::().unwrap_or(0); + let low = self.0[self_len - 10..].parse::().unwrap(); + let mut low = low; + let mut high = high; + let mut bin = Vec::with_capacity(64); + + while low > 0 || high > 0 { + bin.push((low & 1) as u8); + low >>= 1; + + if high > 0 { + low += 5_000_000_000 * (high % 2); + high >>= 1; + } + } + + bin.iter() + .rev() + .map(|b| char::from_digit(*b as u32, 10).unwrap()) + .collect() + } + + pub fn from_binary(num: &str) -> String { + let mut num = BigInt::from_str_radix(num, 2).unwrap(); + let mut dec = Vec::with_capacity(18); + + let ten = 10.to_bigint().unwrap(); + let _two = 2.to_bigint().unwrap(); + let _thirty_two = 32.to_bigint().unwrap(); + + while num.bits() > 50 { + let high: BigInt = &num >> 32; + let low: BigInt = (high.clone() % &ten) << 32 | &num & BigInt::from((1u64 << 32) - 1); + + let next: BigInt = low.clone() % &ten; + dec.push(next.to_u8().unwrap()); + num = (high / &ten) << 32 | (low / &ten); + } + + while !num.is_zero() { + dec.push((num.clone() % &ten).to_u8().unwrap()); + num /= &ten; + } + + dec.iter() + .rev() + .map(|d| char::from_digit(*d as u32, 10).unwrap()) + .collect() + } + + pub fn generate_worker_process() -> u128 { + let time = (chrono::Utc::now().naive_utc().timestamp_millis() - EPOCH) << 22; + let worker = WORKER_ID << 17; + let process = PROCESS_ID << 12; + let increment = INCREMENT.load(atomic::Ordering::Relaxed); + + INCREMENT.store(increment + 1, atomic::Ordering::Relaxed); + + time as u128 | worker | process | increment + } + + pub fn generate() -> Self { + Self(Self::generate_worker_process().to_string()) + } + + pub fn deconstruct(&self) -> DeconstructedSnowflake { + let binary = format!("{:0>64}", self.to_binary()); + + let ts = i64::from_str_radix(&binary[0..42], 2).unwrap() + EPOCH; + let wid = u64::from_str_radix(&binary[42..47], 2).unwrap(); + let pid = u64::from_str_radix(&binary[47..52], 2).unwrap(); + let increment = BigInt::from_str_radix(&binary[52..64], 2).unwrap(); + + DeconstructedSnowflake { + timestamp: ts, + worker_id: wid, + process_id: pid, + increment, + binary, + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct DeconstructedSnowflake { + pub timestamp: i64, + pub worker_id: u64, + pub process_id: u64, + pub increment: BigInt, + pub binary: String, +} + +#[cfg(test)] +mod test { + use super::Snowflake; + + #[test] + fn test_new_snowflake() { + let snow = Snowflake::generate(); + println!("{snow}"); + } + + #[test] + fn snowflake_to_binary() { + let snowflake = super::Snowflake("1104339392517902336".to_string()); + + let bin = snowflake.to_binary(); + println!("{bin}"); + } + + #[test] + fn binary_to_snowflake() { + let snowflake = super::Snowflake::from_binary( + "111101010011011001101101001110010010100000000001000000000000", + ); + println!("{snowflake}"); + } + + #[test] + fn test_deconstruct() { + let new = super::Snowflake::generate(); + + println!("{:?}", new.deconstruct()); + } +} diff --git a/tests/integration.rs b/tests/integration.rs index 045dc08..116e89f 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -1,14 +1,13 @@ use chorus::{ - api::schemas, - api::{AuthUsername, Channel, Guild, GuildCreateSchema, RegisterSchema, User}, - instance::Instance, + instance::{Instance, UserMeta}, + types::{Channel, ChannelCreateSchema, Guild, GuildCreateSchema, RegisterSchema}, URLBundle, }; #[derive(Debug)] struct TestBundle { urls: URLBundle, - user: User, + user: UserMeta, instance: Instance, guild_id: String, channel: Channel, @@ -24,7 +23,7 @@ async fn setup() -> TestBundle { let mut instance = Instance::new(urls.clone()).await.unwrap(); // Requires the existance of the below user. let reg = RegisterSchema::new( - AuthUsername::new("integrationtestuser".to_string()).unwrap(), + "integrationtestuser".to_string(), None, true, None, @@ -45,7 +44,7 @@ async fn setup() -> TestBundle { system_channel_id: None, rules_channel_id: None, }; - let channel_create_schema = schemas::ChannelCreateSchema { + let channel_create_schema = ChannelCreateSchema { name: "testchannel".to_string(), channel_type: Some(0), topic: None, @@ -101,13 +100,13 @@ async fn teardown(mut bundle: TestBundle) { } mod guild { - use chorus::api::{schemas, types, Channel}; + use chorus::types::{Channel, Guild, GuildCreateSchema}; #[tokio::test] async fn guild_creation_deletion() { let mut bundle = crate::setup().await; - let guild_create_schema = schemas::GuildCreateSchema { + let guild_create_schema = GuildCreateSchema { name: Some("test".to_string()), region: None, icon: None, @@ -117,14 +116,13 @@ mod guild { rules_channel_id: None, }; - let guild = - types::Guild::create(&mut bundle.user, bundle.urls.get_api(), guild_create_schema) - .await - .unwrap(); + let guild = Guild::create(&mut bundle.user, bundle.urls.get_api(), guild_create_schema) + .await + .unwrap(); println!("{}", guild); - match types::Guild::delete(&mut bundle.user, bundle.urls.get_api(), guild).await { + match Guild::delete(&mut bundle.user, bundle.urls.get_api(), guild).await { None => assert!(true), Some(_) => assert!(false), } @@ -142,7 +140,7 @@ mod guild { Channel::get( bundle_user.token.as_str(), bundle.instance.urls.get_api(), - &bundle_channel.id, + &bundle_channel.id.to_string(), &mut bundle_user.limits, &mut bundle.instance.limits ) diff --git a/types/config/mod.rs b/types/config/mod.rs new file mode 100644 index 0000000..97b830a --- /dev/null +++ b/types/config/mod.rs @@ -0,0 +1,187 @@ +use serde::{Deserialize, Serialize}; +use serde_json::{Map, Value}; + +pub use crate::{ + config::types::{ + api_configuration::ApiConfiguration, cdn_configuration::CdnConfiguration, + defaults_configuration::DefaultsConfiguration, email_configuration::EmailConfiguration, + endpoint_configuration::EndpointConfiguration, + external_tokens_configuration::ExternalTokensConfiguration, + general_configuration::GeneralConfiguration, gif_configuration::GifConfiguration, + guild_configuration::GuildConfiguration, kafka_configuration::KafkaConfiguration, + limit_configuration::LimitsConfiguration, login_configuration::LoginConfiguration, + metrics_configuration::MetricsConfiguration, + password_reset_configuration::PasswordResetConfiguration, + rabbit_mq_configuration::RabbitMQConfiguration, region_configuration::RegionConfiguration, + register_configuration::RegisterConfiguration, + security_configuration::SecurityConfiguration, sentry_configuration::SentryConfiguration, + template_configuration::TemplateConfiguration, + }, + entities::ConfigEntity, +}; + +mod types; + +#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ConfigValue { + pub gateway: EndpointConfiguration, + pub cdn: CdnConfiguration, + pub api: ApiConfiguration, + pub general: GeneralConfiguration, + pub limits: LimitsConfiguration, + pub security: SecurityConfiguration, + pub login: LoginConfiguration, + pub register: RegisterConfiguration, + pub regions: RegionConfiguration, + pub guild: GuildConfiguration, + pub gif: GifConfiguration, + pub rabbitmq: RabbitMQConfiguration, + pub kafka: KafkaConfiguration, + pub templates: TemplateConfiguration, + pub metrics: MetricsConfiguration, + pub sentry: SentryConfiguration, + pub defaults: DefaultsConfiguration, + pub external: ExternalTokensConfiguration, + pub email: EmailConfiguration, + pub password_reset: PasswordResetConfiguration, +} + +impl ConfigValue { + pub fn to_pairs(&self) -> Vec { + let v = serde_json::json!(self); + + generate_pairs(&v, "") + } + + pub fn from_pairs(pairs: Vec) -> Self { + pairs_to_config(pairs) + } +} + +fn generate_pairs(obj: &Value, key: &str) -> Vec { + let mut pairs = Vec::new(); + match obj { + Value::Object(map) => { + for (k, v) in map { + let new_key = if key.is_empty() { + k.to_string() + } else { + format!("{}_{}", key, k) + }; + pairs.extend(generate_pairs(v, &new_key)); + } + } + Value::Array(arr) => { + for (i, v) in arr.iter().enumerate() { + let new_key = format!("{}_{}", key, i); + pairs.extend(generate_pairs(v, &new_key)); + } + } + _ => pairs.push(ConfigEntity { + key: key.to_string(), + value: Some(obj.clone()), + }), + } + pairs +} + +fn pairs_to_config(pairs: Vec) -> ConfigValue { + let mut value = Value::Object(Map::new()); + + for p in pairs { + let keys: Vec<&str> = p.key.split('_').collect(); + let mut path = vec![]; + + for (i, &key) in keys.iter().enumerate() { + path.push(key); + + if i == keys.len() - 1 { + insert_into(&mut value, &path, p.value.clone().unwrap_or(Value::Null)); + } else if keys[i+1].parse::().is_ok() { + if !path_exists(&value, &path) { + insert_into(&mut value, &path, Value::Array(Vec::new())); + } + } else if !path_exists(&value, &path) { + insert_into(&mut value, &path, Value::Object(Map::new())); + } + } + } + + serde_json::from_value(value).unwrap() +} + +fn path_exists(value: &Value, path: &[&str]) -> bool { + let mut current = value; + + for &key in path { + match current { + Value::Object(map) => { + if let Some(v) = map.get(key) { + current = v; + } else { + return false; + } + }, + Value::Array(arr) => { + if let Ok(index) = key.parse::() { + if let Some(v) = arr.get(index) { + current = v; + } else { + return false; + } + } else { + return false; + } + }, + _ => return false, + } + } + + true +} + +fn insert_into(value: &mut Value, path: &[&str], new_value: Value) { + let last_key = path.last().unwrap(); + let parent_path = &path[0..path.len()-1]; + + let mut current = value; + + for &key in parent_path { + current = match current { + Value::Object(map) => map.get_mut(key).unwrap(), + Value::Array(arr) => arr.get_mut(key.parse::().unwrap()).unwrap(), + _ => unreachable!(), + }; + } + + match current { + Value::Object(map) => { + map.insert((*last_key).to_string(), new_value); + }, + Value::Array(arr) => { + let index = last_key.parse::().unwrap(); + if index >= arr.len() { + arr.resize(index + 1, Value::Null); + } + arr[index] = new_value; + }, + _ => unreachable!(), + }; +} + +#[cfg(test)] +mod test { + use crate::config::{generate_pairs, pairs_to_config, ConfigValue}; + + #[test] + fn test_pairs() { + let c = ConfigValue::default(); + let v = serde_json::json!(&c); + + let pairs = generate_pairs(&v, ""); + + let cfg = pairs_to_config(pairs); + assert_eq!(cfg, c) + } +} diff --git a/types/config/types/api_configuration.rs b/types/config/types/api_configuration.rs new file mode 100644 index 0000000..2d617fe --- /dev/null +++ b/types/config/types/api_configuration.rs @@ -0,0 +1,24 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub struct ApiConfiguration { + pub default_version: String, + pub active_versions: Vec, + pub endpoint_public: Option, +} + +impl Default for ApiConfiguration { + fn default() -> Self { + Self { + default_version: String::from("9"), + active_versions: vec![ + String::from("6"), + String::from("7"), + String::from("8"), + String::from("9"), + ], + endpoint_public: None, + } + } +} diff --git a/types/config/types/cdn_configuration.rs b/types/config/types/cdn_configuration.rs new file mode 100644 index 0000000..5c76273 --- /dev/null +++ b/types/config/types/cdn_configuration.rs @@ -0,0 +1,25 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CdnConfiguration { + pub resize_height_max: u64, + pub resize_width_max: u64, + pub imagor_server_url: Option, + + pub endpoint_public: Option, + pub endpoint_private: Option, +} + +impl Default for CdnConfiguration { + fn default() -> Self { + Self { + resize_height_max: 1000, + resize_width_max: 1000, + imagor_server_url: None, + + endpoint_private: None, + endpoint_public: None, + } + } +} diff --git a/types/config/types/defaults_configuration.rs b/types/config/types/defaults_configuration.rs new file mode 100644 index 0000000..b89b304 --- /dev/null +++ b/types/config/types/defaults_configuration.rs @@ -0,0 +1,10 @@ +use serde::{Deserialize, Serialize}; + +use crate::config::types::subconfigs::defaults::{guild::GuildDefaults, user::UserDefaults}; + +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct DefaultsConfiguration { + pub guild: GuildDefaults, + pub user: UserDefaults, +} diff --git a/types/config/types/email_configuration.rs b/types/config/types/email_configuration.rs new file mode 100644 index 0000000..3025487 --- /dev/null +++ b/types/config/types/email_configuration.rs @@ -0,0 +1,27 @@ +use serde::{Deserialize, Serialize}; + +use crate::config::types::subconfigs::email::{ + mailgun::MailGunConfiguration, mailjet::MailJetConfiguration, sendgrid::SendGridConfiguration, + smtp::SMTPConfiguration, +}; + +#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)] +pub enum EmailProvider { + Smtp, + MailGun, + MailJet, + SendGrid, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +#[derive(Default)] +pub struct EmailConfiguration { + pub provider: Option, + pub smtp: SMTPConfiguration, + pub mailgun: MailGunConfiguration, + pub mailjet: MailJetConfiguration, + pub sendgrid: SendGridConfiguration, +} + + diff --git a/types/config/types/endpoint_configuration.rs b/types/config/types/endpoint_configuration.rs new file mode 100644 index 0000000..b484791 --- /dev/null +++ b/types/config/types/endpoint_configuration.rs @@ -0,0 +1,9 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct EndpointConfiguration { + pub endpoint_client: Option, + pub endpoint_private: Option, + pub endpoint_public: Option, +} diff --git a/types/config/types/external_tokens_configuration.rs b/types/config/types/external_tokens_configuration.rs new file mode 100644 index 0000000..f417b2f --- /dev/null +++ b/types/config/types/external_tokens_configuration.rs @@ -0,0 +1,7 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ExternalTokensConfiguration { + pub twitter: Option, +} diff --git a/types/config/types/general_configuration.rs b/types/config/types/general_configuration.rs new file mode 100644 index 0000000..f67fdf2 --- /dev/null +++ b/types/config/types/general_configuration.rs @@ -0,0 +1,35 @@ +use serde::{Deserialize, Serialize}; + +use crate::utils::Snowflake; + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct GeneralConfiguration { + pub instance_name: String, + pub instance_description: Option, + pub front_page: Option, + pub tos_page: Option, + pub correspondence_email: Option, + pub correspondence_user_id: Option, + pub image: Option, + pub instance_id: Option, + pub auto_create_bot_users: Option, +} + +impl Default for GeneralConfiguration { + fn default() -> Self { + Self { + instance_name: String::from("Spacebar Instance"), + instance_description: Some(String::from( + "This is a Spacebar instance made in the pre-release days", + )), + front_page: None, + tos_page: None, + correspondence_email: None, + correspondence_user_id: None, + image: None, + instance_id: Some(Snowflake::generate()), + auto_create_bot_users: Some(false), + } + } +} diff --git a/types/config/types/gif_configuration.rs b/types/config/types/gif_configuration.rs new file mode 100644 index 0000000..8644fb4 --- /dev/null +++ b/types/config/types/gif_configuration.rs @@ -0,0 +1,26 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "lowercase")] +pub enum GifProvider { + #[default] + Tenor, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct GifConfiguration { + pub enabled: bool, + pub provider: GifProvider, + pub api_key: Option, +} + +impl Default for GifConfiguration { + fn default() -> Self { + Self { + enabled: true, + provider: GifProvider::Tenor, + api_key: Some(String::from("LIVDSRZULELA")), + } + } +} diff --git a/types/config/types/guild_configuration.rs b/types/config/types/guild_configuration.rs new file mode 100644 index 0000000..536396e --- /dev/null +++ b/types/config/types/guild_configuration.rs @@ -0,0 +1,126 @@ +use serde::{Deserialize, Serialize}; + +use crate::config::types::subconfigs::guild::{ + autojoin::AutoJoinConfiguration, discovery::DiscoverConfiguration, +}; + +#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] +pub enum GuildFeatures { + ActivitiesAlpha, + ActivitiesEmployee, + ActivitiesInternalDev, + AnimatedBanner, + AnimatedIcon, + ApplicationCommandPermissionsV2, + AutoModeration, + AutoModTriggerKeywordFilter, + AutoModTriggerMLSpamFilter, + AutoModTriggerSpamLinkFilter, + AutoModTriggerUserProfile, + Banner, + BFG, + BoostingTiersExperimentMediumGuild, + BoostingTiersExperimentSmallGuild, + BotDeveloperEarlyAccess, + BurstReactions, + CommunityCanary, + CommunityExpLargeGated, + CommunityExpLargeUngated, + CommunityExpMedium, + ChannelEmojisGenerated, + ChannelHighlights, + ChannelHighlightsDisabled, + ClydeEnabled, + ClydeExperimentEnabled, + ClydeDisabled, + Community, + CreatorAcceptedNewTerms, + CreatorMonetizable, + CreatorMonetizableDisabled, + CreatorMonetizablePendingNewOwnerOnboarding, + CreatorMonetizableProvisional, + CreatorMonetizableRestricted, + CreatorMonetizableWhiteglove, + CreatorMonetizableApplicationAllowlist, + CreateStorePage, + DeveloperSupportServer, + DiscoverableDisabled, + Discoverable, + EnabledDiscoverableBefore, + ExposedToActivitiesWTPExperiment, + GuestsEnabled, + GuildAutomodDefaultList, + GuildCommunicationDisabledGuilds, + GuildHomeDeprecationOverride, + GuildHomeOverride, + GuildHomeTest, + GuildMemberVerificationExperiment, + GuildOnboarding, + GuildOnboardingAdminOnly, + GuildOnboardingEverEnabled, + GuildOnboardingHasPrompts, + GuildRoleSubscription, + GuildRoleSubscriptionPurchaseFeedbackLoop, + GuildRoleSubscriptionTrials, + GuildServerGuide, + GuildWebPageVanityURL, + HadEarlyActivitiesAccess, + HasDirectoryEntry, + HideFromExperimentUI, + Hub, + IncreasedThreadLimit, + InternalEmployeeOnly, + InviteSplash, + InvitesDisabled, + LinkedToHub, + MarketplacesConnectionRoles, + MemberProfiles, + MemberVerificationGateEnabled, + MemberVerificationManualApproval, + MobileWebRoleSubscriptionPurchasePage, + MonetizationEnabled, + MoreEmoji, + MoreStickers, + News, + NewThreadPermissions, + Partnered, + PremiumTier3Override, + PreviewEnabled, + RaidAlertsDisabled, + RelayEnabled, + RestrictSpamRiskGuild, + RoleIcons, + RoleSubscriptionsAvailableForPurchase, + RoleSubscriptionsEnabled, + RoleSubscriptionsEnabledForPurchase, + Shard, + SharedCanvasFriendsAndFamilyTest, + Soundboard, + SummariesEnabled, + SummariesEnabledGA, + SummariesDisabledByUser, + SummariesEnabledByUser, + TextInStageEnabled, + TextInVoiceEnabled, + ThreadsEnabledTesting, + ThreadsEnabled, + ThreadDefaultAutoArchiveDuration, + ThreadsOnlyChannel, + TicketedEventsEnabled, + TicketingEnabled, + VanityUrls, + Verified, + VIPRegions, + VoiceChannelEffects, + WelcomeScreenEnabled, +} + +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct GuildConfiguration { + pub discovery: DiscoverConfiguration, + pub auto_join: AutoJoinConfiguration, + #[serde(default)] + pub default_features: Vec, +} diff --git a/types/config/types/kafka_configuration.rs b/types/config/types/kafka_configuration.rs new file mode 100644 index 0000000..050ffc3 --- /dev/null +++ b/types/config/types/kafka_configuration.rs @@ -0,0 +1,10 @@ +use serde::{Deserialize, Serialize}; + +use crate::config::types::subconfigs::kafka::KafkaBroker; + +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct KafkaConfiguration { + #[serde(default)] + pub brokers: Option>, +} diff --git a/types/config/types/limit_configuration.rs b/types/config/types/limit_configuration.rs new file mode 100644 index 0000000..ad2ad17 --- /dev/null +++ b/types/config/types/limit_configuration.rs @@ -0,0 +1,17 @@ +use serde::{Deserialize, Serialize}; + +use crate::config::types::subconfigs::limits::{ + channel::ChannelLimits, global::GlobalRateLimits, guild::GuildLimits, message::MessageLimits, + rates::RateLimits, user::UserLimits, +}; + +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct LimitsConfiguration { + pub user: UserLimits, + pub guild: GuildLimits, + pub message: MessageLimits, + pub channel: ChannelLimits, + pub rate: RateLimits, + pub absolute_rate: GlobalRateLimits, +} diff --git a/types/config/types/login_configuration.rs b/types/config/types/login_configuration.rs new file mode 100644 index 0000000..a2b1039 --- /dev/null +++ b/types/config/types/login_configuration.rs @@ -0,0 +1,8 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct LoginConfiguration { + pub require_captcha: bool, + pub require_verification: bool, +} diff --git a/types/config/types/metrics_configuration.rs b/types/config/types/metrics_configuration.rs new file mode 100644 index 0000000..336ac84 --- /dev/null +++ b/types/config/types/metrics_configuration.rs @@ -0,0 +1,13 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct MetricsConfiguration { + pub timeout: u64, +} + +impl Default for MetricsConfiguration { + fn default() -> Self { + Self { timeout: 30000 } + } +} diff --git a/types/config/types/mod.rs b/types/config/types/mod.rs new file mode 100644 index 0000000..dce4eb0 --- /dev/null +++ b/types/config/types/mod.rs @@ -0,0 +1,21 @@ +pub mod api_configuration; +pub mod cdn_configuration; +pub mod defaults_configuration; +pub mod email_configuration; +pub mod endpoint_configuration; +pub mod external_tokens_configuration; +pub mod general_configuration; +pub mod gif_configuration; +pub mod guild_configuration; +pub mod kafka_configuration; +pub mod limit_configuration; +pub mod login_configuration; +pub mod metrics_configuration; +pub mod password_reset_configuration; +pub mod rabbit_mq_configuration; +pub mod region_configuration; +pub mod register_configuration; +pub mod security_configuration; +pub mod sentry_configuration; +pub mod subconfigs; +pub mod template_configuration; diff --git a/types/config/types/password_reset_configuration.rs b/types/config/types/password_reset_configuration.rs new file mode 100644 index 0000000..4dddae9 --- /dev/null +++ b/types/config/types/password_reset_configuration.rs @@ -0,0 +1,7 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct PasswordResetConfiguration { + pub require_captcha: bool, +} diff --git a/types/config/types/rabbit_mq_configuration.rs b/types/config/types/rabbit_mq_configuration.rs new file mode 100644 index 0000000..2437055 --- /dev/null +++ b/types/config/types/rabbit_mq_configuration.rs @@ -0,0 +1,7 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RabbitMQConfiguration { + pub host: Option, +} diff --git a/types/config/types/region_configuration.rs b/types/config/types/region_configuration.rs new file mode 100644 index 0000000..065a8f7 --- /dev/null +++ b/types/config/types/region_configuration.rs @@ -0,0 +1,29 @@ +use serde::{Deserialize, Serialize}; + +use crate::config::types::subconfigs::region::Region; + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RegionConfiguration { + pub default: String, + pub use_default_as_optimal: bool, + pub available: Vec, +} + +impl Default for RegionConfiguration { + fn default() -> Self { + Self { + default: String::from("spacebar"), + use_default_as_optimal: true, + available: vec![Region { + id: String::from("spacebar"), + name: String::from("spacebar"), + endpoint: String::from("127.0.0.1:3004"), + location: None, + vip: false, + custom: false, + deprecated: false, + }], + } + } +} diff --git a/types/config/types/register_configuration.rs b/types/config/types/register_configuration.rs new file mode 100644 index 0000000..304299a --- /dev/null +++ b/types/config/types/register_configuration.rs @@ -0,0 +1,41 @@ +use serde::{Deserialize, Serialize}; + +use crate::config::types::subconfigs::register::{ + DateOfBirthConfiguration, PasswordConfiguration, RegistrationEmailConfiguration, +}; + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RegisterConfiguration { + pub email: RegistrationEmailConfiguration, + pub date_of_birth: DateOfBirthConfiguration, + pub password: PasswordConfiguration, + pub disabled: bool, + pub require_captcha: bool, + pub require_invite: bool, + pub guests_require_invite: bool, + pub allow_new_registration: bool, + pub allow_multiple_accounts: bool, + pub block_proxies: bool, + pub incrementing_discriminators: bool, + pub default_rights: String, +} + +impl Default for RegisterConfiguration { + fn default() -> Self { + Self { + email: RegistrationEmailConfiguration::default(), + date_of_birth: DateOfBirthConfiguration::default(), + password: PasswordConfiguration::default(), + disabled: false, + require_captcha: true, + require_invite: false, + guests_require_invite: true, + allow_new_registration: true, + allow_multiple_accounts: true, + block_proxies: true, + incrementing_discriminators: false, + default_rights: String::from("875069521787904"), + } + } +} diff --git a/types/config/types/security_configuration.rs b/types/config/types/security_configuration.rs new file mode 100644 index 0000000..65008da --- /dev/null +++ b/types/config/types/security_configuration.rs @@ -0,0 +1,42 @@ +use base64::Engine; +use serde::{Deserialize, Serialize}; + +use crate::config::types::subconfigs::security::{CaptchaConfiguration, TwoFactorConfiguration}; + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SecurityConfiguration { + pub captcha: CaptchaConfiguration, + pub two_factor: TwoFactorConfiguration, + pub auto_update: bool, + pub request_signature: String, + pub jwt_secret: String, + pub forwarded_for: Option, + pub ipdata_api_key: Option, + pub mfa_backup_code_count: u8, + pub stats_world_readable: bool, + pub default_registration_token_expiration: u64, +} + +impl Default for SecurityConfiguration { + fn default() -> Self { + let mut req_sig: [u8; 32] = [0; 32]; + let _ = openssl::rand::rand_bytes(&mut req_sig); + let mut jwt_secret: [u8; 256] = [0; 256]; + let _ = openssl::rand::rand_bytes(&mut jwt_secret); + Self { + captcha: Default::default(), + two_factor: Default::default(), + auto_update: true, + request_signature: base64::engine::general_purpose::STANDARD.encode(req_sig), + jwt_secret: base64::engine::general_purpose::STANDARD.encode(jwt_secret), + forwarded_for: None, + ipdata_api_key: Some(String::from( + "eca677b284b3bac29eb72f5e496aa9047f26543605efe99ff2ce35c9", + )), + mfa_backup_code_count: 10, + stats_world_readable: true, + default_registration_token_expiration: 1000 * 60 * 60 * 24 * 7, + } + } +} diff --git a/types/config/types/sentry_configuration.rs b/types/config/types/sentry_configuration.rs new file mode 100644 index 0000000..256bab9 --- /dev/null +++ b/types/config/types/sentry_configuration.rs @@ -0,0 +1,28 @@ +use std::ffi::OsString; + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SentryConfiguration { + pub enabled: bool, + pub endpoint: String, + pub trace_sample_rate: f64, + pub environment: String, +} + +impl Default for SentryConfiguration { + fn default() -> Self { + Self { + enabled: false, + endpoint: String::from( + "https://241c6fb08adb469da1bb82522b25c99f@sentry.quartzinc.space/3", + ), + trace_sample_rate: 1.0, + environment: hostname::get() + .unwrap_or_else(|_| OsString::new()) + .to_string_lossy() + .to_string(), + } + } +} diff --git a/types/config/types/subconfigs/client/mod.rs b/types/config/types/subconfigs/client/mod.rs new file mode 100644 index 0000000..5d3d304 --- /dev/null +++ b/types/config/types/subconfigs/client/mod.rs @@ -0,0 +1,17 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ClientReleaseConfiguration { + pub use_local_release: bool, + pub upstream_version: String, +} + +impl Default for ClientReleaseConfiguration { + fn default() -> Self { + Self { + use_local_release: true, + upstream_version: String::from("0.0.264"), + } + } +} diff --git a/types/config/types/subconfigs/defaults/guild.rs b/types/config/types/subconfigs/defaults/guild.rs new file mode 100644 index 0000000..966c7af --- /dev/null +++ b/types/config/types/subconfigs/defaults/guild.rs @@ -0,0 +1,23 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct GuildDefaults { + pub max_presences: u64, + pub max_video_channel_users: u16, + pub afk_timeout: u16, + pub default_message_notifications: u8, + pub explicit_content_filter: u8, +} + +impl Default for GuildDefaults { + fn default() -> Self { + Self { + max_presences: 250_000, + max_video_channel_users: 200, + afk_timeout: 300, + default_message_notifications: 1, + explicit_content_filter: 0, + } + } +} diff --git a/types/config/types/subconfigs/defaults/mod.rs b/types/config/types/subconfigs/defaults/mod.rs new file mode 100644 index 0000000..56d877f --- /dev/null +++ b/types/config/types/subconfigs/defaults/mod.rs @@ -0,0 +1,2 @@ +pub mod guild; +pub mod user; diff --git a/types/config/types/subconfigs/defaults/user.rs b/types/config/types/subconfigs/defaults/user.rs new file mode 100644 index 0000000..635d6d4 --- /dev/null +++ b/types/config/types/subconfigs/defaults/user.rs @@ -0,0 +1,19 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct UserDefaults { + pub premium: bool, + pub premium_type: u8, + pub verified: bool, +} + +impl Default for UserDefaults { + fn default() -> Self { + Self { + premium: true, + premium_type: 2, + verified: true, + } + } +} diff --git a/types/config/types/subconfigs/email/mailgun.rs b/types/config/types/subconfigs/email/mailgun.rs new file mode 100644 index 0000000..636e462 --- /dev/null +++ b/types/config/types/subconfigs/email/mailgun.rs @@ -0,0 +1,8 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct MailGunConfiguration { + pub api_key: Option, + pub domain: Option, +} diff --git a/types/config/types/subconfigs/email/mailjet.rs b/types/config/types/subconfigs/email/mailjet.rs new file mode 100644 index 0000000..4e505c1 --- /dev/null +++ b/types/config/types/subconfigs/email/mailjet.rs @@ -0,0 +1,8 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct MailJetConfiguration { + pub api_key: Option, + pub api_secret: Option, +} diff --git a/types/config/types/subconfigs/email/mod.rs b/types/config/types/subconfigs/email/mod.rs new file mode 100644 index 0000000..21253fd --- /dev/null +++ b/types/config/types/subconfigs/email/mod.rs @@ -0,0 +1,4 @@ +pub mod mailgun; +pub mod mailjet; +pub mod sendgrid; +pub mod smtp; diff --git a/types/config/types/subconfigs/email/sendgrid.rs b/types/config/types/subconfigs/email/sendgrid.rs new file mode 100644 index 0000000..879c719 --- /dev/null +++ b/types/config/types/subconfigs/email/sendgrid.rs @@ -0,0 +1,7 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SendGridConfiguration { + pub api_key: Option, +} diff --git a/types/config/types/subconfigs/email/smtp.rs b/types/config/types/subconfigs/email/smtp.rs new file mode 100644 index 0000000..a02c66f --- /dev/null +++ b/types/config/types/subconfigs/email/smtp.rs @@ -0,0 +1,10 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub struct SMTPConfiguration { + pub host: Option, + pub port: Option, + pub secure: bool, + pub username: Option, + pub password: Option, +} diff --git a/types/config/types/subconfigs/guild/autojoin.rs b/types/config/types/subconfigs/guild/autojoin.rs new file mode 100644 index 0000000..2d88d46 --- /dev/null +++ b/types/config/types/subconfigs/guild/autojoin.rs @@ -0,0 +1,22 @@ +use serde::{Deserialize, Serialize}; + +use crate::utils::Snowflake; + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct AutoJoinConfiguration { + pub enabled: bool, + #[serde(skip_serializing_if = "Option::is_none")] + pub guilds: Option>, + pub can_leave: bool, +} + +impl Default for AutoJoinConfiguration { + fn default() -> Self { + Self { + enabled: true, + guilds: None, + can_leave: true, + } + } +} diff --git a/types/config/types/subconfigs/guild/discovery.rs b/types/config/types/subconfigs/guild/discovery.rs new file mode 100644 index 0000000..1e283b0 --- /dev/null +++ b/types/config/types/subconfigs/guild/discovery.rs @@ -0,0 +1,21 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct DiscoverConfiguration { + pub show_all_guilds: bool, + pub use_recommendation: bool, + pub offset: u16, + pub limit: u16, +} + +impl Default for DiscoverConfiguration { + fn default() -> Self { + Self { + show_all_guilds: false, + use_recommendation: false, + offset: 0, + limit: 24, + } + } +} diff --git a/types/config/types/subconfigs/guild/mod.rs b/types/config/types/subconfigs/guild/mod.rs new file mode 100644 index 0000000..e4d7dcf --- /dev/null +++ b/types/config/types/subconfigs/guild/mod.rs @@ -0,0 +1,2 @@ +pub mod autojoin; +pub mod discovery; diff --git a/types/config/types/subconfigs/kafka/mod.rs b/types/config/types/subconfigs/kafka/mod.rs new file mode 100644 index 0000000..1ee4015 --- /dev/null +++ b/types/config/types/subconfigs/kafka/mod.rs @@ -0,0 +1,7 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct KafkaBroker { + pub ip: String, + pub port: u16, +} diff --git a/types/config/types/subconfigs/limits/channel.rs b/types/config/types/subconfigs/limits/channel.rs new file mode 100644 index 0000000..03e46e5 --- /dev/null +++ b/types/config/types/subconfigs/limits/channel.rs @@ -0,0 +1,19 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ChannelLimits { + pub max_pins: u16, + pub max_topic: u16, + pub max_webhooks: u16, +} + +impl Default for ChannelLimits { + fn default() -> Self { + Self { + max_pins: 500, + max_topic: 1024, + max_webhooks: 100, + } + } +} diff --git a/types/config/types/subconfigs/limits/global.rs b/types/config/types/subconfigs/limits/global.rs new file mode 100644 index 0000000..87f9e1c --- /dev/null +++ b/types/config/types/subconfigs/limits/global.rs @@ -0,0 +1,41 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct GlobalRateLimit { + pub limit: u16, + pub window: u64, + pub enabled: bool, +} + +impl Default for GlobalRateLimit { + fn default() -> Self { + Self { + limit: 100, + window: 60 * 60 * 1000, + enabled: true, + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct GlobalRateLimits { + pub register: GlobalRateLimit, + pub send_message: GlobalRateLimit, +} + +impl Default for GlobalRateLimits { + fn default() -> Self { + Self { + register: GlobalRateLimit { + limit: 25, + ..Default::default() + }, + send_message: GlobalRateLimit { + limit: 200, + window: 60 * 1000, + ..Default::default() + }, + } + } +} diff --git a/types/config/types/subconfigs/limits/guild.rs b/types/config/types/subconfigs/limits/guild.rs new file mode 100644 index 0000000..6def5a0 --- /dev/null +++ b/types/config/types/subconfigs/limits/guild.rs @@ -0,0 +1,23 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct GuildLimits { + pub max_roles: u16, + pub max_emojis: u16, + pub max_members: u64, + pub max_channels: u32, + pub max_channels_in_category: u32, +} + +impl Default for GuildLimits { + fn default() -> Self { + Self { + max_roles: 1000, + max_emojis: 20_000, + max_members: 25_000_000, + max_channels: 65_535, + max_channels_in_category: 65_535, + } + } +} diff --git a/types/config/types/subconfigs/limits/message.rs b/types/config/types/subconfigs/limits/message.rs new file mode 100644 index 0000000..9d368b9 --- /dev/null +++ b/types/config/types/subconfigs/limits/message.rs @@ -0,0 +1,26 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct MessageLimits { + pub max_characters: u32, + #[serde(default)] + pub max_tts_characters: u32, + pub max_reactions: u32, + pub max_attachment_size: u64, + pub max_bulk_delete: u32, + pub max_embed_download_size: u64, +} + +impl Default for MessageLimits { + fn default() -> Self { + Self { + max_characters: 1048576, + max_tts_characters: 160, + max_reactions: 2048, + max_attachment_size: 1024 * 1024 * 1024, + max_bulk_delete: 1000, + max_embed_download_size: 1024 * 1024 * 5, + } + } +} diff --git a/types/config/types/subconfigs/limits/mod.rs b/types/config/types/subconfigs/limits/mod.rs new file mode 100644 index 0000000..4dbc2fa --- /dev/null +++ b/types/config/types/subconfigs/limits/mod.rs @@ -0,0 +1,7 @@ +pub mod channel; +pub mod global; +pub mod guild; +pub mod message; +pub mod ratelimits; +pub mod rates; +pub mod user; diff --git a/types/config/types/subconfigs/limits/ratelimits/auth.rs b/types/config/types/subconfigs/limits/ratelimits/auth.rs new file mode 100644 index 0000000..3bfdccc --- /dev/null +++ b/types/config/types/subconfigs/limits/ratelimits/auth.rs @@ -0,0 +1,28 @@ +use serde::{Deserialize, Serialize}; + +use crate::config::types::subconfigs::limits::ratelimits::RateLimitOptions; + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct AuthRateLimit { + pub login: RateLimitOptions, + pub register: RateLimitOptions, +} + +impl Default for AuthRateLimit { + fn default() -> Self { + Self { + login: RateLimitOptions { + bot: None, + count: 5, + window: 60, + only_ip: false, + }, + register: RateLimitOptions { + bot: None, + count: 2, + window: 60 * 60 * 12, + only_ip: false, + }, + } + } +} diff --git a/types/config/types/subconfigs/limits/ratelimits/mod.rs b/types/config/types/subconfigs/limits/ratelimits/mod.rs new file mode 100644 index 0000000..66a2b78 --- /dev/null +++ b/types/config/types/subconfigs/limits/ratelimits/mod.rs @@ -0,0 +1,14 @@ +use serde::{Deserialize, Serialize}; + +pub mod auth; +pub mod route; + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RateLimitOptions { + pub bot: Option, + pub count: u64, + pub window: u64, + #[serde(default)] + pub only_ip: bool, +} diff --git a/types/config/types/subconfigs/limits/ratelimits/route.rs b/types/config/types/subconfigs/limits/ratelimits/route.rs new file mode 100644 index 0000000..04e5a42 --- /dev/null +++ b/types/config/types/subconfigs/limits/ratelimits/route.rs @@ -0,0 +1,37 @@ +use serde::{Deserialize, Serialize}; + +use crate::config::types::subconfigs::limits::ratelimits::{auth::AuthRateLimit, RateLimitOptions}; + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct RouteRateLimit { + pub guild: RateLimitOptions, + pub webhook: RateLimitOptions, + pub channel: RateLimitOptions, + pub auth: AuthRateLimit, +} + +impl Default for RouteRateLimit { + fn default() -> Self { + Self { + guild: RateLimitOptions { + bot: None, + count: 5, + window: 5, + only_ip: false, + }, + webhook: RateLimitOptions { + bot: None, + count: 10, + window: 5, + only_ip: false, + }, + channel: RateLimitOptions { + bot: None, + count: 10, + window: 5, + only_ip: false, + }, + auth: AuthRateLimit::default(), + } + } +} diff --git a/types/config/types/subconfigs/limits/rates.rs b/types/config/types/subconfigs/limits/rates.rs new file mode 100644 index 0000000..3185b37 --- /dev/null +++ b/types/config/types/subconfigs/limits/rates.rs @@ -0,0 +1,41 @@ +use serde::{Deserialize, Serialize}; + +use crate::config::types::subconfigs::limits::ratelimits::{ + route::RouteRateLimit, RateLimitOptions, +}; + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct RateLimits { + pub enabled: bool, + pub ip: RateLimitOptions, + pub global: RateLimitOptions, + pub error: RateLimitOptions, + pub routes: RouteRateLimit, +} + +impl Default for RateLimits { + fn default() -> Self { + Self { + enabled: false, + ip: RateLimitOptions { + bot: None, + count: 500, + window: 5, + only_ip: false, + }, + global: RateLimitOptions { + bot: None, + count: 250, + window: 5, + only_ip: false, + }, + error: RateLimitOptions { + bot: None, + count: 10, + window: 5, + only_ip: false, + }, + routes: RouteRateLimit::default(), + } + } +} diff --git a/types/config/types/subconfigs/limits/user.rs b/types/config/types/subconfigs/limits/user.rs new file mode 100644 index 0000000..e43b746 --- /dev/null +++ b/types/config/types/subconfigs/limits/user.rs @@ -0,0 +1,19 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct UserLimits { + pub max_guilds: u64, + pub max_username: u16, + pub max_friends: u64, +} + +impl Default for UserLimits { + fn default() -> Self { + Self { + max_guilds: 1048576, + max_username: 32, + max_friends: 5000, + } + } +} diff --git a/types/config/types/subconfigs/mod.rs b/types/config/types/subconfigs/mod.rs new file mode 100644 index 0000000..4c85096 --- /dev/null +++ b/types/config/types/subconfigs/mod.rs @@ -0,0 +1,9 @@ +pub mod client; +pub mod defaults; +pub mod email; +pub mod guild; +pub mod kafka; +pub mod limits; +pub mod region; +pub mod register; +pub mod security; diff --git a/types/config/types/subconfigs/region/mod.rs b/types/config/types/subconfigs/region/mod.rs new file mode 100644 index 0000000..e0b1800 --- /dev/null +++ b/types/config/types/subconfigs/region/mod.rs @@ -0,0 +1,21 @@ +use serde::{Deserialize, Serialize}; + + + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct LatLong { + pub latitude: f64, + pub longitude: f64, +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct Region { + pub id: String, + pub name: String, + pub endpoint: String, + pub location: Option, + pub vip: bool, + pub custom: bool, + #[serde(default)] + pub deprecated: bool, +} diff --git a/types/config/types/subconfigs/register/date_of_birth.rs b/types/config/types/subconfigs/register/date_of_birth.rs new file mode 100644 index 0000000..9c1bec1 --- /dev/null +++ b/types/config/types/subconfigs/register/date_of_birth.rs @@ -0,0 +1,16 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct DateOfBirthConfiguration { + pub required: bool, + pub minimum: u8, +} + +impl Default for DateOfBirthConfiguration { + fn default() -> Self { + Self { + required: true, + minimum: 13, + } + } +} diff --git a/types/config/types/subconfigs/register/email.rs b/types/config/types/subconfigs/register/email.rs new file mode 100644 index 0000000..ac99bfc --- /dev/null +++ b/types/config/types/subconfigs/register/email.rs @@ -0,0 +1,21 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct RegistrationEmailConfiguration { + pub required: bool, + pub allowlist: bool, + #[serde(default)] + pub blacklist: bool, + #[serde(default)] + pub domains: Vec, +} +impl Default for RegistrationEmailConfiguration { + fn default() -> Self { + Self { + required: false, + allowlist: false, + blacklist: true, + domains: Vec::new(), + } + } +} diff --git a/types/config/types/subconfigs/register/mod.rs b/types/config/types/subconfigs/register/mod.rs new file mode 100644 index 0000000..ad92571 --- /dev/null +++ b/types/config/types/subconfigs/register/mod.rs @@ -0,0 +1,7 @@ +mod date_of_birth; +mod email; +mod password; + +pub use date_of_birth::DateOfBirthConfiguration; +pub use email::RegistrationEmailConfiguration; +pub use password::PasswordConfiguration; diff --git a/types/config/types/subconfigs/register/password.rs b/types/config/types/subconfigs/register/password.rs new file mode 100644 index 0000000..9247f7d --- /dev/null +++ b/types/config/types/subconfigs/register/password.rs @@ -0,0 +1,23 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct PasswordConfiguration { + pub required: bool, + pub min_length: u8, + pub min_numbers: u8, + pub min_upper_case: u8, + pub min_symbols: u8, +} + +impl Default for PasswordConfiguration { + fn default() -> Self { + Self { + required: false, + min_length: 8, + min_numbers: 2, + min_upper_case: 2, + min_symbols: 0, + } + } +} diff --git a/types/config/types/subconfigs/security/captcha.rs b/types/config/types/subconfigs/security/captcha.rs new file mode 100644 index 0000000..82bb517 --- /dev/null +++ b/types/config/types/subconfigs/security/captcha.rs @@ -0,0 +1,27 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "lowercase")] +pub enum CaptchaService { + Recaptcha, + HCaptcha, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct CaptchaConfiguration { + pub enabled: bool, + pub service: CaptchaService, + pub sitekey: Option, + pub secret: Option, +} + +impl Default for CaptchaConfiguration { + fn default() -> Self { + Self { + enabled: false, + service: CaptchaService::HCaptcha, + sitekey: None, + secret: None, + } + } +} diff --git a/types/config/types/subconfigs/security/mod.rs b/types/config/types/subconfigs/security/mod.rs new file mode 100644 index 0000000..ceeb0d3 --- /dev/null +++ b/types/config/types/subconfigs/security/mod.rs @@ -0,0 +1,5 @@ +mod captcha; +mod twofactor; + +pub use captcha::{CaptchaConfiguration, CaptchaService}; +pub use twofactor::TwoFactorConfiguration; diff --git a/types/config/types/subconfigs/security/twofactor.rs b/types/config/types/subconfigs/security/twofactor.rs new file mode 100644 index 0000000..39a0373 --- /dev/null +++ b/types/config/types/subconfigs/security/twofactor.rs @@ -0,0 +1,15 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct TwoFactorConfiguration { + pub generate_backup_codes: bool, +} + +impl Default for TwoFactorConfiguration { + fn default() -> Self { + Self { + generate_backup_codes: true, + } + } +} diff --git a/types/config/types/template_configuration.rs b/types/config/types/template_configuration.rs new file mode 100644 index 0000000..932670e --- /dev/null +++ b/types/config/types/template_configuration.rs @@ -0,0 +1,21 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct TemplateConfiguration { + pub enabled: bool, + pub allow_template_creation: bool, + pub allow_discord_templates: bool, + pub allow_raws: bool, +} + +impl Default for TemplateConfiguration { + fn default() -> Self { + Self { + enabled: true, + allow_template_creation: true, + allow_discord_templates: true, + allow_raws: true, + } + } +} diff --git a/types/entities/application.rs b/types/entities/application.rs new file mode 100644 index 0000000..b838a5b --- /dev/null +++ b/types/entities/application.rs @@ -0,0 +1,140 @@ + + +use crate::utils::Snowflake; +use bitflags::{bitflags, Flags}; +use serde::{Deserialize, Serialize}; +use serde_json::Value; +#[cfg(feature = "sqlx")] +use sqlx::FromRow; + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[cfg_attr(feature = "sqlx", derive(FromRow))] +pub struct Application { + pub id: Snowflake, + pub name: String, + pub icon: Option, + pub description: Option, + pub summary: Option, + #[cfg(feature = "sqlx")] + pub r#type: Option>, + #[cfg(not(feature = "sqlx"))] + pub r#type: Option, + pub hook: bool, + pub bot_public: bool, + pub bot_require_code_grant: bool, + pub verify_key: String, + pub owner_id: Snowflake, + pub flags: u64, + #[cfg(feature = "sqlx")] + pub redirect_uris: Option>>, + #[cfg(not(feature = "sqlx"))] + pub redirect_uris: Option>, + pub rpc_application_state: i64, + pub store_application_state: i64, + pub verification_state: i64, + pub interactions_endpoint_url: Option, + pub integration_public: bool, + pub integration_require_code_grant: bool, + pub discoverability_state: i64, + pub discovery_eligibility_flags: i64, + pub bot_user_id: Snowflake, + #[cfg(feature = "sqlx")] + pub tags: Option>>, + #[cfg(not(feature = "sqlx"))] + pub tags: Option>, + pub cover_image: Option, + #[cfg(feature = "sqlx")] + pub install_params: Option>, + #[cfg(not(feature = "sqlx"))] + pub install_params: Option, + pub terms_of_service_url: Option, + pub privacy_policy_url: Option, + pub team_id: Option, +} + +impl Application { + pub fn flags(&self) -> ApplicationFlags { + ApplicationFlags::from_bits(self.flags.to_owned()).unwrap() + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct InstallParams { + pub scopes: Vec, + pub permissions: String, +} + +bitflags! { + #[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)] + pub struct ApplicationFlags: u64 { + const APPLICATION_AUTO_MODERATION_RULE_CREATE_BADGE = 1 << 6; + const GATEWAY_PRESENCE = 1 << 12; + const GATEWAY_PRESENCE_LIMITED = 1 << 13; + const GATEWAY_GUILD_MEMBERS = 1 << 14; + const GATEWAY_GUILD_MEMBERS_LIMITED = 1 << 15; + const VERIFICATION_PENDING_GUILD_LIMIT = 1 << 16; + const EMBEDDED = 1 << 17; + const GATEWAY_MESSAGE_CONTENT = 1 << 18; + const GATEWAY_MESSAGE_CONTENT_LIMITED = 1 << 19; + const APPLICATION_COMMAND_BADGE = 1 << 23; + } +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct ApplicationCommand { + pub id: Snowflake, + pub application_id: Snowflake, + pub name: String, + pub description: String, + pub options: Vec, +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct ApplicationCommandOption { + pub r#type: ApplicationCommandOptionType, + pub name: String, + pub description: String, + pub required: bool, + pub choices: Vec, + pub options: Vec, +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct ApplicationCommandOptionChoice { + pub name: String, + pub value: Value, +} + +#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)] +pub enum ApplicationCommandOptionType { + #[serde(rename = "SUB_COMMAND")] + SubCommand = 1, + #[serde(rename = "SUB_COMMAND_GROUP")] + SubCommandGroup = 2, + #[serde(rename = "STRING")] + String = 3, + #[serde(rename = "INTEGER")] + Integer = 4, + #[serde(rename = "BOOLEAN")] + Boolean = 5, + #[serde(rename = "USER")] + User = 6, + #[serde(rename = "CHANNEL")] + Channel = 7, + #[serde(rename = "ROLE")] + Role = 8, +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct ApplicationCommandInteractionData { + pub id: Snowflake, + pub name: String, + pub options: Vec, +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct ApplicationCommandInteractionDataOption { + pub name: String, + pub value: Value, + pub options: Vec, +} diff --git a/types/entities/attachment.rs b/types/entities/attachment.rs new file mode 100644 index 0000000..56384f7 --- /dev/null +++ b/types/entities/attachment.rs @@ -0,0 +1,113 @@ +use serde::{Deserialize, Serialize}; + +use crate::utils::Snowflake; + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct Attachment { + pub id: Snowflake, + pub filename: String, + pub description: Option, + pub content_type: Option, + pub size: u64, + pub url: String, + pub proxy_url: String, + pub height: Option, + pub width: Option, + pub message_id: Snowflake, + pub ephemeral: Option, + pub duration_secs: Option, + pub waveform: Option, + #[serde(skip_serializing)] + pub content: Vec, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] + +pub struct PartialDiscordFileAttachment { + pub id: Option, + pub filename: String, + pub description: Option, + pub content_type: Option, + pub size: Option, + pub url: Option, + pub proxy_url: Option, + pub height: Option, + pub width: Option, + pub ephemeral: Option, + pub duration_secs: Option, + pub waveform: Option, + #[serde(skip_serializing)] + pub content: Vec, +} + +impl PartialDiscordFileAttachment { + /** + Moves `self.content` out of `self` and returns it. + # Returns + Vec + */ + pub fn move_content(self) -> (Vec, PartialDiscordFileAttachment) { + let content = self.content; + let updated_struct = PartialDiscordFileAttachment { + id: self.id, + filename: self.filename, + description: self.description, + content_type: self.content_type, + size: self.size, + url: self.url, + proxy_url: self.proxy_url, + height: self.height, + width: self.width, + ephemeral: self.ephemeral, + duration_secs: self.duration_secs, + waveform: self.waveform, + content: Vec::new(), + }; + (content, updated_struct) + } + + pub fn move_filename(self) -> (String, PartialDiscordFileAttachment) { + let filename = self.filename; + let updated_struct = PartialDiscordFileAttachment { + id: self.id, + filename: String::new(), + description: self.description, + content_type: self.content_type, + size: self.size, + url: self.url, + proxy_url: self.proxy_url, + height: self.height, + width: self.width, + + ephemeral: self.ephemeral, + duration_secs: self.duration_secs, + waveform: self.waveform, + content: self.content, + }; + (filename, updated_struct) + } + + pub fn move_content_type(self) -> (Option, PartialDiscordFileAttachment) { + let content_type = self.content_type; + let updated_struct = PartialDiscordFileAttachment { + id: self.id, + filename: self.filename, + description: self.description, + content_type: None, + size: self.size, + url: self.url, + proxy_url: self.proxy_url, + height: self.height, + width: self.width, + ephemeral: self.ephemeral, + duration_secs: self.duration_secs, + waveform: self.waveform, + content: self.content, + }; + (content_type, updated_struct) + } + + pub fn set_id(&mut self, id: i16) { + self.id = Some(id); + } +} diff --git a/types/entities/channel.rs b/types/entities/channel.rs new file mode 100644 index 0000000..71c65a1 --- /dev/null +++ b/types/entities/channel.rs @@ -0,0 +1,116 @@ +use serde::{Deserialize, Serialize}; + +use crate::{ + entities::{GuildMember, User}, + utils::Snowflake, +}; + +#[derive(Default, Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] +pub struct Channel { + pub id: Snowflake, + #[serde(rename = "type")] + pub channel_type: ChannelType, + pub guild_id: Option, + pub position: Option, + pub permission_overwrites: Option>, + pub name: Option, + pub topic: Option, + pub nsfw: Option, + pub last_message_id: Option, + pub bitrate: Option, + pub user_limit: Option, + pub rate_limit_per_user: Option, + pub recipients: Option>, + pub icon: Option, + pub owner_id: Option, + pub application_id: Option, + pub parent_id: Option, + pub last_pin_timestamp: Option, + pub rtc_region: Option, + pub video_quality_mode: Option, + pub message_count: Option, + pub member_count: Option, + pub thread_metadata: Option, + pub member: Option, + pub default_auto_archive_duration: Option, + pub permissions: Option, + pub flags: Option, + pub total_message_sent: Option, + pub available_tags: Option>, + pub applied_tags: Option>, + pub default_reaction_emoji: Option, + pub default_thread_rate_limit_per_user: Option, + pub default_sort_order: Option, + pub default_forum_layout: Option, +} + +#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)] +pub struct Tag { + pub id: u64, + pub name: String, + pub moderated: bool, + pub emoji_id: Option, + pub emoji_name: Option, +} + +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] +pub struct PermissionOverwrite { + pub id: String, + #[serde(rename = "type")] + pub overwrite_type: u8, + pub allow: String, + pub deny: String, +} + +#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)] +pub struct ThreadMetadata { + pub archived: bool, + pub auto_archive_duration: i32, + pub archive_timestamp: String, + pub locked: bool, + pub invitable: Option, + pub create_timestamp: Option, +} + +#[derive(Default, Debug, Deserialize, Serialize, Clone, PartialEq, Eq)] +pub struct ThreadMember { + pub id: Option, + pub user_id: Option, + pub join_timestamp: Option, + pub flags: Option, + pub member: Option, +} + +#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)] +pub struct DefaultReaction { + pub emoji_id: Option, + pub emoji_name: Option, +} + +#[derive(Default, Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq)] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] +#[repr(i32)] +pub enum ChannelType { + #[default] + GuildText = 0, + Dm = 1, + GuildVoice = 2, + GroupDm = 3, + GuildCategory = 4, + GuildNews = 5, + GuildStore = 6, + Encrypted = 7, + EncryptedThreads = 8, + Transactional = 9, + GuildNewsThread = 10, + GuildPublicThread = 11, + GuildPrivateThread = 12, + GuildStageVoice = 13, + Directory = 14, + GuildForum = 15, + TicketTracker = 33, + Kanban = 34, + VoicelessWhiteboard = 35, + CustomStart = 64, + Unhandled = 255, +} diff --git a/types/entities/config.rs b/types/entities/config.rs new file mode 100644 index 0000000..25b1ef1 --- /dev/null +++ b/types/entities/config.rs @@ -0,0 +1,34 @@ +use serde::{Deserialize, Serialize}; +use serde_json::Value; +#[cfg(feature = "sqlx")] +use sqlx::FromRow; + +#[derive(Debug, Serialize, Deserialize)] +#[cfg_attr(feature = "sqlx", derive(FromRow))] +pub struct ConfigEntity { + pub key: String, + pub value: Option, +} + +impl ConfigEntity { + pub fn as_string(&self) -> Option { + let Some(v) = self.value.as_ref() else { + return None; + }; + Some(v.as_str().expect("value is not a string").to_string()) + } + + pub fn as_bool(&self) -> Option { + let Some(v) = self.value.as_ref() else { + return None; + }; + Some(v.as_bool().expect("value is not a boolean")) + } + + pub fn as_int(&self) -> Option { + let Some(v) = self.value.as_ref() else { + return None; + }; + Some(v.as_i64().expect("value is not a number")) + } +} diff --git a/types/entities/emoji.rs b/types/entities/emoji.rs new file mode 100644 index 0000000..2698607 --- /dev/null +++ b/types/entities/emoji.rs @@ -0,0 +1,15 @@ +use serde::{Deserialize, Serialize}; + +use crate::entities::User; + +#[derive(Debug, Deserialize, Serialize, Default, Clone)] +pub struct Emoji { + pub id: Option, + pub name: Option, + pub roles: Option>, + pub user: Option, + pub require_colons: Option, + pub managed: Option, + pub animated: Option, + pub available: Option, +} diff --git a/types/entities/guild.rs b/types/entities/guild.rs new file mode 100644 index 0000000..9b2abe7 --- /dev/null +++ b/types/entities/guild.rs @@ -0,0 +1,117 @@ +use chrono::{DateTime, Utc}; +use serde::{Deserialize, Serialize}; + +use crate::{ + entities::{Channel, Emoji, GuildTemplate, RoleObject, Sticker, User, VoiceState, Webhook}, + interfaces::WelcomeScreenObject, + utils::Snowflake, +}; + +/// See https://discord.com/developers/docs/resources/guild +#[derive(Serialize, Deserialize, Debug, Default, Clone)] +pub struct Guild { + pub id: Snowflake, + pub name: String, + pub icon: Option, + pub icon_hash: Option, + pub splash: Option, + pub discovery_splash: Option, + pub owner: Option, + pub owner_id: Option, + pub permissions: Option, + pub afk_channel_id: Option, + pub afk_timeout: Option, + pub widget_enabled: Option, + pub widget_channel_id: Option, + pub widget_channel: Option, + pub verification_level: Option, + pub default_message_notifications: Option, + pub explicit_content_filter: Option, + pub roles: Vec, + pub emojis: Vec, + pub features: Vec, + pub application_id: Option, + pub system_channel_id: Option, + pub system_channel_flags: Option, + pub rules_channel_id: Option, + pub rules_channel: Option, + pub max_presences: Option, + pub max_members: Option, + pub vanity_url_code: Option, + pub description: Option, + pub banner: Option, + pub premium_tier: Option, + pub premium_subscription_count: Option, + pub preferred_locale: Option, + pub public_updates_channel_id: Option, + pub public_updates_channel: Option, + pub max_video_channel_users: Option, + pub max_stage_video_channel_users: Option, + pub approximate_member_count: Option, + pub approximate_presence_count: Option, + pub member_count: Option, + pub presence_count: Option, + pub welcome_screen: Option, + pub nsfw_level: u8, + pub nsfw: bool, + pub stickers: Option>, + pub premium_progress_bar_enabled: Option, + pub joined_at: String, + pub afk_channel: Option, + pub bans: Option>, + pub primary_category_id: Option, + pub large: Option, + pub channels: Option>, + pub template_id: Option, + pub template: Option, + pub invites: Option>, + pub voice_states: Option>, + pub webhooks: Option>, + pub mfa_level: Option, + pub region: Option, + pub unavailable: bool, + pub parent: Option, +} + +/// See https://docs.spacebar.chat/routes/#get-/guilds/-guild_id-/bans/-user- +#[derive(Serialize, Deserialize, Debug, Default, Clone)] +pub struct GuildBan { + pub id: Snowflake, + pub user_id: Snowflake, + pub guild_id: Snowflake, + pub executor_id: Snowflake, + pub reason: Option, +} + +/// See https://docs.spacebar.chat/routes/#cmp--schemas-invite +#[derive(Serialize, Deserialize, Debug, Default, Clone)] +pub struct GuildInvite { + pub code: String, + pub temporary: Option, + pub uses: Option, + pub max_uses: Option, + pub max_age: Option, + pub created_at: DateTime, + pub expires_at: Option>, + pub guild_id: String, + pub guild: Option, + pub channel_id: String, + pub channel: Option, + pub inviter_id: Option, + pub inviter: Option, + pub target_user_id: Option, + pub target_user: Option, + pub target_user_type: Option, + pub vanity_url: Option, +} + +#[derive(Serialize, Deserialize, Debug, Default)] +pub struct UnavailableGuild { + id: String, + unavailable: bool, +} + +#[derive(Serialize, Deserialize, Debug, Default, Clone)] +pub struct GuildCreateResponse { + pub id: String, +} diff --git a/types/entities/guild_member.rs b/types/entities/guild_member.rs new file mode 100644 index 0000000..272baa7 --- /dev/null +++ b/types/entities/guild_member.rs @@ -0,0 +1,19 @@ +use serde::{Deserialize, Serialize}; + +use crate::entities::User; + +#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)] +pub struct GuildMember { + pub user: Option, + pub nick: Option, + pub avatar: Option, + pub roles: Vec, + pub joined_at: String, + pub premium_since: Option, + pub deaf: bool, + pub mute: bool, + pub flags: i32, + pub pending: Option, + pub permissions: Option, + pub communication_disabled_until: Option, +} diff --git a/types/entities/integration.rs b/types/entities/integration.rs new file mode 100644 index 0000000..95b0de6 --- /dev/null +++ b/types/entities/integration.rs @@ -0,0 +1,36 @@ +use chrono::{DateTime, Utc}; +use serde::{Deserialize, Serialize}; + +use crate::{ + entities::{Application, User}, + utils::Snowflake, +}; + +#[derive(Default, Debug, Deserialize, Serialize, Clone)] +/// See https://discord.com/developers/docs/resources/guild#integration-object-integration-structure +pub struct Integration { + pub id: Snowflake, + pub name: String, + #[serde(rename = "type")] + pub integration_type: String, + pub enabled: bool, + pub syncing: Option, + pub role_id: Option, + pub enabled_emoticons: Option, + pub expire_behaviour: Option, + pub expire_grace_period: Option, + pub user: Option, + pub account: IntegrationAccount, + pub synced_at: Option>, + pub subscriber_count: Option, + pub revoked: Option, + pub application: Option, + pub scopes: Option>, +} + +#[derive(Default, Debug, Deserialize, Serialize, Clone)] +/// See https://discord.com/developers/docs/resources/guild#integration-account-object-integration-account-structure +pub struct IntegrationAccount { + pub id: String, + pub name: String, +} diff --git a/types/entities/message.rs b/types/entities/message.rs new file mode 100644 index 0000000..914affa --- /dev/null +++ b/types/entities/message.rs @@ -0,0 +1,185 @@ +use serde::{Deserialize, Serialize}; + +use crate::{ + entities::{ + Application, Attachment, Channel, Emoji, GuildMember, RoleSubscriptionData, Sticker, + StickerItem, User, + }, + utils::Snowflake, +}; + +#[derive(Debug, Serialize, Deserialize, Default)] +pub struct Message { + id: Snowflake, + pub channel_id: Snowflake, + author: User, + content: String, + timestamp: String, + edited_timestamp: Option, + tts: bool, + mention_everyone: bool, + mentions: Vec, + mention_roles: Vec, + mention_channels: Option>, + pub attachments: Vec, + embeds: Vec, + reactions: Option>, + nonce: Option, + pinned: bool, + webhook_id: Option, + #[serde(rename = "type")] + message_type: i32, + activity: Option, + application: Option, + application_id: Option, + message_reference: Option, + flags: Option, + referenced_message: Option>, + interaction: Option, + thread: Option, + components: Option>, + sticker_items: Option>, + stickers: Option>, + position: Option, + role_subscription_data: Option, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct MessageReference { + pub message_id: Snowflake, + pub channel_id: Snowflake, + pub guild_id: Option, + pub fail_if_not_exists: Option, +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct MessageInteraction { + pub id: Snowflake, + #[serde(rename = "type")] + pub interaction_type: u8, + pub name: String, + pub user: User, + pub member: Option, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct AllowedMention { + parse: Vec, + roles: Vec, + users: Vec, + replied_user: bool, +} + +#[derive(Debug, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +pub enum AllowedMentionType { + Roles, + Users, + Everyone, +} + +#[derive(Debug, Serialize, Deserialize)] +struct ChannelMention { + id: Snowflake, + guild_id: Snowflake, + #[serde(rename = "type")] + channel_type: i32, + name: String, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct Embed { + title: Option, + #[serde(rename = "type")] + embed_type: Option, + description: Option, + url: Option, + timestamp: Option, + color: Option, + footer: Option, + image: Option, + thumbnail: Option, + video: Option, + provider: Option, + author: Option, + fields: Option>, +} + +#[derive(Debug, Serialize, Deserialize)] +struct EmbedFooter { + text: String, + icon_url: Option, + proxy_icon_url: Option, +} + +#[derive(Debug, Serialize, Deserialize)] +struct EmbedImage { + url: String, + proxy_url: String, + height: Option, + width: Option, +} + +#[derive(Debug, Serialize, Deserialize)] +struct EmbedThumbnail { + url: String, + proxy_url: Option, + height: Option, + width: Option, +} + +#[derive(Debug, Serialize, Deserialize)] +struct EmbedVideo { + url: Option, + proxy_url: Option, + height: Option, + width: Option, +} + +#[derive(Debug, Serialize, Deserialize)] +struct EmbedProvider { + name: Option, + url: Option, +} + +#[derive(Debug, Serialize, Deserialize)] +struct EmbedAuthor { + name: String, + url: Option, + icon_url: Option, + proxy_icon_url: Option, +} + +#[derive(Debug, Serialize, Deserialize)] + +struct EmbedField { + name: String, + value: String, + inline: Option, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct Reaction { + pub count: i32, + pub me: bool, + pub emoji: Emoji, +} + +#[derive(Debug, Serialize, Deserialize)] +pub enum Component { + ActionRow = 1, + Button = 2, + StringSelect = 3, + TextInput = 4, + UserSelect = 5, + RoleSelect = 6, + MentionableSelect = 7, + ChannelSelect = 8, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct MessageActivity { + #[serde(rename = "type")] + pub activity_type: i64, + pub party_id: Option, +} diff --git a/types/entities/mod.rs b/types/entities/mod.rs new file mode 100644 index 0000000..006aac1 --- /dev/null +++ b/types/entities/mod.rs @@ -0,0 +1,37 @@ +mod application; +mod attachment; +mod channel; +mod config; +mod emoji; +mod guild; +mod guild_member; +mod integration; +mod message; +mod role; +mod security_key; +mod sticker; +mod team; +mod template; +mod user; +mod user_settings; +mod voice_state; +mod webhook; + +pub use application::*; +pub use attachment::*; +pub use channel::*; +pub use config::*; +pub use emoji::*; +pub use guild::*; +pub use guild_member::*; +pub use integration::*; +pub use message::*; +pub use role::*; +pub use security_key::*; +pub use sticker::*; +pub use team::*; +pub use template::*; +pub use user::*; +pub use user_settings::*; +pub use voice_state::*; +pub use webhook::*; diff --git a/types/entities/role.rs b/types/entities/role.rs new file mode 100644 index 0000000..f440775 --- /dev/null +++ b/types/entities/role.rs @@ -0,0 +1,28 @@ +use serde::{Deserialize, Serialize}; + +use crate::utils::Snowflake; + +#[derive(Serialize, Deserialize, Debug, Default, Clone)] +/// See https://discord.com/developers/docs/topics/permissions#role-object +pub struct RoleObject { + pub id: Snowflake, + pub name: String, + pub color: f64, + pub hoist: bool, + pub icon: Option, + pub unicode_emoji: Option, + pub position: u16, + pub permissions: String, + pub managed: bool, + pub mentionable: bool, + // to:do add role tags https://discord.com/developers/docs/topics/permissions#role-object-role-tags-structure + //pub tags: Option +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct RoleSubscriptionData { + pub role_subscription_listing_id: Snowflake, + pub tier_name: String, + pub total_months_subscribed: u32, + pub is_renewal: bool, +} diff --git a/types/entities/security_key.rs b/types/entities/security_key.rs new file mode 100644 index 0000000..9eaa378 --- /dev/null +++ b/types/entities/security_key.rs @@ -0,0 +1,26 @@ +use serde::{Deserialize, Serialize}; + +use crate::utils::Snowflake; + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct SecurityKey { + pub id: String, + pub user_id: String, + pub key_id: String, + pub public_key: String, + pub counter: u64, + pub name: String, +} + +impl Default for SecurityKey { + fn default() -> Self { + Self { + id: Snowflake::generate().to_string(), + user_id: String::new(), + key_id: String::new(), + public_key: String::new(), + counter: 0, + name: String::new(), + } + } +} diff --git a/types/entities/sticker.rs b/types/entities/sticker.rs new file mode 100644 index 0000000..c3a8950 --- /dev/null +++ b/types/entities/sticker.rs @@ -0,0 +1,27 @@ +use serde::{Deserialize, Serialize}; + +use crate::{entities::User, utils::Snowflake}; + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct Sticker { + pub id: Snowflake, + pub pack_id: Option, + pub name: String, + pub description: Option, + pub tags: String, + pub asset: Option, + #[serde(rename = "type")] + pub sticker_type: u8, + pub format_type: u8, + pub available: Option, + pub guild_id: Option, + pub user: Option, + pub sort_value: Option, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct StickerItem { + pub id: Snowflake, + pub name: String, + pub format_type: u8, +} diff --git a/types/entities/team.rs b/types/entities/team.rs new file mode 100644 index 0000000..5bcf863 --- /dev/null +++ b/types/entities/team.rs @@ -0,0 +1,20 @@ +use serde::{Deserialize, Serialize}; + +use crate::entities::User; + +#[derive(Debug, Deserialize, Serialize, Clone)] +pub struct Team { + pub icon: Option, + pub id: u64, + pub members: Vec, + pub name: String, + pub owner_user_id: u64, +} + +#[derive(Debug, Deserialize, Serialize, Clone)] +pub struct TeamMember { + pub membership_state: u8, + pub permissions: Vec, + pub team_id: u64, + pub user: User, +} diff --git a/types/entities/template.rs b/types/entities/template.rs new file mode 100644 index 0000000..d640320 --- /dev/null +++ b/types/entities/template.rs @@ -0,0 +1,24 @@ +use chrono::{DateTime, Utc}; +use serde::{Deserialize, Serialize}; + +use crate::{ + entities::{Guild, User}, + utils::Snowflake, +}; + +/// See https://docs.spacebar.chat/routes/#cmp--schemas-template +#[derive(Serialize, Deserialize, Debug, Default, Clone)] +pub struct GuildTemplate { + pub code: String, + pub name: String, + pub description: Option, + pub usage_count: Option, + pub creator_id: Snowflake, + pub creator: User, + pub created_at: DateTime, + pub updated_at: DateTime, + pub source_guild_id: String, + pub source_guild: Vec, // Unsure how a {recursive: Guild} looks like, might be a Vec? + pub serialized_source_guild: Vec, + id: Snowflake, +} diff --git a/types/entities/user.rs b/types/entities/user.rs new file mode 100644 index 0000000..ce3563c --- /dev/null +++ b/types/entities/user.rs @@ -0,0 +1,112 @@ +use chrono::{DateTime, Utc}; +use serde::{Deserialize, Serialize}; +use serde_json::{Map, Value}; +#[cfg(feature = "sqlx")] +use sqlx::{FromRow, Type}; + +use crate::{ + errors::Error, + utils::Snowflake, //util::{email::adjust_email, entities::user_setting::UserSettings}, +}; + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)] +#[cfg_attr(feature = "sqlx", derive(Type))] +pub struct UserData { + pub valid_tokens_since: DateTime, + pub hash: Option, +} + +#[derive(Serialize, Deserialize, Debug, Default, Clone)] +pub struct User { + pub id: String, + username: String, + discriminator: String, + avatar: Option, + bot: bool, + system: Option, + mfa_enabled: Option, + accent_color: Option, + locale: Option, + verified: Option, + email: Option, + flags: String, + premium_since: Option, + premium_type: i8, + pronouns: Option, + public_flags: Option, + banner: Option, + bio: String, + theme_colors: Option>, + phone: Option, + nsfw_allowed: bool, + premium: bool, + purchased_flags: i32, + premium_usage_flags: i32, + disabled: Option, +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct User { + pub id: Snowflake, + pub username: String, + pub discriminator: String, + pub avatar: Option, + pub accent_color: Option, + pub banner: Option, + pub theme_colors: Option>, + pub pronouns: Option, + pub bot: bool, + pub bio: String, + pub premium_type: u8, + pub premium_since: Option>, + pub public_flags: u16, +} + +impl From for PublicUser { + fn from(value: User) -> Self { + Self { + id: value.id, + username: value.username, + discriminator: value.discriminator, + avatar: value.avatar, + accent_color: value.accent_color, + banner: value.banner, + theme_colors: value.theme_colors, + pronouns: value.pronouns, + bot: value.bot, + bio: value.bio, + premium_type: value.premium_type, + premium_since: value.premium_since, + public_flags: value.public_flags, + } + } +} + +const CUSTOM_USER_FLAG_OFFSET: u64 = 1 << 32; + +bitflags::bitflags! { + #[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)] + #[cfg_attr(feature = "sqlx", derive(Type))] + pub struct UserFlags: u64 { + const DISCORD_EMPLOYEE = 1 << 0; + const PARTNERED_SERVER_OWNER = 1 << 1; + const HYPESQUAD_EVENTS = 1 << 2; + const BUGHUNTER_LEVEL_1 =1 << 3; + const MFA_SMS = 1 << 4; + const PREMIUM_PROMO_DISMISSED = 1 << 5; + const HOUSE_BRAVERY = 1 << 6; + const HOUSE_BRILLIANCE = 1 << 7; + const HOUSE_BALANCE = 1 << 8; + const EARLY_SUPPORTER = 1 << 9; + const TEAM_USER = 1 << 10; + const TRUST_AND_SAFETY = 1 << 11; + const SYSTEM = 1 << 12; + const HAS_UNREAD_URGENT_MESSAGES = 1 << 13; + const BUGHUNTER_LEVEL_2 = 1 << 14; + const UNDERAGE_DELETED = 1 << 15; + const VERIFIED_BOT = 1 << 16; + const EARLY_VERIFIED_BOT_DEVELOPER = 1 << 17; + const CERTIFIED_MODERATOR = 1 << 18; + const BOT_HTTP_INTERACTIONS = 1 << 19; + } +} diff --git a/types/entities/user_settings.rs b/types/entities/user_settings.rs new file mode 100644 index 0000000..c4adaee --- /dev/null +++ b/types/entities/user_settings.rs @@ -0,0 +1,133 @@ +use chrono::{serde::ts_milliseconds_option, Utc}; +use serde::{Deserialize, Serialize}; + +use crate::utils::Snowflake; + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)] +#[serde(rename_all = "lowercase")] +pub enum UserStatus { + #[default] + Online, + Offline, + Dnd, + Idle, + Invisible, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)] +#[serde(rename_all = "lowercase")] +pub enum UserTheme { + #[default] + Dark, + Light, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct UserSettings { + #[serde(skip_serializing)] + pub index: String, // Database relevance only + pub afk_timeout: u16, + pub allow_accessibility_detection: bool, + pub animate_emoji: bool, + pub animate_stickers: u8, + pub contact_sync_enabled: bool, + pub convert_emoticons: bool, + pub custom_status: Option, + pub default_guilds_restricted: bool, + pub detect_platform_accounts: bool, + pub developer_mode: bool, + pub disable_games_tab: bool, + pub enable_tts_command: bool, + pub explicit_content_filter: u8, + pub friend_source_flags: FriendSourceFlags, + pub gateway_connected: bool, + pub gif_auto_play: bool, + pub guild_folders: Vec, + pub guild_positions: Vec, + pub inline_attachment_media: bool, + pub inline_embed_media: bool, + pub locale: String, + pub message_display_compact: bool, + pub native_phone_integration_enabled: bool, + pub render_embeds: bool, + pub render_reactions: bool, + pub restricted_guilds: Vec, + pub show_current_game: bool, + pub status: UserStatus, + pub stream_notifications_enabled: bool, + pub theme: UserTheme, + pub timezone_offset: i16, +} + +impl Default for UserSettings { + fn default() -> Self { + Self { + index: Snowflake::generate().to_string(), + afk_timeout: 3600, + allow_accessibility_detection: true, + animate_emoji: true, + animate_stickers: 0, + contact_sync_enabled: false, + convert_emoticons: false, + custom_status: None, + default_guilds_restricted: false, + detect_platform_accounts: false, + developer_mode: true, + disable_games_tab: true, + enable_tts_command: false, + explicit_content_filter: 0, + friend_source_flags: FriendSourceFlags::default(), + gateway_connected: false, + gif_auto_play: false, + guild_folders: Vec::new(), + guild_positions: Vec::new(), + inline_attachment_media: true, + inline_embed_media: true, + locale: "en-US".to_string(), + message_display_compact: false, + native_phone_integration_enabled: true, + render_embeds: true, + render_reactions: true, + restricted_guilds: Vec::new(), + show_current_game: true, + status: UserStatus::Online, + stream_notifications_enabled: false, + theme: UserTheme::Dark, + timezone_offset: 0, + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct CustomStatus { + pub emoji_id: Option, + pub emoji_name: Option, + #[serde(with = "ts_milliseconds_option")] + pub expires_at: Option>, + pub text: Option, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct FriendSourceFlags { + pub all: bool, +} + +impl Default for FriendSourceFlags { + fn default() -> Self { + Self { all: true } + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct GuildFolder { + pub color: u32, + pub guild_ids: Vec, + pub id: u16, + pub name: String, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct LoginResult { + pub token: String, + pub settings: UserSettings, +} diff --git a/types/entities/voice_state.rs b/types/entities/voice_state.rs new file mode 100644 index 0000000..79ee1cc --- /dev/null +++ b/types/entities/voice_state.rs @@ -0,0 +1,30 @@ +use chrono::{DateTime, Utc}; +use serde::{Deserialize, Serialize}; + +use crate::{ + entities::{Channel, Guild, GuildMember, User}, + utils::Snowflake, +}; + +/// See https://docs.spacebar.chat/routes/#cmp--schemas-voicestate +#[derive(Serialize, Deserialize, Debug, Default, Clone)] +pub struct VoiceState { + pub guild_id: Snowflake, + pub guild: Option, + pub channel_id: Snowflake, + pub channel: Option, + pub user_id: Snowflake, + pub user: Option, + pub member: Option, + pub session_id: Snowflake, + pub token: String, + pub deaf: bool, + pub mute: bool, + pub self_deaf: bool, + pub self_mute: bool, + pub self_stream: Option, + pub self_video: bool, + pub suppress: bool, + pub request_to_speak_timestamp: Option>, + pub id: Snowflake, +} diff --git a/types/entities/webhook.rs b/types/entities/webhook.rs new file mode 100644 index 0000000..e690a72 --- /dev/null +++ b/types/entities/webhook.rs @@ -0,0 +1,32 @@ +use serde::{Deserialize, Serialize}; + +use crate::{ + entities::{Application, Channel, Guild, User}, + utils::Snowflake, +}; + +/// See https://docs.spacebar.chat/routes/#cmp--schemas-webhook +#[derive(Serialize, Deserialize, Debug, Default, Clone)] +pub struct Webhook { + #[serde(rename = "type")] + pub webhook_type: i32, + pub name: String, + pub avatar: String, + pub token: String, + pub guild_id: Snowflake, + #[serde(skip_serializing_if = "Option::is_none")] + pub guild: Option, + pub channel_id: Snowflake, + #[serde(skip_serializing_if = "Option::is_none")] + pub channel: Option, + pub application_id: Snowflake, + #[serde(skip_serializing_if = "Option::is_none")] + pub application: Option, + pub user_id: Snowflake, + #[serde(skip_serializing_if = "Option::is_none")] + pub user: Option, + pub source_guild_id: Snowflake, + #[serde(skip_serializing_if = "Option::is_none")] + pub source_guild: Option, + pub id: Snowflake, +} diff --git a/types/errors.rs b/types/errors.rs new file mode 100644 index 0000000..63f6ceb --- /dev/null +++ b/types/errors.rs @@ -0,0 +1,54 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[cfg(feature = "sqlx")] + #[error("SQLX error: {0}")] + SQLX(#[from] sqlx::Error), + + #[error("serde: {0}")] + Serde(#[from] serde_json::Error), + + #[error(transparent)] + IO(#[from] std::io::Error), + + #[error(transparent)] + FieldFormat(#[from] FieldFormatError), +} + +#[derive(Debug, PartialEq, Eq, thiserror::Error)] +pub enum FieldFormatError { + #[error("Password must be between 1 and 72 characters.")] + PasswordError, + #[error("Username must be between 2 and 32 characters.")] + UsernameError, + #[error("Consent must be 'true' to register.")] + ConsentError, + #[error("The provided email address is in an invalid format.")] + EmailError, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct ErrorResponse { + pub code: i32, + pub message: String, + pub errors: IntermittentError, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct IntermittentError { + #[serde(flatten)] + pub errors: std::collections::HashMap, +} + +#[derive(Serialize, Deserialize, Debug, Default)] +pub struct ErrorField { + #[serde(default)] + pub _errors: Vec, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct APIError { + pub message: String, + pub code: String, +} diff --git a/types/events/channel.rs b/types/events/channel.rs new file mode 100644 index 0000000..19e4238 --- /dev/null +++ b/types/events/channel.rs @@ -0,0 +1,41 @@ +use chrono::{DateTime, Utc}; +use serde::{Deserialize, Serialize}; +use crate::entities::Channel; +use crate::events::WebSocketEvent; + +#[derive(Debug, Default, Deserialize, Serialize)] +/// See https://discord.com/developers/docs/topics/gateway-events#channel-pins-update +pub struct ChannelPinsUpdate { + pub guild_id: Option, + pub channel_id: String, + pub last_pin_timestamp: Option>, +} + +impl WebSocketEvent for ChannelPinsUpdate {} + +#[derive(Debug, Default, Deserialize, Serialize)] +/// See https://discord.com/developers/docs/topics/gateway-events#channel-create +/// Not directly serialized, as the inner payload is a channel object +pub struct ChannelCreate { + pub channel: Channel, +} + +impl WebSocketEvent for ChannelCreate {} + +#[derive(Debug, Default, Deserialize, Serialize)] +/// See https://discord.com/developers/docs/topics/gateway-events#channel-update +/// Not directly serialized, as the inner payload is a channel object +pub struct ChannelUpdate { + pub channel: Channel, +} + +impl WebSocketEvent for ChannelUpdate {} + +#[derive(Debug, Default, Deserialize, Serialize)] +/// See https://discord.com/developers/docs/topics/gateway-events#channel-delete +/// Not directly serialized, as the inner payload is a channel object +pub struct ChannelDelete { + pub channel: Channel, +} + +impl WebSocketEvent for ChannelDelete {} \ No newline at end of file diff --git a/types/events/guild.rs b/types/events/guild.rs new file mode 100644 index 0000000..3b17861 --- /dev/null +++ b/types/events/guild.rs @@ -0,0 +1,41 @@ +use serde::{Deserialize, Serialize}; +use crate::entities::{Guild, UnavailableGuild, User}; +use crate::events::WebSocketEvent; + +#[derive(Debug, Deserialize, Serialize, Default)] +/// See https://discord.com/developers/docs/topics/gateway-events#guild-create +/// This one is particularly painful, it can be a Guild object with extra field or an unavailbile guild object +pub struct GuildCreate { + pub d: GuildCreateDataOption, +} + +#[derive(Debug, Deserialize, Serialize)] +pub enum GuildCreateDataOption { + UnavailableGuild(UnavailableGuild), + Guild(Guild), +} + +impl Default for GuildCreateDataOption { + fn default() -> Self { + GuildCreateDataOption::UnavailableGuild(UnavailableGuild::default()) + } +} +impl WebSocketEvent for GuildCreate {} + +#[derive(Debug, Default, Deserialize, Serialize)] +/// See https://discord.com/developers/docs/topics/gateway-events#guild-ban-add-guild-ban-add-event-fields +pub struct GuildBanAdd { + pub guild_id: String, + pub user: User, +} + +impl WebSocketEvent for GuildBanAdd {} + +#[derive(Debug, Default, Deserialize, Serialize)] +/// See https://discord.com/developers/docs/topics/gateway-events#guild-ban-remove +pub struct GuildBanRemove { + pub guild_id: String, + pub user: User, +} + +impl WebSocketEvent for GuildBanRemove {} \ No newline at end of file diff --git a/types/events/heartbeat.rs b/types/events/heartbeat.rs new file mode 100644 index 0000000..cc73239 --- /dev/null +++ b/types/events/heartbeat.rs @@ -0,0 +1,17 @@ +use serde::{Deserialize, Serialize}; +use crate::events::WebSocketEvent; + +#[derive(Debug, Default, Deserialize, Serialize)] +pub struct GatewayHeartbeat { + pub op: u8, + pub d: Option, +} + +impl WebSocketEvent for GatewayHeartbeat {} + +#[derive(Debug, Default, Deserialize, Serialize)] +pub struct GatewayHeartbeatAck { + pub op: i32, +} + +impl WebSocketEvent for GatewayHeartbeatAck {} \ No newline at end of file diff --git a/types/events/hello.rs b/types/events/hello.rs new file mode 100644 index 0000000..cb63dae --- /dev/null +++ b/types/events/hello.rs @@ -0,0 +1,17 @@ +use serde::{Deserialize, Serialize}; +use crate::events::WebSocketEvent; + +#[derive(Debug, Default, Deserialize, Serialize)] +pub struct GatewayHello { + pub op: i32, + pub d: HelloData, +} + +impl WebSocketEvent for GatewayHello {} + +#[derive(Debug, Default, Deserialize, Serialize)] +pub struct HelloData { + pub heartbeat_interval: u128, +} + +impl WebSocketEvent for HelloData {} \ No newline at end of file diff --git a/types/events/identify.rs b/types/events/identify.rs new file mode 100644 index 0000000..7443631 --- /dev/null +++ b/types/events/identify.rs @@ -0,0 +1,22 @@ +use serde::{Deserialize, Serialize}; +use crate::events::{PresenceUpdate, WebSocketEvent}; + +#[derive(Debug, Deserialize, Serialize, Default)] +pub struct GatewayIdentifyPayload { + pub token: String, + pub properties: GatewayIdentifyConnectionProps, + pub compress: Option, + pub large_threshold: Option, //default: 50 + pub shard: Option>, + pub presence: Option, + pub intents: i32, +} + +impl WebSocketEvent for GatewayIdentifyPayload {} + +#[derive(Debug, Deserialize, Serialize, Default)] +pub struct GatewayIdentifyConnectionProps { + pub os: String, + pub browser: String, + pub device: String, +} \ No newline at end of file diff --git a/types/events/message.rs b/types/events/message.rs new file mode 100644 index 0000000..edaaa4b --- /dev/null +++ b/types/events/message.rs @@ -0,0 +1,101 @@ +use serde::{Deserialize, Serialize}; + +use crate::{ + entities::{Emoji, GuildMember, Message, User}, + utils::Snowflake, +}; + +use super::WebSocketEvent; + +#[derive(Debug, Deserialize, Serialize, Default)] +pub struct TypingStartEvent { + pub channel_id: String, + pub guild_id: Option, + pub user_id: String, + pub timestamp: i64, + pub member: Option, +} + +impl WebSocketEvent for TypingStartEvent {} + +#[derive(Debug, Serialize, Deserialize, Default)] +pub struct MessageCreate { + #[serde(flatten)] + message: Message, + guild_id: Option, + member: Option, + mentions: Vec<(User, GuildMember)>, // Not sure if this is correct: https://discord.com/developers/docs/topics/gateway-events#message-create +} + +impl WebSocketEvent for MessageCreate {} + +#[derive(Debug, Serialize, Deserialize, Default)] +pub struct MessageUpdate { + #[serde(flatten)] + message: Message, + guild_id: Option, + member: Option, + mentions: Vec<(User, GuildMember)>, // Not sure if this is correct: https://discord.com/developers/docs/topics/gateway-events#message-create +} + +impl WebSocketEvent for MessageUpdate {} + +#[derive(Debug, Serialize, Deserialize, Default)] +pub struct MessageDelete { + id: String, + channel_id: String, + guild_id: Option, +} + +impl WebSocketEvent for MessageDelete {} + +#[derive(Debug, Serialize, Deserialize, Default)] +pub struct MessageDeleteBulk { + ids: Vec, + channel_id: String, + guild_id: Option, +} + +impl WebSocketEvent for MessageDeleteBulk {} + +#[derive(Debug, Serialize, Deserialize, Default)] +pub struct MessageReactionAdd { + user_id: String, + channel_id: String, + message_id: String, + guild_id: Option, + member: Option, + emoji: Emoji, +} + +impl WebSocketEvent for MessageReactionAdd {} + +#[derive(Debug, Serialize, Deserialize, Default)] +pub struct MessageReactionRemove { + user_id: String, + channel_id: String, + message_id: String, + guild_id: Option, + emoji: Emoji, +} + +impl WebSocketEvent for MessageReactionRemove {} + +#[derive(Debug, Serialize, Deserialize, Default)] +pub struct MessageReactionRemoveAll { + channel_id: String, + message_id: String, + guild_id: Option, +} + +impl WebSocketEvent for MessageReactionRemoveAll {} + +#[derive(Debug, Serialize, Deserialize, Default)] +pub struct MessageReactionRemoveEmoji { + channel_id: String, + message_id: String, + guild_id: Option, + emoji: Emoji, +} + +impl WebSocketEvent for MessageReactionRemoveEmoji {} diff --git a/types/events/mod.rs b/types/events/mod.rs new file mode 100644 index 0000000..418cd31 --- /dev/null +++ b/types/events/mod.rs @@ -0,0 +1,41 @@ +use serde::{Deserialize, Serialize}; + +mod channel; +mod guild; +mod heartbeat; +mod hello; +mod identify; +mod message; +mod presence; +mod ready; +mod request_members; +mod resume; +mod thread; +mod user; +mod voice_status; + +pub use channel::*; +pub use guild::*; +pub use heartbeat::*; +pub use hello::*; +pub use identify::*; +pub use message::*; +pub use presence::*; +pub use ready::*; +pub use request_members::*; +pub use resume::*; +pub use thread::*; +pub use user::*; +pub use voice_status::*; + +pub trait WebSocketEvent {} + +#[derive(Debug, Default, Deserialize, Serialize)] +pub struct GatewayPayload { + pub op: u8, + pub d: Option, + pub s: Option, + pub t: Option, +} + +impl WebSocketEvent for GatewayPayload {} diff --git a/types/events/presence.rs b/types/events/presence.rs new file mode 100644 index 0000000..c62e6ae --- /dev/null +++ b/types/events/presence.rs @@ -0,0 +1,24 @@ +use serde::{Deserialize, Serialize}; +use crate::entities::User; +use crate::events::WebSocketEvent; +use crate::interfaces::Activity; + +#[derive(Debug, Deserialize, Serialize, Default, Clone)] +/// See https://discord.com/developers/docs/topics/gateway-events#presence-update-presence-update-event-fields +pub struct PresenceUpdate { + pub user: User, + pub guild_id: String, + pub status: String, + pub activities: Vec, + pub client_status: ClientStatusObject, +} + +#[derive(Debug, Deserialize, Serialize, Default, Clone)] +/// See https://discord.com/developers/docs/topics/gateway-events#client-status-object +pub struct ClientStatusObject { + pub desktop: Option, + pub mobile: Option, + pub web: Option, +} + +impl WebSocketEvent for PresenceUpdate {} \ No newline at end of file diff --git a/types/events/ready.rs b/types/events/ready.rs new file mode 100644 index 0000000..3d42f6e --- /dev/null +++ b/types/events/ready.rs @@ -0,0 +1,15 @@ +use serde::{Deserialize, Serialize}; +use crate::entities::{UnavailableGuild, User}; +use crate::events::WebSocketEvent; + +#[derive(Debug, Deserialize, Serialize, Default)] +pub struct GatewayReady { + pub v: u8, + pub user: User, + pub guilds: Vec, + pub session_id: String, + pub resume_gateway_url: Option, + pub shard: Option<(u64, u64)>, +} + +impl WebSocketEvent for GatewayReady {} \ No newline at end of file diff --git a/types/events/request_members.rs b/types/events/request_members.rs new file mode 100644 index 0000000..192fe71 --- /dev/null +++ b/types/events/request_members.rs @@ -0,0 +1,15 @@ +use serde::{Deserialize, Serialize}; +use crate::events::WebSocketEvent; + +#[derive(Debug, Deserialize, Serialize, Default)] +/// See https://discord.com/developers/docs/topics/gateway-events#request-guild-members-request-guild-members-structure +pub struct GatewayRequestGuildMembers { + pub guild_id: String, + pub query: Option, + pub limit: u64, + pub presence: Option, + pub user_ids: Option, + pub nonce: Option, +} + +impl WebSocketEvent for GatewayRequestGuildMembers {} \ No newline at end of file diff --git a/types/events/resume.rs b/types/events/resume.rs new file mode 100644 index 0000000..abdc5d6 --- /dev/null +++ b/types/events/resume.rs @@ -0,0 +1,11 @@ +use serde::{Deserialize, Serialize}; +use crate::events::WebSocketEvent; + +#[derive(Debug, Deserialize, Serialize, Default)] +pub struct GatewayResume { + pub token: String, + pub session_id: String, + pub seq: String, +} + +impl WebSocketEvent for GatewayResume {} \ No newline at end of file diff --git a/types/events/thread.rs b/types/events/thread.rs new file mode 100644 index 0000000..214ae8b --- /dev/null +++ b/types/events/thread.rs @@ -0,0 +1,82 @@ +use serde::{Deserialize, Serialize}; +use crate::entities::{Channel, GuildMember, ThreadMember}; +use crate::events::WebSocketEvent; + +#[derive(Debug, Default, Deserialize, Serialize)] +/// See https://discord.com/developers/docs/topics/gateway-events#thread-create +/// Not directly serialized, as the inner payload is a channel object +pub struct ThreadCreate { + pub thread: Channel, +} + +impl WebSocketEvent for ThreadCreate {} + +#[derive(Debug, Default, Deserialize, Serialize)] +/// See https://discord.com/developers/docs/topics/gateway-events#thread-update +/// Not directly serialized, as the inner payload is a channel object +pub struct ThreadUpdate { + pub thread: Channel, +} + +impl WebSocketEvent for ThreadUpdate {} + +#[derive(Debug, Default, Deserialize, Serialize)] +/// See https://discord.com/developers/docs/topics/gateway-events#thread-delete +/// Not directly serialized, as the inner payload is a channel object +pub struct ThreadDelete { + pub thread: Channel, +} + +impl WebSocketEvent for ThreadDelete {} + +#[derive(Debug, Default, Deserialize, Serialize)] +/// See https://discord.com/developers/docs/topics/gateway-events#thread-list-sync +pub struct ThreadListSync { + pub guild_id: String, + pub channel_ids: Option>, + pub threads: Vec, + pub members: Vec, +} + +impl WebSocketEvent for ThreadListSync {} + +#[derive(Debug, Default, Deserialize, Serialize)] +/// See https://discord.com/developers/docs/topics/gateway-events#thread-member-update +/// The inner payload is a thread member object with an extra field. +/// The extra field is a bit painful, because we can't just serialize a thread member object +pub struct ThreadMemberUpdate { + pub id: Option, + pub user_id: Option, + pub join_timestamp: Option, + pub flags: Option, + pub member: Option, + pub guild_id: String, +} + +impl ThreadMemberUpdate { + /// Convert self to a thread member, losing the added guild_id field + pub fn to_thread_member(self) -> ThreadMember { + ThreadMember { + id: self.id, + user_id: self.user_id, + join_timestamp: self.join_timestamp.clone(), + flags: self.flags, + member: self.member, + } + } +} + +impl WebSocketEvent for ThreadMemberUpdate {} + +#[derive(Debug, Default, Deserialize, Serialize)] +/// See https://discord.com/developers/docs/topics/gateway-events#thread-members-update +pub struct ThreadMembersUpdate { + pub id: String, + pub guild_id: String, + /// Capped at 50 + pub member_count: u8, + pub added_members: Option>, + pub removed_members: Option>, +} + +impl WebSocketEvent for ThreadMembersUpdate {} \ No newline at end of file diff --git a/types/events/user.rs b/types/events/user.rs new file mode 100644 index 0000000..385bb43 --- /dev/null +++ b/types/events/user.rs @@ -0,0 +1,12 @@ +use serde::{Deserialize, Serialize}; +use crate::entities::User; +use crate::events::WebSocketEvent; + +#[derive(Debug, Default, Deserialize, Serialize)] +/// See https://discord.com/developers/docs/topics/gateway-events#user-update +/// Not directly serialized, as the inner payload is the user object +pub struct UserUpdate { + pub user: User, +} + +impl WebSocketEvent for UserUpdate {} \ No newline at end of file diff --git a/types/events/voice_status.rs b/types/events/voice_status.rs new file mode 100644 index 0000000..9e5b968 --- /dev/null +++ b/types/events/voice_status.rs @@ -0,0 +1,13 @@ +use serde::{Deserialize, Serialize}; +use crate::events::WebSocketEvent; + +#[derive(Debug, Deserialize, Serialize, Default)] +/// See https://discord.com/developers/docs/topics/gateway-events#update-voice-state-gateway-voice-state-update-structure +pub struct GatewayVoiceStateUpdate { + pub guild_id: String, + pub channel_id: Option, + pub self_mute: bool, + pub self_deaf: bool, +} + +impl WebSocketEvent for GatewayVoiceStateUpdate {} \ No newline at end of file diff --git a/types/interfaces/activity.rs b/types/interfaces/activity.rs new file mode 100644 index 0000000..fd3e4be --- /dev/null +++ b/types/interfaces/activity.rs @@ -0,0 +1,57 @@ +use serde::{Deserialize, Serialize}; +use crate::entities::Emoji; + +#[derive(Debug, Deserialize, Serialize, Clone)] +pub struct Activity { + name: String, + #[serde(rename = "type")] + activity_type: i32, + url: Option, + created_at: i64, + timestamps: Option, + application_id: Option, + details: Option, + state: Option, + emoji: Option, + party: Option, + assets: Option, + secrets: Option, + instance: Option, + flags: Option, + buttons: Option>, +} + + +#[derive(Debug, Deserialize, Serialize, Clone)] +struct ActivityTimestamps { + start: Option, + end: Option, +} + +#[derive(Debug, Deserialize, Serialize, Clone)] +struct ActivityParty { + id: Option, + size: Option>, +} + +#[derive(Debug, Deserialize, Serialize, Clone)] +struct ActivityAssets { + large_image: Option, + large_text: Option, + small_image: Option, + small_text: Option, +} + +#[derive(Debug, Deserialize, Serialize, Clone)] +struct ActivitySecrets { + join: Option, + spectate: Option, + #[serde(rename = "match")] + match_string: Option, +} + +#[derive(Debug, Deserialize, Serialize, Clone)] +struct ActivityButton { + label: String, + url: String, +} \ No newline at end of file diff --git a/types/interfaces/connected_account.rs b/types/interfaces/connected_account.rs new file mode 100644 index 0000000..e69de29 diff --git a/types/interfaces/guild_welcome_screen.rs b/types/interfaces/guild_welcome_screen.rs new file mode 100644 index 0000000..af645d3 --- /dev/null +++ b/types/interfaces/guild_welcome_screen.rs @@ -0,0 +1,17 @@ +use serde::{Deserialize, Serialize}; +use crate::utils::Snowflake; + +#[derive(Serialize, Deserialize, Debug, Default, Clone)] +pub struct WelcomeScreenObject { + pub enabled: bool, + pub description: Option, + pub welcome_channels: Vec, +} + +#[derive(Serialize, Deserialize, Debug, Default, Clone)] +pub struct WelcomeScreenChannel { + pub channel_id: Snowflake, + pub description: String, + pub emoji_id: Option, + pub emoji_name: Option, +} \ No newline at end of file diff --git a/types/interfaces/interaction.rs b/types/interfaces/interaction.rs new file mode 100644 index 0000000..76b18e4 --- /dev/null +++ b/types/interfaces/interaction.rs @@ -0,0 +1,39 @@ +use serde::{Deserialize, Serialize}; +use serde_json::Value; +use crate::entities::{AllowedMention, Embed}; +use crate::utils::Snowflake; + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct Interaction { + pub id: Snowflake, + pub r#type: InteractionType, + pub data: Value, + pub guild_id: Snowflake, + pub channel_id: Snowflake, + pub member_id: Snowflake, + pub token: String, + pub version: i32, +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub enum InteractionType { + SelfCommand = 0, + Ping = 1, + ApplicationCommand = 2 +} + +pub enum InteractionResponseType { + SelfCommandResponse = 0, + Pong = 1, + Acknowledge = 2, + ChannelMessage = 3, + ChannelMessageWithSource = 4, + AcknowledgeWithSource = 5, +} + +pub struct InteractionApplicationCommandCallbackData { + pub tts: bool, + pub content: String, + pub embeds: Vec, + pub allowed_mentions: AllowedMention +} diff --git a/types/interfaces/mod.rs b/types/interfaces/mod.rs new file mode 100644 index 0000000..6e0a7df --- /dev/null +++ b/types/interfaces/mod.rs @@ -0,0 +1,11 @@ +mod activity; +mod connected_account; +mod guild_welcome_screen; +mod interaction; +mod status; + +pub use activity::*; +pub use connected_account::*; +pub use guild_welcome_screen::*; +pub use interaction::*; +pub use status::*; \ No newline at end of file diff --git a/types/interfaces/status.rs b/types/interfaces/status.rs new file mode 100644 index 0000000..105dd87 --- /dev/null +++ b/types/interfaces/status.rs @@ -0,0 +1,9 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Deserialize, Serialize, Default, Clone)] +/// See https://discord.com/developers/docs/topics/gateway-events#client-status-object +pub struct ClientStatusObject { + pub desktop: Option, + pub mobile: Option, + pub web: Option, +} \ No newline at end of file diff --git a/types/lib.rs b/types/lib.rs new file mode 100644 index 0000000..a3b0671 --- /dev/null +++ b/types/lib.rs @@ -0,0 +1,7 @@ +pub mod config; // Maybe feature lock this for backend/web usage only? web would only need it in the case of the admin panel +pub mod entities; +pub mod errors; +pub mod events; +pub mod utils; +pub mod interfaces; +pub mod schema; diff --git a/types/schema/apierror.rs b/types/schema/apierror.rs new file mode 100644 index 0000000..95e72a5 --- /dev/null +++ b/types/schema/apierror.rs @@ -0,0 +1,72 @@ +#[cfg(feature = "poem")] +use poem::{http::StatusCode, IntoResponse, Response}; +use serde_json::{json, Value}; + +#[derive(Debug, thiserror::Error)] +pub enum APIError { + #[error(transparent)] + Auth(#[from] AuthError), +} + +impl APIError { + pub fn error_payload(&self) -> Value { + match self { + APIError::Auth(auth_err) => auth_err.error_payload(), + } + } +} + +#[derive(Debug, thiserror::Error)] +pub enum AuthError { + #[error("INVALID_LOGIN")] + InvalidLogin, + #[error("INVALID_CAPTCHA")] + InvalidCaptcha, +} + +impl AuthError { + pub fn error_code(&self) -> &str { + match self { + AuthError::InvalidLogin => "INVALID_LOGIN", + AuthError::InvalidCaptcha => "INVALID_CATPCA", + } + } + + pub fn error_payload(&self) -> Value { + match self { + AuthError::InvalidLogin => json!({ + "login": { + "message": "auth:login.INVALID_LOGIN", + "code": self.error_code() + } + }), + AuthError::InvalidCaptcha => json!([json!({ + "captcha_key": "TODO", + "captcha_sitekey": "TODO", + "captcha_service": "TODO" + })]), + } + } +} + +#[cfg(feature = "poem")] +impl poem::error::ResponseError for APIError { + fn status(&self) -> StatusCode { + match self { + APIError::Auth(auth_err) => match auth_err { + AuthError::InvalidLogin => StatusCode::UNAUTHORIZED, + AuthError::InvalidCaptcha => StatusCode::BAD_REQUEST, + }, + } + } + + fn as_response(&self) -> Response + where + Self: std::error::Error + Send + Sync + 'static, + { + Response::builder() + .status(self.status()) + .body(self.error_payload().to_string()) + .into_response() + } +} diff --git a/types/schema/auth.rs b/types/schema/auth.rs new file mode 100644 index 0000000..073d8d4 --- /dev/null +++ b/types/schema/auth.rs @@ -0,0 +1,240 @@ +use crate::errors::FieldFormatError; +use regex::Regex; +use serde::{Deserialize, Serialize}; + +/** +A struct that represents a well-formed email address. + */ +#[derive(Clone, PartialEq, Eq, Debug)] +pub struct AuthEmail { + pub email: String, +} + +impl AuthEmail { + /** + Returns a new [`Result`]. + ## Arguments + The email address you want to validate. + ## Errors + You will receive a [`FieldFormatError`], if: + - The email address is not in a valid format. + + */ + pub fn new(email: String) -> Result { + let regex = Regex::new(r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$").unwrap(); + if !regex.is_match(email.as_str()) { + return Err(FieldFormatError::EmailError); + } + Ok(AuthEmail { email }) + } +} + +/** +A struct that represents a well-formed username. +## Arguments +Please use new() to create a new instance of this struct. +## Errors +You will receive a [`FieldFormatError`], if: +- The username is not between 2 and 32 characters. + */ +#[derive(Clone, PartialEq, Eq, Debug)] +pub struct AuthUsername { + pub username: String, +} + +impl AuthUsername { + /** + Returns a new [`Result`]. + ## Arguments + The username you want to validate. + ## Errors + You will receive a [`FieldFormatError`], if: + - The username is not between 2 and 32 characters. + */ + pub fn new(username: String) -> Result { + if username.len() < 2 || username.len() > 32 { + Err(FieldFormatError::UsernameError) + } else { + Ok(AuthUsername { username }) + } + } +} + +/** +A struct that represents a well-formed password. +## Arguments +Please use new() to create a new instance of this struct. +## Errors +You will receive a [`FieldFormatError`], if: +- The password is not between 1 and 72 characters. + */ +#[derive(Clone, PartialEq, Eq, Debug)] +pub struct AuthPassword { + pub password: String, +} + +impl AuthPassword { + /** + Returns a new [`Result`]. + ## Arguments + The password you want to validate. + ## Errors + You will receive a [`FieldFormatError`], if: + - The password is not between 1 and 72 characters. + */ + pub fn new(password: String) -> Result { + if password.is_empty() || password.len() > 72 { + Err(FieldFormatError::PasswordError) + } else { + Ok(AuthPassword { password }) + } + } +} + +/** +A struct that represents a well-formed register request. +## Arguments +Please use new() to create a new instance of this struct. +## Errors +You will receive a [`FieldFormatError`], if: +- The username is not between 2 and 32 characters. +- The password is not between 1 and 72 characters. + */ + +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] +#[serde(rename_all = "snake_case")] +pub struct RegisterSchema { + username: String, + password: Option, + consent: bool, + email: Option, + fingerprint: Option, + invite: Option, + date_of_birth: Option, + gift_code_sku_id: Option, + captcha_key: Option, + promotional_email_opt_in: Option, +} + +impl RegisterSchema { + /** + Returns a new [`Result`]. + ## Arguments + All but "String::username" and "bool::consent" are optional. + + ## Errors + You will receive a [`FieldFormatError`], if: + - The username is less than 2 or more than 32 characters in length + - You supply a `password` which is less than 1 or more than 72 characters in length. + + These constraints have been defined [in the Spacebar-API](https://docs.spacebar.chat/routes/) + */ + pub fn new( + username: String, + password: Option, + consent: bool, + email: Option, + fingerprint: Option, + invite: Option, + date_of_birth: Option, + gift_code_sku_id: Option, + captcha_key: Option, + promotional_email_opt_in: Option, + ) -> Result { + let username = AuthUsername::new(username)?.username; + + let email = if let Some(email) = email { + Some(AuthEmail::new(email)?.email) + } else { + None + }; + + let password = if let Some(password) = password { + Some(AuthPassword::new(password)?.password) + } else { + None + }; + + if !consent { + return Err(FieldFormatError::ConsentError); + } + + Ok(RegisterSchema { + username, + password, + consent, + email, + fingerprint, + invite, + date_of_birth, + gift_code_sku_id, + captcha_key, + promotional_email_opt_in, + }) + } +} + +/** +A struct that represents a well-formed login request. +## Arguments +Please use new() to create a new instance of this struct. +## Errors +You will receive a [`FieldFormatError`], if: +- The username is not between 2 and 32 characters. +- The password is not between 1 and 72 characters. + */ +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] +#[serde(rename_all = "snake_case")] +pub struct LoginSchema { + pub login: String, + pub password: String, + pub undelete: Option, + pub captcha_key: Option, + pub login_source: Option, + pub gift_code_sku_id: Option, +} + +impl LoginSchema { + /** + Returns a new [`Result`]. + ## Arguments + login: The username you want to login with. + password: The password you want to login with. + undelete: Honestly no idea what this is for. + captcha_key: The captcha key you want to login with. + login_source: The login source. + gift_code_sku_id: The gift code sku id. + ## Errors + You will receive a [`FieldFormatError`], if: + - The username is less than 2 or more than 32 characters in length + */ + pub fn new( + login: String, + password: String, + undelete: Option, + captcha_key: Option, + login_source: Option, + gift_code_sku_id: Option, + ) -> Result { + let login = AuthUsername::new(login)?.username; + let password = AuthPassword::new(password)?.password; + + Ok(LoginSchema { + login, + password, + undelete, + captcha_key, + login_source, + gift_code_sku_id, + }) + } +} + +#[derive(Debug, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +pub struct TotpSchema { + code: String, + ticket: String, + gift_code_sku_id: Option, + login_source: Option, +} diff --git a/types/schema/channel.rs b/types/schema/channel.rs new file mode 100644 index 0000000..f6ee53c --- /dev/null +++ b/types/schema/channel.rs @@ -0,0 +1,27 @@ +use serde::{Deserialize, Serialize}; + +use crate::entities::PermissionOverwrite; + +#[derive(Debug, Deserialize, Serialize)] +#[serde(rename_all = "snake_case")] +pub struct ChannelCreateSchema { + pub name: String, + #[serde(rename = "type")] + pub channel_type: Option, + pub topic: Option, + pub icon: Option, + pub bitrate: Option, + pub user_limit: Option, + pub rate_limit_per_user: Option, + pub position: Option, + pub permission_overwrites: Option>, + pub parent_id: Option, + pub id: Option, + pub nsfw: Option, + pub rtc_region: Option, + pub default_auto_archive_duration: Option, + pub default_reaction_emoji: Option, + pub flags: Option, + pub default_thread_rate_limit_per_user: Option, + pub video_quality_mode: Option, +} diff --git a/types/schema/guild.rs b/types/schema/guild.rs new file mode 100644 index 0000000..92bb635 --- /dev/null +++ b/types/schema/guild.rs @@ -0,0 +1,14 @@ +use serde::{Deserialize, Serialize}; +use crate::entities::Channel; + +#[derive(Debug, Deserialize, Serialize)] +#[serde(rename_all = "snake_case")] +pub struct GuildCreateSchema { + pub name: Option, + pub region: Option, + pub icon: Option, + pub channels: Option>, + pub guild_template_code: Option, + pub system_channel_id: Option, + pub rules_channel_id: Option, +} \ No newline at end of file diff --git a/types/schema/message.rs b/types/schema/message.rs new file mode 100644 index 0000000..a37f581 --- /dev/null +++ b/types/schema/message.rs @@ -0,0 +1,47 @@ +use serde::{Deserialize, Serialize}; +use crate::entities::{AllowedMention, Component, Embed, MessageReference, PartialDiscordFileAttachment}; + +#[derive(Debug, Deserialize, Serialize)] +#[serde(rename_all = "snake_case")] +pub struct MessageSendSchema { + #[serde(rename = "type")] + message_type: Option, + content: Option, + nonce: Option, + tts: Option, + embeds: Option>, + allowed_mentions: Option, + message_reference: Option, + components: Option>, + sticker_ids: Option>, + pub attachments: Option>, +} + +// make a new() method for MessageSendSchema +impl MessageSendSchema { + pub fn new( + message_type: Option, + content: Option, + nonce: Option, + tts: Option, + embeds: Option>, + allowed_mentions: Option, + message_reference: Option, + components: Option>, + sticker_ids: Option>, + attachments: Option>, + ) -> MessageSendSchema { + MessageSendSchema { + message_type, + content, + nonce, + tts, + embeds, + allowed_mentions, + message_reference, + components, + sticker_ids, + attachments, + } + } +} \ No newline at end of file diff --git a/types/schema/mod.rs b/types/schema/mod.rs new file mode 100644 index 0000000..6fe3e37 --- /dev/null +++ b/types/schema/mod.rs @@ -0,0 +1,103 @@ +mod apierror; +mod auth; +mod channel; +mod guild; +mod message; +mod user; + +pub use apierror::*; +pub use auth::*; +pub use channel::*; +pub use guild::*; +pub use message::*; +pub use user::*; + +#[cfg(test)] +mod schemas_tests { + use super::*; + use crate::errors::FieldFormatError; + + #[test] + fn password_too_short() { + assert_eq!( + AuthPassword::new("".to_string()), + Err(FieldFormatError::PasswordError) + ); + } + + #[test] + fn password_too_long() { + let mut long_pw = String::new(); + for _ in 0..73 { + long_pw += "a"; + } + assert_eq!( + AuthPassword::new(long_pw), + Err(FieldFormatError::PasswordError) + ); + } + + #[test] + fn username_too_short() { + assert_eq!( + AuthUsername::new("T".to_string()), + Err(FieldFormatError::UsernameError) + ); + } + + #[test] + fn username_too_long() { + let mut long_un = String::new(); + for _ in 0..33 { + long_un += "a"; + } + assert_eq!( + AuthUsername::new(long_un), + Err(FieldFormatError::UsernameError) + ); + } + + #[test] + fn consent_false() { + assert_eq!( + RegisterSchema::new( + "Test".to_string(), + None, + false, + None, + None, + None, + None, + None, + None, + None, + ), + Err(FieldFormatError::ConsentError) + ); + } + + #[test] + fn invalid_email() { + assert_eq!( + AuthEmail::new("p@p.p".to_string()), + Err(FieldFormatError::EmailError) + ) + } + + #[test] + fn valid_email() { + let reg = RegisterSchema::new( + "Testy".to_string(), + None, + true, + Some("me@mail.de".to_string()), + None, + None, + None, + None, + None, + None, + ); + assert_ne!(reg, Err(FieldFormatError::EmailError)); + } +} diff --git a/types/schema/user.rs b/types/schema/user.rs new file mode 100644 index 0000000..00f4962 --- /dev/null +++ b/types/schema/user.rs @@ -0,0 +1,16 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Deserialize, Serialize)] +#[serde(rename_all = "snake_case")] +pub struct UserModifySchema { + pub username: Option, + pub avatar: Option, + pub bio: Option, + pub accent_color: Option, + pub banner: Option, + pub current_password: Option, + pub new_password: Option, + pub code: Option, + pub email: Option, + pub discriminator: Option, +} \ No newline at end of file diff --git a/types/utils/jwt.rs b/types/utils/jwt.rs new file mode 100644 index 0000000..0a38dd4 --- /dev/null +++ b/types/utils/jwt.rs @@ -0,0 +1,44 @@ +use jsonwebtoken::{encode, EncodingKey, Header}; +use serde::{Deserialize, Serialize}; + +use crate::{utils::Snowflake}; + +pub fn generate_token(id: &Snowflake, email: String, jwt_key: &str) -> String { + let claims = Claims::new(&email, id); + + build_token(&claims, jwt_key).unwrap() +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct Claims { + pub exp: i64, + pub iat: i64, + pub email: String, + pub id: String, +} + +impl Claims { + pub fn new(user: &str, id: &Snowflake) -> Self { + let unix = chrono::Utc::now().timestamp(); + Self { + exp: unix + (60 * 60 * 24), + id: id.to_string(), + iat: unix, + email: user.to_string(), + } + } +} + +pub fn build_token(claims: &Claims, jwt_key: &str) -> Result { + encode( + &Header::default(), + claims, + &EncodingKey::from_secret(jwt_key.as_bytes()), + ) +} + +/*pub fn decode_token(token: &str) -> Result, Error> { + let mut validation = Validation::new(Algorithm::HS256); + validation.sub = Some("quartzauth".to_string()); + decode(token, &DecodingKey::from_secret(JWT_SECRET), &validation) +}*/ diff --git a/types/utils/mod.rs b/types/utils/mod.rs new file mode 100644 index 0000000..ec2fd4a --- /dev/null +++ b/types/utils/mod.rs @@ -0,0 +1,8 @@ +pub mod jwt; +mod regexes; +mod rights; +mod snowflake; + +pub use regexes::*; +pub use rights::Rights; +pub use snowflake::{DeconstructedSnowflake, Snowflake}; diff --git a/types/utils/regexes.rs b/types/utils/regexes.rs new file mode 100644 index 0000000..0f160eb --- /dev/null +++ b/types/utils/regexes.rs @@ -0,0 +1,12 @@ +use lazy_static::lazy_static; +use regex::Regex; + +lazy_static! { + static ref DOUBLE_WHITE_SPACE_RE: Regex = Regex::new(r"\s\s+").unwrap(); + static ref SPECIAL_CHAR: Regex = Regex::new(r"@#`:\r\n\t\f\v\p{C}").unwrap(); + static ref CHANNEL_MENTION: Regex = Regex::new(r"<#(\d+)>").unwrap(); + static ref USER_MENTION: Regex = Regex::new(r"<@!?(\d+)>").unwrap(); + static ref ROLE_MENTION: Regex = Regex::new(r"<@&(\d+)>").unwrap(); + static ref EVERYONE_MENTION: Regex = Regex::new(r"@everyone").unwrap(); + static ref HERE_MENTION: Regex = Regex::new(r"@here").unwrap(); +} diff --git a/types/utils/rights.rs b/types/utils/rights.rs new file mode 100644 index 0000000..0198af6 --- /dev/null +++ b/types/utils/rights.rs @@ -0,0 +1,127 @@ +use bitflags::bitflags; + +bitflags! { + pub struct Rights: u64 { + const OPERATOR = 1 << 0; + const MANAGE_APPLICATIONS = 1 << 1; + const MANAGE_GUILDS = 1 << 2; + const MANAGE_MESSAGES = 1 << 3; + const MANAGE_RATE_LIMITS = 1 << 4; + const MANAGE_ROUTING = 1 << 5; + const MANAGE_TICKETS = 1 << 6; + const MANAGE_USERS = 1 << 7; + const ADD_MEMBERS = 1 << 8; + const BYPASS_RATE_LIMITS = 1 << 9; + const CREATE_APPLICATIONS = 1 << 10; + const CREATE_CHANNELS = 1 << 11; + const CREATE_DMS = 1 << 12; + const CREATE_DM_GROUPS = 1 << 13; + const CREATE_GUILDS = 1 << 14; + const CREATE_INVITES = 1 << 15; + const CREATE_ROLES = 1 << 16; + const CREATE_TEMPLATES = 1 << 17; + const CREATE_WEBHOOKS = 1 << 18; + const JOIN_GUILDS = 1 << 19; + const PIN_MESSAGES = 1 << 20; + const SELF_ADD_REACTIONS = 1 << 21; + const SELF_DELETE_MESSAGES = 1 << 22; + const SELF_EDIT_MESSAGES = 1 << 23; + const SELF_EDIT_NAME = 1 << 24; + const SEND_MESSAGES = 1 << 25; + const USE_ACTIVITIES = 1 << 26; + const USE_VIDEO = 1 << 27; + const USE_VOICE = 1 << 28; + const INVITE_USERS = 1 << 29; + const SELF_DELETE_DISABLE = 1 << 30; + const DEBTABLE = 1 << 31; + const CREDITABLE = 1 << 32; + const KICK_BAN_MEMBERS = 1 << 33; + const SELF_LEAVE_GROUPS = 1 << 34; + const PRESENCE = 1 << 35; + const SELF_ADD_DISCOVERABLE = 1 << 36; + const MANAGE_GUILD_DIRECTORY = 1 << 37; + const POGGERS = 1 << 38; + const USE_ACHIEVEMENTS = 1 << 39; + const INITIATE_INTERACTIONS = 1 << 40; + const RESPOND_TO_INTERACTIONS = 1 << 41; + const SEND_BACKDATED_EVENTS = 1 << 42; + const USE_MASS_INVITES = 1 << 43; + const ACCEPT_INVITES = 1 << 44; + const SELF_EDIT_FLAGS = 1 << 45; + const EDIT_FLAGS = 1 << 46; + const MANAGE_GROUPS = 1 << 47; + const VIEW_SERVER_STATS = 1 << 48; + const RESEND_VERIFICATION_EMAIL = 1 << 49; + } +} + +impl Rights { + pub fn any(&self, permission: Rights, check_operator: bool) -> bool { + (check_operator && self.contains(Rights::OPERATOR)) || self.contains(permission) + } + + pub fn has(&self, permission: Rights, check_operator: bool) -> bool { + (check_operator && self.contains(Rights::OPERATOR)) || self.contains(permission) + } + + pub fn has_throw(&self, permission: Rights) -> Result { + if self.has(permission, true) { + Ok(true) + } else { + Err("You are missing the following rights") + } + } +} + +fn all_rights() -> Rights { + Rights::OPERATOR + | Rights::MANAGE_APPLICATIONS + | Rights::MANAGE_GUILDS + | Rights::MANAGE_MESSAGES + | Rights::MANAGE_RATE_LIMITS + | Rights::MANAGE_ROUTING + | Rights::MANAGE_TICKETS + | Rights::MANAGE_USERS + | Rights::ADD_MEMBERS + | Rights::BYPASS_RATE_LIMITS + | Rights::CREATE_APPLICATIONS + | Rights::CREATE_CHANNELS + | Rights::CREATE_DMS + | Rights::CREATE_DM_GROUPS + | Rights::CREATE_GUILDS + | Rights::CREATE_INVITES + | Rights::CREATE_ROLES + | Rights::CREATE_TEMPLATES + | Rights::CREATE_WEBHOOKS + | Rights::JOIN_GUILDS + | Rights::PIN_MESSAGES + | Rights::SELF_ADD_REACTIONS + | Rights::SELF_DELETE_MESSAGES + | Rights::SELF_EDIT_MESSAGES + | Rights::SELF_EDIT_NAME + | Rights::SEND_MESSAGES + | Rights::USE_ACTIVITIES + | Rights::USE_VIDEO + | Rights::USE_VOICE + | Rights::INVITE_USERS + | Rights::SELF_DELETE_DISABLE + | Rights::DEBTABLE + | Rights::CREDITABLE + | Rights::KICK_BAN_MEMBERS + | Rights::SELF_LEAVE_GROUPS + | Rights::PRESENCE + | Rights::SELF_ADD_DISCOVERABLE + | Rights::MANAGE_GUILD_DIRECTORY + | Rights::POGGERS + | Rights::USE_ACHIEVEMENTS + | Rights::INITIATE_INTERACTIONS + | Rights::RESPOND_TO_INTERACTIONS + | Rights::SEND_BACKDATED_EVENTS + | Rights::USE_MASS_INVITES + | Rights::ACCEPT_INVITES + | Rights::SELF_EDIT_FLAGS + | Rights::EDIT_FLAGS + | Rights::MANAGE_GROUPS + | Rights::VIEW_SERVER_STATS + | Rights::RESEND_VERIFICATION_EMAIL +} diff --git a/types/utils/snowflake.rs b/types/utils/snowflake.rs new file mode 100644 index 0000000..7c756fa --- /dev/null +++ b/types/utils/snowflake.rs @@ -0,0 +1,160 @@ +use std::fmt::Display; + +use atomic::Atomic; +use bigdecimal::{Num, ToPrimitive, Zero}; +use num_bigint::{BigInt, ToBigInt}; +use serde::{Deserialize, Serialize}; +#[cfg(feature = "sqlx")] +use sqlx::Type; + +const EPOCH: i64 = 1420070400000; +static WORKER_ID: u128 = 0; +static PROCESS_ID: u128 = 1; +lazy_static::lazy_static! { + static ref INCREMENT: Atomic = Atomic::default(); +} +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[cfg_attr(feature = "sqlx", derive(Type))] +#[cfg_attr(feature = "sqlx", sqlx(transparent))] +pub struct Snowflake(String); + +impl Default for Snowflake { + fn default() -> Self { + Self::generate() + } +} + +impl Display for Snowflake { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.0) + } +} + +impl Snowflake { + pub fn to_binary(&self) -> String { + let self_len = self.0.len(); + let high = self.0[..self_len - 10].parse::().unwrap_or(0); + let low = self.0[self_len - 10..].parse::().unwrap(); + let mut low = low; + let mut high = high; + let mut bin = Vec::with_capacity(64); + + while low > 0 || high > 0 { + bin.push((low & 1) as u8); + low >>= 1; + + if high > 0 { + low += 5_000_000_000 * (high % 2); + high >>= 1; + } + } + + bin.iter() + .rev() + .map(|b| char::from_digit(*b as u32, 10).unwrap()) + .collect() + } + + pub fn from_binary(num: &str) -> String { + let mut num = BigInt::from_str_radix(num, 2).unwrap(); + let mut dec = Vec::with_capacity(18); + + let ten = 10.to_bigint().unwrap(); + let _two = 2.to_bigint().unwrap(); + let _thirty_two = 32.to_bigint().unwrap(); + + while num.bits() > 50 { + let high: BigInt = &num >> 32; + let low: BigInt = (high.clone() % &ten) << 32 | &num & BigInt::from((1u64 << 32) - 1); + + let next: BigInt = low.clone() % &ten; + dec.push(next.to_u8().unwrap()); + num = (high / &ten) << 32 | (low / &ten); + } + + while !num.is_zero() { + dec.push((num.clone() % &ten).to_u8().unwrap()); + num /= &ten; + } + + dec.iter() + .rev() + .map(|d| char::from_digit(*d as u32, 10).unwrap()) + .collect() + } + + pub fn generate_worker_process() -> u128 { + let time = (chrono::Utc::now().naive_utc().timestamp_millis() - EPOCH) << 22; + let worker = WORKER_ID << 17; + let process = PROCESS_ID << 12; + let increment = INCREMENT.load(atomic::Ordering::Relaxed); + + INCREMENT.store(increment + 1, atomic::Ordering::Relaxed); + + time as u128 | worker | process | increment + } + + pub fn generate() -> Self { + Self(Self::generate_worker_process().to_string()) + } + + pub fn deconstruct(&self) -> DeconstructedSnowflake { + let binary = format!("{:0>64}", self.to_binary()); + + let ts = i64::from_str_radix(&binary[0..42], 2).unwrap() + EPOCH; + let wid = u64::from_str_radix(&binary[42..47], 2).unwrap(); + let pid = u64::from_str_radix(&binary[47..52], 2).unwrap(); + let increment = BigInt::from_str_radix(&binary[52..64], 2).unwrap(); + + DeconstructedSnowflake { + timestamp: ts, + worker_id: wid, + process_id: pid, + increment, + binary, + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct DeconstructedSnowflake { + pub timestamp: i64, + pub worker_id: u64, + pub process_id: u64, + pub increment: BigInt, + pub binary: String, +} + +#[cfg(test)] +mod test { + use super::Snowflake; + + #[test] + fn test_new_snowflake() { + let snow = Snowflake::generate(); + println!("{snow}"); + } + + #[test] + fn snowflake_to_binary() { + let snowflake = super::Snowflake("1104339392517902336".to_string()); + + let bin = snowflake.to_binary(); + println!("{bin}"); + } + + #[test] + fn binary_to_snowflake() { + let snowflake = super::Snowflake::from_binary( + "111101010011011001101101001110010010100000000001000000000000", + ); + println!("{snowflake}"); + } + + #[test] + fn test_deconstruct() { + let new = super::Snowflake::generate(); + + println!("{:?}", new.deconstruct()); + } +}