From 163db425366033aa93efe0a70471667828a63228 Mon Sep 17 00:00:00 2001 From: kozabrada123 <59031733+kozabrada123@users.noreply.github.com> Date: Sat, 20 Jan 2024 13:15:13 +0100 Subject: [PATCH 01/53] Minor instance updates (#465) make Instance::from_url_bundle pub, update Instance docs --- src/instance.rs | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/src/instance.rs b/src/instance.rs index a4967e8..68a1f7d 100644 --- a/src/instance.rs +++ b/src/instance.rs @@ -19,6 +19,7 @@ use crate::UrlBundle; #[derive(Debug, Clone, Default, Serialize, Deserialize)] /// The [`Instance`]; what you will be using to perform all sorts of actions on the Spacebar server. +/// /// If `limits_information` is `None`, then the instance will not be rate limited. pub struct Instance { pub urls: UrlBundle, @@ -72,8 +73,18 @@ impl PartialEq for LimitsInformation { } impl Instance { - /// Creates a new [`Instance`] from the [relevant instance urls](UrlBundle). To create an Instance from one singular url, use [`Instance::from_root_url()`]. - async fn from_url_bundle(urls: UrlBundle) -> ChorusResult { + + pub(crate) fn clone_limits_if_some(&self) -> Option> { + if self.limits_information.is_some() { + return Some(self.limits_information.as_ref().unwrap().ratelimits.clone()); + } + None + } + + /// Creates a new [`Instance`] from the [relevant instance urls](UrlBundle). + /// + /// To create an Instance from one singular url, use [`Instance::new()`]. + pub async fn from_url_bundle(urls: UrlBundle) -> ChorusResult { let is_limited: Option = Instance::is_limited(&urls.api).await?; let limit_information; @@ -103,17 +114,9 @@ impl Instance { Ok(instance) } - pub(crate) fn clone_limits_if_some(&self) -> Option> { - if self.limits_information.is_some() { - return Some(self.limits_information.as_ref().unwrap().ratelimits.clone()); - } - None - } - /// Creates a new [`Instance`] by trying to get the [relevant instance urls](UrlBundle) from a root url. - /// Shorthand for `Instance::new(UrlBundle::from_root_domain(root_domain).await?)`. /// - /// If `limited` is `true`, then Chorus will track and enforce rate limits for this instance. + /// Shorthand for `Instance::from_url_bundle(UrlBundle::from_root_domain(root_domain).await?)`. pub async fn new(root_url: &str) -> ChorusResult { let urls = UrlBundle::from_root_url(root_url).await?; Instance::from_url_bundle(urls).await From 92654989e8470086388f6e48dc6e5d7ae7c8a49f Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Sun, 21 Jan 2024 17:06:43 +0100 Subject: [PATCH 02/53] Define type alias `Shared` --- src/gateway/mod.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/gateway/mod.rs b/src/gateway/mod.rs index 076ed54..c5f415e 100644 --- a/src/gateway/mod.rs +++ b/src/gateway/mod.rs @@ -122,3 +122,11 @@ impl GatewayEvent { } } } + +/// A type alias for [`Arc>`], used to make the public facing API concerned with +/// Composite structs more ergonomic. +/// ## Note +/// +/// While `T` does not have to implement `Composite` to be used with `Shared`, +/// the primary use of `Shared` is with types that implement `Composite`. +pub type Shared = Arc>; From 39b1f1fa72537724c95cd208d6ce0034a5be6e9f Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Sun, 21 Jan 2024 17:07:19 +0100 Subject: [PATCH 03/53] Replace use of Arc> with Shared --- src/gateway/handle.rs | 6 +++--- tests/common/mod.rs | 17 ++++++++--------- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/src/gateway/handle.rs b/src/gateway/handle.rs index 620faba..d44dfa2 100644 --- a/src/gateway/handle.rs +++ b/src/gateway/handle.rs @@ -42,8 +42,8 @@ impl GatewayHandle { pub async fn observe>( &self, - object: Arc>, - ) -> Arc> { + object: Shared, + ) -> Shared { let mut store = self.store.lock().await; let id = object.read().unwrap().id(); if let Some(channel) = store.get(&id) { @@ -84,7 +84,7 @@ impl GatewayHandle { /// with all of its observable fields being observed. pub async fn observe_and_into_inner>( &self, - object: Arc>, + object: Shared, ) -> T { let channel = self.observe(object.clone()).await; let object = channel.read().unwrap().clone(); diff --git a/tests/common/mod.rs b/tests/common/mod.rs index bce419a..eae2b26 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -1,6 +1,5 @@ -use std::sync::{Arc, RwLock}; - -use chorus::gateway::Gateway; +use chorus::gateway::{Gateway, Shared}; +use chorus::types::Composite; use chorus::{ instance::{ChorusUser, Instance}, types::{ @@ -16,9 +15,9 @@ pub(crate) struct TestBundle { pub urls: UrlBundle, pub user: ChorusUser, pub instance: Instance, - pub guild: Arc>, - pub role: Arc>, - pub channel: Arc>, + pub guild: Shared, + pub role: Shared, + pub channel: Shared, } #[allow(unused)] @@ -119,9 +118,9 @@ pub(crate) async fn setup() -> TestBundle { urls, user, instance, - guild: Arc::new(RwLock::new(guild)), - role: Arc::new(RwLock::new(role)), - channel: Arc::new(RwLock::new(channel)), + guild: guild.to_shared(), + role: role.to_shared(), + channel: channel.to_shared(), } } From 48fddb737850616a8fe00b47cc677ae84e55acfb Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Sun, 21 Jan 2024 17:07:30 +0100 Subject: [PATCH 04/53] rustfmt --- examples/gateway_simple.rs | 4 ++-- src/gateway/heartbeat.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/gateway_simple.rs b/examples/gateway_simple.rs index affa850..7a0d807 100644 --- a/examples/gateway_simple.rs +++ b/examples/gateway_simple.rs @@ -31,8 +31,8 @@ async fn main() { identify.token = token; // Send off the event - gateway.send_identify(identify).await; - + gateway.send_identify(identify).await; + // Do something on the main thread so we don't quit loop { sleep(Duration::from_secs(3600)).await; diff --git a/src/gateway/heartbeat.rs b/src/gateway/heartbeat.rs index 8e37697..e6991f3 100644 --- a/src/gateway/heartbeat.rs +++ b/src/gateway/heartbeat.rs @@ -71,7 +71,7 @@ impl HeartbeatHandler { let mut last_heartbeat_timestamp: Instant = Instant::now(); let mut last_heartbeat_acknowledged = true; let mut last_seq_number: Option = None; - + loop { if kill_receive.try_recv().is_ok() { trace!("GW: Closing heartbeat task"); From 22e8ca2a974fef2aaa74205e17f917bdfcb0b3f8 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Sun, 21 Jan 2024 17:07:54 +0100 Subject: [PATCH 05/53] Define public method `to_shared` for dyn Composite --- src/types/entities/mod.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/types/entities/mod.rs b/src/types/entities/mod.rs index 8343628..8a06c35 100644 --- a/src/types/entities/mod.rs +++ b/src/types/entities/mod.rs @@ -23,6 +23,8 @@ pub use user_settings::*; pub use voice_state::*; pub use webhook::*; +use crate::gateway::Shared; + #[cfg(feature = "client")] use crate::gateway::Updateable; @@ -121,4 +123,11 @@ pub trait Composite { } vec } + + fn to_shared(self) -> Shared + where + Self: Sized, + { + Arc::new(RwLock::new(self)) + } } From 400d4c74cf4709f15256be584b00265c4bcf344c Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Sun, 21 Jan 2024 17:10:24 +0100 Subject: [PATCH 06/53] Rename to_shared to into_shared --- src/types/entities/mod.rs | 2 +- tests/common/mod.rs | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/types/entities/mod.rs b/src/types/entities/mod.rs index 8a06c35..3f075ce 100644 --- a/src/types/entities/mod.rs +++ b/src/types/entities/mod.rs @@ -124,7 +124,7 @@ pub trait Composite { vec } - fn to_shared(self) -> Shared + fn into_shared(self) -> Shared where Self: Sized, { diff --git a/tests/common/mod.rs b/tests/common/mod.rs index eae2b26..a267125 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -118,9 +118,9 @@ pub(crate) async fn setup() -> TestBundle { urls, user, instance, - guild: guild.to_shared(), - role: role.to_shared(), - channel: channel.to_shared(), + guild: guild.into_shared(), + role: role.into_shared(), + channel: channel.into_shared(), } } From bae0ce8b1f9c7f1189518a6453cc18cdd7b61ce9 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Sun, 21 Jan 2024 17:15:11 +0100 Subject: [PATCH 07/53] Add documentation for into_shared --- src/types/entities/mod.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/types/entities/mod.rs b/src/types/entities/mod.rs index 3f075ce..7c914b1 100644 --- a/src/types/entities/mod.rs +++ b/src/types/entities/mod.rs @@ -124,6 +124,12 @@ pub trait Composite { vec } + /// Uses [`Shared`] to provide an ergonomic alternative to `Arc::new(RwLock::new(obj))`. + /// + /// [`Shared`] can then be observed using the [`Gateway`], turning the underlying + /// `dyn Composite` into a self-updating struct, which is a tracked variant of a chorus + /// entity struct, updating its' held information when new information concerning itself arrives + /// over the [`Gateway`] connection, reducing the need for expensive network-API calls. fn into_shared(self) -> Shared where Self: Sized, From 196a36a5febf6ba1fa132f8a4b7c893d3942df84 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Sun, 21 Jan 2024 20:13:00 +0100 Subject: [PATCH 08/53] Write documentation for observe --- src/gateway/handle.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/gateway/handle.rs b/src/gateway/handle.rs index d44dfa2..bc64077 100644 --- a/src/gateway/handle.rs +++ b/src/gateway/handle.rs @@ -40,6 +40,15 @@ impl GatewayHandle { .unwrap(); } + /// Recursively observes a [`Shared`] object, by making sure all [`Composite `] fields within + /// that object and its children are being watched. + /// + /// Observing means, that if new information arrives about the observed object or its children, + /// the object automatically gets updated, without you needing to request new information about + /// the object in question from the API, which is expensive and can lead to rate limiting. + /// + /// The [`Shared`] object returned by this method points to a different object than the one + /// being supplied as a &self function argument. pub async fn observe>( &self, object: Shared, From b5923c30e27fed849300f0bd0e73822d9a09f898 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Sun, 21 Jan 2024 20:24:17 +0100 Subject: [PATCH 09/53] Replace Arc, Rwlock with Shared --- tests/gateway.rs | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/tests/gateway.rs b/tests/gateway.rs index 5bf5865..deb3129 100644 --- a/tests/gateway.rs +++ b/tests/gateway.rs @@ -1,10 +1,8 @@ mod common; -use std::sync::{Arc, RwLock}; - use chorus::errors::GatewayError; use chorus::gateway::*; -use chorus::types::{self, ChannelModifySchema, RoleCreateModifySchema, RoleObject}; +use chorus::types::{self, ChannelModifySchema, Composite, RoleCreateModifySchema, RoleObject}; #[cfg(target_arch = "wasm32")] use wasm_bindgen_test::*; #[cfg(target_arch = "wasm32")] @@ -97,11 +95,7 @@ async fn test_recursive_self_updating_structs() { .await .unwrap(); // Watch role; - bundle - .user - .gateway - .observe(Arc::new(RwLock::new(role.clone()))) - .await; + bundle.user.gateway.observe(role.into_shared()).await; // Update Guild and check for Guild let inner_guild = guild.read().unwrap().clone(); assert!(inner_guild.roles.is_some()); @@ -113,7 +107,7 @@ async fn test_recursive_self_updating_structs() { let role_inner = bundle .user .gateway - .observe_and_into_inner(Arc::new(RwLock::new(role.clone()))) + .observe_and_into_inner(role.into_shared()) .await; assert_eq!(role_inner.name, "yippieee"); // Check if the change propagated From c4bea069b7d736e95fdcbb15fc3597111370118f Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Mon, 22 Jan 2024 14:50:33 +0100 Subject: [PATCH 10/53] Fix errors by moving into_shared out of Composite --- src/types/entities/mod.rs | 40 ++++++++++++++++++--------------------- tests/common/mod.rs | 8 ++++---- tests/gateway.rs | 6 +++--- 3 files changed, 25 insertions(+), 29 deletions(-) diff --git a/src/types/entities/mod.rs b/src/types/entities/mod.rs index 7c914b1..ee31bf4 100644 --- a/src/types/entities/mod.rs +++ b/src/types/entities/mod.rs @@ -71,9 +71,9 @@ pub trait Composite { async fn watch_whole(self, gateway: &GatewayHandle) -> Self; async fn option_observe_fn( - value: Option>>, + value: Option>, gateway: &GatewayHandle, - ) -> Option>> + ) -> Option> where T: Composite + Debug, { @@ -86,9 +86,9 @@ pub trait Composite { } async fn option_vec_observe_fn( - value: Option>>>, + value: Option>>, gateway: &GatewayHandle, - ) -> Option>>> + ) -> Option>> where T: Composite, { @@ -103,17 +103,14 @@ pub trait Composite { } } - async fn value_observe_fn(value: Arc>, gateway: &GatewayHandle) -> Arc> + async fn value_observe_fn(value: Shared, gateway: &GatewayHandle) -> Shared where T: Composite, { gateway.observe(value).await } - async fn vec_observe_fn( - value: Vec>>, - gateway: &GatewayHandle, - ) -> Vec>> + async fn vec_observe_fn(value: Vec>, gateway: &GatewayHandle) -> Vec> where T: Composite, { @@ -123,17 +120,16 @@ pub trait Composite { } vec } - - /// Uses [`Shared`] to provide an ergonomic alternative to `Arc::new(RwLock::new(obj))`. - /// - /// [`Shared`] can then be observed using the [`Gateway`], turning the underlying - /// `dyn Composite` into a self-updating struct, which is a tracked variant of a chorus - /// entity struct, updating its' held information when new information concerning itself arrives - /// over the [`Gateway`] connection, reducing the need for expensive network-API calls. - fn into_shared(self) -> Shared - where - Self: Sized, - { - Arc::new(RwLock::new(self)) - } +} + +/// Uses [`Shared`] to provide an ergonomic alternative to `Arc::new(RwLock::new(obj))`. +/// +/// [`Shared`] can then be observed using the [`Gateway`], turning the underlying +/// `dyn Composite` into a self-updating struct, which is a tracked variant of a chorus +/// entity struct, updating its' held information when new information concerning itself arrives +/// over the [`Gateway`] connection, reducing the need for expensive network-API calls. +pub fn into_shared + Updateable + Clone + Debug + Sized>( + composite: T, +) -> Shared { + Arc::new(RwLock::new(composite)) } diff --git a/tests/common/mod.rs b/tests/common/mod.rs index a267125..fe064fb 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -1,5 +1,5 @@ use chorus::gateway::{Gateway, Shared}; -use chorus::types::Composite; +use chorus::types::into_shared; use chorus::{ instance::{ChorusUser, Instance}, types::{ @@ -118,9 +118,9 @@ pub(crate) async fn setup() -> TestBundle { urls, user, instance, - guild: guild.into_shared(), - role: role.into_shared(), - channel: channel.into_shared(), + guild: into_shared(guild), + role: into_shared(role), + channel: into_shared(channel), } } diff --git a/tests/gateway.rs b/tests/gateway.rs index deb3129..84361fb 100644 --- a/tests/gateway.rs +++ b/tests/gateway.rs @@ -2,7 +2,7 @@ mod common; use chorus::errors::GatewayError; use chorus::gateway::*; -use chorus::types::{self, ChannelModifySchema, Composite, RoleCreateModifySchema, RoleObject}; +use chorus::types::{self, into_shared, ChannelModifySchema, RoleCreateModifySchema, RoleObject}; #[cfg(target_arch = "wasm32")] use wasm_bindgen_test::*; #[cfg(target_arch = "wasm32")] @@ -95,7 +95,7 @@ async fn test_recursive_self_updating_structs() { .await .unwrap(); // Watch role; - bundle.user.gateway.observe(role.into_shared()).await; + bundle.user.gateway.observe(into_shared(role.clone())).await; // Update Guild and check for Guild let inner_guild = guild.read().unwrap().clone(); assert!(inner_guild.roles.is_some()); @@ -107,7 +107,7 @@ async fn test_recursive_self_updating_structs() { let role_inner = bundle .user .gateway - .observe_and_into_inner(role.into_shared()) + .observe_and_into_inner(into_shared(role.clone())) .await; assert_eq!(role_inner.name, "yippieee"); // Check if the change propagated From b521928f81d185ece190b92580a7d6b51e04acc0 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Mon, 22 Jan 2024 14:56:23 +0100 Subject: [PATCH 11/53] Make IntoShared trait with blanket implementation --- src/types/entities/mod.rs | 24 ++++++++++++++---------- tests/common/mod.rs | 8 ++++---- tests/gateway.rs | 10 +++++++--- 3 files changed, 25 insertions(+), 17 deletions(-) diff --git a/src/types/entities/mod.rs b/src/types/entities/mod.rs index ee31bf4..d314d82 100644 --- a/src/types/entities/mod.rs +++ b/src/types/entities/mod.rs @@ -122,14 +122,18 @@ pub trait Composite { } } -/// Uses [`Shared`] to provide an ergonomic alternative to `Arc::new(RwLock::new(obj))`. -/// -/// [`Shared`] can then be observed using the [`Gateway`], turning the underlying -/// `dyn Composite` into a self-updating struct, which is a tracked variant of a chorus -/// entity struct, updating its' held information when new information concerning itself arrives -/// over the [`Gateway`] connection, reducing the need for expensive network-API calls. -pub fn into_shared + Updateable + Clone + Debug + Sized>( - composite: T, -) -> Shared { - Arc::new(RwLock::new(composite)) +pub trait IntoShared { + /// Uses [`Shared`] to provide an ergonomic alternative to `Arc::new(RwLock::new(obj))`. + /// + /// [`Shared`] can then be observed using the [`Gateway`], turning the underlying + /// `dyn Composite` into a self-updating struct, which is a tracked variant of a chorus + /// entity struct, updating its' held information when new information concerning itself arrives + /// over the [`Gateway`] connection, reducing the need for expensive network-API calls. + fn into_shared(self) -> Shared; +} + +impl + Updateable + Clone + Debug + ?Sized> IntoShared for T { + fn into_shared(self) -> Shared { + Arc::new(RwLock::new(self)) + } } diff --git a/tests/common/mod.rs b/tests/common/mod.rs index fe064fb..e18f92b 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -1,5 +1,5 @@ use chorus::gateway::{Gateway, Shared}; -use chorus::types::into_shared; +use chorus::types::IntoShared; use chorus::{ instance::{ChorusUser, Instance}, types::{ @@ -118,9 +118,9 @@ pub(crate) async fn setup() -> TestBundle { urls, user, instance, - guild: into_shared(guild), - role: into_shared(role), - channel: into_shared(channel), + guild: guild.into_shared(), + role: role.into_shared(), + channel: channel.into_shared(), } } diff --git a/tests/gateway.rs b/tests/gateway.rs index 84361fb..66259f7 100644 --- a/tests/gateway.rs +++ b/tests/gateway.rs @@ -2,7 +2,7 @@ mod common; use chorus::errors::GatewayError; use chorus::gateway::*; -use chorus::types::{self, into_shared, ChannelModifySchema, RoleCreateModifySchema, RoleObject}; +use chorus::types::{self, ChannelModifySchema, IntoShared, RoleCreateModifySchema, RoleObject}; #[cfg(target_arch = "wasm32")] use wasm_bindgen_test::*; #[cfg(target_arch = "wasm32")] @@ -95,7 +95,11 @@ async fn test_recursive_self_updating_structs() { .await .unwrap(); // Watch role; - bundle.user.gateway.observe(into_shared(role.clone())).await; + bundle + .user + .gateway + .observe(role.clone().into_shared()) + .await; // Update Guild and check for Guild let inner_guild = guild.read().unwrap().clone(); assert!(inner_guild.roles.is_some()); @@ -107,7 +111,7 @@ async fn test_recursive_self_updating_structs() { let role_inner = bundle .user .gateway - .observe_and_into_inner(into_shared(role.clone())) + .observe_and_into_inner(role.clone().into_shared()) .await; assert_eq!(role_inner.name, "yippieee"); // Check if the change propagated From 2209efc6a0abc0dab3e61b188e2d4e4b78806f8f Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Mon, 22 Jan 2024 15:00:46 +0100 Subject: [PATCH 12/53] Loosen bounds on IntoShared --- src/types/entities/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/types/entities/mod.rs b/src/types/entities/mod.rs index d314d82..5ceada6 100644 --- a/src/types/entities/mod.rs +++ b/src/types/entities/mod.rs @@ -132,7 +132,7 @@ pub trait IntoShared { fn into_shared(self) -> Shared; } -impl + Updateable + Clone + Debug + ?Sized> IntoShared for T { +impl IntoShared for T { fn into_shared(self) -> Shared { Arc::new(RwLock::new(self)) } From a3bae6d312e4198f1dc41d68ec0745da9b9f1a71 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Mon, 22 Jan 2024 20:57:08 +0100 Subject: [PATCH 13/53] Fix broken behaviour for ConfigEntity --- src/types/entities/config.rs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/types/entities/config.rs b/src/types/entities/config.rs index 25b1ef1..6244e9d 100644 --- a/src/types/entities/config.rs +++ b/src/types/entities/config.rs @@ -15,20 +15,29 @@ impl ConfigEntity { let Some(v) = self.value.as_ref() else { return None; }; - Some(v.as_str().expect("value is not a string").to_string()) + let Some(v) = v.as_str() else { + return None; + }; + Some(v.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")) + let Some(v) = v.as_bool() else { + return None; + }; + Some(v) } 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")) + let Some(v) = v.as_i64() else { + return None; + }; + Some(v) } } From c405162372614d0c06addbb388296e98e49e11f2 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Mon, 22 Jan 2024 20:57:17 +0100 Subject: [PATCH 14/53] rustfmt --- src/instance.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/instance.rs b/src/instance.rs index 68a1f7d..7ec673c 100644 --- a/src/instance.rs +++ b/src/instance.rs @@ -73,7 +73,6 @@ impl PartialEq for LimitsInformation { } impl Instance { - pub(crate) fn clone_limits_if_some(&self) -> Option> { if self.limits_information.is_some() { return Some(self.limits_information.as_ref().unwrap().ratelimits.clone()); From 0a0e6c3ffeb62afd0e089b5bd4fa758dad8f3898 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Mon, 22 Jan 2024 20:57:29 +0100 Subject: [PATCH 15/53] write unit tests for config and entities --- tests/types.rs | 119 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 119 insertions(+) create mode 100644 tests/types.rs diff --git a/tests/types.rs b/tests/types.rs new file mode 100644 index 0000000..c5f131c --- /dev/null +++ b/tests/types.rs @@ -0,0 +1,119 @@ +mod config { + mod subconfigs { + mod client { + use chorus::types::types::subconfigs::client::ClientReleaseConfiguration; + + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] + #[cfg_attr(not(target_arch = "wasm32"), test)] + fn client_release_configuration() { + let _client_release_configuration = ClientReleaseConfiguration::default(); + } + } + + mod limits { + use chorus::types::types::subconfigs::limits::rates::RateLimits; + + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] + #[cfg_attr(not(target_arch = "wasm32"), test)] + fn rates() { + let rate_limits = RateLimits::default(); + let hash_map = rate_limits.to_hash_map(); + assert!(hash_map.contains_key(&chorus::types::LimitType::ChannelBaseline)); + assert!(hash_map.contains_key(&chorus::types::LimitType::GuildBaseline)); + assert!(hash_map.contains_key(&chorus::types::LimitType::AuthLogin)); + assert!(hash_map.contains_key(&chorus::types::LimitType::AuthRegister)); + assert!(hash_map.contains_key(&chorus::types::LimitType::Error)); + assert!(hash_map.contains_key(&chorus::types::LimitType::Global)); + assert!(hash_map.contains_key(&chorus::types::LimitType::Ip)); + assert!(hash_map.contains_key(&chorus::types::LimitType::WebhookBaseline)); + assert!(hash_map.len() == 8) + } + } + } + + mod guild_configuration { + use std::ops::Deref; + + use chorus::types::types::guild_configuration::GuildFeaturesList; + + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] + #[cfg_attr(not(target_arch = "wasm32"), test)] + fn deref_guild_features_list() { + let guild_features_list = &GuildFeaturesList::default(); + let _guild_features_list_deref = guild_features_list.deref().clone(); + } + } + + mod domains_configuration { + use chorus::types::types::domains_configuration::Domains; + + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] + #[cfg_attr(not(target_arch = "wasm32"), test)] + fn display_domains() { + let domains = Domains { + cdn: "http://localhost:3020/cdn/".to_string(), + gateway: "ws://localhost:3020/".to_string(), + api_endpoint: "http://localhost:3020/".to_string(), + default_api_version: "9".to_string(), + }; + let fmt_domains = domains.to_string(); + assert!(fmt_domains.contains("CDN URL: http://localhost:3020/cdn/")); + assert!(fmt_domains.contains("Gateway URL: ws://localhost:3020/")); + assert!(fmt_domains.contains("API Endpoint: http://localhost:3020/")); + assert!(fmt_domains.contains("Default API Version: 9")); + } + } +} + +mod entities { + use std::sync::{Arc, RwLock}; + + use chorus::types::{ApplicationFlags, ConfigEntity, Emoji, User}; + use serde_json::json; + + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] + #[cfg_attr(not(target_arch = "wasm32"), test)] + fn application() { + let application = chorus::types::Application::default(); + assert!(application.name == *""); + assert!(application.verify_key == *""); + assert_ne!( + application.owner.read().unwrap().clone(), + Arc::new(RwLock::new(User::default())) + .read() + .unwrap() + .clone() + ); + let flags = ApplicationFlags::from_bits(0).unwrap(); + assert!(application.flags() == flags); + } + + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] + #[cfg_attr(not(target_arch = "wasm32"), test)] + fn config() { + let mut config_entity = ConfigEntity { + key: "ExampleKey".to_string(), + value: Some(json!(1)), + }; + config_entity.as_int().unwrap(); + assert!(config_entity.as_bool().is_none()); + assert!(config_entity.as_string().is_none()); + config_entity.value = Some(json!(true)); + config_entity.as_bool().unwrap(); + assert!(config_entity.as_int().is_none()); + assert!(config_entity.as_string().is_none()); + config_entity.value = Some(json!("Hello")); + config_entity.as_string().unwrap(); + assert!(config_entity.as_bool().is_none()); + assert!(config_entity.as_int().is_none()); + } + + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] + #[cfg_attr(not(target_arch = "wasm32"), test)] + fn emoji() { + let emoji = Emoji::default(); + let another_emoji = Emoji::default(); + assert_ne!(emoji.id, another_emoji.id); + assert_ne!(emoji, another_emoji); + } +} From 9d543fcd2772e9d740221e03952dd0d601b8e004 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Tue, 23 Jan 2024 18:43:06 +0100 Subject: [PATCH 16/53] Remove impl Eq from types that didn't qualify for Eq --- src/instance.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/instance.rs b/src/instance.rs index 7ec673c..bfa794a 100644 --- a/src/instance.rs +++ b/src/instance.rs @@ -37,8 +37,6 @@ impl PartialEq for Instance { } } -impl Eq for Instance {} - impl std::hash::Hash for Instance { fn hash(&self, state: &mut H) { self.urls.hash(state); @@ -171,8 +169,6 @@ impl PartialEq for ChorusUser { } } -impl Eq for ChorusUser {} - impl ChorusUser { pub fn token(&self) -> String { self.token.clone() From 5e13f12cbdff451fbbd3ce6fea998866baaf606c Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Tue, 23 Jan 2024 19:07:23 +0100 Subject: [PATCH 17/53] Replace usage of Arc> in public APIs with Shared<...> --- src/instance.rs | 16 ++++++++-------- src/types/entities/application.rs | 15 ++++++++------- src/types/entities/audit_log.rs | 3 ++- src/types/entities/auto_moderation.rs | 7 ++++--- src/types/entities/channel.rs | 7 ++++--- src/types/entities/emoji.rs | 3 ++- src/types/entities/guild.rs | 19 ++++++++++--------- src/types/entities/guild_member.rs | 3 ++- src/types/entities/integration.rs | 5 +++-- src/types/entities/invite.rs | 5 +++-- src/types/entities/message.rs | 3 ++- src/types/entities/mod.rs | 2 -- src/types/entities/relationship.rs | 3 ++- src/types/entities/sticker.rs | 3 ++- src/types/entities/team.rs | 3 ++- src/types/entities/template.rs | 7 ++++--- src/types/entities/user_settings.rs | 6 ++++-- src/types/entities/voice_state.rs | 3 ++- src/types/entities/webhook.rs | 5 +++-- src/types/events/channel.rs | 11 ++++++----- src/types/events/guild.rs | 18 +++++++++--------- src/types/events/mod.rs | 10 ++++++---- 22 files changed, 88 insertions(+), 69 deletions(-) diff --git a/src/instance.rs b/src/instance.rs index bfa794a..cd5f6b2 100644 --- a/src/instance.rs +++ b/src/instance.rs @@ -9,7 +9,7 @@ use reqwest::Client; use serde::{Deserialize, Serialize}; use crate::errors::ChorusResult; -use crate::gateway::{Gateway, GatewayHandle}; +use crate::gateway::{Gateway, GatewayHandle, Shared}; use crate::ratelimiter::ChorusRequest; use crate::types::types::subconfigs::limits::rates::RateLimits; use crate::types::{ @@ -153,11 +153,11 @@ impl fmt::Display for Token { /// It is used for most authenticated actions on a Spacebar server. /// It also has its own [Gateway] connection. pub struct ChorusUser { - pub belongs_to: Arc>, + pub belongs_to: Shared, pub token: String, pub limits: Option>, - pub settings: Arc>, - pub object: Arc>, + pub settings: Shared, + pub object: Shared, pub gateway: GatewayHandle, } @@ -184,11 +184,11 @@ impl ChorusUser { /// This isn't the prefered way to create a ChorusUser. /// See [Instance::login_account] and [Instance::register_account] instead. pub fn new( - belongs_to: Arc>, + belongs_to: Shared, token: String, limits: Option>, - settings: Arc>, - object: Arc>, + settings: Shared, + object: Shared, gateway: GatewayHandle, ) -> ChorusUser { ChorusUser { @@ -206,7 +206,7 @@ impl ChorusUser { /// registering or logging in to the Instance, where you do not yet have a User object, but still /// need to make a RateLimited request. To use the [`GatewayHandle`], you will have to identify /// first. - pub(crate) async fn shell(instance: Arc>, token: String) -> ChorusUser { + pub(crate) async fn shell(instance: Shared, token: String) -> ChorusUser { let settings = Arc::new(RwLock::new(UserSettings::default())); let object = Arc::new(RwLock::new(User::default())); let wss_url = instance.read().unwrap().urls.wss.clone(); diff --git a/src/types/entities/application.rs b/src/types/entities/application.rs index 0b55626..0f95ce8 100644 --- a/src/types/entities/application.rs +++ b/src/types/entities/application.rs @@ -5,6 +5,7 @@ use serde::{Deserialize, Serialize}; use serde_json::Value; use serde_repr::{Deserialize_repr, Serialize_repr}; +use crate::gateway::Shared; use crate::types::utils::Snowflake; use crate::types::{Team, User}; @@ -27,7 +28,7 @@ pub struct Application { pub bot_require_code_grant: bool, pub verify_key: String, #[cfg_attr(feature = "sqlx", sqlx(skip))] - pub owner: Arc>, + pub owner: Shared, pub flags: u64, #[cfg(feature = "sqlx")] pub redirect_uris: Option>>, @@ -49,7 +50,7 @@ pub struct Application { #[cfg(feature = "sqlx")] pub install_params: Option>, #[cfg(not(feature = "sqlx"))] - pub install_params: Option>>, + pub install_params: Option>, pub terms_of_service_url: Option, pub privacy_policy_url: Option, #[cfg_attr(feature = "sqlx", sqlx(skip))] @@ -142,7 +143,7 @@ pub struct ApplicationCommand { pub application_id: Snowflake, pub name: String, pub description: String, - pub options: Vec>>, + pub options: Vec>, } #[derive(Debug, Clone, Serialize, Deserialize)] @@ -154,7 +155,7 @@ pub struct ApplicationCommandOption { pub description: String, pub required: bool, pub choices: Vec, - pub options: Arc>>, + pub options: Shared>, } #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] @@ -190,14 +191,14 @@ pub enum ApplicationCommandOptionType { pub struct ApplicationCommandInteractionData { pub id: Snowflake, pub name: String, - pub options: Vec>>, + pub options: Vec>, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ApplicationCommandInteractionDataOption { pub name: String, pub value: Value, - pub options: Vec>>, + pub options: Vec>, } #[derive(Debug, Default, Clone, Serialize, Deserialize)] @@ -206,7 +207,7 @@ pub struct GuildApplicationCommandPermissions { pub id: Snowflake, pub application_id: Snowflake, pub guild_id: Snowflake, - pub permissions: Vec>>, + pub permissions: Vec>, } #[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)] diff --git a/src/types/entities/audit_log.rs b/src/types/entities/audit_log.rs index be14f0f..b6a43a7 100644 --- a/src/types/entities/audit_log.rs +++ b/src/types/entities/audit_log.rs @@ -2,13 +2,14 @@ use std::sync::{Arc, RwLock}; use serde::{Deserialize, Serialize}; +use crate::gateway::Shared; use crate::types::utils::Snowflake; #[derive(Serialize, Deserialize, Debug, Default, Clone)] /// See pub struct AuditLogEntry { pub target_id: Option, - pub changes: Option>>>, + pub changes: Option>>, pub user_id: Option, pub id: Snowflake, // to:do implement an enum for these types diff --git a/src/types/entities/auto_moderation.rs b/src/types/entities/auto_moderation.rs index a8910b1..11bc258 100644 --- a/src/types/entities/auto_moderation.rs +++ b/src/types/entities/auto_moderation.rs @@ -1,5 +1,6 @@ use std::sync::{Arc, RwLock}; +use crate::gateway::Shared; #[cfg(feature = "client")] use crate::gateway::Updateable; @@ -21,8 +22,8 @@ pub struct AutoModerationRule { pub creator_id: Snowflake, pub event_type: AutoModerationRuleEventType, pub trigger_type: AutoModerationRuleTriggerType, - pub trigger_metadata: Arc>, - pub actions: Vec>>, + pub trigger_metadata: Shared, + pub actions: Vec>, pub enabled: bool, pub exempt_roles: Vec, pub exempt_channels: Vec, @@ -99,7 +100,7 @@ pub enum AutoModerationRuleKeywordPresetType { pub struct AutoModerationAction { #[serde(rename = "type")] pub action_type: AutoModerationActionType, - pub metadata: Option>>, + pub metadata: Option>, } #[derive(Serialize_repr, Deserialize_repr, Debug, Clone, Default)] diff --git a/src/types/entities/channel.rs b/src/types/entities/channel.rs index 5acf2ae..75fffbc 100644 --- a/src/types/entities/channel.rs +++ b/src/types/entities/channel.rs @@ -6,6 +6,7 @@ use serde_aux::prelude::deserialize_string_from_number; use serde_repr::{Deserialize_repr, Serialize_repr}; use std::fmt::Debug; +use crate::gateway::Shared; use crate::types::{ entities::{GuildMember, User}, utils::Snowflake, @@ -71,13 +72,13 @@ pub struct Channel { pub permission_overwrites: Option>>, #[cfg(not(feature = "sqlx"))] #[cfg_attr(feature = "client", observe_option_vec)] - pub permission_overwrites: Option>>>, + pub permission_overwrites: Option>>, pub permissions: Option, pub position: Option, pub rate_limit_per_user: Option, #[cfg_attr(feature = "sqlx", sqlx(skip))] #[cfg_attr(feature = "client", observe_option_vec)] - pub recipients: Option>>>, + pub recipients: Option>>, pub rtc_region: Option, #[cfg_attr(feature = "sqlx", sqlx(skip))] pub thread_metadata: Option, @@ -171,7 +172,7 @@ pub struct ThreadMember { pub user_id: Option, pub join_timestamp: Option, pub flags: Option, - pub member: Option>>, + pub member: Option>, } #[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)] diff --git a/src/types/entities/emoji.rs b/src/types/entities/emoji.rs index b3916e2..fd2b252 100644 --- a/src/types/entities/emoji.rs +++ b/src/types/entities/emoji.rs @@ -3,6 +3,7 @@ use std::sync::{Arc, RwLock}; use serde::{Deserialize, Serialize}; +use crate::gateway::Shared; use crate::types::entities::User; use crate::types::Snowflake; @@ -31,7 +32,7 @@ pub struct Emoji { #[cfg(not(feature = "sqlx"))] pub roles: Option>, #[cfg_attr(feature = "sqlx", sqlx(skip))] - pub user: Option>>, + pub user: Option>, pub require_colons: Option, pub managed: Option, pub animated: Option, diff --git a/src/types/entities/guild.rs b/src/types/entities/guild.rs index 1fe235b..e386fed 100644 --- a/src/types/entities/guild.rs +++ b/src/types/entities/guild.rs @@ -6,6 +6,7 @@ use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; use serde_repr::{Deserialize_repr, Serialize_repr}; +use crate::gateway::Shared; use crate::types::types::guild_configuration::GuildFeaturesList; use crate::types::{ entities::{Channel, Emoji, RoleObject, Sticker, User, VoiceState, Webhook}, @@ -45,14 +46,14 @@ pub struct Guild { pub bans: Option>, #[cfg_attr(feature = "sqlx", sqlx(skip))] #[cfg_attr(feature = "client", observe_option_vec)] - pub channels: Option>>>, + pub channels: Option>>, pub default_message_notifications: Option, pub description: Option, pub discovery_splash: Option, #[cfg_attr(feature = "sqlx", sqlx(skip))] #[cfg_attr(feature = "client", observe_vec)] #[serde(default)] - pub emojis: Vec>>, + pub emojis: Vec>, pub explicit_content_filter: Option, //#[cfg_attr(feature = "sqlx", sqlx(try_from = "String"))] pub features: Option, @@ -88,7 +89,7 @@ pub struct Guild { pub region: Option, #[cfg_attr(feature = "sqlx", sqlx(skip))] #[cfg_attr(feature = "client", observe_option_vec)] - pub roles: Option>>>, + pub roles: Option>>, #[cfg_attr(feature = "sqlx", sqlx(skip))] pub rules_channel: Option, pub rules_channel_id: Option, @@ -102,10 +103,10 @@ pub struct Guild { pub verification_level: Option, #[cfg_attr(feature = "sqlx", sqlx(skip))] #[cfg_attr(feature = "client", observe_option_vec)] - pub voice_states: Option>>>, + pub voice_states: Option>>, #[cfg_attr(feature = "sqlx", sqlx(skip))] #[cfg_attr(feature = "client", observe_option_vec)] - pub webhooks: Option>>>, + pub webhooks: Option>>, #[cfg(feature = "sqlx")] pub welcome_screen: Option>, #[cfg(not(feature = "sqlx"))] @@ -239,11 +240,11 @@ pub struct GuildInvite { pub created_at: DateTime, pub expires_at: Option>, pub guild_id: Snowflake, - pub guild: Option>>, + pub guild: Option>, pub channel_id: Snowflake, - pub channel: Option>>, + pub channel: Option>, pub inviter_id: Option, - pub inviter: Option>>, + pub inviter: Option>, pub target_user_id: Option, pub target_user: Option, pub target_user_type: Option, @@ -296,7 +297,7 @@ pub struct GuildScheduledEvent { pub entity_type: GuildScheduledEventEntityType, pub entity_id: Option, pub entity_metadata: Option, - pub creator: Option>>, + pub creator: Option>, pub user_count: Option, pub image: Option, } diff --git a/src/types/entities/guild_member.rs b/src/types/entities/guild_member.rs index bf2f93b..a9701fa 100644 --- a/src/types/entities/guild_member.rs +++ b/src/types/entities/guild_member.rs @@ -2,6 +2,7 @@ use std::sync::{Arc, RwLock}; use serde::{Deserialize, Serialize}; +use crate::gateway::Shared; use crate::types::{entities::PublicUser, Snowflake}; #[derive(Debug, Deserialize, Default, Serialize, Clone)] @@ -10,7 +11,7 @@ use crate::types::{entities::PublicUser, Snowflake}; /// # Reference /// See pub struct GuildMember { - pub user: Option>>, + pub user: Option>, pub nick: Option, pub avatar: Option, pub roles: Vec, diff --git a/src/types/entities/integration.rs b/src/types/entities/integration.rs index 0913213..15ca42f 100644 --- a/src/types/entities/integration.rs +++ b/src/types/entities/integration.rs @@ -3,6 +3,7 @@ use std::sync::{Arc, RwLock}; use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; +use crate::gateway::Shared; use crate::types::{ entities::{Application, User}, utils::Snowflake, @@ -23,14 +24,14 @@ pub struct Integration { pub expire_behaviour: Option, pub expire_grace_period: Option, #[cfg_attr(feature = "sqlx", sqlx(skip))] - pub user: Option>>, + pub user: Option>, #[cfg_attr(feature = "sqlx", sqlx(skip))] pub account: IntegrationAccount, pub synced_at: Option>, pub subscriber_count: Option, pub revoked: Option, #[cfg_attr(feature = "sqlx", sqlx(skip))] - pub application: Option>>, + pub application: Option>, pub scopes: Option>, } diff --git a/src/types/entities/invite.rs b/src/types/entities/invite.rs index d08ad1d..577d237 100644 --- a/src/types/entities/invite.rs +++ b/src/types/entities/invite.rs @@ -3,6 +3,7 @@ use std::sync::{Arc, RwLock}; use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; +use crate::gateway::Shared; use crate::types::{Snowflake, WelcomeScreenObject}; use super::guild::GuildScheduledEvent; @@ -21,7 +22,7 @@ pub struct Invite { pub flags: Option, pub guild: Option, pub guild_id: Option, - pub guild_scheduled_event: Option>>, + pub guild_scheduled_event: Option>, #[serde(rename = "type")] pub invite_type: Option, pub inviter: Option, @@ -59,7 +60,7 @@ pub struct InviteGuild { /// See #[derive(Debug, Serialize, Deserialize)] pub struct InviteStageInstance { - pub members: Vec>>, + pub members: Vec>, pub participant_count: i32, pub speaker_count: i32, pub topic: String, diff --git a/src/types/entities/message.rs b/src/types/entities/message.rs index 41c4d51..802f806 100644 --- a/src/types/entities/message.rs +++ b/src/types/entities/message.rs @@ -2,6 +2,7 @@ use std::sync::{Arc, RwLock}; use serde::{Deserialize, Serialize}; +use crate::gateway::Shared; use crate::types::{ entities::{ Application, Attachment, Channel, Emoji, GuildMember, PublicUser, RoleSubscriptionData, @@ -121,7 +122,7 @@ pub struct MessageInteraction { pub interaction_type: u8, pub name: String, pub user: User, - pub member: Option>>, + pub member: Option>, } #[derive(Debug, Default, PartialEq, Clone, Serialize, Deserialize, Eq, PartialOrd, Ord)] diff --git a/src/types/entities/mod.rs b/src/types/entities/mod.rs index 5ceada6..7e88a62 100644 --- a/src/types/entities/mod.rs +++ b/src/types/entities/mod.rs @@ -24,7 +24,6 @@ pub use voice_state::*; pub use webhook::*; use crate::gateway::Shared; - #[cfg(feature = "client")] use crate::gateway::Updateable; @@ -36,7 +35,6 @@ use async_trait::async_trait; #[cfg(feature = "client")] use std::fmt::Debug; - #[cfg(feature = "client")] use std::sync::{Arc, RwLock}; diff --git a/src/types/entities/relationship.rs b/src/types/entities/relationship.rs index 576d99a..b73388e 100644 --- a/src/types/entities/relationship.rs +++ b/src/types/entities/relationship.rs @@ -4,6 +4,7 @@ use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; use serde_repr::{Deserialize_repr, Serialize_repr}; +use crate::gateway::Shared; use crate::types::Snowflake; use super::PublicUser; @@ -15,7 +16,7 @@ pub struct Relationship { #[serde(rename = "type")] pub relationship_type: RelationshipType, pub nickname: Option, - pub user: Arc>, + pub user: Shared, pub since: Option>, } diff --git a/src/types/entities/sticker.rs b/src/types/entities/sticker.rs index 593206d..c4bc4e8 100644 --- a/src/types/entities/sticker.rs +++ b/src/types/entities/sticker.rs @@ -2,6 +2,7 @@ use std::sync::{Arc, RwLock}; use serde::{Deserialize, Serialize}; +use crate::gateway::Shared; use crate::types::{entities::User, utils::Snowflake}; #[derive(Debug, Serialize, Deserialize, Clone, Default)] @@ -24,7 +25,7 @@ pub struct Sticker { pub available: Option, pub guild_id: Option, #[cfg_attr(feature = "sqlx", sqlx(skip))] - pub user: Option>>, + pub user: Option>, pub sort_value: Option, } diff --git a/src/types/entities/team.rs b/src/types/entities/team.rs index 8e32f55..1359a9a 100644 --- a/src/types/entities/team.rs +++ b/src/types/entities/team.rs @@ -2,6 +2,7 @@ use std::sync::{Arc, RwLock}; use serde::{Deserialize, Serialize}; +use crate::gateway::Shared; use crate::types::entities::User; use crate::types::Snowflake; @@ -21,5 +22,5 @@ pub struct TeamMember { pub membership_state: u8, pub permissions: Vec, pub team_id: Snowflake, - pub user: Arc>, + pub user: Shared, } diff --git a/src/types/entities/template.rs b/src/types/entities/template.rs index 1305a98..a5c7a38 100644 --- a/src/types/entities/template.rs +++ b/src/types/entities/template.rs @@ -3,6 +3,7 @@ use std::sync::{Arc, RwLock}; use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; +use crate::gateway::Shared; use crate::types::{ entities::{Guild, User}, utils::Snowflake, @@ -18,13 +19,13 @@ pub struct GuildTemplate { pub usage_count: Option, pub creator_id: Snowflake, #[cfg_attr(feature = "sqlx", sqlx(skip))] - pub creator: Arc>, + pub creator: Shared, pub created_at: DateTime, pub updated_at: DateTime, pub source_guild_id: Snowflake, #[cfg_attr(feature = "sqlx", sqlx(skip))] - pub source_guild: Vec>>, + pub source_guild: Vec>, // Unsure how a {recursive: Guild} looks like, might be a Vec? #[cfg_attr(feature = "sqlx", sqlx(skip))] - pub serialized_source_guild: Vec>>, + pub serialized_source_guild: Vec>, } diff --git a/src/types/entities/user_settings.rs b/src/types/entities/user_settings.rs index e6db7e7..a27e748 100644 --- a/src/types/entities/user_settings.rs +++ b/src/types/entities/user_settings.rs @@ -3,6 +3,8 @@ use std::sync::{Arc, RwLock}; use chrono::{serde::ts_milliseconds_option, Utc}; use serde::{Deserialize, Serialize}; +use crate::gateway::Shared; + #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)] #[cfg_attr(feature = "sqlx", derive(sqlx::Type))] #[serde(rename_all = "lowercase")] @@ -77,7 +79,7 @@ pub struct UserSettings { #[cfg(not(feature = "sqlx"))] pub restricted_guilds: Vec, pub show_current_game: bool, - pub status: Arc>, + pub status: Shared, pub stream_notifications_enabled: bool, pub theme: UserTheme, pub timezone_offset: i16, @@ -153,5 +155,5 @@ pub struct GuildFolder { #[derive(Debug, Serialize, Deserialize)] pub struct LoginResult { pub token: String, - pub settings: Arc>, + pub settings: Shared, } diff --git a/src/types/entities/voice_state.rs b/src/types/entities/voice_state.rs index 1a0c3b3..d75dde7 100644 --- a/src/types/entities/voice_state.rs +++ b/src/types/entities/voice_state.rs @@ -3,6 +3,7 @@ use std::sync::{Arc, RwLock}; #[cfg(feature = "client")] use chorus_macros::Composite; +use crate::gateway::Shared; #[cfg(feature = "client")] use crate::types::Composite; @@ -33,7 +34,7 @@ pub struct VoiceState { pub guild: Option, pub channel_id: Option, pub user_id: Snowflake, - pub member: Option>>, + pub member: Option>, pub session_id: String, pub token: Option, pub deaf: bool, diff --git a/src/types/entities/webhook.rs b/src/types/entities/webhook.rs index cf5716c..ad4f4ec 100644 --- a/src/types/entities/webhook.rs +++ b/src/types/entities/webhook.rs @@ -3,6 +3,7 @@ use std::sync::{Arc, RwLock}; use serde::{Deserialize, Serialize}; +use crate::gateway::Shared; #[cfg(feature = "client")] use crate::gateway::Updateable; @@ -36,10 +37,10 @@ pub struct Webhook { pub application_id: Snowflake, #[serde(skip_serializing_if = "Option::is_none")] #[cfg_attr(feature = "sqlx", sqlx(skip))] - pub user: Option>>, + pub user: Option>, #[serde(skip_serializing_if = "Option::is_none")] #[cfg_attr(feature = "sqlx", sqlx(skip))] - pub source_guild: Option>>, + pub source_guild: Option>, #[serde(skip_serializing_if = "Option::is_none")] pub url: Option, } diff --git a/src/types/events/channel.rs b/src/types/events/channel.rs index eb557d7..5ca73ec 100644 --- a/src/types/events/channel.rs +++ b/src/types/events/channel.rs @@ -1,4 +1,5 @@ use crate::types::events::WebSocketEvent; +use crate::types::IntoShared; use crate::types::{entities::Channel, JsonField, Snowflake, SourceUrlField}; use chorus_macros::{JsonField, SourceUrlField}; use chrono::{DateTime, Utc}; @@ -8,7 +9,7 @@ use serde::{Deserialize, Serialize}; use super::UpdateMessage; #[cfg(feature = "client")] -use std::sync::{Arc, RwLock}; +use crate::gateway::Shared; #[cfg(feature = "client")] use crate::types::Guild; @@ -42,9 +43,9 @@ impl UpdateMessage for ChannelCreate { self.channel.guild_id } - fn update(&mut self, object_to_update: Arc>) { + fn update(&mut self, object_to_update: Shared) { let mut write = object_to_update.write().unwrap(); - let update = Arc::new(RwLock::new(self.channel.clone())); + let update = self.channel.clone().into_shared(); if write.channels.is_some() { write.channels.as_mut().unwrap().push(update); } else { @@ -68,7 +69,7 @@ impl WebSocketEvent for ChannelUpdate {} #[cfg(feature = "client")] impl UpdateMessage for ChannelUpdate { - fn update(&mut self, object_to_update: Arc>) { + fn update(&mut self, object_to_update: Shared) { let mut write = object_to_update.write().unwrap(); *write = self.channel.clone(); } @@ -114,7 +115,7 @@ impl UpdateMessage for ChannelDelete { self.channel.guild_id } - fn update(&mut self, object_to_update: Arc>) { + fn update(&mut self, object_to_update: Shared) { if self.id().is_none() { return; } diff --git a/src/types/events/guild.rs b/src/types/events/guild.rs index 89e4a75..04f871a 100644 --- a/src/types/events/guild.rs +++ b/src/types/events/guild.rs @@ -5,8 +5,8 @@ use serde::{Deserialize, Serialize}; use crate::types::entities::{Guild, PublicUser, UnavailableGuild}; use crate::types::events::WebSocketEvent; use crate::types::{ - AuditLogEntry, Emoji, GuildMember, GuildScheduledEvent, JsonField, RoleObject, Snowflake, - SourceUrlField, Sticker, + AuditLogEntry, Emoji, GuildMember, GuildScheduledEvent, IntoShared, JsonField, RoleObject, + Snowflake, SourceUrlField, Sticker, }; use super::PresenceUpdate; @@ -14,7 +14,7 @@ use super::PresenceUpdate; #[cfg(feature = "client")] use super::UpdateMessage; #[cfg(feature = "client")] -use std::sync::{Arc, RwLock}; +use crate::gateway::Shared; #[derive(Debug, Deserialize, Serialize, Default, Clone, SourceUrlField, JsonField)] /// See ; @@ -38,7 +38,7 @@ impl UpdateMessage for GuildCreate { } } - fn update(&mut self, _: Arc>) {} + fn update(&mut self, _: Shared) {} } #[derive(Debug, Deserialize, Serialize, Clone)] @@ -114,7 +114,7 @@ impl UpdateMessage for GuildDelete { fn id(&self) -> Option { Some(self.guild.id) } - fn update(&mut self, _: Arc>) {} + fn update(&mut self, _: Shared) {} } impl WebSocketEvent for GuildDelete {} @@ -229,16 +229,16 @@ impl UpdateMessage for GuildRoleCreate { Some(self.guild_id) } - fn update(&mut self, object_to_update: Arc>) { + fn update(&mut self, object_to_update: Shared) { let mut object_to_update = object_to_update.write().unwrap(); if object_to_update.roles.is_some() { object_to_update .roles .as_mut() .unwrap() - .push(Arc::new(RwLock::new(self.role.clone()))); + .push(self.role.clone().into_shared()); } else { - object_to_update.roles = Some(Vec::from([Arc::new(RwLock::new(self.role.clone()))])); + object_to_update.roles = Some(Vec::from([self.role.clone().into_shared()])); } } } @@ -262,7 +262,7 @@ impl UpdateMessage for GuildRoleUpdate { Some(self.role.id) } - fn update(&mut self, object_to_update: Arc>) { + fn update(&mut self, object_to_update: Shared) { let mut write = object_to_update.write().unwrap(); *write = self.role.clone(); } diff --git a/src/types/events/mod.rs b/src/types/events/mod.rs index f4e926c..ac0f121 100644 --- a/src/types/events/mod.rs +++ b/src/types/events/mod.rs @@ -39,9 +39,9 @@ use serde_json::{from_str, from_value, to_value, Value}; #[cfg(feature = "client")] use std::collections::HashMap; -use std::fmt::Debug; #[cfg(feature = "client")] -use std::sync::{Arc, RwLock}; +use crate::gateway::Shared; +use std::fmt::Debug; #[cfg(feature = "client")] use serde::de::DeserializeOwned; @@ -132,7 +132,7 @@ pub(crate) trait UpdateMessage: Clone + JsonField + SourceUrlField where T: Updateable + Serialize + DeserializeOwned + Clone, { - fn update(&mut self, object_to_update: Arc>) { + fn update(&mut self, object_to_update: Shared) { update_object(self.get_json(), object_to_update) } fn id(&self) -> Option; @@ -152,8 +152,10 @@ pub trait SourceUrlField: Clone { /// Only applicable for events where the Update struct is the same as the Entity struct pub(crate) fn update_object( value: String, - object: Arc>, + object: Shared<(impl Updateable + Serialize + DeserializeOwned + Clone)>, ) { + use crate::gateway::Shared; + let data_from_event: HashMap = from_str(&value).unwrap(); let mut original_data: HashMap = from_value(to_value(object.clone()).unwrap()).unwrap(); From 3df3eae7ffe809859319e78f3f47eba98946ae54 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Tue, 23 Jan 2024 19:08:26 +0100 Subject: [PATCH 18/53] APPEND: Remove unused imports --- src/types/entities/application.rs | 2 -- src/types/entities/audit_log.rs | 2 -- src/types/entities/auto_moderation.rs | 2 -- src/types/entities/channel.rs | 2 -- src/types/entities/emoji.rs | 1 - src/types/entities/guild.rs | 1 - src/types/entities/guild_member.rs | 2 -- src/types/entities/integration.rs | 2 -- src/types/entities/invite.rs | 2 -- src/types/entities/message.rs | 2 -- src/types/entities/relationship.rs | 2 -- src/types/entities/sticker.rs | 2 -- src/types/entities/team.rs | 2 -- src/types/entities/template.rs | 2 -- src/types/entities/voice_state.rs | 2 -- src/types/entities/webhook.rs | 1 - src/types/events/mod.rs | 2 -- 17 files changed, 31 deletions(-) diff --git a/src/types/entities/application.rs b/src/types/entities/application.rs index 0f95ce8..95df4f2 100644 --- a/src/types/entities/application.rs +++ b/src/types/entities/application.rs @@ -1,5 +1,3 @@ -use std::sync::{Arc, RwLock}; - use bitflags::bitflags; use serde::{Deserialize, Serialize}; use serde_json::Value; diff --git a/src/types/entities/audit_log.rs b/src/types/entities/audit_log.rs index b6a43a7..4023b7a 100644 --- a/src/types/entities/audit_log.rs +++ b/src/types/entities/audit_log.rs @@ -1,5 +1,3 @@ -use std::sync::{Arc, RwLock}; - use serde::{Deserialize, Serialize}; use crate::gateway::Shared; diff --git a/src/types/entities/auto_moderation.rs b/src/types/entities/auto_moderation.rs index 11bc258..779f58b 100644 --- a/src/types/entities/auto_moderation.rs +++ b/src/types/entities/auto_moderation.rs @@ -1,5 +1,3 @@ -use std::sync::{Arc, RwLock}; - use crate::gateway::Shared; #[cfg(feature = "client")] use crate::gateway::Updateable; diff --git a/src/types/entities/channel.rs b/src/types/entities/channel.rs index 75fffbc..c268859 100644 --- a/src/types/entities/channel.rs +++ b/src/types/entities/channel.rs @@ -1,5 +1,3 @@ -use std::sync::{Arc, RwLock}; - use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; use serde_aux::prelude::deserialize_string_from_number; diff --git a/src/types/entities/emoji.rs b/src/types/entities/emoji.rs index fd2b252..5170637 100644 --- a/src/types/entities/emoji.rs +++ b/src/types/entities/emoji.rs @@ -1,5 +1,4 @@ use std::fmt::Debug; -use std::sync::{Arc, RwLock}; use serde::{Deserialize, Serialize}; diff --git a/src/types/entities/guild.rs b/src/types/entities/guild.rs index e386fed..ee796e4 100644 --- a/src/types/entities/guild.rs +++ b/src/types/entities/guild.rs @@ -1,5 +1,4 @@ use std::fmt::Debug; -use std::sync::{Arc, RwLock}; use bitflags::bitflags; use chrono::{DateTime, Utc}; diff --git a/src/types/entities/guild_member.rs b/src/types/entities/guild_member.rs index a9701fa..a18afbc 100644 --- a/src/types/entities/guild_member.rs +++ b/src/types/entities/guild_member.rs @@ -1,5 +1,3 @@ -use std::sync::{Arc, RwLock}; - use serde::{Deserialize, Serialize}; use crate::gateway::Shared; diff --git a/src/types/entities/integration.rs b/src/types/entities/integration.rs index 15ca42f..580590a 100644 --- a/src/types/entities/integration.rs +++ b/src/types/entities/integration.rs @@ -1,5 +1,3 @@ -use std::sync::{Arc, RwLock}; - use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; diff --git a/src/types/entities/invite.rs b/src/types/entities/invite.rs index 577d237..f9eaaa3 100644 --- a/src/types/entities/invite.rs +++ b/src/types/entities/invite.rs @@ -1,5 +1,3 @@ -use std::sync::{Arc, RwLock}; - use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; diff --git a/src/types/entities/message.rs b/src/types/entities/message.rs index 802f806..923ee61 100644 --- a/src/types/entities/message.rs +++ b/src/types/entities/message.rs @@ -1,5 +1,3 @@ -use std::sync::{Arc, RwLock}; - use serde::{Deserialize, Serialize}; use crate::gateway::Shared; diff --git a/src/types/entities/relationship.rs b/src/types/entities/relationship.rs index b73388e..6f207f2 100644 --- a/src/types/entities/relationship.rs +++ b/src/types/entities/relationship.rs @@ -1,5 +1,3 @@ -use std::sync::{Arc, RwLock}; - use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; use serde_repr::{Deserialize_repr, Serialize_repr}; diff --git a/src/types/entities/sticker.rs b/src/types/entities/sticker.rs index c4bc4e8..d4331fd 100644 --- a/src/types/entities/sticker.rs +++ b/src/types/entities/sticker.rs @@ -1,5 +1,3 @@ -use std::sync::{Arc, RwLock}; - use serde::{Deserialize, Serialize}; use crate::gateway::Shared; diff --git a/src/types/entities/team.rs b/src/types/entities/team.rs index 1359a9a..daac58c 100644 --- a/src/types/entities/team.rs +++ b/src/types/entities/team.rs @@ -1,5 +1,3 @@ -use std::sync::{Arc, RwLock}; - use serde::{Deserialize, Serialize}; use crate::gateway::Shared; diff --git a/src/types/entities/template.rs b/src/types/entities/template.rs index a5c7a38..167697f 100644 --- a/src/types/entities/template.rs +++ b/src/types/entities/template.rs @@ -1,5 +1,3 @@ -use std::sync::{Arc, RwLock}; - use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; diff --git a/src/types/entities/voice_state.rs b/src/types/entities/voice_state.rs index d75dde7..911cf96 100644 --- a/src/types/entities/voice_state.rs +++ b/src/types/entities/voice_state.rs @@ -1,5 +1,3 @@ -use std::sync::{Arc, RwLock}; - #[cfg(feature = "client")] use chorus_macros::Composite; diff --git a/src/types/entities/webhook.rs b/src/types/entities/webhook.rs index ad4f4ec..7eb2b66 100644 --- a/src/types/entities/webhook.rs +++ b/src/types/entities/webhook.rs @@ -1,5 +1,4 @@ use std::fmt::Debug; -use std::sync::{Arc, RwLock}; use serde::{Deserialize, Serialize}; diff --git a/src/types/events/mod.rs b/src/types/events/mod.rs index ac0f121..5644d8a 100644 --- a/src/types/events/mod.rs +++ b/src/types/events/mod.rs @@ -154,8 +154,6 @@ pub(crate) fn update_object( value: String, object: Shared<(impl Updateable + Serialize + DeserializeOwned + Clone)>, ) { - use crate::gateway::Shared; - let data_from_event: HashMap = from_str(&value).unwrap(); let mut original_data: HashMap = from_value(to_value(object.clone()).unwrap()).unwrap(); From f17fcb3c2cb408eee0d9284d86e32003c0aef5fe Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Tue, 23 Jan 2024 20:50:19 +0100 Subject: [PATCH 19/53] Create tests from to_str and from_str for GuildFeatures --- tests/types.rs | 833 ++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 832 insertions(+), 1 deletion(-) diff --git a/tests/types.rs b/tests/types.rs index c5f131c..e9a1de5 100644 --- a/tests/types.rs +++ b/tests/types.rs @@ -33,8 +33,10 @@ mod config { mod guild_configuration { use std::ops::Deref; + use std::str::FromStr; - use chorus::types::types::guild_configuration::GuildFeaturesList; + use chorus::types::types::guild_configuration::{GuildFeatures, GuildFeaturesList}; + use chorus::types::{Error, GuildError}; #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] #[cfg_attr(not(target_arch = "wasm32"), test)] @@ -42,6 +44,835 @@ mod config { let guild_features_list = &GuildFeaturesList::default(); let _guild_features_list_deref = guild_features_list.deref().clone(); } + + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] + #[cfg_attr(not(target_arch = "wasm32"), test)] + fn test_deref_mut() { + let mut guild_features_list = GuildFeaturesList::default(); + guild_features_list.clear(); + let mut list = GuildFeaturesList::default().to_vec(); + list.push(GuildFeatures::ActivitiesAlpha); + *guild_features_list = list.to_vec(); + assert_eq!(guild_features_list.len(), 1); + } + + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] + #[cfg_attr(not(target_arch = "wasm32"), test)] + fn test_display() { + let mut guild_features_list = GuildFeaturesList::default(); + guild_features_list.push(GuildFeatures::ActivitiesAlpha); + guild_features_list.push(GuildFeatures::AnimatedBanner); + assert_eq!( + format!("{}", guild_features_list), + "ACTIVITIES_ALPHA,ANIMATED_BANNER" + ); + } + + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] + #[cfg_attr(not(target_arch = "wasm32"), test)] + fn test_from_str() { + // GPT moment + assert_eq!( + GuildFeatures::from_str("ACTIVITIES_ALPHA").unwrap(), + GuildFeatures::ActivitiesAlpha + ); + assert_eq!( + GuildFeatures::from_str("ACTIVITIES_EMPLOYEE").unwrap(), + GuildFeatures::ActivitiesEmployee + ); + assert_eq!( + GuildFeatures::from_str("ACTIVITIES_INTERNAL_DEV").unwrap(), + GuildFeatures::ActivitiesInternalDev + ); + assert_eq!( + GuildFeatures::from_str("ANIMATED_BANNER").unwrap(), + GuildFeatures::AnimatedBanner + ); + assert_eq!( + GuildFeatures::from_str("ANIMATED_ICON").unwrap(), + GuildFeatures::AnimatedIcon + ); + assert_eq!( + GuildFeatures::from_str("APPLICATION_COMMAND_PERMISSIONS_V2").unwrap(), + GuildFeatures::ApplicationCommandPermissionsV2 + ); + assert_eq!( + GuildFeatures::from_str("AUTO_MODERATION").unwrap(), + GuildFeatures::AutoModeration + ); + assert_eq!( + GuildFeatures::from_str("AUTO_MOD_TRIGGER_KEYWORD_FILTER").unwrap(), + GuildFeatures::AutoModTriggerKeywordFilter + ); + assert_eq!( + GuildFeatures::from_str("AUTO_MOD_TRIGGER_ML_SPAM_FILTER").unwrap(), + GuildFeatures::AutoModTriggerMLSpamFilter + ); + assert_eq!( + GuildFeatures::from_str("AUTO_MOD_TRIGGER_SPAM_LINK_FILTER").unwrap(), + GuildFeatures::AutoModTriggerSpamLinkFilter + ); + assert_eq!( + GuildFeatures::from_str("AUTO_MOD_TRIGGER_USER_PROFILE").unwrap(), + GuildFeatures::AutoModTriggerUserProfile + ); + assert_eq!( + GuildFeatures::from_str("BANNER").unwrap(), + GuildFeatures::Banner + ); + assert_eq!(GuildFeatures::from_str("BFG").unwrap(), GuildFeatures::Bfg); + assert_eq!( + GuildFeatures::from_str("BOOSTING_TIERS_EXPERIMENT_MEDIUM_GUILD").unwrap(), + GuildFeatures::BoostingTiersExperimentMediumGuild + ); + assert_eq!( + GuildFeatures::from_str("BOOSTING_TIERS_EXPERIMENT_SMALL_GUILD").unwrap(), + GuildFeatures::BoostingTiersExperimentSmallGuild + ); + assert_eq!( + GuildFeatures::from_str("BOT_DEVELOPER_EARLY_ACCESS").unwrap(), + GuildFeatures::BotDeveloperEarlyAccess + ); + assert_eq!( + GuildFeatures::from_str("BURST_REACTIONS").unwrap(), + GuildFeatures::BurstReactions + ); + assert_eq!( + GuildFeatures::from_str("COMMUNITY_CANARY").unwrap(), + GuildFeatures::CommunityCanary + ); + assert_eq!( + GuildFeatures::from_str("COMMUNITY_EXP_LARGE_GATED").unwrap(), + GuildFeatures::CommunityExpLargeGated + ); + assert_eq!( + GuildFeatures::from_str("COMMUNITY_EXP_LARGE_UNGATED").unwrap(), + GuildFeatures::CommunityExpLargeUngated + ); + assert_eq!( + GuildFeatures::from_str("COMMUNITY_EXP_MEDIUM").unwrap(), + GuildFeatures::CommunityExpMedium + ); + assert_eq!( + GuildFeatures::from_str("CHANNEL_EMOJIS_GENERATED").unwrap(), + GuildFeatures::ChannelEmojisGenerated + ); + assert_eq!( + GuildFeatures::from_str("CHANNEL_HIGHLIGHTS").unwrap(), + GuildFeatures::ChannelHighlights + ); + assert_eq!( + GuildFeatures::from_str("CHANNEL_HIGHLIGHTS_DISABLED").unwrap(), + GuildFeatures::ChannelHighlightsDisabled + ); + assert_eq!( + GuildFeatures::from_str("CLYDE_ENABLED").unwrap(), + GuildFeatures::ClydeEnabled + ); + assert_eq!( + GuildFeatures::from_str("CLYDE_EXPERIMENT_ENABLED").unwrap(), + GuildFeatures::ClydeExperimentEnabled + ); + assert_eq!( + GuildFeatures::from_str("CLYDE_DISABLED").unwrap(), + GuildFeatures::ClydeDisabled + ); + assert_eq!( + GuildFeatures::from_str("COMMUNITY").unwrap(), + GuildFeatures::Community + ); + assert_eq!( + GuildFeatures::from_str("CREATOR_ACCEPTED_NEW_TERMS").unwrap(), + GuildFeatures::CreatorAcceptedNewTerms + ); + assert_eq!( + GuildFeatures::from_str("CREATOR_MONETIZABLE").unwrap(), + GuildFeatures::CreatorMonetizable + ); + assert_eq!( + GuildFeatures::from_str("CREATOR_MONETIZABLE_DISABLED").unwrap(), + GuildFeatures::CreatorMonetizableDisabled + ); + assert_eq!( + GuildFeatures::from_str("CREATOR_MONETIZABLE_PENDING_NEW_OWNER_ONBOARDING") + .unwrap(), + GuildFeatures::CreatorMonetizablePendingNewOwnerOnboarding + ); + assert_eq!( + GuildFeatures::from_str("CREATOR_MONETIZABLE_PROVISIONAL").unwrap(), + GuildFeatures::CreatorMonetizableProvisional + ); + assert_eq!( + GuildFeatures::from_str("CREATOR_MONETIZABLE_RESTRICTED").unwrap(), + GuildFeatures::CreatorMonetizableRestricted + ); + assert_eq!( + GuildFeatures::from_str("CREATOR_MONETIZABLE_WHITEGLOVE").unwrap(), + GuildFeatures::CreatorMonetizableWhiteglove + ); + assert_eq!( + GuildFeatures::from_str("CREATOR_MONETIZABLE_APPLICATION_ALLOWLIST").unwrap(), + GuildFeatures::CreatorMonetizableApplicationAllowlist + ); + assert_eq!( + GuildFeatures::from_str("CREATE_STORE_PAGE").unwrap(), + GuildFeatures::CreateStorePage + ); + assert_eq!( + GuildFeatures::from_str("DEVELOPER_SUPPORT_SERVER").unwrap(), + GuildFeatures::DeveloperSupportServer + ); + assert_eq!( + GuildFeatures::from_str("DISCOVERABLE_DISABLED").unwrap(), + GuildFeatures::DiscoverableDisabled + ); + assert_eq!( + GuildFeatures::from_str("DISCOVERABLE").unwrap(), + GuildFeatures::Discoverable + ); + assert_eq!( + GuildFeatures::from_str("ENABLED_DISCOVERABLE_BEFORE").unwrap(), + GuildFeatures::EnabledDiscoverableBefore + ); + assert_eq!( + GuildFeatures::from_str("EXPOSED_TO_ACTIVITIES_WTP_EXPERIMENT").unwrap(), + GuildFeatures::ExposedToActivitiesWTPExperiment + ); + assert_eq!( + GuildFeatures::from_str("GUESTS_ENABLED").unwrap(), + GuildFeatures::GuestsEnabled + ); + assert_eq!( + GuildFeatures::from_str("GUILD_AUTOMOD_DEFAULT_LIST").unwrap(), + GuildFeatures::GuildAutomodDefaultList + ); + assert_eq!( + GuildFeatures::from_str("GUILD_COMMUNICATION_DISABLED_GUILDS").unwrap(), + GuildFeatures::GuildCommunicationDisabledGuilds + ); + assert_eq!( + GuildFeatures::from_str("GUILD_HOME_DEPRECATION_OVERRIDE").unwrap(), + GuildFeatures::GuildHomeDeprecationOverride + ); + assert_eq!( + GuildFeatures::from_str("GUILD_HOME_OVERRIDE").unwrap(), + GuildFeatures::GuildHomeOverride + ); + assert_eq!( + GuildFeatures::from_str("GUILD_HOME_TEST").unwrap(), + GuildFeatures::GuildHomeTest + ); + assert_eq!( + GuildFeatures::from_str("GUILD_MEMBER_VERIFICATION_EXPERIMENT").unwrap(), + GuildFeatures::GuildMemberVerificationExperiment + ); + assert_eq!( + GuildFeatures::from_str("GUILD_ONBOARDING").unwrap(), + GuildFeatures::GuildOnboarding + ); + assert_eq!( + GuildFeatures::from_str("GUILD_ONBOARDING_ADMIN_ONLY").unwrap(), + GuildFeatures::GuildOnboardingAdminOnly + ); + assert_eq!( + GuildFeatures::from_str("GUILD_ONBOARDING_EVER_ENABLED").unwrap(), + GuildFeatures::GuildOnboardingEverEnabled + ); + assert_eq!( + GuildFeatures::from_str("GUILD_ONBOARDING_HAS_PROMPTS").unwrap(), + GuildFeatures::GuildOnboardingHasPrompts + ); + assert_eq!( + GuildFeatures::from_str("GUILD_ROLE_SUBSCRIPTION").unwrap(), + GuildFeatures::GuildRoleSubscription + ); + assert_eq!( + GuildFeatures::from_str("GUILD_ROLE_SUBSCRIPTION_PURCHASE_FEEDBACK_LOOP").unwrap(), + GuildFeatures::GuildRoleSubscriptionPurchaseFeedbackLoop + ); + assert_eq!( + GuildFeatures::from_str("GUILD_ROLE_SUBSCRIPTION_TRIALS").unwrap(), + GuildFeatures::GuildRoleSubscriptionTrials + ); + assert_eq!( + GuildFeatures::from_str("GUILD_SERVER_GUIDE").unwrap(), + GuildFeatures::GuildServerGuide + ); + assert_eq!( + GuildFeatures::from_str("GUILD_WEB_PAGE_VANITY_URL").unwrap(), + GuildFeatures::GuildWebPageVanityURL + ); + assert_eq!( + GuildFeatures::from_str("HAD_EARLY_ACTIVITIES_ACCESS").unwrap(), + GuildFeatures::HadEarlyActivitiesAccess + ); + assert_eq!( + GuildFeatures::from_str("HAS_DIRECTORY_ENTRY").unwrap(), + GuildFeatures::HasDirectoryEntry + ); + assert_eq!( + GuildFeatures::from_str("HIDE_FROM_EXPERIMENT_UI").unwrap(), + GuildFeatures::HideFromExperimentUi + ); + assert_eq!(GuildFeatures::from_str("HUB").unwrap(), GuildFeatures::Hub); + assert_eq!( + GuildFeatures::from_str("INCREASED_THREAD_LIMIT").unwrap(), + GuildFeatures::IncreasedThreadLimit + ); + assert_eq!( + GuildFeatures::from_str("INTERNAL_EMPLOYEE_ONLY").unwrap(), + GuildFeatures::InternalEmployeeOnly + ); + assert_eq!( + GuildFeatures::from_str("INVITE_SPLASH").unwrap(), + GuildFeatures::InviteSplash + ); + assert_eq!( + GuildFeatures::from_str("INVITES_DISABLED").unwrap(), + GuildFeatures::InvitesDisabled + ); + assert_eq!( + GuildFeatures::from_str("LINKED_TO_HUB").unwrap(), + GuildFeatures::LinkedToHub + ); + assert_eq!( + GuildFeatures::from_str("MARKETPLACES_CONNECTION_ROLES").unwrap(), + GuildFeatures::MarketplacesConnectionRoles + ); + assert_eq!( + GuildFeatures::from_str("MEMBER_PROFILES").unwrap(), + GuildFeatures::MemberProfiles + ); + assert_eq!( + GuildFeatures::from_str("MEMBER_VERIFICATION_GATE_ENABLED").unwrap(), + GuildFeatures::MemberVerificationGateEnabled + ); + assert_eq!( + GuildFeatures::from_str("MEMBER_VERIFICATION_MANUAL_APPROVAL").unwrap(), + GuildFeatures::MemberVerificationManualApproval + ); + assert_eq!( + GuildFeatures::from_str("MOBILE_WEB_ROLE_SUBSCRIPTION_PURCHASE_PAGE").unwrap(), + GuildFeatures::MobileWebRoleSubscriptionPurchasePage + ); + assert_eq!( + GuildFeatures::from_str("MONETIZATION_ENABLED").unwrap(), + GuildFeatures::MonetizationEnabled + ); + assert_eq!( + GuildFeatures::from_str("MORE_EMOJI").unwrap(), + GuildFeatures::MoreEmoji + ); + assert_eq!( + GuildFeatures::from_str("MORE_STICKERS").unwrap(), + GuildFeatures::MoreStickers + ); + assert_eq!( + GuildFeatures::from_str("NEWS").unwrap(), + GuildFeatures::News + ); + assert_eq!( + GuildFeatures::from_str("NEW_THREAD_PERMISSIONS").unwrap(), + GuildFeatures::NewThreadPermissions + ); + assert_eq!( + GuildFeatures::from_str("PARTNERED").unwrap(), + GuildFeatures::Partnered + ); + assert_eq!( + GuildFeatures::from_str("PREMIUM_TIER_3_OVERRIDE").unwrap(), + GuildFeatures::PremiumTier3Override + ); + assert_eq!( + GuildFeatures::from_str("PREVIEW_ENABLED").unwrap(), + GuildFeatures::PreviewEnabled + ); + assert_eq!( + GuildFeatures::from_str("RAID_ALERTS_DISABLED").unwrap(), + GuildFeatures::RaidAlertsDisabled + ); + assert_eq!( + GuildFeatures::from_str("RELAY_ENABLED").unwrap(), + GuildFeatures::RelayEnabled + ); + assert_eq!( + GuildFeatures::from_str("RESTRICT_SPAM_RISK_GUILD").unwrap(), + GuildFeatures::RestrictSpamRiskGuild + ); + assert_eq!( + GuildFeatures::from_str("ROLE_ICONS").unwrap(), + GuildFeatures::RoleIcons + ); + assert_eq!( + GuildFeatures::from_str("ROLE_SUBSCRIPTIONS_AVAILABLE_FOR_PURCHASE").unwrap(), + GuildFeatures::RoleSubscriptionsAvailableForPurchase + ); + assert_eq!( + GuildFeatures::from_str("ROLE_SUBSCRIPTIONS_ENABLED").unwrap(), + GuildFeatures::RoleSubscriptionsEnabled + ); + assert_eq!( + GuildFeatures::from_str("ROLE_SUBSCRIPTIONS_ENABLED_FOR_PURCHASE").unwrap(), + GuildFeatures::RoleSubscriptionsEnabledForPurchase + ); + assert_eq!( + GuildFeatures::from_str("SHARD").unwrap(), + GuildFeatures::Shard + ); + assert_eq!( + GuildFeatures::from_str("SHARED_CANVAS_FRIENDS_AND_FAMILY_TEST").unwrap(), + GuildFeatures::SharedCanvasFriendsAndFamilyTest + ); + assert_eq!( + GuildFeatures::from_str("SOUNDBOARD").unwrap(), + GuildFeatures::Soundboard + ); + assert_eq!( + GuildFeatures::from_str("SUMMARIES_ENABLED").unwrap(), + GuildFeatures::SummariesEnabled + ); + assert_eq!( + GuildFeatures::from_str("SUMMARIES_ENABLED_GA").unwrap(), + GuildFeatures::SummariesEnabledGa + ); + assert_eq!( + GuildFeatures::from_str("SUMMARIES_DISABLED_BY_USER").unwrap(), + GuildFeatures::SummariesDisabledByUser + ); + assert_eq!( + GuildFeatures::from_str("SUMMARIES_ENABLED_BY_USER").unwrap(), + GuildFeatures::SummariesEnabledByUser + ); + assert_eq!( + GuildFeatures::from_str("TEXT_IN_STAGE_ENABLED").unwrap(), + GuildFeatures::TextInStageEnabled + ); + assert_eq!( + GuildFeatures::from_str("TEXT_IN_VOICE_ENABLED").unwrap(), + GuildFeatures::TextInVoiceEnabled + ); + assert_eq!( + GuildFeatures::from_str("THREADS_ENABLED_TESTING").unwrap(), + GuildFeatures::ThreadsEnabledTesting + ); + assert_eq!( + GuildFeatures::from_str("THREADS_ENABLED").unwrap(), + GuildFeatures::ThreadsEnabled + ); + assert_eq!( + GuildFeatures::from_str("THREAD_DEFAULT_AUTO_ARCHIVE_DURATION").unwrap(), + GuildFeatures::ThreadDefaultAutoArchiveDuration + ); + assert_eq!( + GuildFeatures::from_str("THREADS_ONLY_CHANNEL").unwrap(), + GuildFeatures::ThreadsOnlyChannel + ); + assert_eq!( + GuildFeatures::from_str("TICKETED_EVENTS_ENABLED").unwrap(), + GuildFeatures::TicketedEventsEnabled + ); + assert_eq!( + GuildFeatures::from_str("TICKETING_ENABLED").unwrap(), + GuildFeatures::TicketingEnabled + ); + assert_eq!( + GuildFeatures::from_str("VANITY_URL").unwrap(), + GuildFeatures::VanityUrl + ); + assert_eq!( + GuildFeatures::from_str("VERIFIED").unwrap(), + GuildFeatures::Verified + ); + assert_eq!( + GuildFeatures::from_str("VIP_REGIONS").unwrap(), + GuildFeatures::VipRegions + ); + assert_eq!( + GuildFeatures::from_str("VOICE_CHANNEL_EFFECTS").unwrap(), + GuildFeatures::VoiceChannelEffects + ); + assert_eq!( + GuildFeatures::from_str("WELCOME_SCREEN_ENABLED").unwrap(), + GuildFeatures::WelcomeScreenEnabled + ); + assert_eq!( + GuildFeatures::from_str("ALIASABLE_NAMES").unwrap(), + GuildFeatures::AliasableNames + ); + assert_eq!( + GuildFeatures::from_str("ALLOW_INVALID_CHANNEL_NAME").unwrap(), + GuildFeatures::AllowInvalidChannelName + ); + assert_eq!( + GuildFeatures::from_str("ALLOW_UNNAMED_CHANNELS").unwrap(), + GuildFeatures::AllowUnnamedChannels + ); + assert_eq!( + GuildFeatures::from_str("CROSS_CHANNEL_REPLIES").unwrap(), + GuildFeatures::CrossChannelReplies + ); + assert_eq!( + GuildFeatures::from_str("IRC_LIKE_CATEGORY_NAMES").unwrap(), + GuildFeatures::IrcLikeCategoryNames + ); + assert_eq!( + GuildFeatures::from_str("INVITES_CLOSED").unwrap(), + GuildFeatures::InvitesClosed + ); + assert_eq!( + GuildFeatures::from_str("INVALID").unwrap_err().to_string(), + Error::Guild(GuildError::InvalidGuildFeature).to_string() + ); + } + + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] + #[cfg_attr(not(target_arch = "wasm32"), test)] + fn test_to_str() { + assert_eq!(GuildFeatures::ActivitiesAlpha.to_str(), "ACTIVITIES_ALPHA"); + assert_eq!( + GuildFeatures::ActivitiesEmployee.to_str(), + "ACTIVITIES_EMPLOYEE" + ); + assert_eq!( + GuildFeatures::ActivitiesInternalDev.to_str(), + "ACTIVITIES_INTERNAL_DEV" + ); + assert_eq!(GuildFeatures::AnimatedBanner.to_str(), "ANIMATED_BANNER"); + assert_eq!(GuildFeatures::AnimatedIcon.to_str(), "ANIMATED_ICON"); + assert_eq!( + GuildFeatures::ApplicationCommandPermissionsV2.to_str(), + "APPLICATION_COMMAND_PERMISSIONS_V2" + ); + assert_eq!(GuildFeatures::AutoModeration.to_str(), "AUTO_MODERATION"); + assert_eq!( + GuildFeatures::AutoModTriggerKeywordFilter.to_str(), + "AUTO_MOD_TRIGGER_KEYWORD_FILTER" + ); + assert_eq!( + GuildFeatures::AutoModTriggerMLSpamFilter.to_str(), + "AUTO_MOD_TRIGGER_ML_SPAM_FILTER" + ); + assert_eq!( + GuildFeatures::AutoModTriggerSpamLinkFilter.to_str(), + "AUTO_MOD_TRIGGER_SPAM_LINK_FILTER" + ); + assert_eq!( + GuildFeatures::AutoModTriggerUserProfile.to_str(), + "AUTO_MOD_TRIGGER_USER_PROFILE" + ); + assert_eq!(GuildFeatures::Banner.to_str(), "BANNER"); + assert_eq!(GuildFeatures::Bfg.to_str(), "BFG"); + assert_eq!( + GuildFeatures::BoostingTiersExperimentMediumGuild.to_str(), + "BOOSTING_TIERS_EXPERIMENT_MEDIUM_GUILD" + ); + assert_eq!( + GuildFeatures::BoostingTiersExperimentSmallGuild.to_str(), + "BOOSTING_TIERS_EXPERIMENT_SMALL_GUILD" + ); + assert_eq!( + GuildFeatures::BotDeveloperEarlyAccess.to_str(), + "BOT_DEVELOPER_EARLY_ACCESS" + ); + assert_eq!(GuildFeatures::BurstReactions.to_str(), "BURST_REACTIONS"); + assert_eq!(GuildFeatures::CommunityCanary.to_str(), "COMMUNITY_CANARY"); + assert_eq!( + GuildFeatures::CommunityExpLargeGated.to_str(), + "COMMUNITY_EXP_LARGE_GATED" + ); + assert_eq!( + GuildFeatures::CommunityExpLargeUngated.to_str(), + "COMMUNITY_EXP_LARGE_UNGATED" + ); + assert_eq!( + GuildFeatures::CommunityExpMedium.to_str(), + "COMMUNITY_EXP_MEDIUM" + ); + assert_eq!( + GuildFeatures::ChannelEmojisGenerated.to_str(), + "CHANNEL_EMOJIS_GENERATED" + ); + assert_eq!( + GuildFeatures::ChannelHighlights.to_str(), + "CHANNEL_HIGHLIGHTS" + ); + assert_eq!( + GuildFeatures::ChannelHighlightsDisabled.to_str(), + "CHANNEL_HIGHLIGHTS_DISABLED" + ); + assert_eq!(GuildFeatures::ClydeEnabled.to_str(), "CLYDE_ENABLED"); + assert_eq!( + GuildFeatures::ClydeExperimentEnabled.to_str(), + "CLYDE_EXPERIMENT_ENABLED" + ); + assert_eq!(GuildFeatures::ClydeDisabled.to_str(), "CLYDE_DISABLED"); + assert_eq!(GuildFeatures::Community.to_str(), "COMMUNITY"); + assert_eq!( + GuildFeatures::CreatorAcceptedNewTerms.to_str(), + "CREATOR_ACCEPTED_NEW_TERMS" + ); + assert_eq!( + GuildFeatures::CreatorMonetizable.to_str(), + "CREATOR_MONETIZABLE" + ); + assert_eq!( + GuildFeatures::CreatorMonetizableDisabled.to_str(), + "CREATOR_MONETIZABLE_DISABLED" + ); + assert_eq!( + GuildFeatures::CreatorMonetizablePendingNewOwnerOnboarding.to_str(), + "CREATOR_MONETIZABLE_PENDING_NEW_OWNER_ONBOARDING" + ); + assert_eq!( + GuildFeatures::CreatorMonetizableProvisional.to_str(), + "CREATOR_MONETIZABLE_PROVISIONAL" + ); + assert_eq!( + GuildFeatures::CreatorMonetizableRestricted.to_str(), + "CREATOR_MONETIZABLE_RESTRICTED" + ); + assert_eq!( + GuildFeatures::CreatorMonetizableWhiteglove.to_str(), + "CREATOR_MONETIZABLE_WHITEGLOVE" + ); + assert_eq!( + GuildFeatures::CreatorMonetizableApplicationAllowlist.to_str(), + "CREATOR_MONETIZABLE_APPLICATION_ALLOWLIST" + ); + assert_eq!(GuildFeatures::CreateStorePage.to_str(), "CREATE_STORE_PAGE"); + assert_eq!( + GuildFeatures::DeveloperSupportServer.to_str(), + "DEVELOPER_SUPPORT_SERVER" + ); + assert_eq!( + GuildFeatures::DiscoverableDisabled.to_str(), + "DISCOVERABLE_DISABLED" + ); + assert_eq!(GuildFeatures::Discoverable.to_str(), "DISCOVERABLE"); + assert_eq!( + GuildFeatures::EnabledDiscoverableBefore.to_str(), + "ENABLED_DISCOVERABLE_BEFORE" + ); + assert_eq!( + GuildFeatures::ExposedToActivitiesWTPExperiment.to_str(), + "EXPOSED_TO_ACTIVITIES_WTP_EXPERIMENT" + ); + assert_eq!(GuildFeatures::GuestsEnabled.to_str(), "GUESTS_ENABLED"); + assert_eq!( + GuildFeatures::GuildAutomodDefaultList.to_str(), + "GUILD_AUTOMOD_DEFAULT_LIST" + ); + assert_eq!( + GuildFeatures::GuildCommunicationDisabledGuilds.to_str(), + "GUILD_COMMUNICATION_DISABLED_GUILDS" + ); + assert_eq!( + GuildFeatures::GuildHomeDeprecationOverride.to_str(), + "GUILD_HOME_DEPRECATION_OVERRIDE" + ); + assert_eq!( + GuildFeatures::GuildHomeOverride.to_str(), + "GUILD_HOME_OVERRIDE" + ); + assert_eq!(GuildFeatures::GuildHomeTest.to_str(), "GUILD_HOME_TEST"); + assert_eq!( + GuildFeatures::GuildMemberVerificationExperiment.to_str(), + "GUILD_MEMBER_VERIFICATION_EXPERIMENT" + ); + assert_eq!(GuildFeatures::GuildOnboarding.to_str(), "GUILD_ONBOARDING"); + assert_eq!( + GuildFeatures::GuildOnboardingAdminOnly.to_str(), + "GUILD_ONBOARDING_ADMIN_ONLY" + ); + assert_eq!( + GuildFeatures::GuildOnboardingEverEnabled.to_str(), + "GUILD_ONBOARDING_EVER_ENABLED" + ); + assert_eq!( + GuildFeatures::GuildOnboardingHasPrompts.to_str(), + "GUILD_ONBOARDING_HAS_PROMPTS" + ); + assert_eq!( + GuildFeatures::GuildRoleSubscription.to_str(), + "GUILD_ROLE_SUBSCRIPTION" + ); + assert_eq!( + GuildFeatures::GuildRoleSubscriptionPurchaseFeedbackLoop.to_str(), + "GUILD_ROLE_SUBSCRIPTION_PURCHASE_FEEDBACK_LOOP" + ); + assert_eq!( + GuildFeatures::GuildRoleSubscriptionTrials.to_str(), + "GUILD_ROLE_SUBSCRIPTION_TRIALS" + ); + assert_eq!( + GuildFeatures::GuildServerGuide.to_str(), + "GUILD_SERVER_GUIDE" + ); + assert_eq!( + GuildFeatures::GuildWebPageVanityURL.to_str(), + "GUILD_WEB_PAGE_VANITY_URL" + ); + assert_eq!( + GuildFeatures::HadEarlyActivitiesAccess.to_str(), + "HAD_EARLY_ACTIVITIES_ACCESS" + ); + assert_eq!( + GuildFeatures::HasDirectoryEntry.to_str(), + "HAS_DIRECTORY_ENTRY" + ); + assert_eq!( + GuildFeatures::HideFromExperimentUi.to_str(), + "HIDE_FROM_EXPERIMENT_UI" + ); + assert_eq!(GuildFeatures::Hub.to_str(), "HUB"); + assert_eq!( + GuildFeatures::IncreasedThreadLimit.to_str(), + "INCREASED_THREAD_LIMIT" + ); + assert_eq!( + GuildFeatures::InternalEmployeeOnly.to_str(), + "INTERNAL_EMPLOYEE_ONLY" + ); + assert_eq!(GuildFeatures::InviteSplash.to_str(), "INVITE_SPLASH"); + assert_eq!(GuildFeatures::InvitesDisabled.to_str(), "INVITES_DISABLED"); + assert_eq!(GuildFeatures::LinkedToHub.to_str(), "LINKED_TO_HUB"); + assert_eq!( + GuildFeatures::MarketplacesConnectionRoles.to_str(), + "MARKETPLACES_CONNECTION_ROLES" + ); + assert_eq!(GuildFeatures::MemberProfiles.to_str(), "MEMBER_PROFILES"); + assert_eq!( + GuildFeatures::MemberVerificationGateEnabled.to_str(), + "MEMBER_VERIFICATION_GATE_ENABLED" + ); + assert_eq!( + GuildFeatures::MemberVerificationManualApproval.to_str(), + "MEMBER_VERIFICATION_MANUAL_APPROVAL" + ); + assert_eq!( + GuildFeatures::MobileWebRoleSubscriptionPurchasePage.to_str(), + "MOBILE_WEB_ROLE_SUBSCRIPTION_PURCHASE_PAGE" + ); + assert_eq!( + GuildFeatures::MonetizationEnabled.to_str(), + "MONETIZATION_ENABLED" + ); + assert_eq!(GuildFeatures::MoreEmoji.to_str(), "MORE_EMOJI"); + assert_eq!(GuildFeatures::MoreStickers.to_str(), "MORE_STICKERS"); + assert_eq!(GuildFeatures::News.to_str(), "NEWS"); + assert_eq!( + GuildFeatures::NewThreadPermissions.to_str(), + "NEW_THREAD_PERMISSIONS" + ); + assert_eq!(GuildFeatures::Partnered.to_str(), "PARTNERED"); + assert_eq!( + GuildFeatures::PremiumTier3Override.to_str(), + "PREMIUM_TIER_3_OVERRIDE" + ); + assert_eq!(GuildFeatures::PreviewEnabled.to_str(), "PREVIEW_ENABLED"); + assert_eq!( + GuildFeatures::RaidAlertsDisabled.to_str(), + "RAID_ALERTS_DISABLED" + ); + assert_eq!(GuildFeatures::RelayEnabled.to_str(), "RELAY_ENABLED"); + assert_eq!( + GuildFeatures::RestrictSpamRiskGuild.to_str(), + "RESTRICT_SPAM_RISK_GUILD" + ); + assert_eq!(GuildFeatures::RoleIcons.to_str(), "ROLE_ICONS"); + assert_eq!( + GuildFeatures::RoleSubscriptionsAvailableForPurchase.to_str(), + "ROLE_SUBSCRIPTIONS_AVAILABLE_FOR_PURCHASE" + ); + assert_eq!( + GuildFeatures::RoleSubscriptionsEnabled.to_str(), + "ROLE_SUBSCRIPTIONS_ENABLED" + ); + assert_eq!( + GuildFeatures::RoleSubscriptionsEnabledForPurchase.to_str(), + "ROLE_SUBSCRIPTIONS_ENABLED_FOR_PURCHASE" + ); + assert_eq!(GuildFeatures::Shard.to_str(), "SHARD"); + assert_eq!( + GuildFeatures::SharedCanvasFriendsAndFamilyTest.to_str(), + "SHARED_CANVAS_FRIENDS_AND_FAMILY_TEST" + ); + assert_eq!(GuildFeatures::Soundboard.to_str(), "SOUNDBOARD"); + assert_eq!( + GuildFeatures::SummariesEnabled.to_str(), + "SUMMARIES_ENABLED" + ); + assert_eq!( + GuildFeatures::SummariesEnabledGa.to_str(), + "SUMMARIES_ENABLED_GA" + ); + assert_eq!( + GuildFeatures::SummariesDisabledByUser.to_str(), + "SUMMARIES_DISABLED_BY_USER" + ); + assert_eq!( + GuildFeatures::SummariesEnabledByUser.to_str(), + "SUMMARIES_ENABLED_BY_USER" + ); + assert_eq!( + GuildFeatures::TextInStageEnabled.to_str(), + "TEXT_IN_STAGE_ENABLED" + ); + assert_eq!( + GuildFeatures::TextInVoiceEnabled.to_str(), + "TEXT_IN_VOICE_ENABLED" + ); + assert_eq!( + GuildFeatures::ThreadsEnabledTesting.to_str(), + "THREADS_ENABLED_TESTING" + ); + assert_eq!(GuildFeatures::ThreadsEnabled.to_str(), "THREADS_ENABLED"); + assert_eq!( + GuildFeatures::ThreadDefaultAutoArchiveDuration.to_str(), + "THREAD_DEFAULT_AUTO_ARCHIVE_DURATION" + ); + assert_eq!( + GuildFeatures::ThreadsOnlyChannel.to_str(), + "THREADS_ONLY_CHANNEL" + ); + assert_eq!( + GuildFeatures::TicketedEventsEnabled.to_str(), + "TICKETED_EVENTS_ENABLED" + ); + assert_eq!( + GuildFeatures::TicketingEnabled.to_str(), + "TICKETING_ENABLED" + ); + assert_eq!(GuildFeatures::VanityUrl.to_str(), "VANITY_URL"); + assert_eq!(GuildFeatures::Verified.to_str(), "VERIFIED"); + assert_eq!(GuildFeatures::VipRegions.to_str(), "VIP_REGIONS"); + assert_eq!( + GuildFeatures::VoiceChannelEffects.to_str(), + "VOICE_CHANNEL_EFFECTS" + ); + assert_eq!( + GuildFeatures::WelcomeScreenEnabled.to_str(), + "WELCOME_SCREEN_ENABLED" + ); + assert_eq!(GuildFeatures::AliasableNames.to_str(), "ALIASABLE_NAMES"); + assert_eq!( + GuildFeatures::AllowInvalidChannelName.to_str(), + "ALLOW_INVALID_CHANNEL_NAME" + ); + assert_eq!( + GuildFeatures::AllowUnnamedChannels.to_str(), + "ALLOW_UNNAMED_CHANNELS" + ); + assert_eq!( + GuildFeatures::CrossChannelReplies.to_str(), + "CROSS_CHANNEL_REPLIES" + ); + assert_eq!( + GuildFeatures::IrcLikeCategoryNames.to_str(), + "IRC_LIKE_CATEGORY_NAMES" + ); + assert_eq!(GuildFeatures::InvitesClosed.to_str(), "INVITES_CLOSED"); + } } mod domains_configuration { From b24714bc687a35d2f6b0bf6f8a7cf1d7adb80090 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Tue, 23 Jan 2024 21:05:01 +0100 Subject: [PATCH 20/53] Remove Eq fromn Guild as it is not Eq --- src/types/entities/guild.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/types/entities/guild.rs b/src/types/entities/guild.rs index ee796e4..4a3d952 100644 --- a/src/types/entities/guild.rs +++ b/src/types/entities/guild.rs @@ -217,8 +217,6 @@ impl std::cmp::PartialEq for Guild { } } -impl std::cmp::Eq for Guild {} - /// See #[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq, Eq, Hash)] #[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))] From 713f9d7846b48e7ed83aa7d820a0e0689e72dca0 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Tue, 23 Jan 2024 21:13:15 +0100 Subject: [PATCH 21/53] Add unit tests for guild.rs entities --- tests/types.rs | 54 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/tests/types.rs b/tests/types.rs index e9a1de5..2caef09 100644 --- a/tests/types.rs +++ b/tests/types.rs @@ -947,4 +947,58 @@ mod entities { assert_ne!(emoji.id, another_emoji.id); assert_ne!(emoji, another_emoji); } + + mod guild { + use std::hash::{Hash, Hasher}; + + use chorus::types::{Guild, GuildInvite}; + + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] + #[cfg_attr(not(target_arch = "wasm32"), test)] + fn guild_hash() { + let id: u64 = 1; + let mut guild1 = Guild::default(); + let mut guild2 = Guild::default(); + guild1.id = id.into(); + guild2.id = id.into(); + let mut hasher1 = std::collections::hash_map::DefaultHasher::new(); + guild1.hash(&mut hasher1); + + let mut hasher2 = std::collections::hash_map::DefaultHasher::new(); + guild2.hash(&mut hasher2); + + assert_eq!(hasher1.finish(), hasher2.finish()); + } + + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] + #[cfg_attr(not(target_arch = "wasm32"), test)] + fn guild_invite_hash() { + let id: u64 = 1; + let mut invite1 = GuildInvite::default(); + let mut invite2 = GuildInvite::default(); + invite1.channel_id = id.into(); + invite2.channel_id = id.into(); + invite1.guild_id = id.into(); + invite2.guild_id = id.into(); + let mut hasher1 = std::collections::hash_map::DefaultHasher::new(); + invite1.hash(&mut hasher1); + + let mut hasher2 = std::collections::hash_map::DefaultHasher::new(); + invite2.hash(&mut hasher2); + + assert_eq!(hasher1.finish(), hasher2.finish()); + } + + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] + #[cfg_attr(not(target_arch = "wasm32"), test)] + fn guild_partial_eq() { + let id: u64 = 1; + let mut guild1 = Guild::default(); + let mut guild2 = Guild::default(); + guild1.id = id.into(); + guild2.id = id.into(); + + assert_eq!(guild1, guild2); + } + } } From c13ba6b29169b0b5716771da0cb75c4dd31c3c3f Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Tue, 23 Jan 2024 23:06:14 +0100 Subject: [PATCH 22/53] Rename to_public_user into into_public_user --- src/types/entities/user.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/types/entities/user.rs b/src/types/entities/user.rs index a7bdf63..d9f70ce 100644 --- a/src/types/entities/user.rs +++ b/src/types/entities/user.rs @@ -26,7 +26,7 @@ pub struct UserData { } impl User { - pub fn to_public_user(self) -> PublicUser { + pub fn into_public_user(self) -> PublicUser { PublicUser::from(self) } } From fcd405939f5d98f68c46e593cea352079ba82661 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Tue, 23 Jan 2024 23:06:24 +0100 Subject: [PATCH 23/53] Add to_public_user test --- tests/user.rs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 tests/user.rs diff --git a/tests/user.rs b/tests/user.rs new file mode 100644 index 0000000..bf7938b --- /dev/null +++ b/tests/user.rs @@ -0,0 +1,18 @@ +use chorus::types::{PublicUser, Snowflake, User}; + +#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] +#[cfg_attr(not(target_arch = "wasm32"), test)] +fn to_public_user() { + let mut user = User::default(); + let mut public_user = PublicUser { + username: Some("".to_string()), + discriminator: Some("".to_string()), + ..Default::default() + }; + let id: Snowflake = 1_u64.into(); + user.id = id; + public_user.id = id; + + let from_user = user.into_public_user(); + assert_eq!(public_user, from_user); +} From 24cec523e0e1935bb305b1d773faf3a31976bd2a Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Tue, 23 Jan 2024 23:11:37 +0100 Subject: [PATCH 24/53] Remove old/redundant code from attachment.rs --- src/api/channels/messages.rs | 6 +-- src/types/entities/attachment.rs | 70 -------------------------------- 2 files changed, 3 insertions(+), 73 deletions(-) diff --git a/src/api/channels/messages.rs b/src/api/channels/messages.rs index 6dfdfbf..3ee5228 100644 --- a/src/api/channels/messages.rs +++ b/src/api/channels/messages.rs @@ -36,7 +36,7 @@ impl Message { chorus_request.deserialize_response::(user).await } else { for (index, attachment) in message.attachments.iter_mut().enumerate() { - attachment.get_mut(index).unwrap().set_id(index as i16); + attachment.get_mut(index).unwrap().id = Some(index as i16); } let mut form = reqwest::multipart::Form::new(); let payload_json = to_string(&message).unwrap(); @@ -45,8 +45,8 @@ impl Message { form = form.part("payload_json", payload_field); for (index, attachment) in message.attachments.unwrap().into_iter().enumerate() { - let (attachment_content, current_attachment) = attachment.move_content(); - let (attachment_filename, _) = current_attachment.move_filename(); + let attachment_content = attachment.content; + let attachment_filename = attachment.filename; let part_name = format!("files[{}]", index); let content_disposition = format!( "form-data; name=\"{}\"'; filename=\"{}\"", diff --git a/src/types/entities/attachment.rs b/src/types/entities/attachment.rs index ffbc520..9aacc08 100644 --- a/src/types/entities/attachment.rs +++ b/src/types/entities/attachment.rs @@ -55,73 +55,3 @@ pub struct PartialDiscordFileAttachment { #[serde(skip_serializing)] pub content: Vec, } - -impl PartialDiscordFileAttachment { - /// Moves `self.content` out of `self` and returns it. - 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) - } - - /// Moves `self.filename` out of `self` and returns it. - 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) - } - - /// Moves `self.content_type` out of `self` and returns it. - 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); - } -} From 96ef83270b9f4624cc8c280670f559e01baef11b Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Tue, 23 Jan 2024 23:31:35 +0100 Subject: [PATCH 25/53] Add partial_eq test for relationship.rs/Relationship --- tests/types.rs | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/tests/types.rs b/tests/types.rs index 2caef09..7c40095 100644 --- a/tests/types.rs +++ b/tests/types.rs @@ -1001,4 +1001,36 @@ mod entities { assert_eq!(guild1, guild2); } } + + mod relationship { + use chorus::types::{IntoShared, Relationship, User}; + + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] + #[cfg_attr(not(target_arch = "wasm32"), test)] + fn relationship_partial_eq() { + let user = User::default(); + // These 2 users are different, because they do not have the same Snowflake "id". + let user_2 = User::default(); + let relationship_1 = Relationship { + id: 32_u64.into(), + relationship_type: chorus::types::RelationshipType::Friends, + nickname: Some("Xenia".to_string()), + user: user.into_public_user().into_shared(), + since: None, + }; + + let relationship_2 = Relationship { + id: 32_u64.into(), + relationship_type: chorus::types::RelationshipType::Friends, + nickname: Some("Xenia".to_string()), + user: user_2.into_public_user().into_shared(), + since: None, + }; + + // This should succeed, even though the two users' IDs are different. This is because + // `User` is only `PartialEq`, and the actual user object is not checked, since the + // `RwLock` would have to be locked. + assert_eq!(relationship_1, relationship_2); + } + } } From a564293fef114afe58833bd7b5d6ec7216852098 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Tue, 23 Jan 2024 23:42:00 +0100 Subject: [PATCH 26/53] Add Message PartialEq Test --- tests/types.rs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/tests/types.rs b/tests/types.rs index 7c40095..5acd4c6 100644 --- a/tests/types.rs +++ b/tests/types.rs @@ -1033,4 +1033,22 @@ mod entities { assert_eq!(relationship_1, relationship_2); } } + + mod message { + use chorus::types::{Message, Snowflake}; + + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] + #[cfg_attr(not(target_arch = "wasm32"), test)] + fn message_partial_eq() { + let id: Snowflake = 1_u64.into(); + let mut message1 = Message::default(); + let mut message2 = Message::default(); + message1.id = id; + message1.channel_id = id; + message2.id = id; + message2.channel_id = id; + + assert_eq!(message1, message2); + } + } } From f3771b31ca518761c29629a571241d6668e2e9fd Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Tue, 23 Jan 2024 23:50:22 +0100 Subject: [PATCH 27/53] Remove PartialOrd from Emoji because unneccessary --- src/types/entities/emoji.rs | 34 ---------------------------------- src/types/entities/message.rs | 2 +- src/types/entities/user.rs | 2 +- src/types/schema/guild.rs | 2 +- 4 files changed, 3 insertions(+), 37 deletions(-) diff --git a/src/types/entities/emoji.rs b/src/types/entities/emoji.rs index 5170637..1c78cba 100644 --- a/src/types/entities/emoji.rs +++ b/src/types/entities/emoji.rs @@ -62,37 +62,3 @@ impl PartialEq for Emoji { || self.available != other.available) } } - -impl PartialOrd for Emoji { - fn partial_cmp(&self, other: &Self) -> Option { - match self.id.partial_cmp(&other.id) { - Some(core::cmp::Ordering::Equal) => {} - ord => return ord, - } - match self.name.partial_cmp(&other.name) { - Some(core::cmp::Ordering::Equal) => {} - ord => return ord, - } - match self.roles.partial_cmp(&other.roles) { - Some(core::cmp::Ordering::Equal) => {} - ord => return ord, - } - match self.roles.partial_cmp(&other.roles) { - Some(core::cmp::Ordering::Equal) => {} - ord => return ord, - } - match self.require_colons.partial_cmp(&other.require_colons) { - Some(core::cmp::Ordering::Equal) => {} - ord => return ord, - } - match self.managed.partial_cmp(&other.managed) { - Some(core::cmp::Ordering::Equal) => {} - ord => return ord, - } - match self.animated.partial_cmp(&other.animated) { - Some(core::cmp::Ordering::Equal) => {} - ord => return ord, - } - self.available.partial_cmp(&other.available) - } -} diff --git a/src/types/entities/message.rs b/src/types/entities/message.rs index 923ee61..1e6810d 100644 --- a/src/types/entities/message.rs +++ b/src/types/entities/message.rs @@ -218,7 +218,7 @@ pub struct EmbedField { inline: Option, } -#[derive(Debug, Clone, Serialize, Deserialize, PartialOrd, PartialEq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub struct Reaction { pub count: u32, pub burst_count: u32, diff --git a/src/types/entities/user.rs b/src/types/entities/user.rs index d9f70ce..90221ca 100644 --- a/src/types/entities/user.rs +++ b/src/types/entities/user.rs @@ -133,7 +133,7 @@ bitflags::bitflags! { } } -#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, PartialOrd)] +#[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] pub struct UserProfileMetadata { pub guild_id: Option, pub pronouns: String, diff --git a/src/types/schema/guild.rs b/src/types/schema/guild.rs index 56c0f9e..9d2c27d 100644 --- a/src/types/schema/guild.rs +++ b/src/types/schema/guild.rs @@ -78,7 +78,7 @@ impl std::default::Default for GetUserGuildSchema { } } -#[derive(Debug, Default, Deserialize, Serialize, Clone, PartialEq, PartialOrd)] +#[derive(Debug, Default, Deserialize, Serialize, Clone, PartialEq)] pub struct GuildPreview { pub id: Snowflake, pub name: String, From 764f86731d236a028222f1635310a7063bc59056 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Wed, 24 Jan 2024 12:21:46 +0100 Subject: [PATCH 28/53] Add test to hit ratelimit --- tests/ratelimit.rs | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 tests/ratelimit.rs diff --git a/tests/ratelimit.rs b/tests/ratelimit.rs new file mode 100644 index 0000000..f9498d7 --- /dev/null +++ b/tests/ratelimit.rs @@ -0,0 +1,23 @@ +use chorus::errors::ChorusError; + +mod common; + +#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] +#[cfg_attr(not(target_arch = "wasm32"), tokio::test)] +async fn hit_ratelimit() { + let mut bundle = common::setup().await; + let mut _count = 0; + let guild = bundle.guild.read().unwrap().clone(); + while _count < 1000 { + _count += 1; + match guild.channels(&mut bundle.user).await { + Err(ChorusError::RateLimited { bucket: _ }) => { + return; + } + Err(_) => panic!("Hit different rate limit"), + _ => continue, + } + } + common::teardown(bundle).await; + panic!("Ratelimit never triggered"); +} From b6f5d93e6677d0fc3a8838955f860c3334df42e9 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Wed, 24 Jan 2024 12:32:08 +0100 Subject: [PATCH 29/53] Add test for get_limit_config --- tests/ratelimit.rs | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/tests/ratelimit.rs b/tests/ratelimit.rs index f9498d7..922857b 100644 --- a/tests/ratelimit.rs +++ b/tests/ratelimit.rs @@ -1,4 +1,5 @@ use chorus::errors::ChorusError; +use chorus::ratelimiter::ChorusRequest; mod common; @@ -21,3 +22,26 @@ async fn hit_ratelimit() { common::teardown(bundle).await; panic!("Ratelimit never triggered"); } + +#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] +#[cfg_attr(not(target_arch = "wasm32"), tokio::test)] +async fn get_limit_config() { + let conf = ChorusRequest::get_limits_config("http://localhost:3001/api") + .await + .unwrap(); + assert!(conf.channel.max_pins > 0); + assert!(conf.channel.max_topic > 0); + assert!(conf.channel.max_webhooks > 0); + assert!(conf.guild.max_roles > 0); + assert!(conf.guild.max_channels > 0); + assert!(conf.guild.max_emojis > 0); + assert!(conf.guild.max_channels_in_category > 0); + assert!(conf.guild.max_members > 0); + assert!(conf.message.max_attachment_size > 0); + assert!(conf.message.max_bulk_delete > 0); + assert!(conf.message.max_reactions > 0); + assert!(conf.message.max_characters > 0); + assert!(conf.message.max_tts_characters == 0); + assert!(conf.user.max_guilds > 0); + assert!(conf.user.max_friends > 0); +} From 9eb62607282c262e443c04e180b69eafcc4a9306 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Wed, 24 Jan 2024 18:51:10 +0100 Subject: [PATCH 30/53] remove hit limit test --- tests/ratelimit.rs | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/tests/ratelimit.rs b/tests/ratelimit.rs index 922857b..8843ed0 100644 --- a/tests/ratelimit.rs +++ b/tests/ratelimit.rs @@ -1,28 +1,7 @@ -use chorus::errors::ChorusError; use chorus::ratelimiter::ChorusRequest; mod common; -#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] -#[cfg_attr(not(target_arch = "wasm32"), tokio::test)] -async fn hit_ratelimit() { - let mut bundle = common::setup().await; - let mut _count = 0; - let guild = bundle.guild.read().unwrap().clone(); - while _count < 1000 { - _count += 1; - match guild.channels(&mut bundle.user).await { - Err(ChorusError::RateLimited { bucket: _ }) => { - return; - } - Err(_) => panic!("Hit different rate limit"), - _ => continue, - } - } - common::teardown(bundle).await; - panic!("Ratelimit never triggered"); -} - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] #[cfg_attr(not(target_arch = "wasm32"), tokio::test)] async fn get_limit_config() { From 9168e5b9041a40750c6fecab80822c4cf15ba643 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Wed, 24 Jan 2024 23:01:38 +0100 Subject: [PATCH 31/53] extend self updating structs test --- tests/gateway.rs | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/tests/gateway.rs b/tests/gateway.rs index 66259f7..0109e2f 100644 --- a/tests/gateway.rs +++ b/tests/gateway.rs @@ -2,7 +2,10 @@ mod common; use chorus::errors::GatewayError; use chorus::gateway::*; -use chorus::types::{self, ChannelModifySchema, IntoShared, RoleCreateModifySchema, RoleObject}; +use chorus::types::{ + self, Channel, ChannelCreateSchema, ChannelModifySchema, IntoShared, RoleCreateModifySchema, + RoleObject, +}; #[cfg(target_arch = "wasm32")] use wasm_bindgen_test::*; #[cfg(target_arch = "wasm32")] @@ -37,6 +40,7 @@ async fn test_gateway_authenticate() { #[cfg_attr(not(target_arch = "wasm32"), tokio::test)] async fn test_self_updating_structs() { let mut bundle = common::setup().await; + let received_channel = bundle .user .gateway @@ -64,6 +68,34 @@ async fn test_self_updating_structs() { "selfupdating".to_string() ); + let guild = bundle + .user + .gateway + .observe_and_into_inner(bundle.guild.clone()) + .await; + assert!(guild.channels.is_none()); + + Channel::create( + &mut bundle.user, + guild.id, + None, + ChannelCreateSchema { + name: "selfupdating2".to_string(), + channel_type: Some(types::ChannelType::GuildText), + ..Default::default() + }, + ) + .await + .unwrap(); + + let guild = bundle + .user + .gateway + .observe_and_into_inner(guild.into_shared()) + .await; + assert!(guild.channels.is_some()); + assert!(guild.channels.as_ref().unwrap().len() == 1); + common::teardown(bundle).await } From 153bfc26c9203c2b6925a55c3a437e63ff87c7c8 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Wed, 24 Jan 2024 23:02:16 +0100 Subject: [PATCH 32/53] Add comment about test_self_updating_structs --- tests/gateway.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/gateway.rs b/tests/gateway.rs index 0109e2f..76f69ae 100644 --- a/tests/gateway.rs +++ b/tests/gateway.rs @@ -39,6 +39,8 @@ async fn test_gateway_authenticate() { #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] #[cfg_attr(not(target_arch = "wasm32"), tokio::test)] async fn test_self_updating_structs() { + // PRETTYFYME: This test is a bit of a mess, but it works. Ideally, each self-updating struct + // would have its own test. let mut bundle = common::setup().await; let received_channel = bundle From 60f55d9d3a7e1e61cfeb008fc70ec170b24c9b35 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Wed, 24 Jan 2024 23:26:59 +0100 Subject: [PATCH 33/53] exclude trivial id() functions from coverage --- src/types/entities/voice_state.rs | 1 + src/types/events/auto_moderation.rs | 2 ++ src/types/events/channel.rs | 4 ++++ src/types/events/guild.rs | 6 ++++++ src/types/events/mod.rs | 1 + src/types/events/thread.rs | 1 + 6 files changed, 15 insertions(+) diff --git a/src/types/entities/voice_state.rs b/src/types/entities/voice_state.rs index 911cf96..035f8f9 100644 --- a/src/types/entities/voice_state.rs +++ b/src/types/entities/voice_state.rs @@ -47,6 +47,7 @@ pub struct VoiceState { } impl Updateable for VoiceState { + #[cfg(not(tarpaulin_include))] fn id(&self) -> Snowflake { if let Some(id) = self.id { id // ID exists: Only the case for Spacebar Server impls diff --git a/src/types/events/auto_moderation.rs b/src/types/events/auto_moderation.rs index 2a2eb6b..b667486 100644 --- a/src/types/events/auto_moderation.rs +++ b/src/types/events/auto_moderation.rs @@ -31,7 +31,9 @@ pub struct AutoModerationRuleUpdate { } #[cfg(feature = "client")] +#[cfg(not(tarpaulin_include))] impl UpdateMessage for AutoModerationRuleUpdate { + #[cfg(not(tarpaulin_include))] fn id(&self) -> Option { Some(self.rule.id) } diff --git a/src/types/events/channel.rs b/src/types/events/channel.rs index 5ca73ec..10e8561 100644 --- a/src/types/events/channel.rs +++ b/src/types/events/channel.rs @@ -39,6 +39,7 @@ impl WebSocketEvent for ChannelCreate {} #[cfg(feature = "client")] impl UpdateMessage for ChannelCreate { + #[cfg(not(tarpaulin_include))] fn id(&self) -> Option { self.channel.guild_id } @@ -73,6 +74,8 @@ impl UpdateMessage for ChannelUpdate { let mut write = object_to_update.write().unwrap(); *write = self.channel.clone(); } + + #[cfg(not(tarpaulin_include))] fn id(&self) -> Option { Some(self.channel.id) } @@ -111,6 +114,7 @@ pub struct ChannelDelete { #[cfg(feature = "client")] impl UpdateMessage for ChannelDelete { + #[cfg(not(tarpaulin_include))] fn id(&self) -> Option { self.channel.guild_id } diff --git a/src/types/events/guild.rs b/src/types/events/guild.rs index 04f871a..a460403 100644 --- a/src/types/events/guild.rs +++ b/src/types/events/guild.rs @@ -30,7 +30,9 @@ pub struct GuildCreate { } #[cfg(feature = "client")] +#[cfg(not(tarpaulin_include))] impl UpdateMessage for GuildCreate { + #[cfg(not(tarpaulin_include))] fn id(&self) -> Option { match &self.d { GuildCreateDataOption::UnavailableGuild(unavailable) => Some(unavailable.id), @@ -92,6 +94,7 @@ impl WebSocketEvent for GuildUpdate {} #[cfg(feature = "client")] impl UpdateMessage for GuildUpdate { + #[cfg(not(tarpaulin_include))] fn id(&self) -> Option { Some(self.guild.id) } @@ -111,6 +114,7 @@ pub struct GuildDelete { #[cfg(feature = "client")] impl UpdateMessage for GuildDelete { + #[cfg(not(tarpaulin_include))] fn id(&self) -> Option { Some(self.guild.id) } @@ -225,6 +229,7 @@ impl WebSocketEvent for GuildRoleCreate {} #[cfg(feature = "client")] impl UpdateMessage for GuildRoleCreate { + #[cfg(not(tarpaulin_include))] fn id(&self) -> Option { Some(self.guild_id) } @@ -258,6 +263,7 @@ impl WebSocketEvent for GuildRoleUpdate {} #[cfg(feature = "client")] impl UpdateMessage for GuildRoleUpdate { + #[cfg(not(tarpaulin_include))] fn id(&self) -> Option { Some(self.role.id) } diff --git a/src/types/events/mod.rs b/src/types/events/mod.rs index 5644d8a..5c8e6c4 100644 --- a/src/types/events/mod.rs +++ b/src/types/events/mod.rs @@ -135,6 +135,7 @@ where fn update(&mut self, object_to_update: Shared) { update_object(self.get_json(), object_to_update) } + #[cfg(not(tarpaulin_include))] fn id(&self) -> Option; } diff --git a/src/types/events/thread.rs b/src/types/events/thread.rs index cff5f6f..34dd1a2 100644 --- a/src/types/events/thread.rs +++ b/src/types/events/thread.rs @@ -32,6 +32,7 @@ impl WebSocketEvent for ThreadUpdate {} #[cfg(feature = "client")] impl UpdateMessage for ThreadUpdate { + #[cfg(not(tarpaulin_include))] fn id(&self) -> Option { Some(self.thread.id) } From 148ce747159e25c0b92f09f89cfd40ffddcad29f Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Tue, 30 Jan 2024 10:00:05 +0100 Subject: [PATCH 34/53] Change license to Mozilla Public License v2.0 --- LICENSE | 1034 ++++++++++++++++++++----------------------------------- 1 file changed, 373 insertions(+), 661 deletions(-) diff --git a/LICENSE b/LICENSE index 0ad25db..a612ad9 100644 --- a/LICENSE +++ b/LICENSE @@ -1,661 +1,373 @@ - GNU AFFERO GENERAL PUBLIC LICENSE - Version 3, 19 November 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The GNU Affero General Public License is a free, copyleft license for -software and other kinds of works, specifically designed to ensure -cooperation with the community in the case of network server software. - - The licenses for most software and other practical works are designed -to take away your freedom to share and change the works. By contrast, -our General Public Licenses are intended to guarantee your freedom to -share and change all versions of a program--to make sure it remains free -software for all its users. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -them if you wish), that you receive source code or can get it if you -want it, that you can change the software or use pieces of it in new -free programs, and that you know you can do these things. - - Developers that use our General Public Licenses protect your rights -with two steps: (1) assert copyright on the software, and (2) offer -you this License which gives you legal permission to copy, distribute -and/or modify the software. - - A secondary benefit of defending all users' freedom is that -improvements made in alternate versions of the program, if they -receive widespread use, become available for other developers to -incorporate. Many developers of free software are heartened and -encouraged by the resulting cooperation. However, in the case of -software used on network servers, this result may fail to come about. -The GNU General Public License permits making a modified version and -letting the public access it on a server without ever releasing its -source code to the public. - - The GNU Affero General Public License is designed specifically to -ensure that, in such cases, the modified source code becomes available -to the community. It requires the operator of a network server to -provide the source code of the modified version running there to the -users of that server. Therefore, public use of a modified version, on -a publicly accessible server, gives the public access to the source -code of the modified version. - - An older license, called the Affero General Public License and -published by Affero, was designed to accomplish similar goals. This is -a different license, not a version of the Affero GPL, but Affero has -released a new version of the Affero GPL which permits relicensing under -this license. - - The precise terms and conditions for copying, distribution and -modification follow. - - TERMS AND CONDITIONS - - 0. Definitions. - - "This License" refers to version 3 of the GNU Affero General Public License. - - "Copyright" also means copyright-like laws that apply to other kinds of -works, such as semiconductor masks. - - "The Program" refers to any copyrightable work licensed under this -License. Each licensee is addressed as "you". "Licensees" and -"recipients" may be individuals or organizations. - - To "modify" a work means to copy from or adapt all or part of the work -in a fashion requiring copyright permission, other than the making of an -exact copy. The resulting work is called a "modified version" of the -earlier work or a work "based on" the earlier work. - - A "covered work" means either the unmodified Program or a work based -on the Program. - - To "propagate" a work means to do anything with it that, without -permission, would make you directly or secondarily liable for -infringement under applicable copyright law, except executing it on a -computer or modifying a private copy. Propagation includes copying, -distribution (with or without modification), making available to the -public, and in some countries other activities as well. - - To "convey" a work means any kind of propagation that enables other -parties to make or receive copies. Mere interaction with a user through -a computer network, with no transfer of a copy, is not conveying. - - An interactive user interface displays "Appropriate Legal Notices" -to the extent that it includes a convenient and prominently visible -feature that (1) displays an appropriate copyright notice, and (2) -tells the user that there is no warranty for the work (except to the -extent that warranties are provided), that licensees may convey the -work under this License, and how to view a copy of this License. If -the interface presents a list of user commands or options, such as a -menu, a prominent item in the list meets this criterion. - - 1. Source Code. - - The "source code" for a work means the preferred form of the work -for making modifications to it. "Object code" means any non-source -form of a work. - - A "Standard Interface" means an interface that either is an official -standard defined by a recognized standards body, or, in the case of -interfaces specified for a particular programming language, one that -is widely used among developers working in that language. - - The "System Libraries" of an executable work include anything, other -than the work as a whole, that (a) is included in the normal form of -packaging a Major Component, but which is not part of that Major -Component, and (b) serves only to enable use of the work with that -Major Component, or to implement a Standard Interface for which an -implementation is available to the public in source code form. A -"Major Component", in this context, means a major essential component -(kernel, window system, and so on) of the specific operating system -(if any) on which the executable work runs, or a compiler used to -produce the work, or an object code interpreter used to run it. - - The "Corresponding Source" for a work in object code form means all -the source code needed to generate, install, and (for an executable -work) run the object code and to modify the work, including scripts to -control those activities. However, it does not include the work's -System Libraries, or general-purpose tools or generally available free -programs which are used unmodified in performing those activities but -which are not part of the work. For example, Corresponding Source -includes interface definition files associated with source files for -the work, and the source code for shared libraries and dynamically -linked subprograms that the work is specifically designed to require, -such as by intimate data communication or control flow between those -subprograms and other parts of the work. - - The Corresponding Source need not include anything that users -can regenerate automatically from other parts of the Corresponding -Source. - - The Corresponding Source for a work in source code form is that -same work. - - 2. Basic Permissions. - - All rights granted under this License are granted for the term of -copyright on the Program, and are irrevocable provided the stated -conditions are met. This License explicitly affirms your unlimited -permission to run the unmodified Program. The output from running a -covered work is covered by this License only if the output, given its -content, constitutes a covered work. This License acknowledges your -rights of fair use or other equivalent, as provided by copyright law. - - You may make, run and propagate covered works that you do not -convey, without conditions so long as your license otherwise remains -in force. You may convey covered works to others for the sole purpose -of having them make modifications exclusively for you, or provide you -with facilities for running those works, provided that you comply with -the terms of this License in conveying all material for which you do -not control copyright. Those thus making or running the covered works -for you must do so exclusively on your behalf, under your direction -and control, on terms that prohibit them from making any copies of -your copyrighted material outside their relationship with you. - - Conveying under any other circumstances is permitted solely under -the conditions stated below. Sublicensing is not allowed; section 10 -makes it unnecessary. - - 3. Protecting Users' Legal Rights From Anti-Circumvention Law. - - No covered work shall be deemed part of an effective technological -measure under any applicable law fulfilling obligations under article -11 of the WIPO copyright treaty adopted on 20 December 1996, or -similar laws prohibiting or restricting circumvention of such -measures. - - When you convey a covered work, you waive any legal power to forbid -circumvention of technological measures to the extent such circumvention -is effected by exercising rights under this License with respect to -the covered work, and you disclaim any intention to limit operation or -modification of the work as a means of enforcing, against the work's -users, your or third parties' legal rights to forbid circumvention of -technological measures. - - 4. Conveying Verbatim Copies. - - You may convey verbatim copies of the Program's source code as you -receive it, in any medium, provided that you conspicuously and -appropriately publish on each copy an appropriate copyright notice; -keep intact all notices stating that this License and any -non-permissive terms added in accord with section 7 apply to the code; -keep intact all notices of the absence of any warranty; and give all -recipients a copy of this License along with the Program. - - You may charge any price or no price for each copy that you convey, -and you may offer support or warranty protection for a fee. - - 5. Conveying Modified Source Versions. - - You may convey a work based on the Program, or the modifications to -produce it from the Program, in the form of source code under the -terms of section 4, provided that you also meet all of these conditions: - - a) The work must carry prominent notices stating that you modified - it, and giving a relevant date. - - b) The work must carry prominent notices stating that it is - released under this License and any conditions added under section - 7. This requirement modifies the requirement in section 4 to - "keep intact all notices". - - c) You must license the entire work, as a whole, under this - License to anyone who comes into possession of a copy. This - License will therefore apply, along with any applicable section 7 - additional terms, to the whole of the work, and all its parts, - regardless of how they are packaged. This License gives no - permission to license the work in any other way, but it does not - invalidate such permission if you have separately received it. - - d) If the work has interactive user interfaces, each must display - Appropriate Legal Notices; however, if the Program has interactive - interfaces that do not display Appropriate Legal Notices, your - work need not make them do so. - - A compilation of a covered work with other separate and independent -works, which are not by their nature extensions of the covered work, -and which are not combined with it such as to form a larger program, -in or on a volume of a storage or distribution medium, is called an -"aggregate" if the compilation and its resulting copyright are not -used to limit the access or legal rights of the compilation's users -beyond what the individual works permit. Inclusion of a covered work -in an aggregate does not cause this License to apply to the other -parts of the aggregate. - - 6. Conveying Non-Source Forms. - - You may convey a covered work in object code form under the terms -of sections 4 and 5, provided that you also convey the -machine-readable Corresponding Source under the terms of this License, -in one of these ways: - - a) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by the - Corresponding Source fixed on a durable physical medium - customarily used for software interchange. - - b) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by a - written offer, valid for at least three years and valid for as - long as you offer spare parts or customer support for that product - model, to give anyone who possesses the object code either (1) a - copy of the Corresponding Source for all the software in the - product that is covered by this License, on a durable physical - medium customarily used for software interchange, for a price no - more than your reasonable cost of physically performing this - conveying of source, or (2) access to copy the - Corresponding Source from a network server at no charge. - - c) Convey individual copies of the object code with a copy of the - written offer to provide the Corresponding Source. This - alternative is allowed only occasionally and noncommercially, and - only if you received the object code with such an offer, in accord - with subsection 6b. - - d) Convey the object code by offering access from a designated - place (gratis or for a charge), and offer equivalent access to the - Corresponding Source in the same way through the same place at no - further charge. You need not require recipients to copy the - Corresponding Source along with the object code. If the place to - copy the object code is a network server, the Corresponding Source - may be on a different server (operated by you or a third party) - that supports equivalent copying facilities, provided you maintain - clear directions next to the object code saying where to find the - Corresponding Source. Regardless of what server hosts the - Corresponding Source, you remain obligated to ensure that it is - available for as long as needed to satisfy these requirements. - - e) Convey the object code using peer-to-peer transmission, provided - you inform other peers where the object code and Corresponding - Source of the work are being offered to the general public at no - charge under subsection 6d. - - A separable portion of the object code, whose source code is excluded -from the Corresponding Source as a System Library, need not be -included in conveying the object code work. - - A "User Product" is either (1) a "consumer product", which means any -tangible personal property which is normally used for personal, family, -or household purposes, or (2) anything designed or sold for incorporation -into a dwelling. In determining whether a product is a consumer product, -doubtful cases shall be resolved in favor of coverage. For a particular -product received by a particular user, "normally used" refers to a -typical or common use of that class of product, regardless of the status -of the particular user or of the way in which the particular user -actually uses, or expects or is expected to use, the product. A product -is a consumer product regardless of whether the product has substantial -commercial, industrial or non-consumer uses, unless such uses represent -the only significant mode of use of the product. - - "Installation Information" for a User Product means any methods, -procedures, authorization keys, or other information required to install -and execute modified versions of a covered work in that User Product from -a modified version of its Corresponding Source. The information must -suffice to ensure that the continued functioning of the modified object -code is in no case prevented or interfered with solely because -modification has been made. - - If you convey an object code work under this section in, or with, or -specifically for use in, a User Product, and the conveying occurs as -part of a transaction in which the right of possession and use of the -User Product is transferred to the recipient in perpetuity or for a -fixed term (regardless of how the transaction is characterized), the -Corresponding Source conveyed under this section must be accompanied -by the Installation Information. But this requirement does not apply -if neither you nor any third party retains the ability to install -modified object code on the User Product (for example, the work has -been installed in ROM). - - The requirement to provide Installation Information does not include a -requirement to continue to provide support service, warranty, or updates -for a work that has been modified or installed by the recipient, or for -the User Product in which it has been modified or installed. Access to a -network may be denied when the modification itself materially and -adversely affects the operation of the network or violates the rules and -protocols for communication across the network. - - Corresponding Source conveyed, and Installation Information provided, -in accord with this section must be in a format that is publicly -documented (and with an implementation available to the public in -source code form), and must require no special password or key for -unpacking, reading or copying. - - 7. Additional Terms. - - "Additional permissions" are terms that supplement the terms of this -License by making exceptions from one or more of its conditions. -Additional permissions that are applicable to the entire Program shall -be treated as though they were included in this License, to the extent -that they are valid under applicable law. If additional permissions -apply only to part of the Program, that part may be used separately -under those permissions, but the entire Program remains governed by -this License without regard to the additional permissions. - - When you convey a copy of a covered work, you may at your option -remove any additional permissions from that copy, or from any part of -it. (Additional permissions may be written to require their own -removal in certain cases when you modify the work.) You may place -additional permissions on material, added by you to a covered work, -for which you have or can give appropriate copyright permission. - - Notwithstanding any other provision of this License, for material you -add to a covered work, you may (if authorized by the copyright holders of -that material) supplement the terms of this License with terms: - - a) Disclaiming warranty or limiting liability differently from the - terms of sections 15 and 16 of this License; or - - b) Requiring preservation of specified reasonable legal notices or - author attributions in that material or in the Appropriate Legal - Notices displayed by works containing it; or - - c) Prohibiting misrepresentation of the origin of that material, or - requiring that modified versions of such material be marked in - reasonable ways as different from the original version; or - - d) Limiting the use for publicity purposes of names of licensors or - authors of the material; or - - e) Declining to grant rights under trademark law for use of some - trade names, trademarks, or service marks; or - - f) Requiring indemnification of licensors and authors of that - material by anyone who conveys the material (or modified versions of - it) with contractual assumptions of liability to the recipient, for - any liability that these contractual assumptions directly impose on - those licensors and authors. - - All other non-permissive additional terms are considered "further -restrictions" within the meaning of section 10. If the Program as you -received it, or any part of it, contains a notice stating that it is -governed by this License along with a term that is a further -restriction, you may remove that term. If a license document contains -a further restriction but permits relicensing or conveying under this -License, you may add to a covered work material governed by the terms -of that license document, provided that the further restriction does -not survive such relicensing or conveying. - - If you add terms to a covered work in accord with this section, you -must place, in the relevant source files, a statement of the -additional terms that apply to those files, or a notice indicating -where to find the applicable terms. - - Additional terms, permissive or non-permissive, may be stated in the -form of a separately written license, or stated as exceptions; -the above requirements apply either way. - - 8. Termination. - - You may not propagate or modify a covered work except as expressly -provided under this License. Any attempt otherwise to propagate or -modify it is void, and will automatically terminate your rights under -this License (including any patent licenses granted under the third -paragraph of section 11). - - However, if you cease all violation of this License, then your -license from a particular copyright holder is reinstated (a) -provisionally, unless and until the copyright holder explicitly and -finally terminates your license, and (b) permanently, if the copyright -holder fails to notify you of the violation by some reasonable means -prior to 60 days after the cessation. - - Moreover, your license from a particular copyright holder is -reinstated permanently if the copyright holder notifies you of the -violation by some reasonable means, this is the first time you have -received notice of violation of this License (for any work) from that -copyright holder, and you cure the violation prior to 30 days after -your receipt of the notice. - - Termination of your rights under this section does not terminate the -licenses of parties who have received copies or rights from you under -this License. If your rights have been terminated and not permanently -reinstated, you do not qualify to receive new licenses for the same -material under section 10. - - 9. Acceptance Not Required for Having Copies. - - You are not required to accept this License in order to receive or -run a copy of the Program. Ancillary propagation of a covered work -occurring solely as a consequence of using peer-to-peer transmission -to receive a copy likewise does not require acceptance. However, -nothing other than this License grants you permission to propagate or -modify any covered work. These actions infringe copyright if you do -not accept this License. Therefore, by modifying or propagating a -covered work, you indicate your acceptance of this License to do so. - - 10. Automatic Licensing of Downstream Recipients. - - Each time you convey a covered work, the recipient automatically -receives a license from the original licensors, to run, modify and -propagate that work, subject to this License. You are not responsible -for enforcing compliance by third parties with this License. - - An "entity transaction" is a transaction transferring control of an -organization, or substantially all assets of one, or subdividing an -organization, or merging organizations. If propagation of a covered -work results from an entity transaction, each party to that -transaction who receives a copy of the work also receives whatever -licenses to the work the party's predecessor in interest had or could -give under the previous paragraph, plus a right to possession of the -Corresponding Source of the work from the predecessor in interest, if -the predecessor has it or can get it with reasonable efforts. - - You may not impose any further restrictions on the exercise of the -rights granted or affirmed under this License. For example, you may -not impose a license fee, royalty, or other charge for exercise of -rights granted under this License, and you may not initiate litigation -(including a cross-claim or counterclaim in a lawsuit) alleging that -any patent claim is infringed by making, using, selling, offering for -sale, or importing the Program or any portion of it. - - 11. Patents. - - A "contributor" is a copyright holder who authorizes use under this -License of the Program or a work on which the Program is based. The -work thus licensed is called the contributor's "contributor version". - - A contributor's "essential patent claims" are all patent claims -owned or controlled by the contributor, whether already acquired or -hereafter acquired, that would be infringed by some manner, permitted -by this License, of making, using, or selling its contributor version, -but do not include claims that would be infringed only as a -consequence of further modification of the contributor version. For -purposes of this definition, "control" includes the right to grant -patent sublicenses in a manner consistent with the requirements of -this License. - - Each contributor grants you a non-exclusive, worldwide, royalty-free -patent license under the contributor's essential patent claims, to -make, use, sell, offer for sale, import and otherwise run, modify and -propagate the contents of its contributor version. - - In the following three paragraphs, a "patent license" is any express -agreement or commitment, however denominated, not to enforce a patent -(such as an express permission to practice a patent or covenant not to -sue for patent infringement). To "grant" such a patent license to a -party means to make such an agreement or commitment not to enforce a -patent against the party. - - If you convey a covered work, knowingly relying on a patent license, -and the Corresponding Source of the work is not available for anyone -to copy, free of charge and under the terms of this License, through a -publicly available network server or other readily accessible means, -then you must either (1) cause the Corresponding Source to be so -available, or (2) arrange to deprive yourself of the benefit of the -patent license for this particular work, or (3) arrange, in a manner -consistent with the requirements of this License, to extend the patent -license to downstream recipients. "Knowingly relying" means you have -actual knowledge that, but for the patent license, your conveying the -covered work in a country, or your recipient's use of the covered work -in a country, would infringe one or more identifiable patents in that -country that you have reason to believe are valid. - - If, pursuant to or in connection with a single transaction or -arrangement, you convey, or propagate by procuring conveyance of, a -covered work, and grant a patent license to some of the parties -receiving the covered work authorizing them to use, propagate, modify -or convey a specific copy of the covered work, then the patent license -you grant is automatically extended to all recipients of the covered -work and works based on it. - - A patent license is "discriminatory" if it does not include within -the scope of its coverage, prohibits the exercise of, or is -conditioned on the non-exercise of one or more of the rights that are -specifically granted under this License. You may not convey a covered -work if you are a party to an arrangement with a third party that is -in the business of distributing software, under which you make payment -to the third party based on the extent of your activity of conveying -the work, and under which the third party grants, to any of the -parties who would receive the covered work from you, a discriminatory -patent license (a) in connection with copies of the covered work -conveyed by you (or copies made from those copies), or (b) primarily -for and in connection with specific products or compilations that -contain the covered work, unless you entered into that arrangement, -or that patent license was granted, prior to 28 March 2007. - - Nothing in this License shall be construed as excluding or limiting -any implied license or other defenses to infringement that may -otherwise be available to you under applicable patent law. - - 12. No Surrender of Others' Freedom. - - If conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot convey a -covered work so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you may -not convey it at all. For example, if you agree to terms that obligate you -to collect a royalty for further conveying from those to whom you convey -the Program, the only way you could satisfy both those terms and this -License would be to refrain entirely from conveying the Program. - - 13. Remote Network Interaction; Use with the GNU General Public License. - - Notwithstanding any other provision of this License, if you modify the -Program, your modified version must prominently offer all users -interacting with it remotely through a computer network (if your version -supports such interaction) an opportunity to receive the Corresponding -Source of your version by providing access to the Corresponding Source -from a network server at no charge, through some standard or customary -means of facilitating copying of software. This Corresponding Source -shall include the Corresponding Source for any work covered by version 3 -of the GNU General Public License that is incorporated pursuant to the -following paragraph. - - Notwithstanding any other provision of this License, you have -permission to link or combine any covered work with a work licensed -under version 3 of the GNU General Public License into a single -combined work, and to convey the resulting work. The terms of this -License will continue to apply to the part which is the covered work, -but the work with which it is combined will remain governed by version -3 of the GNU General Public License. - - 14. Revised Versions of this License. - - The Free Software Foundation may publish revised and/or new versions of -the GNU Affero General Public License from time to time. Such new versions -will be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - - Each version is given a distinguishing version number. If the -Program specifies that a certain numbered version of the GNU Affero General -Public License "or any later version" applies to it, you have the -option of following the terms and conditions either of that numbered -version or of any later version published by the Free Software -Foundation. If the Program does not specify a version number of the -GNU Affero General Public License, you may choose any version ever published -by the Free Software Foundation. - - If the Program specifies that a proxy can decide which future -versions of the GNU Affero General Public License can be used, that proxy's -public statement of acceptance of a version permanently authorizes you -to choose that version for the Program. - - Later license versions may give you additional or different -permissions. However, no additional obligations are imposed on any -author or copyright holder as a result of your choosing to follow a -later version. - - 15. Disclaimer of Warranty. - - THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY -APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT -HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY -OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM -IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF -ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. Limitation of Liability. - - IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS -THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY -GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE -USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF -DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD -PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), -EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF -SUCH DAMAGES. - - 17. Interpretation of Sections 15 and 16. - - If the disclaimer of warranty and limitation of liability provided -above cannot be given local legal effect according to their terms, -reviewing courts shall apply local law that most closely approximates -an absolute waiver of all civil liability in connection with the -Program, unless a warranty or assumption of liability accompanies a -copy of the Program in return for a fee. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -state the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published - by the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . - -Also add information on how to contact you by electronic and paper mail. - - If your software can interact with users remotely through a computer -network, you should also make sure that it provides a way for users to -get its source. For example, if your program is a web application, its -interface could display a "Source" link that leads users to an archive -of the code. There are many ways you could offer source, and different -solutions will be better for different programs; see section 13 for the -specific requirements. - - You should also get your employer (if you work as a programmer) or school, -if any, to sign a "copyright disclaimer" for the program, if necessary. -For more information on this, and how to apply and follow the GNU AGPL, see -. +Mozilla Public License Version 2.0 +================================== + +1. Definitions +-------------- + +1.1. "Contributor" + means each individual or legal entity that creates, contributes to + the creation of, or owns Covered Software. + +1.2. "Contributor Version" + means the combination of the Contributions of others (if any) used + by a Contributor and that particular Contributor's Contribution. + +1.3. "Contribution" + means Covered Software of a particular Contributor. + +1.4. "Covered Software" + means Source Code Form to which the initial Contributor has attached + the notice in Exhibit A, the Executable Form of such Source Code + Form, and Modifications of such Source Code Form, in each case + including portions thereof. + +1.5. "Incompatible With Secondary Licenses" + means + + (a) that the initial Contributor has attached the notice described + in Exhibit B to the Covered Software; or + + (b) that the Covered Software was made available under the terms of + version 1.1 or earlier of the License, but not also under the + terms of a Secondary License. + +1.6. "Executable Form" + means any form of the work other than Source Code Form. + +1.7. "Larger Work" + means a work that combines Covered Software with other material, in + a separate file or files, that is not Covered Software. + +1.8. "License" + means this document. + +1.9. "Licensable" + means having the right to grant, to the maximum extent possible, + whether at the time of the initial grant or subsequently, any and + all of the rights conveyed by this License. + +1.10. "Modifications" + means any of the following: + + (a) any file in Source Code Form that results from an addition to, + deletion from, or modification of the contents of Covered + Software; or + + (b) any new file in Source Code Form that contains any Covered + Software. + +1.11. "Patent Claims" of a Contributor + means any patent claim(s), including without limitation, method, + process, and apparatus claims, in any patent Licensable by such + Contributor that would be infringed, but for the grant of the + License, by the making, using, selling, offering for sale, having + made, import, or transfer of either its Contributions or its + Contributor Version. + +1.12. "Secondary License" + means either the GNU General Public License, Version 2.0, the GNU + Lesser General Public License, Version 2.1, the GNU Affero General + Public License, Version 3.0, or any later versions of those + licenses. + +1.13. "Source Code Form" + means the form of the work preferred for making modifications. + +1.14. "You" (or "Your") + means an individual or a legal entity exercising rights under this + License. For legal entities, "You" includes any entity that + controls, is controlled by, or is under common control with You. For + purposes of this definition, "control" means (a) the power, direct + or indirect, to cause the direction or management of such entity, + whether by contract or otherwise, or (b) ownership of more than + fifty percent (50%) of the outstanding shares or beneficial + ownership of such entity. + +2. License Grants and Conditions +-------------------------------- + +2.1. Grants + +Each Contributor hereby grants You a world-wide, royalty-free, +non-exclusive license: + +(a) under intellectual property rights (other than patent or trademark) + Licensable by such Contributor to use, reproduce, make available, + modify, display, perform, distribute, and otherwise exploit its + Contributions, either on an unmodified basis, with Modifications, or + as part of a Larger Work; and + +(b) under Patent Claims of such Contributor to make, use, sell, offer + for sale, have made, import, and otherwise transfer either its + Contributions or its Contributor Version. + +2.2. Effective Date + +The licenses granted in Section 2.1 with respect to any Contribution +become effective for each Contribution on the date the Contributor first +distributes such Contribution. + +2.3. Limitations on Grant Scope + +The licenses granted in this Section 2 are the only rights granted under +this License. No additional rights or licenses will be implied from the +distribution or licensing of Covered Software under this License. +Notwithstanding Section 2.1(b) above, no patent license is granted by a +Contributor: + +(a) for any code that a Contributor has removed from Covered Software; + or + +(b) for infringements caused by: (i) Your and any other third party's + modifications of Covered Software, or (ii) the combination of its + Contributions with other software (except as part of its Contributor + Version); or + +(c) under Patent Claims infringed by Covered Software in the absence of + its Contributions. + +This License does not grant any rights in the trademarks, service marks, +or logos of any Contributor (except as may be necessary to comply with +the notice requirements in Section 3.4). + +2.4. Subsequent Licenses + +No Contributor makes additional grants as a result of Your choice to +distribute the Covered Software under a subsequent version of this +License (see Section 10.2) or under the terms of a Secondary License (if +permitted under the terms of Section 3.3). + +2.5. Representation + +Each Contributor represents that the Contributor believes its +Contributions are its original creation(s) or it has sufficient rights +to grant the rights to its Contributions conveyed by this License. + +2.6. Fair Use + +This License is not intended to limit any rights You have under +applicable copyright doctrines of fair use, fair dealing, or other +equivalents. + +2.7. Conditions + +Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted +in Section 2.1. + +3. Responsibilities +------------------- + +3.1. Distribution of Source Form + +All distribution of Covered Software in Source Code Form, including any +Modifications that You create or to which You contribute, must be under +the terms of this License. You must inform recipients that the Source +Code Form of the Covered Software is governed by the terms of this +License, and how they can obtain a copy of this License. You may not +attempt to alter or restrict the recipients' rights in the Source Code +Form. + +3.2. Distribution of Executable Form + +If You distribute Covered Software in Executable Form then: + +(a) such Covered Software must also be made available in Source Code + Form, as described in Section 3.1, and You must inform recipients of + the Executable Form how they can obtain a copy of such Source Code + Form by reasonable means in a timely manner, at a charge no more + than the cost of distribution to the recipient; and + +(b) You may distribute such Executable Form under the terms of this + License, or sublicense it under different terms, provided that the + license for the Executable Form does not attempt to limit or alter + the recipients' rights in the Source Code Form under this License. + +3.3. Distribution of a Larger Work + +You may create and distribute a Larger Work under terms of Your choice, +provided that You also comply with the requirements of this License for +the Covered Software. If the Larger Work is a combination of Covered +Software with a work governed by one or more Secondary Licenses, and the +Covered Software is not Incompatible With Secondary Licenses, this +License permits You to additionally distribute such Covered Software +under the terms of such Secondary License(s), so that the recipient of +the Larger Work may, at their option, further distribute the Covered +Software under the terms of either this License or such Secondary +License(s). + +3.4. Notices + +You may not remove or alter the substance of any license notices +(including copyright notices, patent notices, disclaimers of warranty, +or limitations of liability) contained within the Source Code Form of +the Covered Software, except that You may alter any license notices to +the extent required to remedy known factual inaccuracies. + +3.5. Application of Additional Terms + +You may choose to offer, and to charge a fee for, warranty, support, +indemnity or liability obligations to one or more recipients of Covered +Software. However, You may do so only on Your own behalf, and not on +behalf of any Contributor. You must make it absolutely clear that any +such warranty, support, indemnity, or liability obligation is offered by +You alone, and You hereby agree to indemnify every Contributor for any +liability incurred by such Contributor as a result of warranty, support, +indemnity or liability terms You offer. You may include additional +disclaimers of warranty and limitations of liability specific to any +jurisdiction. + +4. Inability to Comply Due to Statute or Regulation +--------------------------------------------------- + +If it is impossible for You to comply with any of the terms of this +License with respect to some or all of the Covered Software due to +statute, judicial order, or regulation then You must: (a) comply with +the terms of this License to the maximum extent possible; and (b) +describe the limitations and the code they affect. Such description must +be placed in a text file included with all distributions of the Covered +Software under this License. Except to the extent prohibited by statute +or regulation, such description must be sufficiently detailed for a +recipient of ordinary skill to be able to understand it. + +5. Termination +-------------- + +5.1. The rights granted under this License will terminate automatically +if You fail to comply with any of its terms. However, if You become +compliant, then the rights granted under this License from a particular +Contributor are reinstated (a) provisionally, unless and until such +Contributor explicitly and finally terminates Your grants, and (b) on an +ongoing basis, if such Contributor fails to notify You of the +non-compliance by some reasonable means prior to 60 days after You have +come back into compliance. Moreover, Your grants from a particular +Contributor are reinstated on an ongoing basis if such Contributor +notifies You of the non-compliance by some reasonable means, this is the +first time You have received notice of non-compliance with this License +from such Contributor, and You become compliant prior to 30 days after +Your receipt of the notice. + +5.2. If You initiate litigation against any entity by asserting a patent +infringement claim (excluding declaratory judgment actions, +counter-claims, and cross-claims) alleging that a Contributor Version +directly or indirectly infringes any patent, then the rights granted to +You by any and all Contributors for the Covered Software under Section +2.1 of this License shall terminate. + +5.3. In the event of termination under Sections 5.1 or 5.2 above, all +end user license agreements (excluding distributors and resellers) which +have been validly granted by You or Your distributors under this License +prior to termination shall survive termination. + +************************************************************************ +* * +* 6. Disclaimer of Warranty * +* ------------------------- * +* * +* Covered Software is provided under this License on an "as is" * +* basis, without warranty of any kind, either expressed, implied, or * +* statutory, including, without limitation, warranties that the * +* Covered Software is free of defects, merchantable, fit for a * +* particular purpose or non-infringing. The entire risk as to the * +* quality and performance of the Covered Software is with You. * +* Should any Covered Software prove defective in any respect, You * +* (not any Contributor) assume the cost of any necessary servicing, * +* repair, or correction. This disclaimer of warranty constitutes an * +* essential part of this License. No use of any Covered Software is * +* authorized under this License except under this disclaimer. * +* * +************************************************************************ + +************************************************************************ +* * +* 7. Limitation of Liability * +* -------------------------- * +* * +* Under no circumstances and under no legal theory, whether tort * +* (including negligence), contract, or otherwise, shall any * +* Contributor, or anyone who distributes Covered Software as * +* permitted above, be liable to You for any direct, indirect, * +* special, incidental, or consequential damages of any character * +* including, without limitation, damages for lost profits, loss of * +* goodwill, work stoppage, computer failure or malfunction, or any * +* and all other commercial damages or losses, even if such party * +* shall have been informed of the possibility of such damages. This * +* limitation of liability shall not apply to liability for death or * +* personal injury resulting from such party's negligence to the * +* extent applicable law prohibits such limitation. Some * +* jurisdictions do not allow the exclusion or limitation of * +* incidental or consequential damages, so this exclusion and * +* limitation may not apply to You. * +* * +************************************************************************ + +8. Litigation +------------- + +Any litigation relating to this License may be brought only in the +courts of a jurisdiction where the defendant maintains its principal +place of business and such litigation shall be governed by laws of that +jurisdiction, without reference to its conflict-of-law provisions. +Nothing in this Section shall prevent a party's ability to bring +cross-claims or counter-claims. + +9. Miscellaneous +---------------- + +This License represents the complete agreement concerning the subject +matter hereof. If any provision of this License is held to be +unenforceable, such provision shall be reformed only to the extent +necessary to make it enforceable. Any law or regulation which provides +that the language of a contract shall be construed against the drafter +shall not be used to construe this License against a Contributor. + +10. Versions of the License +--------------------------- + +10.1. New Versions + +Mozilla Foundation is the license steward. Except as provided in Section +10.3, no one other than the license steward has the right to modify or +publish new versions of this License. Each version will be given a +distinguishing version number. + +10.2. Effect of New Versions + +You may distribute the Covered Software under the terms of the version +of the License under which You originally received the Covered Software, +or under the terms of any subsequent version published by the license +steward. + +10.3. Modified Versions + +If you create software not governed by this License, and you want to +create a new license for such software, you may create and use a +modified version of this License if you rename the license and remove +any references to the name of the license steward (except to note that +such modified license differs from this License). + +10.4. Distributing Source Code Form that is Incompatible With Secondary +Licenses + +If You choose to distribute Source Code Form that is Incompatible With +Secondary Licenses under the terms of this version of the License, the +notice described in Exhibit B of this License must be attached. + +Exhibit A - Source Code Form License Notice +------------------------------------------- + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. + +If it is not possible or desirable to put the notice in a particular +file, then You may include the notice in a location (such as a LICENSE +file in a relevant directory) where a recipient would be likely to look +for such a notice. + +You may add additional accurate notices of copyright ownership. + +Exhibit B - "Incompatible With Secondary Licenses" Notice +--------------------------------------------------------- + + This Source Code Form is "Incompatible With Secondary Licenses", as + defined by the Mozilla Public License, v. 2.0. From 3f06cc011c0d78ea713832757b57342cdd0b0048 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Tue, 30 Jan 2024 10:03:14 +0100 Subject: [PATCH 35/53] Change license to Mozilla Public License v2.0 --- Cargo.toml | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index f14d9b7..c5b06c6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,7 @@ name = "chorus" description = "A library for interacting with multiple Spacebar-compatible Instances at once." version = "0.14.0" -license = "AGPL-3.0" +license = "MPL-2.0" edition = "2021" repository = "https://github.com/polyphony-chat/chorus" readme = "README.md" @@ -24,10 +24,7 @@ serde_json = { version = "1.0.111", features = ["raw_value"] } serde-aux = "4.3.1" serde_with = "3.4.0" serde_repr = "0.1.18" -reqwest = { features = [ - "multipart", - "json", -], version = "0.11.23" } +reqwest = { features = ["multipart", "json"], version = "0.11.23" } url = "2.5.0" chrono = { version = "0.4.31", features = ["serde"] } regex = "1.10.2" From b8ba92cf0793e3c003af7699b5876d832f0b933a Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Tue, 30 Jan 2024 17:19:34 +0100 Subject: [PATCH 36/53] Include license header everywhere --- chorus-macros/src/lib.rs | 4 ++++ examples/gateway_observers.rs | 4 ++++ examples/gateway_simple.rs | 4 ++++ examples/instance.rs | 4 ++++ examples/login.rs | 4 ++++ src/api/auth/login.rs | 4 ++++ src/api/auth/mod.rs | 4 ++++ src/api/auth/register.rs | 4 ++++ src/api/channels/channels.rs | 4 ++++ src/api/channels/messages.rs | 4 ++++ src/api/channels/mod.rs | 4 ++++ src/api/channels/permissions.rs | 4 ++++ src/api/channels/reactions.rs | 4 ++++ src/api/guilds/guilds.rs | 4 ++++ src/api/guilds/member.rs | 4 ++++ src/api/guilds/messages.rs | 4 ++++ src/api/guilds/mod.rs | 4 ++++ src/api/guilds/roles.rs | 4 ++++ src/api/invites/mod.rs | 4 ++++ src/api/mod.rs | 4 ++++ src/api/policies/instance/instance.rs | 4 ++++ src/api/policies/instance/mod.rs | 4 ++++ src/api/policies/mod.rs | 4 ++++ src/api/users/channels.rs | 4 ++++ src/api/users/guilds.rs | 4 ++++ src/api/users/mod.rs | 4 ++++ src/api/users/relationships.rs | 4 ++++ src/api/users/users.rs | 4 ++++ src/errors.rs | 4 ++++ src/gateway/backends/mod.rs | 4 ++++ src/gateway/backends/tungstenite.rs | 4 ++++ src/gateway/backends/wasm.rs | 4 ++++ src/gateway/events.rs | 4 ++++ src/gateway/gateway.rs | 4 ++++ src/gateway/handle.rs | 4 ++++ src/gateway/heartbeat.rs | 4 ++++ src/gateway/message.rs | 4 ++++ src/gateway/mod.rs | 4 ++++ src/instance.rs | 4 ++++ src/lib.rs | 4 ++++ src/ratelimiter.rs | 4 ++++ src/types/config/mod.rs | 4 ++++ src/types/config/types/api_configuration.rs | 4 ++++ src/types/config/types/cdn_configuration.rs | 4 ++++ src/types/config/types/defaults_configuration.rs | 4 ++++ src/types/config/types/domains_configuration.rs | 4 ++++ src/types/config/types/email_configuration.rs | 4 ++++ src/types/config/types/endpoint_configuration.rs | 4 ++++ src/types/config/types/external_tokens_configuration.rs | 4 ++++ src/types/config/types/general_configuration.rs | 4 ++++ src/types/config/types/gif_configuration.rs | 4 ++++ src/types/config/types/guild_configuration.rs | 4 ++++ src/types/config/types/kafka_configuration.rs | 4 ++++ src/types/config/types/limit_configuration.rs | 4 ++++ src/types/config/types/login_configuration.rs | 4 ++++ src/types/config/types/metrics_configuration.rs | 4 ++++ src/types/config/types/mod.rs | 4 ++++ src/types/config/types/password_reset_configuration.rs | 4 ++++ src/types/config/types/rabbit_mq_configuration.rs | 4 ++++ src/types/config/types/region_configuration.rs | 4 ++++ src/types/config/types/register_configuration.rs | 4 ++++ src/types/config/types/security_configuration.rs | 4 ++++ src/types/config/types/sentry_configuration.rs | 4 ++++ src/types/config/types/subconfigs/client/mod.rs | 4 ++++ src/types/config/types/subconfigs/defaults/guild.rs | 4 ++++ src/types/config/types/subconfigs/defaults/mod.rs | 4 ++++ src/types/config/types/subconfigs/defaults/user.rs | 4 ++++ src/types/config/types/subconfigs/email/mailgun.rs | 4 ++++ src/types/config/types/subconfigs/email/mailjet.rs | 4 ++++ src/types/config/types/subconfigs/email/mod.rs | 4 ++++ src/types/config/types/subconfigs/email/sendgrid.rs | 4 ++++ src/types/config/types/subconfigs/email/smtp.rs | 4 ++++ src/types/config/types/subconfigs/guild/autojoin.rs | 4 ++++ src/types/config/types/subconfigs/guild/discovery.rs | 4 ++++ src/types/config/types/subconfigs/guild/mod.rs | 4 ++++ src/types/config/types/subconfigs/kafka/mod.rs | 4 ++++ src/types/config/types/subconfigs/limits/channel.rs | 4 ++++ src/types/config/types/subconfigs/limits/global.rs | 4 ++++ src/types/config/types/subconfigs/limits/guild.rs | 4 ++++ src/types/config/types/subconfigs/limits/message.rs | 4 ++++ src/types/config/types/subconfigs/limits/mod.rs | 4 ++++ src/types/config/types/subconfigs/limits/ratelimits/auth.rs | 4 ++++ src/types/config/types/subconfigs/limits/ratelimits/mod.rs | 4 ++++ src/types/config/types/subconfigs/limits/ratelimits/route.rs | 4 ++++ src/types/config/types/subconfigs/limits/rates.rs | 4 ++++ src/types/config/types/subconfigs/limits/user.rs | 4 ++++ src/types/config/types/subconfigs/mod.rs | 4 ++++ src/types/config/types/subconfigs/region/mod.rs | 4 ++++ src/types/config/types/subconfigs/register/date_of_birth.rs | 4 ++++ src/types/config/types/subconfigs/register/email.rs | 4 ++++ src/types/config/types/subconfigs/register/mod.rs | 4 ++++ src/types/config/types/subconfigs/register/password.rs | 4 ++++ src/types/config/types/subconfigs/security/captcha.rs | 4 ++++ src/types/config/types/subconfigs/security/mod.rs | 4 ++++ src/types/config/types/subconfigs/security/twofactor.rs | 4 ++++ src/types/config/types/template_configuration.rs | 4 ++++ src/types/entities/application.rs | 4 ++++ src/types/entities/attachment.rs | 4 ++++ src/types/entities/audit_log.rs | 4 ++++ src/types/entities/auto_moderation.rs | 4 ++++ src/types/entities/channel.rs | 4 ++++ src/types/entities/config.rs | 4 ++++ src/types/entities/emoji.rs | 4 ++++ src/types/entities/guild.rs | 4 ++++ src/types/entities/guild_member.rs | 4 ++++ src/types/entities/integration.rs | 4 ++++ src/types/entities/invite.rs | 4 ++++ src/types/entities/message.rs | 4 ++++ src/types/entities/mod.rs | 4 ++++ src/types/entities/ratelimits.rs | 4 ++++ src/types/entities/relationship.rs | 4 ++++ src/types/entities/role.rs | 4 ++++ src/types/entities/security_key.rs | 4 ++++ src/types/entities/stage_instance.rs | 4 ++++ src/types/entities/sticker.rs | 4 ++++ src/types/entities/team.rs | 4 ++++ src/types/entities/template.rs | 4 ++++ src/types/entities/user.rs | 4 ++++ src/types/entities/user_settings.rs | 4 ++++ src/types/entities/voice_state.rs | 4 ++++ src/types/entities/webhook.rs | 4 ++++ src/types/errors.rs | 4 ++++ src/types/events/application.rs | 4 ++++ src/types/events/auto_moderation.rs | 4 ++++ src/types/events/call.rs | 4 ++++ src/types/events/channel.rs | 4 ++++ src/types/events/guild.rs | 4 ++++ src/types/events/heartbeat.rs | 4 ++++ src/types/events/hello.rs | 4 ++++ src/types/events/identify.rs | 4 ++++ src/types/events/integration.rs | 4 ++++ src/types/events/interaction.rs | 4 ++++ src/types/events/invite.rs | 4 ++++ src/types/events/lazy_request.rs | 4 ++++ src/types/events/message.rs | 4 ++++ src/types/events/mod.rs | 4 ++++ src/types/events/passive_update.rs | 4 ++++ src/types/events/presence.rs | 4 ++++ src/types/events/ready.rs | 4 ++++ src/types/events/relationship.rs | 4 ++++ src/types/events/request_members.rs | 4 ++++ src/types/events/resume.rs | 4 ++++ src/types/events/session.rs | 4 ++++ src/types/events/stage_instance.rs | 4 ++++ src/types/events/thread.rs | 4 ++++ src/types/events/user.rs | 4 ++++ src/types/events/voice.rs | 4 ++++ src/types/events/webhooks.rs | 4 ++++ src/types/events/webrtc/identify.rs | 4 ++++ src/types/events/webrtc/mod.rs | 4 ++++ src/types/events/webrtc/ready.rs | 4 ++++ src/types/interfaces/activity.rs | 4 ++++ src/types/interfaces/connected_account.rs | 4 +++- src/types/interfaces/guild_welcome_screen.rs | 4 ++++ src/types/interfaces/interaction.rs | 4 ++++ src/types/interfaces/mod.rs | 4 ++++ src/types/interfaces/status.rs | 4 ++++ src/types/mod.rs | 4 ++++ src/types/schema/apierror.rs | 4 ++++ src/types/schema/auth.rs | 4 ++++ src/types/schema/channel.rs | 4 ++++ src/types/schema/guild.rs | 4 ++++ src/types/schema/message.rs | 4 ++++ src/types/schema/mod.rs | 4 ++++ src/types/schema/relationship.rs | 4 ++++ src/types/schema/role.rs | 4 ++++ src/types/schema/user.rs | 4 ++++ src/types/utils/jwt.rs | 4 ++++ src/types/utils/mod.rs | 4 ++++ src/types/utils/regexes.rs | 4 ++++ src/types/utils/rights.rs | 4 ++++ src/types/utils/snowflake.rs | 4 ++++ src/voice.rs | 4 ++++ tests/auth.rs | 4 ++++ tests/channels.rs | 4 ++++ tests/common/mod.rs | 4 ++++ tests/gateway.rs | 4 ++++ tests/guilds.rs | 4 ++++ tests/instance.rs | 4 ++++ tests/invites.rs | 4 ++++ tests/members.rs | 4 ++++ tests/messages.rs | 4 ++++ tests/ratelimit.rs | 4 ++++ tests/relationships.rs | 4 ++++ tests/roles.rs | 4 ++++ tests/types.rs | 4 ++++ tests/urlbundle.rs | 4 ++++ tests/user.rs | 4 ++++ tests/wasm.rs | 4 ++++ 189 files changed, 755 insertions(+), 1 deletion(-) diff --git a/chorus-macros/src/lib.rs b/chorus-macros/src/lib.rs index fd68df0..ba8f27e 100644 --- a/chorus-macros/src/lib.rs +++ b/chorus-macros/src/lib.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use proc_macro::TokenStream; use quote::quote; use syn::{parse_macro_input, Data, DeriveInput, Field, Fields, FieldsNamed}; diff --git a/examples/gateway_observers.rs b/examples/gateway_observers.rs index a26ecfb..01dc854 100644 --- a/examples/gateway_observers.rs +++ b/examples/gateway_observers.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use async_trait::async_trait; use chorus::gateway::Gateway; use chorus::{ diff --git a/examples/gateway_simple.rs b/examples/gateway_simple.rs index 7a0d807..60d7000 100644 --- a/examples/gateway_simple.rs +++ b/examples/gateway_simple.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use std::time::Duration; use chorus::gateway::Gateway; diff --git a/examples/instance.rs b/examples/instance.rs index 2ec45a3..0bbdc17 100644 --- a/examples/instance.rs +++ b/examples/instance.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use chorus::instance::Instance; #[tokio::main(flavor = "current_thread")] diff --git a/examples/login.rs b/examples/login.rs index 144030b..016801f 100644 --- a/examples/login.rs +++ b/examples/login.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use chorus::instance::Instance; use chorus::types::LoginSchema; diff --git a/src/api/auth/login.rs b/src/api/auth/login.rs index ff99be8..7689af7 100644 --- a/src/api/auth/login.rs +++ b/src/api/auth/login.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use std::sync::{Arc, RwLock}; use reqwest::Client; diff --git a/src/api/auth/mod.rs b/src/api/auth/mod.rs index ae3b219..79c5c30 100644 --- a/src/api/auth/mod.rs +++ b/src/api/auth/mod.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use std::sync::{Arc, RwLock}; pub use login::*; diff --git a/src/api/auth/register.rs b/src/api/auth/register.rs index aa0b483..deece4d 100644 --- a/src/api/auth/register.rs +++ b/src/api/auth/register.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use std::sync::{Arc, RwLock}; use reqwest::Client; diff --git a/src/api/channels/channels.rs b/src/api/channels/channels.rs index 9560d74..6c41576 100644 --- a/src/api/channels/channels.rs +++ b/src/api/channels/channels.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use reqwest::Client; use serde_json::to_string; diff --git a/src/api/channels/messages.rs b/src/api/channels/messages.rs index 3ee5228..feabc37 100644 --- a/src/api/channels/messages.rs +++ b/src/api/channels/messages.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use http::header::CONTENT_DISPOSITION; use http::HeaderMap; use reqwest::{multipart, Client}; diff --git a/src/api/channels/mod.rs b/src/api/channels/mod.rs index b72861e..73ae99c 100644 --- a/src/api/channels/mod.rs +++ b/src/api/channels/mod.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + pub use channels::*; pub use messages::*; pub use permissions::*; diff --git a/src/api/channels/permissions.rs b/src/api/channels/permissions.rs index 5360890..03465b8 100644 --- a/src/api/channels/permissions.rs +++ b/src/api/channels/permissions.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use reqwest::Client; use serde_json::to_string; diff --git a/src/api/channels/reactions.rs b/src/api/channels/reactions.rs index 94d3087..b7c42e1 100644 --- a/src/api/channels/reactions.rs +++ b/src/api/channels/reactions.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use crate::{ errors::ChorusResult, instance::ChorusUser, diff --git a/src/api/guilds/guilds.rs b/src/api/guilds/guilds.rs index b433c84..d5d321e 100644 --- a/src/api/guilds/guilds.rs +++ b/src/api/guilds/guilds.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use reqwest::Client; use serde_json::from_str; use serde_json::to_string; diff --git a/src/api/guilds/member.rs b/src/api/guilds/member.rs index 885ddf9..0037ec7 100644 --- a/src/api/guilds/member.rs +++ b/src/api/guilds/member.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use reqwest::Client; use crate::{ diff --git a/src/api/guilds/messages.rs b/src/api/guilds/messages.rs index 60fd4e3..31a62e1 100644 --- a/src/api/guilds/messages.rs +++ b/src/api/guilds/messages.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use crate::errors::ChorusResult; use crate::instance::ChorusUser; use crate::types::{Guild, Message, MessageSearchQuery, Snowflake}; diff --git a/src/api/guilds/mod.rs b/src/api/guilds/mod.rs index f1fa039..dcd2552 100644 --- a/src/api/guilds/mod.rs +++ b/src/api/guilds/mod.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + pub use guilds::*; pub use messages::*; pub use roles::*; diff --git a/src/api/guilds/roles.rs b/src/api/guilds/roles.rs index f131367..6100a48 100644 --- a/src/api/guilds/roles.rs +++ b/src/api/guilds/roles.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use reqwest::Client; use serde_json::to_string; diff --git a/src/api/invites/mod.rs b/src/api/invites/mod.rs index 80b47d2..68f1417 100644 --- a/src/api/invites/mod.rs +++ b/src/api/invites/mod.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use reqwest::Client; use serde_json::to_string; diff --git a/src/api/mod.rs b/src/api/mod.rs index ab3f9b9..963a9e4 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + //! All of the API's endpoints. pub use channels::messages::*; pub use guilds::*; diff --git a/src/api/policies/instance/instance.rs b/src/api/policies/instance/instance.rs index 4de5fd8..584db33 100644 --- a/src/api/policies/instance/instance.rs +++ b/src/api/policies/instance/instance.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use serde_json::from_str; use crate::errors::{ChorusError, ChorusResult}; diff --git a/src/api/policies/instance/mod.rs b/src/api/policies/instance/mod.rs index b3a9148..8cdb125 100644 --- a/src/api/policies/instance/mod.rs +++ b/src/api/policies/instance/mod.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + pub use instance::*; pub mod instance; diff --git a/src/api/policies/mod.rs b/src/api/policies/mod.rs index 1d5ea99..377ec30 100644 --- a/src/api/policies/mod.rs +++ b/src/api/policies/mod.rs @@ -1 +1,5 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + pub mod instance; diff --git a/src/api/users/channels.rs b/src/api/users/channels.rs index 330b3e3..15d779c 100644 --- a/src/api/users/channels.rs +++ b/src/api/users/channels.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use reqwest::Client; use serde_json::to_string; diff --git a/src/api/users/guilds.rs b/src/api/users/guilds.rs index 6ffcdfc..aac2f7a 100644 --- a/src/api/users/guilds.rs +++ b/src/api/users/guilds.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use reqwest::Client; use serde_json::to_string; diff --git a/src/api/users/mod.rs b/src/api/users/mod.rs index ba789ba..fea9f69 100644 --- a/src/api/users/mod.rs +++ b/src/api/users/mod.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + pub use channels::*; pub use guilds::*; pub use relationships::*; diff --git a/src/api/users/relationships.rs b/src/api/users/relationships.rs index 4f9602c..3d68603 100644 --- a/src/api/users/relationships.rs +++ b/src/api/users/relationships.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use reqwest::Client; use serde_json::to_string; diff --git a/src/api/users/users.rs b/src/api/users/users.rs index 0f31d6f..f7b0f3b 100644 --- a/src/api/users/users.rs +++ b/src/api/users/users.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use std::sync::{Arc, RwLock}; use reqwest::Client; diff --git a/src/errors.rs b/src/errors.rs index c20ac64..8cb3e78 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + //! Contains all the errors that can be returned by the library. use custom_error::custom_error; diff --git a/src/gateway/backends/mod.rs b/src/gateway/backends/mod.rs index edb5dc9..6df070d 100644 --- a/src/gateway/backends/mod.rs +++ b/src/gateway/backends/mod.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + #[cfg(all(not(target_arch = "wasm32"), feature = "client"))] pub mod tungstenite; #[cfg(all(not(target_arch = "wasm32"), feature = "client"))] diff --git a/src/gateway/backends/tungstenite.rs b/src/gateway/backends/tungstenite.rs index 5184329..fe537d3 100644 --- a/src/gateway/backends/tungstenite.rs +++ b/src/gateway/backends/tungstenite.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use futures_util::{ stream::{SplitSink, SplitStream}, StreamExt, diff --git a/src/gateway/backends/wasm.rs b/src/gateway/backends/wasm.rs index e9927ac..83f4b37 100644 --- a/src/gateway/backends/wasm.rs +++ b/src/gateway/backends/wasm.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use futures_util::{ stream::{SplitSink, SplitStream}, StreamExt, diff --git a/src/gateway/events.rs b/src/gateway/events.rs index fdb7b25..cf074bd 100644 --- a/src/gateway/events.rs +++ b/src/gateway/events.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use super::*; use crate::types; diff --git a/src/gateway/gateway.rs b/src/gateway/gateway.rs index 684b9d2..3011857 100644 --- a/src/gateway/gateway.rs +++ b/src/gateway/gateway.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use std::time::Duration; use futures_util::{SinkExt, StreamExt}; diff --git a/src/gateway/handle.rs b/src/gateway/handle.rs index bc64077..4ce725d 100644 --- a/src/gateway/handle.rs +++ b/src/gateway/handle.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use futures_util::SinkExt; use log::*; diff --git a/src/gateway/heartbeat.rs b/src/gateway/heartbeat.rs index e6991f3..7c77914 100644 --- a/src/gateway/heartbeat.rs +++ b/src/gateway/heartbeat.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use futures_util::SinkExt; use log::*; diff --git a/src/gateway/message.rs b/src/gateway/message.rs index 2c12e48..f484d35 100644 --- a/src/gateway/message.rs +++ b/src/gateway/message.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use crate::types; use super::*; diff --git a/src/gateway/mod.rs b/src/gateway/mod.rs index c5f415e..01f2163 100644 --- a/src/gateway/mod.rs +++ b/src/gateway/mod.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use async_trait::async_trait; pub mod backends; diff --git a/src/instance.rs b/src/instance.rs index cd5f6b2..d78e738 100644 --- a/src/instance.rs +++ b/src/instance.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + //! Instance and ChorusUser objects. use std::collections::HashMap; diff --git a/src/lib.rs b/src/lib.rs index 1bbeeef..d4f8c21 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + /*! Chorus combines all the required functionalities of a user-centric Spacebar library into one package. The library handles various aspects on your behalf, such as rate limiting, authentication and maintaining diff --git a/src/ratelimiter.rs b/src/ratelimiter.rs index f6a7c26..5e69d95 100644 --- a/src/ratelimiter.rs +++ b/src/ratelimiter.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + //! Ratelimiter and request handling functionality. use std::collections::HashMap; diff --git a/src/types/config/mod.rs b/src/types/config/mod.rs index 4a11c71..4419208 100644 --- a/src/types/config/mod.rs +++ b/src/types/config/mod.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use serde::{Deserialize, Serialize}; use serde_json::{Map, Value}; diff --git a/src/types/config/types/api_configuration.rs b/src/types/config/types/api_configuration.rs index 2d617fe..2543d9c 100644 --- a/src/types/config/types/api_configuration.rs +++ b/src/types/config/types/api_configuration.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] diff --git a/src/types/config/types/cdn_configuration.rs b/src/types/config/types/cdn_configuration.rs index 5c76273..c0045b2 100644 --- a/src/types/config/types/cdn_configuration.rs +++ b/src/types/config/types/cdn_configuration.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] diff --git a/src/types/config/types/defaults_configuration.rs b/src/types/config/types/defaults_configuration.rs index c2b67c2..e6e0867 100644 --- a/src/types/config/types/defaults_configuration.rs +++ b/src/types/config/types/defaults_configuration.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use serde::{Deserialize, Serialize}; use crate::types::config::types::subconfigs::defaults::{guild::GuildDefaults, user::UserDefaults}; diff --git a/src/types/config/types/domains_configuration.rs b/src/types/config/types/domains_configuration.rs index 297b827..6e9218e 100644 --- a/src/types/config/types/domains_configuration.rs +++ b/src/types/config/types/domains_configuration.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use serde::{Deserialize, Serialize}; #[derive(Deserialize, Serialize, Eq, PartialEq, Hash, Clone, Debug)] diff --git a/src/types/config/types/email_configuration.rs b/src/types/config/types/email_configuration.rs index 954f4de..ffeb726 100644 --- a/src/types/config/types/email_configuration.rs +++ b/src/types/config/types/email_configuration.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use serde::{Deserialize, Serialize}; use crate::types::config::types::subconfigs::email::{ diff --git a/src/types/config/types/endpoint_configuration.rs b/src/types/config/types/endpoint_configuration.rs index b484791..0119cb2 100644 --- a/src/types/config/types/endpoint_configuration.rs +++ b/src/types/config/types/endpoint_configuration.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] diff --git a/src/types/config/types/external_tokens_configuration.rs b/src/types/config/types/external_tokens_configuration.rs index f417b2f..44e1313 100644 --- a/src/types/config/types/external_tokens_configuration.rs +++ b/src/types/config/types/external_tokens_configuration.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] diff --git a/src/types/config/types/general_configuration.rs b/src/types/config/types/general_configuration.rs index 450fd52..d49a6f8 100644 --- a/src/types/config/types/general_configuration.rs +++ b/src/types/config/types/general_configuration.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use serde::{Deserialize, Serialize}; use crate::types::utils::Snowflake; diff --git a/src/types/config/types/gif_configuration.rs b/src/types/config/types/gif_configuration.rs index 8644fb4..f0e1358 100644 --- a/src/types/config/types/gif_configuration.rs +++ b/src/types/config/types/gif_configuration.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)] diff --git a/src/types/config/types/guild_configuration.rs b/src/types/config/types/guild_configuration.rs index 65897ea..af40b30 100644 --- a/src/types/config/types/guild_configuration.rs +++ b/src/types/config/types/guild_configuration.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use std::fmt::{Display, Formatter}; #[cfg(feature = "sqlx")] use std::io::Write; diff --git a/src/types/config/types/kafka_configuration.rs b/src/types/config/types/kafka_configuration.rs index 46d10b6..909dc03 100644 --- a/src/types/config/types/kafka_configuration.rs +++ b/src/types/config/types/kafka_configuration.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use serde::{Deserialize, Serialize}; use crate::types::config::types::subconfigs::kafka::KafkaBroker; diff --git a/src/types/config/types/limit_configuration.rs b/src/types/config/types/limit_configuration.rs index 44f888a..f322a30 100644 --- a/src/types/config/types/limit_configuration.rs +++ b/src/types/config/types/limit_configuration.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use serde::{Deserialize, Serialize}; use crate::types::config::types::subconfigs::limits::{ diff --git a/src/types/config/types/login_configuration.rs b/src/types/config/types/login_configuration.rs index a2b1039..83125e0 100644 --- a/src/types/config/types/login_configuration.rs +++ b/src/types/config/types/login_configuration.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] diff --git a/src/types/config/types/metrics_configuration.rs b/src/types/config/types/metrics_configuration.rs index 336ac84..98d3536 100644 --- a/src/types/config/types/metrics_configuration.rs +++ b/src/types/config/types/metrics_configuration.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] diff --git a/src/types/config/types/mod.rs b/src/types/config/types/mod.rs index 6ea2c03..a35b305 100644 --- a/src/types/config/types/mod.rs +++ b/src/types/config/types/mod.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + pub mod api_configuration; pub mod cdn_configuration; pub mod defaults_configuration; diff --git a/src/types/config/types/password_reset_configuration.rs b/src/types/config/types/password_reset_configuration.rs index 4dddae9..d1c730e 100644 --- a/src/types/config/types/password_reset_configuration.rs +++ b/src/types/config/types/password_reset_configuration.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] diff --git a/src/types/config/types/rabbit_mq_configuration.rs b/src/types/config/types/rabbit_mq_configuration.rs index 2437055..c073fa0 100644 --- a/src/types/config/types/rabbit_mq_configuration.rs +++ b/src/types/config/types/rabbit_mq_configuration.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] diff --git a/src/types/config/types/region_configuration.rs b/src/types/config/types/region_configuration.rs index 078fa03..9e933a2 100644 --- a/src/types/config/types/region_configuration.rs +++ b/src/types/config/types/region_configuration.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use serde::{Deserialize, Serialize}; use crate::types::config::types::subconfigs::region::Region; diff --git a/src/types/config/types/register_configuration.rs b/src/types/config/types/register_configuration.rs index 4a20824..a4573bf 100644 --- a/src/types/config/types/register_configuration.rs +++ b/src/types/config/types/register_configuration.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use serde::{Deserialize, Serialize}; use crate::types::config::types::subconfigs::register::{ diff --git a/src/types/config/types/security_configuration.rs b/src/types/config/types/security_configuration.rs index caeb72c..0bd190f 100644 --- a/src/types/config/types/security_configuration.rs +++ b/src/types/config/types/security_configuration.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use base64::Engine; use rand::Fill; use serde::{Deserialize, Serialize}; diff --git a/src/types/config/types/sentry_configuration.rs b/src/types/config/types/sentry_configuration.rs index 99de4ff..e6e15df 100644 --- a/src/types/config/types/sentry_configuration.rs +++ b/src/types/config/types/sentry_configuration.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use std::ffi::OsString; use serde::{Deserialize, Serialize}; diff --git a/src/types/config/types/subconfigs/client/mod.rs b/src/types/config/types/subconfigs/client/mod.rs index 5d3d304..95aefdc 100644 --- a/src/types/config/types/subconfigs/client/mod.rs +++ b/src/types/config/types/subconfigs/client/mod.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use serde::{Deserialize, Serialize}; #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)] diff --git a/src/types/config/types/subconfigs/defaults/guild.rs b/src/types/config/types/subconfigs/defaults/guild.rs index a709f82..8509fe5 100644 --- a/src/types/config/types/subconfigs/defaults/guild.rs +++ b/src/types/config/types/subconfigs/defaults/guild.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use serde::{Deserialize, Serialize}; use crate::types::{ExplicitContentFilterLevel, MessageNotificationLevel}; diff --git a/src/types/config/types/subconfigs/defaults/mod.rs b/src/types/config/types/subconfigs/defaults/mod.rs index 56d877f..250738f 100644 --- a/src/types/config/types/subconfigs/defaults/mod.rs +++ b/src/types/config/types/subconfigs/defaults/mod.rs @@ -1,2 +1,6 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + 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 index 635d6d4..d7dc7b3 100644 --- a/src/types/config/types/subconfigs/defaults/user.rs +++ b/src/types/config/types/subconfigs/defaults/user.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] diff --git a/src/types/config/types/subconfigs/email/mailgun.rs b/src/types/config/types/subconfigs/email/mailgun.rs index 636e462..9f803ad 100644 --- a/src/types/config/types/subconfigs/email/mailgun.rs +++ b/src/types/config/types/subconfigs/email/mailgun.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] diff --git a/src/types/config/types/subconfigs/email/mailjet.rs b/src/types/config/types/subconfigs/email/mailjet.rs index 4e505c1..b1c9b42 100644 --- a/src/types/config/types/subconfigs/email/mailjet.rs +++ b/src/types/config/types/subconfigs/email/mailjet.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] diff --git a/src/types/config/types/subconfigs/email/mod.rs b/src/types/config/types/subconfigs/email/mod.rs index 21253fd..f3cbbec 100644 --- a/src/types/config/types/subconfigs/email/mod.rs +++ b/src/types/config/types/subconfigs/email/mod.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + pub mod mailgun; pub mod mailjet; pub mod sendgrid; diff --git a/src/types/config/types/subconfigs/email/sendgrid.rs b/src/types/config/types/subconfigs/email/sendgrid.rs index 879c719..993a917 100644 --- a/src/types/config/types/subconfigs/email/sendgrid.rs +++ b/src/types/config/types/subconfigs/email/sendgrid.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] diff --git a/src/types/config/types/subconfigs/email/smtp.rs b/src/types/config/types/subconfigs/email/smtp.rs index a02c66f..38ade70 100644 --- a/src/types/config/types/subconfigs/email/smtp.rs +++ b/src/types/config/types/subconfigs/email/smtp.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] diff --git a/src/types/config/types/subconfigs/guild/autojoin.rs b/src/types/config/types/subconfigs/guild/autojoin.rs index fe72c6c..2cb5cff 100644 --- a/src/types/config/types/subconfigs/guild/autojoin.rs +++ b/src/types/config/types/subconfigs/guild/autojoin.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use serde::{Deserialize, Serialize}; use crate::types::utils::Snowflake; diff --git a/src/types/config/types/subconfigs/guild/discovery.rs b/src/types/config/types/subconfigs/guild/discovery.rs index 1e283b0..50738f1 100644 --- a/src/types/config/types/subconfigs/guild/discovery.rs +++ b/src/types/config/types/subconfigs/guild/discovery.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] diff --git a/src/types/config/types/subconfigs/guild/mod.rs b/src/types/config/types/subconfigs/guild/mod.rs index e4d7dcf..f180356 100644 --- a/src/types/config/types/subconfigs/guild/mod.rs +++ b/src/types/config/types/subconfigs/guild/mod.rs @@ -1,2 +1,6 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + 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 index 1ee4015..459cfd8 100644 --- a/src/types/config/types/subconfigs/kafka/mod.rs +++ b/src/types/config/types/subconfigs/kafka/mod.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] diff --git a/src/types/config/types/subconfigs/limits/channel.rs b/src/types/config/types/subconfigs/limits/channel.rs index 03e46e5..2415726 100644 --- a/src/types/config/types/subconfigs/limits/channel.rs +++ b/src/types/config/types/subconfigs/limits/channel.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] diff --git a/src/types/config/types/subconfigs/limits/global.rs b/src/types/config/types/subconfigs/limits/global.rs index 87f9e1c..0140447 100644 --- a/src/types/config/types/subconfigs/limits/global.rs +++ b/src/types/config/types/subconfigs/limits/global.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] diff --git a/src/types/config/types/subconfigs/limits/guild.rs b/src/types/config/types/subconfigs/limits/guild.rs index 6def5a0..9ef8f90 100644 --- a/src/types/config/types/subconfigs/limits/guild.rs +++ b/src/types/config/types/subconfigs/limits/guild.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] diff --git a/src/types/config/types/subconfigs/limits/message.rs b/src/types/config/types/subconfigs/limits/message.rs index 9d368b9..3beb76e 100644 --- a/src/types/config/types/subconfigs/limits/message.rs +++ b/src/types/config/types/subconfigs/limits/message.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] diff --git a/src/types/config/types/subconfigs/limits/mod.rs b/src/types/config/types/subconfigs/limits/mod.rs index 4dbc2fa..549e909 100644 --- a/src/types/config/types/subconfigs/limits/mod.rs +++ b/src/types/config/types/subconfigs/limits/mod.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + pub mod channel; pub mod global; pub mod guild; diff --git a/src/types/config/types/subconfigs/limits/ratelimits/auth.rs b/src/types/config/types/subconfigs/limits/ratelimits/auth.rs index 9815a5d..78f1908 100644 --- a/src/types/config/types/subconfigs/limits/ratelimits/auth.rs +++ b/src/types/config/types/subconfigs/limits/ratelimits/auth.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use serde::{Deserialize, Serialize}; use crate::types::config::types::subconfigs::limits::ratelimits::RateLimitOptions; diff --git a/src/types/config/types/subconfigs/limits/ratelimits/mod.rs b/src/types/config/types/subconfigs/limits/ratelimits/mod.rs index 934b922..501afe9 100644 --- a/src/types/config/types/subconfigs/limits/ratelimits/mod.rs +++ b/src/types/config/types/subconfigs/limits/ratelimits/mod.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use serde::{Deserialize, Serialize}; pub mod auth; diff --git a/src/types/config/types/subconfigs/limits/ratelimits/route.rs b/src/types/config/types/subconfigs/limits/ratelimits/route.rs index 1aa0be2..7c70fdc 100644 --- a/src/types/config/types/subconfigs/limits/ratelimits/route.rs +++ b/src/types/config/types/subconfigs/limits/ratelimits/route.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use serde::{Deserialize, Serialize}; use crate::types::config::types::subconfigs::limits::ratelimits::{ diff --git a/src/types/config/types/subconfigs/limits/rates.rs b/src/types/config/types/subconfigs/limits/rates.rs index 642dcc8..4c9b9a1 100644 --- a/src/types/config/types/subconfigs/limits/rates.rs +++ b/src/types/config/types/subconfigs/limits/rates.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use std::collections::HashMap; use serde::{Deserialize, Serialize}; diff --git a/src/types/config/types/subconfigs/limits/user.rs b/src/types/config/types/subconfigs/limits/user.rs index e43b746..473535a 100644 --- a/src/types/config/types/subconfigs/limits/user.rs +++ b/src/types/config/types/subconfigs/limits/user.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] diff --git a/src/types/config/types/subconfigs/mod.rs b/src/types/config/types/subconfigs/mod.rs index 4c85096..4366dc4 100644 --- a/src/types/config/types/subconfigs/mod.rs +++ b/src/types/config/types/subconfigs/mod.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + pub mod client; pub mod defaults; pub mod email; diff --git a/src/types/config/types/subconfigs/region/mod.rs b/src/types/config/types/subconfigs/region/mod.rs index 08c3b73..1661c09 100644 --- a/src/types/config/types/subconfigs/region/mod.rs +++ b/src/types/config/types/subconfigs/region/mod.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] diff --git a/src/types/config/types/subconfigs/register/date_of_birth.rs b/src/types/config/types/subconfigs/register/date_of_birth.rs index 9c1bec1..6689297 100644 --- a/src/types/config/types/subconfigs/register/date_of_birth.rs +++ b/src/types/config/types/subconfigs/register/date_of_birth.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] diff --git a/src/types/config/types/subconfigs/register/email.rs b/src/types/config/types/subconfigs/register/email.rs index 8688cd2..f2aea63 100644 --- a/src/types/config/types/subconfigs/register/email.rs +++ b/src/types/config/types/subconfigs/register/email.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] diff --git a/src/types/config/types/subconfigs/register/mod.rs b/src/types/config/types/subconfigs/register/mod.rs index a2d714e..9a873ae 100644 --- a/src/types/config/types/subconfigs/register/mod.rs +++ b/src/types/config/types/subconfigs/register/mod.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + 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 index 9247f7d..1d380ba 100644 --- a/src/types/config/types/subconfigs/register/password.rs +++ b/src/types/config/types/subconfigs/register/password.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] diff --git a/src/types/config/types/subconfigs/security/captcha.rs b/src/types/config/types/subconfigs/security/captcha.rs index 82bb517..c97b6e2 100644 --- a/src/types/config/types/subconfigs/security/captcha.rs +++ b/src/types/config/types/subconfigs/security/captcha.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] diff --git a/src/types/config/types/subconfigs/security/mod.rs b/src/types/config/types/subconfigs/security/mod.rs index 8455000..26146cd 100644 --- a/src/types/config/types/subconfigs/security/mod.rs +++ b/src/types/config/types/subconfigs/security/mod.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + 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 index 39a0373..1182a45 100644 --- a/src/types/config/types/subconfigs/security/twofactor.rs +++ b/src/types/config/types/subconfigs/security/twofactor.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] diff --git a/src/types/config/types/template_configuration.rs b/src/types/config/types/template_configuration.rs index 932670e..9f370b4 100644 --- a/src/types/config/types/template_configuration.rs +++ b/src/types/config/types/template_configuration.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] diff --git a/src/types/entities/application.rs b/src/types/entities/application.rs index 95df4f2..4014e55 100644 --- a/src/types/entities/application.rs +++ b/src/types/entities/application.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use bitflags::bitflags; use serde::{Deserialize, Serialize}; use serde_json::Value; diff --git a/src/types/entities/attachment.rs b/src/types/entities/attachment.rs index 9aacc08..f2e221d 100644 --- a/src/types/entities/attachment.rs +++ b/src/types/entities/attachment.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use serde::{Deserialize, Serialize}; use crate::types::utils::Snowflake; diff --git a/src/types/entities/audit_log.rs b/src/types/entities/audit_log.rs index 4023b7a..477fb20 100644 --- a/src/types/entities/audit_log.rs +++ b/src/types/entities/audit_log.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use serde::{Deserialize, Serialize}; use crate::gateway::Shared; diff --git a/src/types/entities/auto_moderation.rs b/src/types/entities/auto_moderation.rs index 779f58b..cd69bf2 100644 --- a/src/types/entities/auto_moderation.rs +++ b/src/types/entities/auto_moderation.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use crate::gateway::Shared; #[cfg(feature = "client")] use crate::gateway::Updateable; diff --git a/src/types/entities/channel.rs b/src/types/entities/channel.rs index c268859..7d000dc 100644 --- a/src/types/entities/channel.rs +++ b/src/types/entities/channel.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; use serde_aux::prelude::deserialize_string_from_number; diff --git a/src/types/entities/config.rs b/src/types/entities/config.rs index 6244e9d..000fe25 100644 --- a/src/types/entities/config.rs +++ b/src/types/entities/config.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use serde::{Deserialize, Serialize}; use serde_json::Value; #[cfg(feature = "sqlx")] diff --git a/src/types/entities/emoji.rs b/src/types/entities/emoji.rs index 1c78cba..e84b025 100644 --- a/src/types/entities/emoji.rs +++ b/src/types/entities/emoji.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use std::fmt::Debug; use serde::{Deserialize, Serialize}; diff --git a/src/types/entities/guild.rs b/src/types/entities/guild.rs index 4a3d952..52ec5e5 100644 --- a/src/types/entities/guild.rs +++ b/src/types/entities/guild.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use std::fmt::Debug; use bitflags::bitflags; diff --git a/src/types/entities/guild_member.rs b/src/types/entities/guild_member.rs index a18afbc..14414c5 100644 --- a/src/types/entities/guild_member.rs +++ b/src/types/entities/guild_member.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use serde::{Deserialize, Serialize}; use crate::gateway::Shared; diff --git a/src/types/entities/integration.rs b/src/types/entities/integration.rs index 580590a..97d21c3 100644 --- a/src/types/entities/integration.rs +++ b/src/types/entities/integration.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; diff --git a/src/types/entities/invite.rs b/src/types/entities/invite.rs index f9eaaa3..720203a 100644 --- a/src/types/entities/invite.rs +++ b/src/types/entities/invite.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; diff --git a/src/types/entities/message.rs b/src/types/entities/message.rs index 1e6810d..d764243 100644 --- a/src/types/entities/message.rs +++ b/src/types/entities/message.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use serde::{Deserialize, Serialize}; use crate::gateway::Shared; diff --git a/src/types/entities/mod.rs b/src/types/entities/mod.rs index 7e88a62..1400d0e 100644 --- a/src/types/entities/mod.rs +++ b/src/types/entities/mod.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + pub use application::*; pub use attachment::*; pub use audit_log::*; diff --git a/src/types/entities/ratelimits.rs b/src/types/entities/ratelimits.rs index f766a12..1823e76 100644 --- a/src/types/entities/ratelimits.rs +++ b/src/types/entities/ratelimits.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use std::hash::Hash; use serde::{Deserialize, Serialize}; diff --git a/src/types/entities/relationship.rs b/src/types/entities/relationship.rs index 6f207f2..a568256 100644 --- a/src/types/entities/relationship.rs +++ b/src/types/entities/relationship.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; use serde_repr::{Deserialize_repr, Serialize_repr}; diff --git a/src/types/entities/role.rs b/src/types/entities/role.rs index 6a8327e..1b5e91e 100644 --- a/src/types/entities/role.rs +++ b/src/types/entities/role.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use bitflags::bitflags; use serde::{Deserialize, Serialize}; use serde_aux::prelude::{deserialize_option_number_from_string, deserialize_string_from_number}; diff --git a/src/types/entities/security_key.rs b/src/types/entities/security_key.rs index 2cf8f66..7e0bb6b 100644 --- a/src/types/entities/security_key.rs +++ b/src/types/entities/security_key.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use serde::{Deserialize, Serialize}; use crate::types::utils::Snowflake; diff --git a/src/types/entities/stage_instance.rs b/src/types/entities/stage_instance.rs index 8810f52..d48231b 100644 --- a/src/types/entities/stage_instance.rs +++ b/src/types/entities/stage_instance.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use serde::{Deserialize, Serialize}; use serde_repr::{Deserialize_repr, Serialize_repr}; diff --git a/src/types/entities/sticker.rs b/src/types/entities/sticker.rs index d4331fd..8b95bc4 100644 --- a/src/types/entities/sticker.rs +++ b/src/types/entities/sticker.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use serde::{Deserialize, Serialize}; use crate::gateway::Shared; diff --git a/src/types/entities/team.rs b/src/types/entities/team.rs index daac58c..98bd23e 100644 --- a/src/types/entities/team.rs +++ b/src/types/entities/team.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use serde::{Deserialize, Serialize}; use crate::gateway::Shared; diff --git a/src/types/entities/template.rs b/src/types/entities/template.rs index 167697f..f34fbd7 100644 --- a/src/types/entities/template.rs +++ b/src/types/entities/template.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; diff --git a/src/types/entities/user.rs b/src/types/entities/user.rs index 90221ca..66fbab8 100644 --- a/src/types/entities/user.rs +++ b/src/types/entities/user.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use crate::types::utils::Snowflake; use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; diff --git a/src/types/entities/user_settings.rs b/src/types/entities/user_settings.rs index a27e748..6c072a8 100644 --- a/src/types/entities/user_settings.rs +++ b/src/types/entities/user_settings.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use std::sync::{Arc, RwLock}; use chrono::{serde::ts_milliseconds_option, Utc}; diff --git a/src/types/entities/voice_state.rs b/src/types/entities/voice_state.rs index 035f8f9..dc0047d 100644 --- a/src/types/entities/voice_state.rs +++ b/src/types/entities/voice_state.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + #[cfg(feature = "client")] use chorus_macros::Composite; diff --git a/src/types/entities/webhook.rs b/src/types/entities/webhook.rs index 7eb2b66..f973956 100644 --- a/src/types/entities/webhook.rs +++ b/src/types/entities/webhook.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use std::fmt::Debug; use serde::{Deserialize, Serialize}; diff --git a/src/types/errors.rs b/src/types/errors.rs index db7cbf7..f0a488c 100644 --- a/src/types/errors.rs +++ b/src/types/errors.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use serde::{Deserialize, Serialize}; #[derive(Debug, thiserror::Error)] diff --git a/src/types/events/application.rs b/src/types/events/application.rs index 7fee577..43537ed 100644 --- a/src/types/events/application.rs +++ b/src/types/events/application.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use serde::{Deserialize, Serialize}; use crate::types::{GuildApplicationCommandPermissions, WebSocketEvent}; diff --git a/src/types/events/auto_moderation.rs b/src/types/events/auto_moderation.rs index b667486..fd42207 100644 --- a/src/types/events/auto_moderation.rs +++ b/src/types/events/auto_moderation.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use crate::types::{JsonField, SourceUrlField}; use chorus_macros::{JsonField, SourceUrlField}; use serde::{Deserialize, Serialize}; diff --git a/src/types/events/call.rs b/src/types/events/call.rs index 508aae2..11a8801 100644 --- a/src/types/events/call.rs +++ b/src/types/events/call.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use serde::{Deserialize, Serialize}; use crate::types::{Snowflake, VoiceState, WebSocketEvent}; diff --git a/src/types/events/channel.rs b/src/types/events/channel.rs index 10e8561..748d04a 100644 --- a/src/types/events/channel.rs +++ b/src/types/events/channel.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use crate::types::events::WebSocketEvent; use crate::types::IntoShared; use crate::types::{entities::Channel, JsonField, Snowflake, SourceUrlField}; diff --git a/src/types/events/guild.rs b/src/types/events/guild.rs index a460403..92f46ea 100644 --- a/src/types/events/guild.rs +++ b/src/types/events/guild.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use chorus_macros::{JsonField, SourceUrlField}; use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; diff --git a/src/types/events/heartbeat.rs b/src/types/events/heartbeat.rs index b402ff9..2b4141b 100644 --- a/src/types/events/heartbeat.rs +++ b/src/types/events/heartbeat.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use crate::types::events::WebSocketEvent; use serde::{Deserialize, Serialize}; diff --git a/src/types/events/hello.rs b/src/types/events/hello.rs index fef3e22..83542c9 100644 --- a/src/types/events/hello.rs +++ b/src/types/events/hello.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use crate::types::WebSocketEvent; use serde::{Deserialize, Serialize}; diff --git a/src/types/events/identify.rs b/src/types/events/identify.rs index 12bc369..648b554 100644 --- a/src/types/events/identify.rs +++ b/src/types/events/identify.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use crate::types::events::{PresenceUpdate, WebSocketEvent}; use serde::{Deserialize, Serialize}; use serde_with::serde_as; diff --git a/src/types/events/integration.rs b/src/types/events/integration.rs index 2423e78..3550c4e 100644 --- a/src/types/events/integration.rs +++ b/src/types/events/integration.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use serde::{Deserialize, Serialize}; use crate::types::{Integration, Snowflake, WebSocketEvent}; diff --git a/src/types/events/interaction.rs b/src/types/events/interaction.rs index 304e7d4..48d5526 100644 --- a/src/types/events/interaction.rs +++ b/src/types/events/interaction.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use serde::{Deserialize, Serialize}; use crate::types::{Interaction, WebSocketEvent}; diff --git a/src/types/events/invite.rs b/src/types/events/invite.rs index 674cc62..01e76ff 100644 --- a/src/types/events/invite.rs +++ b/src/types/events/invite.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use serde::{Deserialize, Serialize}; use crate::types::{GuildInvite, Snowflake, WebSocketEvent}; diff --git a/src/types/events/lazy_request.rs b/src/types/events/lazy_request.rs index fd53183..0311e42 100644 --- a/src/types/events/lazy_request.rs +++ b/src/types/events/lazy_request.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use std::collections::HashMap; use serde::{Deserialize, Serialize}; diff --git a/src/types/events/message.rs b/src/types/events/message.rs index fac083b..ccf74c7 100644 --- a/src/types/events/message.rs +++ b/src/types/events/message.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use serde::{Deserialize, Serialize}; use crate::types::{ diff --git a/src/types/events/mod.rs b/src/types/events/mod.rs index 5c8e6c4..a607f2c 100644 --- a/src/types/events/mod.rs +++ b/src/types/events/mod.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use serde::{Deserialize, Serialize}; pub use application::*; diff --git a/src/types/events/passive_update.rs b/src/types/events/passive_update.rs index 234af3e..0f728a2 100644 --- a/src/types/events/passive_update.rs +++ b/src/types/events/passive_update.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use serde::{Deserialize, Serialize}; use super::{ChannelUnreadUpdateObject, WebSocketEvent}; diff --git a/src/types/events/presence.rs b/src/types/events/presence.rs index e9a7dee..afbf633 100644 --- a/src/types/events/presence.rs +++ b/src/types/events/presence.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use crate::types::{events::WebSocketEvent, UserStatus}; use crate::types::{Activity, ClientStatusObject, PublicUser, Snowflake}; use serde::{Deserialize, Serialize}; diff --git a/src/types/events/ready.rs b/src/types/events/ready.rs index ea46b69..7e88bdf 100644 --- a/src/types/events/ready.rs +++ b/src/types/events/ready.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use serde::{Deserialize, Serialize}; use crate::types::entities::{Guild, User}; diff --git a/src/types/events/relationship.rs b/src/types/events/relationship.rs index a1f75a5..6352d91 100644 --- a/src/types/events/relationship.rs +++ b/src/types/events/relationship.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use crate::types::{events::WebSocketEvent, Relationship, RelationshipType, Snowflake}; use serde::{Deserialize, Serialize}; diff --git a/src/types/events/request_members.rs b/src/types/events/request_members.rs index 526313b..0e4d9dd 100644 --- a/src/types/events/request_members.rs +++ b/src/types/events/request_members.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use crate::types::{events::WebSocketEvent, Snowflake}; use serde::{Deserialize, Serialize}; diff --git a/src/types/events/resume.rs b/src/types/events/resume.rs index 45d2235..86b3dff 100644 --- a/src/types/events/resume.rs +++ b/src/types/events/resume.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use crate::types::events::WebSocketEvent; use serde::{Deserialize, Serialize}; diff --git a/src/types/events/session.rs b/src/types/events/session.rs index 868c8e8..2e5de7a 100644 --- a/src/types/events/session.rs +++ b/src/types/events/session.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use serde::{Deserialize, Serialize}; use crate::types::{Activity, WebSocketEvent}; diff --git a/src/types/events/stage_instance.rs b/src/types/events/stage_instance.rs index c2bbc46..3a0fa64 100644 --- a/src/types/events/stage_instance.rs +++ b/src/types/events/stage_instance.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use serde::{Deserialize, Serialize}; use crate::types::{StageInstance, WebSocketEvent}; diff --git a/src/types/events/thread.rs b/src/types/events/thread.rs index 34dd1a2..abfecf9 100644 --- a/src/types/events/thread.rs +++ b/src/types/events/thread.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use chorus_macros::{JsonField, SourceUrlField}; use serde::{Deserialize, Serialize}; diff --git a/src/types/events/user.rs b/src/types/events/user.rs index 7165812..130ddd1 100644 --- a/src/types/events/user.rs +++ b/src/types/events/user.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use serde::{Deserialize, Serialize}; use crate::types::entities::PublicUser; diff --git a/src/types/events/voice.rs b/src/types/events/voice.rs index 2618ee1..76687d4 100644 --- a/src/types/events/voice.rs +++ b/src/types/events/voice.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use crate::types::{events::WebSocketEvent, Snowflake, VoiceState}; use serde::{Deserialize, Serialize}; diff --git a/src/types/events/webhooks.rs b/src/types/events/webhooks.rs index 518b332..814589c 100644 --- a/src/types/events/webhooks.rs +++ b/src/types/events/webhooks.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use serde::{Deserialize, Serialize}; use crate::types::Snowflake; diff --git a/src/types/events/webrtc/identify.rs b/src/types/events/webrtc/identify.rs index 45f1037..f409362 100644 --- a/src/types/events/webrtc/identify.rs +++ b/src/types/events/webrtc/identify.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use crate::types::{Snowflake, WebSocketEvent}; use serde::{Deserialize, Serialize}; diff --git a/src/types/events/webrtc/mod.rs b/src/types/events/webrtc/mod.rs index ebaf7b2..a6d4ba4 100644 --- a/src/types/events/webrtc/mod.rs +++ b/src/types/events/webrtc/mod.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + pub use identify::*; pub use ready::*; diff --git a/src/types/events/webrtc/ready.rs b/src/types/events/webrtc/ready.rs index 008e41e..417e042 100644 --- a/src/types/events/webrtc/ready.rs +++ b/src/types/events/webrtc/ready.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use std::net::Ipv4Addr; use crate::types::WebSocketEvent; diff --git a/src/types/interfaces/activity.rs b/src/types/interfaces/activity.rs index 0da4747..b042d91 100644 --- a/src/types/interfaces/activity.rs +++ b/src/types/interfaces/activity.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use serde::{Deserialize, Serialize}; use crate::types::{entities::Emoji, Snowflake}; diff --git a/src/types/interfaces/connected_account.rs b/src/types/interfaces/connected_account.rs index 8b13789..66e0819 100644 --- a/src/types/interfaces/connected_account.rs +++ b/src/types/interfaces/connected_account.rs @@ -1 +1,3 @@ - +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. diff --git a/src/types/interfaces/guild_welcome_screen.rs b/src/types/interfaces/guild_welcome_screen.rs index dbeef0f..1646ca1 100644 --- a/src/types/interfaces/guild_welcome_screen.rs +++ b/src/types/interfaces/guild_welcome_screen.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use serde::{Deserialize, Serialize}; use crate::types::utils::Snowflake; diff --git a/src/types/interfaces/interaction.rs b/src/types/interfaces/interaction.rs index 2aa29fd..f88eaf9 100644 --- a/src/types/interfaces/interaction.rs +++ b/src/types/interfaces/interaction.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use serde::{Deserialize, Serialize}; use serde_json::Value; diff --git a/src/types/interfaces/mod.rs b/src/types/interfaces/mod.rs index 946eb39..309020d 100644 --- a/src/types/interfaces/mod.rs +++ b/src/types/interfaces/mod.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + pub use activity::*; pub use connected_account::*; pub use guild_welcome_screen::*; diff --git a/src/types/interfaces/status.rs b/src/types/interfaces/status.rs index d5c07b6..fa5f821 100644 --- a/src/types/interfaces/status.rs +++ b/src/types/interfaces/status.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use serde::{Deserialize, Serialize}; #[derive(Debug, Deserialize, Serialize, Default, Clone, PartialEq, Eq)] diff --git a/src/types/mod.rs b/src/types/mod.rs index 6f06ef0..8eee13a 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + //! All the types, entities, events and interfaces of the Spacebar API. pub use config::*; diff --git a/src/types/schema/apierror.rs b/src/types/schema/apierror.rs index 95e72a5..0dd1f6f 100644 --- a/src/types/schema/apierror.rs +++ b/src/types/schema/apierror.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + #[cfg(feature = "poem")] use poem::{http::StatusCode, IntoResponse, Response}; use serde_json::{json, Value}; diff --git a/src/types/schema/auth.rs b/src/types/schema/auth.rs index 2159de9..2796805 100644 --- a/src/types/schema/auth.rs +++ b/src/types/schema/auth.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use serde::{Deserialize, Serialize}; #[derive(Debug, Default, Clone, Serialize, Deserialize, PartialEq, Eq)] diff --git a/src/types/schema/channel.rs b/src/types/schema/channel.rs index 354459c..1502f97 100644 --- a/src/types/schema/channel.rs +++ b/src/types/schema/channel.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use bitflags::bitflags; use serde::{Deserialize, Serialize}; diff --git a/src/types/schema/guild.rs b/src/types/schema/guild.rs index 9d2c27d..50519b1 100644 --- a/src/types/schema/guild.rs +++ b/src/types/schema/guild.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use bitflags::bitflags; use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; diff --git a/src/types/schema/message.rs b/src/types/schema/message.rs index 4e34910..7551b6b 100644 --- a/src/types/schema/message.rs +++ b/src/types/schema/message.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use serde::{Deserialize, Serialize}; use crate::types::entities::{ diff --git a/src/types/schema/mod.rs b/src/types/schema/mod.rs index 08dae05..d353e09 100644 --- a/src/types/schema/mod.rs +++ b/src/types/schema/mod.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + pub use apierror::*; pub use auth::*; pub use channel::*; diff --git a/src/types/schema/relationship.rs b/src/types/schema/relationship.rs index b0a60d6..54145ae 100644 --- a/src/types/schema/relationship.rs +++ b/src/types/schema/relationship.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use serde::{Deserialize, Serialize}; use crate::types::RelationshipType; diff --git a/src/types/schema/role.rs b/src/types/schema/role.rs index 284f506..168d999 100644 --- a/src/types/schema/role.rs +++ b/src/types/schema/role.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use serde::{Deserialize, Serialize}; #[derive(Debug, Deserialize, Serialize, Clone)] diff --git a/src/types/schema/user.rs b/src/types/schema/user.rs index 5584cf4..7d21754 100644 --- a/src/types/schema/user.rs +++ b/src/types/schema/user.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use std::collections::HashMap; use serde::{Deserialize, Serialize}; diff --git a/src/types/utils/jwt.rs b/src/types/utils/jwt.rs index ca0aebb..6addb4c 100644 --- a/src/types/utils/jwt.rs +++ b/src/types/utils/jwt.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use crate::types::utils::Snowflake; use jsonwebtoken::{encode, EncodingKey, Header}; use serde::{Deserialize, Serialize}; diff --git a/src/types/utils/mod.rs b/src/types/utils/mod.rs index 1b1b3b6..a160260 100644 --- a/src/types/utils/mod.rs +++ b/src/types/utils/mod.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + pub use regexes::*; pub use rights::Rights; pub use snowflake::Snowflake; diff --git a/src/types/utils/regexes.rs b/src/types/utils/regexes.rs index 0f160eb..f854b4f 100644 --- a/src/types/utils/regexes.rs +++ b/src/types/utils/regexes.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use lazy_static::lazy_static; use regex::Regex; diff --git a/src/types/utils/rights.rs b/src/types/utils/rights.rs index 5a3c373..4b1aa13 100644 --- a/src/types/utils/rights.rs +++ b/src/types/utils/rights.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use bitflags::bitflags; bitflags! { diff --git a/src/types/utils/snowflake.rs b/src/types/utils/snowflake.rs index b4e1d9e..1582085 100644 --- a/src/types/utils/snowflake.rs +++ b/src/types/utils/snowflake.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use std::{ fmt::Display, sync::atomic::{AtomicUsize, Ordering}, diff --git a/src/voice.rs b/src/voice.rs index c2fcaf2..79ac5ba 100644 --- a/src/voice.rs +++ b/src/voice.rs @@ -1,2 +1,6 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + //! Where the voice chat implementation will be, once it's finished. //! For development on voice, see the feature/voice branch. diff --git a/tests/auth.rs b/tests/auth.rs index 130bfb6..35007f1 100644 --- a/tests/auth.rs +++ b/tests/auth.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use chorus::types::{LoginSchema, RegisterSchema}; #[cfg(target_arch = "wasm32")] use wasm_bindgen_test::*; diff --git a/tests/channels.rs b/tests/channels.rs index d9842c6..14359d2 100644 --- a/tests/channels.rs +++ b/tests/channels.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use chorus::types::{ self, Channel, GetChannelMessagesSchema, MessageSendSchema, PermissionFlags, PermissionOverwrite, PrivateChannelCreateSchema, RelationshipType, Snowflake, diff --git a/tests/common/mod.rs b/tests/common/mod.rs index e18f92b..339bab8 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use chorus::gateway::{Gateway, Shared}; use chorus::types::IntoShared; use chorus::{ diff --git a/tests/gateway.rs b/tests/gateway.rs index 76f69ae..771db02 100644 --- a/tests/gateway.rs +++ b/tests/gateway.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + mod common; use chorus::errors::GatewayError; diff --git a/tests/guilds.rs b/tests/guilds.rs index ab955de..e3a73f5 100644 --- a/tests/guilds.rs +++ b/tests/guilds.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use chorus::types::{ CreateChannelInviteSchema, Guild, GuildBanCreateSchema, GuildCreateSchema, GuildModifySchema, }; diff --git a/tests/instance.rs b/tests/instance.rs index f1243a5..eb5fc60 100644 --- a/tests/instance.rs +++ b/tests/instance.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + mod common; #[cfg(target_arch = "wasm32")] use wasm_bindgen_test::*; diff --git a/tests/invites.rs b/tests/invites.rs index ae1b9ab..e020199 100644 --- a/tests/invites.rs +++ b/tests/invites.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + mod common; use chorus::types::CreateChannelInviteSchema; #[cfg(target_arch = "wasm32")] diff --git a/tests/members.rs b/tests/members.rs index 9b415c3..a66d25a 100644 --- a/tests/members.rs +++ b/tests/members.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use chorus::{errors::ChorusResult, types::GuildMember}; #[cfg(target_arch = "wasm32")] use wasm_bindgen_test::*; diff --git a/tests/messages.rs b/tests/messages.rs index 7ff7598..3ca6e16 100644 --- a/tests/messages.rs +++ b/tests/messages.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use std::fs::File; use std::io::{BufReader, Read}; diff --git a/tests/ratelimit.rs b/tests/ratelimit.rs index 8843ed0..bae78c9 100644 --- a/tests/ratelimit.rs +++ b/tests/ratelimit.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use chorus::ratelimiter::ChorusRequest; mod common; diff --git a/tests/relationships.rs b/tests/relationships.rs index c8ee9cc..2eea5b3 100644 --- a/tests/relationships.rs +++ b/tests/relationships.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use chorus::types::{self, Relationship, RelationshipType}; #[cfg(target_arch = "wasm32")] use wasm_bindgen_test::*; diff --git a/tests/roles.rs b/tests/roles.rs index 8dda704..3246140 100644 --- a/tests/roles.rs +++ b/tests/roles.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use chorus::types::{self, RoleCreateModifySchema, RoleObject}; #[cfg(target_arch = "wasm32")] use wasm_bindgen_test::*; diff --git a/tests/types.rs b/tests/types.rs index 5acd4c6..8895bb4 100644 --- a/tests/types.rs +++ b/tests/types.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + mod config { mod subconfigs { mod client { diff --git a/tests/urlbundle.rs b/tests/urlbundle.rs index 790229b..5ecdafe 100644 --- a/tests/urlbundle.rs +++ b/tests/urlbundle.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use chorus::types::types::domains_configuration::WellKnownResponse; use chorus::UrlBundle; use serde_json::json; diff --git a/tests/user.rs b/tests/user.rs index bf7938b..2fbc187 100644 --- a/tests/user.rs +++ b/tests/user.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use chorus::types::{PublicUser, Snowflake, User}; #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] diff --git a/tests/wasm.rs b/tests/wasm.rs index d5d26c1..7c30719 100644 --- a/tests/wasm.rs +++ b/tests/wasm.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + use wasm_bindgen_test::wasm_bindgen_test; wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser); From c5bfac66e5deb880b518976699922a1076a1d7e7 Mon Sep 17 00:00:00 2001 From: Dirk Stolle Date: Wed, 31 Jan 2024 22:27:53 +0100 Subject: [PATCH 37/53] Fix a few typos (#471) --- README.md | 2 +- examples/login.rs | 2 +- src/errors.rs | 2 +- src/gateway/handle.rs | 2 +- src/gateway/heartbeat.rs | 2 +- src/gateway/message.rs | 4 ++-- src/instance.rs | 2 +- src/lib.rs | 2 +- src/types/events/lazy_request.rs | 2 +- src/types/events/message.rs | 2 +- tests/common/mod.rs | 2 +- 11 files changed, 12 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index f512f0a..6f37762 100644 --- a/README.md +++ b/README.md @@ -87,7 +87,7 @@ let login_schema = LoginSchema { password: "Correct-Horse-Battery-Staple".to_string(), ..Default::default() }; -// Each user connects to the Gateway. The Gateway connection lives on a seperate thread. Depending on +// Each user connects to the Gateway. The Gateway connection lives on a separate thread. Depending on // the runtime feature you choose, this can potentially take advantage of all of your computers' threads. let user = instance .login_account(login_schema) diff --git a/examples/login.rs b/examples/login.rs index 144030b..9054a20 100644 --- a/examples/login.rs +++ b/examples/login.rs @@ -13,7 +13,7 @@ async fn main() { password: "Correct-Horse-Battery-Staple".to_string(), ..Default::default() }; - // Each user connects to the Gateway. The Gateway connection lives on a seperate thread. Depending on + // Each user connects to the Gateway. The Gateway connection lives on a separate thread. Depending on // the runtime feature you choose, this can potentially take advantage of all of your computers' threads. let user = instance .login_account(login_schema) diff --git a/src/errors.rs b/src/errors.rs index c20ac64..e481648 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -17,7 +17,7 @@ custom_error! { /// Server did not respond. NoResponse = "Did not receive a response from the Server.", /// Reqwest returned an Error instead of a Response object. - RequestFailed{url:String, error: String} = "An error occured while trying to GET from {url}: {error}", + RequestFailed{url:String, error: String} = "An error occurred while trying to GET from {url}: {error}", /// Response received, however, it was not of the successful responses type. Used when no other, special case applies. ReceivedErrorCode{error_code: u16, error: String} = "Received the following error code while requesting from the route: {error_code}", /// Used when there is likely something wrong with the instance, the request was directed to. diff --git a/src/gateway/handle.rs b/src/gateway/handle.rs index bc64077..b155fb5 100644 --- a/src/gateway/handle.rs +++ b/src/gateway/handle.rs @@ -169,7 +169,7 @@ impl GatewayHandle { /// Closes the websocket connection and stops all gateway tasks; /// - /// Esentially pulls the plug on the gateway, leaving it possible to resume; + /// Essentially pulls the plug on the gateway, leaving it possible to resume; pub async fn close(&self) { self.kill_send.send(()).unwrap(); self.websocket_send.lock().await.close().await.unwrap(); diff --git a/src/gateway/heartbeat.rs b/src/gateway/heartbeat.rs index e6991f3..1d2f617 100644 --- a/src/gateway/heartbeat.rs +++ b/src/gateway/heartbeat.rs @@ -128,7 +128,7 @@ impl HeartbeatHandler { let send_result = websocket_tx.lock().await.send(msg.into()).await; if send_result.is_err() { // We couldn't send, the websocket is broken - warn!("GW: Couldnt send heartbeat, websocket seems broken"); + warn!("GW: Couldn't send heartbeat, websocket seems broken"); break; } diff --git a/src/gateway/message.rs b/src/gateway/message.rs index 2c12e48..77efa3f 100644 --- a/src/gateway/message.rs +++ b/src/gateway/message.rs @@ -2,14 +2,14 @@ use crate::types; use super::*; -/// Represents a messsage received from the gateway. This will be either a [types::GatewayReceivePayload], containing events, or a [GatewayError]. +/// Represents a message received from the gateway. This will be either a [types::GatewayReceivePayload], containing events, or a [GatewayError]. /// This struct is used internally when handling messages. #[derive(Clone, Debug)] pub struct GatewayMessage(pub String); impl GatewayMessage { /// Parses the message as an error; - /// Returns the error if succesfully parsed, None if the message isn't an error + /// Returns the error if successfully parsed, None if the message isn't an error pub fn error(&self) -> Option { // Some error strings have dots on the end, which we don't care about let processed_content = self.0.to_lowercase().replace('.', ""); diff --git a/src/instance.rs b/src/instance.rs index cd5f6b2..d160bfc 100644 --- a/src/instance.rs +++ b/src/instance.rs @@ -181,7 +181,7 @@ impl ChorusUser { /// Creates a new [ChorusUser] from existing data. /// /// # Notes - /// This isn't the prefered way to create a ChorusUser. + /// This isn't the preferred way to create a ChorusUser. /// See [Instance::login_account] and [Instance::register_account] instead. pub fn new( belongs_to: Shared, diff --git a/src/lib.rs b/src/lib.rs index 1bbeeef..2efa91e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -44,7 +44,7 @@ let login_schema = LoginSchema { password: "Correct-Horse-Battery-Staple".to_string(), ..Default::default() }; -// Each user connects to the Gateway. The Gateway connection lives on a seperate thread. Depending on +// Each user connects to the Gateway. The Gateway connection lives on a separate thread. Depending on // the runtime feature you choose, this can potentially take advantage of all of your computers' threads. let user = instance .login_account(login_schema) diff --git a/src/types/events/lazy_request.rs b/src/types/events/lazy_request.rs index fd53183..be2103c 100644 --- a/src/types/events/lazy_request.rs +++ b/src/types/events/lazy_request.rs @@ -11,7 +11,7 @@ use super::WebSocketEvent; /// /// Sent to the server to signify lazy loading of a guild; /// Sent by the official client when switching to a guild or channel; -/// After this, you should recieve message updates +/// After this, you should receive message updates /// /// See /// diff --git a/src/types/events/message.rs b/src/types/events/message.rs index fac083b..e93df58 100644 --- a/src/types/events/message.rs +++ b/src/types/events/message.rs @@ -131,7 +131,7 @@ impl WebSocketEvent for MessageReactionRemoveEmoji {} /// Not documented anywhere unofficially /// /// Apparently "Message ACK refers to marking a message as read for Discord's API." () -/// I suspect this is sent and recieved from the gateway to let clients on other devices know the user has read a message +/// I suspect this is sent and received from the gateway to let clients on other devices know the user has read a message /// /// {"t":"MESSAGE_ACK","s":3,"op":0,"d":{"version":52,"message_id":"1107236673638633472","last_viewed":null,"flags":null,"channel_id":"967363950217936897"}} pub struct MessageACK { diff --git a/tests/common/mod.rs b/tests/common/mod.rs index e18f92b..02fef4f 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -52,7 +52,7 @@ impl TestBundle { // Set up a test by creating an Instance and a User. Reduces Test boilerplate. pub(crate) async fn setup() -> TestBundle { let instance = Instance::new("http://localhost:3001/api").await.unwrap(); - // Requires the existance of the below user. + // Requires the existence of the below user. let reg = RegisterSchema { username: "integrationtestuser".into(), consent: true, From 750cc21b0a3b87518e49b9b5233143556653398a Mon Sep 17 00:00:00 2001 From: Dirk Stolle Date: Wed, 31 Jan 2024 22:43:54 +0100 Subject: [PATCH 38/53] Bump actions/setup-node in GHA workflow to v4 (#472) --- .github/workflows/build_and_test.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml index 0cf6fd9..4364550 100644 --- a/.github/workflows/build_and_test.yml +++ b/.github/workflows/build_and_test.yml @@ -19,7 +19,7 @@ jobs: - name: Clone spacebar server run: | git clone https://github.com/bitfl0wer/server.git - - uses: actions/setup-node@v3 + - uses: actions/setup-node@v4 with: node-version: 18 cache: 'npm' @@ -52,7 +52,7 @@ jobs: # - name: Clone spacebar server # run: | # git clone https://github.com/bitfl0wer/server.git - # - uses: actions/setup-node@v3 + # - uses: actions/setup-node@v4 # with: # node-version: 18 # cache: 'npm' @@ -80,7 +80,7 @@ jobs: - name: Clone spacebar server run: | git clone https://github.com/bitfl0wer/server.git - - uses: actions/setup-node@v3 + - uses: actions/setup-node@v4 with: node-version: 18 cache: 'npm' @@ -108,7 +108,7 @@ jobs: - name: Clone spacebar server run: | git clone https://github.com/bitfl0wer/server.git - - uses: actions/setup-node@v3 + - uses: actions/setup-node@v4 with: node-version: 18 cache: 'npm' From 282602d5c3b8c14ccb77a979b592352aeb2a0d0c Mon Sep 17 00:00:00 2001 From: Dirk Stolle Date: Wed, 31 Jan 2024 22:47:46 +0100 Subject: [PATCH 39/53] Update h2 to 0.3.24 to fix vulnerability RUSTSEC-2024-0003 (#474) The update fixes a resource exhaustion vulnerability in h2 which may lead to Denial of Service. For more information on that see . --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9a7b2bd..8cb377b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -666,9 +666,9 @@ checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" [[package]] name = "h2" -version = "0.3.23" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b553656127a00601c8ae5590fcfdc118e4083a7924b6cf4ffc1ea4b99dc429d7" +checksum = "bb2c4422095b67ee78da96fbb51a4cc413b3b25883c7717ff7ca1ab31022c9c9" dependencies = [ "bytes", "fnv", From fde7f18d57738dd309f0d2b1a4dd7de740979ece Mon Sep 17 00:00:00 2001 From: kozabrada123 <59031733+kozabrada123@users.noreply.github.com> Date: Wed, 31 Jan 2024 22:53:13 +0100 Subject: [PATCH 40/53] Fix example in readme for #456 (#475) --- README.md | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/README.md b/README.md index 6f37762..74bc673 100644 --- a/README.md +++ b/README.md @@ -53,16 +53,10 @@ To connect to a Spacebar compatible server, you need to create an [`Instance`](h ```rs use chorus::instance::Instance; -use chorus::UrlBundle; #[tokio::main] async fn main() { - let bundle = UrlBundle::new( - "https://example.com/api".to_string(), - "wss://example.com/".to_string(), - "https://example.com/cdn".to_string(), - ); - let instance = Instance::new(bundle) + let instance = Instance::new("https://example.com") .await .expect("Failed to connect to the Spacebar server"); // You can create as many instances of `Instance` as you want, but each `Instance` should likely be unique. From 862ed9195f1526116462deafc1065b5ecb3347a2 Mon Sep 17 00:00:00 2001 From: Dirk Stolle Date: Wed, 31 Jan 2024 23:02:10 +0100 Subject: [PATCH 41/53] Replace unmaintained actions-rs/toolchain by dtolnay/rust-toolchain (#473) --- .github/workflows/rust-clippy.yml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.github/workflows/rust-clippy.yml b/.github/workflows/rust-clippy.yml index 4c45b0b..819f45d 100644 --- a/.github/workflows/rust-clippy.yml +++ b/.github/workflows/rust-clippy.yml @@ -29,12 +29,9 @@ jobs: uses: actions/checkout@v4 - name: Install Rust toolchain - uses: actions-rs/toolchain@v1 + uses: dtolnay/rust-toolchain@stable with: - profile: minimal - toolchain: stable components: clippy - override: true - name: Install required cargo run: cargo install clippy-sarif sarif-fmt From 45f07e0510b779ec80f3774fb2b12f48fc5c0756 Mon Sep 17 00:00:00 2001 From: kozabrada123 <59031733+kozabrada123@users.noreply.github.com> Date: Wed, 31 Jan 2024 23:15:08 +0100 Subject: [PATCH 42/53] Update github/codeql-action to v3 (#476) --- .github/workflows/rust-clippy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/rust-clippy.yml b/.github/workflows/rust-clippy.yml index 819f45d..b12c0ed 100644 --- a/.github/workflows/rust-clippy.yml +++ b/.github/workflows/rust-clippy.yml @@ -44,7 +44,7 @@ jobs: continue-on-error: true - name: Upload analysis results to GitHub - uses: github/codeql-action/upload-sarif@v2 + uses: github/codeql-action/upload-sarif@v3 with: sarif_file: rust-clippy-results.sarif wait-for-processing: true From 7cc5bb11f9c9f60864bb804067e71c0bc80d6862 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Thu, 1 Feb 2024 11:52:29 +0100 Subject: [PATCH 43/53] Move contribution guidelines to CONTRIBUTING.md --- CONTRIBUTING.md | 10 ++++++++++ README.md | 6 +----- 2 files changed, 11 insertions(+), 5 deletions(-) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..34a1153 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,10 @@ + +# Contributing + +**Please refer to the [contribution guidelines](https://github.com/polyphony-chat/.github/blob/main/CONTRIBUTION_GUIDELINES.md) and [our Code of Conduct](https://github.com/polyphony-chat/.github/blob/main/CODE_OF_CONDUCT.md) before making a contribution.** + +Chorus is currently missing voice support and a lot of API endpoints, many of which should be trivial to implement, +ever since [we streamlined the process of doing so](https://github.com/polyphony-chat/chorus/discussions/401). + +If you'd like to contribute new functionality, check out [The 'Meta'-issues.](https://github.com/polyphony-chat/chorus/issues?q=is%3Aissue+label%3A%22Type%3A+Meta%22+) They contain a comprehensive list of all features which are yet missing for full Discord.com compatibility. +Please feel free to open an Issue with the idea you have, or a Pull Request. \ No newline at end of file diff --git a/README.md b/README.md index f512f0a..bf46df1 100644 --- a/README.md +++ b/README.md @@ -134,11 +134,7 @@ This crate uses Semantic Versioning 2.0.0 as its versioning scheme. You can read ## Contributing -Chorus is currently missing voice support and a lot of API endpoints, many of which should be trivial to implement, -ever since [we streamlined the process of doing so](https://github.com/polyphony-chat/chorus/discussions/401). - -If you'd like to contribute new functionality, check out [The 'Meta'-issues.](https://github.com/polyphony-chat/chorus/issues?q=is%3Aissue+label%3A%22Type%3A+Meta%22+) They contain a comprehensive list of all features which are yet missing for full Discord.com compatibility. -Please feel free to open an Issue with the idea you have, or a Pull Request. Please keep our [contribution guidelines](https://github.com/polyphony-chat/.github/blob/main/CONTRIBUTION_GUIDELINES.md) in mind. Your contribution might not be accepted if it violates these guidelines or [our Code of Conduct](https://github.com/polyphony-chat/.github/blob/main/CODE_OF_CONDUCT.md). +See [CONTRIBUTING.md](./CONTRIBUTING.md).
Progress Tracker/Roadmap From 5a1931951ff63ed3843048076bcb74fc753a12f7 Mon Sep 17 00:00:00 2001 From: kozabrada123 <59031733+kozabrada123@users.noreply.github.com> Date: Sun, 25 Feb 2024 09:54:06 +0100 Subject: [PATCH 44/53] Fix broken luna.gitlab.io links (#480) docs: fix broken luna.gitlab.io links --- src/api/users/relationships.rs | 8 ++++---- src/api/users/users.rs | 2 +- src/types/events/lazy_request.rs | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/api/users/relationships.rs b/src/api/users/relationships.rs index 3d68603..c77db44 100644 --- a/src/api/users/relationships.rs +++ b/src/api/users/relationships.rs @@ -19,7 +19,7 @@ impl ChorusUser { /// Retrieves a list of mutual friends between the authenticated user and a given user. /// /// # Reference - /// See + /// See pub async fn get_mutual_relationships( &mut self, user_id: Snowflake, @@ -41,7 +41,7 @@ impl ChorusUser { /// Retrieves the user's relationships. /// /// # Reference - /// See + /// See pub async fn get_relationships(&mut self) -> ChorusResult> { let url = format!( "{}/users/@me/relationships", @@ -59,7 +59,7 @@ impl ChorusUser { /// Sends a friend request to a user. /// /// # Reference - /// See + /// See pub async fn send_friend_request( &mut self, schema: FriendRequestSendSchema, @@ -136,7 +136,7 @@ impl ChorusUser { /// Removes the relationship between the authenticated user and a given user. /// /// # Reference - /// See + /// See pub async fn remove_relationship(&mut self, user_id: Snowflake) -> ChorusResult<()> { let url = format!( "{}/users/@me/relationships/{}", diff --git a/src/api/users/users.rs b/src/api/users/users.rs index f7b0f3b..b80bc1e 100644 --- a/src/api/users/users.rs +++ b/src/api/users/users.rs @@ -117,7 +117,7 @@ impl User { /// Gets the user's settings. /// /// # Reference - /// See + /// See pub async fn get_settings( token: &String, url_api: &String, diff --git a/src/types/events/lazy_request.rs b/src/types/events/lazy_request.rs index fbea772..0c80c13 100644 --- a/src/types/events/lazy_request.rs +++ b/src/types/events/lazy_request.rs @@ -17,7 +17,7 @@ use super::WebSocketEvent; /// Sent by the official client when switching to a guild or channel; /// After this, you should receive message updates /// -/// See +/// See /// /// {"op":14,"d":{"guild_id":"848582562217590824","typing":true,"activities":true,"threads":true}} pub struct LazyRequest { From ea111a4e35734dea5026ba2d2afbaaf9b525b93f Mon Sep 17 00:00:00 2001 From: kozabrada123 <59031733+kozabrada123@users.noreply.github.com> Date: Thu, 14 Mar 2024 20:25:24 +0100 Subject: [PATCH 45/53] Bump mio to fix RUSTSEC-2024-0019 (#482) --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8cb377b..d9f585d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1085,9 +1085,9 @@ dependencies = [ [[package]] name = "mio" -version = "0.8.10" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09" +checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" dependencies = [ "libc", "wasi", From 2d72442522024535ec579055f0b42cbd5ac51654 Mon Sep 17 00:00:00 2001 From: kozabrada123 <59031733+kozabrada123@users.noreply.github.com> Date: Fri, 15 Mar 2024 16:13:51 +0100 Subject: [PATCH 46/53] Minor docs updates, add Get Private Channels (#483) * feat: add get_private_channels * minor docs update and reorder --- src/api/guilds/guilds.rs | 166 +++++++++++++++++++++----------------- src/api/users/channels.rs | 20 +++++ src/types/schema/guild.rs | 6 ++ 3 files changed, 119 insertions(+), 73 deletions(-) diff --git a/src/api/guilds/guilds.rs b/src/api/guilds/guilds.rs index d5d321e..e2ff9ba 100644 --- a/src/api/guilds/guilds.rs +++ b/src/api/guilds/guilds.rs @@ -18,6 +18,25 @@ use crate::types::{ use crate::types::{GuildBan, Snowflake}; impl Guild { + /// Fetches a guild by its id. + /// + /// # Reference + /// See + pub async fn get(guild_id: Snowflake, user: &mut ChorusUser) -> ChorusResult { + let chorus_request = ChorusRequest { + request: Client::new() + .get(format!( + "{}/guilds/{}", + user.belongs_to.read().unwrap().urls.api, + guild_id + )) + .header("Authorization", user.token()), + limit_type: LimitType::Guild(guild_id), + }; + let response = chorus_request.deserialize_response::(user).await?; + Ok(response) + } + /// Creates a new guild. /// /// # Reference @@ -38,6 +57,35 @@ impl Guild { chorus_request.deserialize_response::(user).await } + /// Modify a guild's settings. + /// + /// Requires the [MANAGE_GUILD](crate::types::PermissionFlags::MANAGE_GUILD) permission. + /// + /// Returns the updated guild. + /// + /// # Reference + /// + pub async fn modify( + guild_id: Snowflake, + schema: GuildModifySchema, + user: &mut ChorusUser, + ) -> ChorusResult { + let chorus_request = ChorusRequest { + request: Client::new() + .patch(format!( + "{}/guilds/{}", + user.belongs_to.read().unwrap().urls.api, + guild_id, + )) + .header("Authorization", user.token()) + .header("Content-Type", "application/json") + .body(to_string(&schema).unwrap()), + limit_type: LimitType::Guild(guild_id), + }; + let response = chorus_request.deserialize_response::(user).await?; + Ok(response) + } + /// Deletes a guild by its id. /// /// User must be the owner. @@ -127,77 +175,11 @@ impl Guild { }; } - /// Fetches a guild by its id. + /// Returns a guild preview object for the given guild ID. + /// + /// If the user is not in the guild, the guild must be discoverable. /// - /// # Reference - /// See - pub async fn get(guild_id: Snowflake, user: &mut ChorusUser) -> ChorusResult { - let chorus_request = ChorusRequest { - request: Client::new() - .get(format!( - "{}/guilds/{}", - user.belongs_to.read().unwrap().urls.api, - guild_id - )) - .header("Authorization", user.token()), - limit_type: LimitType::Guild(guild_id), - }; - let response = chorus_request.deserialize_response::(user).await?; - Ok(response) - } - - pub async fn create_ban( - guild_id: Snowflake, - user_id: Snowflake, - audit_log_reason: Option, - schema: GuildBanCreateSchema, - user: &mut ChorusUser, - ) -> ChorusResult<()> { - // FIXME: Return GuildBan instead of (). Requires to be resolved. - let request = ChorusRequest::new( - http::Method::PUT, - format!( - "{}/guilds/{}/bans/{}", - user.belongs_to.read().unwrap().urls.api, - guild_id, - user_id - ) - .as_str(), - Some(to_string(&schema).unwrap()), - audit_log_reason.as_deref(), - None, - Some(user), - LimitType::Guild(guild_id), - ); - request.handle_request_as_result(user).await - } - - /// # Reference - /// - pub async fn modify( - guild_id: Snowflake, - schema: GuildModifySchema, - user: &mut ChorusUser, - ) -> ChorusResult { - let chorus_request = ChorusRequest { - request: Client::new() - .patch(format!( - "{}/guilds/{}", - user.belongs_to.read().unwrap().urls.api, - guild_id, - )) - .header("Authorization", user.token()) - .header("Content-Type", "application/json") - .body(to_string(&schema).unwrap()), - limit_type: LimitType::Guild(guild_id), - }; - let response = chorus_request.deserialize_response::(user).await?; - Ok(response) - } - - /// Returns a guild preview object for the given guild ID. If the user is not in the guild, the guild must be discoverable. /// # Reference: - /// /// See pub async fn get_preview( guild_id: Snowflake, @@ -274,7 +256,9 @@ impl Guild { request.deserialize_response::>(user).await } - /// Removes a member from a guild. Requires the KICK_MEMBERS permission. Returns a 204 empty response on success. + /// Removes a member from a guild. + /// + /// Requires the [KICK_MEMBERS](crate::types::PermissionFlags::KICK_MEMBERS) permission. /// /// # Reference /// See @@ -387,7 +371,9 @@ impl Guild { .await } - /// Returns a list of ban objects for the guild. Requires the `BAN_MEMBERS` permission. + /// Returns a list of ban objects for the guild. + /// + /// Requires the [BAN_MEMBERS](crate::types::PermissionFlags::BAN_MEMBERS) permission. /// /// # Reference: /// See @@ -417,7 +403,9 @@ impl Guild { request.deserialize_response::>(user).await } - /// Returns a ban object for the given user. Requires the `BAN_MEMBERS` permission. + /// Returns a ban object for the given user. + /// + /// Requires the [BAN_MEMBERS](crate::types::PermissionFlags::BAN_MEMBERS) permission. /// /// # Reference: /// See @@ -445,7 +433,39 @@ impl Guild { request.deserialize_response::(user).await } - /// Removes the ban for a user. Requires the BAN_MEMBERS permissions. Returns a 204 empty response on success. + /// Creates a ban from the guild. + /// + /// Requires the [BAN_MEMBERS](crate::types::PermissionFlags::BAN_MEMBERS) permission. + /// + pub async fn create_ban( + guild_id: Snowflake, + user_id: Snowflake, + audit_log_reason: Option, + schema: GuildBanCreateSchema, + user: &mut ChorusUser, + ) -> ChorusResult<()> { + // FIXME: Return GuildBan instead of (). Requires to be resolved. + let request = ChorusRequest::new( + http::Method::PUT, + format!( + "{}/guilds/{}/bans/{}", + user.belongs_to.read().unwrap().urls.api, + guild_id, + user_id + ) + .as_str(), + Some(to_string(&schema).unwrap()), + audit_log_reason.as_deref(), + None, + Some(user), + LimitType::Guild(guild_id), + ); + request.handle_request_as_result(user).await + } + + /// Removes the ban for a user. + /// + /// Requires the [BAN_MEMBERS](crate::types::PermissionFlags::BAN_MEMBERS) permission. /// /// # Reference: /// See diff --git a/src/api/users/channels.rs b/src/api/users/channels.rs index 15d779c..44ed897 100644 --- a/src/api/users/channels.rs +++ b/src/api/users/channels.rs @@ -13,6 +13,26 @@ use crate::{ }; impl ChorusUser { + /// Fetches a list of private channels the user is in. + /// + /// # Reference: + /// See + pub async fn get_private_channels(&mut self) -> ChorusResult> { + let url = format!( + "{}/users/@me/channels", + self.belongs_to.read().unwrap().urls.api + ); + ChorusRequest { + request: Client::new() + .get(url) + .header("Authorization", self.token()) + .header("Content-Type", "application/json"), + limit_type: LimitType::Global, + } + .deserialize_response::>(self) + .await + } + /// Creates a DM channel or group DM channel. /// /// One recipient creates or returns an existing DM channel, diff --git a/src/types/schema/guild.rs b/src/types/schema/guild.rs index 50519b1..2e29ce0 100644 --- a/src/types/schema/guild.rs +++ b/src/types/schema/guild.rs @@ -38,6 +38,8 @@ pub struct GuildBanCreateSchema { #[derive(Debug, Deserialize, Serialize, Default, Clone, Eq, PartialEq)] #[serde(rename_all = "snake_case")] +/// Represents the schema used to modify a guild. +/// See: pub struct GuildModifySchema { pub name: Option, pub icon: Option>, @@ -47,6 +49,7 @@ pub struct GuildModifySchema { pub discovery_splash: Option>, pub owner_id: Option, pub description: Option, + /// Deprecated pub region: Option, pub afk_channel_id: Option, pub afk_timeout: Option, @@ -56,6 +59,9 @@ pub struct GuildModifySchema { pub features: Option>, pub system_channel_id: Option, pub system_channel_flags: Option, + /// If set to Some(1), will create a new #rules channel + /// + /// Reference: pub rules_channel_id: Option, pub public_updates_channel_id: Option, pub safety_alerts_channel_id: Option, From 9304af2594d79756443f3a8389d22154d497e902 Mon Sep 17 00:00:00 2001 From: kozabrada123 <59031733+kozabrada123@users.noreply.github.com> Date: Fri, 15 Mar 2024 17:08:46 +0100 Subject: [PATCH 47/53] Ignore unused imports for pub use (#484) --- src/api/auth/mod.rs | 3 +++ src/api/channels/mod.rs | 1 + src/api/guilds/mod.rs | 1 + src/api/mod.rs | 2 ++ src/api/policies/instance/mod.rs | 1 + src/api/users/mod.rs | 1 + src/types/interfaces/mod.rs | 1 + src/types/utils/mod.rs | 1 + 8 files changed, 11 insertions(+) diff --git a/src/api/auth/mod.rs b/src/api/auth/mod.rs index 79c5c30..5bd539f 100644 --- a/src/api/auth/mod.rs +++ b/src/api/auth/mod.rs @@ -4,7 +4,10 @@ use std::sync::{Arc, RwLock}; +#[allow(unused_imports)] pub use login::*; + +#[allow(unused_imports)] pub use register::*; use crate::gateway::Gateway; diff --git a/src/api/channels/mod.rs b/src/api/channels/mod.rs index 73ae99c..a8c3047 100644 --- a/src/api/channels/mod.rs +++ b/src/api/channels/mod.rs @@ -2,6 +2,7 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. +#![allow(unused_imports)] pub use channels::*; pub use messages::*; pub use permissions::*; diff --git a/src/api/guilds/mod.rs b/src/api/guilds/mod.rs index dcd2552..e8304a1 100644 --- a/src/api/guilds/mod.rs +++ b/src/api/guilds/mod.rs @@ -2,6 +2,7 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. +#![allow(unused_imports)] pub use guilds::*; pub use messages::*; pub use roles::*; diff --git a/src/api/mod.rs b/src/api/mod.rs index 963a9e4..c9ca279 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -3,6 +3,8 @@ // file, You can obtain one at http://mozilla.org/MPL/2.0/. //! All of the API's endpoints. + +#![allow(unused_imports)] pub use channels::messages::*; pub use guilds::*; pub use invites::*; diff --git a/src/api/policies/instance/mod.rs b/src/api/policies/instance/mod.rs index 8cdb125..e2b97e1 100644 --- a/src/api/policies/instance/mod.rs +++ b/src/api/policies/instance/mod.rs @@ -2,6 +2,7 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. +#![allow(unused_imports)] pub use instance::*; pub mod instance; diff --git a/src/api/users/mod.rs b/src/api/users/mod.rs index fea9f69..b11772a 100644 --- a/src/api/users/mod.rs +++ b/src/api/users/mod.rs @@ -2,6 +2,7 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. +#![allow(unused_imports)] pub use channels::*; pub use guilds::*; pub use relationships::*; diff --git a/src/types/interfaces/mod.rs b/src/types/interfaces/mod.rs index 309020d..b5742d6 100644 --- a/src/types/interfaces/mod.rs +++ b/src/types/interfaces/mod.rs @@ -2,6 +2,7 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. +#![allow(unused_imports)] pub use activity::*; pub use connected_account::*; pub use guild_welcome_screen::*; diff --git a/src/types/utils/mod.rs b/src/types/utils/mod.rs index a160260..8879688 100644 --- a/src/types/utils/mod.rs +++ b/src/types/utils/mod.rs @@ -2,6 +2,7 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. +#![allow(unused_imports)] pub use regexes::*; pub use rights::Rights; pub use snowflake::Snowflake; From 70ecd021b112a7d209739291579eff69437bf182 Mon Sep 17 00:00:00 2001 From: kozabrada123 <59031733+kozabrada123@users.noreply.github.com> Date: Tue, 16 Apr 2024 08:31:14 +0200 Subject: [PATCH 48/53] Bump whoami to fix stack buffer overflow (#485) his was done since whoami versions < 1.5.0 are vulnerable to a stack buffer overflow. --- Cargo.lock | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d9f585d..2968277 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2588,6 +2588,12 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "wasite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" + [[package]] name = "wasm-bindgen" version = "0.2.89" @@ -2705,9 +2711,13 @@ dependencies = [ [[package]] name = "whoami" -version = "1.4.1" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22fc3756b8a9133049b26c7f61ab35416c130e8c09b660f5b3958b446f52cc50" +checksum = "0fec781d48b41f8163426ed18e8fc2864c12937df9ce54c88ede7bd47270893e" +dependencies = [ + "redox_syscall", + "wasite", +] [[package]] name = "wildmatch" From 23c50cd26258c094a631004233f052bbfda732c5 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Tue, 16 Apr 2024 15:13:27 +0200 Subject: [PATCH 49/53] bump version, add rust-version --- Cargo.lock | 2 +- Cargo.toml | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2968277..9bd7e74 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -199,7 +199,7 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chorus" -version = "0.14.0" +version = "0.15.0" dependencies = [ "async-trait", "base64 0.21.7", diff --git a/Cargo.toml b/Cargo.toml index c5b06c6..ac9ea55 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,13 +1,14 @@ [package] name = "chorus" description = "A library for interacting with multiple Spacebar-compatible Instances at once." -version = "0.14.0" +version = "0.15.0" license = "MPL-2.0" edition = "2021" repository = "https://github.com/polyphony-chat/chorus" readme = "README.md" keywords = ["spacebar", "discord", "polyphony"] website = ["https://discord.com/invite/m3FpcapGDD"] +rust-version = "1.67.1" [features] From 5209348ac10013806dd75ed554d42be023723829 Mon Sep 17 00:00:00 2001 From: kozabrada123 <59031733+kozabrada123@users.noreply.github.com> Date: Tue, 16 Apr 2024 15:54:30 +0200 Subject: [PATCH 50/53] Fix code scanning alerts on #487 (#488) fix codescanning alerts on 0.15.0 --- src/types/entities/config.rs | 24 ++++++------------------ 1 file changed, 6 insertions(+), 18 deletions(-) diff --git a/src/types/entities/config.rs b/src/types/entities/config.rs index 000fe25..aca828a 100644 --- a/src/types/entities/config.rs +++ b/src/types/entities/config.rs @@ -16,32 +16,20 @@ pub struct ConfigEntity { impl ConfigEntity { pub fn as_string(&self) -> Option { - let Some(v) = self.value.as_ref() else { - return None; - }; - let Some(v) = v.as_str() else { - return None; - }; + let v = self.value.as_ref()?; + let v = v.as_str()?; Some(v.to_string()) } pub fn as_bool(&self) -> Option { - let Some(v) = self.value.as_ref() else { - return None; - }; - let Some(v) = v.as_bool() else { - return None; - }; + let v = self.value.as_ref()?; + let v = v.as_bool()?; Some(v) } pub fn as_int(&self) -> Option { - let Some(v) = self.value.as_ref() else { - return None; - }; - let Some(v) = v.as_i64() else { - return None; - }; + let v = self.value.as_ref()?; + let v = v.as_i64()?; Some(v) } } From 74d6785e506e0e8ba226d08a6b8ffb49d2347cfb Mon Sep 17 00:00:00 2001 From: kozabrada123 <59031733+kozabrada123@users.noreply.github.com> Date: Tue, 16 Apr 2024 17:18:21 +0200 Subject: [PATCH 51/53] Primitive voice implementation (feature/voice) (#457) * Add Webrtc Identify & Ready * Add more webrtc typings * Attempt an untested voice gateway implementation * fmt * Merge with main * Same allow as for voice as normal gateway * Test error observer * Minor updates * More derives * Even more derives * Small types update * e * Minor doc fixes * Modernise voice gateway * Add default impl for voicegatewayerror * Make voice event fields pub * Event updates via the scientific method * ?? * Fix bad request in voice gateway init * Voice gateway updates * Fix error failing to 'deserialize' properly * Update voice identify * Clarify FIXME related to #430 * Update to v7 * Create seperate voice_gateway.rs and voice_udp.rs * Restructure voice to new module * fix: deserialization error in speaking bitflags * feat: kinda janky ip discovery impl * feat: return ip discovery data + minor update * feat: packet parsing! * fix: voice works again * feat: add voice_media_sink_wants (comitting uncommited changes to merge) * chore: rename events/webrtc to events/voice_gateway * Add UdpHandle * chore: clippy + other misc updates * fix: attempt to fix failing wasm build * chore: yes clippy, that is indeed an unneeded return statement * feat: add VoiceData struct * feat: add VoiceData reference to UdpHandler * feat: decryption? * chore: formatting * feat: add ssrc definition (op 12) * feat: add untested sending & asbtract nonce generation * feat: Public api! (sorta) * small updates * feat: add sequence number * chore: yes * feat: merge VoiceHandler into official development * chore: yes clippy, you are special * fix: duplicated gateway events * feat: first try at vgw wasm compat * fix: blunder * fix: gateway connect using wrong url * fix: properly using encrypted data, bad practice for buffer creation * chore: split voice udp * feat: udp error handling, create udp/backends * fix: its the same * chore: clarify UDP on WASM * api: split voice gateway and udp features, test for voice gateway in WASM * feat: new encryption modes, minor code quality * docs: document voice encryption modes * chore: unused imports * chore: update getrandom version to match wasm version * chore: update on packet size FIXME * drop buf asap * Okay can't do that actually * tests: add nonce test * normal tests work? * docs: fix doc warning, fix incorrect refrences to 'webrtc' * chore: json isn't a doc test * tests: better gateway auth test * testing tests * update voice heartbeat, fix the new test issue * committed too much * fix: unused import * fix: use ip discovery address as string, not as Vec * chore: less obnoxious logging * chore: better unimplemented voice modes handling * chore: remove unused variable * chore: use matches macro * add voice examples, make gateway ones clearer * rename voice example * chore: remove unused VoiceHandler * fix: implement gateway Reconnect and InvalidSession * Typo Co-authored-by: Flori <39242991+bitfl0wer@users.noreply.github.com> * Fix a bunch of typos Co-authored-by: Flori <39242991+bitfl0wer@users.noreply.github.com> * fix: error handling while loading native certs * fix: guh * use be for nonce bytes * fix: refactor gw and vgw closures * remove outdated docs --------- Co-authored-by: Flori <39242991+bitfl0wer@users.noreply.github.com> --- .github/workflows/build_and_test.yml | 4 +- Cargo.lock | 131 +++++++ Cargo.toml | 6 + README.md | 2 +- examples/gateway_observers.rs | 16 +- examples/gateway_simple.rs | 17 +- examples/voice_simple/Cargo.toml | 13 + examples/voice_simple/src/main.rs | 311 +++++++++++++++ src/api/guilds/messages.rs | 2 +- src/errors.rs | 58 ++- src/gateway/backends/tungstenite.rs | 12 +- src/gateway/events.rs | 2 + src/gateway/gateway.rs | 222 +++-------- src/gateway/handle.rs | 2 +- src/gateway/heartbeat.rs | 11 +- src/gateway/mod.rs | 8 +- src/lib.rs | 5 +- src/types/entities/voice_state.rs | 1 + src/types/events/invalid_session.rs | 17 + src/types/events/mod.rs | 8 +- src/types/events/reconnect.rs | 12 + src/types/events/voice.rs | 5 +- .../events/voice_gateway/client_connect.rs | 40 ++ .../events/voice_gateway/client_disconnect.rs | 18 + src/types/events/voice_gateway/hello.rs | 24 ++ .../{webrtc => voice_gateway}/identify.rs | 21 +- .../events/voice_gateway/media_sink_wants.rs | 18 + src/types/events/voice_gateway/mod.rs | 170 +++++++++ .../events/{webrtc => voice_gateway}/ready.rs | 27 +- .../events/voice_gateway/select_protocol.rs | 52 +++ .../voice_gateway/session_description.rs | 39 ++ src/types/events/voice_gateway/speaking.rs | 52 +++ .../events/voice_gateway/ssrc_definition.rs | 53 +++ .../voice_gateway/voice_backend_version.rs | 21 ++ src/voice.rs | 6 - src/voice/crypto.rs | 90 +++++ src/voice/gateway/backends/mod.rs | 27 ++ src/voice/gateway/backends/tungstenite.rs | 77 ++++ src/voice/gateway/backends/wasm.rs | 52 +++ src/voice/gateway/events.rs | 28 ++ src/voice/gateway/gateway.rs | 349 +++++++++++++++++ src/voice/gateway/handle.rs | 105 ++++++ src/voice/gateway/heartbeat.rs | 174 +++++++++ src/voice/gateway/message.rs | 46 +++ .../events/webrtc => voice/gateway}/mod.rs | 14 +- src/voice/mod.rs | 17 + src/voice/udp/backends/mod.rs | 16 + src/voice/udp/backends/tokio.rs | 37 ++ src/voice/udp/events.rs | 25 ++ src/voice/udp/handle.rs | 256 +++++++++++++ src/voice/udp/handler.rs | 356 ++++++++++++++++++ src/voice/udp/mod.rs | 18 + src/voice/voice_data.rs | 25 ++ tests/gateway.rs | 50 ++- 54 files changed, 2942 insertions(+), 226 deletions(-) create mode 100644 examples/voice_simple/Cargo.toml create mode 100644 examples/voice_simple/src/main.rs create mode 100644 src/types/events/invalid_session.rs create mode 100644 src/types/events/reconnect.rs create mode 100644 src/types/events/voice_gateway/client_connect.rs create mode 100644 src/types/events/voice_gateway/client_disconnect.rs create mode 100644 src/types/events/voice_gateway/hello.rs rename src/types/events/{webrtc => voice_gateway}/identify.rs (50%) create mode 100644 src/types/events/voice_gateway/media_sink_wants.rs create mode 100644 src/types/events/voice_gateway/mod.rs rename src/types/events/{webrtc => voice_gateway}/ready.rs (52%) create mode 100644 src/types/events/voice_gateway/select_protocol.rs create mode 100644 src/types/events/voice_gateway/session_description.rs create mode 100644 src/types/events/voice_gateway/speaking.rs create mode 100644 src/types/events/voice_gateway/ssrc_definition.rs create mode 100644 src/types/events/voice_gateway/voice_backend_version.rs delete mode 100644 src/voice.rs create mode 100644 src/voice/crypto.rs create mode 100644 src/voice/gateway/backends/mod.rs create mode 100644 src/voice/gateway/backends/tungstenite.rs create mode 100644 src/voice/gateway/backends/wasm.rs create mode 100644 src/voice/gateway/events.rs create mode 100644 src/voice/gateway/gateway.rs create mode 100644 src/voice/gateway/handle.rs create mode 100644 src/voice/gateway/heartbeat.rs create mode 100644 src/voice/gateway/message.rs rename src/{types/events/webrtc => voice/gateway}/mod.rs (52%) create mode 100644 src/voice/mod.rs create mode 100644 src/voice/udp/backends/mod.rs create mode 100644 src/voice/udp/backends/tokio.rs create mode 100644 src/voice/udp/events.rs create mode 100644 src/voice/udp/handle.rs create mode 100644 src/voice/udp/handler.rs create mode 100644 src/voice/udp/mod.rs create mode 100644 src/voice/voice_data.rs diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml index 4364550..4c38d19 100644 --- a/.github/workflows/build_and_test.yml +++ b/.github/workflows/build_and_test.yml @@ -100,7 +100,7 @@ jobs: rustup target add wasm32-unknown-unknown curl -L --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/cargo-bins/cargo-binstall/main/install-from-binstall-release.sh | bash cargo binstall --no-confirm wasm-bindgen-cli --version "0.2.88" --force - GECKODRIVER=$(which geckodriver) cargo test --target wasm32-unknown-unknown --no-default-features --features="client, rt" + GECKODRIVER=$(which geckodriver) cargo test --target wasm32-unknown-unknown --no-default-features --features="client, rt, voice_gateway" wasm-chrome: runs-on: macos-latest steps: @@ -128,4 +128,4 @@ jobs: rustup target add wasm32-unknown-unknown curl -L --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/cargo-bins/cargo-binstall/main/install-from-binstall-release.sh | bash cargo binstall --no-confirm wasm-bindgen-cli --version "0.2.88" --force - CHROMEDRIVER=$(which chromedriver) cargo test --target wasm32-unknown-unknown --no-default-features --features="client, rt" \ No newline at end of file + CHROMEDRIVER=$(which chromedriver) cargo test --target wasm32-unknown-unknown --no-default-features --features="client, rt, voice_gateway" diff --git a/Cargo.lock b/Cargo.lock index 9bd7e74..456ae43 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,6 +17,16 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "aead" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" +dependencies = [ + "crypto-common", + "generic-array", +] + [[package]] name = "ahash" version = "0.8.7" @@ -206,7 +216,9 @@ dependencies = [ "bitflags 2.4.1", "chorus-macros", "chrono", + "crypto_secretbox", "custom_error", + "discortp", "futures-util", "getrandom", "hostname", @@ -264,6 +276,17 @@ dependencies = [ "windows-targets 0.48.5", ] +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", + "zeroize", +] + [[package]] name = "console_error_panic_hook" version = "0.1.7" @@ -342,9 +365,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ "generic-array", + "rand_core", "typenum", ] +[[package]] +name = "crypto_secretbox" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d6cf87adf719ddf43a805e92c6870a531aedda35ff640442cbaf8674e141e1" +dependencies = [ + "aead", + "cipher", + "generic-array", + "poly1305", + "salsa20", + "subtle", + "zeroize", +] + [[package]] name = "custom_error" version = "1.9.2" @@ -425,6 +464,16 @@ dependencies = [ "subtle", ] +[[package]] +name = "discortp" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524b9439c09174aede2c88d58cfc6b83575b06569d1af4d07562f76595b2896b" +dependencies = [ + "pnet_macros", + "pnet_macros_support", +] + [[package]] name = "dotenvy" version = "0.15.7" @@ -643,6 +692,7 @@ checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", + "zeroize", ] [[package]] @@ -923,6 +973,15 @@ dependencies = [ "serde", ] +[[package]] +name = "inout" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +dependencies = [ + "generic-array", +] + [[package]] name = "ipnet" version = "2.9.0" @@ -1123,6 +1182,12 @@ dependencies = [ "libc", ] +[[package]] +name = "no-std-net" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43794a0ace135be66a25d3ae77d41b91615fb68ae937f904090203e81f755b65" + [[package]] name = "nom" version = "7.1.3" @@ -1217,6 +1282,12 @@ version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +[[package]] +name = "opaque-debug" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" + [[package]] name = "openssl" version = "0.10.62" @@ -1363,6 +1434,36 @@ version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69d3587f8a9e599cc7ec2c00e331f71c4e69a5f9a4b8a6efd5b07466b9736f9a" +[[package]] +name = "pnet_base" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9d3a993d49e5fd5d4d854d6999d4addca1f72d86c65adf224a36757161c02b6" +dependencies = [ + "no-std-net", +] + +[[package]] +name = "pnet_macros" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48dd52a5211fac27e7acb14cfc9f30ae16ae0e956b7b779c8214c74559cef4c3" +dependencies = [ + "proc-macro2", + "quote", + "regex", + "syn 1.0.109", +] + +[[package]] +name = "pnet_macros_support" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89de095dc7739349559913aed1ef6a11e73ceade4897dadc77c5e09de6740750" +dependencies = [ + "pnet_base", +] + [[package]] name = "poem" version = "1.3.59" @@ -1406,6 +1507,17 @@ dependencies = [ "syn 2.0.48", ] +[[package]] +name = "poly1305" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" +dependencies = [ + "cpufeatures", + "opaque-debug", + "universal-hash", +] + [[package]] name = "powerfmt" version = "0.2.0" @@ -1688,6 +1800,15 @@ version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" +[[package]] +name = "salsa20" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97a22f5af31f73a954c10289c93e8a50cc23d971e80ee446f1f6f7137a088213" +dependencies = [ + "cipher", +] + [[package]] name = "schannel" version = "0.1.23" @@ -2526,6 +2647,16 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e" +[[package]] +name = "universal-hash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" +dependencies = [ + "crypto-common", + "subtle", +] + [[package]] name = "untrusted" version = "0.7.1" diff --git a/Cargo.toml b/Cargo.toml index ac9ea55..45060d4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,6 +17,9 @@ backend = ["dep:poem", "dep:sqlx"] rt-multi-thread = ["tokio/rt-multi-thread"] rt = ["tokio/rt"] client = [] +voice = ["voice_udp", "voice_gateway"] +voice_udp = ["dep:discortp", "dep:crypto_secretbox"] +voice_gateway = [] [dependencies] tokio = { version = "1.35.1", features = ["macros", "sync"] } @@ -50,6 +53,8 @@ sqlx = { version = "0.7.3", features = [ "runtime-tokio-native-tls", "any", ], optional = true } +discortp = { version = "0.5.0", optional = true, features = ["rtp", "discord", "demux"] } +crypto_secretbox = { version = "0.1.1", optional = true } rand = "0.8.5" [target.'cfg(not(target_arch = "wasm32"))'.dependencies] @@ -61,6 +66,7 @@ tokio-tungstenite = { version = "0.20.1", features = [ ] } native-tls = "0.2.11" hostname = "0.3.1" +getrandom = { version = "0.2.12" } [target.'cfg(target_arch = "wasm32")'.dependencies] getrandom = { version = "0.2.12", features = ["js"] } diff --git a/README.md b/README.md index 376cf79..f1ee7e8 100644 --- a/README.md +++ b/README.md @@ -119,7 +119,7 @@ like "proxy connection checking" are already disabled on this version, which oth ### wasm To test for wasm, you will need to `cargo install wasm-pack`. You can then run -`wasm-pack test -- --headless -- --target wasm32-unknown-unknown --features="rt, client" --no-default-features` +`wasm-pack test -- --headless -- --target wasm32-unknown-unknown --features="rt, client, voice_gateway" --no-default-features` to run the tests for wasm. ## Versioning diff --git a/examples/gateway_observers.rs b/examples/gateway_observers.rs index 01dc854..0f15759 100644 --- a/examples/gateway_observers.rs +++ b/examples/gateway_observers.rs @@ -2,6 +2,15 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. +// This example showcase how to properly use gateway observers. +// +// To properly run it, you will need to change the token below. + +const TOKEN: &str = ""; + +/// Find the gateway websocket url of the server we want to connect to +const GATEWAY_URL: &str = "wss://gateway.old.server.spacebar.chat/"; + use async_trait::async_trait; use chorus::gateway::Gateway; use chorus::{ @@ -36,11 +45,10 @@ impl Observer for ExampleObserver { #[tokio::main(flavor = "current_thread")] async fn main() { - // Find the gateway websocket url of the server we want to connect to - let websocket_url_spacebar = "wss://gateway.old.server.spacebar.chat/".to_string(); + let gateway_websocket_url = GATEWAY_URL.to_string(); // Initiate the gateway connection - let gateway = Gateway::spawn(websocket_url_spacebar).await.unwrap(); + let gateway = Gateway::spawn(gateway_websocket_url).await.unwrap(); // Create an instance of our observer let observer = ExampleObserver {}; @@ -59,7 +67,7 @@ async fn main() { .subscribe(shared_observer); // Authenticate so we will receive any events - let token = "SecretToken".to_string(); + let token = TOKEN.to_string(); let mut identify = GatewayIdentifyPayload::common(); identify.token = token; gateway.send_identify(identify).await; diff --git a/examples/gateway_simple.rs b/examples/gateway_simple.rs index 60d7000..e8ff59a 100644 --- a/examples/gateway_simple.rs +++ b/examples/gateway_simple.rs @@ -2,6 +2,16 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. +// This example showcases how to initiate a gateway connection manually +// (e. g. not through ChorusUser) +// +// To properly run it, you will need to modify the token below. + +const TOKEN: &str = ""; + +/// Find the gateway websocket url of the server we want to connect to +const GATEWAY_URL: &str = "wss://gateway.old.server.spacebar.chat/"; + use std::time::Duration; use chorus::gateway::Gateway; @@ -15,16 +25,15 @@ use wasmtimer::tokio::sleep; /// This example creates a simple gateway connection and a session with an Identify event #[tokio::main(flavor = "current_thread")] async fn main() { - // Find the gateway websocket url of the server we want to connect to - let websocket_url_spacebar = "wss://gateway.old.server.spacebar.chat/".to_string(); + let gateway_websocket_url = GATEWAY_URL.to_string(); // Initiate the gateway connection, starting a listener in one thread and a heartbeat handler in another - let gateway = Gateway::spawn(websocket_url_spacebar).await.unwrap(); + let gateway = Gateway::spawn(gateway_websocket_url).await.unwrap(); // At this point, we are connected to the server and are sending heartbeats, however we still haven't authenticated // Get a token for an account on the server - let token = "SecretToken".to_string(); + let token = TOKEN.to_string(); // Create an identify event // An Identify event is how the server authenticates us and gets info about our os and browser, along with our intents / capabilities diff --git a/examples/voice_simple/Cargo.toml b/examples/voice_simple/Cargo.toml new file mode 100644 index 0000000..dc488e6 --- /dev/null +++ b/examples/voice_simple/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "voice_simple" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +async-trait = "*" +chorus = { path = "../../", features = ["rt", "client", "voice"] } +tokio = { version = "*", features = ["full"] } +simplelog = "*" +log = "*" diff --git a/examples/voice_simple/src/main.rs b/examples/voice_simple/src/main.rs new file mode 100644 index 0000000..a6f77db --- /dev/null +++ b/examples/voice_simple/src/main.rs @@ -0,0 +1,311 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +// This example showcases how to use the voice udp channel. +// +// To use this to properly communicate with voice, you will need to bring your own opus bindings +// along with potentially sending some other events, like Speaking +// +// To properly run this example, you will need to change some values below, +// like the token, guild and channel ids. + +const TOKEN: &str = ""; + +const VOICE_GUILD_ID: Option = None; +const VOICE_CHANNEL_ID: Option = Some(Snowflake(0_u64)); + +const GATEWAY_URL: &str = "wss://gateway.discord.gg"; + +use async_trait::async_trait; +use simplelog::{TermLogger, Config, WriteLogger}; +use std::{net::SocketAddrV4, sync::Arc, fs::File, time::Duration}; + +use chorus::{ + gateway::{Observer, Gateway}, + types::{ + GatewayReady, SelectProtocol, SelectProtocolData, SessionDescription, Snowflake, Speaking, + SpeakingBitflags, SsrcDefinition, VoiceEncryptionMode, VoiceIdentify, VoiceProtocol, + VoiceReady, VoiceServerUpdate, GatewayIdentifyPayload, UpdateVoiceState, + }, + voice::{ + gateway::{VoiceGateway, VoiceGatewayHandle}, + udp::{UdpHandle, UdpHandler}, + voice_data::VoiceData, + }, +}; +use log::{info, LevelFilter}; +use tokio::sync::{Mutex, RwLock}; + +extern crate chorus; +extern crate tokio; + +/// Handles in between connections between the gateway and UDP modules +#[derive(Debug, Clone)] +pub struct VoiceHandler { + pub voice_gateway_connection: Arc>>, + pub voice_udp_connection: Arc>>, + pub data: Arc>, +} + +impl VoiceHandler { + /// Creates a new [VoiceHandler], only initializing the data + pub fn new() -> VoiceHandler { + Self { + data: Arc::new(RwLock::new(VoiceData::default())), + voice_gateway_connection: Arc::new(Mutex::new(None)), + voice_udp_connection: Arc::new(Mutex::new(None)), + } + } +} + +impl Default for VoiceHandler { + fn default() -> Self { + Self::new() + } +} + +#[async_trait] +// On [VoiceServerUpdate] we get our starting data and URL for the voice gateway server. +impl Observer for VoiceHandler { + async fn update(&self, data: &VoiceServerUpdate) { + let mut data_lock = self.data.write().await; + + data_lock.server_data = Some(data.clone()); + let user_id = data_lock.user_id; + let session_id = data_lock.session_id.clone(); + + drop(data_lock); + + // Create and connect to the voice gateway + let voice_gateway_handle = VoiceGateway::spawn(data.endpoint.clone().unwrap()) + .await + .unwrap(); + + let server_id: Snowflake; + + if data.guild_id.is_some() { + server_id = data.guild_id.unwrap(); + } else { + server_id = data.channel_id.unwrap(); + } + + let voice_identify = VoiceIdentify { + server_id, + user_id, + session_id, + token: data.token.clone(), + video: Some(false), + }; + + voice_gateway_handle.send_identify(voice_identify).await; + + let cloned_gateway_handle = voice_gateway_handle.clone(); + + let mut voice_events = cloned_gateway_handle.events.lock().await; + + let self_reference = Arc::new(self.clone()); + + // Subscribe to voice gateway events + voice_events.voice_ready.subscribe(self_reference.clone()); + voice_events + .session_description + .subscribe(self_reference.clone()); + voice_events.speaking.subscribe(self_reference.clone()); + voice_events + .ssrc_definition + .subscribe(self_reference.clone()); + + *self.voice_gateway_connection.lock().await = Some(voice_gateway_handle); + } +} + +#[async_trait] +// On [VoiceReady] we get info for establishing a UDP connection, and we immediately need said UDP +// connection for ip discovery. +impl Observer for VoiceHandler { + async fn update(&self, data: &VoiceReady) { + let mut data_lock = self.data.write().await; + + data_lock.ready_data = Some(data.clone()); + + drop(data_lock); + + // Create a udp connection and perform ip discovery + let udp_handle = UdpHandler::spawn( + self.data.clone(), + std::net::SocketAddr::V4(SocketAddrV4::new(data.ip, data.port)), + data.ssrc, + ) + .await + .unwrap(); + + // Subscribe ourself to receiving rtp data + udp_handle + .events + .lock() + .await + .rtp + .subscribe(Arc::new(self.clone())); + + let ip_discovery = self.data.read().await.ip_discovery.clone().unwrap(); + + *self.voice_udp_connection.lock().await = Some(udp_handle.clone()); + + let string_ip_address = + String::from_utf8(ip_discovery.address).expect("Ip discovery gave non string ip"); + + // Send a select protocol, which tells the server where we'll be receiving data and what + // mode to encrypt data in + self.voice_gateway_connection + .lock() + .await + .clone() + .unwrap() + .send_select_protocol(SelectProtocol { + protocol: VoiceProtocol::Udp, + data: SelectProtocolData { + address: string_ip_address, + port: ip_discovery.port, + // There are several other voice encryption modes available, though not all are + // implemented in chorus + mode: VoiceEncryptionMode::Xsalsa20Poly1305, + }, + ..Default::default() + }) + .await; + } +} + +#[async_trait] +// Session descryption gives us final info regarding codecs and our encryption key +impl Observer for VoiceHandler { + async fn update(&self, data: &SessionDescription) { + let mut data_write = self.data.write().await; + + data_write.session_description = Some(data.clone()); + + drop(data_write); + } +} + +#[async_trait] +// Ready is used just to obtain some info, like the user id and session id +impl Observer for VoiceHandler { + async fn update(&self, data: &GatewayReady) { + let mut lock = self.data.write().await; + lock.user_id = data.user.id; + lock.session_id = data.session_id.clone(); + drop(lock); + } +} + +#[async_trait] +// This is the received voice data +impl Observer for VoiceHandler { + async fn update(&self, data: &chorus::voice::discortp::rtp::Rtp) { + info!( + "Received decrypted voice data! {:?} (SSRC: {})", + data.payload.clone(), + data.ssrc, + ); + } +} + +#[async_trait] +// This event gives extra info about who is speaking +impl Observer for VoiceHandler { + async fn update(&self, data: &Speaking) { + println!( + "Received Speaking! (SRRC: {}, flags: {:?})", + data.ssrc, + SpeakingBitflags::from_bits(data.speaking).unwrap() + ); + } +} + +#[async_trait] +// This event gives some info about which user has which ssrc +impl Observer for VoiceHandler { + async fn update(&self, data: &SsrcDefinition) { + println!( + "Received SSRC Definition! (User {} has audio ssrc {})", + data.user_id.unwrap(), + data.audio_ssrc + ); + } +} + +#[tokio::main] +async fn main() { + simplelog::CombinedLogger::init(vec![ + TermLogger::new( + LevelFilter::Debug, + Config::default(), + simplelog::TerminalMode::Mixed, + simplelog::ColorChoice::Auto, + ), + WriteLogger::new( + LevelFilter::Trace, + Config::default(), + File::create("latest.log").unwrap(), + ), + ]) + .unwrap(); + + let gateway = Gateway::spawn(GATEWAY_URL.to_string()) + .await + .unwrap(); + + let mut identify = GatewayIdentifyPayload::common(); + identify.token = TOKEN.to_string(); + + gateway.send_identify(identify).await; + + let voice_handler = Arc::new(VoiceHandler::new()); + + // Voice handler needs voice server update + gateway + .events + .lock() + .await + .voice + .server_update + .subscribe(voice_handler.clone()); + + // It also needs a bit of the data in ready + gateway + .events + .lock() + .await + .session + .ready + .subscribe(voice_handler.clone()); + + // Data which channel to update the local user to be joined into. + // + // guild_id and channel_id can be some to join guild voice channels + // + // guild_id can be none and channel id some to join dm calls + // + // both can be none to leave all voice channels + let voice_state_update = UpdateVoiceState { + guild_id: VOICE_GUILD_ID, + channel_id: VOICE_CHANNEL_ID, + self_mute: false, + self_deaf: false, + ..Default::default() + }; + + gateway.send_update_voice_state(voice_state_update).await; + + loop { + tokio::time::sleep(Duration::from_millis(1000)).await; + + // Potentially send some data here + /*let voice_udp_option = voice_handler.voice_udp_connection.lock().await.clone(); + if voice_udp_option.is_some() { + voice_udp_option.unwrap().send_opus_data(0, vec![1, 2, 3, 4, 5]).await.unwrap(); + }*/ + } +} diff --git a/src/api/guilds/messages.rs b/src/api/guilds/messages.rs index 31a62e1..b45f3e3 100644 --- a/src/api/guilds/messages.rs +++ b/src/api/guilds/messages.rs @@ -13,7 +13,7 @@ impl Guild { /// permission to be present on the current user. /// /// If the guild/channel you are searching is not yet indexed, the endpoint will return a 202 accepted response. - /// In this case, the method will return a [`ChorusError::InvalidResponse`] error. + /// In this case, the method will return a [`ChorusError::InvalidResponse`](crate::errors::ChorusError::InvalidResponse) error. /// /// # Reference: /// See diff --git a/src/errors.rs b/src/errors.rs index fffe566..a2f174d 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -67,7 +67,7 @@ custom_error! { } custom_error! { - /// For errors we receive from the gateway, see https://discord-userdoccers.vercel.app/topics/opcodes-and-status-codes#gateway-close-event-codes; + /// For errors we receive from the gateway, see ; /// /// Supposed to be sent as numbers, though they are sent as string most of the time? /// @@ -100,3 +100,59 @@ custom_error! { } impl WebSocketEvent for GatewayError {} + +custom_error! { + /// Voice Gateway errors + /// + /// Similar to [GatewayError]. + /// + /// See ; + #[derive(Clone, Default, PartialEq, Eq)] + pub VoiceGatewayError + // Errors we receive + #[default] + UnknownOpcode = "You sent an invalid opcode", + FailedToDecodePayload = "You sent an invalid payload in your identifying to the (Voice) Gateway", + NotAuthenticated = "You sent a payload before identifying with the (Voice) Gateway", + AuthenticationFailed = "The token you sent in your identify payload is incorrect", + AlreadyAuthenticated = "You sent more than one identify payload", + SessionNoLongerValid = "Your session is no longer valid", + SessionTimeout = "Your session has timed out", + ServerNotFound = "We can't find the server you're trying to connect to", + UnknownProtocol = "We didn't recognize the protocol you sent", + Disconnected = "Channel was deleted, you were kicked, voice server changed, or the main gateway session was dropped. Should not reconnect.", + VoiceServerCrashed = "The server crashed, try resuming", + UnknownEncryptionMode = "Server failed to decrypt data", + + // Errors when initiating a gateway connection + CannotConnect{error: String} = "Cannot connect due to a tungstenite error: {error}", + NonHelloOnInitiate{opcode: u8} = "Received non hello on initial gateway connection ({opcode}), something is definitely wrong", + + // Other misc errors + UnexpectedOpcodeReceived{opcode: u8} = "Received an opcode we weren't expecting to receive: {opcode}", +} + +impl WebSocketEvent for VoiceGatewayError {} + +custom_error! { + /// Voice UDP errors. + #[derive(Clone, PartialEq, Eq)] + pub VoiceUdpError + + // General errors + BrokenSocket{error: String} = "Could not write / read from UDP socket: {error}", + NoData = "We have not set received the necessary data to perform this operation.", + + // Encryption errors + EncryptionModeNotImplemented{encryption_mode: String} = "Voice encryption mode {encryption_mode} is not yet implemented.", + NoKey = "Tried to encrypt / decrypt rtp data, but no key has been received yet", + FailedEncryption = "Tried to encrypt rtp data, but failed. Most likely this is an issue chorus' nonce generation. Please open an issue on the chorus github: https://github.com/polyphony-chat/chorus/issues/new", + FailedDecryption = "Tried to decrypt rtp data, but failed. Most likely this is an issue chorus' nonce generation. Please open an issue on the chorus github: https://github.com/polyphony-chat/chorus/issues/new", + FailedNonceGeneration{error: String} = "Tried to generate nonce, but failed due to error: {error}.", + + // Errors when initiating a socket connection + CannotBind{error: String} = "Cannot bind socket due to a UDP error: {error}", + CannotConnect{error: String} = "Cannot connect due to a UDP error: {error}", +} + +impl WebSocketEvent for VoiceUdpError {} diff --git a/src/gateway/backends/tungstenite.rs b/src/gateway/backends/tungstenite.rs index fe537d3..a9f9f64 100644 --- a/src/gateway/backends/tungstenite.rs +++ b/src/gateway/backends/tungstenite.rs @@ -27,8 +27,16 @@ impl TungsteniteBackend { websocket_url: &str, ) -> Result<(TungsteniteSink, TungsteniteStream), crate::errors::GatewayError> { let mut roots = rustls::RootCertStore::empty(); - for cert in rustls_native_certs::load_native_certs().expect("could not load platform certs") - { + let certs = rustls_native_certs::load_native_certs(); + + if let Err(e) = certs { + log::error!("Failed to load platform native certs! {:?}", e); + return Err(GatewayError::CannotConnect { + error: format!("{:?}", e), + }); + } + + for cert in certs.unwrap() { roots.add(&rustls::Certificate(cert.0)).unwrap(); } let (websocket_stream, _) = match connect_async_tls_with_config( diff --git a/src/gateway/events.rs b/src/gateway/events.rs index cf074bd..8d38cca 100644 --- a/src/gateway/events.rs +++ b/src/gateway/events.rs @@ -46,6 +46,8 @@ pub struct Session { pub ready: GatewayEvent, pub ready_supplemental: GatewayEvent, pub replace: GatewayEvent, + pub reconnect: GatewayEvent, + pub invalid: GatewayEvent, } #[derive(Default, Debug)] diff --git a/src/gateway/gateway.rs b/src/gateway/gateway.rs index 3011857..dabfeb6 100644 --- a/src/gateway/gateway.rs +++ b/src/gateway/gateway.rs @@ -9,13 +9,14 @@ use log::*; #[cfg(not(target_arch = "wasm32"))] use tokio::task; -use self::event::Events; +use super::events::Events; use super::*; use super::{Sink, Stream}; use crate::types::{ self, AutoModerationRule, AutoModerationRuleUpdate, Channel, ChannelCreate, ChannelDelete, - ChannelUpdate, Guild, GuildRoleCreate, GuildRoleUpdate, JsonField, RoleObject, SourceUrlField, - ThreadUpdate, UpdateMessage, WebSocketEvent, + ChannelUpdate, GatewayInvalidSession, GatewayReconnect, Guild, GuildRoleCreate, + GuildRoleUpdate, JsonField, RoleObject, SourceUrlField, ThreadUpdate, UpdateMessage, + WebSocketEvent, }; #[derive(Debug)] @@ -25,6 +26,7 @@ pub struct Gateway { websocket_send: Arc>, websocket_receive: Stream, kill_send: tokio::sync::broadcast::Sender<()>, + kill_receive: tokio::sync::broadcast::Receiver<()>, store: Arc>>>>, url: String, } @@ -74,6 +76,7 @@ impl Gateway { websocket_send: shared_websocket_send.clone(), websocket_receive, kill_send: kill_send.clone(), + kill_receive: kill_send.subscribe(), store: store.clone(), url: websocket_url.clone(), }; @@ -98,14 +101,21 @@ impl Gateway { } /// The main gateway listener task; - /// - /// Can only be stopped by closing the websocket, cannot be made to listen for kill pub async fn gateway_listen_task(&mut self) { loop { - let msg = self.websocket_receive.next().await; + let msg; + + tokio::select! { + Ok(_) = self.kill_receive.recv() => { + log::trace!("GW: Closing listener task"); + break; + } + message = self.websocket_receive.next() => { + msg = message; + } + } // PRETTYFYME: Remove inline conditional compiling - // This if chain can be much better but if let is unstable on stable rust #[cfg(not(target_arch = "wasm32"))] if let Some(Ok(message)) = msg { self.handle_message(message.into()).await; @@ -339,10 +349,42 @@ impl Gateway { .unwrap(); } GATEWAY_RECONNECT => { - todo!() + trace!("GW: Received Reconnect"); + + let reconnect = GatewayReconnect {}; + + self.events + .lock() + .await + .session + .reconnect + .notify(reconnect) + .await; } GATEWAY_INVALID_SESSION => { - todo!() + trace!("GW: Received Invalid Session"); + + let mut resumable: bool = false; + + if let Some(raw_value) = gateway_payload.event_data { + if let Ok(deserialized) = serde_json::from_str(raw_value.get()) { + resumable = deserialized; + } else { + warn!("Failed to parse part of INVALID_SESSION ('{}' as bool), assuming non-resumable", raw_value.get()); + } + } else { + warn!("Failed to parse part of INVALID_SESSION ('d' missing), assuming non-resumable"); + } + + let invalid_session = GatewayInvalidSession { resumable }; + + self.events + .lock() + .await + .session + .invalid + .notify(invalid_session) + .await; } // Starts our heartbeat // We should have already handled this in gateway init @@ -398,165 +440,3 @@ impl Gateway { } } } - -pub mod event { - use super::*; - - #[derive(Default, Debug)] - pub struct Events { - pub application: Application, - pub auto_moderation: AutoModeration, - pub session: Session, - pub message: Message, - pub user: User, - pub relationship: Relationship, - pub channel: Channel, - pub thread: Thread, - pub guild: Guild, - pub invite: Invite, - pub integration: Integration, - pub interaction: Interaction, - pub stage_instance: StageInstance, - pub call: Call, - pub voice: Voice, - pub webhooks: Webhooks, - pub gateway_identify_payload: GatewayEvent, - pub gateway_resume: GatewayEvent, - pub error: GatewayEvent, - } - - #[derive(Default, Debug)] - pub struct Application { - pub command_permissions_update: GatewayEvent, - } - - #[derive(Default, Debug)] - pub struct AutoModeration { - pub rule_create: GatewayEvent, - pub rule_update: GatewayEvent, - pub rule_delete: GatewayEvent, - pub action_execution: GatewayEvent, - } - - #[derive(Default, Debug)] - pub struct Session { - pub ready: GatewayEvent, - pub ready_supplemental: GatewayEvent, - pub replace: GatewayEvent, - } - - #[derive(Default, Debug)] - pub struct StageInstance { - pub create: GatewayEvent, - pub update: GatewayEvent, - pub delete: 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 ack: GatewayEvent, - } - - #[derive(Default, Debug)] - pub struct User { - pub update: GatewayEvent, - pub guild_settings_update: GatewayEvent, - pub presence_update: GatewayEvent, - pub typing_start: GatewayEvent, - } - - #[derive(Default, Debug)] - pub struct Relationship { - pub add: GatewayEvent, - pub remove: GatewayEvent, - } - - #[derive(Default, Debug)] - pub struct Channel { - pub create: GatewayEvent, - pub update: GatewayEvent, - pub unread_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, - } - - #[derive(Default, Debug)] - pub struct Guild { - pub create: GatewayEvent, - pub update: GatewayEvent, - pub delete: GatewayEvent, - pub audit_log_entry_create: GatewayEvent, - pub ban_add: GatewayEvent, - pub ban_remove: GatewayEvent, - pub emojis_update: GatewayEvent, - pub stickers_update: GatewayEvent, - pub integrations_update: GatewayEvent, - pub member_add: GatewayEvent, - pub member_remove: GatewayEvent, - pub member_update: GatewayEvent, - pub members_chunk: GatewayEvent, - pub role_create: GatewayEvent, - pub role_update: GatewayEvent, - pub role_delete: GatewayEvent, - pub role_scheduled_event_create: GatewayEvent, - pub role_scheduled_event_update: GatewayEvent, - pub role_scheduled_event_delete: GatewayEvent, - pub role_scheduled_event_user_add: GatewayEvent, - pub role_scheduled_event_user_remove: GatewayEvent, - pub passive_update_v1: GatewayEvent, - } - - #[derive(Default, Debug)] - pub struct Invite { - pub create: GatewayEvent, - pub delete: GatewayEvent, - } - - #[derive(Default, Debug)] - pub struct Integration { - pub create: GatewayEvent, - pub update: GatewayEvent, - pub delete: GatewayEvent, - } - - #[derive(Default, Debug)] - pub struct Interaction { - pub create: GatewayEvent, - } - - #[derive(Default, Debug)] - pub struct Call { - pub create: GatewayEvent, - pub update: GatewayEvent, - pub delete: GatewayEvent, - } - - #[derive(Default, Debug)] - pub struct Voice { - pub state_update: GatewayEvent, - pub server_update: GatewayEvent, - } - - #[derive(Default, Debug)] - pub struct Webhooks { - pub update: GatewayEvent, - } -} diff --git a/src/gateway/handle.rs b/src/gateway/handle.rs index 7c05306..6af5f0d 100644 --- a/src/gateway/handle.rs +++ b/src/gateway/handle.rs @@ -7,7 +7,7 @@ use log::*; use std::fmt::Debug; -use super::{event::Events, *}; +use super::{events::Events, *}; use crate::types::{self, Composite}; /// Represents a handle to a Gateway connection. A Gateway connection will create observable diff --git a/src/gateway/heartbeat.rs b/src/gateway/heartbeat.rs index 82c1f8a..5dcc98d 100644 --- a/src/gateway/heartbeat.rs +++ b/src/gateway/heartbeat.rs @@ -26,7 +26,7 @@ use super::*; use crate::types; /// The amount of time we wait for a heartbeat ack before resending our heartbeat in ms -const HEARTBEAT_ACK_TIMEOUT: u64 = 2000; +pub const HEARTBEAT_ACK_TIMEOUT: u64 = 2000; /// Handles sending heartbeats to the gateway in another thread #[allow(dead_code)] // FIXME: Remove this, once HeartbeatHandler is used @@ -77,11 +77,6 @@ impl HeartbeatHandler { let mut last_seq_number: Option = None; loop { - if kill_receive.try_recv().is_ok() { - trace!("GW: Closing heartbeat task"); - break; - } - let timeout = if last_heartbeat_acknowledged { heartbeat_interval } else { @@ -115,6 +110,10 @@ impl HeartbeatHandler { } } } + Ok(_) = kill_receive.recv() => { + log::trace!("GW: Closing heartbeat task"); + break; + } } if should_send { diff --git a/src/gateway/mod.rs b/src/gateway/mod.rs index 01f2163..5a5881a 100644 --- a/src/gateway/mod.rs +++ b/src/gateway/mod.rs @@ -98,6 +98,12 @@ pub struct GatewayEvent { } impl GatewayEvent { + pub fn new() -> Self { + Self { + observers: Vec::new(), + } + } + /// Returns true if the GatewayEvent is observed by at least one Observer. pub fn is_observed(&self) -> bool { !self.observers.is_empty() @@ -120,7 +126,7 @@ impl GatewayEvent { } /// Notifies the observers of the GatewayEvent. - async fn notify(&self, new_event_data: T) { + pub(crate) async fn notify(&self, new_event_data: T) { for observer in &self.observers { observer.update(&new_event_data).await; } diff --git a/src/lib.rs b/src/lib.rs index 0ca4edf..6ffd8b8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -132,7 +132,10 @@ pub mod instance; #[cfg(feature = "client")] pub mod ratelimiter; pub mod types; -#[cfg(feature = "client")] +#[cfg(all( + feature = "client", + any(feature = "voice_udp", feature = "voice_gateway") +))] pub mod voice; #[derive(Clone, Default, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] diff --git a/src/types/entities/voice_state.rs b/src/types/entities/voice_state.rs index dc0047d..304b724 100644 --- a/src/types/entities/voice_state.rs +++ b/src/types/entities/voice_state.rs @@ -37,6 +37,7 @@ pub struct VoiceState { pub channel_id: Option, pub user_id: Snowflake, pub member: Option>, + /// Includes alphanumeric characters, not a snowflake pub session_id: String, pub token: Option, pub deaf: bool, diff --git a/src/types/events/invalid_session.rs b/src/types/events/invalid_session.rs new file mode 100644 index 0000000..ae61879 --- /dev/null +++ b/src/types/events/invalid_session.rs @@ -0,0 +1,17 @@ +use serde::{Deserialize, Serialize}; + +use super::WebSocketEvent; + +#[derive(Debug, Deserialize, Serialize, Default, Clone)] +/// Your session is now invalid. +/// +/// Either reauthenticate and reidentify or resume if possible. +/// +/// # Reference +/// See +pub struct GatewayInvalidSession { + #[serde(rename = "d")] + pub resumable: bool, +} + +impl WebSocketEvent for GatewayInvalidSession {} diff --git a/src/types/events/mod.rs b/src/types/events/mod.rs index a607f2c..6faafd1 100644 --- a/src/types/events/mod.rs +++ b/src/types/events/mod.rs @@ -14,12 +14,14 @@ pub use hello::*; pub use identify::*; pub use integration::*; pub use interaction::*; +pub use invalid_session::*; pub use invite::*; pub use lazy_request::*; pub use message::*; pub use passive_update::*; pub use presence::*; pub use ready::*; +pub use reconnect::*; pub use relationship::*; pub use request_members::*; pub use resume::*; @@ -28,8 +30,8 @@ pub use stage_instance::*; pub use thread::*; pub use user::*; pub use voice::*; +pub use voice_gateway::*; pub use webhooks::*; -pub use webrtc::*; #[cfg(feature = "client")] use super::Snowflake; @@ -60,12 +62,14 @@ mod hello; mod identify; mod integration; mod interaction; +mod invalid_session; mod invite; mod lazy_request; mod message; mod passive_update; mod presence; mod ready; +mod reconnect; mod relationship; mod request_members; mod resume; @@ -76,7 +80,7 @@ mod user; mod voice; mod webhooks; -mod webrtc; +mod voice_gateway; pub trait WebSocketEvent: Send + Sync + Debug {} diff --git a/src/types/events/reconnect.rs b/src/types/events/reconnect.rs new file mode 100644 index 0000000..d2751cc --- /dev/null +++ b/src/types/events/reconnect.rs @@ -0,0 +1,12 @@ +use serde::{Deserialize, Serialize}; + +use super::WebSocketEvent; + +#[derive(Debug, Deserialize, Serialize, Default, Clone)] +/// "The reconnect event is dispatched when a client should reconnect to the Gateway (and resume their existing session, if they have one). This event usually occurs during deploys to migrate sessions gracefully off old hosts" +/// +/// # Reference +/// See +pub struct GatewayReconnect {} + +impl WebSocketEvent for GatewayReconnect {} diff --git a/src/types/events/voice.rs b/src/types/events/voice.rs index 76687d4..0a4484f 100644 --- a/src/types/events/voice.rs +++ b/src/types/events/voice.rs @@ -38,7 +38,10 @@ impl WebSocketEvent for VoiceStateUpdate {} /// Received to indicate which voice endpoint, token and guild_id to use; pub struct VoiceServerUpdate { pub token: String, - pub guild_id: Snowflake, + /// The guild this voice server update is for + pub guild_id: Option, + /// The private channel this voice server update is for + pub channel_id: Option, pub endpoint: Option, } diff --git a/src/types/events/voice_gateway/client_connect.rs b/src/types/events/voice_gateway/client_connect.rs new file mode 100644 index 0000000..3929275 --- /dev/null +++ b/src/types/events/voice_gateway/client_connect.rs @@ -0,0 +1,40 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +use crate::types::{Snowflake, WebSocketEvent}; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Default, Deserialize, Serialize, Clone, PartialEq, Copy)] +/// Sent when another user connects to the voice server. +/// +/// Contains the user id and "flags". +/// +/// Not documented anywhere, if you know what this is, please reach out +/// +/// {"op":18,"d":{"user_id":"1234567890","flags":2}} +pub struct VoiceClientConnectFlags { + pub user_id: Snowflake, + // Likely some sort of bitflags + // + // Not always sent, sometimes null? + pub flags: Option, +} + +impl WebSocketEvent for VoiceClientConnectFlags {} + +#[derive(Debug, Default, Deserialize, Serialize, Clone, PartialEq, Copy)] +/// Sent when another user connects to the voice server. +/// +/// Contains the user id and "platform". +/// +/// Not documented anywhere, if you know what this is, please reach out +/// +/// {"op":20,"d":{"user_id":"1234567890","platform":0}} +pub struct VoiceClientConnectPlatform { + pub user_id: Snowflake, + // Likely an enum + pub platform: u8, +} + +impl WebSocketEvent for VoiceClientConnectPlatform {} diff --git a/src/types/events/voice_gateway/client_disconnect.rs b/src/types/events/voice_gateway/client_disconnect.rs new file mode 100644 index 0000000..cc1d949 --- /dev/null +++ b/src/types/events/voice_gateway/client_disconnect.rs @@ -0,0 +1,18 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +use crate::types::{Snowflake, WebSocketEvent}; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Default, Deserialize, Serialize, Clone, PartialEq, Copy)] +/// Sent when another user disconnects from the voice server. +/// +/// When received, the SSRC of the user should be discarded. +/// +/// See +pub struct VoiceClientDisconnection { + pub user_id: Snowflake, +} + +impl WebSocketEvent for VoiceClientDisconnection {} diff --git a/src/types/events/voice_gateway/hello.rs b/src/types/events/voice_gateway/hello.rs new file mode 100644 index 0000000..08bd09e --- /dev/null +++ b/src/types/events/voice_gateway/hello.rs @@ -0,0 +1,24 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +use crate::types::WebSocketEvent; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Default, Deserialize, Serialize, Clone, PartialEq, Copy)] +/// Contains info on how often the client should send heartbeats to the server; +/// +/// Differs from the normal hello data in that discord sends heartbeat interval as a float. +/// +/// See +pub struct VoiceHelloData { + /// The voice gateway version. + /// + /// Note: no idea why this is sent, we already specify the version when establishing a connection. + #[serde(rename = "v")] + pub version: u8, + /// How often a client should send heartbeats, in milliseconds + pub heartbeat_interval: f64, +} + +impl WebSocketEvent for VoiceHelloData {} diff --git a/src/types/events/webrtc/identify.rs b/src/types/events/voice_gateway/identify.rs similarity index 50% rename from src/types/events/webrtc/identify.rs rename to src/types/events/voice_gateway/identify.rs index f409362..d33cd40 100644 --- a/src/types/events/webrtc/identify.rs +++ b/src/types/events/voice_gateway/identify.rs @@ -6,17 +6,20 @@ use crate::types::{Snowflake, WebSocketEvent}; use serde::{Deserialize, Serialize}; #[derive(Debug, Deserialize, Serialize, Default, Clone, PartialEq, Eq)] -/// The identify payload for the webrtc stream; -/// Contains info to begin a webrtc connection; -/// See https://discord.com/developers/docs/topics/voice-connections#establishing-a-voice-websocket-connection-example-voice-identify-payload; +/// The identify payload for the voice gateway connection; +/// +/// Contains authentication info and context to authenticate to the voice gateway. +/// +/// See pub struct VoiceIdentify { - server_id: Snowflake, - user_id: Snowflake, - session_id: String, - token: String, + /// The ID of the guild or the private channel being connected to + pub server_id: Snowflake, + pub user_id: Snowflake, + pub session_id: String, + pub token: String, #[serde(skip_serializing_if = "Option::is_none")] - /// Undocumented field, but is also in discord client comms - video: Option, + pub video: Option, + // TODO: Add video streams } impl WebSocketEvent for VoiceIdentify {} diff --git a/src/types/events/voice_gateway/media_sink_wants.rs b/src/types/events/voice_gateway/media_sink_wants.rs new file mode 100644 index 0000000..1f79eda --- /dev/null +++ b/src/types/events/voice_gateway/media_sink_wants.rs @@ -0,0 +1,18 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +use crate::types::WebSocketEvent; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Default, Deserialize, Serialize, Clone, PartialEq, Copy)] +/// What does this do? +/// +/// {"op":15,"d":{"any":100}} +/// +/// Opcode from +pub struct VoiceMediaSinkWants { + pub any: u16, +} + +impl WebSocketEvent for VoiceMediaSinkWants {} diff --git a/src/types/events/voice_gateway/mod.rs b/src/types/events/voice_gateway/mod.rs new file mode 100644 index 0000000..0546d29 --- /dev/null +++ b/src/types/events/voice_gateway/mod.rs @@ -0,0 +1,170 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +use super::WebSocketEvent; +use serde::{Deserialize, Serialize}; +use serde_json::{value::RawValue, Value}; + +pub use client_connect::*; +pub use client_disconnect::*; +pub use hello::*; +pub use identify::*; +pub use media_sink_wants::*; +pub use ready::*; +pub use select_protocol::*; +pub use session_description::*; +pub use speaking::*; +pub use ssrc_definition::*; +pub use voice_backend_version::*; + +mod client_connect; +mod client_disconnect; +mod hello; +mod identify; +mod media_sink_wants; +mod ready; +mod select_protocol; +mod session_description; +mod speaking; +mod ssrc_definition; +mod voice_backend_version; + +#[derive(Debug, Default, Serialize, Clone)] +/// The payload used for sending events to the voice gateway. +/// +/// Similar to [VoiceGatewayReceivePayload], except we send a [Value] for d whilst we receive a [serde_json::value::RawValue] +pub struct VoiceGatewaySendPayload { + #[serde(rename = "op")] + pub op_code: u8, + + #[serde(rename = "d")] + pub data: Value, +} + +impl WebSocketEvent for VoiceGatewaySendPayload {} + +#[derive(Debug, Deserialize, Clone)] +/// The payload used for receiving events from the voice gateway. +/// +/// Note that this is similar to the regular gateway, except we no longer have s or t +/// +/// Similar to [VoiceGatewaySendPayload], except we send a [Value] for d whilst we receive a [serde_json::value::RawValue] +pub struct VoiceGatewayReceivePayload<'a> { + #[serde(rename = "op")] + pub op_code: u8, + + #[serde(borrow)] + #[serde(rename = "d")] + pub data: &'a RawValue, +} + +impl<'a> WebSocketEvent for VoiceGatewayReceivePayload<'a> {} + +/// The modes of encryption available in voice UDP connections; +/// +/// Not all encryption modes are implemented; it is generally recommended +/// to use either [[VoiceEncryptionMode::Xsalsa20Poly1305]] or +/// [[VoiceEncryptionMode::Xsalsa20Poly1305Suffix]] +/// +/// See and +#[derive(Debug, Default, Serialize, Deserialize, Clone, Copy, PartialEq, Eq)] +#[serde(rename_all = "snake_case")] +pub enum VoiceEncryptionMode { + #[default] + // Officially Documented + /// Use XSalsa20Poly1305 encryption, using the rtp header as a nonce. + /// + /// Fully implemented + Xsalsa20Poly1305, + /// Use XSalsa20Poly1305 encryption, using a random 24 byte suffix as a nonce. + /// + /// Fully implemented + Xsalsa20Poly1305Suffix, + /// Use XSalsa20Poly1305 encryption, using a 4 byte incremental value as a nonce. + /// + /// Fully implemented + Xsalsa20Poly1305Lite, + // Officially Undocumented + /// Not implemented yet, we have no idea what the rtpsize nonces are. + Xsalsa20Poly1305LiteRtpsize, + /// Not implemented yet, we have no idea what the nonce is. + AeadAes256Gcm, + /// Not implemented yet, we have no idea what the rtpsize nonces are. + AeadAes256GcmRtpsize, + /// Not implemented yet, we have no idea what the rtpsize nonces are. + AeadXchacha20Poly1305Rtpsize, +} + +impl VoiceEncryptionMode { + /// Returns whether this encryption mode uses Xsalsa20Poly1305 encryption. + pub fn is_xsalsa20_poly1305(&self) -> bool { + matches!( + *self, + VoiceEncryptionMode::Xsalsa20Poly1305 + | VoiceEncryptionMode::Xsalsa20Poly1305Lite + | VoiceEncryptionMode::Xsalsa20Poly1305Suffix + | VoiceEncryptionMode::Xsalsa20Poly1305LiteRtpsize + ) + } + + /// Returns whether this encryption mode uses AeadAes256Gcm encryption. + pub fn is_aead_aes256_gcm(&self) -> bool { + matches!( + *self, + VoiceEncryptionMode::AeadAes256Gcm | VoiceEncryptionMode::AeadAes256GcmRtpsize + ) + } + + /// Returns whether this encryption mode uses AeadXchacha20Poly1305 encryption. + pub fn is_aead_xchacha20_poly1305(&self) -> bool { + *self == VoiceEncryptionMode::AeadXchacha20Poly1305Rtpsize + } +} + +/// The possible audio codecs to use +#[derive(Debug, Default, Serialize, Deserialize, Clone, Copy, PartialEq, Eq)] +#[serde(rename_all = "lowercase")] +pub enum AudioCodec { + #[default] + Opus, +} + +/// The possible video codecs to use +#[derive(Debug, Default, Serialize, Deserialize, Clone, Copy, PartialEq, Eq)] +#[serde(rename_all = "UPPERCASE")] +pub enum VideoCodec { + #[default] + VP8, + VP9, + H264, +} + +// The various voice opcodes +pub const VOICE_IDENTIFY: u8 = 0; +pub const VOICE_SELECT_PROTOCOL: u8 = 1; +pub const VOICE_READY: u8 = 2; +pub const VOICE_HEARTBEAT: u8 = 3; +pub const VOICE_SESSION_DESCRIPTION: u8 = 4; +pub const VOICE_SPEAKING: u8 = 5; +pub const VOICE_HEARTBEAT_ACK: u8 = 6; +pub const VOICE_RESUME: u8 = 7; +pub const VOICE_HELLO: u8 = 8; +pub const VOICE_RESUMED: u8 = 9; +pub const VOICE_SSRC_DEFINITION: u8 = 12; +pub const VOICE_CLIENT_DISCONNECT: u8 = 13; +pub const VOICE_SESSION_UPDATE: u8 = 14; + +/// What is this? +/// +/// {"op":15,"d":{"any":100}} +/// +/// Opcode from +pub const VOICE_MEDIA_SINK_WANTS: u8 = 15; +/// See +/// Sent with empty data from the client, the server responds with the voice backend version; +pub const VOICE_BACKEND_VERSION: u8 = 16; + +// These two get simultaenously fired when a user joins, one has flags and one has a platform +pub const VOICE_CLIENT_CONNECT_FLAGS: u8 = 18; +pub const VOICE_CLIENT_CONNECT_PLATFORM: u8 = 20; diff --git a/src/types/events/webrtc/ready.rs b/src/types/events/voice_gateway/ready.rs similarity index 52% rename from src/types/events/webrtc/ready.rs rename to src/types/events/voice_gateway/ready.rs index 417e042..1f7f90f 100644 --- a/src/types/events/webrtc/ready.rs +++ b/src/types/events/voice_gateway/ready.rs @@ -7,15 +7,27 @@ use std::net::Ipv4Addr; use crate::types::WebSocketEvent; use serde::{Deserialize, Serialize}; +use super::VoiceEncryptionMode; + #[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)] -/// The ready event for the webrtc stream; -/// Used to give info after the identify event; -/// See https://discord.com/developers/docs/topics/voice-connections#establishing-a-voice-websocket-connection-example-voice-ready-payload; +/// The voice gateway's ready event; +/// +/// Gives the user info about the UDP connection IP and port, srrc to use, +/// available encryption modes and other data. +/// +/// Sent in response to an Identify event. +/// +/// See pub struct VoiceReady { - ssrc: i32, - ip: Ipv4Addr, - port: u32, - modes: Vec, + /// See + pub ssrc: u32, + pub ip: Ipv4Addr, + pub port: u16, + /// The available encryption modes for the UDP connection + pub modes: Vec, + #[serde(default)] + pub experiments: Vec, + // TODO: Add video streams // Heartbeat interval is also sent, but is "an erroneous field and should be ignored. The correct heartbeat_interval value comes from the Hello payload." } @@ -26,6 +38,7 @@ impl Default for VoiceReady { ip: Ipv4Addr::UNSPECIFIED, port: 0, modes: Vec::new(), + experiments: Vec::new(), } } } diff --git a/src/types/events/voice_gateway/select_protocol.rs b/src/types/events/voice_gateway/select_protocol.rs new file mode 100644 index 0000000..375a12c --- /dev/null +++ b/src/types/events/voice_gateway/select_protocol.rs @@ -0,0 +1,52 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +use serde::{Deserialize, Serialize}; + +use super::VoiceEncryptionMode; + +#[derive(Debug, Deserialize, Serialize, Clone, Default)] +/// An event sent by the client to the voice gateway server, +/// detailing what protocol, address and encryption to use; +/// +/// See +pub struct SelectProtocol { + /// The protocol to use. The only option chorus supports is [VoiceProtocol::Udp]. + pub protocol: VoiceProtocol, + pub data: SelectProtocolData, + /// The UUID4 RTC connection ID, used for analytics. + /// + /// Note: Not recommended to set this + pub rtc_connection_id: Option, + // TODO: Add codecs, what is a codec object + /// The possible experiments we want to enable + #[serde(rename = "experiments")] + pub enabled_experiments: Vec, +} + +/// The possible protocol for sending a receiving voice data. +/// +/// See +#[derive(Debug, Default, Serialize, Deserialize, Clone, Copy, PartialEq, Eq)] +#[serde(rename_all = "snake_case")] +pub enum VoiceProtocol { + #[default] + /// Sending data via UDP, documented and the only protocol chorus supports. + Udp, + // Possible value, yet NOT RECOMMENDED, AS CHORUS DOES NOT SUPPORT WEBRTC + //Webrtc, +} + +#[derive(Debug, Default, Deserialize, Serialize, Clone)] +/// The data field of the SelectProtocol Event +/// +/// See +pub struct SelectProtocolData { + /// Our external IP we got from IP discovery + pub address: String, + /// Our external UDP port we got from IP discovery + pub port: u16, + /// The mode of encryption to use + pub mode: VoiceEncryptionMode, +} diff --git a/src/types/events/voice_gateway/session_description.rs b/src/types/events/voice_gateway/session_description.rs new file mode 100644 index 0000000..9c1b3d4 --- /dev/null +++ b/src/types/events/voice_gateway/session_description.rs @@ -0,0 +1,39 @@ +use super::{AudioCodec, VideoCodec, VoiceEncryptionMode}; +use crate::types::WebSocketEvent; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Deserialize, Serialize, Clone, Default)] +/// Event that describes our encryption mode and secret key for encryption +/// +/// See +pub struct SessionDescription { + pub audio_codec: AudioCodec, + pub video_codec: VideoCodec, + pub media_session_id: String, + /// The encryption mode to use + #[serde(rename = "mode")] + pub encryption_mode: VoiceEncryptionMode, + /// The secret key we'll use for encryption + pub secret_key: [u8; 32], + /// The keyframe interval in milliseconds + pub keyframe_interval: Option, +} + +impl WebSocketEvent for SessionDescription {} + +#[derive(Debug, Deserialize, Serialize, Clone, Default)] +/// Event that might be sent to update session parameters +/// +/// See +pub struct SessionUpdate { + #[serde(rename = "audio_codec")] + pub new_audio_codec: Option, + + #[serde(rename = "video_codec")] + pub new_video_codec: Option, + + #[serde(rename = "media_session_id")] + pub new_media_session_id: Option, +} + +impl WebSocketEvent for SessionUpdate {} diff --git a/src/types/events/voice_gateway/speaking.rs b/src/types/events/voice_gateway/speaking.rs new file mode 100644 index 0000000..a18ba77 --- /dev/null +++ b/src/types/events/voice_gateway/speaking.rs @@ -0,0 +1,52 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +use bitflags::bitflags; +use serde::{Deserialize, Serialize}; + +use crate::types::{Snowflake, WebSocketEvent}; + +/// Event that tells the server we are speaking; +/// +/// Essentially, what allows us to send UDP data and lights up the green circle around your avatar. +/// +/// See +#[derive(Debug, Deserialize, Serialize, Clone, Default)] +pub struct Speaking { + /// Data about the audio we're transmitting. + /// + /// See [SpeakingBitflags] + pub speaking: u8, + pub ssrc: u32, + /// The user id of the speaking user, only sent by the server + #[serde(skip_serializing)] + pub user_id: Option, + /// Delay in milliseconds, not sent by the server + #[serde(default)] + pub delay: u64, +} + +impl WebSocketEvent for Speaking {} + +bitflags! { + /// Bitflags of speaking types; + /// + /// See + #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Serialize, Deserialize)] + pub struct SpeakingBitflags: u8 { + /// Whether we'll be transmitting normal voice audio + const MICROPHONE = 1 << 0; + /// Whether we'll be transmitting context audio for video, no speaking indicator + const SOUNDSHARE = 1 << 1; + /// Whether we are a priority speaker, lowering audio of other speakers + const PRIORITY = 1 << 2; + } +} + +impl Default for SpeakingBitflags { + /// Returns the default value for these flags, assuming normal microphone audio and not being a priority speaker + fn default() -> Self { + Self::MICROPHONE + } +} diff --git a/src/types/events/voice_gateway/ssrc_definition.rs b/src/types/events/voice_gateway/ssrc_definition.rs new file mode 100644 index 0000000..6692dc9 --- /dev/null +++ b/src/types/events/voice_gateway/ssrc_definition.rs @@ -0,0 +1,53 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +use crate::types::{Snowflake, WebSocketEvent}; +use serde::{Deserialize, Serialize}; + +/// Defines an event which provides ssrcs for voice and video for a user id. +/// +/// This event is sent when we begin to speak. +/// +/// It must be sent before sending audio, or else clients will not be able to play the stream. +/// +/// This event is sent via opcode 12. +/// +/// Examples of the event: +/// +/// When receiving: +/// ```json +/// {"op":12,"d":{"video_ssrc":0,"user_id":"463640391196082177","streams":[{"ssrc":26595,"rtx_ssrc":26596,"rid":"100","quality":100,"max_resolution":{"width":1280,"type":"fixed","height":720},"max_framerate":30,"active":false}],"audio_ssrc":26597}}{"op":12,"d":{"video_ssrc":0,"user_id":"463640391196082177","streams":[{"ssrc":26595,"rtx_ssrc":26596,"rid":"100","quality":100,"max_resolution":{"width":1280,"type":"fixed","height":720},"max_framerate":30,"active":false}],"audio_ssrc":26597}} +/// ``` +/// +/// When sending: +/// ```json +/// {"op":12,"d":{"audio_ssrc":2307250864,"video_ssrc":0,"rtx_ssrc":0,"streams":[{"type":"video","rid":"100","ssrc":26595,"active":false,"quality":100,"rtx_ssrc":26596,"max_bitrate":2500000,"max_framerate":30,"max_resolution":{"type":"fixed","width":1280,"height":720}}]}} +/// ``` +#[derive(Debug, Deserialize, Serialize, Default, Clone, PartialEq, Eq)] +pub struct SsrcDefinition { + /// The ssrc used for video communications. + /// + /// Is always sent and received, though is 0 if describing only the audio ssrc. + #[serde(default)] + pub video_ssrc: usize, + /// The ssrc used for audio communications. + /// + /// Is always sent and received, though is 0 if describing only the video ssrc. + #[serde(default)] + pub audio_ssrc: usize, + // Not sure what this is + // It is usually 0 + #[serde(default)] + pub rtx_ssrc: usize, + /// The user id these ssrcs apply to. + /// + /// Is never sent by the user and is filled in by the server + #[serde(skip_serializing)] + pub user_id: Option, + // TODO: Add video streams + #[serde(default)] + pub streams: Vec, +} + +impl WebSocketEvent for SsrcDefinition {} diff --git a/src/types/events/voice_gateway/voice_backend_version.rs b/src/types/events/voice_gateway/voice_backend_version.rs new file mode 100644 index 0000000..f8ee76e --- /dev/null +++ b/src/types/events/voice_gateway/voice_backend_version.rs @@ -0,0 +1,21 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +use crate::types::WebSocketEvent; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Default, Deserialize, Serialize, Clone, PartialEq)] +/// Received from the voice gateway server to describe the backend version. +/// +/// See +pub struct VoiceBackendVersion { + /// The voice backend's version + #[serde(rename = "voice")] + pub voice_version: String, + /// The WebRTC worker's version + #[serde(rename = "rtc_worker")] + pub rtc_worker_version: String, +} + +impl WebSocketEvent for VoiceBackendVersion {} diff --git a/src/voice.rs b/src/voice.rs deleted file mode 100644 index 79ac5ba..0000000 --- a/src/voice.rs +++ /dev/null @@ -1,6 +0,0 @@ -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -//! Where the voice chat implementation will be, once it's finished. -//! For development on voice, see the feature/voice branch. diff --git a/src/voice/crypto.rs b/src/voice/crypto.rs new file mode 100644 index 0000000..b59ce98 --- /dev/null +++ b/src/voice/crypto.rs @@ -0,0 +1,90 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +//! Defines cryptography functions used within the voice implementation. +//! +//! All functions in this module return a 24 byte long `Vec`. + +/// Gets an `xsalsa20_poly1305` nonce from an rtppacket. +/// +/// See +pub(crate) fn get_xsalsa20_poly1305_nonce(packet: &[u8]) -> Vec { + let mut rtp_header = Vec::with_capacity(24); + rtp_header.append(&mut packet[0..12].to_vec()); + + // The header is only 12 bytes, but the nonce has to be 24 + while rtp_header.len() < 24 { + rtp_header.push(0); + } + + rtp_header +} + +/// Gets an `xsalsa20_poly1305_suffix` nonce from an rtppacket. +/// +/// See +pub(crate) fn get_xsalsa20_poly1305_suffix_nonce(packet: &[u8]) -> Vec { + let mut nonce = Vec::with_capacity(24); + + nonce.append(&mut packet[(packet.len() - 24)..packet.len()].to_vec()); + + nonce +} + +/// Gets an `xsalsa20_poly1305_lite` nonce from an rtppacket. +/// +/// See +pub(crate) fn get_xsalsa20_poly1305_lite_nonce(packet: &[u8]) -> Vec { + let mut nonce = Vec::with_capacity(24); + + nonce.append(&mut packet[(packet.len() - 4)..packet.len()].to_vec()); + + // The suffix is only 4 bytes, but the nonce has to be 24 + while nonce.len() < 24 { + nonce.push(0); + } + + nonce +} + +#[test] +// Asserts all functions that retrieve a nonce from packet bytes +fn test_packet_nonce_derives() { + let test_packet_bytes = vec![ + 144, 120, 98, 5, 71, 174, 52, 64, 0, 4, 85, 36, 178, 8, 37, 146, 35, 154, 141, 36, 125, 15, + 65, 179, 227, 108, 165, 56, 68, 68, 3, 62, 87, 233, 7, 81, 147, 93, 22, 95, 115, 202, 48, + 66, 190, 229, 69, 146, 66, 108, 60, 114, 2, 228, 111, 40, 108, 5, 68, 226, 76, 240, 20, + 231, 210, 214, 123, 175, 188, 161, 10, 125, 13, 196, 114, 248, 50, 84, 103, 139, 86, 223, + 82, 173, 8, 209, 78, 188, 169, 151, 157, 42, 189, 153, 228, 105, 199, 19, 185, 16, 33, 133, + 113, 253, 145, 36, 106, 14, 222, 128, 226, 239, 10, 39, 72, 113, 33, 113, + ]; + + let nonce_1 = get_xsalsa20_poly1305_nonce(&test_packet_bytes); + let nonce_1_expected = vec![ + 144, 120, 98, 5, 71, 174, 52, 64, 0, 4, 85, 36, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + ]; + + let nonce_2 = get_xsalsa20_poly1305_suffix_nonce(&test_packet_bytes); + let nonce_2_expected = vec![ + 228, 105, 199, 19, 185, 16, 33, 133, 113, 253, 145, 36, 106, 14, 222, 128, 226, 239, 10, + 39, 72, 113, 33, 113, + ]; + + let nonce_3 = get_xsalsa20_poly1305_lite_nonce(&test_packet_bytes); + let nonce_3_expected = vec![ + 72, 113, 33, 113, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + ]; + + println!("nonce 1: {:?}", nonce_1); + println!("nonce 2: {:?}", nonce_2); + println!("nonce 3: {:?}", nonce_3); + + assert_eq!(nonce_1.len(), 24); + assert_eq!(nonce_2.len(), 24); + assert_eq!(nonce_3.len(), 24); + + assert_eq!(nonce_1, nonce_1_expected); + assert_eq!(nonce_2, nonce_2_expected); + assert_eq!(nonce_3, nonce_3_expected); +} diff --git a/src/voice/gateway/backends/mod.rs b/src/voice/gateway/backends/mod.rs new file mode 100644 index 0000000..7f3f3dd --- /dev/null +++ b/src/voice/gateway/backends/mod.rs @@ -0,0 +1,27 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#[cfg(all(not(target_arch = "wasm32"), feature = "voice_gateway"))] +pub mod tungstenite; +#[cfg(all(not(target_arch = "wasm32"), feature = "voice_gateway"))] +pub use tungstenite::*; + +#[cfg(all(target_arch = "wasm32", feature = "voice_gateway"))] +pub mod wasm; +#[cfg(all(target_arch = "wasm32", feature = "voice_gateway"))] +pub use wasm::*; + +#[cfg(all(not(target_arch = "wasm32"), feature = "voice_gateway"))] +pub type Sink = tungstenite::TungsteniteSink; +#[cfg(all(not(target_arch = "wasm32"), feature = "voice_gateway"))] +pub type Stream = tungstenite::TungsteniteStream; +#[cfg(all(not(target_arch = "wasm32"), feature = "voice_gateway"))] +pub type WebSocketBackend = tungstenite::TungsteniteBackend; + +#[cfg(all(target_arch = "wasm32", feature = "voice_gateway"))] +pub type Sink = wasm::WasmSink; +#[cfg(all(target_arch = "wasm32", feature = "voice_gateway"))] +pub type Stream = wasm::WasmStream; +#[cfg(all(target_arch = "wasm32", feature = "voice_gateway"))] +pub type WebSocketBackend = wasm::WasmBackend; diff --git a/src/voice/gateway/backends/tungstenite.rs b/src/voice/gateway/backends/tungstenite.rs new file mode 100644 index 0000000..26cc0fe --- /dev/null +++ b/src/voice/gateway/backends/tungstenite.rs @@ -0,0 +1,77 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +use futures_util::{ + stream::{SplitSink, SplitStream}, + StreamExt, +}; +use tokio::net::TcpStream; +use tokio_tungstenite::{ + connect_async_tls_with_config, tungstenite, Connector, MaybeTlsStream, WebSocketStream, +}; + +use crate::{errors::VoiceGatewayError, voice::gateway::VoiceGatewayMessage}; + +#[derive(Debug, Clone)] +pub struct TungsteniteBackend; + +// These could be made into inherent associated types when that's stabilized +pub type TungsteniteSink = + SplitSink>, tungstenite::Message>; +pub type TungsteniteStream = SplitStream>>; + +impl TungsteniteBackend { + pub async fn connect( + websocket_url: &str, + ) -> Result<(TungsteniteSink, TungsteniteStream), crate::errors::VoiceGatewayError> { + let mut roots = rustls::RootCertStore::empty(); + let certs = rustls_native_certs::load_native_certs(); + + if let Err(e) = certs { + log::error!("Failed to load platform native certs! {:?}", e); + return Err(VoiceGatewayError::CannotConnect { + error: format!("{:?}", e), + }); + } + + for cert in certs.unwrap() { + roots.add(&rustls::Certificate(cert.0)).unwrap(); + } + let (websocket_stream, _) = match connect_async_tls_with_config( + websocket_url, + None, + false, + Some(Connector::Rustls( + rustls::ClientConfig::builder() + .with_safe_defaults() + .with_root_certificates(roots) + .with_no_client_auth() + .into(), + )), + ) + .await + { + Ok(websocket_stream) => websocket_stream, + Err(e) => { + return Err(VoiceGatewayError::CannotConnect { + error: e.to_string(), + }) + } + }; + + Ok(websocket_stream.split()) + } +} + +impl From for tungstenite::Message { + fn from(message: VoiceGatewayMessage) -> Self { + Self::Text(message.0) + } +} + +impl From for VoiceGatewayMessage { + fn from(value: tungstenite::Message) -> Self { + Self(value.to_string()) + } +} diff --git a/src/voice/gateway/backends/wasm.rs b/src/voice/gateway/backends/wasm.rs new file mode 100644 index 0000000..a39723e --- /dev/null +++ b/src/voice/gateway/backends/wasm.rs @@ -0,0 +1,52 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +use futures_util::{ + stream::{SplitSink, SplitStream}, + StreamExt, +}; + +use ws_stream_wasm::*; + +use crate::errors::VoiceGatewayError; +use crate::voice::gateway::VoiceGatewayMessage; + +#[derive(Debug, Clone)] +pub struct WasmBackend; + +// These could be made into inherent associated types when that's stabilized +pub type WasmSink = SplitSink; +pub type WasmStream = SplitStream; + +impl WasmBackend { + pub async fn connect(websocket_url: &str) -> Result<(WasmSink, WasmStream), VoiceGatewayError> { + let (_, websocket_stream) = match WsMeta::connect(websocket_url, None).await { + Ok(stream) => Ok(stream), + Err(e) => Err(VoiceGatewayError::CannotConnect { + error: e.to_string(), + }), + }?; + + Ok(websocket_stream.split()) + } +} + +impl From for WsMessage { + fn from(message: VoiceGatewayMessage) -> Self { + Self::Text(message.0) + } +} + +impl From for VoiceGatewayMessage { + fn from(value: WsMessage) -> Self { + match value { + WsMessage::Text(text) => Self(text), + WsMessage::Binary(bin) => { + let mut text = String::new(); + let _ = bin.iter().map(|v| text.push_str(&v.to_string())); + Self(text) + } + } + } +} diff --git a/src/voice/gateway/events.rs b/src/voice/gateway/events.rs new file mode 100644 index 0000000..af043b3 --- /dev/null +++ b/src/voice/gateway/events.rs @@ -0,0 +1,28 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +use crate::{ + errors::VoiceGatewayError, + gateway::GatewayEvent, + types::{ + SessionDescription, SessionUpdate, Speaking, SsrcDefinition, VoiceBackendVersion, + VoiceClientConnectFlags, VoiceClientConnectPlatform, VoiceClientDisconnection, + VoiceMediaSinkWants, VoiceReady, + }, +}; + +#[derive(Default, Debug)] +pub struct VoiceEvents { + pub voice_ready: GatewayEvent, + pub backend_version: GatewayEvent, + pub session_description: GatewayEvent, + pub session_update: GatewayEvent, + pub speaking: GatewayEvent, + pub ssrc_definition: GatewayEvent, + pub client_disconnect: GatewayEvent, + pub client_connect_flags: GatewayEvent, + pub client_connect_platform: GatewayEvent, + pub media_sink_wants: GatewayEvent, + pub error: GatewayEvent, +} diff --git a/src/voice/gateway/gateway.rs b/src/voice/gateway/gateway.rs new file mode 100644 index 0000000..4727ae4 --- /dev/null +++ b/src/voice/gateway/gateway.rs @@ -0,0 +1,349 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +use std::{sync::Arc, time::Duration}; + +use log::*; + +use tokio::sync::Mutex; + +use futures_util::SinkExt; +use futures_util::StreamExt; + +use crate::{ + errors::VoiceGatewayError, + gateway::GatewayEvent, + types::{ + VoiceGatewayReceivePayload, VoiceHelloData, WebSocketEvent, VOICE_BACKEND_VERSION, + VOICE_CLIENT_CONNECT_FLAGS, VOICE_CLIENT_CONNECT_PLATFORM, VOICE_CLIENT_DISCONNECT, + VOICE_HEARTBEAT, VOICE_HEARTBEAT_ACK, VOICE_HELLO, VOICE_IDENTIFY, VOICE_MEDIA_SINK_WANTS, + VOICE_READY, VOICE_RESUME, VOICE_SELECT_PROTOCOL, VOICE_SESSION_DESCRIPTION, + VOICE_SESSION_UPDATE, VOICE_SPEAKING, VOICE_SSRC_DEFINITION, + }, + voice::gateway::{ + heartbeat::VoiceHeartbeatThreadCommunication, VoiceGatewayMessage, WebSocketBackend, + }, +}; + +use super::{ + events::VoiceEvents, heartbeat::VoiceHeartbeatHandler, Sink, Stream, VoiceGatewayHandle, +}; + +#[derive(Debug)] +pub struct VoiceGateway { + events: Arc>, + heartbeat_handler: VoiceHeartbeatHandler, + websocket_send: Arc>, + websocket_receive: Stream, + kill_send: tokio::sync::broadcast::Sender<()>, + kill_receive: tokio::sync::broadcast::Receiver<()>, +} + +impl VoiceGateway { + #[allow(clippy::new_ret_no_self)] + pub async fn spawn(websocket_url: String) -> Result { + // Append the needed things to the websocket url + let processed_url = format!("wss://{}/?v=7", websocket_url); + trace!("Created voice socket url: {}", processed_url.clone()); + + let (websocket_send, mut websocket_receive) = + WebSocketBackend::connect(&processed_url).await?; + + let shared_websocket_send = Arc::new(Mutex::new(websocket_send)); + + // Create a shared broadcast channel for killing all gateway tasks + let (kill_send, mut _kill_receive) = tokio::sync::broadcast::channel::<()>(16); + + // 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 + #[cfg(not(target_arch = "wasm32"))] + let msg: VoiceGatewayMessage = websocket_receive.next().await.unwrap().unwrap().into(); + #[cfg(target_arch = "wasm32")] + let msg: VoiceGatewayMessage = websocket_receive.next().await.unwrap().into(); + let gateway_payload: VoiceGatewayReceivePayload = serde_json::from_str(&msg.0).unwrap(); + + if gateway_payload.op_code != VOICE_HELLO { + return Err(VoiceGatewayError::NonHelloOnInitiate { + opcode: gateway_payload.op_code, + }); + } + + info!("VGW: Received Hello"); + + // The hello data for voice gateways is in float milliseconds, so we convert it to f64 seconds + let gateway_hello: VoiceHelloData = + serde_json::from_str(gateway_payload.data.get()).unwrap(); + let heartbeat_interval_seconds: f64 = gateway_hello.heartbeat_interval / 1000.0; + + let voice_events = VoiceEvents::default(); + let shared_events = Arc::new(Mutex::new(voice_events)); + + let mut gateway = VoiceGateway { + events: shared_events.clone(), + heartbeat_handler: VoiceHeartbeatHandler::new( + Duration::from_secs_f64(heartbeat_interval_seconds), + 1, // to:do actually compute nonce + shared_websocket_send.clone(), + kill_send.subscribe(), + ), + websocket_send: shared_websocket_send.clone(), + websocket_receive, + kill_send: kill_send.clone(), + kill_receive: kill_send.subscribe(), + }; + + // Now we can continuously check for messages in a different task, since we aren't going to receive another hello + #[cfg(not(target_arch = "wasm32"))] + tokio::task::spawn(async move { + gateway.gateway_listen_task().await; + }); + #[cfg(target_arch = "wasm32")] + wasm_bindgen_futures::spawn_local(async move { + gateway.gateway_listen_task().await; + }); + + Ok(VoiceGatewayHandle { + url: websocket_url.clone(), + events: shared_events, + websocket_send: shared_websocket_send.clone(), + kill_send: kill_send.clone(), + }) + } + + /// The main gateway listener task; + pub async fn gateway_listen_task(&mut self) { + loop { + let msg; + + tokio::select! { + Ok(_) = self.kill_receive.recv() => { + log::trace!("VGW: Closing listener task"); + break; + } + message = self.websocket_receive.next() => { + msg = message; + } + } + + // PRETTYFYME: Remove inline conditional compiling + #[cfg(not(target_arch = "wasm32"))] + if let Some(Ok(message)) = msg { + self.handle_message(message.into()).await; + continue; + } + #[cfg(target_arch = "wasm32")] + if let Some(message) = msg { + self.handle_message(message.into()).await; + continue; + } + + // We couldn't receive the next message or it was an error, something is wrong with the websocket, close + warn!("VGW: Websocket is broken, stopping gateway"); + break; + } + } + + /// Closes the websocket connection and stops all tasks + async fn close(&mut self) { + self.kill_send.send(()).unwrap(); + self.websocket_send.lock().await.close().await.unwrap(); + } + + /// Deserializes and updates a dispatched event, when we already know its type; + /// (Called for every event in handle_message) + async fn handle_event<'a, T: WebSocketEvent + serde::Deserialize<'a>>( + data: &'a str, + event: &mut GatewayEvent, + ) -> Result<(), serde_json::Error> { + let data_deserialize_result: Result = serde_json::from_str(data); + + if data_deserialize_result.is_err() { + return Err(data_deserialize_result.err().unwrap()); + } + + event.notify(data_deserialize_result.unwrap()).await; + Ok(()) + } + + /// This handles a message as a websocket event and updates its events along with the events' observers + pub async fn handle_message(&mut self, msg: VoiceGatewayMessage) { + if msg.0.is_empty() { + return; + } + + let Ok(gateway_payload) = msg.payload() else { + if let Some(error) = msg.error() { + warn!("GW: Received error {:?}, connection will close..", error); + self.close().await; + self.events.lock().await.error.notify(error).await; + } else { + warn!( + "Message unrecognised: {:?}, please open an issue on the chorus github", + msg.0 + ); + } + return; + }; + + // See + match gateway_payload.op_code { + VOICE_READY => { + trace!("VGW: Received READY!"); + + let event = &mut self.events.lock().await.voice_ready; + let result = VoiceGateway::handle_event(gateway_payload.data.get(), event).await; + if result.is_err() { + warn!("Failed to parse VOICE_READY ({})", result.err().unwrap()); + } + } + VOICE_BACKEND_VERSION => { + trace!("VGW: Received Backend Version"); + + let event = &mut self.events.lock().await.backend_version; + let result = VoiceGateway::handle_event(gateway_payload.data.get(), event).await; + if result.is_err() { + warn!( + "Failed to parse VOICE_BACKEND_VERSION ({})", + result.err().unwrap() + ); + } + } + VOICE_SESSION_DESCRIPTION => { + trace!("VGW: Received Session Description"); + + let event = &mut self.events.lock().await.session_description; + let result = VoiceGateway::handle_event(gateway_payload.data.get(), event).await; + if result.is_err() { + warn!( + "Failed to parse VOICE_SESSION_DESCRIPTION ({})", + result.err().unwrap() + ); + } + } + VOICE_SESSION_UPDATE => { + trace!("VGW: Received Session Update"); + + let event = &mut self.events.lock().await.session_update; + let result = VoiceGateway::handle_event(gateway_payload.data.get(), event).await; + if result.is_err() { + warn!( + "Failed to parse VOICE_SESSION_UPDATE ({})", + result.err().unwrap() + ); + } + } + VOICE_SPEAKING => { + trace!("VGW: Received Speaking"); + + let event = &mut self.events.lock().await.speaking; + let result = VoiceGateway::handle_event(gateway_payload.data.get(), event).await; + if result.is_err() { + warn!("Failed to parse VOICE_SPEAKING ({})", result.err().unwrap()); + } + } + VOICE_SSRC_DEFINITION => { + trace!("VGW: Received Ssrc Definition"); + + let event = &mut self.events.lock().await.ssrc_definition; + let result = VoiceGateway::handle_event(gateway_payload.data.get(), event).await; + if result.is_err() { + warn!( + "Failed to parse VOICE_SSRC_DEFINITION ({})", + result.err().unwrap() + ); + } + } + VOICE_CLIENT_DISCONNECT => { + trace!("VGW: Received Client Disconnect"); + + let event = &mut self.events.lock().await.client_disconnect; + let result = VoiceGateway::handle_event(gateway_payload.data.get(), event).await; + if result.is_err() { + warn!( + "Failed to parse VOICE_CLIENT_DISCONNECT ({})", + result.err().unwrap() + ); + } + } + VOICE_CLIENT_CONNECT_FLAGS => { + trace!("VGW: Received Client Connect Flags"); + + let event = &mut self.events.lock().await.client_connect_flags; + let result = VoiceGateway::handle_event(gateway_payload.data.get(), event).await; + if result.is_err() { + warn!( + "Failed to parse VOICE_CLIENT_CONNECT_FLAGS ({})", + result.err().unwrap() + ); + } + } + VOICE_CLIENT_CONNECT_PLATFORM => { + trace!("VGW: Received Client Connect Platform"); + + let event = &mut self.events.lock().await.client_connect_platform; + let result = VoiceGateway::handle_event(gateway_payload.data.get(), event).await; + if result.is_err() { + warn!( + "Failed to parse VOICE_CLIENT_CONNECT_PLATFORM ({})", + result.err().unwrap() + ); + } + } + VOICE_MEDIA_SINK_WANTS => { + trace!("VGW: Received Media Sink Wants"); + + let event = &mut self.events.lock().await.media_sink_wants; + let result = VoiceGateway::handle_event(gateway_payload.data.get(), event).await; + if result.is_err() { + warn!( + "Failed to parse VOICE_MEDIA_SINK_WANTS ({})", + result.err().unwrap() + ); + } + } + // We received a heartbeat from the server + // "Discord may send the app a Heartbeat (opcode 1) event, in which case the app should send a Heartbeat event immediately." + VOICE_HEARTBEAT => { + trace!("VGW: Received Heartbeat // Heartbeat Request"); + + // Tell the heartbeat handler it should send a heartbeat right away + let heartbeat_communication = VoiceHeartbeatThreadCommunication { + updated_nonce: None, + op_code: Some(VOICE_HEARTBEAT), + }; + + self.heartbeat_handler + .send + .send(heartbeat_communication) + .await + .unwrap(); + } + VOICE_HEARTBEAT_ACK => { + trace!("VGW: Received Heartbeat ACK"); + + // Tell the heartbeat handler we received an ack + + let heartbeat_communication = VoiceHeartbeatThreadCommunication { + updated_nonce: None, + op_code: Some(VOICE_HEARTBEAT_ACK), + }; + + self.heartbeat_handler + .send + .send(heartbeat_communication) + .await + .unwrap(); + } + VOICE_IDENTIFY | VOICE_SELECT_PROTOCOL | VOICE_RESUME => { + info!( + "VGW: Received unexpected opcode ({}) for current state. This might be due to a faulty server implementation and is likely not the fault of chorus.", + gateway_payload.op_code + ); + } + _ => { + warn!("VGW: Received unrecognized voice gateway op code ({})! Please open an issue on the chorus github so we can implement it", gateway_payload.op_code); + } + } + } +} diff --git a/src/voice/gateway/handle.rs b/src/voice/gateway/handle.rs new file mode 100644 index 0000000..b48080a --- /dev/null +++ b/src/voice/gateway/handle.rs @@ -0,0 +1,105 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +use std::sync::Arc; + +use log::*; + +use futures_util::SinkExt; + +use serde_json::json; +use tokio::sync::Mutex; + +use crate::types::{ + SelectProtocol, Speaking, SsrcDefinition, VoiceGatewaySendPayload, VoiceIdentify, + VOICE_BACKEND_VERSION, VOICE_IDENTIFY, VOICE_SELECT_PROTOCOL, VOICE_SPEAKING, + VOICE_SSRC_DEFINITION, +}; + +use super::{events::VoiceEvents, Sink, VoiceGatewayMessage}; + +/// Represents a handle to a Voice Gateway connection. +/// Using this handle you can send Gateway Events directly. +#[derive(Debug, Clone)] +pub struct VoiceGatewayHandle { + pub url: String, + pub events: Arc>, + pub websocket_send: Arc>, + /// Tells gateway tasks to close + pub(super) kill_send: tokio::sync::broadcast::Sender<()>, +} + +impl VoiceGatewayHandle { + /// Sends json to the gateway with an opcode + async fn send_json(&self, op_code: u8, to_send: serde_json::Value) { + let gateway_payload = VoiceGatewaySendPayload { + op_code, + data: to_send, + }; + + let payload_json = serde_json::to_string(&gateway_payload).unwrap(); + let message = VoiceGatewayMessage(payload_json); + + self.websocket_send + .lock() + .await + .send(message.into()) + .await + .unwrap(); + } + + /// Sends a voice identify event to the gateway + pub async fn send_identify(&self, to_send: VoiceIdentify) { + let to_send_value = serde_json::to_value(&to_send).unwrap(); + + trace!("VGW: Sending Identify.."); + + self.send_json(VOICE_IDENTIFY, to_send_value).await; + } + + /// Sends a select protocol event to the gateway + pub async fn send_select_protocol(&self, to_send: SelectProtocol) { + let to_send_value = serde_json::to_value(&to_send).unwrap(); + + trace!("VGW: Sending Select Protocol"); + + self.send_json(VOICE_SELECT_PROTOCOL, to_send_value).await; + } + + /// Sends a speaking event to the gateway + pub async fn send_speaking(&self, to_send: Speaking) { + let to_send_value = serde_json::to_value(&to_send).unwrap(); + + trace!("VGW: Sending Speaking"); + + self.send_json(VOICE_SPEAKING, to_send_value).await; + } + + /// Sends an ssrc definition event + pub async fn send_ssrc_definition(&self, to_send: SsrcDefinition) { + let to_send_value = serde_json::to_value(&to_send).unwrap(); + + trace!("VGW: Sending SsrcDefinition"); + + self.send_json(VOICE_SSRC_DEFINITION, to_send_value).await; + } + + /// Sends a voice backend version request to the gateway + pub async fn send_voice_backend_version_request(&self) { + let data_empty_object = json!("{}"); + + trace!("VGW: Requesting voice backend version"); + + self.send_json(VOICE_BACKEND_VERSION, data_empty_object) + .await; + } + + /// Closes the websocket connection and stops all gateway tasks; + /// + /// Essentially pulls the plug on the voice gateway, leaving it possible to resume; + pub async fn close(&self) { + self.kill_send.send(()).unwrap(); + self.websocket_send.lock().await.close().await.unwrap(); + } +} diff --git a/src/voice/gateway/heartbeat.rs b/src/voice/gateway/heartbeat.rs new file mode 100644 index 0000000..2b9fde5 --- /dev/null +++ b/src/voice/gateway/heartbeat.rs @@ -0,0 +1,174 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +use futures_util::SinkExt; +use log::*; + +#[cfg(not(target_arch = "wasm32"))] +use tokio::time::Instant; +#[cfg(target_arch = "wasm32")] +use wasmtimer::std::Instant; + +#[cfg(not(target_arch = "wasm32"))] +use tokio::time::sleep_until; +#[cfg(target_arch = "wasm32")] +use wasmtimer::tokio::sleep_until; + +use std::{sync::Arc, time::Duration}; + +use tokio::sync::{ + mpsc::{Receiver, Sender}, + Mutex, +}; + +#[cfg(not(target_arch = "wasm32"))] +use tokio::task; + +use crate::{ + gateway::heartbeat::HEARTBEAT_ACK_TIMEOUT, + types::{VoiceGatewaySendPayload, VOICE_HEARTBEAT, VOICE_HEARTBEAT_ACK}, + voice::gateway::VoiceGatewayMessage, +}; + +use super::Sink; + +/// Handles sending heartbeats to the voice gateway in another thread +#[allow(dead_code)] // FIXME: Remove this, once all fields of VoiceHeartbeatHandler are used +#[derive(Debug)] +pub(super) struct VoiceHeartbeatHandler { + /// The heartbeat interval in milliseconds + pub heartbeat_interval: Duration, + /// The send channel for the heartbeat thread + pub send: Sender, +} + +impl VoiceHeartbeatHandler { + pub fn new( + heartbeat_interval: Duration, + starting_nonce: u64, + websocket_tx: Arc>, + kill_rc: tokio::sync::broadcast::Receiver<()>, + ) -> Self { + let (send, receive) = tokio::sync::mpsc::channel(32); + let kill_receive = kill_rc.resubscribe(); + + #[cfg(not(target_arch = "wasm32"))] + task::spawn(async move { + Self::heartbeat_task( + websocket_tx, + heartbeat_interval, + starting_nonce, + receive, + kill_receive, + ) + .await; + }); + #[cfg(target_arch = "wasm32")] + wasm_bindgen_futures::spawn_local(async move { + Self::heartbeat_task( + websocket_tx, + heartbeat_interval, + starting_nonce, + receive, + kill_receive, + ) + .await; + }); + + Self { + heartbeat_interval, + send, + } + } + + /// The main heartbeat task; + /// + /// Can be killed by the kill broadcast; + /// If the websocket is closed, will die out next time it tries to send a heartbeat; + pub async fn heartbeat_task( + websocket_tx: Arc>, + heartbeat_interval: Duration, + starting_nonce: u64, + mut receive: Receiver, + mut kill_receive: tokio::sync::broadcast::Receiver<()>, + ) { + let mut last_heartbeat_timestamp: Instant = Instant::now(); + let mut last_heartbeat_acknowledged = true; + let mut nonce: u64 = starting_nonce; + + loop { + let timeout = if last_heartbeat_acknowledged { + heartbeat_interval + } else { + // If the server hasn't acknowledged our heartbeat we should resend it + Duration::from_millis(HEARTBEAT_ACK_TIMEOUT) + }; + + let mut should_send = false; + + tokio::select! { + () = sleep_until(last_heartbeat_timestamp + timeout) => { + should_send = true; + } + Some(communication) = receive.recv() => { + // If we received a nonce update, use that nonce now + if communication.updated_nonce.is_some() { + nonce = communication.updated_nonce.unwrap(); + } + + if let Some(op_code) = communication.op_code { + match op_code { + VOICE_HEARTBEAT => { + // As per the api docs, if the server sends us a Heartbeat, that means we need to respond with a heartbeat immediately + should_send = true; + } + VOICE_HEARTBEAT_ACK => { + // The server received our heartbeat + last_heartbeat_acknowledged = true; + } + _ => {} + } + } + } + Ok(_) = kill_receive.recv() => { + log::trace!("VGW: Closing heartbeat task"); + break; + } + } + + if should_send { + trace!("VGW: Sending Heartbeat.."); + + let heartbeat = VoiceGatewaySendPayload { + op_code: VOICE_HEARTBEAT, + data: nonce.into(), + }; + + let heartbeat_json = serde_json::to_string(&heartbeat).unwrap(); + + let msg = VoiceGatewayMessage(heartbeat_json); + + let send_result = websocket_tx.lock().await.send(msg.into()).await; + if send_result.is_err() { + // We couldn't send, the websocket is broken + warn!("VGW: Couldnt send heartbeat, websocket seems broken"); + break; + } + + last_heartbeat_timestamp = Instant::now(); + last_heartbeat_acknowledged = false; + } + } + } +} + +/// Used for communications between the voice heartbeat and voice gateway thread. +/// Either signifies a nonce update, a heartbeat ACK or a Heartbeat request by the server +#[derive(Clone, Copy, Debug)] +pub(super) struct VoiceHeartbeatThreadCommunication { + /// The opcode for the communication we received, if relevant + pub(super) op_code: Option, + /// The new nonce to use, if any + pub(super) updated_nonce: Option, +} diff --git a/src/voice/gateway/message.rs b/src/voice/gateway/message.rs new file mode 100644 index 0000000..4b40f35 --- /dev/null +++ b/src/voice/gateway/message.rs @@ -0,0 +1,46 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +use crate::{errors::VoiceGatewayError, types::VoiceGatewayReceivePayload}; + +/// Represents a message received from the voice websocket connection. +/// +/// This will be either a [VoiceGatewayReceivePayload], containing voice gateway events, or a [VoiceGatewayError]. +/// +/// This struct is used internally when handling messages. +#[derive(Clone, Debug)] +pub struct VoiceGatewayMessage(pub String); + +impl VoiceGatewayMessage { + /// Parses the message as an error; + /// Returns the error if successfully parsed, None if the message isn't an error + pub fn error(&self) -> Option { + // Some error strings have dots on the end, which we don't care about + let processed_content = self.0.to_lowercase().replace('.', ""); + + match processed_content.as_str() { + "unknown opcode" | "4001" => Some(VoiceGatewayError::UnknownOpcode), + "decode error" | "failed to decode payload" | "4002" => { + Some(VoiceGatewayError::FailedToDecodePayload) + } + "not authenticated" | "4003" => Some(VoiceGatewayError::NotAuthenticated), + "authentication failed" | "4004" => Some(VoiceGatewayError::AuthenticationFailed), + "already authenticated" | "4005" => Some(VoiceGatewayError::AlreadyAuthenticated), + "session is no longer valid" | "4006" => Some(VoiceGatewayError::SessionNoLongerValid), + "session timeout" | "4009" => Some(VoiceGatewayError::SessionTimeout), + "server not found" | "4011" => Some(VoiceGatewayError::ServerNotFound), + "unknown protocol" | "4012" => Some(VoiceGatewayError::UnknownProtocol), + "disconnected" | "4014" => Some(VoiceGatewayError::Disconnected), + "voice server crashed" | "4015" => Some(VoiceGatewayError::VoiceServerCrashed), + "unknown encryption mode" | "4016" => Some(VoiceGatewayError::UnknownEncryptionMode), + _ => None, + } + } + + /// Parses the message as a payload; + /// Returns a result of deserializing + pub fn payload(&self) -> Result { + serde_json::from_str(&self.0) + } +} diff --git a/src/types/events/webrtc/mod.rs b/src/voice/gateway/mod.rs similarity index 52% rename from src/types/events/webrtc/mod.rs rename to src/voice/gateway/mod.rs index a6d4ba4..e8b36e5 100644 --- a/src/types/events/webrtc/mod.rs +++ b/src/voice/gateway/mod.rs @@ -2,8 +2,14 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. -pub use identify::*; -pub use ready::*; +pub mod backends; +pub mod events; +pub mod gateway; +pub mod handle; +pub mod heartbeat; +pub mod message; -mod identify; -mod ready; +pub use backends::*; +pub use gateway::*; +pub use handle::*; +pub use message::*; diff --git a/src/voice/mod.rs b/src/voice/mod.rs new file mode 100644 index 0000000..0d4f6e1 --- /dev/null +++ b/src/voice/mod.rs @@ -0,0 +1,17 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +//! Module for all voice functionality within chorus. + +mod crypto; +#[cfg(feature = "voice_gateway")] +pub mod gateway; +#[cfg(feature = "voice_udp")] +pub mod udp; +#[cfg(feature = "voice_udp")] +pub mod voice_data; + +// Pub use this so users can interact with packet types if they want +#[cfg(feature = "voice_udp")] +pub use discortp; diff --git a/src/voice/udp/backends/mod.rs b/src/voice/udp/backends/mod.rs new file mode 100644 index 0000000..e8c98f2 --- /dev/null +++ b/src/voice/udp/backends/mod.rs @@ -0,0 +1,16 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#[cfg(all(not(target_arch = "wasm32"), feature = "voice_udp"))] +pub mod tokio; +#[cfg(all(not(target_arch = "wasm32"), feature = "voice_udp"))] +pub use tokio::*; + +#[cfg(all(not(target_arch = "wasm32"), feature = "voice_udp"))] +pub type UdpSocket = tokio::TokioSocket; +#[cfg(all(not(target_arch = "wasm32"), feature = "voice_udp"))] +pub type UdpBackend = tokio::TokioBackend; + +#[cfg(all(target_arch = "wasm32", feature = "voice_udp"))] +compile_error!("UDP Voice support is not (and will likely never be) supported for WASM. This is because UDP cannot be used in the browser. We are however looking into Webrtc for WASM voice support."); diff --git a/src/voice/udp/backends/tokio.rs b/src/voice/udp/backends/tokio.rs new file mode 100644 index 0000000..e529a0c --- /dev/null +++ b/src/voice/udp/backends/tokio.rs @@ -0,0 +1,37 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +use std::net::SocketAddr; + +use crate::errors::VoiceUdpError; + +#[derive(Debug, Clone)] +pub struct TokioBackend; + +pub type TokioSocket = tokio::net::UdpSocket; + +impl TokioBackend { + pub async fn connect(url: SocketAddr) -> Result { + // Bind with a port number of 0, so the os assigns this listener a port + let udp_socket_result = TokioSocket::bind("0.0.0.0:0").await; + + if let Err(e) = udp_socket_result { + return Err(VoiceUdpError::CannotBind { + error: format!("{:?}", e), + }); + } + + let udp_socket = udp_socket_result.unwrap(); + + let connection_result = udp_socket.connect(url).await; + + if let Err(e) = connection_result { + return Err(VoiceUdpError::CannotConnect { + error: format!("{:?}", e), + }); + } + + Ok(udp_socket) + } +} diff --git a/src/voice/udp/events.rs b/src/voice/udp/events.rs new file mode 100644 index 0000000..d4917fe --- /dev/null +++ b/src/voice/udp/events.rs @@ -0,0 +1,25 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +use discortp::{rtcp::Rtcp, rtp::Rtp}; + +use crate::{gateway::GatewayEvent, types::WebSocketEvent}; + +impl WebSocketEvent for Rtp {} +impl WebSocketEvent for Rtcp {} + +#[derive(Debug)] +pub struct VoiceUDPEvents { + pub rtp: GatewayEvent, + pub rtcp: GatewayEvent, +} + +impl Default for VoiceUDPEvents { + fn default() -> Self { + Self { + rtp: GatewayEvent::new(), + rtcp: GatewayEvent::new(), + } + } +} diff --git a/src/voice/udp/handle.rs b/src/voice/udp/handle.rs new file mode 100644 index 0000000..822384d --- /dev/null +++ b/src/voice/udp/handle.rs @@ -0,0 +1,256 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +use std::sync::Arc; + +use crypto_secretbox::{ + aead::Aead, cipher::generic_array::GenericArray, KeyInit, XSalsa20Poly1305, +}; +use discortp::Packet; + +use getrandom::getrandom; +use log::*; + +use tokio::{sync::Mutex, sync::RwLock}; + +use super::UdpSocket; + +use crate::{ + errors::VoiceUdpError, + types::VoiceEncryptionMode, + voice::{crypto::get_xsalsa20_poly1305_nonce, voice_data::VoiceData}, +}; + +use super::{events::VoiceUDPEvents, RTP_HEADER_SIZE}; + +/// Handle to a voice UDP connection +/// +/// Can be safely cloned and will still correspond to the same connection. +#[derive(Debug, Clone)] +pub struct UdpHandle { + pub events: Arc>, + pub(super) socket: Arc, + pub data: Arc>, +} + +impl UdpHandle { + /// Constructs and sends encoded opus rtp data. + /// + /// Automatically makes an [RtpPacket](discortp::rtp::RtpPacket), encrypts it and sends it. + /// + /// # Errors + /// If we do not have VoiceReady data, which contains our ssrc, this returns a + /// [VoiceUdpError::NoData] error. + /// + /// If we have not received an encryption key, this returns a [VoiceUdpError::NoKey] error. + /// + /// If the UDP socket is broken, this returns a [VoiceUdpError::BrokenSocket] error. + pub async fn send_opus_data( + &self, + timestamp: u32, + payload: Vec, + ) -> Result<(), VoiceUdpError> { + let voice_ready_data_result = self.data.read().await.ready_data.clone(); + if voice_ready_data_result.is_none() { + return Err(VoiceUdpError::NoData); + } + + let ssrc = voice_ready_data_result.unwrap().ssrc; + let sequence_number = self.data.read().await.last_sequence_number.wrapping_add(1); + self.data.write().await.last_sequence_number = sequence_number; + + let payload_len = payload.len(); + + let rtp_data = discortp::rtp::Rtp { + // Always the same + version: 2, + padding: 0, + extension: 0, + csrc_count: 0, + csrc_list: Vec::new(), + marker: 0, + payload_type: discortp::rtp::RtpType::Dynamic(120), + // Actually variable + sequence: sequence_number.into(), + timestamp: timestamp.into(), + ssrc, + payload, + }; + + let buffer_size = payload_len + RTP_HEADER_SIZE as usize; + + let mut buffer = vec![0; buffer_size]; + + let mut rtp_packet = discortp::rtp::MutableRtpPacket::new(&mut buffer).expect("Mangled rtp packet creation buffer, something is very wrong. Please open an issue on the chorus github: https://github.com/polyphony-chat/chorus/issues/new"); + rtp_packet.populate(&rtp_data); + + self.send_rtp_packet(rtp_packet).await + } + + /// Encrypts and sends and rtp packet. + /// + /// # Errors + /// If we have not received an encryption key, this returns a [VoiceUdpError::NoKey] error. + /// + /// If the Udp socket is broken, this returns a [VoiceUdpError::BrokenSocket] error. + pub async fn send_rtp_packet( + &self, + packet: discortp::rtp::MutableRtpPacket<'_>, + ) -> Result<(), VoiceUdpError> { + let mut buffer = self.encrypt_rtp_packet_payload(&packet).await?; + let new_packet = discortp::rtp::MutableRtpPacket::new(&mut buffer).unwrap(); + self.send_encrypted_rtp_packet(new_packet.consume_to_immutable()) + .await?; + Ok(()) + } + + /// Encrypts an unencrypted rtp packet, returning a copy of the packet's bytes with an + /// encrypted payload + /// + /// # Errors + /// If we have not received an encryption key, this returns a [VoiceUdpError::NoKey] error. + /// + /// When using voice encryption modes which require special nonce generation, and said generation fails, this returns a [VoiceUdpError::FailedNonceGeneration] error. + pub async fn encrypt_rtp_packet_payload( + &self, + packet: &discortp::rtp::MutableRtpPacket<'_>, + ) -> Result, VoiceUdpError> { + let payload = packet.payload(); + + let session_description_result = self.data.read().await.session_description.clone(); + + // We are trying to encrypt, but have not received SessionDescription yet, + // which contains the secret key. + if session_description_result.is_none() { + return Err(VoiceUdpError::NoKey); + } + + let session_description = session_description_result.unwrap(); + + let mut nonce_bytes = match session_description.encryption_mode { + VoiceEncryptionMode::Xsalsa20Poly1305 => get_xsalsa20_poly1305_nonce(packet.packet()), + VoiceEncryptionMode::Xsalsa20Poly1305Suffix => { + // Generate 24 random bytes + let mut random_destinaton: Vec = vec![0; 24]; + let random_result = getrandom(&mut random_destinaton); + if let Err(e) = random_result { + return Err(VoiceUdpError::FailedNonceGeneration { + error: format!("{:?}", e), + }); + } + random_destinaton + } + VoiceEncryptionMode::Xsalsa20Poly1305Lite => { + // "Incremental 4 bytes (32bit) int value" + let mut data_lock = self.data.write().await; + let nonce = data_lock + .last_udp_encryption_nonce + .unwrap_or_default() + .wrapping_add(1); + + data_lock.last_udp_encryption_nonce = Some(nonce); + drop(data_lock); + // TODO: Is big endian correct? This is not documented anywhere + let mut bytes = nonce.to_be_bytes().to_vec(); + + // This is 4 bytes, it has to be a different size, appends 0s + while bytes.len() < 24 { + bytes.push(0); + } + bytes + } + _ => { + error!( + "This voice encryption mode ({:?}) is not yet implemented.", + session_description.encryption_mode + ); + return Err(VoiceUdpError::EncryptionModeNotImplemented { + encryption_mode: format!("{:?}", session_description.encryption_mode), + }); + } + }; + + let key = GenericArray::from_slice(&session_description.secret_key); + + let encryption_result; + + if session_description.encryption_mode.is_xsalsa20_poly1305() { + let nonce = GenericArray::from_slice(&nonce_bytes); + + let encryptor = XSalsa20Poly1305::new(key); + + encryption_result = encryptor.encrypt(nonce, payload); + } + // Note: currently unused because I have no idea what the AeadAes256Gcm nonce is + /*else if session_description.encryption_mode.is_aead_aes256_gcm() { + let nonce = GenericArray::from_slice(&nonce_bytes); + + let encryptor = Aes256Gcm::new(key); + + encryption_result = encryptor.encrypt(nonce, payload); + + }*/ + else { + error!( + "This voice encryption mode ({:?}) is not yet implemented.", + session_description.encryption_mode + ); + return Err(VoiceUdpError::EncryptionModeNotImplemented { + encryption_mode: format!("{:?}", session_description.encryption_mode), + }); + } + + if encryption_result.is_err() { + // Safety: If encryption fails here, it's chorus' fault, and it makes no sense to + // return the error to the user. + // + // This is not an error the user should account for, which is why we throw it here. + panic!("{}", VoiceUdpError::FailedEncryption); + } + + let mut encrypted_payload = encryption_result.unwrap(); + + // Append the nonce bytes, if needed + // All other encryption modes have an explicit nonce, whereas Xsalsa20Poly1305 + // has the nonce as the rtp header. + if session_description.encryption_mode != VoiceEncryptionMode::Xsalsa20Poly1305 { + encrypted_payload.append(&mut nonce_bytes); + } + + // We need to allocate a new buffer, since the old one is too small for our new encrypted + // data + let buffer_size = encrypted_payload.len() + RTP_HEADER_SIZE as usize; + + let mut new_buffer: Vec = Vec::with_capacity(buffer_size); + + let mut rtp_header = packet.packet().to_vec()[0..RTP_HEADER_SIZE as usize].to_vec(); + + new_buffer.append(&mut rtp_header); + new_buffer.append(&mut encrypted_payload); + + Ok(new_buffer) + } + + /// Sends an (already encrypted) rtp packet to the connection. + /// + /// # Errors + /// If the Udp socket is broken, this returns a [VoiceUdpError::BrokenSocket] error. + pub async fn send_encrypted_rtp_packet( + &self, + packet: discortp::rtp::RtpPacket<'_>, + ) -> Result<(), VoiceUdpError> { + let raw_bytes = packet.packet(); + + let send_res = self.socket.send(raw_bytes).await; + if let Err(e) = send_res { + return Err(VoiceUdpError::BrokenSocket { + error: format!("{:?}", e), + }); + } + + trace!("VUDP: Sent rtp packet!"); + + Ok(()) + } +} diff --git a/src/voice/udp/handler.rs b/src/voice/udp/handler.rs new file mode 100644 index 0000000..c21709b --- /dev/null +++ b/src/voice/udp/handler.rs @@ -0,0 +1,356 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +use std::{net::SocketAddr, sync::Arc}; + +use crypto_secretbox::aead::Aead; +use crypto_secretbox::cipher::generic_array::GenericArray; +use crypto_secretbox::KeyInit; +use crypto_secretbox::XSalsa20Poly1305; + +use discortp::demux::Demuxed; +use discortp::discord::{ + IpDiscovery, IpDiscoveryPacket, IpDiscoveryType, MutableIpDiscoveryPacket, +}; +use discortp::rtcp::report::ReceiverReport; +use discortp::rtcp::report::SenderReport; +use discortp::{demux::demux, Packet}; +use tokio::sync::{Mutex, RwLock}; + +use super::UdpBackend; +use super::UdpSocket; + +use super::RTP_HEADER_SIZE; +use crate::errors::VoiceUdpError; +use crate::types::VoiceEncryptionMode; +use crate::voice::crypto::get_xsalsa20_poly1305_lite_nonce; +use crate::voice::crypto::get_xsalsa20_poly1305_nonce; +use crate::voice::crypto::get_xsalsa20_poly1305_suffix_nonce; +use crate::voice::voice_data::VoiceData; + +use super::{events::VoiceUDPEvents, UdpHandle}; + +use log::*; + +#[derive(Debug)] +/// The main UDP struct, which handles receiving, parsing and decrypting the rtp packets +pub struct UdpHandler { + events: Arc>, + pub data: Arc>, + socket: Arc, +} + +impl UdpHandler { + /// Spawns a new UDP handler and performs IP discovery. + /// + /// Mutates the given data_reference with the IP discovery data. + pub async fn spawn( + data_reference: Arc>, + url: SocketAddr, + ssrc: u32, + ) -> Result { + let udp_socket = UdpBackend::connect(url).await?; + + // First perform ip discovery + let ip_discovery = IpDiscovery { + pkt_type: IpDiscoveryType::Request, + ssrc, + length: 70, + address: Vec::new(), + port: 0, + payload: Vec::new(), + }; + + // Minimum size with an empty Address value, + 64 bytes for the actual address size + let size = IpDiscoveryPacket::minimum_packet_size() + 64; + + let mut buf: Vec = vec![0; size]; + + // Safety: expect is justified here, since this is an error which should never happen. + // If this errors, the code at fault is the buffer size calculation. + let mut ip_discovery_packet = + MutableIpDiscoveryPacket::new(&mut buf).expect("Mangled ip discovery packet creation buffer, something is very wrong. Please open an issue on the chorus github: https://github.com/polyphony-chat/chorus/issues/new"); + + ip_discovery_packet.populate(&ip_discovery); + + let data = ip_discovery_packet.packet(); + + debug!("VUDP: Sending Ip Discovery {:?}", &data); + + let send_res = udp_socket.send(data).await; + if let Err(e) = send_res { + return Err(VoiceUdpError::BrokenSocket { + error: format!("{:?}", e), + }); + } + + debug!("VUDP: Sent packet discovery request"); + + // Handle the ip discovery response + let received_size_or_err = udp_socket.recv(&mut buf).await; + + if let Err(e) = received_size_or_err { + return Err(VoiceUdpError::BrokenSocket { + error: format!("{:?}", e), + }); + } + + let receieved_ip_discovery = IpDiscoveryPacket::new(&buf).expect("Could not make ipdiscovery packet from received data, something is very wrong. Please open an issue on the chorus github: https://github.com/polyphony-chat/chorus/issues/new"); + + debug!( + "VUDP: Received ip discovery!!! {:?}", + receieved_ip_discovery + ); + + let ip_discovery = IpDiscovery { + pkt_type: receieved_ip_discovery.get_pkt_type(), + length: receieved_ip_discovery.get_length(), + ssrc: receieved_ip_discovery.get_ssrc(), + address: receieved_ip_discovery.get_address(), + port: receieved_ip_discovery.get_port(), + payload: Vec::new(), + }; + + let mut data_reference_lock = data_reference.write().await; + data_reference_lock.ip_discovery = Some(ip_discovery); + drop(data_reference_lock); + + let socket = Arc::new(udp_socket); + + let events = VoiceUDPEvents::default(); + let shared_events = Arc::new(Mutex::new(events)); + + let mut handler = UdpHandler { + events: shared_events.clone(), + data: data_reference.clone(), + socket: socket.clone(), + }; + + // Now we can continuously check for messages in a different task + tokio::spawn(async move { + handler.listen_task().await; + }); + + Ok(UdpHandle { + events: shared_events, + socket, + data: data_reference, + }) + } + + /// The main listen task; + /// + /// Receives UDP messages and parses them. + async fn listen_task(&mut self) { + loop { + // FIXME: is there a max size for these packets? + // Allocating 512 bytes seems a bit extreme + // + // Update: see + // > "The RTP standard does not set a maximum size.." + // + // The theoretical max for this buffer would be 1458 bytes, but that is imo + // unreasonable to allocate for every message. + let mut buf: Vec = vec![0; 512]; + + let result = self.socket.recv(&mut buf).await; + if let Ok(size) = result { + self.handle_message(&buf[0..size]).await; + continue; + } + + warn!("VUDP: Voice UDP is broken, closing connection"); + break; + } + } + + /// Handles a message buf + async fn handle_message(&self, buf: &[u8]) { + let parsed = demux(buf); + + match parsed { + Demuxed::Rtp(rtp) => { + trace!("VUDP: Parsed packet as rtp! {:?}", buf); + + let decryption_result = self.decrypt_rtp_packet_payload(&rtp).await; + + if let Err(err) = decryption_result { + match err { + VoiceUdpError::NoKey => { + warn!("VUDP: Received encyrpted voice data, but no encryption key, CANNOT DECRYPT!"); + return; + } + VoiceUdpError::FailedDecryption => { + warn!("VUDP: Failed to decrypt voice data!"); + return; + } + _ => { + error!("VUDP: Failed to decrypt voice data: {}", err); + return; + } + } + } + + let decrypted = decryption_result.unwrap(); + + trace!("VUDP: Successfully decrypted voice data!"); + + let rtp_with_decrypted_data = discortp::rtp::Rtp { + ssrc: rtp.get_ssrc(), + marker: rtp.get_marker(), + version: rtp.get_version(), + padding: rtp.get_padding(), + sequence: rtp.get_sequence(), + extension: rtp.get_extension(), + timestamp: rtp.get_timestamp(), + csrc_list: rtp.get_csrc_list(), + csrc_count: rtp.get_csrc_count(), + payload_type: rtp.get_payload_type(), + payload: decrypted, + }; + + self.events + .lock() + .await + .rtp + .notify(rtp_with_decrypted_data) + .await; + } + Demuxed::Rtcp(rtcp) => { + trace!("VUDP: Parsed packet as rtcp!"); + + let rtcp_data = match rtcp { + discortp::rtcp::RtcpPacket::KnownType(knowntype) => { + discortp::rtcp::Rtcp::KnownType(knowntype) + } + discortp::rtcp::RtcpPacket::SenderReport(senderreport) => { + discortp::rtcp::Rtcp::SenderReport(SenderReport { + payload: senderreport.payload().to_vec(), + padding: senderreport.get_padding(), + version: senderreport.get_version(), + ssrc: senderreport.get_ssrc(), + pkt_length: senderreport.get_pkt_length(), + packet_type: senderreport.get_packet_type(), + rx_report_count: senderreport.get_rx_report_count(), + }) + } + discortp::rtcp::RtcpPacket::ReceiverReport(receiverreport) => { + discortp::rtcp::Rtcp::ReceiverReport(ReceiverReport { + payload: receiverreport.payload().to_vec(), + padding: receiverreport.get_padding(), + version: receiverreport.get_version(), + ssrc: receiverreport.get_ssrc(), + pkt_length: receiverreport.get_pkt_length(), + packet_type: receiverreport.get_packet_type(), + rx_report_count: receiverreport.get_rx_report_count(), + }) + } + _ => { + unreachable!(); + } + }; + + self.events.lock().await.rtcp.notify(rtcp_data).await; + } + Demuxed::FailedParse(e) => { + trace!("VUDP: Failed to parse packet: {:?}", e); + } + Demuxed::TooSmall => { + unreachable!() + } + } + } + + /// Decrypts an encrypted rtp packet, returning a decrypted copy of the packet's payload + /// bytes. + /// + /// # Errors + /// If we have not received an encryption key, this returns a [VoiceUdpError::NoKey] error. + /// + /// If the decryption fails, this returns a [VoiceUdpError::FailedDecryption]. + pub async fn decrypt_rtp_packet_payload( + &self, + rtp: &discortp::rtp::RtpPacket<'_>, + ) -> Result, VoiceUdpError> { + let packet_bytes = rtp.packet(); + + let mut ciphertext: Vec = + packet_bytes[(RTP_HEADER_SIZE as usize)..packet_bytes.len()].to_vec(); + + let session_description_result = self.data.read().await.session_description.clone(); + + // We are trying to decrypt, but have not received SessionDescription yet, + // which contains the secret key + if session_description_result.is_none() { + return Err(VoiceUdpError::NoKey); + } + + let session_description = session_description_result.unwrap(); + + let nonce_bytes = match session_description.encryption_mode { + VoiceEncryptionMode::Xsalsa20Poly1305 => get_xsalsa20_poly1305_nonce(packet_bytes), + VoiceEncryptionMode::Xsalsa20Poly1305Suffix => { + // Remove the suffix from the ciphertext + ciphertext = ciphertext[0..ciphertext.len() - 24].to_vec(); + get_xsalsa20_poly1305_suffix_nonce(packet_bytes) + } + // Note: Rtpsize is documented by userdoccers to be the same, yet decryption + // doesn't work. + // + // I have no idea how Rtpsize works. + VoiceEncryptionMode::Xsalsa20Poly1305Lite => { + // Remove the suffix from the ciphertext + ciphertext = ciphertext[0..ciphertext.len() - 4].to_vec(); + get_xsalsa20_poly1305_lite_nonce(packet_bytes) + } + _ => { + error!( + "This voice encryption mode ({:?}) is not yet implemented.", + session_description.encryption_mode + ); + return Err(VoiceUdpError::EncryptionModeNotImplemented { + encryption_mode: format!("{:?}", session_description.encryption_mode), + }); + } + }; + + let key = GenericArray::from_slice(&session_description.secret_key); + + let decryption_result; + + if session_description.encryption_mode.is_xsalsa20_poly1305() { + let nonce = GenericArray::from_slice(&nonce_bytes); + + let decryptor = XSalsa20Poly1305::new(key); + + decryption_result = decryptor.decrypt(nonce, ciphertext.as_ref()); + } + // Note: currently unused because I have no idea what the AeadAes256Gcm nonce is + /*else if session_description.encryption_mode.is_aead_aes256_gcm() { + let nonce = GenericArray::from_slice(&nonce_bytes); + + let decryptor = Aes256Gcm::new(key); + + decryption_result = decryptor.decrypt(nonce, ciphertext.as_ref()); + + }*/ + else { + error!( + "This voice encryption mode ({:?}) is not yet implemented.", + session_description.encryption_mode + ); + return Err(VoiceUdpError::EncryptionModeNotImplemented { + encryption_mode: format!("{:?}", session_description.encryption_mode), + }); + } + + // Note: this may seem like we are throwing away valuable error handling data, + // but the decryption error provides no extra info. + if decryption_result.is_err() { + return Err(VoiceUdpError::FailedDecryption); + } + + Ok(decryption_result.unwrap()) + } +} diff --git a/src/voice/udp/mod.rs b/src/voice/udp/mod.rs new file mode 100644 index 0000000..5ae839a --- /dev/null +++ b/src/voice/udp/mod.rs @@ -0,0 +1,18 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +//! Defines the UDP component of voice communications, sending and receiving raw rtp data. + +/// See +/// This always adds up to 12 bytes +const RTP_HEADER_SIZE: u8 = 12; + +pub mod backends; +pub mod events; +pub mod handle; +pub mod handler; + +pub use backends::*; +pub use handle::*; +pub use handler::*; diff --git a/src/voice/voice_data.rs b/src/voice/voice_data.rs new file mode 100644 index 0000000..6bc408c --- /dev/null +++ b/src/voice/voice_data.rs @@ -0,0 +1,25 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +use discortp::discord::IpDiscovery; + +use crate::types::{SessionDescription, Snowflake, VoiceReady, VoiceServerUpdate}; + +#[derive(Debug, Default)] +/// Saves data shared between parts of the voice architecture; +/// +/// Struct used to give the UDP connection data received from the gateway. +pub struct VoiceData { + pub server_data: Option, + pub ready_data: Option, + pub session_description: Option, + pub user_id: Snowflake, + pub session_id: String, + /// The last sequence number we used, has to be incremented by one every time we send a message + pub last_sequence_number: u16, + pub ip_discovery: Option, + + /// The last UDP encryption nonce, if we are using an encryption mode with incremental nonces. + pub last_udp_encryption_nonce: Option, +} diff --git a/tests/gateway.rs b/tests/gateway.rs index 771db02..9f72a64 100644 --- a/tests/gateway.rs +++ b/tests/gateway.rs @@ -4,17 +4,26 @@ mod common; +use std::sync::Arc; +use std::time::Duration; + +use async_trait::async_trait; use chorus::errors::GatewayError; use chorus::gateway::*; use chorus::types::{ - self, Channel, ChannelCreateSchema, ChannelModifySchema, IntoShared, RoleCreateModifySchema, - RoleObject, + self, Channel, ChannelCreateSchema, ChannelModifySchema, GatewayReady, IntoShared, + RoleCreateModifySchema, RoleObject, }; #[cfg(target_arch = "wasm32")] use wasm_bindgen_test::*; #[cfg(target_arch = "wasm32")] wasm_bindgen_test_configure!(run_in_browser); +#[cfg(not(target_arch = "wasm32"))] +use tokio::time::sleep; +#[cfg(target_arch = "wasm32")] +use wasmtimer::tokio::sleep; + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] #[cfg_attr(not(target_arch = "wasm32"), tokio::test)] /// Tests establishing a connection (hello and heartbeats) on the local gateway; @@ -25,6 +34,18 @@ async fn test_gateway_establish() { common::teardown(bundle).await } +#[derive(Debug)] +struct GatewayReadyObserver { + channel: tokio::sync::mpsc::Sender<()>, +} + +#[async_trait] +impl Observer for GatewayReadyObserver { + async fn update(&self, _data: &GatewayReady) { + self.channel.send(()).await.unwrap(); + } +} + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] #[cfg_attr(not(target_arch = "wasm32"), tokio::test)] /// Tests establishing a connection and authenticating @@ -33,10 +54,35 @@ async fn test_gateway_authenticate() { let gateway: GatewayHandle = Gateway::spawn(bundle.urls.wss.clone()).await.unwrap(); + let (ready_send, mut ready_receive) = tokio::sync::mpsc::channel(1); + + let observer = Arc::new(GatewayReadyObserver { + channel: ready_send, + }); + + gateway + .events + .lock() + .await + .session + .ready + .subscribe(observer); + let mut identify = types::GatewayIdentifyPayload::common(); identify.token = bundle.user.token.clone(); gateway.send_identify(identify).await; + + tokio::select! { + // Fail, we timed out waiting for it + () = sleep(Duration::from_secs(20)) => { + println!("Timed out waiting for event, failing.."); + assert!(false); + } + // Sucess, we have received it + Some(_) = ready_receive.recv() => {} + }; + common::teardown(bundle).await } From a95dda5a8736fe554312b4a90c6eb004dedfd0d1 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Tue, 16 Apr 2024 17:38:36 +0200 Subject: [PATCH 52/53] change version to 0.15.0 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f1ee7e8..dc49230 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,7 @@ To get started with Chorus, import it into your project by adding the following ```toml [dependencies] -chorus = "0.14.0" +chorus = "0.15.0" ``` ### Establishing a Connection From 0cbc88503ba9582d5e7d196135205f632450c5a4 Mon Sep 17 00:00:00 2001 From: kozabrada123 <59031733+kozabrada123@users.noreply.github.com> Date: Tue, 16 Apr 2024 17:40:38 +0200 Subject: [PATCH 53/53] Limit test actions to 30 minutes (#489) fix: limit all tests to 30 minutes --- .github/workflows/build_and_test.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml index 4c38d19..0f680cc 100644 --- a/.github/workflows/build_and_test.yml +++ b/.github/workflows/build_and_test.yml @@ -13,6 +13,7 @@ jobs: linux: runs-on: ubuntu-latest + timeout-minutes: 30 steps: - uses: actions/checkout@v4 @@ -75,6 +76,7 @@ jobs: # SAFARIDRIVER=$(which safaridriver) cargo test --target wasm32-unknown-unknown --no-default-features --features="client, rt" --no-fail-fast wasm-gecko: runs-on: macos-latest + timeout-minutes: 30 steps: - uses: actions/checkout@v4 - name: Clone spacebar server @@ -103,6 +105,7 @@ jobs: GECKODRIVER=$(which geckodriver) cargo test --target wasm32-unknown-unknown --no-default-features --features="client, rt, voice_gateway" wasm-chrome: runs-on: macos-latest + timeout-minutes: 30 steps: - uses: actions/checkout@v4 - name: Clone spacebar server