This commit is contained in:
kozabrada123 2024-07-20 16:07:23 +00:00 committed by GitHub
commit 5d181445c3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
151 changed files with 4061 additions and 1925 deletions

View File

@ -46,6 +46,26 @@ jobs:
cargo build --verbose --all-features cargo build --verbose --all-features
cargo test --verbose --all-features cargo test --verbose --all-features
fi fi
- name: Check common non-default feature configurations
run: |
echo "No features:"
cargo check --features="" --no-default-features
echo "Only client:"
cargo check --features="client" --no-default-features
echo "Only backend:"
cargo check --features="backend" --no-default-features
echo "Only voice:"
cargo check --features="voice" --no-default-features
echo "Only voice gateway:"
cargo check --features="voice_gateway" --no-default-features
echo "Backend + client:"
cargo check --features="backend, client" --no-default-features
echo "Backend + voice:"
cargo check --features="backend, voice" --no-default-features
echo "Backend + voice gateway:"
cargo check --features="backend, voice_gateway" --no-default-features
echo "Client + voice gateway:"
cargo check --features="client, voice_gateway" --no-default-features
# wasm-safari: # wasm-safari:
# runs-on: macos-latest # runs-on: macos-latest
# steps: # steps:
@ -75,7 +95,7 @@ jobs:
# cargo binstall --no-confirm wasm-bindgen-cli --version "0.2.88" --force # cargo binstall --no-confirm wasm-bindgen-cli --version "0.2.88" --force
# SAFARIDRIVER=$(which safaridriver) cargo test --target wasm32-unknown-unknown --no-default-features --features="client, rt" --no-fail-fast # SAFARIDRIVER=$(which safaridriver) cargo test --target wasm32-unknown-unknown --no-default-features --features="client, rt" --no-fail-fast
wasm-gecko: wasm-gecko:
runs-on: macos-latest runs-on: ubuntu-latest
timeout-minutes: 30 timeout-minutes: 30
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
@ -101,10 +121,10 @@ jobs:
run: | run: |
rustup target add wasm32-unknown-unknown 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 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 cargo binstall --no-confirm wasm-bindgen-cli --version "0.2.92" --force
GECKODRIVER=$(which geckodriver) cargo test --target wasm32-unknown-unknown --no-default-features --features="client, rt, voice_gateway" GECKODRIVER=$(which geckodriver) cargo test --target wasm32-unknown-unknown --no-default-features --features="client, rt, voice_gateway"
wasm-chrome: wasm-chrome:
runs-on: macos-latest runs-on: ubuntu-latest
timeout-minutes: 30 timeout-minutes: 30
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
@ -130,5 +150,5 @@ jobs:
run: | run: |
rustup target add wasm32-unknown-unknown 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 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 cargo binstall --no-confirm wasm-bindgen-cli --version "0.2.92" --force
CHROMEDRIVER=$(which chromedriver) cargo test --target wasm32-unknown-unknown --no-default-features --features="client, rt, voice_gateway" CHROMEDRIVER=$(which chromedriver) cargo test --target wasm32-unknown-unknown --no-default-features --features="client, rt, voice_gateway"

1092
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -13,10 +13,10 @@ rust-version = "1.67.1"
[features] [features]
default = ["client", "rt-multi-thread"] default = ["client", "rt-multi-thread"]
backend = ["dep:poem", "dep:sqlx"] backend = ["poem", "sqlx"]
rt-multi-thread = ["tokio/rt-multi-thread"] rt-multi-thread = ["tokio/rt-multi-thread"]
rt = ["tokio/rt"] rt = ["tokio/rt"]
client = [] client = ["flate2"]
voice = ["voice_udp", "voice_gateway"] voice = ["voice_udp", "voice_gateway"]
voice_udp = ["dep:discortp", "dep:crypto_secretbox"] voice_udp = ["dep:discortp", "dep:crypto_secretbox"]
voice_gateway = [] voice_gateway = []
@ -28,43 +28,51 @@ serde_json = { version = "1.0.111", features = ["raw_value"] }
serde-aux = "4.3.1" serde-aux = "4.3.1"
serde_with = "3.4.0" serde_with = "3.4.0"
serde_repr = "0.1.18" serde_repr = "0.1.18"
reqwest = { features = ["multipart", "json"], version = "0.11.23" } reqwest = { features = [
"multipart",
"json",
"rustls-tls-webpki-roots",
], version = "=0.11.26", default-features = false }
url = "2.5.0" url = "2.5.0"
chrono = { version = "0.4.31", features = ["serde"] } chrono = { version = "0.4.31", features = ["serde"] }
regex = "1.10.2" regex = "1.10.2"
custom_error = "1.9.2" custom_error = "1.9.2"
futures-util = "0.3.30" futures-util = "0.3.30"
http = "0.2.11" http = "0.2.12"
base64 = "0.21.7" base64 = "0.21.7"
bitflags = { version = "2.4.1", features = ["serde"] } bitflags = { version = "2.4.1", features = ["serde"] }
lazy_static = "1.4.0" lazy_static = "1.4.0"
poem = { version = "1.3.59", optional = true } poem = { version = "3.0.1", features = ["multipart"], optional = true }
thiserror = "1.0.56" thiserror = "1.0.56"
jsonwebtoken = "8.3.0" jsonwebtoken = "8.3.0"
log = "0.4.20" log = "0.4.20"
async-trait = "0.1.77" async-trait = "0.1.77"
chorus-macros = "0.2.0" chorus-macros = { path = "./chorus-macros", version = "0" } # Note: version here is used when releasing. This will use the latest release. Make sure to republish the crate when code in macros is changed!
sqlx = { version = "0.7.3", features = [ sqlx = { version = "0.7.3", features = [
"mysql", "mysql",
"sqlite", "sqlite",
"json", "json",
"chrono", "chrono",
"ipnetwork", "ipnetwork",
"runtime-tokio-native-tls", "runtime-tokio-rustls",
"any", "any",
], optional = true } ], optional = true }
discortp = { version = "0.5.0", optional = true, features = ["rtp", "discord", "demux"] } discortp = { version = "0.5.0", optional = true, features = [
"rtp",
"discord",
"demux",
] }
crypto_secretbox = { version = "0.1.1", optional = true } crypto_secretbox = { version = "0.1.1", optional = true }
rand = "0.8.5" rand = "0.8.5"
flate2 = { version = "1.0.30", optional = true }
webpki-roots = "0.26.3"
pubserve = { version = "1.1.0-alpha.1", features = ["async", "send"] }
[target.'cfg(not(target_arch = "wasm32"))'.dependencies] [target.'cfg(not(target_arch = "wasm32"))'.dependencies]
rustls = "0.21.10" rustls = "0.21.10"
rustls-native-certs = "0.6.3"
tokio-tungstenite = { version = "0.20.1", features = [ tokio-tungstenite = { version = "0.20.1", features = [
"rustls-tls-native-roots", "rustls-tls-webpki-roots",
"rustls-native-certs",
] } ] }
native-tls = "0.2.11"
hostname = "0.3.1" hostname = "0.3.1"
getrandom = { version = "0.2.12" } getrandom = { version = "0.2.12" }
@ -76,5 +84,6 @@ wasmtimer = "0.2.0"
[dev-dependencies] [dev-dependencies]
lazy_static = "1.4.0" lazy_static = "1.4.0"
wasm-bindgen-test = "0.3.39" wasm-bindgen-test = "0.3.42"
wasm-bindgen = "0.2.89" wasm-bindgen = "0.2.92"
simple_logger = { version = "5.0.0", default-features = false }

View File

@ -97,7 +97,25 @@ All major desktop operating systems (Windows, macOS (aarch64/x86_64), Linux (aar
`wasm32-unknown-unknown` is a supported compilation target on versions `0.12.0` and up. This allows you to use `wasm32-unknown-unknown` is a supported compilation target on versions `0.12.0` and up. This allows you to use
Chorus in your browser, or in any other environment that supports WebAssembly. Chorus in your browser, or in any other environment that supports WebAssembly.
We recommend checking out the examples directory, as well as the documentation for more information. To compile for `wasm32-unknown-unknown`, execute the following command:
```sh
cargo build --target=wasm32-unknown-unknown --no-default-features
```
The following features are supported on `wasm32-unknown-unknown`:
| Feature | WASM Support |
| ----------------- | ------------ |
| `client` | ✅ |
| `rt` | ✅ |
| `rt-multi-thread` | ❌ |
| `backend` | ❌ |
| `voice` | ❌ |
| `voice_udp` | ❌ |
| `voice_gateway` | ✅ |
We recommend checking out the "examples" directory, as well as the documentation for more information.
## MSRV (Minimum Supported Rust Version) ## MSRV (Minimum Supported Rust Version)

6
build-wasm.sh Executable file
View File

@ -0,0 +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 https://mozilla.org/MPL/2.0/.
#!/bin/sh
cargo build --no-default-features --target=wasm32-unknown-unknown "$@"

View File

@ -15,7 +15,7 @@ dependencies = [
[[package]] [[package]]
name = "chorus-macros" name = "chorus-macros"
version = "0.1.0" version = "0.4.1"
dependencies = [ dependencies = [
"async-trait", "async-trait",
"quote", "quote",

View File

@ -1,9 +1,10 @@
[package] [package]
name = "chorus-macros" name = "chorus-macros"
version = "0.2.0" version = "0.4.1"
edition = "2021" edition = "2021"
license = "AGPL-3.0" license = "MPL-2.0"
description = "Macros for the chorus crate." description = "Macros for the chorus crate."
repository = "https://github.com/polyphony-chat/chorus"
[lib] [lib]
proc-macro = true proc-macro = true

View File

@ -6,6 +6,18 @@ use proc_macro::TokenStream;
use quote::quote; use quote::quote;
use syn::{parse_macro_input, Data, DeriveInput, Field, Fields, FieldsNamed}; use syn::{parse_macro_input, Data, DeriveInput, Field, Fields, FieldsNamed};
#[proc_macro_derive(WebSocketEvent)]
pub fn websocket_event_macro_derive(input: TokenStream) -> TokenStream {
let ast: syn::DeriveInput = syn::parse(input).unwrap();
let name = &ast.ident;
quote! {
impl WebSocketEvent for #name {}
}
.into()
}
#[proc_macro_derive(Updateable)] #[proc_macro_derive(Updateable)]
pub fn updateable_macro_derive(input: TokenStream) -> TokenStream { pub fn updateable_macro_derive(input: TokenStream) -> TokenStream {
let ast: syn::DeriveInput = syn::parse(input).unwrap(); let ast: syn::DeriveInput = syn::parse(input).unwrap();
@ -143,3 +155,70 @@ pub fn composite_derive(input: TokenStream) -> TokenStream {
_ => panic!("Composite derive macro only supports structs"), _ => panic!("Composite derive macro only supports structs"),
} }
} }
#[proc_macro_derive(SqlxBitFlags)]
pub fn sqlx_bitflag_derive(input: TokenStream) -> TokenStream {
let ast: syn::DeriveInput = syn::parse(input).unwrap();
let name = &ast.ident;
quote!{
#[cfg(feature = "sqlx")]
impl sqlx::Type<sqlx::MySql> for #name {
fn type_info() -> sqlx::mysql::MySqlTypeInfo {
u64::type_info()
}
}
#[cfg(feature = "sqlx")]
impl<'q> sqlx::Encode<'q, sqlx::MySql> for #name {
fn encode_by_ref(&self, buf: &mut <sqlx::MySql as sqlx::database::HasArguments<'q>>::ArgumentBuffer) -> sqlx::encode::IsNull {
u64::encode_by_ref(&self.bits(), buf)
}
}
#[cfg(feature = "sqlx")]
impl<'q> sqlx::Decode<'q, sqlx::MySql> for #name {
fn decode(value: <sqlx::MySql as sqlx::database::HasValueRef<'q>>::ValueRef) -> Result<Self, sqlx::error::BoxDynError> {
u64::decode(value).map(|d| #name::from_bits(d).unwrap())
}
}
}
.into()
}
#[proc_macro_derive(SerdeBitFlags)]
pub fn serde_bitflag_derive(input: TokenStream) -> TokenStream {
let ast: syn::DeriveInput = syn::parse(input).unwrap();
let name = &ast.ident;
quote! {
impl std::str::FromStr for #name {
type Err = std::num::ParseIntError;
fn from_str(s: &str) -> Result<#name, Self::Err> {
s.parse::<u64>().map(#name::from_bits).map(|f| f.unwrap_or(#name::empty()))
}
}
impl serde::Serialize for #name {
fn serialize<S: serde::ser::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
serializer.serialize_str(&self.bits().to_string())
}
}
impl<'de> serde::Deserialize<'de> for #name {
fn deserialize<D>(deserializer: D) -> Result<#name, D::Error> where D: serde::de::Deserializer<'de> + Sized {
// let s = String::deserialize(deserializer)?.parse::<u64>().map_err(serde::de::Error::custom)?;
let s = crate::types::serde::string_or_u64(deserializer)?;
// Note: while truncating may not be ideal, it's better than a panic if there are
// extra flags
Ok(Self::from_bits_truncate(s))
}
}
}
.into()
}

View File

@ -3,6 +3,8 @@
// file, You can obtain one at http://mozilla.org/MPL/2.0/. // file, You can obtain one at http://mozilla.org/MPL/2.0/.
// This example showcase how to properly use gateway observers. // This example showcase how to properly use gateway observers.
// (This assumes you have a manually created gateway, if you created
// a ChorusUser by e.g. logging in, you can access the gateway with user.gateway)
// //
// To properly run it, you will need to change the token below. // To properly run it, you will need to change the token below.
@ -12,12 +14,12 @@ const TOKEN: &str = "";
const GATEWAY_URL: &str = "wss://gateway.old.server.spacebar.chat/"; const GATEWAY_URL: &str = "wss://gateway.old.server.spacebar.chat/";
use async_trait::async_trait; use async_trait::async_trait;
use chorus::gateway::Gateway; use chorus::gateway::{Gateway, GatewayOptions};
use chorus::{ use chorus::{
self, self,
gateway::Observer,
types::{GatewayIdentifyPayload, GatewayReady}, types::{GatewayIdentifyPayload, GatewayReady},
}; };
use pubserve::Subscriber;
use std::{sync::Arc, time::Duration}; use std::{sync::Arc, time::Duration};
use tokio::{self}; use tokio::{self};
@ -36,7 +38,7 @@ pub struct ExampleObserver {}
// The Observer trait can be implemented for a struct for a given websocketevent to handle observing it // The Observer trait can be implemented for a struct for a given websocketevent to handle observing it
// One struct can be an observer of multiple websocketevents, if needed // One struct can be an observer of multiple websocketevents, if needed
#[async_trait] #[async_trait]
impl Observer<GatewayReady> for ExampleObserver { impl Subscriber<GatewayReady> for ExampleObserver {
// After we subscribe to an event this function is called every time we receive it // After we subscribe to an event this function is called every time we receive it
async fn update(&self, _data: &GatewayReady) { async fn update(&self, _data: &GatewayReady) {
println!("Observed Ready!"); println!("Observed Ready!");
@ -47,8 +49,16 @@ impl Observer<GatewayReady> for ExampleObserver {
async fn main() { async fn main() {
let gateway_websocket_url = GATEWAY_URL.to_string(); let gateway_websocket_url = GATEWAY_URL.to_string();
// These options specify the encoding format, compression, etc
//
// For most cases the defaults should work, though some implementations
// might only support some formats or not support compression
let options = GatewayOptions::default();
// Initiate the gateway connection // Initiate the gateway connection
let gateway = Gateway::spawn(gateway_websocket_url).await.unwrap(); let gateway = Gateway::spawn(gateway_websocket_url, options)
.await
.unwrap();
// Create an instance of our observer // Create an instance of our observer
let observer = ExampleObserver {}; let observer = ExampleObserver {};

View File

@ -3,7 +3,7 @@
// file, You can obtain one at http://mozilla.org/MPL/2.0/. // file, You can obtain one at http://mozilla.org/MPL/2.0/.
// This example showcases how to initiate a gateway connection manually // This example showcases how to initiate a gateway connection manually
// (e. g. not through ChorusUser) // (e. g. not through ChorusUser or Instance)
// //
// To properly run it, you will need to modify the token below. // To properly run it, you will need to modify the token below.
@ -14,7 +14,7 @@ const GATEWAY_URL: &str = "wss://gateway.old.server.spacebar.chat/";
use std::time::Duration; use std::time::Duration;
use chorus::gateway::Gateway; use chorus::gateway::{Gateway, GatewayOptions};
use chorus::{self, types::GatewayIdentifyPayload}; use chorus::{self, types::GatewayIdentifyPayload};
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
@ -26,9 +26,15 @@ use wasmtimer::tokio::sleep;
#[tokio::main(flavor = "current_thread")] #[tokio::main(flavor = "current_thread")]
async fn main() { async fn main() {
let gateway_websocket_url = GATEWAY_URL.to_string(); let gateway_websocket_url = GATEWAY_URL.to_string();
// These options specify the encoding format, compression, etc
//
// For most cases the defaults should work, though some implementations
// might only support some formats or not support compression
let options = GatewayOptions::default();
// Initiate the gateway connection, starting a listener in one thread and a heartbeat handler in another // Initiate the gateway connection, starting a listener in one thread and a heartbeat handler in another
let gateway = Gateway::spawn(gateway_websocket_url).await.unwrap(); let gateway = Gateway::spawn(gateway_websocket_url, options).await.unwrap();
// At this point, we are connected to the server and are sending heartbeats, however we still haven't authenticated // At this point, we are connected to the server and are sending heartbeats, however we still haven't authenticated

18
semver_release_checks.yml Normal file
View File

@ -0,0 +1,18 @@
name: Semver release checks
on:
pull_request:
branches: ["main"]
env:
CARGO_TERM_COLOR: always
jobs:
semver-checks:
runs-on: ubuntu-latest
timeout-minutes: 60
steps:
- uses: actions/checkout@v4
- uses: obi1kenobi/cargo-semver-checks-action@v2

View File

@ -11,7 +11,7 @@ use crate::errors::ChorusResult;
use crate::gateway::Gateway; use crate::gateway::Gateway;
use crate::instance::{ChorusUser, Instance}; use crate::instance::{ChorusUser, Instance};
use crate::ratelimiter::ChorusRequest; use crate::ratelimiter::ChorusRequest;
use crate::types::{GatewayIdentifyPayload, LimitType, LoginResult, LoginSchema}; use crate::types::{GatewayIdentifyPayload, LimitType, LoginResult, LoginSchema, User};
impl Instance { impl Instance {
/// Logs into an existing account on the spacebar server. /// Logs into an existing account on the spacebar server.
@ -30,27 +30,22 @@ impl Instance {
// We do not have a user yet, and the UserRateLimits will not be affected by a login // We do not have a user yet, and the UserRateLimits will not be affected by a login
// request (since login is an instance wide limit), which is why we are just cloning the // request (since login is an instance wide limit), which is why we are just cloning the
// instances' limits to pass them on as user_rate_limits later. // instances' limits to pass them on as user_rate_limits later.
let mut shell = let mut user =
ChorusUser::shell(Arc::new(RwLock::new(self.clone())), "None".to_string()).await; ChorusUser::shell(Arc::new(RwLock::new(self.clone())), "None".to_string()).await;
let login_result = chorus_request let login_result = chorus_request
.deserialize_response::<LoginResult>(&mut shell) .deserialize_response::<LoginResult>(&mut user)
.await?; .await?;
let object = self.get_user(login_result.token.clone(), None).await?; user.set_token(login_result.token);
if self.limits_information.is_some() { user.settings = login_result.settings;
self.limits_information.as_mut().unwrap().ratelimits = shell.limits.clone().unwrap();
} let object = User::get(&mut user, None).await?;
*user.object.write().unwrap() = object;
let mut identify = GatewayIdentifyPayload::common(); let mut identify = GatewayIdentifyPayload::common();
let gateway = Gateway::spawn(self.urls.wss.clone()).await.unwrap(); identify.token = user.token();
identify.token = login_result.token.clone(); user.gateway.send_identify(identify).await;
gateway.send_identify(identify).await;
let user = ChorusUser::new(
Arc::new(RwLock::new(self.clone())),
login_result.token,
self.clone_limits_if_some(),
login_result.settings,
Arc::new(RwLock::new(object)),
gateway,
);
Ok(user) Ok(user)
} }
} }

View File

@ -23,26 +23,19 @@ pub mod register;
impl Instance { impl Instance {
/// Logs into an existing account on the spacebar server, using only a token. /// Logs into an existing account on the spacebar server, using only a token.
pub async fn login_with_token(&mut self, token: String) -> ChorusResult<ChorusUser> { pub async fn login_with_token(&mut self, token: String) -> ChorusResult<ChorusUser> {
let object_result = self.get_user(token.clone(), None).await; let mut user =
if let Err(e) = object_result { ChorusUser::shell(Arc::new(RwLock::new(self.clone())), token).await;
return Result::Err(e);
} let object = User::get(&mut user, None).await?;
let settings = User::get_settings(&mut user).await?;
*user.object.write().unwrap() = object;
*user.settings.write().unwrap() = settings;
let user_settings = User::get_settings(&token, &self.urls.api, &mut self.clone())
.await
.unwrap();
let mut identify = GatewayIdentifyPayload::common(); let mut identify = GatewayIdentifyPayload::common();
let gateway = Gateway::spawn(self.urls.wss.clone()).await.unwrap(); identify.token = user.token();
identify.token = token.clone(); user.gateway.send_identify(identify).await;
gateway.send_identify(identify).await;
let user = ChorusUser::new(
Arc::new(RwLock::new(self.clone())),
token.clone(),
self.clone_limits_if_some(),
Arc::new(RwLock::new(user_settings)),
Arc::new(RwLock::new(object_result.unwrap())),
gateway,
);
Ok(user) Ok(user)
} }
} }

View File

@ -8,7 +8,7 @@ use reqwest::Client;
use serde_json::to_string; use serde_json::to_string;
use crate::gateway::{Gateway, GatewayHandle}; use crate::gateway::{Gateway, GatewayHandle};
use crate::types::GatewayIdentifyPayload; use crate::types::{GatewayIdentifyPayload, User};
use crate::{ use crate::{
errors::ChorusResult, errors::ChorusResult,
instance::{ChorusUser, Instance, Token}, instance::{ChorusUser, Instance, Token},
@ -37,29 +37,25 @@ impl Instance {
// We do not have a user yet, and the UserRateLimits will not be affected by a login // We do not have a user yet, and the UserRateLimits will not be affected by a login
// request (since register is an instance wide limit), which is why we are just cloning // request (since register is an instance wide limit), which is why we are just cloning
// the instances' limits to pass them on as user_rate_limits later. // the instances' limits to pass them on as user_rate_limits later.
let mut shell = let mut user =
ChorusUser::shell(Arc::new(RwLock::new(self.clone())), "None".to_string()).await; ChorusUser::shell(Arc::new(RwLock::new(self.clone())), "None".to_string()).await;
let token = chorus_request let token = chorus_request
.deserialize_response::<Token>(&mut shell) .deserialize_response::<Token>(&mut user)
.await? .await?
.token; .token;
if self.limits_information.is_some() { user.set_token(token);
self.limits_information.as_mut().unwrap().ratelimits = shell.limits.unwrap();
} let object = User::get(&mut user, None).await?;
let user_object = self.get_user(token.clone(), None).await.unwrap(); let settings = User::get_settings(&mut user).await?;
let settings = ChorusUser::get_settings(&token, &self.urls.api.clone(), self).await?;
*user.object.write().unwrap() = object;
*user.settings.write().unwrap() = settings;
let mut identify = GatewayIdentifyPayload::common(); let mut identify = GatewayIdentifyPayload::common();
let gateway: GatewayHandle = Gateway::spawn(self.urls.wss.clone()).await.unwrap(); identify.token = user.token();
identify.token = token.clone(); user.gateway.send_identify(identify).await;
gateway.send_identify(identify).await;
let user = ChorusUser::new(
Arc::new(RwLock::new(self.clone())),
token.clone(),
self.clone_limits_if_some(),
Arc::new(RwLock::new(settings)),
Arc::new(RwLock::new(user_object)),
gateway,
);
Ok(user) Ok(user)
} }
} }

View File

@ -10,7 +10,7 @@ use crate::{
}; };
/// Useful metadata for working with [`types::Reaction`], bundled together nicely. /// Useful metadata for working with [`types::Reaction`], bundled together nicely.
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq, Copy, Hash, PartialOrd, Ord)]
pub struct ReactionMeta { pub struct ReactionMeta {
pub message_id: types::Snowflake, pub message_id: types::Snowflake,
pub channel_id: types::Snowflake, pub channel_id: types::Snowflake,

View File

@ -44,6 +44,16 @@ impl ChorusUser {
&mut self, &mut self,
query: Option<GetUserGuildSchema>, query: Option<GetUserGuildSchema>,
) -> ChorusResult<Vec<Guild>> { ) -> ChorusResult<Vec<Guild>> {
let query_parameters = {
if let Some(query_some) = query {
query_some.to_query()
}
else {
Vec::new()
}
};
let url = format!( let url = format!(
"{}/users/@me/guilds", "{}/users/@me/guilds",
self.belongs_to.read().unwrap().urls.api, self.belongs_to.read().unwrap().urls.api,
@ -53,7 +63,7 @@ impl ChorusUser {
.get(url) .get(url)
.header("Authorization", self.token()) .header("Authorization", self.token())
.header("Content-Type", "application/json") .header("Content-Type", "application/json")
.body(to_string(&query).unwrap()), .query(&query_parameters),
limit_type: LimitType::Global, limit_type: LimitType::Global,
}; };

View File

@ -30,13 +30,9 @@ impl ChorusUser {
/// Gets the user's settings. /// Gets the user's settings.
/// ///
/// # Notes /// # Notes
/// This functions is a wrapper around [`User::get_settings`]. /// This function is a wrapper around [`User::get_settings`].
pub async fn get_settings( pub async fn get_settings(&mut self) -> ChorusResult<UserSettings> {
token: &String, User::get_settings(self).await
url_api: &String,
instance: &mut Instance,
) -> ChorusResult<UserSettings> {
User::get_settings(token, url_api, instance).await
} }
/// Modifies the current user's representation. (See [`User`]) /// Modifies the current user's representation. (See [`User`])
@ -44,12 +40,18 @@ impl ChorusUser {
/// # Reference /// # Reference
/// See <https://discord-userdoccers.vercel.app/resources/user#modify-current-user> /// See <https://discord-userdoccers.vercel.app/resources/user#modify-current-user>
pub async fn modify(&mut self, modify_schema: UserModifySchema) -> ChorusResult<User> { pub async fn modify(&mut self, modify_schema: UserModifySchema) -> ChorusResult<User> {
if modify_schema.new_password.is_some()
// See <https://docs.discord.sex/resources/user#json-params>, note 1
let requires_current_password = modify_schema.username.is_some()
|| modify_schema.discriminator.is_some()
|| modify_schema.email.is_some() || modify_schema.email.is_some()
|| modify_schema.code.is_some() || modify_schema.date_of_birth.is_some()
{ || modify_schema.new_password.is_some();
if requires_current_password && modify_schema.current_password.is_none() {
return Err(ChorusError::PasswordRequired); return Err(ChorusError::PasswordRequired);
} }
let request = Client::new() let request = Client::new()
.patch(format!( .patch(format!(
"{}/users/@me", "{}/users/@me",
@ -118,56 +120,21 @@ impl User {
/// ///
/// # Reference /// # Reference
/// See <https://luna.gitlab.io/discord-unofficial-docs/docs/user_settings.html#get-usersmesettings> /// See <https://luna.gitlab.io/discord-unofficial-docs/docs/user_settings.html#get-usersmesettings>
pub async fn get_settings( pub async fn get_settings(user: &mut ChorusUser) -> ChorusResult<UserSettings> {
token: &String, let url_api = user.belongs_to.read().unwrap().urls.api.clone();
url_api: &String,
instance: &mut Instance,
) -> ChorusResult<UserSettings> {
let request: reqwest::RequestBuilder = Client::new() let request: reqwest::RequestBuilder = Client::new()
.get(format!("{}/users/@me/settings", url_api)) .get(format!("{}/users/@me/settings", url_api))
.header("Authorization", token); .header("Authorization", user.token());
let mut user =
ChorusUser::shell(Arc::new(RwLock::new(instance.clone())), token.clone()).await;
let chorus_request = ChorusRequest { let chorus_request = ChorusRequest {
request, request,
limit_type: LimitType::Global, limit_type: LimitType::Global,
}; };
let result = match chorus_request.send_request(&mut user).await { match chorus_request.send_request(user).await {
Ok(result) => Ok(serde_json::from_str(&result.text().await.unwrap()).unwrap()), Ok(result) => {
let result_text = result.text().await.unwrap();
Ok(serde_json::from_str(&result_text).unwrap())
}
Err(e) => Err(e), Err(e) => Err(e),
};
if instance.limits_information.is_some() {
instance.limits_information.as_mut().unwrap().ratelimits = user
.belongs_to
.read()
.unwrap()
.clone_limits_if_some()
.unwrap();
} }
result
}
}
impl Instance {
/// Gets a user by id, or if the id is None, gets the current user.
///
/// # Notes
/// This function is a wrapper around [`User::get`].
///
/// # Reference
/// See <https://discord-userdoccers.vercel.app/resources/user#get-user> and
/// <https://discord-userdoccers.vercel.app/resources/user#get-current-user>
pub async fn get_user(&mut self, token: String, id: Option<&String>) -> ChorusResult<User> {
let mut user = ChorusUser::shell(Arc::new(RwLock::new(self.clone())), token).await;
let result = User::get(&mut user, id).await;
if self.limits_information.is_some() {
self.limits_information.as_mut().unwrap().ratelimits = user
.belongs_to
.read()
.unwrap()
.clone_limits_if_some()
.unwrap();
}
result
} }
} }

View File

@ -6,6 +6,7 @@
use custom_error::custom_error; use custom_error::custom_error;
use crate::types::WebSocketEvent; use crate::types::WebSocketEvent;
use chorus_macros::WebSocketEvent;
custom_error! { custom_error! {
#[derive(PartialEq, Eq, Clone, Hash)] #[derive(PartialEq, Eq, Clone, Hash)]
@ -72,7 +73,7 @@ custom_error! {
/// Supposed to be sent as numbers, though they are sent as string most of the time? /// Supposed to be sent as numbers, though they are sent as string most of the time?
/// ///
/// Also includes errors when initiating a connection and unexpected opcodes /// Also includes errors when initiating a connection and unexpected opcodes
#[derive(PartialEq, Eq, Default, Clone)] #[derive(PartialEq, Eq, Default, Clone, WebSocketEvent)]
pub GatewayError pub GatewayError
// Errors we have received from the gateway // Errors we have received from the gateway
#[default] #[default]
@ -92,22 +93,20 @@ custom_error! {
DisallowedIntents = "You sent a disallowed intent. You may have tried to specify an intent that you have not enabled or are not approved for", DisallowedIntents = "You sent a disallowed intent. You may have tried to specify an intent that you have not enabled or are not approved for",
// Errors when initiating a gateway connection // Errors when initiating a gateway connection
CannotConnect{error: String} = "Cannot connect due to a tungstenite error: {error}", CannotConnect{error: String} = "Cannot connect due to a websocket error: {error}",
NonHelloOnInitiate{opcode: u8} = "Received non hello on initial gateway connection ({opcode}), something is definitely wrong", NonHelloOnInitiate{opcode: u8} = "Received non hello on initial gateway connection ({opcode}), something is definitely wrong",
// Other misc errors // Other misc errors
UnexpectedOpcodeReceived{opcode: u8} = "Received an opcode we weren't expecting to receive: {opcode}", UnexpectedOpcodeReceived{opcode: u8} = "Received an opcode we weren't expecting to receive: {opcode}",
} }
impl WebSocketEvent for GatewayError {}
custom_error! { custom_error! {
/// Voice Gateway errors /// Voice Gateway errors
/// ///
/// Similar to [GatewayError]. /// Similar to [GatewayError].
/// ///
/// See <https://discord.com/developers/docs/topics/opcodes-and-status-codes#voice-voice-close-event-codes>; /// See <https://discord.com/developers/docs/topics/opcodes-and-status-codes#voice-voice-close-event-codes>;
#[derive(Clone, Default, PartialEq, Eq)] #[derive(Clone, Default, PartialEq, Eq, WebSocketEvent)]
pub VoiceGatewayError pub VoiceGatewayError
// Errors we receive // Errors we receive
#[default] #[default]
@ -125,18 +124,16 @@ custom_error! {
UnknownEncryptionMode = "Server failed to decrypt data", UnknownEncryptionMode = "Server failed to decrypt data",
// Errors when initiating a gateway connection // Errors when initiating a gateway connection
CannotConnect{error: String} = "Cannot connect due to a tungstenite error: {error}", CannotConnect{error: String} = "Cannot connect due to a websocket error: {error}",
NonHelloOnInitiate{opcode: u8} = "Received non hello on initial gateway connection ({opcode}), something is definitely wrong", NonHelloOnInitiate{opcode: u8} = "Received non hello on initial gateway connection ({opcode}), something is definitely wrong",
// Other misc errors // Other misc errors
UnexpectedOpcodeReceived{opcode: u8} = "Received an opcode we weren't expecting to receive: {opcode}", UnexpectedOpcodeReceived{opcode: u8} = "Received an opcode we weren't expecting to receive: {opcode}",
} }
impl WebSocketEvent for VoiceGatewayError {}
custom_error! { custom_error! {
/// Voice UDP errors. /// Voice UDP errors.
#[derive(Clone, PartialEq, Eq)] #[derive(Clone, PartialEq, Eq, WebSocketEvent)]
pub VoiceUdpError pub VoiceUdpError
// General errors // General errors
@ -155,4 +152,3 @@ custom_error! {
CannotConnect{error: String} = "Cannot connect due to a UDP error: {error}", CannotConnect{error: String} = "Cannot connect due to a UDP error: {error}",
} }
impl WebSocketEvent for VoiceUdpError {}

View File

@ -2,6 +2,7 @@
// License, v. 2.0. If a copy of the MPL was not distributed with this // 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/. // file, You can obtain one at http://mozilla.org/MPL/2.0/.
use custom_error::custom_error;
use futures_util::{ use futures_util::{
stream::{SplitSink, SplitStream}, stream::{SplitSink, SplitStream},
StreamExt, StreamExt,
@ -11,10 +12,9 @@ use tokio_tungstenite::{
connect_async_tls_with_config, tungstenite, Connector, MaybeTlsStream, WebSocketStream, connect_async_tls_with_config, tungstenite, Connector, MaybeTlsStream, WebSocketStream,
}; };
use crate::errors::GatewayError; use crate::gateway::{GatewayMessage, RawGatewayMessage};
use crate::gateway::GatewayMessage;
#[derive(Debug, Clone)] #[derive(Debug, Clone, Copy)]
pub struct TungsteniteBackend; pub struct TungsteniteBackend;
// These could be made into inherent associated types when that's stabilized // These could be made into inherent associated types when that's stabilized
@ -22,23 +22,29 @@ pub type TungsteniteSink =
SplitSink<WebSocketStream<MaybeTlsStream<TcpStream>>, tungstenite::Message>; SplitSink<WebSocketStream<MaybeTlsStream<TcpStream>>, tungstenite::Message>;
pub type TungsteniteStream = SplitStream<WebSocketStream<MaybeTlsStream<TcpStream>>>; pub type TungsteniteStream = SplitStream<WebSocketStream<MaybeTlsStream<TcpStream>>>;
custom_error! {
pub TungsteniteBackendError
FailedToLoadCerts{error: std::io::Error} = "failed to load platform native certs: {error}",
TungsteniteError{error: tungstenite::error::Error} = "encountered a tungstenite error: {error}",
}
impl TungsteniteBackend { impl TungsteniteBackend {
pub async fn connect( pub async fn connect(
websocket_url: &str, websocket_url: &str,
) -> Result<(TungsteniteSink, TungsteniteStream), crate::errors::GatewayError> { ) -> Result<(TungsteniteSink, TungsteniteStream), TungsteniteBackendError> {
let mut roots = rustls::RootCertStore::empty(); let certs = webpki_roots::TLS_SERVER_ROOTS;
let certs = rustls_native_certs::load_native_certs(); let roots = rustls::RootCertStore {
roots: certs
if let Err(e) = certs { .iter()
log::error!("Failed to load platform native certs! {:?}", e); .map(|cert| {
return Err(GatewayError::CannotConnect { rustls::OwnedTrustAnchor::from_subject_spki_name_constraints(
error: format!("{:?}", e), cert.subject.to_vec(),
}); cert.subject_public_key_info.to_vec(),
} cert.name_constraints.as_ref().map(|der| der.to_vec()),
)
for cert in certs.unwrap() { })
roots.add(&rustls::Certificate(cert.0)).unwrap(); .collect(),
} };
let (websocket_stream, _) = match connect_async_tls_with_config( let (websocket_stream, _) = match connect_async_tls_with_config(
websocket_url, websocket_url,
None, None,
@ -54,11 +60,7 @@ impl TungsteniteBackend {
.await .await
{ {
Ok(websocket_stream) => websocket_stream, Ok(websocket_stream) => websocket_stream,
Err(e) => { Err(e) => return Err(TungsteniteBackendError::TungsteniteError { error: e }),
return Err(GatewayError::CannotConnect {
error: e.to_string(),
})
}
}; };
Ok(websocket_stream.split()) Ok(websocket_stream.split())
@ -76,3 +78,22 @@ impl From<tungstenite::Message> for GatewayMessage {
Self(value.to_string()) Self(value.to_string())
} }
} }
impl From<RawGatewayMessage> for tungstenite::Message {
fn from(message: RawGatewayMessage) -> Self {
match message {
RawGatewayMessage::Text(text) => tungstenite::Message::Text(text),
RawGatewayMessage::Bytes(bytes) => tungstenite::Message::Binary(bytes),
}
}
}
impl From<tungstenite::Message> for RawGatewayMessage {
fn from(value: tungstenite::Message) -> Self {
match value {
tungstenite::Message::Binary(bytes) => RawGatewayMessage::Bytes(bytes),
tungstenite::Message::Text(text) => RawGatewayMessage::Text(text),
_ => RawGatewayMessage::Text(value.to_string()),
}
}
}

View File

@ -9,8 +9,7 @@ use futures_util::{
use ws_stream_wasm::*; use ws_stream_wasm::*;
use crate::errors::GatewayError; use crate::gateway::{GatewayMessage, RawGatewayMessage};
use crate::gateway::GatewayMessage;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct WasmBackend; pub struct WasmBackend;
@ -22,13 +21,8 @@ pub type WasmStream = SplitStream<WsStream>;
impl WasmBackend { impl WasmBackend {
pub async fn connect( pub async fn connect(
websocket_url: &str, websocket_url: &str,
) -> Result<(WasmSink, WasmStream), crate::errors::GatewayError> { ) -> Result<(WasmSink, WasmStream), ws_stream_wasm::WsErr> {
let (_, websocket_stream) = match WsMeta::connect(websocket_url, None).await { let (_, websocket_stream) = WsMeta::connect(websocket_url, None).await?;
Ok(stream) => Ok(stream),
Err(e) => Err(GatewayError::CannotConnect {
error: e.to_string(),
}),
}?;
Ok(websocket_stream.split()) Ok(websocket_stream.split())
} }
@ -52,3 +46,21 @@ impl From<WsMessage> for GatewayMessage {
} }
} }
} }
impl From<RawGatewayMessage> for WsMessage {
fn from(message: RawGatewayMessage) -> Self {
match message {
RawGatewayMessage::Text(text) => WsMessage::Text(text),
RawGatewayMessage::Bytes(bytes) => WsMessage::Binary(bytes),
}
}
}
impl From<WsMessage> for RawGatewayMessage {
fn from(value: WsMessage) -> Self {
match value {
WsMessage::Binary(bytes) => RawGatewayMessage::Bytes(bytes),
WsMessage::Text(text) => RawGatewayMessage::Text(text),
}
}
}

View File

@ -2,6 +2,8 @@
// License, v. 2.0. If a copy of the MPL was not distributed with this // 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/. // file, You can obtain one at http://mozilla.org/MPL/2.0/.
use pubserve::Publisher;
use super::*; use super::*;
use crate::types; use crate::types;
@ -23,144 +25,144 @@ pub struct Events {
pub call: Call, pub call: Call,
pub voice: Voice, pub voice: Voice,
pub webhooks: Webhooks, pub webhooks: Webhooks,
pub gateway_identify_payload: GatewayEvent<types::GatewayIdentifyPayload>, pub gateway_identify_payload: Publisher<types::GatewayIdentifyPayload>,
pub gateway_resume: GatewayEvent<types::GatewayResume>, pub gateway_resume: Publisher<types::GatewayResume>,
pub error: GatewayEvent<GatewayError>, pub error: Publisher<GatewayError>,
} }
#[derive(Default, Debug)] #[derive(Default, Debug)]
pub struct Application { pub struct Application {
pub command_permissions_update: GatewayEvent<types::ApplicationCommandPermissionsUpdate>, pub command_permissions_update: Publisher<types::ApplicationCommandPermissionsUpdate>,
} }
#[derive(Default, Debug)] #[derive(Default, Debug)]
pub struct AutoModeration { pub struct AutoModeration {
pub rule_create: GatewayEvent<types::AutoModerationRuleCreate>, pub rule_create: Publisher<types::AutoModerationRuleCreate>,
pub rule_update: GatewayEvent<types::AutoModerationRuleUpdate>, pub rule_update: Publisher<types::AutoModerationRuleUpdate>,
pub rule_delete: GatewayEvent<types::AutoModerationRuleDelete>, pub rule_delete: Publisher<types::AutoModerationRuleDelete>,
pub action_execution: GatewayEvent<types::AutoModerationActionExecution>, pub action_execution: Publisher<types::AutoModerationActionExecution>,
} }
#[derive(Default, Debug)] #[derive(Default, Debug)]
pub struct Session { pub struct Session {
pub ready: GatewayEvent<types::GatewayReady>, pub ready: Publisher<types::GatewayReady>,
pub ready_supplemental: GatewayEvent<types::GatewayReadySupplemental>, pub ready_supplemental: Publisher<types::GatewayReadySupplemental>,
pub replace: GatewayEvent<types::SessionsReplace>, pub replace: Publisher<types::SessionsReplace>,
pub reconnect: GatewayEvent<types::GatewayReconnect>, pub reconnect: Publisher<types::GatewayReconnect>,
pub invalid: GatewayEvent<types::GatewayInvalidSession>, pub invalid: Publisher<types::GatewayInvalidSession>,
} }
#[derive(Default, Debug)] #[derive(Default, Debug)]
pub struct StageInstance { pub struct StageInstance {
pub create: GatewayEvent<types::StageInstanceCreate>, pub create: Publisher<types::StageInstanceCreate>,
pub update: GatewayEvent<types::StageInstanceUpdate>, pub update: Publisher<types::StageInstanceUpdate>,
pub delete: GatewayEvent<types::StageInstanceDelete>, pub delete: Publisher<types::StageInstanceDelete>,
} }
#[derive(Default, Debug)] #[derive(Default, Debug)]
pub struct Message { pub struct Message {
pub create: GatewayEvent<types::MessageCreate>, pub create: Publisher<types::MessageCreate>,
pub update: GatewayEvent<types::MessageUpdate>, pub update: Publisher<types::MessageUpdate>,
pub delete: GatewayEvent<types::MessageDelete>, pub delete: Publisher<types::MessageDelete>,
pub delete_bulk: GatewayEvent<types::MessageDeleteBulk>, pub delete_bulk: Publisher<types::MessageDeleteBulk>,
pub reaction_add: GatewayEvent<types::MessageReactionAdd>, pub reaction_add: Publisher<types::MessageReactionAdd>,
pub reaction_remove: GatewayEvent<types::MessageReactionRemove>, pub reaction_remove: Publisher<types::MessageReactionRemove>,
pub reaction_remove_all: GatewayEvent<types::MessageReactionRemoveAll>, pub reaction_remove_all: Publisher<types::MessageReactionRemoveAll>,
pub reaction_remove_emoji: GatewayEvent<types::MessageReactionRemoveEmoji>, pub reaction_remove_emoji: Publisher<types::MessageReactionRemoveEmoji>,
pub ack: GatewayEvent<types::MessageACK>, pub ack: Publisher<types::MessageACK>,
} }
#[derive(Default, Debug)] #[derive(Default, Debug)]
pub struct User { pub struct User {
pub update: GatewayEvent<types::UserUpdate>, pub update: Publisher<types::UserUpdate>,
pub guild_settings_update: GatewayEvent<types::UserGuildSettingsUpdate>, pub guild_settings_update: Publisher<types::UserGuildSettingsUpdate>,
pub presence_update: GatewayEvent<types::PresenceUpdate>, pub presence_update: Publisher<types::PresenceUpdate>,
pub typing_start: GatewayEvent<types::TypingStartEvent>, pub typing_start: Publisher<types::TypingStartEvent>,
} }
#[derive(Default, Debug)] #[derive(Default, Debug)]
pub struct Relationship { pub struct Relationship {
pub add: GatewayEvent<types::RelationshipAdd>, pub add: Publisher<types::RelationshipAdd>,
pub remove: GatewayEvent<types::RelationshipRemove>, pub remove: Publisher<types::RelationshipRemove>,
} }
#[derive(Default, Debug)] #[derive(Default, Debug)]
pub struct Channel { pub struct Channel {
pub create: GatewayEvent<types::ChannelCreate>, pub create: Publisher<types::ChannelCreate>,
pub update: GatewayEvent<types::ChannelUpdate>, pub update: Publisher<types::ChannelUpdate>,
pub unread_update: GatewayEvent<types::ChannelUnreadUpdate>, pub unread_update: Publisher<types::ChannelUnreadUpdate>,
pub delete: GatewayEvent<types::ChannelDelete>, pub delete: Publisher<types::ChannelDelete>,
pub pins_update: GatewayEvent<types::ChannelPinsUpdate>, pub pins_update: Publisher<types::ChannelPinsUpdate>,
} }
#[derive(Default, Debug)] #[derive(Default, Debug)]
pub struct Thread { pub struct Thread {
pub create: GatewayEvent<types::ThreadCreate>, pub create: Publisher<types::ThreadCreate>,
pub update: GatewayEvent<types::ThreadUpdate>, pub update: Publisher<types::ThreadUpdate>,
pub delete: GatewayEvent<types::ThreadDelete>, pub delete: Publisher<types::ThreadDelete>,
pub list_sync: GatewayEvent<types::ThreadListSync>, pub list_sync: Publisher<types::ThreadListSync>,
pub member_update: GatewayEvent<types::ThreadMemberUpdate>, pub member_update: Publisher<types::ThreadMemberUpdate>,
pub members_update: GatewayEvent<types::ThreadMembersUpdate>, pub members_update: Publisher<types::ThreadMembersUpdate>,
} }
#[derive(Default, Debug)] #[derive(Default, Debug)]
pub struct Guild { pub struct Guild {
pub create: GatewayEvent<types::GuildCreate>, pub create: Publisher<types::GuildCreate>,
pub update: GatewayEvent<types::GuildUpdate>, pub update: Publisher<types::GuildUpdate>,
pub delete: GatewayEvent<types::GuildDelete>, pub delete: Publisher<types::GuildDelete>,
pub audit_log_entry_create: GatewayEvent<types::GuildAuditLogEntryCreate>, pub audit_log_entry_create: Publisher<types::GuildAuditLogEntryCreate>,
pub ban_add: GatewayEvent<types::GuildBanAdd>, pub ban_add: Publisher<types::GuildBanAdd>,
pub ban_remove: GatewayEvent<types::GuildBanRemove>, pub ban_remove: Publisher<types::GuildBanRemove>,
pub emojis_update: GatewayEvent<types::GuildEmojisUpdate>, pub emojis_update: Publisher<types::GuildEmojisUpdate>,
pub stickers_update: GatewayEvent<types::GuildStickersUpdate>, pub stickers_update: Publisher<types::GuildStickersUpdate>,
pub integrations_update: GatewayEvent<types::GuildIntegrationsUpdate>, pub integrations_update: Publisher<types::GuildIntegrationsUpdate>,
pub member_add: GatewayEvent<types::GuildMemberAdd>, pub member_add: Publisher<types::GuildMemberAdd>,
pub member_remove: GatewayEvent<types::GuildMemberRemove>, pub member_remove: Publisher<types::GuildMemberRemove>,
pub member_update: GatewayEvent<types::GuildMemberUpdate>, pub member_update: Publisher<types::GuildMemberUpdate>,
pub members_chunk: GatewayEvent<types::GuildMembersChunk>, pub members_chunk: Publisher<types::GuildMembersChunk>,
pub role_create: GatewayEvent<types::GuildRoleCreate>, pub role_create: Publisher<types::GuildRoleCreate>,
pub role_update: GatewayEvent<types::GuildRoleUpdate>, pub role_update: Publisher<types::GuildRoleUpdate>,
pub role_delete: GatewayEvent<types::GuildRoleDelete>, pub role_delete: Publisher<types::GuildRoleDelete>,
pub role_scheduled_event_create: GatewayEvent<types::GuildScheduledEventCreate>, pub role_scheduled_event_create: Publisher<types::GuildScheduledEventCreate>,
pub role_scheduled_event_update: GatewayEvent<types::GuildScheduledEventUpdate>, pub role_scheduled_event_update: Publisher<types::GuildScheduledEventUpdate>,
pub role_scheduled_event_delete: GatewayEvent<types::GuildScheduledEventDelete>, pub role_scheduled_event_delete: Publisher<types::GuildScheduledEventDelete>,
pub role_scheduled_event_user_add: GatewayEvent<types::GuildScheduledEventUserAdd>, pub role_scheduled_event_user_add: Publisher<types::GuildScheduledEventUserAdd>,
pub role_scheduled_event_user_remove: GatewayEvent<types::GuildScheduledEventUserRemove>, pub role_scheduled_event_user_remove: Publisher<types::GuildScheduledEventUserRemove>,
pub passive_update_v1: GatewayEvent<types::PassiveUpdateV1>, pub passive_update_v1: Publisher<types::PassiveUpdateV1>,
} }
#[derive(Default, Debug)] #[derive(Default, Debug)]
pub struct Invite { pub struct Invite {
pub create: GatewayEvent<types::InviteCreate>, pub create: Publisher<types::InviteCreate>,
pub delete: GatewayEvent<types::InviteDelete>, pub delete: Publisher<types::InviteDelete>,
} }
#[derive(Default, Debug)] #[derive(Default, Debug)]
pub struct Integration { pub struct Integration {
pub create: GatewayEvent<types::IntegrationCreate>, pub create: Publisher<types::IntegrationCreate>,
pub update: GatewayEvent<types::IntegrationUpdate>, pub update: Publisher<types::IntegrationUpdate>,
pub delete: GatewayEvent<types::IntegrationDelete>, pub delete: Publisher<types::IntegrationDelete>,
} }
#[derive(Default, Debug)] #[derive(Default, Debug)]
pub struct Interaction { pub struct Interaction {
pub create: GatewayEvent<types::InteractionCreate>, pub create: Publisher<types::InteractionCreate>,
} }
#[derive(Default, Debug)] #[derive(Default, Debug)]
pub struct Call { pub struct Call {
pub create: GatewayEvent<types::CallCreate>, pub create: Publisher<types::CallCreate>,
pub update: GatewayEvent<types::CallUpdate>, pub update: Publisher<types::CallUpdate>,
pub delete: GatewayEvent<types::CallDelete>, pub delete: Publisher<types::CallDelete>,
} }
#[derive(Default, Debug)] #[derive(Default, Debug)]
pub struct Voice { pub struct Voice {
pub state_update: GatewayEvent<types::VoiceStateUpdate>, pub state_update: Publisher<types::VoiceStateUpdate>,
pub server_update: GatewayEvent<types::VoiceServerUpdate>, pub server_update: Publisher<types::VoiceServerUpdate>,
} }
#[derive(Default, Debug)] #[derive(Default, Debug)]
pub struct Webhooks { pub struct Webhooks {
pub update: GatewayEvent<types::WebhooksUpdate>, pub update: Publisher<types::WebhooksUpdate>,
} }

View File

@ -4,8 +4,10 @@
use std::time::Duration; use std::time::Duration;
use flate2::Decompress;
use futures_util::{SinkExt, StreamExt}; use futures_util::{SinkExt, StreamExt};
use log::*; use log::*;
use pubserve::Publisher;
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
use tokio::task; use tokio::task;
@ -19,6 +21,9 @@ use crate::types::{
WebSocketEvent, WebSocketEvent,
}; };
/// Tells us we have received enough of the buffer to decompress it
const ZLIB_SUFFIX: [u8; 4] = [0, 0, 255, 255];
#[derive(Debug)] #[derive(Debug)]
pub struct Gateway { pub struct Gateway {
events: Arc<Mutex<Events>>, events: Arc<Mutex<Events>>,
@ -28,14 +33,36 @@ pub struct Gateway {
kill_send: tokio::sync::broadcast::Sender<()>, kill_send: tokio::sync::broadcast::Sender<()>,
kill_receive: tokio::sync::broadcast::Receiver<()>, kill_receive: tokio::sync::broadcast::Receiver<()>,
store: Arc<Mutex<HashMap<Snowflake, Arc<RwLock<ObservableObject>>>>>, store: Arc<Mutex<HashMap<Snowflake, Arc<RwLock<ObservableObject>>>>>,
/// Url which was used to initialize the gateway
url: String, url: String,
/// Options which were used to initialize the gateway
options: GatewayOptions,
zlib_inflate: Option<flate2::Decompress>,
zlib_buffer: Option<Vec<u8>>,
} }
impl Gateway { impl Gateway {
#[allow(clippy::new_ret_no_self)] #[allow(clippy::new_ret_no_self)]
pub async fn spawn(websocket_url: String) -> Result<GatewayHandle, GatewayError> { /// Creates / opens a new gateway connection.
let (websocket_send, mut websocket_receive) = ///
WebSocketBackend::connect(&websocket_url).await?; /// # Note
/// The websocket url should begin with the prefix wss:// or ws:// (for unsecure connections)
pub async fn spawn(
websocket_url: String,
options: GatewayOptions,
) -> Result<GatewayHandle, GatewayError> {
let url = options.add_to_url(websocket_url);
debug!("GW: Connecting to {}", url);
let (websocket_send, mut websocket_receive) = match WebSocketBackend::connect(&url).await {
Ok(streams) => streams,
Err(e) => {
return Err(GatewayError::CannotConnect {
error: format!("{:?}", e),
});
}
};
let shared_websocket_send = Arc::new(Mutex::new(websocket_send)); let shared_websocket_send = Arc::new(Mutex::new(websocket_send));
@ -45,10 +72,34 @@ impl Gateway {
// Wait for the first hello and then spawn both tasks so we avoid nested tasks // 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 // This automatically spawns the heartbeat task, but from the main thread
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
let msg: GatewayMessage = websocket_receive.next().await.unwrap().unwrap().into(); let received: RawGatewayMessage = websocket_receive.next().await.unwrap().unwrap().into();
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]
let msg: GatewayMessage = websocket_receive.next().await.unwrap().into(); let received: RawGatewayMessage = websocket_receive.next().await.unwrap().into();
let gateway_payload: types::GatewayReceivePayload = serde_json::from_str(&msg.0).unwrap();
let message: GatewayMessage;
let zlib_buffer;
let zlib_inflate;
match options.transport_compression {
GatewayTransportCompression::None => {
zlib_buffer = None;
zlib_inflate = None;
message = GatewayMessage::from_raw_json_message(received).unwrap();
}
GatewayTransportCompression::ZLibStream => {
zlib_buffer = Some(Vec::new());
let mut inflate = Decompress::new(true);
message =
GatewayMessage::from_zlib_stream_json_message(received, &mut inflate).unwrap();
zlib_inflate = Some(inflate);
}
}
let gateway_payload: types::GatewayReceivePayload =
serde_json::from_str(&message.0).unwrap();
if gateway_payload.op_code != GATEWAY_HELLO { if gateway_payload.op_code != GATEWAY_HELLO {
return Err(GatewayError::NonHelloOnInitiate { return Err(GatewayError::NonHelloOnInitiate {
@ -78,7 +129,10 @@ impl Gateway {
kill_send: kill_send.clone(), kill_send: kill_send.clone(),
kill_receive: kill_send.subscribe(), kill_receive: kill_send.subscribe(),
store: store.clone(), store: store.clone(),
url: websocket_url.clone(), url: url.clone(),
options,
zlib_inflate,
zlib_buffer,
}; };
// Now we can continuously check for messages in a different task, since we aren't going to receive another hello // Now we can continuously check for messages in a different task, since we aren't going to receive another hello
@ -92,7 +146,7 @@ impl Gateway {
}); });
Ok(GatewayHandle { Ok(GatewayHandle {
url: websocket_url.clone(), url: url.clone(),
events: shared_events, events: shared_events,
websocket_send: shared_websocket_send.clone(), websocket_send: shared_websocket_send.clone(),
kill_send: kill_send.clone(), kill_send: kill_send.clone(),
@ -101,7 +155,7 @@ impl Gateway {
} }
/// The main gateway listener task; /// The main gateway listener task;
pub async fn gateway_listen_task(&mut self) { async fn gateway_listen_task(&mut self) {
loop { loop {
let msg; let msg;
@ -118,12 +172,12 @@ impl Gateway {
// PRETTYFYME: Remove inline conditional compiling // PRETTYFYME: Remove inline conditional compiling
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
if let Some(Ok(message)) = msg { if let Some(Ok(message)) = msg {
self.handle_message(message.into()).await; self.handle_raw_message(message.into()).await;
continue; continue;
} }
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]
if let Some(message) = msg { if let Some(message) = msg {
self.handle_message(message.into()).await; self.handle_raw_message(message.into()).await;
continue; continue;
} }
@ -144,7 +198,7 @@ impl Gateway {
#[allow(dead_code)] // TODO: Remove this allow annotation #[allow(dead_code)] // TODO: Remove this allow annotation
async fn handle_event<'a, T: WebSocketEvent + serde::Deserialize<'a>>( async fn handle_event<'a, T: WebSocketEvent + serde::Deserialize<'a>>(
data: &'a str, data: &'a str,
event: &mut GatewayEvent<T>, event: &mut Publisher<T>,
) -> Result<(), serde_json::Error> { ) -> Result<(), serde_json::Error> {
let data_deserialize_result: Result<T, serde_json::Error> = serde_json::from_str(data); let data_deserialize_result: Result<T, serde_json::Error> = serde_json::from_str(data);
@ -152,12 +206,46 @@ impl Gateway {
return Err(data_deserialize_result.err().unwrap()); return Err(data_deserialize_result.err().unwrap());
} }
event.notify(data_deserialize_result.unwrap()).await; event.publish(data_deserialize_result.unwrap()).await;
Ok(()) Ok(())
} }
/// Takes a [RawGatewayMessage], converts it to [GatewayMessage] based
/// of connection options and calls handle_message
async fn handle_raw_message(&mut self, raw_message: RawGatewayMessage) {
let message;
match self.options.transport_compression {
GatewayTransportCompression::None => {
message = GatewayMessage::from_raw_json_message(raw_message).unwrap()
}
GatewayTransportCompression::ZLibStream => {
let message_bytes = raw_message.into_bytes();
let can_decompress = message_bytes.len() > 4
&& message_bytes[message_bytes.len() - 4..] == ZLIB_SUFFIX;
let zlib_buffer = self.zlib_buffer.as_mut().unwrap();
zlib_buffer.extend(message_bytes.clone());
if !can_decompress {
return;
}
let zlib_buffer = self.zlib_buffer.as_ref().unwrap();
let inflate = self.zlib_inflate.as_mut().unwrap();
message =
GatewayMessage::from_zlib_stream_json_bytes(zlib_buffer, inflate).unwrap();
self.zlib_buffer = Some(Vec::new());
}
};
self.handle_message(message).await;
}
/// This handles a message as a websocket event and updates its events along with the events' observers /// 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: GatewayMessage) { async fn handle_message(&mut self, msg: GatewayMessage) {
if msg.0.is_empty() { if msg.0.is_empty() {
return; return;
} }
@ -166,7 +254,7 @@ impl Gateway {
if let Some(error) = msg.error() { if let Some(error) = msg.error() {
warn!("GW: Received error {:?}, connection will close..", error); warn!("GW: Received error {:?}, connection will close..", error);
self.close().await; self.close().await;
self.events.lock().await.error.notify(error).await; self.events.lock().await.error.publish(error).await;
} else { } else {
warn!( warn!(
"Message unrecognised: {:?}, please open an issue on the chorus github", "Message unrecognised: {:?}, please open an issue on the chorus github",
@ -194,7 +282,10 @@ impl Gateway {
let event = &mut self.events.lock().await.$($path).+; let event = &mut self.events.lock().await.$($path).+;
let json = gateway_payload.event_data.unwrap().get(); let json = gateway_payload.event_data.unwrap().get();
match serde_json::from_str(json) { match serde_json::from_str(json) {
Err(err) => warn!("Failed to parse gateway event {event_name} ({err})"), Err(err) => {
warn!("Failed to parse gateway event {event_name} ({err})");
trace!("Event data: {json}");
},
Ok(message) => { Ok(message) => {
$( $(
let mut message: $message_type = message; let mut message: $message_type = message;
@ -202,7 +293,7 @@ impl Gateway {
let id = if message.id().is_some() { let id = if message.id().is_some() {
message.id().unwrap() message.id().unwrap()
} else { } else {
event.notify(message).await; event.publish(message).await;
return; return;
}; };
if let Some(to_update) = store.get(&id) { if let Some(to_update) = store.get(&id) {
@ -224,25 +315,22 @@ impl Gateway {
} }
} }
)? )?
event.notify(message).await; event.publish(message).await;
} }
} }
},)* },)*
"RESUMED" => (), "RESUMED" => (),
"SESSIONS_REPLACE" => { "SESSIONS_REPLACE" => {
let result: Result<Vec<types::Session>, serde_json::Error> = let json = gateway_payload.event_data.unwrap().get();
serde_json::from_str(gateway_payload.event_data.unwrap().get()); let result: Result<Vec<types::Session>, serde_json::Error> = serde_json::from_str(json);
match result { match result {
Err(err) => { Err(err) => {
warn!( warn!("Failed to parse gateway event {event_name} ({err})");
"Failed to parse gateway event {} ({})", trace!("Event data: {json}");
event_name,
err
);
return; return;
} }
Ok(sessions) => { Ok(sessions) => {
self.events.lock().await.session.replace.notify( self.events.lock().await.session.replace.publish(
types::SessionsReplace {sessions} types::SessionsReplace {sessions}
).await; ).await;
} }
@ -250,6 +338,7 @@ impl Gateway {
}, },
_ => { _ => {
warn!("Received unrecognized gateway event ({event_name})! Please open an issue on the chorus github so we can implement it"); warn!("Received unrecognized gateway event ({event_name})! Please open an issue on the chorus github so we can implement it");
trace!("Event data: {}", gateway_payload.event_data.unwrap().get());
} }
} }
}; };
@ -358,7 +447,7 @@ impl Gateway {
.await .await
.session .session
.reconnect .reconnect
.notify(reconnect) .publish(reconnect)
.await; .await;
} }
GATEWAY_INVALID_SESSION => { GATEWAY_INVALID_SESSION => {
@ -383,7 +472,7 @@ impl Gateway {
.await .await
.session .session
.invalid .invalid
.notify(invalid_session) .publish(invalid_session)
.await; .await;
} }
// Starts our heartbeat // Starts our heartbeat

View File

@ -8,7 +8,7 @@ use log::*;
use std::fmt::Debug; use std::fmt::Debug;
use super::{events::Events, *}; use super::{events::Events, *};
use crate::types::{self, Composite}; use crate::types::{self, Composite, Shared};
/// Represents a handle to a Gateway connection. A Gateway connection will create observable /// Represents a handle to a Gateway connection. A Gateway connection will create observable
/// [`GatewayEvents`](GatewayEvent), which you can subscribe to. Gateway events include all currently /// [`GatewayEvents`](GatewayEvent), which you can subscribe to. Gateway events include all currently
@ -154,7 +154,7 @@ impl GatewayHandle {
/// Sends a call sync to the server /// Sends a call sync to the server
pub async fn send_call_sync(&self, to_send: types::CallSync) { pub async fn send_call_sync(&self, to_send: types::CallSync) {
let to_send_value = serde_json::to_value(&to_send).unwrap(); let to_send_value = serde_json::to_value(to_send).unwrap();
trace!("GW: Sending Call Sync.."); trace!("GW: Sending Call Sync..");

View File

@ -2,11 +2,41 @@
// License, v. 2.0. If a copy of the MPL was not distributed with this // 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/. // file, You can obtain one at http://mozilla.org/MPL/2.0/.
use std::string::FromUtf8Error;
use crate::types; use crate::types;
use super::*; use super::*;
/// Represents a message received from the gateway. This will be either a [types::GatewayReceivePayload], containing events, or a [GatewayError]. /// Defines a raw gateway message, being either string json or bytes
///
/// This is used as an intermediary type between types from different websocket implementations
#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) enum RawGatewayMessage {
Text(String),
Bytes(Vec<u8>),
}
impl RawGatewayMessage {
/// Attempt to consume the message into a String, will try to convert binary to utf8
pub fn into_text(self) -> Result<String, FromUtf8Error> {
match self {
RawGatewayMessage::Text(text) => Ok(text),
RawGatewayMessage::Bytes(bytes) => String::from_utf8(bytes),
}
}
/// Consume the message into bytes, will convert text to binary
pub fn into_bytes(self) -> Vec<u8> {
match self {
RawGatewayMessage::Text(text) => text.as_bytes().to_vec(),
RawGatewayMessage::Bytes(bytes) => bytes,
}
}
}
/// Represents a json 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. /// This struct is used internally when handling messages.
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct GatewayMessage(pub String); pub struct GatewayMessage(pub String);
@ -44,4 +74,48 @@ impl GatewayMessage {
pub fn payload(&self) -> Result<types::GatewayReceivePayload, serde_json::Error> { pub fn payload(&self) -> Result<types::GatewayReceivePayload, serde_json::Error> {
serde_json::from_str(&self.0) serde_json::from_str(&self.0)
} }
/// Create self from an uncompressed json [RawGatewayMessage]
pub(crate) fn from_raw_json_message(
message: RawGatewayMessage,
) -> Result<GatewayMessage, FromUtf8Error> {
let text = message.into_text()?;
Ok(GatewayMessage(text))
}
/// Attempt to create self by decompressing zlib-stream bytes
// Thanks to <https://github.com/ByteAlex/zlib-stream-rs>, their
// code helped a lot with the stream implementation
pub(crate) fn from_zlib_stream_json_bytes(
bytes: &[u8],
inflate: &mut flate2::Decompress,
) -> Result<GatewayMessage, std::io::Error> {
// Note: is there a better way to handle the size of this output buffer?
//
// This used to be 10, I measured it at 11.5, so a safe bet feels like 20
//
// ^ - This dude is naive. apparently not even 20x is okay. Measured at 47.9x!!!!
// If it is >100x ever, I will literally explode
//
// About an hour later, you ^ will literally explode.
// 133 vs 13994 -- 105.21805x ratio
// Let's hope it doesn't go above 200??
let mut output = Vec::with_capacity(bytes.len() * 200);
let _status = inflate.decompress_vec(bytes, &mut output, flate2::FlushDecompress::Sync)?;
output.shrink_to_fit();
let string = String::from_utf8(output).unwrap();
Ok(GatewayMessage(string))
}
/// Attempt to create self by decompressing a zlib-stream bytes raw message
pub(crate) fn from_zlib_stream_json_message(
message: RawGatewayMessage,
inflate: &mut flate2::Decompress,
) -> Result<GatewayMessage, std::io::Error> {
Self::from_zlib_stream_json_bytes(&message.into_bytes(), inflate)
}
} }

View File

@ -2,7 +2,6 @@
// License, v. 2.0. If a copy of the MPL was not distributed with this // 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/. // file, You can obtain one at http://mozilla.org/MPL/2.0/.
use async_trait::async_trait;
pub mod backends; pub mod backends;
pub mod events; pub mod events;
@ -10,15 +9,17 @@ pub mod gateway;
pub mod handle; pub mod handle;
pub mod heartbeat; pub mod heartbeat;
pub mod message; pub mod message;
pub mod options;
pub use backends::*; pub use backends::*;
pub use gateway::*; pub use gateway::*;
pub use handle::*; pub use handle::*;
use heartbeat::*; use heartbeat::*;
pub use message::*; pub use message::*;
pub use options::*;
use crate::errors::GatewayError; use crate::errors::GatewayError;
use crate::types::{Snowflake, WebSocketEvent}; use crate::types::{Snowflake};
use std::any::Any; use std::any::Any;
use std::collections::HashMap; use std::collections::HashMap;
@ -80,63 +81,3 @@ pub type ObservableObject = dyn Send + Sync + Any;
pub trait Updateable: 'static + Send + Sync { pub trait Updateable: 'static + Send + Sync {
fn id(&self) -> Snowflake; fn id(&self) -> Snowflake;
} }
/// Trait which defines the behavior of an Observer. An Observer is an object which is subscribed to
/// an Observable. The Observer is notified when the Observable's data changes.
/// In this case, the Observable is a [`GatewayEvent`], which is a wrapper around a WebSocketEvent.
/// Note that `Debug` is used to tell `Observer`s apart when unsubscribing.
#[async_trait]
pub trait Observer<T>: Sync + Send + std::fmt::Debug {
async fn update(&self, data: &T);
}
/// GatewayEvent is a wrapper around a WebSocketEvent. It is used to notify the observers of a
/// change in the WebSocketEvent. GatewayEvents are observable.
#[derive(Default, Debug)]
pub struct GatewayEvent<T: WebSocketEvent> {
observers: Vec<Arc<dyn Observer<T>>>,
}
impl<T: WebSocketEvent> GatewayEvent<T> {
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()
}
/// Subscribes an Observer to the GatewayEvent.
pub fn subscribe(&mut self, observable: Arc<dyn Observer<T>>) {
self.observers.push(observable);
}
/// Unsubscribes an Observer from the GatewayEvent.
pub fn unsubscribe(&mut self, observable: &dyn Observer<T>) {
// .retain()'s closure retains only those elements of the vector, which have a different
// pointer value than observable.
// The usage of the debug format to compare the generic T of observers is quite stupid, but the only thing to compare between them is T and if T == T they are the same
// anddd there is no way to do that without using format
let to_remove = format!("{:?}", observable);
self.observers
.retain(|obs| format!("{:?}", obs) != to_remove);
}
/// Notifies the observers of the GatewayEvent.
pub(crate) async fn notify(&self, new_event_data: T) {
for observer in &self.observers {
observer.update(&new_event_data).await;
}
}
}
/// A type alias for [`Arc<RwLock<T>>`], 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<T> = Arc<RwLock<T>>;

116
src/gateway/options.rs Normal file
View File

@ -0,0 +1,116 @@
// 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/.
#[derive(Clone, PartialEq, Eq, Ord, PartialOrd, Debug, Default, Copy)]
/// Options passed when initializing the gateway connection.
///
/// E.g. compression
///
/// # Note
///
/// Discord allows specifying the api version (v10, v9, ...) as well, but chorus is built upon one
/// main version (v9).
///
/// Similarly, discord also supports etf encoding, while chorus does not (yet).
/// We are looking into supporting it as an option, since it is faster and more lightweight.
///
/// See <https://docs.discord.sex/topics/gateway#connections>
pub struct GatewayOptions {
pub encoding: GatewayEncoding,
pub transport_compression: GatewayTransportCompression,
}
impl GatewayOptions {
/// Adds the options to an existing gateway url
///
/// Returns the new url
pub(crate) fn add_to_url(&self, url: String) -> String {
let mut url = url;
let mut parameters = Vec::with_capacity(2);
let encoding = self.encoding.to_url_parameter();
parameters.push(encoding);
let compression = self.transport_compression.to_url_parameter();
if let Some(some_compression) = compression {
parameters.push(some_compression);
}
let mut has_parameters = url.contains('?') && url.contains('=');
if !has_parameters {
// Insure it ends in a /, so we don't get a 400 error
if !url.ends_with('/') {
url.push('/');
}
// Lets hope that if it already has parameters the person knew to add '/'
}
for parameter in parameters {
if !has_parameters {
url = format!("{}?{}", url, parameter);
has_parameters = true;
} else {
url = format!("{}&{}", url, parameter);
}
}
url
}
}
#[derive(Clone, Copy, PartialEq, Eq, Ord, PartialOrd, Debug, Default)]
/// Possible transport compression options for the gateway.
///
/// See <https://docs.discord.sex/topics/gateway#transport-compression>
pub enum GatewayTransportCompression {
/// Do not transport compress packets
None,
/// Transport compress using zlib stream
#[default]
ZLibStream,
}
impl GatewayTransportCompression {
/// Returns the option as a url parameter.
///
/// If set to [GatewayTransportCompression::None] returns [None].
///
/// If set to anything else, returns a string like "compress=zlib-stream"
pub(crate) fn to_url_parameter(self) -> Option<String> {
match self {
Self::None => None,
Self::ZLibStream => Some(String::from("compress=zlib-stream")),
}
}
}
#[derive(Clone, Copy, PartialEq, Eq, Ord, PartialOrd, Debug, Default)]
/// See <https://docs.discord.sex/topics/gateway#encoding-and-compression>
pub enum GatewayEncoding {
/// Javascript object notation, a standard for websocket connections,
/// but contains a lot of overhead
#[default]
Json,
/// A binary format originating from Erlang
///
/// Should be lighter and faster than json.
///
/// !! Chorus does not implement ETF yet !!
ETF,
}
impl GatewayEncoding {
/// Returns the option as a url parameter.
///
/// Returns a string like "encoding=json"
pub(crate) fn to_url_parameter(self) -> String {
match self {
Self::Json => String::from("encoding=json"),
Self::ETF => String::from("encoding=etf"),
}
}
}

View File

@ -13,11 +13,11 @@ use reqwest::Client;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::errors::ChorusResult; use crate::errors::ChorusResult;
use crate::gateway::{Gateway, GatewayHandle, Shared}; use crate::gateway::{Gateway, GatewayHandle, GatewayOptions};
use crate::ratelimiter::ChorusRequest; use crate::ratelimiter::ChorusRequest;
use crate::types::types::subconfigs::limits::rates::RateLimits; use crate::types::types::subconfigs::limits::rates::RateLimits;
use crate::types::{ use crate::types::{
GeneralConfiguration, Limit, LimitType, LimitsConfiguration, User, UserSettings, GeneralConfiguration, Limit, LimitType, LimitsConfiguration, Shared, User, UserSettings,
}; };
use crate::UrlBundle; use crate::UrlBundle;
@ -31,24 +31,8 @@ pub struct Instance {
pub limits_information: Option<LimitsInformation>, pub limits_information: Option<LimitsInformation>,
#[serde(skip)] #[serde(skip)]
pub client: Client, pub client: Client,
} #[serde(skip)]
pub gateway_options: GatewayOptions,
impl PartialEq for Instance {
fn eq(&self, other: &Self) -> bool {
self.urls == other.urls
&& self.instance_info == other.instance_info
&& self.limits_information == other.limits_information
}
}
impl std::hash::Hash for Instance {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.urls.hash(state);
self.instance_info.hash(state);
if let Some(inf) = &self.limits_information {
inf.hash(state);
}
}
} }
#[derive(Debug, Clone, Serialize, Deserialize, Default, Eq)] #[derive(Debug, Clone, Serialize, Deserialize, Default, Eq)]
@ -67,6 +51,7 @@ impl std::hash::Hash for LimitsInformation {
} }
} }
#[cfg(not(tarpaulin_include))]
impl PartialEq for LimitsInformation { impl PartialEq for LimitsInformation {
fn eq(&self, other: &Self) -> bool { fn eq(&self, other: &Self) -> bool {
self.ratelimits.iter().eq(other.ratelimits.iter()) self.ratelimits.iter().eq(other.ratelimits.iter())
@ -104,6 +89,7 @@ impl Instance {
instance_info: GeneralConfiguration::default(), instance_info: GeneralConfiguration::default(),
limits_information: limit_information, limits_information: limit_information,
client: Client::new(), client: Client::new(),
gateway_options: GatewayOptions::default(),
}; };
instance.instance_info = match instance.general_configuration_schema().await { instance.instance_info = match instance.general_configuration_schema().await {
Ok(schema) => schema, Ok(schema) => schema,
@ -139,6 +125,13 @@ impl Instance {
Err(_) => Ok(None), Err(_) => Ok(None),
} }
} }
/// Sets the [`GatewayOptions`] the instance will use when spawning new connections.
///
/// These options are used on the gateways created when logging in and registering.
pub fn set_gateway_options(&mut self, options: GatewayOptions) {
self.gateway_options = options;
}
} }
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
@ -165,14 +158,6 @@ pub struct ChorusUser {
pub gateway: GatewayHandle, pub gateway: GatewayHandle,
} }
impl PartialEq for ChorusUser {
fn eq(&self, other: &Self) -> bool {
self.token == other.token
&& self.limits == other.limits
&& self.gateway.url == other.gateway.url
}
}
impl ChorusUser { impl ChorusUser {
pub fn token(&self) -> String { pub fn token(&self) -> String {
self.token.clone() self.token.clone()
@ -215,7 +200,9 @@ impl ChorusUser {
let object = Arc::new(RwLock::new(User::default())); let object = Arc::new(RwLock::new(User::default()));
let wss_url = instance.read().unwrap().urls.wss.clone(); let wss_url = instance.read().unwrap().urls.wss.clone();
// Dummy gateway object // Dummy gateway object
let gateway = Gateway::spawn(wss_url).await.unwrap(); let gateway = Gateway::spawn(wss_url, GatewayOptions::default())
.await
.unwrap();
ChorusUser { ChorusUser {
token, token,
belongs_to: instance.clone(), belongs_to: instance.clone(),

View File

@ -98,7 +98,6 @@ This crate uses Semantic Versioning 2.0.0 as its versioning scheme. You can read
)] )]
#![allow(clippy::module_inception)] #![allow(clippy::module_inception)]
#![deny( #![deny(
missing_debug_implementations,
clippy::extra_unused_lifetimes, clippy::extra_unused_lifetimes,
clippy::from_over_into, clippy::from_over_into,
clippy::needless_borrow, clippy::needless_borrow,
@ -110,7 +109,9 @@ This crate uses Semantic Versioning 2.0.0 as its versioning scheme. You can read
clippy::unimplemented, clippy::unimplemented,
clippy::dbg_macro, clippy::dbg_macro,
clippy::print_stdout, clippy::print_stdout,
clippy::print_stderr clippy::print_stderr,
missing_debug_implementations,
missing_copy_implementations
)] )]
#[cfg(all(feature = "rt", feature = "rt_multi_thread"))] #[cfg(all(feature = "rt", feature = "rt_multi_thread"))]
compile_error!("feature \"rt\" and feature \"rt_multi_thread\" cannot be enabled at the same time"); compile_error!("feature \"rt\" and feature \"rt_multi_thread\" cannot be enabled at the same time");

View File

@ -6,7 +6,6 @@
use std::collections::HashMap; use std::collections::HashMap;
use log::{self, debug};
use reqwest::{Client, RequestBuilder, Response}; use reqwest::{Client, RequestBuilder, Response};
use serde::Deserialize; use serde::Deserialize;
use serde_json::from_str; use serde_json::from_str;
@ -88,7 +87,7 @@ impl ChorusRequest {
let client = user.belongs_to.read().unwrap().client.clone(); let client = user.belongs_to.read().unwrap().client.clone();
let result = match client.execute(self.request.build().unwrap()).await { let result = match client.execute(self.request.build().unwrap()).await {
Ok(result) => { Ok(result) => {
debug!("Request successful: {:?}", result); log::trace!("Request successful: {:?}", result);
result result
} }
Err(error) => { Err(error) => {
@ -494,7 +493,7 @@ impl ChorusRequest {
user: &mut ChorusUser, user: &mut ChorusUser,
) -> ChorusResult<T> { ) -> ChorusResult<T> {
let response = self.send_request(user).await?; let response = self.send_request(user).await?;
debug!("Got response: {:?}", response); log::trace!("Got response: {:?}", response);
let response_text = match response.text().await { let response_text = match response.text().await {
Ok(string) => string, Ok(string) => string,
Err(e) => { Err(e) => {

View File

@ -6,7 +6,7 @@ use serde::{Deserialize, Serialize};
use crate::types::config::types::subconfigs::defaults::{guild::GuildDefaults, user::UserDefaults}; use crate::types::config::types::subconfigs::defaults::{guild::GuildDefaults, user::UserDefaults};
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize, Copy)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct DefaultsConfiguration { pub struct DefaultsConfiguration {
pub guild: GuildDefaults, pub guild: GuildDefaults,

View File

@ -3,19 +3,10 @@
// file, You can obtain one at http://mozilla.org/MPL/2.0/. // file, You can obtain one at http://mozilla.org/MPL/2.0/.
use std::fmt::{Display, Formatter}; use std::fmt::{Display, Formatter};
#[cfg(feature = "sqlx")]
use std::io::Write;
use std::ops::{Deref, DerefMut}; use std::ops::{Deref, DerefMut};
use std::str::FromStr; use std::str::FromStr;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
#[cfg(feature = "sqlx")]
use sqlx::{
database::{HasArguments, HasValueRef},
encode::IsNull,
error::BoxDynError,
Decode, MySql,
};
use crate::types::config::types::subconfigs::guild::{ use crate::types::config::types::subconfigs::guild::{
autojoin::AutoJoinConfiguration, discovery::DiscoverConfiguration, autojoin::AutoJoinConfiguration, discovery::DiscoverConfiguration,
@ -172,8 +163,8 @@ impl Display for GuildFeaturesList {
#[cfg(feature = "sqlx")] #[cfg(feature = "sqlx")]
impl<'r> sqlx::Decode<'r, sqlx::MySql> for GuildFeaturesList { impl<'r> sqlx::Decode<'r, sqlx::MySql> for GuildFeaturesList {
fn decode(value: <MySql as HasValueRef<'r>>::ValueRef) -> Result<Self, BoxDynError> { fn decode(value: <sqlx::MySql as sqlx::database::HasValueRef<'r>>::ValueRef) -> Result<Self, sqlx::error::BoxDynError> {
let v = <&str as Decode<sqlx::MySql>>::decode(value)?; let v = <String as sqlx::Decode<sqlx::MySql>>::decode(value)?;
Ok(Self( Ok(Self(
v.split(',') v.split(',')
.filter(|f| !f.is_empty()) .filter(|f| !f.is_empty())
@ -185,9 +176,9 @@ impl<'r> sqlx::Decode<'r, sqlx::MySql> for GuildFeaturesList {
#[cfg(feature = "sqlx")] #[cfg(feature = "sqlx")]
impl<'q> sqlx::Encode<'q, sqlx::MySql> for GuildFeaturesList { impl<'q> sqlx::Encode<'q, sqlx::MySql> for GuildFeaturesList {
fn encode_by_ref(&self, buf: &mut <MySql as HasArguments<'q>>::ArgumentBuffer) -> IsNull { fn encode_by_ref(&self, buf: &mut <sqlx::MySql as sqlx::database::HasArguments<'q>>::ArgumentBuffer) -> sqlx::encode::IsNull {
if self.is_empty() { if self.is_empty() {
return IsNull::Yes; return sqlx::encode::IsNull::Yes;
} }
let features = self let features = self
.iter() .iter()
@ -195,30 +186,18 @@ impl<'q> sqlx::Encode<'q, sqlx::MySql> for GuildFeaturesList {
.collect::<Vec<_>>() .collect::<Vec<_>>()
.join(","); .join(",");
let _ = buf.write(features.as_bytes()); <String as sqlx::Encode<sqlx::MySql>>::encode_by_ref(&features, buf)
IsNull::No
} }
} }
#[cfg(feature = "sqlx")] #[cfg(feature = "sqlx")]
impl sqlx::Type<sqlx::MySql> for GuildFeaturesList { impl sqlx::Type<sqlx::MySql> for GuildFeaturesList {
fn type_info() -> sqlx::mysql::MySqlTypeInfo { fn type_info() -> sqlx::mysql::MySqlTypeInfo {
<&str as sqlx::Type<sqlx::MySql>>::type_info() <String as sqlx::Type<sqlx::MySql>>::type_info()
} }
fn compatible(ty: &sqlx::mysql::MySqlTypeInfo) -> bool { fn compatible(ty: &sqlx::mysql::MySqlTypeInfo) -> bool {
<&str as sqlx::Type<sqlx::MySql>>::compatible(ty) <String as sqlx::Type<sqlx::MySql>>::compatible(ty)
}
}
#[cfg(feature = "sqlx")]
impl sqlx::TypeInfo for GuildFeaturesList {
fn is_null(&self) -> bool {
false
}
fn name(&self) -> &str {
"TEXT"
} }
} }
@ -376,6 +355,12 @@ impl FromStr for GuildFeatures {
} }
} }
impl From<Vec<GuildFeatures>> for GuildFeaturesList {
fn from(features: Vec<GuildFeatures>) -> GuildFeaturesList {
Self(features)
}
}
impl GuildFeatures { impl GuildFeatures {
pub fn to_str(&self) -> &'static str { pub fn to_str(&self) -> &'static str {
match *self { match *self {

View File

@ -4,7 +4,9 @@
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] #[derive(
Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize, Copy, Hash, PartialOrd, Ord,
)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct LoginConfiguration { pub struct LoginConfiguration {
pub require_captcha: bool, pub require_captcha: bool,

View File

@ -4,7 +4,7 @@
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Ord, PartialOrd, Copy, Hash)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct MetricsConfiguration { pub struct MetricsConfiguration {
pub timeout: u64, pub timeout: u64,

View File

@ -4,7 +4,9 @@
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] #[derive(
Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize, Copy, Hash, PartialOrd, Ord,
)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct PasswordResetConfiguration { pub struct PasswordResetConfiguration {
pub require_captcha: bool, pub require_captcha: bool,

View File

@ -3,10 +3,11 @@
// file, You can obtain one at http://mozilla.org/MPL/2.0/. // file, You can obtain one at http://mozilla.org/MPL/2.0/.
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_aux::prelude::deserialize_number_from_string;
use crate::types::config::types::subconfigs::register::{ use crate::types::{config::types::subconfigs::register::{
DateOfBirthConfiguration, PasswordConfiguration, RegistrationEmailConfiguration, DateOfBirthConfiguration, PasswordConfiguration, RegistrationEmailConfiguration,
}; }, Rights};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
@ -22,7 +23,8 @@ pub struct RegisterConfiguration {
pub allow_multiple_accounts: bool, pub allow_multiple_accounts: bool,
pub block_proxies: bool, pub block_proxies: bool,
pub incrementing_discriminators: bool, pub incrementing_discriminators: bool,
pub default_rights: String, #[serde(deserialize_with = "deserialize_number_from_string")]
pub default_rights: Rights,
} }
impl Default for RegisterConfiguration { impl Default for RegisterConfiguration {
@ -39,7 +41,7 @@ impl Default for RegisterConfiguration {
allow_multiple_accounts: true, allow_multiple_accounts: true,
block_proxies: true, block_proxies: true,
incrementing_discriminators: false, incrementing_discriminators: false,
default_rights: String::from("875069521787904"), default_rights: Rights::from_bits(648540060672).expect("failed to parse default_rights"),
} }
} }
} }

View File

@ -6,7 +6,7 @@ use serde::{Deserialize, Serialize};
use crate::types::{ExplicitContentFilterLevel, MessageNotificationLevel}; use crate::types::{ExplicitContentFilterLevel, MessageNotificationLevel};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Copy, Hash, PartialOrd, Ord)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct GuildDefaults { pub struct GuildDefaults {
pub max_presences: u64, pub max_presences: u64,

View File

@ -4,7 +4,7 @@
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Copy, Hash, PartialOrd, Ord)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct UserDefaults { pub struct UserDefaults {
pub premium: bool, pub premium: bool,

View File

@ -4,7 +4,7 @@
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Copy, PartialOrd, Ord, Hash)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct DiscoverConfiguration { pub struct DiscoverConfiguration {
pub show_all_guilds: bool, pub show_all_guilds: bool,

View File

@ -4,7 +4,7 @@
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Copy, PartialOrd, Ord)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct ChannelLimits { pub struct ChannelLimits {
pub max_pins: u16, pub max_pins: u16,

View File

@ -4,7 +4,7 @@
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Copy, PartialOrd, Ord, Hash)]
pub struct GlobalRateLimit { pub struct GlobalRateLimit {
pub limit: u16, pub limit: u16,
pub window: u64, pub window: u64,
@ -21,7 +21,7 @@ impl Default for GlobalRateLimit {
} }
} }
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Copy, PartialOrd, Ord, Hash)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct GlobalRateLimits { pub struct GlobalRateLimits {
pub register: GlobalRateLimit, pub register: GlobalRateLimit,

View File

@ -4,7 +4,7 @@
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Copy, Hash, PartialOrd, Ord)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct GuildLimits { pub struct GuildLimits {
pub max_roles: u16, pub max_roles: u16,

View File

@ -4,7 +4,7 @@
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Copy, PartialOrd, Ord, Hash)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct MessageLimits { pub struct MessageLimits {
pub max_characters: u32, pub max_characters: u32,

View File

@ -6,7 +6,7 @@ use serde::{Deserialize, Serialize};
use crate::types::config::types::subconfigs::limits::ratelimits::RateLimitOptions; use crate::types::config::types::subconfigs::limits::ratelimits::RateLimitOptions;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Hash)] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Copy, PartialOrd, Ord, Hash)]
pub struct AuthRateLimit { pub struct AuthRateLimit {
pub login: RateLimitOptions, pub login: RateLimitOptions,
pub register: RateLimitOptions, pub register: RateLimitOptions,

View File

@ -7,7 +7,7 @@ use serde::{Deserialize, Serialize};
pub mod auth; pub mod auth;
pub mod route; pub mod route;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Hash)] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Hash, PartialOrd, Ord, Copy)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct RateLimitOptions { pub struct RateLimitOptions {
pub bot: Option<u64>, pub bot: Option<u64>,

View File

@ -8,7 +8,7 @@ use crate::types::config::types::subconfigs::limits::ratelimits::{
auth::AuthRateLimit, RateLimitOptions, auth::AuthRateLimit, RateLimitOptions,
}; };
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Hash)] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Hash, Copy, PartialOrd, Ord)]
pub struct RouteRateLimit { pub struct RouteRateLimit {
pub guild: RateLimitOptions, pub guild: RateLimitOptions,
pub webhook: RateLimitOptions, pub webhook: RateLimitOptions,

View File

@ -50,14 +50,14 @@ impl Default for RateLimits {
impl RateLimits { impl RateLimits {
pub fn to_hash_map(&self) -> HashMap<LimitType, RateLimitOptions> { pub fn to_hash_map(&self) -> HashMap<LimitType, RateLimitOptions> {
let mut map = HashMap::new(); let mut map = HashMap::new();
map.insert(LimitType::AuthLogin, self.routes.auth.login.clone()); map.insert(LimitType::AuthLogin, self.routes.auth.login);
map.insert(LimitType::AuthRegister, self.routes.auth.register.clone()); map.insert(LimitType::AuthRegister, self.routes.auth.register);
map.insert(LimitType::ChannelBaseline, self.routes.channel.clone()); map.insert(LimitType::ChannelBaseline, self.routes.channel);
map.insert(LimitType::Error, self.error.clone()); map.insert(LimitType::Error, self.error);
map.insert(LimitType::Global, self.global.clone()); map.insert(LimitType::Global, self.global);
map.insert(LimitType::Ip, self.ip.clone()); map.insert(LimitType::Ip, self.ip);
map.insert(LimitType::WebhookBaseline, self.routes.webhook.clone()); map.insert(LimitType::WebhookBaseline, self.routes.webhook);
map.insert(LimitType::GuildBaseline, self.routes.guild.clone()); map.insert(LimitType::GuildBaseline, self.routes.guild);
map map
} }
} }

View File

@ -4,7 +4,7 @@
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, PartialOrd, Ord, Copy, Hash)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct UserLimits { pub struct UserLimits {
pub max_guilds: u64, pub max_guilds: u64,

View File

@ -4,13 +4,13 @@
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Serialize, Deserialize, PartialOrd, Copy)]
pub struct LatLong { pub struct LatLong {
pub latitude: f64, pub latitude: f64,
pub longitude: f64, pub longitude: f64,
} }
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Serialize, Deserialize, PartialOrd)]
pub struct Region { pub struct Region {
pub id: String, pub id: String,
pub name: String, pub name: String,

View File

@ -4,7 +4,7 @@
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Copy, PartialOrd, Ord, Hash)]
pub struct DateOfBirthConfiguration { pub struct DateOfBirthConfiguration {
pub required: bool, pub required: bool,
pub minimum: u8, pub minimum: u8,

View File

@ -4,7 +4,7 @@
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Copy, PartialOrd, Ord, Hash)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct PasswordConfiguration { pub struct PasswordConfiguration {
pub required: bool, pub required: bool,

View File

@ -4,7 +4,7 @@
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Copy, Hash, PartialOrd, Ord)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct TwoFactorConfiguration { pub struct TwoFactorConfiguration {
pub generate_backup_codes: bool, pub generate_backup_codes: bool,

View File

@ -4,7 +4,7 @@
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Copy, PartialOrd, Ord, Hash)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct TemplateConfiguration { pub struct TemplateConfiguration {
pub enabled: bool, pub enabled: bool,

View File

@ -7,10 +7,13 @@ use serde::{Deserialize, Serialize};
use serde_json::Value; use serde_json::Value;
use serde_repr::{Deserialize_repr, Serialize_repr}; use serde_repr::{Deserialize_repr, Serialize_repr};
use crate::gateway::Shared;
use crate::types::utils::Snowflake; use crate::types::utils::Snowflake;
use crate::types::Shared;
use crate::types::{Team, User}; use crate::types::{Team, User};
#[allow(unused_imports)]
use super::{arc_rwlock_ptr_eq, option_arc_rwlock_ptr_eq};
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))] #[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))]
/// # Reference /// # Reference
@ -31,7 +34,7 @@ pub struct Application {
pub verify_key: String, pub verify_key: String,
#[cfg_attr(feature = "sqlx", sqlx(skip))] #[cfg_attr(feature = "sqlx", sqlx(skip))]
pub owner: Shared<User>, pub owner: Shared<User>,
pub flags: u64, pub flags: ApplicationFlags,
#[cfg(feature = "sqlx")] #[cfg(feature = "sqlx")]
pub redirect_uris: Option<sqlx::types::Json<Vec<String>>>, pub redirect_uris: Option<sqlx::types::Json<Vec<String>>>,
#[cfg(not(feature = "sqlx"))] #[cfg(not(feature = "sqlx"))]
@ -59,6 +62,61 @@ pub struct Application {
pub team: Option<Team>, pub team: Option<Team>,
} }
#[cfg(not(tarpaulin_include))]
impl PartialEq for Application {
fn eq(&self, other: &Self) -> bool {
self.id == other.id
&& self.name == other.name
&& self.icon == other.icon
&& self.description == other.description
&& self.summary == other.summary
&& self.r#type == other.r#type
&& self.hook == other.hook
&& self.bot_public == other.bot_public
&& self.bot_require_code_grant == other.bot_require_code_grant
&& self.verify_key == other.verify_key
&& arc_rwlock_ptr_eq(&self.owner, &other.owner)
&& self.flags == other.flags
&& self.redirect_uris == other.redirect_uris
&& self.rpc_application_state == other.rpc_application_state
&& self.store_application_state == other.store_application_state
&& self.verification_state == other.verification_state
&& self.interactions_endpoint_url == other.interactions_endpoint_url
&& self.integration_public == other.integration_public
&& self.integration_require_code_grant == other.integration_require_code_grant
&& self.discoverability_state == other.discoverability_state
&& self.discovery_eligibility_flags == other.discovery_eligibility_flags
&& self.tags == other.tags
&& self.cover_image == other.cover_image
&& compare_install_params(&self.install_params, &other.install_params)
&& self.terms_of_service_url == other.terms_of_service_url
&& self.privacy_policy_url == other.privacy_policy_url
&& self.team == other.team
}
}
#[cfg(not(tarpaulin_include))]
#[cfg(feature = "sqlx")]
fn compare_install_params(
a: &Option<sqlx::types::Json<InstallParams>>,
b: &Option<sqlx::types::Json<InstallParams>>,
) -> bool {
match (a, b) {
(Some(a), Some(b)) => a.encode_to_string() == b.encode_to_string(),
(None, None) => true,
_ => false,
}
}
#[cfg(not(tarpaulin_include))]
#[cfg(not(feature = "sqlx"))]
fn compare_install_params(
a: &Option<Shared<InstallParams>>,
b: &Option<Shared<InstallParams>>,
) -> bool {
option_arc_rwlock_ptr_eq(a, b)
}
impl Default for Application { impl Default for Application {
fn default() -> Self { fn default() -> Self {
Self { Self {
@ -73,7 +131,7 @@ impl Default for Application {
bot_require_code_grant: false, bot_require_code_grant: false,
verify_key: "".to_string(), verify_key: "".to_string(),
owner: Default::default(), owner: Default::default(),
flags: 0, flags: ApplicationFlags::empty(),
redirect_uris: None, redirect_uris: None,
rpc_application_state: 0, rpc_application_state: 0,
store_application_state: 1, store_application_state: 1,
@ -93,12 +151,6 @@ impl Default for Application {
} }
} }
impl Application {
pub fn flags(&self) -> ApplicationFlags {
ApplicationFlags::from_bits(self.flags.to_owned()).unwrap()
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
/// # Reference /// # Reference
/// See <https://discord.com/developers/docs/resources/application#install-params-object> /// See <https://discord.com/developers/docs/resources/application#install-params-object>
@ -108,7 +160,8 @@ pub struct InstallParams {
} }
bitflags! { bitflags! {
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, chorus_macros::SerdeBitFlags)]
#[cfg_attr(feature = "sqlx", derive(chorus_macros::SqlxBitFlags))]
/// # Reference /// # Reference
/// See <https://discord.com/developers/docs/resources/application#application-object-application-flags> /// See <https://discord.com/developers/docs/resources/application#application-object-application-flags>
pub struct ApplicationFlags: u64 { pub struct ApplicationFlags: u64 {
@ -212,7 +265,9 @@ pub struct GuildApplicationCommandPermissions {
pub permissions: Vec<Shared<ApplicationCommandPermission>>, pub permissions: Vec<Shared<ApplicationCommandPermission>>,
} }
#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)] #[derive(
Debug, Default, Clone, PartialEq, Serialize, Deserialize, Copy, Eq, Hash, PartialOrd, Ord,
)]
/// See <https://discord.com/developers/docs/interactions/application-commands#application-command-permissions-object-application-command-permissions-structure> /// See <https://discord.com/developers/docs/interactions/application-commands#application-command-permissions-object-application-command-permissions-structure>
pub struct ApplicationCommandPermission { pub struct ApplicationCommandPermission {
pub id: Snowflake, pub id: Snowflake,
@ -222,7 +277,19 @@ pub struct ApplicationCommandPermission {
pub permission: bool, pub permission: bool,
} }
#[derive(Serialize_repr, Deserialize_repr, Debug, Default, Clone, PartialEq, Eq, Hash)] #[derive(
Serialize_repr,
Deserialize_repr,
Debug,
Default,
Clone,
PartialEq,
Eq,
Hash,
Copy,
PartialOrd,
Ord,
)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")] #[serde(rename_all = "SCREAMING_SNAKE_CASE")]
#[repr(u8)] #[repr(u8)]
/// See <https://discord.com/developers/docs/interactions/application-commands#application-command-permissions-object-application-command-permission-type> /// See <https://discord.com/developers/docs/interactions/application-commands#application-command-permissions-object-application-command-permission-type>

View File

@ -2,25 +2,85 @@
// License, v. 2.0. If a copy of the MPL was not distributed with this // 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/. // file, You can obtain one at http://mozilla.org/MPL/2.0/.
use serde::{Deserialize, Serialize}; #[allow(unused_imports)]
use super::option_vec_arc_rwlock_ptr_eq;
use serde::{Deserialize, Serialize};
use serde_repr::{Deserialize_repr, Serialize_repr};
use crate::gateway::Shared;
use crate::types::utils::Snowflake; use crate::types::utils::Snowflake;
use crate::types::{
AutoModerationRuleTriggerType, IntegrationType, PermissionOverwriteType, Shared,
};
#[derive(Serialize, Deserialize, Debug, Default, Clone)] #[derive(Serialize, Deserialize, Debug, Default, Clone)]
#[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))]
/// See <https://discord.com/developers/docs/resources/audit-log#audit-log-entry-object> /// See <https://discord.com/developers/docs/resources/audit-log#audit-log-entry-object>
pub struct AuditLogEntry { pub struct AuditLogEntry {
pub target_id: Option<String>, pub target_id: Option<String>,
#[cfg(feature = "sqlx")]
pub changes: sqlx::types::Json<Option<Vec<Shared<AuditLogChange>>>>,
#[cfg(not(feature = "sqlx"))]
pub changes: Option<Vec<Shared<AuditLogChange>>>, pub changes: Option<Vec<Shared<AuditLogChange>>>,
pub user_id: Option<Snowflake>, pub user_id: Option<Snowflake>,
pub id: Snowflake, pub id: Snowflake,
// to:do implement an enum for these types pub action_type: AuditLogActionType,
pub action_type: u8, #[cfg(feature = "sqlx")]
// to:do add better options type pub options: Option<sqlx::types::Json<AuditEntryInfo>>,
pub options: Option<serde_json::Value>, #[cfg(not(feature = "sqlx"))]
pub options: Option<AuditEntryInfo>,
pub reason: Option<String>, pub reason: Option<String>,
} }
impl PartialEq for AuditLogEntry {
fn eq(&self, other: &Self) -> bool {
self.target_id == other.target_id
&& self.user_id == other.user_id
&& self.id == other.id
&& self.action_type == other.action_type
&& compare_options(&self.options, &other.options)
&& self.reason == other.reason
&& compare_changes(&self.changes, &other.changes)
}
}
#[cfg(not(tarpaulin_include))]
#[cfg(feature = "sqlx")]
fn compare_options(
a: &Option<sqlx::types::Json<AuditEntryInfo>>,
b: &Option<sqlx::types::Json<AuditEntryInfo>>,
) -> bool {
match (a, b) {
(Some(a), Some(b)) => a.encode_to_string() == b.encode_to_string(),
(None, None) => true,
_ => false,
}
}
#[cfg(not(tarpaulin_include))]
#[cfg(not(feature = "sqlx"))]
fn compare_options(a: &Option<AuditEntryInfo>, b: &Option<AuditEntryInfo>) -> bool {
a == b
}
#[cfg(not(tarpaulin_include))]
#[cfg(feature = "sqlx")]
fn compare_changes(
a: &sqlx::types::Json<Option<Vec<Shared<AuditLogChange>>>>,
b: &sqlx::types::Json<Option<Vec<Shared<AuditLogChange>>>>,
) -> bool {
a.encode_to_string() == b.encode_to_string()
}
#[cfg(not(tarpaulin_include))]
#[cfg(not(feature = "sqlx"))]
fn compare_changes(
a: &Option<Vec<Shared<AuditLogChange>>>,
b: &Option<Vec<Shared<AuditLogChange>>>,
) -> bool {
option_vec_arc_rwlock_ptr_eq(a, b)
}
#[derive(Serialize, Deserialize, Debug, Default, Clone)] #[derive(Serialize, Deserialize, Debug, Default, Clone)]
/// See <https://discord.com/developers/docs/resources/audit-log#audit-log-change-object> /// See <https://discord.com/developers/docs/resources/audit-log#audit-log-change-object>
pub struct AuditLogChange { pub struct AuditLogChange {
@ -28,3 +88,175 @@ pub struct AuditLogChange {
pub old_value: Option<serde_json::Value>, pub old_value: Option<serde_json::Value>,
pub key: String, pub key: String,
} }
#[derive(
Default,
Serialize_repr,
Deserialize_repr,
Debug,
Clone,
Copy,
PartialEq,
Eq,
Hash,
PartialOrd,
Ord,
)]
#[repr(u8)]
#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
/// # Reference:
/// See <https://docs.discord.sex/resources/audit-log#audit-log-events>
pub enum AuditLogActionType {
#[default]
/// Guild settings were updated
GuildUpdate = 1,
/// Channel was created
ChannelCreate = 10,
/// Channel settings were updated
ChannelUpdate = 11,
/// Channel was deleted
ChannelDelete = 12,
/// Permission overwrite was added to a channel
ChannelOverwriteCreate = 13,
/// Permission overwrite was updated for a channel
ChannelOverwriteUpdate = 14,
/// Permission overwrite was deleted from a channel
ChannelOverwriteDelete = 15,
/// Member was removed from guild
MemberKick = 20,
/// Members were pruned from guild
MemberPrune = 21,
/// Member was banned from guild
MemberBanAdd = 22,
/// Member was unbanned from guild
MemberBanRemove = 23,
/// Member was updated in guild
MemberUpdate = 24,
/// Member was added or removed from a role
MemberRoleUpdate = 25,
/// Member was moved to a different voice channel
MemberMove = 26,
/// Member was disconnected from a voice channel
MemberDisconnect = 27,
/// Bot user was added to guild
BotAdd = 28,
/// Role was created
RoleCreate = 30,
/// Role was edited
RoleUpdate = 31,
/// Role was deleted
RoleDelete = 32,
/// Guild invite was created
InviteCreate = 40,
/// Guild invite was updated
InviteUpdate = 41,
/// Guild invite was deleted
InviteDelete = 42,
/// Webhook was created
WebhookCreate = 50,
/// Webhook properties or channel were updated
WebhookUpdate = 51,
/// Webhook was deleted
WebhookDelete = 52,
/// Emoji was created
EmojiCreate = 60,
/// Emoji name was updated
EmojiUpdate = 61,
/// Emoji was deleted
EmojiDelete = 62,
/// Single message was deleted
MessageDelete = 72,
/// Multiple messages were deleted
MessageBulkDelete = 73,
/// Message was pinned to a channel
MessagePin = 74,
/// Message was unpinned from a channel
MessageUnpin = 75,
/// Interaction was added to guild
IntegrationCreate = 80,
/// Integration was updated (e.g. its scopes were updated)
IntegrationUpdate = 81,
/// Integration was removed from guild
IntegrationDelete = 82,
/// Stage instance was created (stage channel becomes live)
StageInstanceCreate = 83,
/// Stage instance details were updated
StageInstanceUpdate = 84,
/// Stage instance was deleted (stage channel no longer live)
StageInstanceDelete = 85,
/// Sticker was created
StickerCreate = 90,
/// Sticker details were updated
StickerUpdate = 91,
/// Sticker was deleted
StickerDelete = 92,
/// Event was created
GuildScheduledEventCreate = 100,
/// Event was updated
GuildScheduledEventUpdate = 101,
/// Event was cancelled
GuildScheduledEventDelete = 102,
/// Thread was created in a channel
ThreadCreate = 110,
/// Thread was updated
ThreadUpdate = 111,
/// Thread was deleted
ThreadDelete = 112,
/// Permissions were updated for a command
ApplicationCommandPermissionUpdate = 121,
/// AutoMod rule created
AutoModerationRuleCreate = 140,
/// AutoMod rule was updated
AutoModerationRuleUpdate = 141,
/// AutoMod rule was deleted
AutoModerationRuleDelete = 142,
/// Message was blocked by AutoMod
AutoModerationBlockMessage = 143,
/// Message was flagged by AutoMod
AutoModerationFlagToChannel = 144,
/// Member was timed out by AutoMod
AutoModerationUserCommunicationDisabled = 145,
/// Member was quarantined by AutoMod
AutoModerationQuarantineUser = 146,
/// Creator monetization request was created
CreatorMonetizationRequestCreated = 150,
/// Creator monetization terms were accepted
CreatorMonetizationTermsAccepted = 151,
/// Onboarding prompt was created
OnboardingPromptCreate = 163,
/// Onboarding prompt was updated
OnboardingPromptUpdate = 164,
/// Onboarding prompt was deleted
OnboardingPromptDelete = 165,
/// Onboarding was created
OnboardingCreate = 166,
/// Onboarding was updated
OnboardingUpdate = 167,
/// Voice channel status was updated
VoiceChannelStatusUpdate = 192,
/// Voice channel status was deleted
VoiceChannelStatusDelete = 193,
}
#[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq)]
pub struct AuditEntryInfo {
pub application_id: Option<Snowflake>,
pub auto_moderation_rule_name: Option<String>,
pub auto_moderation_rule_trigger_type: Option<AutoModerationRuleTriggerType>,
pub channel_id: Option<Snowflake>,
// #[serde(option_string)]
pub count: Option<u64>,
// #[serde(option_string)]
pub delete_member_days: Option<u64>,
/// The ID of the overwritten entity
pub id: Option<Snowflake>,
pub integration_type: Option<IntegrationType>,
// #[serde(option_string)]
pub members_removed: Option<u64>,
// #[serde(option_string)]
pub message_id: Option<u64>,
pub role_name: Option<String>,
#[serde(rename = "type")]
pub overwrite_type: Option<PermissionOverwriteType>,
pub status: Option<String>,
}

View File

@ -2,9 +2,9 @@
// License, v. 2.0. If a copy of the MPL was not distributed with this // 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/. // file, You can obtain one at http://mozilla.org/MPL/2.0/.
use crate::gateway::Shared;
#[cfg(feature = "client")] #[cfg(feature = "client")]
use crate::gateway::Updateable; use crate::gateway::Updateable;
use crate::types::Shared;
#[cfg(feature = "client")] #[cfg(feature = "client")]
use chorus_macros::Updateable; use chorus_macros::Updateable;
@ -31,7 +31,7 @@ pub struct AutoModerationRule {
pub exempt_channels: Vec<Snowflake>, pub exempt_channels: Vec<Snowflake>,
} }
#[derive(Serialize_repr, Deserialize_repr, Debug, Clone, Default)] #[derive(Serialize_repr, Deserialize_repr, Debug, Clone, Default, Copy)]
#[repr(u8)] #[repr(u8)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")] #[serde(rename_all = "SCREAMING_SNAKE_CASE")]
/// See <https://discord.com/developers/docs/resources/auto-moderation#auto-moderation-rule-object-event-types> /// See <https://discord.com/developers/docs/resources/auto-moderation#auto-moderation-rule-object-event-types>
@ -40,7 +40,9 @@ pub enum AutoModerationRuleEventType {
MessageSend = 1, MessageSend = 1,
} }
#[derive(Serialize_repr, Deserialize_repr, Debug, Clone, Default)] #[derive(
Serialize_repr, Deserialize_repr, Debug, Clone, Default, PartialEq, Eq, PartialOrd, Ord, Copy,
)]
#[repr(u8)] #[repr(u8)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")] #[serde(rename_all = "SCREAMING_SNAKE_CASE")]
/// See <https://discord.com/developers/docs/resources/auto-moderation#auto-moderation-rule-object-trigger-types> /// See <https://discord.com/developers/docs/resources/auto-moderation#auto-moderation-rule-object-trigger-types>
@ -52,7 +54,7 @@ pub enum AutoModerationRuleTriggerType {
MentionSpam = 5, MentionSpam = 5,
} }
#[derive(Serialize, Deserialize, Debug, Clone, Default)] #[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq, Eq, PartialOrd, Ord)]
#[serde(untagged)] #[serde(untagged)]
/// See <https://discord.com/developers/docs/resources/auto-moderation#auto-moderation-rule-object-trigger-metadata> /// See <https://discord.com/developers/docs/resources/auto-moderation#auto-moderation-rule-object-trigger-metadata>
pub enum AutoModerationRuleTriggerMetadata { pub enum AutoModerationRuleTriggerMetadata {
@ -63,7 +65,7 @@ pub enum AutoModerationRuleTriggerMetadata {
None, None,
} }
#[derive(Serialize, Deserialize, Debug, Clone, Default)] #[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq, Eq, PartialOrd, Ord)]
/// See <https://discord.com/developers/docs/resources/auto-moderation#auto-moderation-rule-object-trigger-metadata> /// See <https://discord.com/developers/docs/resources/auto-moderation#auto-moderation-rule-object-trigger-metadata>
pub struct AutoModerationRuleTriggerMetadataForKeyword { pub struct AutoModerationRuleTriggerMetadataForKeyword {
pub keyword_filter: Vec<String>, pub keyword_filter: Vec<String>,
@ -71,14 +73,14 @@ pub struct AutoModerationRuleTriggerMetadataForKeyword {
pub allow_list: Vec<String>, pub allow_list: Vec<String>,
} }
#[derive(Serialize, Deserialize, Debug, Clone, Default)] #[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq, Eq, PartialOrd, Ord)]
/// See <https://discord.com/developers/docs/resources/auto-moderation#auto-moderation-rule-object-trigger-metadata> /// See <https://discord.com/developers/docs/resources/auto-moderation#auto-moderation-rule-object-trigger-metadata>
pub struct AutoModerationRuleTriggerMetadataForKeywordPreset { pub struct AutoModerationRuleTriggerMetadataForKeywordPreset {
pub presets: Vec<AutoModerationRuleKeywordPresetType>, pub presets: Vec<AutoModerationRuleKeywordPresetType>,
pub allow_list: Vec<String>, pub allow_list: Vec<String>,
} }
#[derive(Serialize, Deserialize, Debug, Clone, Default)] #[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq, Eq, PartialOrd, Ord, Copy)]
/// See <https://discord.com/developers/docs/resources/auto-moderation#auto-moderation-rule-object-trigger-metadata> /// See <https://discord.com/developers/docs/resources/auto-moderation#auto-moderation-rule-object-trigger-metadata>
pub struct AutoModerationRuleTriggerMetadataForMentionSpam { pub struct AutoModerationRuleTriggerMetadataForMentionSpam {
/// Max 50 /// Max 50
@ -86,7 +88,9 @@ pub struct AutoModerationRuleTriggerMetadataForMentionSpam {
pub mention_raid_protection_enabled: bool, pub mention_raid_protection_enabled: bool,
} }
#[derive(Serialize_repr, Deserialize_repr, Debug, Clone, Default)] #[derive(
Serialize_repr, Deserialize_repr, Debug, Clone, Default, PartialEq, Eq, PartialOrd, Ord, Copy,
)]
#[repr(u8)] #[repr(u8)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")] #[serde(rename_all = "SCREAMING_SNAKE_CASE")]
/// See <https://discord.com/developers/docs/resources/auto-moderation#auto-moderation-rule-object-keyword-preset-types> /// See <https://discord.com/developers/docs/resources/auto-moderation#auto-moderation-rule-object-keyword-preset-types>
@ -105,7 +109,9 @@ pub struct AutoModerationAction {
pub metadata: Option<Shared<AutoModerationActionMetadata>>, pub metadata: Option<Shared<AutoModerationActionMetadata>>,
} }
#[derive(Serialize_repr, Deserialize_repr, Debug, Clone, Default)] #[derive(
Serialize_repr, Deserialize_repr, Debug, Clone, Default, PartialEq, Eq, PartialOrd, Ord, Copy, Hash
)]
#[repr(u8)] #[repr(u8)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")] #[serde(rename_all = "SCREAMING_SNAKE_CASE")]
/// See <https://discord.com/developers/docs/resources/auto-moderation#auto-moderation-action-object-action-types> /// See <https://discord.com/developers/docs/resources/auto-moderation#auto-moderation-action-object-action-types>
@ -116,7 +122,7 @@ pub enum AutoModerationActionType {
Timeout = 3, Timeout = 3,
} }
#[derive(Serialize, Deserialize, Debug, Clone, Default)] #[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq, Eq, PartialOrd, Ord)]
#[serde(untagged)] #[serde(untagged)]
/// See <https://discord.com/developers/docs/resources/auto-moderation#auto-moderation-action-object-action-metadata> /// See <https://discord.com/developers/docs/resources/auto-moderation#auto-moderation-action-object-action-metadata>
pub enum AutoModerationActionMetadata { pub enum AutoModerationActionMetadata {
@ -127,19 +133,19 @@ pub enum AutoModerationActionMetadata {
None, None,
} }
#[derive(Serialize, Deserialize, Debug, Clone, Default)] #[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq, Eq, PartialOrd, Ord)]
/// See <https://discord.com/developers/docs/resources/auto-moderation#auto-moderation-action-object-action-metadata> /// See <https://discord.com/developers/docs/resources/auto-moderation#auto-moderation-action-object-action-metadata>
pub struct AutoModerationActionMetadataForBlockMessage { pub struct AutoModerationActionMetadataForBlockMessage {
pub custom_message: Option<String>, pub custom_message: Option<String>,
} }
#[derive(Serialize, Deserialize, Debug, Clone, Default)] #[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq, Eq, PartialOrd, Ord, Copy)]
/// See <https://discord.com/developers/docs/resources/auto-moderation#auto-moderation-action-object-action-metadata> /// See <https://discord.com/developers/docs/resources/auto-moderation#auto-moderation-action-object-action-metadata>
pub struct AutoModerationActionMetadataForSendAlertMessage { pub struct AutoModerationActionMetadataForSendAlertMessage {
pub channel_id: Snowflake, pub channel_id: Snowflake,
} }
#[derive(Serialize, Deserialize, Debug, Clone, Default)] #[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq, Eq, PartialOrd, Ord, Copy)]
/// See <https://discord.com/developers/docs/resources/auto-moderation#auto-moderation-action-object-action-metadata> /// See <https://discord.com/developers/docs/resources/auto-moderation#auto-moderation-action-object-action-metadata>
pub struct AutoModerationActionMetadataForTimeout { pub struct AutoModerationActionMetadataForTimeout {
/// Max 2419200 /// Max 2419200

View File

@ -3,15 +3,15 @@
// file, You can obtain one at http://mozilla.org/MPL/2.0/. // file, You can obtain one at http://mozilla.org/MPL/2.0/.
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Deserializer, Serialize};
use serde_aux::prelude::deserialize_string_from_number;
use serde_repr::{Deserialize_repr, Serialize_repr}; use serde_repr::{Deserialize_repr, Serialize_repr};
use std::fmt::Debug; use std::fmt::{Debug, Formatter};
use std::str::FromStr;
use crate::gateway::Shared;
use crate::types::{ use crate::types::{
entities::{GuildMember, User}, entities::{GuildMember, User},
utils::Snowflake, utils::Snowflake,
PermissionFlags, Shared,
}; };
#[cfg(feature = "client")] #[cfg(feature = "client")]
@ -25,6 +25,12 @@ use crate::gateway::Updateable;
#[cfg(feature = "client")] #[cfg(feature = "client")]
use chorus_macros::{observe_option_vec, Composite, Updateable}; use chorus_macros::{observe_option_vec, Composite, Updateable};
use serde::de::{Error, Visitor};
#[cfg(feature = "sqlx")]
use sqlx::types::Json;
use super::{option_arc_rwlock_ptr_eq, option_vec_arc_rwlock_ptr_eq};
#[derive(Default, Debug, Serialize, Deserialize, Clone)] #[derive(Default, Debug, Serialize, Deserialize, Clone)]
#[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))] #[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))]
@ -64,7 +70,9 @@ pub struct Channel {
pub managed: Option<bool>, pub managed: Option<bool>,
#[cfg_attr(feature = "sqlx", sqlx(skip))] #[cfg_attr(feature = "sqlx", sqlx(skip))]
pub member: Option<ThreadMember>, pub member: Option<ThreadMember>,
#[cfg_attr(feature = "sqlx", sqlx(skip))]
pub member_count: Option<i32>, pub member_count: Option<i32>,
#[cfg_attr(feature = "sqlx", sqlx(skip))]
pub message_count: Option<i32>, pub message_count: Option<i32>,
pub name: Option<String>, pub name: Option<String>,
pub nsfw: Option<bool>, pub nsfw: Option<bool>,
@ -75,6 +83,7 @@ pub struct Channel {
#[cfg(not(feature = "sqlx"))] #[cfg(not(feature = "sqlx"))]
#[cfg_attr(feature = "client", observe_option_vec)] #[cfg_attr(feature = "client", observe_option_vec)]
pub permission_overwrites: Option<Vec<Shared<PermissionOverwrite>>>, pub permission_overwrites: Option<Vec<Shared<PermissionOverwrite>>>,
#[cfg_attr(feature = "sqlx", sqlx(skip))]
pub permissions: Option<String>, pub permissions: Option<String>,
pub position: Option<i32>, pub position: Option<i32>,
pub rate_limit_per_user: Option<i32>, pub rate_limit_per_user: Option<i32>,
@ -85,19 +94,28 @@ pub struct Channel {
#[cfg_attr(feature = "sqlx", sqlx(skip))] #[cfg_attr(feature = "sqlx", sqlx(skip))]
pub thread_metadata: Option<ThreadMetadata>, pub thread_metadata: Option<ThreadMetadata>,
pub topic: Option<String>, pub topic: Option<String>,
#[cfg_attr(feature = "sqlx", sqlx(skip))]
pub total_message_sent: Option<i32>, pub total_message_sent: Option<i32>,
pub user_limit: Option<i32>, pub user_limit: Option<i32>,
pub video_quality_mode: Option<i32>, pub video_quality_mode: Option<i32>,
} }
#[cfg(not(tarpaulin_include))]
#[allow(clippy::nonminimal_bool)]
impl PartialEq for Channel { impl PartialEq for Channel {
fn eq(&self, other: &Self) -> bool { fn eq(&self, other: &Self) -> bool {
self.application_id == other.application_id self.application_id == other.application_id
&& self.applied_tags == other.applied_tags
&& self.applied_tags == other.applied_tags
&& self.available_tags == other.available_tags
&& self.available_tags == other.available_tags
&& self.bitrate == other.bitrate && self.bitrate == other.bitrate
&& self.channel_type == other.channel_type && self.channel_type == other.channel_type
&& self.created_at == other.created_at && self.created_at == other.created_at
&& self.default_auto_archive_duration == other.default_auto_archive_duration && self.default_auto_archive_duration == other.default_auto_archive_duration
&& self.default_forum_layout == other.default_forum_layout && self.default_forum_layout == other.default_forum_layout
&& self.default_reaction_emoji == other.default_reaction_emoji
&& self.default_reaction_emoji == other.default_reaction_emoji
&& self.default_sort_order == other.default_sort_order && self.default_sort_order == other.default_sort_order
&& self.default_thread_rate_limit_per_user == other.default_thread_rate_limit_per_user && self.default_thread_rate_limit_per_user == other.default_thread_rate_limit_per_user
&& self.flags == other.flags && self.flags == other.flags
@ -107,16 +125,23 @@ impl PartialEq for Channel {
&& self.last_message_id == other.last_message_id && self.last_message_id == other.last_message_id
&& self.last_pin_timestamp == other.last_pin_timestamp && self.last_pin_timestamp == other.last_pin_timestamp
&& self.managed == other.managed && self.managed == other.managed
&& self.member == other.member
&& self.member_count == other.member_count && self.member_count == other.member_count
&& self.message_count == other.message_count && self.message_count == other.message_count
&& self.name == other.name && self.name == other.name
&& self.nsfw == other.nsfw && self.nsfw == other.nsfw
&& self.owner_id == other.owner_id && self.owner_id == other.owner_id
&& self.parent_id == other.parent_id && self.parent_id == other.parent_id
&& compare_permission_overwrites(
&self.permission_overwrites,
&other.permission_overwrites,
)
&& self.permissions == other.permissions && self.permissions == other.permissions
&& self.position == other.position && self.position == other.position
&& self.rate_limit_per_user == other.rate_limit_per_user && self.rate_limit_per_user == other.rate_limit_per_user
&& option_vec_arc_rwlock_ptr_eq(&self.recipients, &other.recipients)
&& self.rtc_region == other.rtc_region && self.rtc_region == other.rtc_region
&& self.thread_metadata == other.thread_metadata
&& self.topic == other.topic && self.topic == other.topic
&& self.total_message_sent == other.total_message_sent && self.total_message_sent == other.total_message_sent
&& self.user_limit == other.user_limit && self.user_limit == other.user_limit
@ -124,7 +149,29 @@ impl PartialEq for Channel {
} }
} }
#[derive(Debug, Deserialize, Serialize, Clone)] #[cfg(not(tarpaulin_include))]
#[cfg(feature = "sqlx")]
fn compare_permission_overwrites(
a: &Option<Json<Vec<PermissionOverwrite>>>,
b: &Option<Json<Vec<PermissionOverwrite>>>,
) -> bool {
match (a, b) {
(Some(a), Some(b)) => a.encode_to_string() == b.encode_to_string(),
(None, None) => true,
_ => false,
}
}
#[cfg(not(tarpaulin_include))]
#[cfg(not(feature = "sqlx"))]
fn compare_permission_overwrites(
a: &Option<Vec<Shared<PermissionOverwrite>>>,
b: &Option<Vec<Shared<PermissionOverwrite>>>,
) -> bool {
option_vec_arc_rwlock_ptr_eq(a, b)
}
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
/// A tag that can be applied to a thread in a [ChannelType::GuildForum] or [ChannelType::GuildMedia] channel. /// A tag that can be applied to a thread in a [ChannelType::GuildForum] or [ChannelType::GuildMedia] channel.
/// ///
/// # Reference /// # Reference
@ -144,26 +191,104 @@ pub struct Tag {
pub struct PermissionOverwrite { pub struct PermissionOverwrite {
pub id: Snowflake, pub id: Snowflake,
#[serde(rename = "type")] #[serde(rename = "type")]
#[serde(deserialize_with = "deserialize_string_from_number")] pub overwrite_type: PermissionOverwriteType,
pub overwrite_type: String,
#[serde(default)] #[serde(default)]
#[serde(deserialize_with = "deserialize_string_from_number")] pub allow: PermissionFlags,
pub allow: String,
#[serde(default)] #[serde(default)]
#[serde(deserialize_with = "deserialize_string_from_number")] pub deny: PermissionFlags,
pub deny: String,
} }
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)] #[derive(Debug, Serialize_repr, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Copy)]
#[repr(u8)]
/// # Reference
///
/// See <https://docs.discord.sex/resources/channel#permission-overwrite-type>
pub enum PermissionOverwriteType {
Role = 0,
Member = 1,
}
impl From<u8> for PermissionOverwriteType {
fn from(v: u8) -> Self {
match v {
0 => PermissionOverwriteType::Role,
1 => PermissionOverwriteType::Member,
_ => unreachable!(),
}
}
}
impl FromStr for PermissionOverwriteType {
type Err = serde::de::value::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"role" => Ok(PermissionOverwriteType::Role),
"member" => Ok(PermissionOverwriteType::Member),
_ => Err(Self::Err::custom("invalid permission overwrite type")),
}
}
}
struct PermissionOverwriteTypeVisitor;
impl<'de> Visitor<'de> for PermissionOverwriteTypeVisitor {
type Value = PermissionOverwriteType;
fn expecting(&self, formatter: &mut Formatter) -> std::fmt::Result {
formatter.write_str("a valid permission overwrite type")
}
fn visit_u8<E>(self, v: u8) -> Result<Self::Value, E>
where
E: Error,
{
Ok(PermissionOverwriteType::from(v))
}
fn visit_u64<E>(self, v: u64) -> Result<Self::Value, E>
where
E: Error,
{
self.visit_u8(v as u8)
}
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: Error,
{
PermissionOverwriteType::from_str(v).map_err(E::custom)
}
fn visit_string<E>(self, v: String) -> Result<Self::Value, E>
where
E: Error,
{
self.visit_str(v.as_str())
}
}
impl<'de> Deserialize<'de> for PermissionOverwriteType {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let val = deserializer.deserialize_any(PermissionOverwriteTypeVisitor)?;
Ok(val)
}
}
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Copy)]
/// # Reference /// # Reference
/// See <https://discord-userdoccers.vercel.app/resources/channel#thread-metadata-object> /// See <https://discord-userdoccers.vercel.app/resources/channel#thread-metadata-object>
pub struct ThreadMetadata { pub struct ThreadMetadata {
pub archived: bool, pub archived: bool,
pub auto_archive_duration: i32, pub auto_archive_duration: i32,
pub archive_timestamp: String, pub archive_timestamp: DateTime<Utc>,
pub locked: bool, pub locked: bool,
pub invitable: Option<bool>, pub invitable: Option<bool>,
pub create_timestamp: Option<String>, pub create_timestamp: Option<DateTime<Utc>>,
} }
#[derive(Default, Debug, Deserialize, Serialize, Clone)] #[derive(Default, Debug, Deserialize, Serialize, Clone)]
@ -172,12 +297,23 @@ pub struct ThreadMetadata {
pub struct ThreadMember { pub struct ThreadMember {
pub id: Option<Snowflake>, pub id: Option<Snowflake>,
pub user_id: Option<Snowflake>, pub user_id: Option<Snowflake>,
pub join_timestamp: Option<String>, pub join_timestamp: Option<DateTime<Utc>>,
pub flags: Option<u64>, pub flags: Option<u64>,
pub member: Option<Shared<GuildMember>>, pub member: Option<Shared<GuildMember>>,
} }
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)] #[cfg(not(tarpaulin_include))]
impl PartialEq for ThreadMember {
fn eq(&self, other: &Self) -> bool {
self.id == other.id
&& self.user_id == other.user_id
&& self.join_timestamp == other.join_timestamp
&& self.flags == other.flags
&& option_arc_rwlock_ptr_eq(&self.member, &other.member)
}
}
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq, PartialOrd)]
/// Specifies the emoji to use as the default way to react to a [ChannelType::GuildForum] or [ChannelType::GuildMedia] channel post. /// Specifies the emoji to use as the default way to react to a [ChannelType::GuildForum] or [ChannelType::GuildMedia] channel post.
/// ///
/// # Reference /// # Reference
@ -256,3 +392,11 @@ pub enum ChannelType {
// TODO: Couldn't find reference // TODO: Couldn't find reference
Unhandled = 255, Unhandled = 255,
} }
/// # Reference
/// See <https://docs.discord.sex/resources/message#followed-channel-object>
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq, Copy, Hash, PartialOrd, Ord)]
pub struct FollowedChannel {
pub channel_id: Snowflake,
pub webhook_id: Snowflake,
}

View File

@ -6,9 +6,9 @@ use std::fmt::Debug;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::gateway::Shared;
use crate::types::entities::User; use crate::types::entities::User;
use crate::types::Snowflake; use crate::types::Snowflake;
use crate::types::{PartialEmoji, Shared};
#[cfg(feature = "client")] #[cfg(feature = "client")]
use crate::gateway::GatewayHandle; use crate::gateway::GatewayHandle;
@ -22,6 +22,8 @@ use crate::gateway::Updateable;
#[cfg(feature = "client")] #[cfg(feature = "client")]
use chorus_macros::{Composite, Updateable}; use chorus_macros::{Composite, Updateable};
use super::option_arc_rwlock_ptr_eq;
#[derive(Debug, Clone, Deserialize, Serialize, Default)] #[derive(Debug, Clone, Deserialize, Serialize, Default)]
#[cfg_attr(feature = "client", derive(Updateable, Composite))] #[cfg_attr(feature = "client", derive(Updateable, Composite))]
#[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))] #[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))]
@ -42,27 +44,33 @@ pub struct Emoji {
pub available: Option<bool>, pub available: Option<bool>,
} }
impl std::hash::Hash for Emoji { #[cfg(not(tarpaulin_include))]
fn hash<H: std::hash::Hasher>(&self, state: &mut H) { #[allow(clippy::nonminimal_bool)]
self.id.hash(state); impl PartialEq for Emoji {
self.name.hash(state); fn eq(&self, other: &Self) -> bool {
self.roles.hash(state); self.id == other.id
self.roles.hash(state); && self.name == other.name
self.require_colons.hash(state); && self.roles == other.roles
self.managed.hash(state); && self.roles == other.roles
self.animated.hash(state); && option_arc_rwlock_ptr_eq(&self.user, &other.user)
self.available.hash(state); && self.require_colons == other.require_colons
&& self.managed == other.managed
&& self.animated == other.animated
&& self.available == other.available
} }
} }
impl PartialEq for Emoji { impl From<PartialEmoji> for Emoji {
fn eq(&self, other: &Self) -> bool { fn from(value: PartialEmoji) -> Self {
!(self.id != other.id Self {
|| self.name != other.name id: value.id.unwrap_or_default(), // TODO: Make this method an impl to TryFrom<> instead
|| self.roles != other.roles name: Some(value.name),
|| self.require_colons != other.require_colons roles: None,
|| self.managed != other.managed user: None,
|| self.animated != other.animated require_colons: Some(value.animated),
|| self.available != other.available) managed: None,
animated: Some(value.animated),
available: None,
}
} }
} }

View File

@ -3,27 +3,28 @@
// file, You can obtain one at http://mozilla.org/MPL/2.0/. // file, You can obtain one at http://mozilla.org/MPL/2.0/.
use std::fmt::Debug; use std::fmt::Debug;
use std::hash::Hash;
use bitflags::bitflags; use bitflags::bitflags;
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_repr::{Deserialize_repr, Serialize_repr}; use serde_repr::{Deserialize_repr, Serialize_repr};
use crate::gateway::Shared;
use crate::types::types::guild_configuration::GuildFeaturesList; use crate::types::types::guild_configuration::GuildFeaturesList;
use crate::types::Shared;
use crate::types::{ use crate::types::{
entities::{Channel, Emoji, RoleObject, Sticker, User, VoiceState, Webhook}, entities::{Channel, Emoji, RoleObject, Sticker, User, VoiceState, Webhook},
interfaces::WelcomeScreenObject, interfaces::WelcomeScreenObject,
utils::Snowflake, utils::Snowflake,
}; };
use super::PublicUser; use super::{option_arc_rwlock_ptr_eq, vec_arc_rwlock_ptr_eq, PublicUser};
#[cfg(feature = "client")] #[cfg(feature = "client")]
use crate::gateway::Updateable; use crate::gateway::Updateable;
#[cfg(feature = "client")] #[cfg(feature = "client")]
use chorus_macros::{observe_option_vec, observe_vec, Composite, Updateable}; use chorus_macros::{observe_vec, Composite, Updateable};
#[cfg(feature = "client")] #[cfg(feature = "client")]
use crate::types::Composite; use crate::types::Composite;
@ -46,10 +47,12 @@ pub struct Guild {
pub approximate_presence_count: Option<i32>, pub approximate_presence_count: Option<i32>,
pub banner: Option<String>, pub banner: Option<String>,
#[cfg_attr(feature = "sqlx", sqlx(skip))] #[cfg_attr(feature = "sqlx", sqlx(skip))]
pub bans: Option<Vec<GuildBan>>, #[serde(default)]
pub bans: Vec<GuildBan>,
#[cfg_attr(feature = "sqlx", sqlx(skip))] #[cfg_attr(feature = "sqlx", sqlx(skip))]
#[cfg_attr(feature = "client", observe_option_vec)] #[cfg_attr(feature = "client", observe_vec)]
pub channels: Option<Vec<Shared<Channel>>>, #[serde(default)]
pub channels: Vec<Shared<Channel>>,
pub default_message_notifications: Option<MessageNotificationLevel>, pub default_message_notifications: Option<MessageNotificationLevel>,
pub description: Option<String>, pub description: Option<String>,
pub discovery_splash: Option<String>, pub discovery_splash: Option<String>,
@ -57,17 +60,19 @@ pub struct Guild {
#[cfg_attr(feature = "client", observe_vec)] #[cfg_attr(feature = "client", observe_vec)]
#[serde(default)] #[serde(default)]
pub emojis: Vec<Shared<Emoji>>, pub emojis: Vec<Shared<Emoji>>,
pub explicit_content_filter: Option<i32>, pub explicit_content_filter: Option<ExplicitContentFilterLevel>,
//#[cfg_attr(feature = "sqlx", sqlx(try_from = "String"))] //#[cfg_attr(feature = "sqlx", sqlx(try_from = "String"))]
pub features: Option<GuildFeaturesList>, #[serde(default)]
pub features: GuildFeaturesList,
pub icon: Option<String>, pub icon: Option<String>,
#[cfg_attr(feature = "sqlx", sqlx(skip))] #[cfg_attr(feature = "sqlx", sqlx(skip))]
pub icon_hash: Option<String>, pub icon_hash: Option<String>,
pub id: Snowflake, pub id: Snowflake,
#[cfg_attr(feature = "sqlx", sqlx(skip))] #[cfg_attr(feature = "sqlx", sqlx(skip))]
pub invites: Option<Vec<GuildInvite>>, #[serde(default)]
pub invites: Vec<GuildInvite>,
#[cfg_attr(feature = "sqlx", sqlx(skip))] #[cfg_attr(feature = "sqlx", sqlx(skip))]
pub joined_at: Option<String>, pub joined_at: Option<DateTime<Utc>>,
pub large: Option<bool>, pub large: Option<bool>,
pub max_members: Option<i32>, pub max_members: Option<i32>,
pub max_presences: Option<i32>, pub max_presences: Option<i32>,
@ -91,86 +96,39 @@ pub struct Guild {
pub public_updates_channel_id: Option<Snowflake>, pub public_updates_channel_id: Option<Snowflake>,
pub region: Option<String>, pub region: Option<String>,
#[cfg_attr(feature = "sqlx", sqlx(skip))] #[cfg_attr(feature = "sqlx", sqlx(skip))]
#[cfg_attr(feature = "client", observe_option_vec)] #[cfg_attr(feature = "client", observe_vec)]
pub roles: Option<Vec<Shared<RoleObject>>>, #[serde(default)]
pub roles: Vec<Shared<RoleObject>>,
#[cfg_attr(feature = "sqlx", sqlx(skip))] #[cfg_attr(feature = "sqlx", sqlx(skip))]
pub rules_channel: Option<String>, pub rules_channel: Option<String>,
pub rules_channel_id: Option<Snowflake>, pub rules_channel_id: Option<Snowflake>,
pub splash: Option<String>, pub splash: Option<String>,
#[cfg_attr(feature = "sqlx", sqlx(skip))] #[cfg_attr(feature = "sqlx", sqlx(skip))]
pub stickers: Option<Vec<Sticker>>, #[serde(default)]
pub system_channel_flags: Option<u64>, pub stickers: Vec<Sticker>,
pub system_channel_flags: Option<SystemChannelFlags>,
pub system_channel_id: Option<Snowflake>, pub system_channel_id: Option<Snowflake>,
#[cfg_attr(feature = "sqlx", sqlx(skip))] #[cfg_attr(feature = "sqlx", sqlx(skip))]
pub vanity_url_code: Option<String>, pub vanity_url_code: Option<String>,
pub verification_level: Option<VerificationLevel>, pub verification_level: Option<VerificationLevel>,
#[serde(default)]
#[cfg_attr(feature = "sqlx", sqlx(skip))] #[cfg_attr(feature = "sqlx", sqlx(skip))]
#[cfg_attr(feature = "client", observe_option_vec)] #[cfg_attr(feature = "client", observe_vec)]
pub voice_states: Option<Vec<Shared<VoiceState>>>, pub voice_states: Vec<Shared<VoiceState>>,
#[serde(default)]
#[cfg_attr(feature = "sqlx", sqlx(skip))] #[cfg_attr(feature = "sqlx", sqlx(skip))]
#[cfg_attr(feature = "client", observe_option_vec)] #[cfg_attr(feature = "client", observe_vec)]
pub webhooks: Option<Vec<Shared<Webhook>>>, pub webhooks: Vec<Shared<Webhook>>,
#[cfg(feature = "sqlx")] #[cfg(feature = "sqlx")]
pub welcome_screen: Option<sqlx::types::Json<WelcomeScreenObject>>, pub welcome_screen: sqlx::types::Json<Option<WelcomeScreenObject>>,
#[cfg(not(feature = "sqlx"))] #[cfg(not(feature = "sqlx"))]
pub welcome_screen: Option<WelcomeScreenObject>, pub welcome_screen: Option<WelcomeScreenObject>,
pub widget_channel_id: Option<Snowflake>, pub widget_channel_id: Option<Snowflake>,
pub widget_enabled: Option<bool>, pub widget_enabled: Option<bool>,
} }
impl std::hash::Hash for Guild { #[cfg(not(tarpaulin_include))]
fn hash<H: std::hash::Hasher>(&self, state: &mut H) { impl PartialEq for Guild {
self.afk_channel_id.hash(state);
self.afk_timeout.hash(state);
self.application_id.hash(state);
self.approximate_member_count.hash(state);
self.approximate_presence_count.hash(state);
self.banner.hash(state);
self.bans.hash(state);
self.default_message_notifications.hash(state);
self.description.hash(state);
self.discovery_splash.hash(state);
self.explicit_content_filter.hash(state);
self.features.hash(state);
self.icon.hash(state);
self.icon_hash.hash(state);
self.id.hash(state);
self.invites.hash(state);
self.joined_at.hash(state);
self.large.hash(state);
self.max_members.hash(state);
self.max_presences.hash(state);
self.max_stage_video_channel_users.hash(state);
self.max_video_channel_users.hash(state);
self.mfa_level.hash(state);
self.name.hash(state);
self.nsfw_level.hash(state);
self.owner.hash(state);
self.owner_id.hash(state);
self.permissions.hash(state);
self.preferred_locale.hash(state);
self.premium_progress_bar_enabled.hash(state);
self.premium_subscription_count.hash(state);
self.premium_tier.hash(state);
self.primary_category_id.hash(state);
self.public_updates_channel_id.hash(state);
self.region.hash(state);
self.rules_channel.hash(state);
self.rules_channel_id.hash(state);
self.splash.hash(state);
self.stickers.hash(state);
self.system_channel_flags.hash(state);
self.system_channel_id.hash(state);
self.vanity_url_code.hash(state);
self.verification_level.hash(state);
self.welcome_screen.hash(state);
self.welcome_screen.hash(state);
self.widget_channel_id.hash(state);
self.widget_enabled.hash(state);
}
}
impl std::cmp::PartialEq for Guild {
fn eq(&self, other: &Self) -> bool { fn eq(&self, other: &Self) -> bool {
self.afk_channel_id == other.afk_channel_id self.afk_channel_id == other.afk_channel_id
&& self.afk_timeout == other.afk_timeout && self.afk_timeout == other.afk_timeout
@ -179,14 +137,17 @@ impl std::cmp::PartialEq for Guild {
&& self.approximate_presence_count == other.approximate_presence_count && self.approximate_presence_count == other.approximate_presence_count
&& self.banner == other.banner && self.banner == other.banner
&& self.bans == other.bans && self.bans == other.bans
&& vec_arc_rwlock_ptr_eq(&self.channels, &other.channels)
&& self.default_message_notifications == other.default_message_notifications && self.default_message_notifications == other.default_message_notifications
&& self.description == other.description && self.description == other.description
&& self.discovery_splash == other.discovery_splash && self.discovery_splash == other.discovery_splash
&& vec_arc_rwlock_ptr_eq(&self.emojis, &other.emojis)
&& self.explicit_content_filter == other.explicit_content_filter && self.explicit_content_filter == other.explicit_content_filter
&& self.features == other.features && self.features == other.features
&& self.icon == other.icon && self.icon == other.icon
&& self.icon_hash == other.icon_hash && self.icon_hash == other.icon_hash
&& self.id == other.id && self.id == other.id
&& self.invites == other.invites
&& self.joined_at == other.joined_at && self.joined_at == other.joined_at
&& self.large == other.large && self.large == other.large
&& self.max_members == other.max_members && self.max_members == other.max_members
@ -206,6 +167,7 @@ impl std::cmp::PartialEq for Guild {
&& self.primary_category_id == other.primary_category_id && self.primary_category_id == other.primary_category_id
&& self.public_updates_channel_id == other.public_updates_channel_id && self.public_updates_channel_id == other.public_updates_channel_id
&& self.region == other.region && self.region == other.region
&& vec_arc_rwlock_ptr_eq(&self.roles, &other.roles)
&& self.rules_channel == other.rules_channel && self.rules_channel == other.rules_channel
&& self.rules_channel_id == other.rules_channel_id && self.rules_channel_id == other.rules_channel_id
&& self.splash == other.splash && self.splash == other.splash
@ -214,6 +176,8 @@ impl std::cmp::PartialEq for Guild {
&& self.system_channel_id == other.system_channel_id && self.system_channel_id == other.system_channel_id
&& self.vanity_url_code == other.vanity_url_code && self.vanity_url_code == other.vanity_url_code
&& self.verification_level == other.verification_level && self.verification_level == other.verification_level
&& vec_arc_rwlock_ptr_eq(&self.voice_states, &other.voice_states)
&& vec_arc_rwlock_ptr_eq(&self.webhooks, &other.webhooks)
&& self.welcome_screen == other.welcome_screen && self.welcome_screen == other.welcome_screen
&& self.welcome_screen == other.welcome_screen && self.welcome_screen == other.welcome_screen
&& self.widget_channel_id == other.widget_channel_id && self.widget_channel_id == other.widget_channel_id
@ -225,6 +189,7 @@ impl std::cmp::PartialEq for Guild {
#[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq, Eq, Hash)] #[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))] #[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))]
pub struct GuildBan { pub struct GuildBan {
#[cfg_attr(feature = "sqlx", sqlx(skip))]
pub user: PublicUser, pub user: PublicUser,
pub reason: Option<String>, pub reason: Option<String>,
} }
@ -252,32 +217,36 @@ pub struct GuildInvite {
pub vanity_url: Option<bool>, pub vanity_url: Option<bool>,
} }
impl std::hash::Hash for GuildInvite { #[cfg(not(tarpaulin_include))]
fn hash<H: std::hash::Hasher>(&self, state: &mut H) { impl PartialEq for GuildInvite {
self.code.hash(state); fn eq(&self, other: &Self) -> bool {
self.temporary.hash(state); self.code == other.code
self.uses.hash(state); && self.temporary == other.temporary
self.max_uses.hash(state); && self.uses == other.uses
self.max_age.hash(state); && self.max_uses == other.max_uses
self.created_at.hash(state); && self.max_age == other.max_age
self.expires_at.hash(state); && self.created_at == other.created_at
self.guild_id.hash(state); && self.expires_at == other.expires_at
self.channel_id.hash(state); && self.guild_id == other.guild_id
self.inviter_id.hash(state); && option_arc_rwlock_ptr_eq(&self.guild, &other.guild)
self.target_user_id.hash(state); && self.channel_id == other.channel_id
self.target_user.hash(state); && option_arc_rwlock_ptr_eq(&self.channel, &other.channel)
self.target_user_type.hash(state); && self.inviter_id == other.inviter_id
self.vanity_url.hash(state); && option_arc_rwlock_ptr_eq(&self.inviter, &other.inviter)
&& self.target_user_id == other.target_user_id
&& self.target_user == other.target_user
&& self.target_user_type == other.target_user_type
&& self.vanity_url == other.vanity_url
} }
} }
#[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq, Hash)] #[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq, Hash, Eq, PartialOrd, Ord, Copy)]
pub struct UnavailableGuild { pub struct UnavailableGuild {
pub id: Snowflake, pub id: Snowflake,
pub unavailable: bool, pub unavailable: bool,
} }
#[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq)] #[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Copy)]
pub struct GuildCreateResponse { pub struct GuildCreateResponse {
pub id: Snowflake, pub id: Snowflake,
} }
@ -303,7 +272,29 @@ pub struct GuildScheduledEvent {
pub image: Option<String>, pub image: Option<String>,
} }
#[derive(Serialize_repr, Deserialize_repr, Debug, Default, Clone)] #[cfg(not(tarpaulin_include))]
impl PartialEq for GuildScheduledEvent {
fn eq(&self, other: &Self) -> bool {
self.id == other.id
&& self.guild_id == other.guild_id
&& self.channel_id == other.channel_id
&& self.creator_id == other.creator_id
&& self.name == other.name
&& self.description == other.description
&& self.scheduled_start_time == other.scheduled_start_time
&& self.scheduled_end_time == other.scheduled_end_time
&& self.privacy_level == other.privacy_level
&& self.status == other.status
&& self.entity_type == other.entity_type
&& self.entity_id == other.entity_id
&& self.entity_metadata == other.entity_metadata
&& option_arc_rwlock_ptr_eq(&self.creator, &other.creator)
&& self.user_count == other.user_count
&& self.image == other.image
}
}
#[derive(Serialize_repr, Deserialize_repr, Debug, Default, Clone, PartialEq, Copy)]
#[repr(u8)] #[repr(u8)]
/// See <https://discord.com/developers/docs/resources/guild-scheduled-event#guild-scheduled-event-object-guild-scheduled-event-privacy-level> /// See <https://discord.com/developers/docs/resources/guild-scheduled-event#guild-scheduled-event-object-guild-scheduled-event-privacy-level>
pub enum GuildScheduledEventPrivacyLevel { pub enum GuildScheduledEventPrivacyLevel {
@ -311,7 +302,7 @@ pub enum GuildScheduledEventPrivacyLevel {
GuildOnly = 2, GuildOnly = 2,
} }
#[derive(Serialize_repr, Deserialize_repr, Debug, Default, Clone)] #[derive(Serialize_repr, Deserialize_repr, Debug, Default, Clone, PartialEq, Copy)]
#[repr(u8)] #[repr(u8)]
/// See <https://discord.com/developers/docs/resources/guild-scheduled-event#guild-scheduled-event-object-guild-scheduled-event-status> /// See <https://discord.com/developers/docs/resources/guild-scheduled-event#guild-scheduled-event-object-guild-scheduled-event-status>
pub enum GuildScheduledEventStatus { pub enum GuildScheduledEventStatus {
@ -322,7 +313,19 @@ pub enum GuildScheduledEventStatus {
Canceled = 4, Canceled = 4,
} }
#[derive(Serialize_repr, Deserialize_repr, Debug, Default, Clone)] #[derive(
Serialize_repr,
Deserialize_repr,
Debug,
Default,
Clone,
PartialEq,
Eq,
PartialOrd,
Ord,
Copy,
Hash,
)]
#[repr(u8)] #[repr(u8)]
/// See <https://discord.com/developers/docs/resources/guild-scheduled-event#guild-scheduled-event-object-guild-scheduled-event-entity-types> /// See <https://discord.com/developers/docs/resources/guild-scheduled-event#guild-scheduled-event-object-guild-scheduled-event-entity-types>
pub enum GuildScheduledEventEntityType { pub enum GuildScheduledEventEntityType {
@ -332,7 +335,7 @@ pub enum GuildScheduledEventEntityType {
External = 3, External = 3,
} }
#[derive(Serialize, Deserialize, Debug, Default, Clone)] #[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
/// See <https://discord.com/developers/docs/resources/guild-scheduled-event#guild-scheduled-event-object-guild-scheduled-event-entity-metadata> /// See <https://discord.com/developers/docs/resources/guild-scheduled-event#guild-scheduled-event-object-guild-scheduled-event-entity-metadata>
pub struct GuildScheduledEventEntityMetadata { pub struct GuildScheduledEventEntityMetadata {
pub location: Option<String>, pub location: Option<String>,
@ -347,7 +350,19 @@ pub struct VoiceRegion {
custom: bool, custom: bool,
} }
#[derive(Serialize_repr, Deserialize_repr, Debug, Default, Clone, Eq, PartialEq, Hash, Copy)] #[derive(
Serialize_repr,
Deserialize_repr,
Debug,
Default,
Clone,
Eq,
PartialEq,
Hash,
Copy,
PartialOrd,
Ord,
)]
#[cfg_attr(feature = "sqlx", derive(sqlx::Type))] #[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
#[repr(u8)] #[repr(u8)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")] #[serde(rename_all = "SCREAMING_SNAKE_CASE")]
@ -358,7 +373,19 @@ pub enum MessageNotificationLevel {
OnlyMentions = 1, OnlyMentions = 1,
} }
#[derive(Serialize_repr, Deserialize_repr, Debug, Default, Clone, Eq, PartialEq, Hash, Copy)] #[derive(
Serialize_repr,
Deserialize_repr,
Debug,
Default,
Clone,
Eq,
PartialEq,
Hash,
Copy,
PartialOrd,
Ord,
)]
#[cfg_attr(feature = "sqlx", derive(sqlx::Type))] #[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
#[repr(u8)] #[repr(u8)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")] #[serde(rename_all = "SCREAMING_SNAKE_CASE")]
@ -370,7 +397,19 @@ pub enum ExplicitContentFilterLevel {
AllMembers = 2, AllMembers = 2,
} }
#[derive(Serialize_repr, Deserialize_repr, Debug, Default, Clone, Eq, PartialEq, Hash, Copy)] #[derive(
Serialize_repr,
Deserialize_repr,
Debug,
Default,
Clone,
Eq,
PartialEq,
Hash,
Copy,
PartialOrd,
Ord,
)]
#[cfg_attr(feature = "sqlx", derive(sqlx::Type))] #[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
#[repr(u8)] #[repr(u8)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")] #[serde(rename_all = "SCREAMING_SNAKE_CASE")]
@ -384,7 +423,19 @@ pub enum VerificationLevel {
VeryHigh = 4, VeryHigh = 4,
} }
#[derive(Serialize_repr, Deserialize_repr, Debug, Default, Clone, Eq, PartialEq, Hash, Copy)] #[derive(
Serialize_repr,
Deserialize_repr,
Debug,
Default,
Clone,
Eq,
PartialEq,
Hash,
Copy,
PartialOrd,
Ord,
)]
#[cfg_attr(feature = "sqlx", derive(sqlx::Type))] #[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
#[repr(u8)] #[repr(u8)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")] #[serde(rename_all = "SCREAMING_SNAKE_CASE")]
@ -395,7 +446,19 @@ pub enum MFALevel {
Elevated = 1, Elevated = 1,
} }
#[derive(Serialize_repr, Deserialize_repr, Debug, Default, Clone, Eq, PartialEq, Hash, Copy)] #[derive(
Serialize_repr,
Deserialize_repr,
Debug,
Default,
Clone,
Eq,
PartialEq,
Hash,
Copy,
PartialOrd,
Ord,
)]
#[cfg_attr(feature = "sqlx", derive(sqlx::Type))] #[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
#[repr(u8)] #[repr(u8)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")] #[serde(rename_all = "SCREAMING_SNAKE_CASE")]
@ -408,7 +471,19 @@ pub enum NSFWLevel {
AgeRestricted = 3, AgeRestricted = 3,
} }
#[derive(Serialize_repr, Deserialize_repr, Debug, Default, Clone, Eq, PartialEq, Hash, Copy)] #[derive(
Serialize_repr,
Deserialize_repr,
Debug,
Default,
Clone,
Eq,
PartialEq,
Hash,
Copy,
PartialOrd,
Ord,
)]
#[cfg_attr(feature = "sqlx", derive(sqlx::Type))] #[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
#[repr(u8)] #[repr(u8)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")] #[serde(rename_all = "SCREAMING_SNAKE_CASE")]
@ -422,7 +497,8 @@ pub enum PremiumTier {
} }
bitflags! { bitflags! {
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, chorus_macros::SerdeBitFlags)]
#[cfg_attr(feature = "sqlx", derive(chorus_macros::SqlxBitFlags))]
/// # Reference /// # Reference
/// See <https://discord-userdoccers.vercel.app/resources/guild#system-channel-flags> /// See <https://discord-userdoccers.vercel.app/resources/guild#system-channel-flags>
pub struct SystemChannelFlags: u64 { pub struct SystemChannelFlags: u64 {

View File

@ -2,27 +2,52 @@
// License, v. 2.0. If a copy of the MPL was not distributed with this // 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/. // file, You can obtain one at http://mozilla.org/MPL/2.0/.
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::gateway::Shared;
use crate::types::{entities::PublicUser, Snowflake}; use crate::types::{entities::PublicUser, Snowflake};
use crate::types::{GuildMemberFlags, PermissionFlags, Shared};
use super::option_arc_rwlock_ptr_eq;
#[derive(Debug, Deserialize, Default, Serialize, Clone)] #[derive(Debug, Deserialize, Default, Serialize, Clone)]
#[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))]
/// Represents a participating user in a guild. /// Represents a participating user in a guild.
/// ///
/// # Reference /// # Reference
/// See <https://discord-userdoccers.vercel.app/resources/guild#guild-member-object> /// See <https://discord-userdoccers.vercel.app/resources/guild#guild-member-object>
pub struct GuildMember { pub struct GuildMember {
#[cfg_attr(feature = "sqlx", sqlx(skip))]
pub user: Option<Shared<PublicUser>>, pub user: Option<Shared<PublicUser>>,
pub nick: Option<String>, pub nick: Option<String>,
pub avatar: Option<String>, pub avatar: Option<String>,
#[cfg_attr(feature = "sqlx", sqlx(skip))]
pub roles: Vec<Snowflake>, pub roles: Vec<Snowflake>,
pub joined_at: String, pub joined_at: DateTime<Utc>,
pub premium_since: Option<String>, pub premium_since: Option<DateTime<Utc>>,
pub deaf: bool, pub deaf: bool,
pub mute: bool, pub mute: bool,
pub flags: Option<i32>, pub flags: Option<GuildMemberFlags>,
pub pending: Option<bool>, pub pending: Option<bool>,
pub permissions: Option<String>, #[serde(default)]
pub communication_disabled_until: Option<String>, pub permissions: PermissionFlags,
pub communication_disabled_until: Option<DateTime<Utc>>,
}
#[cfg(not(tarpaulin_include))]
impl PartialEq for GuildMember {
fn eq(&self, other: &Self) -> bool {
self.nick == other.nick
&& self.avatar == other.avatar
&& self.roles == other.roles
&& self.joined_at == other.joined_at
&& self.premium_since == other.premium_since
&& self.deaf == other.deaf
&& self.mute == other.mute
&& self.flags == other.flags
&& self.pending == other.pending
&& self.permissions == other.permissions
&& self.communication_disabled_until == other.communication_disabled_until
&& option_arc_rwlock_ptr_eq(&self.user, &other.user)
}
} }

View File

@ -5,10 +5,10 @@
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::gateway::Shared;
use crate::types::{ use crate::types::{
entities::{Application, User}, entities::{Application, User},
utils::Snowflake, utils::Snowflake,
Shared,
}; };
#[derive(Default, Debug, Deserialize, Serialize, Clone)] #[derive(Default, Debug, Deserialize, Serialize, Clone)]
@ -18,7 +18,7 @@ pub struct Integration {
pub id: Snowflake, pub id: Snowflake,
pub name: String, pub name: String,
#[serde(rename = "type")] #[serde(rename = "type")]
pub integration_type: String, pub integration_type: IntegrationType,
pub enabled: bool, pub enabled: bool,
pub syncing: Option<bool>, pub syncing: Option<bool>,
pub role_id: Option<String>, pub role_id: Option<String>,
@ -43,3 +43,17 @@ pub struct IntegrationAccount {
pub id: String, pub id: String,
pub name: String, pub name: String,
} }
#[derive(
Debug, Default, Serialize, Deserialize, Clone, PartialEq, Eq, PartialOrd, Ord, Copy, Hash,
)]
#[serde(rename_all = "snake_case")]
#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
#[cfg_attr(feature = "sqlx", sqlx(rename_all = "snake_case"))]
pub enum IntegrationType {
#[default]
Twitch,
Youtube,
Discord,
GuildSubscription,
}

View File

@ -5,37 +5,49 @@
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::gateway::Shared; use crate::types::{Snowflake, WelcomeScreenObject, Shared, InviteFlags, InviteType, InviteTargetType, Guild, VerificationLevel};
use crate::types::{Snowflake, WelcomeScreenObject}; use crate::types::types::guild_configuration::GuildFeaturesList;
use super::guild::GuildScheduledEvent; use super::guild::GuildScheduledEvent;
use super::{Application, Channel, GuildMember, NSFWLevel, User}; use super::{Application, Channel, GuildMember, NSFWLevel, User};
/// Represents a code that when used, adds a user to a guild or group DM channel, or creates a relationship between two users. /// Represents a code that when used, adds a user to a guild or group DM channel, or creates a relationship between two users.
/// See <https://discord-userdoccers.vercel.app/resources/invite#invite-object> /// See <https://discord-userdoccers.vercel.app/resources/invite#invite-object>
#[derive(Debug, Serialize, Deserialize)] #[derive(Default, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))]
pub struct Invite { pub struct Invite {
#[cfg_attr(feature = "sqlx", sqlx(skip))]
pub approximate_member_count: Option<i32>, pub approximate_member_count: Option<i32>,
#[cfg_attr(feature = "sqlx", sqlx(skip))]
pub approximate_presence_count: Option<i32>, pub approximate_presence_count: Option<i32>,
#[cfg_attr(feature = "sqlx", sqlx(skip))]
pub channel: Option<Channel>, pub channel: Option<Channel>,
pub code: String, pub code: String,
pub created_at: Option<DateTime<Utc>>, pub created_at: Option<DateTime<Utc>>,
pub expires_at: Option<DateTime<Utc>>, pub expires_at: Option<DateTime<Utc>>,
pub flags: Option<i32>, pub flags: Option<InviteFlags>,
#[cfg_attr(feature = "sqlx", sqlx(skip))]
pub guild: Option<InviteGuild>, pub guild: Option<InviteGuild>,
pub guild_id: Option<Snowflake>, pub guild_id: Option<Snowflake>,
#[cfg_attr(feature = "sqlx", sqlx(skip))]
pub guild_scheduled_event: Option<Shared<GuildScheduledEvent>>, pub guild_scheduled_event: Option<Shared<GuildScheduledEvent>>,
#[serde(rename = "type")] #[serde(rename = "type")]
pub invite_type: Option<i32>, #[cfg_attr(feature = "sqlx", sqlx(rename = "type"))]
pub invite_type: Option<InviteType>,
#[cfg_attr(feature = "sqlx", sqlx(skip))]
pub inviter: Option<User>, pub inviter: Option<User>,
pub max_age: Option<i32>, pub max_age: Option<u32>,
pub max_uses: Option<i32>, pub max_uses: Option<u8>,
#[cfg_attr(feature = "sqlx", sqlx(skip))]
pub stage_instance: Option<InviteStageInstance>, pub stage_instance: Option<InviteStageInstance>,
#[cfg_attr(feature = "sqlx", sqlx(skip))]
pub target_application: Option<Application>, pub target_application: Option<Application>,
pub target_type: Option<i32>, #[cfg_attr(feature = "sqlx", sqlx(rename = "target_user_type"))]
pub target_type: Option<InviteTargetType>,
#[cfg_attr(feature = "sqlx", sqlx(skip))]
pub target_user: Option<User>, pub target_user: Option<User>,
pub temporary: Option<bool>, pub temporary: Option<bool>,
pub uses: Option<i32>, pub uses: Option<u32>,
} }
/// The guild an invite is for. /// The guild an invite is for.
@ -46,8 +58,8 @@ pub struct InviteGuild {
pub name: String, pub name: String,
pub icon: Option<String>, pub icon: Option<String>,
pub splash: Option<String>, pub splash: Option<String>,
pub verification_level: i32, pub verification_level: VerificationLevel,
pub features: Vec<String>, pub features: GuildFeaturesList,
pub vanity_url_code: Option<String>, pub vanity_url_code: Option<String>,
pub description: Option<String>, pub description: Option<String>,
pub banner: Option<String>, pub banner: Option<String>,
@ -59,6 +71,29 @@ pub struct InviteGuild {
pub welcome_screen: Option<WelcomeScreenObject>, pub welcome_screen: Option<WelcomeScreenObject>,
} }
impl From<Guild> for InviteGuild {
fn from(value: Guild) -> Self {
Self {
id: value.id,
name: value.name.unwrap_or_default(),
icon: value.icon,
splash: value.splash,
verification_level: value.verification_level.unwrap_or_default(),
features: value.features,
vanity_url_code: value.vanity_url_code,
description: value.description,
banner: value.banner,
premium_subscription_count: value.premium_subscription_count,
nsfw_deprecated: None,
nsfw_level: value.nsfw_level.unwrap_or_default(),
#[cfg(feature = "sqlx")]
welcome_screen: value.welcome_screen.0,
#[cfg(not(feature = "sqlx"))]
welcome_screen: value.welcome_screen,
}
}
}
/// See <https://discord-userdoccers.vercel.app/resources/invite#invite-stage-instance-object> /// See <https://discord-userdoccers.vercel.app/resources/invite#invite-stage-instance-object>
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
pub struct InviteStageInstance { pub struct InviteStageInstance {

View File

@ -2,17 +2,22 @@
// License, v. 2.0. If a copy of the MPL was not distributed with this // 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/. // file, You can obtain one at http://mozilla.org/MPL/2.0/.
use bitflags::bitflags;
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_repr::{Deserialize_repr, Serialize_repr};
use crate::gateway::Shared;
use crate::types::{ use crate::types::{
entities::{ entities::{
Application, Attachment, Channel, Emoji, GuildMember, PublicUser, RoleSubscriptionData, Application, Attachment, Channel, Emoji, GuildMember, PublicUser, RoleSubscriptionData,
Sticker, StickerItem, User, Sticker, StickerItem, User,
}, },
utils::Snowflake, utils::Snowflake,
Shared,
}; };
use super::option_arc_rwlock_ptr_eq;
#[derive(Debug, Serialize, Deserialize, Default, Clone)] #[derive(Debug, Serialize, Deserialize, Default, Clone)]
#[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))] #[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))]
/// Represents a message sent in a channel. /// Represents a message sent in a channel.
@ -25,8 +30,8 @@ pub struct Message {
#[cfg_attr(feature = "sqlx", sqlx(skip))] #[cfg_attr(feature = "sqlx", sqlx(skip))]
pub author: Option<PublicUser>, pub author: Option<PublicUser>,
pub content: Option<String>, pub content: Option<String>,
pub timestamp: String, pub timestamp: DateTime<Utc>,
pub edited_timestamp: Option<String>, pub edited_timestamp: Option<DateTime<Utc>>,
pub tts: Option<bool>, pub tts: Option<bool>,
pub mention_everyone: bool, pub mention_everyone: bool,
#[cfg_attr(feature = "sqlx", sqlx(skip))] #[cfg_attr(feature = "sqlx", sqlx(skip))]
@ -38,7 +43,7 @@ pub struct Message {
#[cfg_attr(feature = "sqlx", sqlx(skip))] #[cfg_attr(feature = "sqlx", sqlx(skip))]
pub attachments: Option<Vec<Attachment>>, pub attachments: Option<Vec<Attachment>>,
#[cfg(feature = "sqlx")] #[cfg(feature = "sqlx")]
pub embeds: Vec<sqlx::types::Json<Embed>>, pub embeds: sqlx::types::Json<Vec<Embed>>,
#[cfg(not(feature = "sqlx"))] #[cfg(not(feature = "sqlx"))]
pub embeds: Option<Vec<Embed>>, pub embeds: Option<Vec<Embed>>,
#[cfg(feature = "sqlx")] #[cfg(feature = "sqlx")]
@ -49,7 +54,7 @@ pub struct Message {
pub pinned: bool, pub pinned: bool,
pub webhook_id: Option<Snowflake>, pub webhook_id: Option<Snowflake>,
#[serde(rename = "type")] #[serde(rename = "type")]
pub message_type: i32, pub message_type: MessageType,
#[cfg(feature = "sqlx")] #[cfg(feature = "sqlx")]
pub activity: Option<sqlx::types::Json<MessageActivity>>, pub activity: Option<sqlx::types::Json<MessageActivity>>,
#[cfg(not(feature = "sqlx"))] #[cfg(not(feature = "sqlx"))]
@ -61,17 +66,26 @@ pub struct Message {
pub message_reference: Option<sqlx::types::Json<MessageReference>>, pub message_reference: Option<sqlx::types::Json<MessageReference>>,
#[cfg(not(feature = "sqlx"))] #[cfg(not(feature = "sqlx"))]
pub message_reference: Option<MessageReference>, pub message_reference: Option<MessageReference>,
pub flags: Option<u64>, pub flags: Option<MessageFlags>,
#[cfg_attr(feature = "sqlx", sqlx(skip))]
pub referenced_message: Option<Box<Message>>, pub referenced_message: Option<Box<Message>>,
#[cfg_attr(feature = "sqlx", sqlx(skip))]
pub interaction: Option<MessageInteraction>, pub interaction: Option<MessageInteraction>,
#[cfg_attr(feature = "sqlx", sqlx(skip))]
pub thread: Option<Channel>, pub thread: Option<Channel>,
#[cfg(feature = "sqlx")]
pub components: Option<sqlx::types::Json<Vec<Component>>>,
#[cfg(not(feature = "sqlx"))]
pub components: Option<Vec<Component>>, pub components: Option<Vec<Component>>,
#[cfg_attr(feature = "sqlx", sqlx(skip))]
pub sticker_items: Option<Vec<StickerItem>>, pub sticker_items: Option<Vec<StickerItem>>,
#[cfg_attr(feature = "sqlx", sqlx(skip))]
pub stickers: Option<Vec<Sticker>>, pub stickers: Option<Vec<Sticker>>,
pub position: Option<i32>, #[cfg_attr(feature = "sqlx", sqlx(skip))]
pub role_subscription_data: Option<RoleSubscriptionData>, pub role_subscription_data: Option<RoleSubscriptionData>,
} }
#[cfg(not(tarpaulin_include))]
impl PartialEq for Message { impl PartialEq for Message {
fn eq(&self, other: &Self) -> bool { fn eq(&self, other: &Self) -> bool {
self.id == other.id self.id == other.id
@ -88,35 +102,50 @@ impl PartialEq for Message {
&& self.attachments == other.attachments && self.attachments == other.attachments
&& self.embeds == other.embeds && self.embeds == other.embeds
&& self.embeds == other.embeds && self.embeds == other.embeds
&& self.reactions == other.reactions
&& self.reactions == other.reactions
&& self.nonce == other.nonce && self.nonce == other.nonce
&& self.pinned == other.pinned && self.pinned == other.pinned
&& self.webhook_id == other.webhook_id && self.webhook_id == other.webhook_id
&& self.message_type == other.message_type && self.message_type == other.message_type
&& self.activity == other.activity && self.activity == other.activity
&& self.activity == other.activity && self.activity == other.activity
&& self.application == other.application
&& self.application_id == other.application_id && self.application_id == other.application_id
&& self.message_reference == other.message_reference && self.message_reference == other.message_reference
&& self.message_reference == other.message_reference && self.message_reference == other.message_reference
&& self.flags == other.flags && self.flags == other.flags
&& self.referenced_message == other.referenced_message && self.referenced_message == other.referenced_message
&& self.interaction == other.interaction
&& self.thread == other.thread && self.thread == other.thread
&& self.components == other.components && self.components == other.components
&& self.components == other.components
&& self.sticker_items == other.sticker_items && self.sticker_items == other.sticker_items
&& self.position == other.position && self.stickers == other.stickers
&& self.role_subscription_data == other.role_subscription_data && self.role_subscription_data == other.role_subscription_data
} }
} }
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Eq, Ord, PartialOrd)] #[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Eq, Ord, PartialOrd, Copy)]
/// # Reference /// # Reference
/// See <https://discord-userdoccers.vercel.app/resources/message#message-reference-object> /// See <https://discord-userdoccers.vercel.app/resources/message#message-reference-object>
pub struct MessageReference { pub struct MessageReference {
#[serde(rename = "type")]
pub reference_type: MessageReferenceType,
pub message_id: Snowflake, pub message_id: Snowflake,
pub channel_id: Snowflake, pub channel_id: Snowflake,
pub guild_id: Option<Snowflake>, pub guild_id: Option<Snowflake>,
pub fail_if_not_exists: Option<bool>, pub fail_if_not_exists: Option<bool>,
} }
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Eq, Ord, PartialOrd, Copy)]
pub enum MessageReferenceType {
/// A standard reference used by replies and system messages
Default = 0,
/// A reference used to point to a message at a point in time
Forward = 1,
}
#[derive(Debug, Clone, Deserialize, Serialize)] #[derive(Debug, Clone, Deserialize, Serialize)]
pub struct MessageInteraction { pub struct MessageInteraction {
pub id: Snowflake, pub id: Snowflake,
@ -127,7 +156,18 @@ pub struct MessageInteraction {
pub member: Option<Shared<GuildMember>>, pub member: Option<Shared<GuildMember>>,
} }
#[derive(Debug, Default, PartialEq, Clone, Serialize, Deserialize, Eq, PartialOrd, Ord)] #[cfg(not(tarpaulin_include))]
impl PartialEq for MessageInteraction {
fn eq(&self, other: &Self) -> bool {
self.id == other.id
&& self.interaction_type == other.interaction_type
&& self.name == other.name
&& self.user == other.user
&& option_arc_rwlock_ptr_eq(&self.member, &other.member)
}
}
#[derive(Debug, Default, PartialEq, Clone, Serialize, Deserialize, Eq, PartialOrd, Ord, Hash)]
pub struct AllowedMention { pub struct AllowedMention {
parse: Vec<AllowedMentionType>, parse: Vec<AllowedMentionType>,
roles: Vec<Snowflake>, roles: Vec<Snowflake>,
@ -135,7 +175,7 @@ pub struct AllowedMention {
replied_user: bool, replied_user: bool,
} }
#[derive(Debug, PartialEq, Clone, Copy, Serialize, Deserialize, Eq, PartialOrd, Ord)] #[derive(Debug, PartialEq, Clone, Copy, Serialize, Deserialize, Eq, PartialOrd, Ord, Hash)]
#[serde(rename_all = "snake_case")] #[serde(rename_all = "snake_case")]
pub enum AllowedMentionType { pub enum AllowedMentionType {
Roles, Roles,
@ -152,11 +192,11 @@ pub struct ChannelMention {
name: String, name: String,
} }
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, PartialOrd)] #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, PartialOrd, Eq, Hash, Ord)]
pub struct Embed { pub struct Embed {
title: Option<String>, title: Option<String>,
#[serde(rename = "type")] #[serde(rename = "type")]
embed_type: Option<String>, embed_type: Option<EmbedType>,
description: Option<String>, description: Option<String>,
url: Option<String>, url: Option<String>,
timestamp: Option<String>, timestamp: Option<String>,
@ -170,14 +210,32 @@ pub struct Embed {
fields: Option<Vec<EmbedField>>, fields: Option<Vec<EmbedField>>,
} }
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)] #[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[serde(rename_all = "snake_case")]
pub enum EmbedType {
#[deprecated]
ApplicationNews,
Article,
AutoModerationMessage,
AutoModerationNotification,
Gift,
#[serde(rename = "gifv")]
GifVideo,
Image,
Link,
PostPreview,
Rich,
Video,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct EmbedFooter { pub struct EmbedFooter {
text: String, text: String,
icon_url: Option<String>, icon_url: Option<String>,
proxy_icon_url: Option<String>, proxy_icon_url: Option<String>,
} }
#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, PartialOrd, Ord)] #[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, PartialOrd, Ord, Hash)]
pub struct EmbedImage { pub struct EmbedImage {
url: String, url: String,
proxy_url: String, proxy_url: String,
@ -185,7 +243,7 @@ pub struct EmbedImage {
width: Option<i32>, width: Option<i32>,
} }
#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, PartialOrd, Ord)] #[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, PartialOrd, Ord, Hash)]
pub struct EmbedThumbnail { pub struct EmbedThumbnail {
url: String, url: String,
proxy_url: Option<String>, proxy_url: Option<String>,
@ -193,7 +251,7 @@ pub struct EmbedThumbnail {
width: Option<i32>, width: Option<i32>,
} }
#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, PartialOrd, Ord)] #[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, PartialOrd, Ord, Hash)]
struct EmbedVideo { struct EmbedVideo {
url: Option<String>, url: Option<String>,
proxy_url: Option<String>, proxy_url: Option<String>,
@ -201,13 +259,13 @@ struct EmbedVideo {
width: Option<i32>, width: Option<i32>,
} }
#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, PartialOrd, Ord)] #[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, PartialOrd, Ord, Hash)]
pub struct EmbedProvider { pub struct EmbedProvider {
name: Option<String>, name: Option<String>,
url: Option<String>, url: Option<String>,
} }
#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, PartialOrd, Ord)] #[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, PartialOrd, Ord, Hash)]
pub struct EmbedAuthor { pub struct EmbedAuthor {
name: String, name: String,
url: Option<String>, url: Option<String>,
@ -215,7 +273,7 @@ pub struct EmbedAuthor {
proxy_icon_url: Option<String>, proxy_icon_url: Option<String>,
} }
#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, PartialOrd, Ord)] #[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, PartialOrd, Ord, Hash)]
pub struct EmbedField { pub struct EmbedField {
name: String, name: String,
value: String, value: String,
@ -226,10 +284,15 @@ pub struct EmbedField {
pub struct Reaction { pub struct Reaction {
pub count: u32, pub count: u32,
pub burst_count: u32, pub burst_count: u32,
#[serde(default)]
pub me: bool, pub me: bool,
#[serde(default)]
pub burst_me: bool, pub burst_me: bool,
pub burst_colors: Vec<String>, pub burst_colors: Vec<String>,
pub emoji: Emoji, pub emoji: Emoji,
#[cfg(feature = "sqlx")]
#[serde(skip)]
pub user_ids: Vec<Snowflake>,
} }
#[derive(Debug, PartialEq, Clone, Copy, Serialize, Deserialize, Eq, PartialOrd, Ord)] #[derive(Debug, PartialEq, Clone, Copy, Serialize, Deserialize, Eq, PartialOrd, Ord)]
@ -252,3 +315,157 @@ pub struct MessageActivity {
pub activity_type: i64, pub activity_type: i64,
pub party_id: Option<String>, pub party_id: Option<String>,
} }
#[derive(
Debug, Default, PartialEq, Clone, Copy, Serialize_repr, Deserialize_repr, Eq, PartialOrd, Ord,
)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
#[repr(u8)]
#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
/// # Reference
/// See <https://docs.discord.sex/resources/message#message-type>
pub enum MessageType {
/// A default message
#[default]
Default = 0,
/// A message sent when a user is added to a group DM or thread
RecipientAdd = 1,
/// A message sent when a user is removed from a group DM or thread
RecipientRemove = 2,
/// A message sent when a user creates a call in a private channel
Call = 3,
/// A message sent when a group DM or thread's name is changed
ChannelNameChange = 4,
/// A message sent when a group DM's icon is changed
ChannelIconChange = 5,
/// A message sent when a message is pinned in a channel
ChannelPinnedMessage = 6,
/// A message sent when a user joins a guild
GuildMemberJoin = 7,
/// A message sent when a user subscribes to (boosts) a guild
UserPremiumGuildSubscription = 8,
/// A message sent when a user subscribes to (boosts) a guild to tier 1
UserPremiumGuildSubscriptionTier1 = 9,
/// A message sent when a user subscribes to (boosts) a guild to tier 2
UserPremiumGuildSubscriptionTier2 = 10,
/// A message sent when a user subscribes to (boosts) a guild to tier 3
UserPremiumGuildSubscriptionTier3 = 11,
/// A message sent when a news channel is followed
ChannelFollowAdd = 12,
/// A message sent when a user starts streaming in a guild (deprecated)
#[deprecated]
GuildStream = 13,
/// A message sent when a guild is disqualified from discovery
GuildDiscoveryDisqualified = 14,
/// A message sent when a guild requalifies for discovery
GuildDiscoveryRequalified = 15,
/// A message sent when a guild has failed discovery requirements for a week
GuildDiscoveryGracePeriodInitial = 16,
/// A message sent when a guild has failed discovery requirements for 3 weeks
GuildDiscoveryGracePeriodFinal = 17,
/// A message sent when a thread is created
ThreadCreated = 18,
/// A message sent when a user replies to a message
Reply = 19,
/// A message sent when a user uses a slash command
#[serde(rename = "CHAT_INPUT_COMMAND")]
ApplicationCommand = 20,
/// A message sent when a thread starter message is added to a thread
ThreadStarterMessage = 21,
/// A message sent to remind users to invite friends to a guild
GuildInviteReminder = 22,
/// A message sent when a user uses a context menu command
ContextMenuCommand = 23,
/// A message sent when auto moderation takes an action
AutoModerationAction = 24,
/// A message sent when a user purchases or renews a role subscription
RoleSubscriptionPurchase = 25,
/// A message sent when a user is upsold to a premium interaction
InteractionPremiumUpsell = 26,
/// A message sent when a stage channel starts
StageStart = 27,
/// A message sent when a stage channel ends
StageEnd = 28,
/// A message sent when a user starts speaking in a stage channel
StageSpeaker = 29,
/// A message sent when a user raises their hand in a stage channel
StageRaiseHand = 30,
/// A message sent when a stage channel's topic is changed
StageTopic = 31,
/// A message sent when a user purchases an application premium subscription
GuildApplicationPremiumSubscription = 32,
/// A message sent when a user adds an application to group DM
PrivateChannelIntegrationAdded = 33,
/// A message sent when a user removed an application from a group DM
PrivateChannelIntegrationRemoved = 34,
/// A message sent when a user gifts a premium (Nitro) referral
PremiumReferral = 35,
/// A message sent when a user enabled lockdown for the guild
GuildIncidentAlertModeEnabled = 36,
/// A message sent when a user disables lockdown for the guild
GuildIncidentAlertModeDisabled = 37,
/// A message sent when a user reports a raid for the guild
GuildIncidentReportRaid = 38,
/// A message sent when a user reports a false alarm for the guild
GuildIncidentReportFalseAlarm = 39,
/// A message sent when no one sends a message in the current channel for 1 hour
GuildDeadchatRevivePrompt = 40,
/// A message sent when a user buys another user a gift
CustomGift = 41,
GuildGamingStatsPrompt = 42,
/// A message sent when a user purchases a guild product
PurchaseNotification = 44,
}
bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, chorus_macros::SerdeBitFlags)]
#[cfg_attr(feature = "sqlx", derive(chorus_macros::SqlxBitFlags))]
/// # Reference
/// See <https://docs.discord.sex/resources/message#message-type>
pub struct MessageFlags: u64 {
/// This message has been published to subscribed channels (via Channel Following)
const CROSSPOSTED = 1 << 0;
/// This message originated from a message in another channel (via Channel Following)
const IS_CROSSPOST = 1 << 1;
/// Embeds will not be included when serializing this message
const SUPPRESS_EMBEDS = 1 << 2;
/// The source message for this crosspost has been deleted (via Channel Following)
const SOURCE_MESSAGE_DELETED = 1 << 3;
/// This message came from the urgent message system
const URGENT = 1 << 4;
/// This message has an associated thread, with the same ID as the message
const HAS_THREAD = 1 << 5;
/// This message is only visible to the user who invoked the interaction
const EPHEMERAL = 1 << 6;
/// This message is an interaction response and the bot is "thinking"
const LOADING = 1 << 7;
/// Some roles were not mentioned and added to the thread
const FAILED_TO_MENTION_SOME_ROLES_IN_THREAD = 1 << 8;
/// This message contains a link that impersonates Discord
const SHOULD_SHOW_LINK_NOT_DISCORD_WARNING = 1 << 10;
/// This message will not trigger push and desktop notifications
const SUPPRESS_NOTIFICATIONS = 1 << 12;
/// This message's audio attachments are rendered as voice messages
const VOICE_MESSAGE = 1 << 13;
/// This message has a forwarded message snapshot attached
const HAS_SNAPSHOT = 1 << 14;
}
}
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
pub struct PartialEmoji {
#[serde(default)]
pub id: Option<Snowflake>,
pub name: String,
#[serde(default)]
pub animated: bool,
}
#[derive(Debug, PartialEq, Clone, Copy, Serialize, Deserialize, PartialOrd, Ord, Eq, Hash)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
#[repr(u8)]
pub enum ReactionType {
Normal = 0,
Burst = 1, // The dreaded super reactions
}

View File

@ -27,7 +27,10 @@ pub use user_settings::*;
pub use voice_state::*; pub use voice_state::*;
pub use webhook::*; pub use webhook::*;
use crate::gateway::Shared; use crate::types::Shared;
#[cfg(feature = "client")]
use std::sync::{Arc, RwLock};
#[cfg(feature = "client")] #[cfg(feature = "client")]
use crate::gateway::Updateable; use crate::gateway::Updateable;
@ -39,8 +42,6 @@ use async_trait::async_trait;
#[cfg(feature = "client")] #[cfg(feature = "client")]
use std::fmt::Debug; use std::fmt::Debug;
#[cfg(feature = "client")]
use std::sync::{Arc, RwLock};
mod application; mod application;
mod attachment; mod attachment;
@ -134,8 +135,53 @@ pub trait IntoShared {
fn into_shared(self) -> Shared<Self>; fn into_shared(self) -> Shared<Self>;
} }
#[cfg(feature = "client")]
impl<T: Sized> IntoShared for T { impl<T: Sized> IntoShared for T {
fn into_shared(self) -> Shared<Self> { fn into_shared(self) -> Shared<Self> {
Arc::new(RwLock::new(self)) Arc::new(RwLock::new(self))
} }
} }
/// Internal function to compare two `Shared<T>`s by comparing their pointers.
#[cfg_attr(not(feature = "client"), allow(unused_variables))]
pub(crate) fn arc_rwlock_ptr_eq<T>(a: &Shared<T>, b: &Shared<T>) -> bool {
#[cfg(feature = "client")]
{
Shared::ptr_eq(a, b)
}
#[cfg(not(feature = "client"))]
{
true
}
}
/// Internal function to compare two `Vec<Shared<T>>`s by comparing their pointers.
pub(crate) fn vec_arc_rwlock_ptr_eq<T>(a: &[Shared<T>], b: &[Shared<T>]) -> bool {
for (a, b) in a.iter().zip(b.iter()) {
if !arc_rwlock_ptr_eq(a, b) {
return false;
}
}
true
}
/// Internal function to compare two `Option<Shared<T>>`s by comparing their pointers.
pub(crate) fn option_arc_rwlock_ptr_eq<T>(a: &Option<Shared<T>>, b: &Option<Shared<T>>) -> bool {
match (a, b) {
(Some(a), Some(b)) => arc_rwlock_ptr_eq(a, b),
(None, None) => true,
_ => false,
}
}
/// Internal function to compare two `Option<Vec<Shared<T>>>`s by comparing their pointers.
pub(crate) fn option_vec_arc_rwlock_ptr_eq<T>(
a: &Option<Vec<Shared<T>>>,
b: &Option<Vec<Shared<T>>>,
) -> bool {
match (a, b) {
(Some(a), Some(b)) => vec_arc_rwlock_ptr_eq(a, b),
(None, None) => true,
_ => false,
}
}

View File

@ -11,7 +11,9 @@ use crate::types::Snowflake;
/// The different types of ratelimits that can be applied to a request. Includes "Baseline"-variants /// The different types of ratelimits that can be applied to a request. Includes "Baseline"-variants
/// for when the Snowflake is not yet known. /// for when the Snowflake is not yet known.
/// See <https://discord.com/developers/docs/topics/rate-limits#rate-limits> for more information. /// See <https://discord.com/developers/docs/topics/rate-limits#rate-limits> for more information.
#[derive(Clone, Copy, Eq, PartialEq, Debug, Default, Hash, Serialize, Deserialize)] #[derive(
Clone, Copy, Eq, PartialEq, Debug, Default, Hash, Serialize, Deserialize, PartialOrd, Ord,
)]
pub enum LimitType { pub enum LimitType {
AuthRegister, AuthRegister,
AuthLogin, AuthLogin,
@ -29,7 +31,7 @@ pub enum LimitType {
/// A struct that represents the current ratelimits, either instance-wide or user-wide. /// A struct that represents the current ratelimits, either instance-wide or user-wide.
/// See <https://discord.com/developers/docs/topics/rate-limits#rate-limits> for more information. /// See <https://discord.com/developers/docs/topics/rate-limits#rate-limits> for more information.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)] #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash, Copy, PartialOrd, Ord)]
pub struct Limit { pub struct Limit {
pub bucket: LimitType, pub bucket: LimitType,
pub limit: u64, pub limit: u64,

View File

@ -6,10 +6,9 @@ use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_repr::{Deserialize_repr, Serialize_repr}; use serde_repr::{Deserialize_repr, Serialize_repr};
use crate::gateway::Shared; use crate::types::{Shared, Snowflake};
use crate::types::Snowflake;
use super::PublicUser; use super::{arc_rwlock_ptr_eq, PublicUser};
#[derive(Debug, Deserialize, Serialize, Clone, Default)] #[derive(Debug, Deserialize, Serialize, Clone, Default)]
/// See <https://discord-userdoccers.vercel.app/resources/user#relationship-structure> /// See <https://discord-userdoccers.vercel.app/resources/user#relationship-structure>
@ -22,16 +21,30 @@ pub struct Relationship {
pub since: Option<DateTime<Utc>>, pub since: Option<DateTime<Utc>>,
} }
#[cfg(not(tarpaulin_include))]
impl PartialEq for Relationship { impl PartialEq for Relationship {
fn eq(&self, other: &Self) -> bool { fn eq(&self, other: &Self) -> bool {
self.id == other.id self.id == other.id
&& self.relationship_type == other.relationship_type && self.relationship_type == other.relationship_type
&& self.since == other.since
&& self.nickname == other.nickname && self.nickname == other.nickname
&& arc_rwlock_ptr_eq(&self.user, &other.user)
&& self.since == other.since
} }
} }
#[derive(Serialize_repr, Deserialize_repr, Debug, Clone, Default, Eq, PartialEq)] #[derive(
Serialize_repr,
Deserialize_repr,
Debug,
Clone,
Default,
Eq,
PartialEq,
PartialOrd,
Ord,
Copy,
Hash,
)]
#[repr(u8)] #[repr(u8)]
/// See <https://discord-userdoccers.vercel.app/resources/user#relationship-type> /// See <https://discord-userdoccers.vercel.app/resources/user#relationship-type>
pub enum RelationshipType { pub enum RelationshipType {

View File

@ -4,7 +4,7 @@
use bitflags::bitflags; use bitflags::bitflags;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_aux::prelude::{deserialize_option_number_from_string, deserialize_string_from_number}; use serde_aux::prelude::deserialize_option_number_from_string;
use std::fmt::Debug; use std::fmt::Debug;
use crate::types::utils::Snowflake; use crate::types::utils::Snowflake;
@ -34,8 +34,7 @@ pub struct RoleObject {
pub unicode_emoji: Option<String>, pub unicode_emoji: Option<String>,
pub position: u16, pub position: u16,
#[serde(default)] #[serde(default)]
#[serde(deserialize_with = "deserialize_string_from_number")] pub permissions: PermissionFlags,
pub permissions: String,
pub managed: bool, pub managed: bool,
pub mentionable: bool, pub mentionable: bool,
#[cfg(feature = "sqlx")] #[cfg(feature = "sqlx")]
@ -52,7 +51,7 @@ pub struct RoleSubscriptionData {
pub is_renewal: bool, pub is_renewal: bool,
} }
#[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq, Eq, Hash)] #[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq, Eq, Hash, Copy, PartialOrd, Ord)]
/// See <https://discord.com/developers/docs/topics/permissions#role-object-role-tags-structure> /// See <https://discord.com/developers/docs/topics/permissions#role-object-role-tags-structure>
pub struct RoleTags { pub struct RoleTags {
#[serde(default)] #[serde(default)]
@ -71,7 +70,8 @@ pub struct RoleTags {
} }
bitflags! { bitflags! {
#[derive(Debug, Default, Clone, Hash, Serialize, Deserialize, PartialEq, Eq)] #[derive(Debug, Default, Clone, Hash, PartialEq, Eq, PartialOrd, chorus_macros::SerdeBitFlags)]
#[cfg_attr(feature = "sqlx", derive(chorus_macros::SqlxBitFlags))]
/// Permissions limit what users of certain roles can do on a Guild to Guild basis. /// Permissions limit what users of certain roles can do on a Guild to Guild basis.
/// ///
/// # Reference: /// # Reference:

View File

@ -21,7 +21,7 @@ pub struct StageInstance {
pub guild_scheduled_event_id: Option<Snowflake>, pub guild_scheduled_event_id: Option<Snowflake>,
} }
#[derive(Serialize_repr, Deserialize_repr, Debug, Clone, Default)] #[derive(Serialize_repr, Deserialize_repr, Debug, Clone, Default, Copy, PartialEq, Eq, PartialOrd, Ord)]
#[repr(u8)] #[repr(u8)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")] #[serde(rename_all = "SCREAMING_SNAKE_CASE")]
/// See <https://discord.com/developers/docs/resources/stage-instance#stage-instance-object-privacy-level> /// See <https://discord.com/developers/docs/resources/stage-instance#stage-instance-object-privacy-level>

View File

@ -3,9 +3,11 @@
// file, You can obtain one at http://mozilla.org/MPL/2.0/. // file, You can obtain one at http://mozilla.org/MPL/2.0/.
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_repr::{Deserialize_repr, Serialize_repr};
use crate::gateway::Shared; use crate::types::{entities::User, utils::Snowflake, Shared};
use crate::types::{entities::User, utils::Snowflake};
use super::option_arc_rwlock_ptr_eq;
#[derive(Debug, Serialize, Deserialize, Clone, Default)] #[derive(Debug, Serialize, Deserialize, Clone, Default)]
#[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))] #[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))]
@ -19,34 +21,21 @@ pub struct Sticker {
pub pack_id: Option<Snowflake>, pub pack_id: Option<Snowflake>,
pub name: String, pub name: String,
pub description: Option<String>, pub description: Option<String>,
pub tags: String, pub tags: Option<String>,
#[cfg_attr(feature = "sqlx", sqlx(skip))]
pub asset: Option<String>, pub asset: Option<String>,
#[serde(rename = "type")] #[serde(rename = "type")]
pub sticker_type: u8, pub sticker_type: StickerType,
pub format_type: u8, pub format_type: StickerFormatType,
pub available: Option<bool>, pub available: Option<bool>,
pub guild_id: Option<Snowflake>, pub guild_id: Option<Snowflake>,
#[cfg_attr(feature = "sqlx", sqlx(skip))] #[cfg_attr(feature = "sqlx", sqlx(skip))]
pub user: Option<Shared<User>>, pub user: Option<Shared<User>>,
#[cfg_attr(feature = "sqlx", sqlx(skip))]
pub sort_value: Option<u8>, pub sort_value: Option<u8>,
} }
impl std::hash::Hash for Sticker { #[cfg(not(tarpaulin_include))]
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.id.hash(state);
self.pack_id.hash(state);
self.name.hash(state);
self.description.hash(state);
self.tags.hash(state);
self.asset.hash(state);
self.sticker_type.hash(state);
self.format_type.hash(state);
self.available.hash(state);
self.guild_id.hash(state);
self.sort_value.hash(state);
}
}
impl PartialEq for Sticker { impl PartialEq for Sticker {
fn eq(&self, other: &Self) -> bool { fn eq(&self, other: &Self) -> bool {
self.id == other.id self.id == other.id
@ -59,53 +48,16 @@ impl PartialEq for Sticker {
&& self.format_type == other.format_type && self.format_type == other.format_type
&& self.available == other.available && self.available == other.available
&& self.guild_id == other.guild_id && self.guild_id == other.guild_id
&& option_arc_rwlock_ptr_eq(&self.user, &other.user)
&& self.sort_value == other.sort_value && self.sort_value == other.sort_value
} }
} }
impl PartialOrd for Sticker { impl Sticker {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> { pub fn tags(&self) -> Vec<String> {
match self.id.partial_cmp(&other.id) { self.tags.as_ref().map_or(vec![], |s| {
Some(core::cmp::Ordering::Equal) => {} s.split(',').map(|tag| tag.trim().to_string()).collect()
ord => return ord, })
}
match self.pack_id.partial_cmp(&other.pack_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.description.partial_cmp(&other.description) {
Some(core::cmp::Ordering::Equal) => {}
ord => return ord,
}
match self.tags.partial_cmp(&other.tags) {
Some(core::cmp::Ordering::Equal) => {}
ord => return ord,
}
match self.asset.partial_cmp(&other.asset) {
Some(core::cmp::Ordering::Equal) => {}
ord => return ord,
}
match self.sticker_type.partial_cmp(&other.sticker_type) {
Some(core::cmp::Ordering::Equal) => {}
ord => return ord,
}
match self.format_type.partial_cmp(&other.format_type) {
Some(core::cmp::Ordering::Equal) => {}
ord => return ord,
}
match self.available.partial_cmp(&other.available) {
Some(core::cmp::Ordering::Equal) => {}
ord => return ord,
}
match self.guild_id.partial_cmp(&other.guild_id) {
Some(core::cmp::Ordering::Equal) => {}
ord => return ord,
}
self.sort_value.partial_cmp(&other.sort_value)
} }
} }
@ -119,5 +71,68 @@ impl PartialOrd for Sticker {
pub struct StickerItem { pub struct StickerItem {
pub id: Snowflake, pub id: Snowflake,
pub name: String, pub name: String,
pub format_type: u8, pub format_type: StickerFormatType,
}
#[derive(
Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Hash, Serialize_repr, Deserialize_repr,
)]
#[repr(u8)]
#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
#[serde(rename = "SCREAMING_SNAKE_CASE")]
/// # Reference
/// See <https://docs.discord.sex/resources/sticker#sticker-types>
pub enum StickerType {
/// An official sticker in a current or legacy purchasable pack
Standard = 1,
#[default]
/// A sticker uploaded to a guild for the guild's members
Guild = 2,
}
#[derive(
Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Hash, Serialize_repr, Deserialize_repr,
)]
#[repr(u8)]
#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
/// # Reference
/// See <https://docs.discord.sex/resources/sticker#sticker-format-types>
pub enum StickerFormatType {
#[default]
/// A PNG image
PNG = 1,
/// An animated PNG image, using the APNG format - uses CDN
APNG = 2,
/// A lottie animation; requires the VERIFIED and/or PARTNERED guild feature - uses CDN
LOTTIE = 3,
/// An animated GIF image - does not use CDN
GIF = 4,
}
impl StickerFormatType {
pub fn is_animated(&self) -> bool {
matches!(
self,
StickerFormatType::APNG | StickerFormatType::LOTTIE | StickerFormatType::GIF
)
}
pub const fn to_mime(&self) -> &'static str {
match self {
StickerFormatType::PNG => "image/png",
StickerFormatType::APNG => "image/apng",
StickerFormatType::LOTTIE => "application/json",
StickerFormatType::GIF => "image/gif",
}
}
pub fn from_mime(mime: &str) -> Option<Self> {
match mime {
"image/png" => Some(StickerFormatType::PNG),
"image/apng" => Some(StickerFormatType::APNG),
"application/json" => Some(StickerFormatType::LOTTIE),
"image/gif" => Some(StickerFormatType::GIF),
_ => None,
}
}
} }

View File

@ -4,10 +4,12 @@
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::gateway::Shared;
use crate::types::entities::User; use crate::types::entities::User;
use crate::types::Shared;
use crate::types::Snowflake; use crate::types::Snowflake;
use super::arc_rwlock_ptr_eq;
#[derive(Debug, Deserialize, Serialize, Clone)] #[derive(Debug, Deserialize, Serialize, Clone)]
#[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))] #[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))]
pub struct Team { pub struct Team {
@ -19,6 +21,17 @@ pub struct Team {
pub owner_user_id: Snowflake, pub owner_user_id: Snowflake,
} }
#[cfg(not(tarpaulin_include))]
impl PartialEq for Team {
fn eq(&self, other: &Self) -> bool {
self.icon == other.icon
&& self.id == other.id
&& self.members == other.members
&& self.name == other.name
&& self.owner_user_id == other.owner_user_id
}
}
#[derive(Debug, Deserialize, Serialize, Clone)] #[derive(Debug, Deserialize, Serialize, Clone)]
pub struct TeamMember { pub struct TeamMember {
pub membership_state: u8, pub membership_state: u8,
@ -26,3 +39,13 @@ pub struct TeamMember {
pub team_id: Snowflake, pub team_id: Snowflake,
pub user: Shared<User>, pub user: Shared<User>,
} }
#[cfg(not(tarpaulin_include))]
impl PartialEq for TeamMember {
fn eq(&self, other: &Self) -> bool {
self.membership_state == other.membership_state
&& self.permissions == other.permissions
&& self.team_id == other.team_id
&& arc_rwlock_ptr_eq(&self.user, &other.user)
}
}

View File

@ -5,8 +5,8 @@
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::gateway::Shared;
use crate::types::{ use crate::types::{
Shared,
entities::{Guild, User}, entities::{Guild, User},
utils::Snowflake, utils::Snowflake,
}; };

View File

@ -45,7 +45,7 @@ pub struct User {
pub bot: Option<bool>, pub bot: Option<bool>,
pub system: Option<bool>, pub system: Option<bool>,
pub mfa_enabled: Option<bool>, pub mfa_enabled: Option<bool>,
pub accent_color: Option<u8>, pub accent_color: Option<u32>,
#[cfg_attr(feature = "sqlx", sqlx(default))] #[cfg_attr(feature = "sqlx", sqlx(default))]
pub locale: Option<String>, pub locale: Option<String>,
pub verified: Option<bool>, pub verified: Option<bool>,
@ -54,14 +54,14 @@ pub struct User {
/// So we need to account for that /// So we need to account for that
#[serde(default)] #[serde(default)]
#[serde(deserialize_with = "deserialize_option_number_from_string")] #[serde(deserialize_with = "deserialize_option_number_from_string")]
pub flags: Option<i32>, pub flags: Option<UserFlags>,
pub premium_since: Option<DateTime<Utc>>, pub premium_since: Option<DateTime<Utc>>,
pub premium_type: Option<u8>, pub premium_type: Option<u8>,
pub pronouns: Option<String>, pub pronouns: Option<String>,
pub public_flags: Option<u32>, pub public_flags: Option<UserFlags>,
pub banner: Option<String>, pub banner: Option<String>,
pub bio: Option<String>, pub bio: Option<String>,
pub theme_colors: Option<Vec<u8>>, pub theme_colors: Option<Vec<u32>>,
pub phone: Option<String>, pub phone: Option<String>,
pub nsfw_allowed: Option<bool>, pub nsfw_allowed: Option<bool>,
pub premium: Option<bool>, pub premium: Option<bool>,
@ -76,15 +76,15 @@ pub struct PublicUser {
pub username: Option<String>, pub username: Option<String>,
pub discriminator: Option<String>, pub discriminator: Option<String>,
pub avatar: Option<String>, pub avatar: Option<String>,
pub accent_color: Option<u8>, pub accent_color: Option<u32>,
pub banner: Option<String>, pub banner: Option<String>,
pub theme_colors: Option<Vec<u8>>, pub theme_colors: Option<Vec<u32>>,
pub pronouns: Option<String>, pub pronouns: Option<String>,
pub bot: Option<bool>, pub bot: Option<bool>,
pub bio: Option<String>, pub bio: Option<String>,
pub premium_type: Option<u8>, pub premium_type: Option<u8>,
pub premium_since: Option<DateTime<Utc>>, pub premium_since: Option<DateTime<Utc>>,
pub public_flags: Option<u32>, pub public_flags: Option<UserFlags>,
} }
impl From<User> for PublicUser { impl From<User> for PublicUser {
@ -111,8 +111,8 @@ impl From<User> for PublicUser {
const CUSTOM_USER_FLAG_OFFSET: u64 = 1 << 32; const CUSTOM_USER_FLAG_OFFSET: u64 = 1 << 32;
bitflags::bitflags! { bitflags::bitflags! {
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, chorus_macros::SerdeBitFlags)]
#[cfg_attr(feature = "sqlx", derive(sqlx::Type))] #[cfg_attr(feature = "sqlx", derive(chorus_macros::SqlxBitFlags))]
pub struct UserFlags: u64 { pub struct UserFlags: u64 {
const DISCORD_EMPLOYEE = 1 << 0; const DISCORD_EMPLOYEE = 1 << 0;
const PARTNERED_SERVER_OWNER = 1 << 1; const PARTNERED_SERVER_OWNER = 1 << 1;

View File

@ -2,14 +2,13 @@
// License, v. 2.0. If a copy of the MPL was not distributed with this // 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/. // file, You can obtain one at http://mozilla.org/MPL/2.0/.
use std::sync::{Arc, RwLock};
use chrono::{serde::ts_milliseconds_option, Utc}; use chrono::{serde::ts_milliseconds_option, Utc};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::gateway::Shared; use crate::types::Shared;
use serde_aux::field_attributes::deserialize_option_number_from_string;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default, Copy, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "sqlx", derive(sqlx::Type))] #[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
#[serde(rename_all = "lowercase")] #[serde(rename_all = "lowercase")]
pub enum UserStatus { pub enum UserStatus {
@ -27,7 +26,7 @@ impl std::fmt::Display for UserStatus {
} }
} }
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default, Copy, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "sqlx", derive(sqlx::Type))] #[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
#[serde(rename_all = "lowercase")] #[serde(rename_all = "lowercase")]
pub enum UserTheme { pub enum UserTheme {
@ -39,7 +38,7 @@ pub enum UserTheme {
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))] #[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))]
pub struct UserSettings { pub struct UserSettings {
pub afk_timeout: u16, pub afk_timeout: Option<u16>,
pub allow_accessibility_detection: bool, pub allow_accessibility_detection: bool,
pub animate_emoji: bool, pub animate_emoji: bool,
pub animate_stickers: u8, pub animate_stickers: u8,
@ -92,7 +91,7 @@ pub struct UserSettings {
impl Default for UserSettings { impl Default for UserSettings {
fn default() -> Self { fn default() -> Self {
Self { Self {
afk_timeout: 3600, afk_timeout: Some(3600),
allow_accessibility_detection: true, allow_accessibility_detection: true,
animate_emoji: true, animate_emoji: true,
animate_stickers: 0, animate_stickers: 0,
@ -119,7 +118,7 @@ impl Default for UserSettings {
render_reactions: true, render_reactions: true,
restricted_guilds: Default::default(), restricted_guilds: Default::default(),
show_current_game: true, show_current_game: true,
status: Arc::new(RwLock::new(UserStatus::Online)), status: Default::default(),
stream_notifications_enabled: false, stream_notifications_enabled: false,
theme: UserTheme::Dark, theme: UserTheme::Dark,
timezone_offset: 0, timezone_offset: 0,
@ -137,7 +136,7 @@ pub struct CustomStatus {
pub text: Option<String>, pub text: Option<String>,
} }
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Copy, PartialOrd, Ord, Hash)]
pub struct FriendSourceFlags { pub struct FriendSourceFlags {
pub all: bool, pub all: bool,
} }
@ -150,10 +149,17 @@ impl Default for FriendSourceFlags {
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GuildFolder { pub struct GuildFolder {
pub color: u32, pub color: Option<u32>,
pub guild_ids: Vec<String>, pub guild_ids: Vec<String>,
pub id: u16, // FIXME: What is this thing?
pub name: String, // It's not a snowflake, and it's sometimes a string and sometimes an integer.
//
// Ex: 1249181105
//
// It can also be negative somehow? Ex: -1176643795
#[serde(deserialize_with = "deserialize_option_number_from_string")]
pub id: Option<i64>,
pub name: Option<String>,
} }
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]

View File

@ -5,7 +5,8 @@
#[cfg(feature = "client")] #[cfg(feature = "client")]
use chorus_macros::Composite; use chorus_macros::Composite;
use crate::gateway::Shared; use crate::types::Shared;
#[cfg(feature = "client")] #[cfg(feature = "client")]
use crate::types::Composite; use crate::types::Composite;
@ -24,6 +25,8 @@ use crate::types::{
utils::Snowflake, utils::Snowflake,
}; };
use super::option_arc_rwlock_ptr_eq;
/// The VoiceState struct. Note, that Discord does not have an `id` field for this, whereas Spacebar /// The VoiceState struct. Note, that Discord does not have an `id` field for this, whereas Spacebar
/// does. /// does.
/// ///
@ -33,9 +36,11 @@ use crate::types::{
#[cfg_attr(feature = "client", derive(Composite))] #[cfg_attr(feature = "client", derive(Composite))]
pub struct VoiceState { pub struct VoiceState {
pub guild_id: Option<Snowflake>, pub guild_id: Option<Snowflake>,
#[cfg_attr(feature = "sqlx", sqlx(skip))]
pub guild: Option<Guild>, pub guild: Option<Guild>,
pub channel_id: Option<Snowflake>, pub channel_id: Option<Snowflake>,
pub user_id: Snowflake, pub user_id: Snowflake,
#[cfg_attr(feature = "sqlx", sqlx(skip))]
pub member: Option<Shared<GuildMember>>, pub member: Option<Shared<GuildMember>>,
/// Includes alphanumeric characters, not a snowflake /// Includes alphanumeric characters, not a snowflake
pub session_id: String, pub session_id: String,
@ -51,6 +56,29 @@ pub struct VoiceState {
pub id: Option<Snowflake>, // Only exists on Spacebar pub id: Option<Snowflake>, // Only exists on Spacebar
} }
#[cfg(not(tarpaulin_include))]
impl PartialEq for VoiceState {
fn eq(&self, other: &Self) -> bool {
self.guild_id == other.guild_id
&& self.guild == other.guild
&& self.channel_id == other.channel_id
&& self.user_id == other.user_id
&& option_arc_rwlock_ptr_eq(&self.member, &other.member)
&& self.session_id == other.session_id
&& self.token == other.token
&& self.deaf == other.deaf
&& self.mute == other.mute
&& self.self_deaf == other.self_deaf
&& self.self_mute == other.self_mute
&& self.self_stream == other.self_stream
&& self.self_video == other.self_video
&& self.suppress == other.suppress
&& self.request_to_speak_timestamp == other.request_to_speak_timestamp
&& self.id == other.id
}
}
#[cfg(feature = "client")]
impl Updateable for VoiceState { impl Updateable for VoiceState {
#[cfg(not(tarpaulin_include))] #[cfg(not(tarpaulin_include))]
fn id(&self) -> Snowflake { fn id(&self) -> Snowflake {

View File

@ -6,7 +6,8 @@ use std::fmt::Debug;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::gateway::Shared; use crate::types::Shared;
#[cfg(feature = "client")] #[cfg(feature = "client")]
use crate::gateway::Updateable; use crate::gateway::Updateable;
@ -24,6 +25,8 @@ use crate::types::{
utils::Snowflake, utils::Snowflake,
}; };
use super::option_arc_rwlock_ptr_eq;
/// See <https://docs.spacebar.chat/routes/#cmp--schemas-webhook> /// See <https://docs.spacebar.chat/routes/#cmp--schemas-webhook>
#[derive(Serialize, Deserialize, Debug, Default, Clone)] #[derive(Serialize, Deserialize, Debug, Default, Clone)]
#[cfg_attr(feature = "client", derive(Updateable, Composite))] #[cfg_attr(feature = "client", derive(Updateable, Composite))]
@ -31,13 +34,13 @@ use crate::types::{
pub struct Webhook { pub struct Webhook {
pub id: Snowflake, pub id: Snowflake,
#[serde(rename = "type")] #[serde(rename = "type")]
pub webhook_type: i32, pub webhook_type: WebhookType,
pub name: String, pub name: String,
pub avatar: String, pub avatar: String,
pub token: String, pub token: String,
pub guild_id: Snowflake, pub guild_id: Snowflake,
pub channel_id: Snowflake, pub channel_id: Snowflake,
pub application_id: Snowflake, pub application_id: Option<Snowflake>,
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "sqlx", sqlx(skip))] #[cfg_attr(feature = "sqlx", sqlx(skip))]
pub user: Option<Shared<User>>, pub user: Option<Shared<User>>,
@ -47,3 +50,32 @@ pub struct Webhook {
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub url: Option<String>, pub url: Option<String>,
} }
#[cfg(not(tarpaulin_include))]
impl PartialEq for Webhook {
fn eq(&self, other: &Self) -> bool {
self.id == other.id
&& self.webhook_type == other.webhook_type
&& self.name == other.name
&& self.avatar == other.avatar
&& self.token == other.token
&& self.guild_id == other.guild_id
&& self.channel_id == other.channel_id
&& self.application_id == other.application_id
&& option_arc_rwlock_ptr_eq(&self.user, &other.user)
&& option_arc_rwlock_ptr_eq(&self.source_guild, &other.source_guild)
&& self.url == other.url
}
}
#[derive(
Serialize, Deserialize, Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash,
)]
#[repr(u8)]
#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
pub enum WebhookType {
#[default]
Incoming = 1,
ChannelFollower = 2,
Application = 3,
}

View File

@ -21,15 +21,18 @@ pub enum Error {
#[error(transparent)] #[error(transparent)]
Guild(#[from] GuildError), Guild(#[from] GuildError),
#[error("Invalid flags value: {0}")]
InvalidFlags(u64),
} }
#[derive(Debug, PartialEq, Eq, thiserror::Error)] #[derive(Debug, PartialEq, Eq, thiserror::Error, Copy, Clone)]
pub enum GuildError { pub enum GuildError {
#[error("Invalid Guild Feature")] #[error("Invalid Guild Feature")]
InvalidGuildFeature, InvalidGuildFeature,
} }
#[derive(Debug, PartialEq, Eq, thiserror::Error)] #[derive(Debug, PartialEq, Eq, thiserror::Error, Copy, Clone)]
pub enum FieldFormatError { pub enum FieldFormatError {
#[error("Password must be between 1 and 72 characters.")] #[error("Password must be between 1 and 72 characters.")]
PasswordError, PasswordError,

View File

@ -5,12 +5,11 @@
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::types::{GuildApplicationCommandPermissions, WebSocketEvent}; use crate::types::{GuildApplicationCommandPermissions, WebSocketEvent};
use chorus_macros::WebSocketEvent;
#[derive(Debug, Deserialize, Serialize, Default, Clone)] #[derive(Debug, Deserialize, Serialize, Default, Clone, WebSocketEvent)]
/// See <https://discord.com/developers/docs/topics/gateway-events#application-command-permissions-update> /// See <https://discord.com/developers/docs/topics/gateway-events#application-command-permissions-update>
pub struct ApplicationCommandPermissionsUpdate { pub struct ApplicationCommandPermissionsUpdate {
#[serde(flatten)] #[serde(flatten)]
pub permissions: GuildApplicationCommandPermissions, pub permissions: GuildApplicationCommandPermissions,
} }
impl WebSocketEvent for ApplicationCommandPermissionsUpdate {}

View File

@ -2,28 +2,27 @@
// License, v. 2.0. If a copy of the MPL was not distributed with this // 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/. // file, You can obtain one at http://mozilla.org/MPL/2.0/.
use crate::types::{JsonField, SourceUrlField}; use crate::types::{JsonField, SourceUrlField, WebSocketEvent};
use chorus_macros::{JsonField, SourceUrlField}; use chorus_macros::{JsonField, SourceUrlField, WebSocketEvent};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::types::{ use crate::types::{
AutoModerationAction, AutoModerationRule, AutoModerationRuleTriggerType, Snowflake, AutoModerationAction, AutoModerationRule, AutoModerationRuleTriggerType, Snowflake,
WebSocketEvent,
}; };
#[cfg(feature = "client")] #[cfg(feature = "client")]
use super::UpdateMessage; use super::UpdateMessage;
#[derive(Debug, Deserialize, Serialize, Default, Clone)] #[derive(Debug, Deserialize, Serialize, Default, Clone, WebSocketEvent)]
/// See <https://discord.com/developers/docs/topics/gateway-events#auto-moderation-rule-create> /// See <https://discord.com/developers/docs/topics/gateway-events#auto-moderation-rule-create>
pub struct AutoModerationRuleCreate { pub struct AutoModerationRuleCreate {
#[serde(flatten)] #[serde(flatten)]
pub rule: AutoModerationRule, pub rule: AutoModerationRule,
} }
impl WebSocketEvent for AutoModerationRuleCreate {} #[derive(
Debug, Deserialize, Serialize, Default, Clone, JsonField, SourceUrlField, WebSocketEvent,
#[derive(Debug, Deserialize, Serialize, Default, Clone, JsonField, SourceUrlField)] )]
/// See <https://discord.com/developers/docs/topics/gateway-events#auto-moderation-rule-update> /// See <https://discord.com/developers/docs/topics/gateway-events#auto-moderation-rule-update>
pub struct AutoModerationRuleUpdate { pub struct AutoModerationRuleUpdate {
#[serde(flatten)] #[serde(flatten)]
@ -43,18 +42,14 @@ impl UpdateMessage<AutoModerationRule> for AutoModerationRuleUpdate {
} }
} }
impl WebSocketEvent for AutoModerationRuleUpdate {} #[derive(Debug, Deserialize, Serialize, Default, Clone, WebSocketEvent)]
#[derive(Debug, Deserialize, Serialize, Default, Clone)]
/// See <https://discord.com/developers/docs/topics/gateway-events#auto-moderation-rule-delete> /// See <https://discord.com/developers/docs/topics/gateway-events#auto-moderation-rule-delete>
pub struct AutoModerationRuleDelete { pub struct AutoModerationRuleDelete {
#[serde(flatten)] #[serde(flatten)]
pub rule: AutoModerationRule, pub rule: AutoModerationRule,
} }
impl WebSocketEvent for AutoModerationRuleDelete {} #[derive(Debug, Deserialize, Serialize, Default, Clone, WebSocketEvent)]
#[derive(Debug, Deserialize, Serialize, Default, Clone)]
/// See <https://discord.com/developers/docs/topics/gateway-events#auto-moderation-action-execution> /// See <https://discord.com/developers/docs/topics/gateway-events#auto-moderation-action-execution>
pub struct AutoModerationActionExecution { pub struct AutoModerationActionExecution {
pub guild_id: Snowflake, pub guild_id: Snowflake,
@ -69,5 +64,3 @@ pub struct AutoModerationActionExecution {
pub matched_keyword: Option<String>, pub matched_keyword: Option<String>,
pub matched_content: Option<String>, pub matched_content: Option<String>,
} }
impl WebSocketEvent for AutoModerationActionExecution {}

View File

@ -5,8 +5,9 @@
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::types::{Snowflake, VoiceState, WebSocketEvent}; use crate::types::{Snowflake, VoiceState, WebSocketEvent};
use chorus_macros::WebSocketEvent;
#[derive(Debug, Deserialize, Serialize, Default, Clone)] #[derive(Debug, Deserialize, Serialize, Default, Clone, WebSocketEvent)]
/// Officially Undocumented; /// Officially Undocumented;
/// Is sent to a client by the server to signify a new call being created; /// Is sent to a client by the server to signify a new call being created;
/// ///
@ -23,9 +24,7 @@ pub struct CallCreate {
pub channel_id: Snowflake, pub channel_id: Snowflake,
} }
impl WebSocketEvent for CallCreate {} #[derive(Debug, Deserialize, Serialize, Default, Clone, PartialEq, Eq, WebSocketEvent)]
#[derive(Debug, Deserialize, Serialize, Default, Clone, PartialEq, Eq)]
/// Officially Undocumented; /// Officially Undocumented;
/// Updates the client on which calls are ringing, along with a specific call?; /// Updates the client on which calls are ringing, along with a specific call?;
/// ///
@ -40,9 +39,19 @@ pub struct CallUpdate {
pub channel_id: Snowflake, pub channel_id: Snowflake,
} }
impl WebSocketEvent for CallUpdate {} #[derive(
Debug,
#[derive(Debug, Deserialize, Serialize, Default, Clone, PartialEq, Eq)] Deserialize,
Serialize,
Default,
Clone,
PartialEq,
Eq,
WebSocketEvent,
Copy,
PartialOrd,
Ord,
)]
/// Officially Undocumented; /// Officially Undocumented;
/// Deletes a ringing call; /// Deletes a ringing call;
/// Ex: {"t":"CALL_DELETE","s":8,"op":0,"d":{"channel_id":"837609115475771392"}} /// Ex: {"t":"CALL_DELETE","s":8,"op":0,"d":{"channel_id":"837609115475771392"}}
@ -50,9 +59,19 @@ pub struct CallDelete {
pub channel_id: Snowflake, pub channel_id: Snowflake,
} }
impl WebSocketEvent for CallDelete {} #[derive(
Debug,
#[derive(Debug, Deserialize, Serialize, Default, Clone, PartialEq, Eq)] Deserialize,
Serialize,
Default,
Clone,
PartialEq,
Eq,
WebSocketEvent,
Copy,
PartialOrd,
Ord,
)]
/// Officially Undocumented; /// Officially Undocumented;
/// See <https://unofficial-discord-docs.vercel.app/gateway/op13>; /// See <https://unofficial-discord-docs.vercel.app/gateway/op13>;
/// ///
@ -60,5 +79,3 @@ impl WebSocketEvent for CallDelete {}
pub struct CallSync { pub struct CallSync {
pub channel_id: Snowflake, pub channel_id: Snowflake,
} }
impl WebSocketEvent for CallSync {}

View File

@ -3,7 +3,6 @@
// file, You can obtain one at http://mozilla.org/MPL/2.0/. // file, You can obtain one at http://mozilla.org/MPL/2.0/.
use crate::types::events::WebSocketEvent; use crate::types::events::WebSocketEvent;
use crate::types::IntoShared;
use crate::types::{entities::Channel, JsonField, Snowflake, SourceUrlField}; use crate::types::{entities::Channel, JsonField, Snowflake, SourceUrlField};
use chorus_macros::{JsonField, SourceUrlField}; use chorus_macros::{JsonField, SourceUrlField};
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
@ -13,12 +12,15 @@ use serde::{Deserialize, Serialize};
use super::UpdateMessage; use super::UpdateMessage;
#[cfg(feature = "client")] #[cfg(feature = "client")]
use crate::gateway::Shared; use crate::types::Shared;
#[cfg(feature = "client")]
use crate::types::IntoShared;
#[cfg(feature = "client")] #[cfg(feature = "client")]
use crate::types::Guild; use crate::types::Guild;
#[derive(Debug, Default, Deserialize, Serialize)] #[derive(Debug, Default, Deserialize, Serialize, WebSocketEvent, Copy, PartialEq, Clone, Eq, Hash, PartialOrd, Ord)]
/// See <https://discord.com/developers/docs/topics/gateway-events#channel-pins-update> /// See <https://discord.com/developers/docs/topics/gateway-events#channel-pins-update>
pub struct ChannelPinsUpdate { pub struct ChannelPinsUpdate {
pub guild_id: Option<Snowflake>, pub guild_id: Option<Snowflake>,
@ -26,9 +28,7 @@ pub struct ChannelPinsUpdate {
pub last_pin_timestamp: Option<DateTime<Utc>>, pub last_pin_timestamp: Option<DateTime<Utc>>,
} }
impl WebSocketEvent for ChannelPinsUpdate {} #[derive(Debug, Default, Deserialize, Serialize, Clone, JsonField, SourceUrlField, WebSocketEvent)]
#[derive(Debug, Default, Deserialize, Serialize, Clone, JsonField, SourceUrlField)]
/// See <https://discord.com/developers/docs/topics/gateway-events#channel-create> /// See <https://discord.com/developers/docs/topics/gateway-events#channel-create>
pub struct ChannelCreate { pub struct ChannelCreate {
#[serde(flatten)] #[serde(flatten)]
@ -39,8 +39,6 @@ pub struct ChannelCreate {
pub source_url: String, pub source_url: String,
} }
impl WebSocketEvent for ChannelCreate {}
#[cfg(feature = "client")] #[cfg(feature = "client")]
impl UpdateMessage<Guild> for ChannelCreate { impl UpdateMessage<Guild> for ChannelCreate {
#[cfg(not(tarpaulin_include))] #[cfg(not(tarpaulin_include))]
@ -51,15 +49,11 @@ impl UpdateMessage<Guild> for ChannelCreate {
fn update(&mut self, object_to_update: Shared<Guild>) { fn update(&mut self, object_to_update: Shared<Guild>) {
let mut write = object_to_update.write().unwrap(); let mut write = object_to_update.write().unwrap();
let update = self.channel.clone().into_shared(); let update = self.channel.clone().into_shared();
if write.channels.is_some() { write.channels.push(update);
write.channels.as_mut().unwrap().push(update);
} else {
write.channels = Some(Vec::from([update]));
}
} }
} }
#[derive(Debug, Default, Deserialize, Serialize, Clone, JsonField, SourceUrlField)] #[derive(Debug, Default, Deserialize, Serialize, Clone, JsonField, SourceUrlField, WebSocketEvent)]
/// See <https://discord.com/developers/docs/topics/gateway-events#channel-update> /// See <https://discord.com/developers/docs/topics/gateway-events#channel-update>
pub struct ChannelUpdate { pub struct ChannelUpdate {
#[serde(flatten)] #[serde(flatten)]
@ -70,8 +64,6 @@ pub struct ChannelUpdate {
pub source_url: String, pub source_url: String,
} }
impl WebSocketEvent for ChannelUpdate {}
#[cfg(feature = "client")] #[cfg(feature = "client")]
impl UpdateMessage<Channel> for ChannelUpdate { impl UpdateMessage<Channel> for ChannelUpdate {
fn update(&mut self, object_to_update: Shared<Channel>) { fn update(&mut self, object_to_update: Shared<Channel>) {
@ -85,7 +77,7 @@ impl UpdateMessage<Channel> for ChannelUpdate {
} }
} }
#[derive(Debug, Default, Deserialize, Serialize, Clone)] #[derive(Debug, Default, Deserialize, Serialize, Clone, WebSocketEvent)]
/// Officially undocumented. /// Officially undocumented.
/// Sends updates to client about a new message with its id /// Sends updates to client about a new message with its id
/// {"channel_unread_updates": [{"id": "816412869766938648", "last_message_id": "1085892012085104680"}} /// {"channel_unread_updates": [{"id": "816412869766938648", "last_message_id": "1085892012085104680"}}
@ -94,18 +86,16 @@ pub struct ChannelUnreadUpdate {
pub guild_id: Snowflake, pub guild_id: Snowflake,
} }
#[derive(Debug, Default, Deserialize, Serialize, Clone)] #[derive(Debug, Default, Deserialize, Serialize, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
/// Contains very few fields from [Channel] /// Contains very few fields from [Channel]
/// See also [ChannelUnreadUpdate] /// See also [ChannelUnreadUpdate]
pub struct ChannelUnreadUpdateObject { pub struct ChannelUnreadUpdateObject {
pub id: Snowflake, pub id: Snowflake,
pub last_message_id: Snowflake, pub last_message_id: Snowflake,
pub last_pin_timestamp: Option<String>, pub last_pin_timestamp: Option<DateTime<Utc>>,
} }
impl WebSocketEvent for ChannelUnreadUpdate {} #[derive(Debug, Default, Deserialize, Serialize, Clone, JsonField, SourceUrlField, WebSocketEvent)]
#[derive(Debug, Default, Deserialize, Serialize, Clone, JsonField, SourceUrlField)]
/// See <https://discord.com/developers/docs/topics/gateway-events#channel-delete> /// See <https://discord.com/developers/docs/topics/gateway-events#channel-delete>
pub struct ChannelDelete { pub struct ChannelDelete {
#[serde(flatten)] #[serde(flatten)]
@ -128,16 +118,15 @@ impl UpdateMessage<Guild> for ChannelDelete {
return; return;
} }
let mut write = object_to_update.write().unwrap(); let mut write = object_to_update.write().unwrap();
if write.channels.is_none() { if write.channels.is_empty() {
return; return;
} }
for (iteration, item) in (0_u32..).zip(write.channels.as_mut().unwrap().iter()) { for (iteration, item) in (0_u32..).zip(write.channels.iter()) {
if item.read().unwrap().id == self.id().unwrap() { if item.read().unwrap().id == self.id().unwrap() {
write.channels.as_mut().unwrap().remove(iteration as usize); write.channels.remove(iteration as usize);
return; return;
} }
} }
} }
} }
impl WebSocketEvent for ChannelDelete {}

View File

@ -9,8 +9,8 @@ use serde::{Deserialize, Serialize};
use crate::types::entities::{Guild, PublicUser, UnavailableGuild}; use crate::types::entities::{Guild, PublicUser, UnavailableGuild};
use crate::types::events::WebSocketEvent; use crate::types::events::WebSocketEvent;
use crate::types::{ use crate::types::{
AuditLogEntry, Emoji, GuildMember, GuildScheduledEvent, IntoShared, JsonField, RoleObject, AuditLogEntry, Emoji, GuildMember, GuildScheduledEvent, JsonField, RoleObject, Snowflake,
Snowflake, SourceUrlField, Sticker, SourceUrlField, Sticker,
}; };
use super::PresenceUpdate; use super::PresenceUpdate;
@ -18,9 +18,21 @@ use super::PresenceUpdate;
#[cfg(feature = "client")] #[cfg(feature = "client")]
use super::UpdateMessage; use super::UpdateMessage;
#[cfg(feature = "client")] #[cfg(feature = "client")]
use crate::gateway::Shared; use crate::types::IntoShared;
#[cfg(feature = "client")]
use crate::types::Shared;
#[derive(Debug, Deserialize, Serialize, Default, Clone, SourceUrlField, JsonField)] #[derive(
Debug,
Deserialize,
Serialize,
Default,
Clone,
SourceUrlField,
JsonField,
WebSocketEvent,
PartialEq,
)]
/// See <https://discord.com/developers/docs/topics/gateway-events#guild-create>; /// See <https://discord.com/developers/docs/topics/gateway-events#guild-create>;
/// Received to give data about a guild; /// Received to give data about a guild;
// This one is particularly painful, it can be a Guild object with an extra field or an unavailable guild object // This one is particularly painful, it can be a Guild object with an extra field or an unavailable guild object
@ -47,7 +59,7 @@ impl UpdateMessage<Guild> for GuildCreate {
fn update(&mut self, _: Shared<Guild>) {} fn update(&mut self, _: Shared<Guild>) {}
} }
#[derive(Debug, Deserialize, Serialize, Clone)] #[derive(Debug, Deserialize, Serialize, Clone, PartialEq)]
#[serde(untagged)] #[serde(untagged)]
pub enum GuildCreateDataOption { pub enum GuildCreateDataOption {
UnavailableGuild(UnavailableGuild), UnavailableGuild(UnavailableGuild),
@ -60,9 +72,31 @@ impl Default for GuildCreateDataOption {
} }
} }
impl WebSocketEvent for GuildCreate {} #[derive(Debug, Clone, PartialEq)]
pub enum GuildEvents {
#[derive(Debug, Default, Deserialize, Serialize, Clone)] Create(GuildCreate),
Update(GuildUpdate),
Delete(GuildDelete),
BanAdd(GuildBanAdd),
BanRemove(GuildBanRemove),
EmojisUpdate(GuildEmojisUpdate),
StickersUpdate(GuildStickersUpdate),
IntegrationsUpdate(GuildIntegrationsUpdate),
MemberAdd(GuildMemberAdd),
MemberRemove(GuildMemberRemove),
MemberUpdate(GuildMemberUpdate),
MembersChunk(GuildMembersChunk),
RoleCreate(GuildRoleCreate),
RoleUpdate(GuildRoleUpdate),
RoleDelete(GuildRoleDelete),
ScheduledEventCreate(GuildScheduledEventCreate),
ScheduledEventUpdate(GuildScheduledEventUpdate),
ScheduledEventDelete(GuildScheduledEventDelete),
ScheduledEventUserAdd(GuildScheduledEventUserAdd),
ScheduledEventUserRemove(GuildScheduledEventUserRemove),
AuditLogEntryCreate(GuildAuditLogEntryCreate),
}
#[derive(Debug, Default, Deserialize, Serialize, Clone, WebSocketEvent, PartialEq)]
/// See <https://discord.com/developers/docs/topics/gateway-events#guild-ban-add-guild-ban-add-event-fields>; /// See <https://discord.com/developers/docs/topics/gateway-events#guild-ban-add-guild-ban-add-event-fields>;
/// Received to give info about a user being banned from a guild; /// Received to give info about a user being banned from a guild;
pub struct GuildBanAdd { pub struct GuildBanAdd {
@ -70,9 +104,7 @@ pub struct GuildBanAdd {
pub user: PublicUser, pub user: PublicUser,
} }
impl WebSocketEvent for GuildBanAdd {} #[derive(Debug, Default, Deserialize, Serialize, Clone, WebSocketEvent, PartialEq)]
#[derive(Debug, Default, Deserialize, Serialize, Clone)]
/// See <https://discord.com/developers/docs/topics/gateway-events#guild-ban-remove>; /// See <https://discord.com/developers/docs/topics/gateway-events#guild-ban-remove>;
/// Received to give info about a user being unbanned from a guild; /// Received to give info about a user being unbanned from a guild;
pub struct GuildBanRemove { pub struct GuildBanRemove {
@ -80,9 +112,17 @@ pub struct GuildBanRemove {
pub user: PublicUser, pub user: PublicUser,
} }
impl WebSocketEvent for GuildBanRemove {} #[derive(
Debug,
#[derive(Debug, Default, Deserialize, Serialize, Clone, SourceUrlField, JsonField)] Default,
Deserialize,
Serialize,
Clone,
SourceUrlField,
JsonField,
WebSocketEvent,
PartialEq,
)]
/// See <https://discord.com/developers/docs/topics/gateway-events#guild-update>; /// See <https://discord.com/developers/docs/topics/gateway-events#guild-update>;
/// Received to give info about a guild being updated; /// Received to give info about a guild being updated;
pub struct GuildUpdate { pub struct GuildUpdate {
@ -94,8 +134,6 @@ pub struct GuildUpdate {
pub json: String, pub json: String,
} }
impl WebSocketEvent for GuildUpdate {}
#[cfg(feature = "client")] #[cfg(feature = "client")]
impl UpdateMessage<Guild> for GuildUpdate { impl UpdateMessage<Guild> for GuildUpdate {
#[cfg(not(tarpaulin_include))] #[cfg(not(tarpaulin_include))]
@ -104,7 +142,17 @@ impl UpdateMessage<Guild> for GuildUpdate {
} }
} }
#[derive(Debug, Default, Deserialize, Serialize, Clone, SourceUrlField, JsonField)] #[derive(
Debug,
Default,
Deserialize,
Serialize,
Clone,
SourceUrlField,
JsonField,
WebSocketEvent,
PartialEq,
)]
/// See <https://discord.com/developers/docs/topics/gateway-events#guild-delete>; /// See <https://discord.com/developers/docs/topics/gateway-events#guild-delete>;
/// Received to tell the client about a guild being deleted; /// Received to tell the client about a guild being deleted;
pub struct GuildDelete { pub struct GuildDelete {
@ -125,9 +173,7 @@ impl UpdateMessage<Guild> for GuildDelete {
fn update(&mut self, _: Shared<Guild>) {} fn update(&mut self, _: Shared<Guild>) {}
} }
impl WebSocketEvent for GuildDelete {} #[derive(Debug, Default, Deserialize, Serialize, Clone, WebSocketEvent, PartialEq)]
#[derive(Debug, Default, Deserialize, Serialize, Clone)]
/// See <https://discord.com/developers/docs/topics/gateway-events#guild-audit-log-entry-create>; /// See <https://discord.com/developers/docs/topics/gateway-events#guild-audit-log-entry-create>;
/// Received to the client about an audit log entry being added; /// Received to the client about an audit log entry being added;
pub struct GuildAuditLogEntryCreate { pub struct GuildAuditLogEntryCreate {
@ -135,9 +181,7 @@ pub struct GuildAuditLogEntryCreate {
pub entry: AuditLogEntry, pub entry: AuditLogEntry,
} }
impl WebSocketEvent for GuildAuditLogEntryCreate {} #[derive(Debug, Default, Deserialize, Serialize, Clone, WebSocketEvent, PartialEq)]
#[derive(Debug, Default, Deserialize, Serialize, Clone)]
/// See <https://discord.com/developers/docs/topics/gateway-events#guild-emojis-update>; /// See <https://discord.com/developers/docs/topics/gateway-events#guild-emojis-update>;
/// Received to tell the client about a change to a guild's emoji list; /// Received to tell the client about a change to a guild's emoji list;
pub struct GuildEmojisUpdate { pub struct GuildEmojisUpdate {
@ -145,9 +189,7 @@ pub struct GuildEmojisUpdate {
pub emojis: Vec<Emoji>, pub emojis: Vec<Emoji>,
} }
impl WebSocketEvent for GuildEmojisUpdate {} #[derive(Debug, Default, Deserialize, Serialize, Clone, WebSocketEvent, PartialEq)]
#[derive(Debug, Default, Deserialize, Serialize, Clone)]
/// See <https://discord.com/developers/docs/topics/gateway-events#guild-stickers-update>; /// See <https://discord.com/developers/docs/topics/gateway-events#guild-stickers-update>;
/// Received to tell the client about a change to a guild's sticker list; /// Received to tell the client about a change to a guild's sticker list;
pub struct GuildStickersUpdate { pub struct GuildStickersUpdate {
@ -155,17 +197,13 @@ pub struct GuildStickersUpdate {
pub stickers: Vec<Sticker>, pub stickers: Vec<Sticker>,
} }
impl WebSocketEvent for GuildStickersUpdate {} #[derive(Debug, Default, Deserialize, Serialize, Clone, WebSocketEvent, PartialEq, Copy, Eq, Hash, PartialOrd, Ord)]
#[derive(Debug, Default, Deserialize, Serialize, Clone)]
/// See <https://discord.com/developers/docs/topics/gateway-events#guild-integrations-update> /// See <https://discord.com/developers/docs/topics/gateway-events#guild-integrations-update>
pub struct GuildIntegrationsUpdate { pub struct GuildIntegrationsUpdate {
pub guild_id: Snowflake, pub guild_id: Snowflake,
} }
impl WebSocketEvent for GuildIntegrationsUpdate {} #[derive(Debug, Default, Deserialize, Serialize, Clone, WebSocketEvent, PartialEq)]
#[derive(Debug, Default, Deserialize, Serialize, Clone)]
/// See <https://discord.com/developers/docs/topics/gateway-events#guild-member-add>; /// See <https://discord.com/developers/docs/topics/gateway-events#guild-member-add>;
/// Received to tell the client about a user joining a guild; /// Received to tell the client about a user joining a guild;
pub struct GuildMemberAdd { pub struct GuildMemberAdd {
@ -174,9 +212,7 @@ pub struct GuildMemberAdd {
pub guild_id: Snowflake, pub guild_id: Snowflake,
} }
impl WebSocketEvent for GuildMemberAdd {} #[derive(Debug, Default, Deserialize, Serialize, Clone, WebSocketEvent, PartialEq)]
#[derive(Debug, Default, Deserialize, Serialize, Clone)]
/// See <https://discord.com/developers/docs/topics/gateway-events#guild-member-remove>; /// See <https://discord.com/developers/docs/topics/gateway-events#guild-member-remove>;
/// Received to tell the client about a user leaving a guild; /// Received to tell the client about a user leaving a guild;
pub struct GuildMemberRemove { pub struct GuildMemberRemove {
@ -184,9 +220,7 @@ pub struct GuildMemberRemove {
pub user: PublicUser, pub user: PublicUser,
} }
impl WebSocketEvent for GuildMemberRemove {} #[derive(Debug, Default, Deserialize, Serialize, Clone, WebSocketEvent, PartialEq)]
#[derive(Debug, Default, Deserialize, Serialize, Clone)]
/// See <https://discord.com/developers/docs/topics/gateway-events#guild-member-update> /// See <https://discord.com/developers/docs/topics/gateway-events#guild-member-update>
pub struct GuildMemberUpdate { pub struct GuildMemberUpdate {
pub guild_id: Snowflake, pub guild_id: Snowflake,
@ -202,9 +236,7 @@ pub struct GuildMemberUpdate {
pub communication_disabled_until: Option<DateTime<Utc>>, pub communication_disabled_until: Option<DateTime<Utc>>,
} }
impl WebSocketEvent for GuildMemberUpdate {} #[derive(Debug, Default, Deserialize, Serialize, Clone, WebSocketEvent, PartialEq)]
#[derive(Debug, Default, Deserialize, Serialize, Clone)]
/// See <https://discord.com/developers/docs/topics/gateway-events#guild-members-chunk> /// See <https://discord.com/developers/docs/topics/gateway-events#guild-members-chunk>
pub struct GuildMembersChunk { pub struct GuildMembersChunk {
pub guild_id: Snowflake, pub guild_id: Snowflake,
@ -216,9 +248,17 @@ pub struct GuildMembersChunk {
pub nonce: Option<String>, pub nonce: Option<String>,
} }
impl WebSocketEvent for GuildMembersChunk {} #[derive(
Debug,
#[derive(Debug, Default, Deserialize, Serialize, Clone, JsonField, SourceUrlField)] Default,
Deserialize,
Serialize,
Clone,
JsonField,
SourceUrlField,
WebSocketEvent,
PartialEq,
)]
/// See <https://discord.com/developers/docs/topics/gateway-events#guild-role-create> /// See <https://discord.com/developers/docs/topics/gateway-events#guild-role-create>
pub struct GuildRoleCreate { pub struct GuildRoleCreate {
pub guild_id: Snowflake, pub guild_id: Snowflake,
@ -229,8 +269,6 @@ pub struct GuildRoleCreate {
pub source_url: String, pub source_url: String,
} }
impl WebSocketEvent for GuildRoleCreate {}
#[cfg(feature = "client")] #[cfg(feature = "client")]
impl UpdateMessage<Guild> for GuildRoleCreate { impl UpdateMessage<Guild> for GuildRoleCreate {
#[cfg(not(tarpaulin_include))] #[cfg(not(tarpaulin_include))]
@ -240,19 +278,21 @@ impl UpdateMessage<Guild> for GuildRoleCreate {
fn update(&mut self, object_to_update: Shared<Guild>) { fn update(&mut self, object_to_update: Shared<Guild>) {
let mut object_to_update = object_to_update.write().unwrap(); let mut object_to_update = object_to_update.write().unwrap();
if object_to_update.roles.is_some() { object_to_update.roles.push(self.role.clone().into_shared());
object_to_update
.roles
.as_mut()
.unwrap()
.push(self.role.clone().into_shared());
} else {
object_to_update.roles = Some(Vec::from([self.role.clone().into_shared()]));
}
} }
} }
#[derive(Debug, Default, Deserialize, Serialize, Clone, JsonField, SourceUrlField)] #[derive(
Debug,
Default,
Deserialize,
Serialize,
Clone,
JsonField,
SourceUrlField,
WebSocketEvent,
PartialEq,
)]
/// See <https://discord.com/developers/docs/topics/gateway-events#guild-role-update> /// See <https://discord.com/developers/docs/topics/gateway-events#guild-role-update>
pub struct GuildRoleUpdate { pub struct GuildRoleUpdate {
pub guild_id: Snowflake, pub guild_id: Snowflake,
@ -263,8 +303,6 @@ pub struct GuildRoleUpdate {
pub source_url: String, pub source_url: String,
} }
impl WebSocketEvent for GuildRoleUpdate {}
#[cfg(feature = "client")] #[cfg(feature = "client")]
impl UpdateMessage<RoleObject> for GuildRoleUpdate { impl UpdateMessage<RoleObject> for GuildRoleUpdate {
#[cfg(not(tarpaulin_include))] #[cfg(not(tarpaulin_include))]
@ -278,43 +316,35 @@ impl UpdateMessage<RoleObject> for GuildRoleUpdate {
} }
} }
#[derive(Debug, Default, Deserialize, Serialize, Clone)] #[derive(Debug, Default, Deserialize, Serialize, Clone, WebSocketEvent, PartialEq, Eq, PartialOrd, Ord, Hash, Copy)]
/// See <https://discord.com/developers/docs/topics/gateway-events#guild-role-delete> /// See <https://discord.com/developers/docs/topics/gateway-events#guild-role-delete>
pub struct GuildRoleDelete { pub struct GuildRoleDelete {
pub guild_id: Snowflake, pub guild_id: Snowflake,
pub role_id: Snowflake, pub role_id: Snowflake,
} }
impl WebSocketEvent for GuildRoleDelete {} #[derive(Debug, Default, Deserialize, Serialize, Clone, WebSocketEvent, PartialEq)]
#[derive(Debug, Default, Deserialize, Serialize, Clone)]
/// See <https://discord.com/developers/docs/topics/gateway-events#guild-scheduled-event-create> /// See <https://discord.com/developers/docs/topics/gateway-events#guild-scheduled-event-create>
pub struct GuildScheduledEventCreate { pub struct GuildScheduledEventCreate {
#[serde(flatten)] #[serde(flatten)]
pub event: GuildScheduledEvent, pub event: GuildScheduledEvent,
} }
impl WebSocketEvent for GuildScheduledEventCreate {} #[derive(Debug, Default, Deserialize, Serialize, Clone, WebSocketEvent, PartialEq)]
#[derive(Debug, Default, Deserialize, Serialize, Clone)]
/// See <https://discord.com/developers/docs/topics/gateway-events#guild-scheduled-event-update> /// See <https://discord.com/developers/docs/topics/gateway-events#guild-scheduled-event-update>
pub struct GuildScheduledEventUpdate { pub struct GuildScheduledEventUpdate {
#[serde(flatten)] #[serde(flatten)]
pub event: GuildScheduledEvent, pub event: GuildScheduledEvent,
} }
impl WebSocketEvent for GuildScheduledEventUpdate {} #[derive(Debug, Default, Deserialize, Serialize, Clone, WebSocketEvent, PartialEq)]
#[derive(Debug, Default, Deserialize, Serialize, Clone)]
/// See <https://discord.com/developers/docs/topics/gateway-events#guild-scheduled-event-delete> /// See <https://discord.com/developers/docs/topics/gateway-events#guild-scheduled-event-delete>
pub struct GuildScheduledEventDelete { pub struct GuildScheduledEventDelete {
#[serde(flatten)] #[serde(flatten)]
pub event: GuildScheduledEvent, pub event: GuildScheduledEvent,
} }
impl WebSocketEvent for GuildScheduledEventDelete {} #[derive(Debug, Default, Deserialize, Serialize, Clone, WebSocketEvent, PartialEq, Copy, Eq, Hash, PartialOrd, Ord)]
#[derive(Debug, Default, Deserialize, Serialize, Clone)]
/// See <https://discord.com/developers/docs/topics/gateway-events#guild-scheduled-event-user-add> /// See <https://discord.com/developers/docs/topics/gateway-events#guild-scheduled-event-user-add>
pub struct GuildScheduledEventUserAdd { pub struct GuildScheduledEventUserAdd {
pub guild_scheduled_event_id: Snowflake, pub guild_scheduled_event_id: Snowflake,
@ -322,14 +352,10 @@ pub struct GuildScheduledEventUserAdd {
pub guild_id: Snowflake, pub guild_id: Snowflake,
} }
impl WebSocketEvent for GuildScheduledEventUserAdd {} #[derive(Debug, Default, Deserialize, Serialize, Clone, WebSocketEvent, PartialEq, Copy, Eq, Hash, PartialOrd, Ord)]
#[derive(Debug, Default, Deserialize, Serialize, Clone)]
/// See <https://discord.com/developers/docs/topics/gateway-events#guild-scheduled-event-user-remove> /// See <https://discord.com/developers/docs/topics/gateway-events#guild-scheduled-event-user-remove>
pub struct GuildScheduledEventUserRemove { pub struct GuildScheduledEventUserRemove {
pub guild_scheduled_event_id: Snowflake, pub guild_scheduled_event_id: Snowflake,
pub user_id: Snowflake, pub user_id: Snowflake,
pub guild_id: Snowflake, pub guild_id: Snowflake,
} }
impl WebSocketEvent for GuildScheduledEventUserRemove {}

View File

@ -5,17 +5,14 @@
use crate::types::events::WebSocketEvent; use crate::types::events::WebSocketEvent;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
#[derive(Debug, Default, Deserialize, Serialize)] #[derive(Debug, Default, Deserialize, Serialize, WebSocketEvent, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct GatewayHeartbeat { pub struct GatewayHeartbeat {
pub op: u8, pub op: u8,
pub d: Option<u64>, pub d: Option<u64>,
} }
impl WebSocketEvent for GatewayHeartbeat {} #[derive(Debug, Default, Deserialize, Serialize, Clone, WebSocketEvent, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[derive(Debug, Default, Deserialize, Serialize, Clone)]
pub struct GatewayHeartbeatAck { pub struct GatewayHeartbeatAck {
pub op: i32, pub op: i32,
} }
impl WebSocketEvent for GatewayHeartbeatAck {}

View File

@ -3,22 +3,20 @@
// file, You can obtain one at http://mozilla.org/MPL/2.0/. // file, You can obtain one at http://mozilla.org/MPL/2.0/.
use crate::types::WebSocketEvent; use crate::types::WebSocketEvent;
use chorus_macros::WebSocketEvent;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
/// Received on gateway init, tells the client how often to send heartbeats; /// Received on gateway init, tells the client how often to send heartbeats;
#[derive(Debug, Default, Deserialize, Serialize, Clone, PartialEq, Eq)] #[derive(Debug, Default, Deserialize, Serialize, Clone, PartialEq, Eq, WebSocketEvent, Copy, Hash, PartialOrd, Ord)]
pub struct GatewayHello { pub struct GatewayHello {
pub op: i32, pub op: i32,
pub d: HelloData, pub d: HelloData,
} }
impl WebSocketEvent for GatewayHello {} #[derive(Debug, Default, Deserialize, Serialize, Clone, PartialEq, Eq, Copy, WebSocketEvent, Hash, PartialOrd, Ord)]
#[derive(Debug, Default, Deserialize, Serialize, Clone, PartialEq, Eq, Copy)]
/// Contains info on how often the client should send heartbeats to the server; /// Contains info on how often the client should send heartbeats to the server;
pub struct HelloData { pub struct HelloData {
/// How often a client should send heartbeats, in milliseconds /// How often a client should send heartbeats, in milliseconds
pub heartbeat_interval: u64, pub heartbeat_interval: u64,
} }
impl WebSocketEvent for HelloData {}

View File

@ -6,7 +6,7 @@ use crate::types::events::{PresenceUpdate, WebSocketEvent};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_with::serde_as; use serde_with::serde_as;
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] #[derive(Debug, Deserialize, Serialize, Clone, PartialEq, WebSocketEvent)]
pub struct GatewayIdentifyPayload { pub struct GatewayIdentifyPayload {
pub token: String, pub token: String,
pub properties: GatewayIdentifyConnectionProps, pub properties: GatewayIdentifyConnectionProps,
@ -70,9 +70,7 @@ impl GatewayIdentifyPayload {
} }
} }
impl WebSocketEvent for GatewayIdentifyPayload {} #[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq, WebSocketEvent)]
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)]
#[serde_as] #[serde_as]
pub struct GatewayIdentifyConnectionProps { pub struct GatewayIdentifyConnectionProps {
/// Almost always sent /// Almost always sent

View File

@ -5,8 +5,9 @@
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::types::{Integration, Snowflake, WebSocketEvent}; use crate::types::{Integration, Snowflake, WebSocketEvent};
use chorus_macros::WebSocketEvent;
#[derive(Debug, Default, Deserialize, Serialize, Clone)] #[derive(Debug, Default, Deserialize, Serialize, Clone, WebSocketEvent)]
/// See <https://discord.com/developers/docs/topics/gateway-events#integration-create> /// See <https://discord.com/developers/docs/topics/gateway-events#integration-create>
pub struct IntegrationCreate { pub struct IntegrationCreate {
#[serde(flatten)] #[serde(flatten)]
@ -14,9 +15,7 @@ pub struct IntegrationCreate {
pub guild_id: Snowflake, pub guild_id: Snowflake,
} }
impl WebSocketEvent for IntegrationCreate {} #[derive(Debug, Default, Deserialize, Serialize, Clone, WebSocketEvent)]
#[derive(Debug, Default, Deserialize, Serialize, Clone)]
/// See <https://discord.com/developers/docs/topics/gateway-events#integration-update> /// See <https://discord.com/developers/docs/topics/gateway-events#integration-update>
pub struct IntegrationUpdate { pub struct IntegrationUpdate {
#[serde(flatten)] #[serde(flatten)]
@ -24,9 +23,7 @@ pub struct IntegrationUpdate {
pub guild_id: Snowflake, pub guild_id: Snowflake,
} }
impl WebSocketEvent for IntegrationUpdate {} #[derive(Debug, Default, Deserialize, Serialize, Clone, WebSocketEvent, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[derive(Debug, Default, Deserialize, Serialize, Clone)]
/// See <https://discord.com/developers/docs/topics/gateway-events#integration-delete> /// See <https://discord.com/developers/docs/topics/gateway-events#integration-delete>
pub struct IntegrationDelete { pub struct IntegrationDelete {
pub id: Snowflake, pub id: Snowflake,
@ -34,4 +31,3 @@ pub struct IntegrationDelete {
pub application_id: Option<Snowflake>, pub application_id: Option<Snowflake>,
} }
impl WebSocketEvent for IntegrationDelete {}

View File

@ -5,12 +5,12 @@
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::types::{Interaction, WebSocketEvent}; use crate::types::{Interaction, WebSocketEvent};
use chorus_macros::WebSocketEvent;
#[derive(Debug, Default, Deserialize, Serialize, Clone)] #[derive(Debug, Default, Deserialize, Serialize, Clone, WebSocketEvent)]
/// See <https://discord.com/developers/docs/topics/gateway-events#interaction-create> /// See <https://discord.com/developers/docs/topics/gateway-events#interaction-create>
pub struct InteractionCreate { pub struct InteractionCreate {
#[serde(flatten)] #[serde(flatten)]
pub interaction: Interaction, pub interaction: Interaction,
} }
impl WebSocketEvent for InteractionCreate {}

View File

@ -2,7 +2,7 @@ use serde::{Deserialize, Serialize};
use super::WebSocketEvent; use super::WebSocketEvent;
#[derive(Debug, Deserialize, Serialize, Default, Clone)] #[derive(Debug, Deserialize, Serialize, Default, Clone, WebSocketEvent, PartialEq, Eq, Hash, PartialOrd, Ord, Copy)]
/// Your session is now invalid. /// Your session is now invalid.
/// ///
/// Either reauthenticate and reidentify or resume if possible. /// Either reauthenticate and reidentify or resume if possible.
@ -14,4 +14,3 @@ pub struct GatewayInvalidSession {
pub resumable: bool, pub resumable: bool,
} }
impl WebSocketEvent for GatewayInvalidSession {}

View File

@ -5,17 +5,16 @@
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::types::{GuildInvite, Snowflake, WebSocketEvent}; use crate::types::{GuildInvite, Snowflake, WebSocketEvent};
use chorus_macros::WebSocketEvent;
#[derive(Debug, Default, Deserialize, Serialize, Clone)] #[derive(Debug, Default, Deserialize, Serialize, Clone, WebSocketEvent)]
/// See <https://discord.com/developers/docs/topics/gateway-events#invite-create> /// See <https://discord.com/developers/docs/topics/gateway-events#invite-create>
pub struct InviteCreate { pub struct InviteCreate {
#[serde(flatten)] #[serde(flatten)]
pub invite: GuildInvite, pub invite: GuildInvite,
} }
impl WebSocketEvent for InviteCreate {} #[derive(Debug, Default, Deserialize, Serialize, Clone, WebSocketEvent)]
#[derive(Debug, Default, Deserialize, Serialize, Clone)]
/// See <https://discord.com/developers/docs/topics/gateway-events#invite-delete> /// See <https://discord.com/developers/docs/topics/gateway-events#invite-delete>
pub struct InviteDelete { pub struct InviteDelete {
pub channel_id: Snowflake, pub channel_id: Snowflake,
@ -23,4 +22,3 @@ pub struct InviteDelete {
pub code: String, pub code: String,
} }
impl WebSocketEvent for InviteDelete {}

View File

@ -7,10 +7,9 @@ use std::collections::HashMap;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::types::Snowflake; use crate::types::Snowflake;
use super::WebSocketEvent; use super::WebSocketEvent;
#[derive(Debug, Deserialize, Serialize, Default, Clone)] #[derive(Debug, Deserialize, Serialize, Default, Clone, WebSocketEvent)]
/// Officially Undocumented /// Officially Undocumented
/// ///
/// Sent to the server to signify lazy loading of a guild; /// Sent to the server to signify lazy loading of a guild;
@ -31,4 +30,3 @@ pub struct LazyRequest {
pub channels: Option<HashMap<String, Vec<Vec<u64>>>>, pub channels: Option<HashMap<String, Vec<Vec<u64>>>>,
} }
impl WebSocketEvent for LazyRequest {}

View File

@ -6,12 +6,12 @@ use serde::{Deserialize, Serialize};
use crate::types::{ use crate::types::{
entities::{Emoji, GuildMember, Message, PublicUser}, entities::{Emoji, GuildMember, Message, PublicUser},
Snowflake, Snowflake, WebSocketEvent,
}; };
use super::WebSocketEvent; use chorus_macros::WebSocketEvent;
#[derive(Debug, Deserialize, Serialize, Default, Clone)] #[derive(Debug, Deserialize, Serialize, Default, Clone, WebSocketEvent)]
/// # Reference /// # Reference
/// See <https://discord.com/developers/docs/topics/gateway-events#typing-start> /// See <https://discord.com/developers/docs/topics/gateway-events#typing-start>
pub struct TypingStartEvent { pub struct TypingStartEvent {
@ -22,9 +22,7 @@ pub struct TypingStartEvent {
pub member: Option<GuildMember>, pub member: Option<GuildMember>,
} }
impl WebSocketEvent for TypingStartEvent {} #[derive(Debug, Serialize, Deserialize, Default, Clone, WebSocketEvent)]
#[derive(Debug, Serialize, Deserialize, Default, Clone)]
/// See <https://discord.com/developers/docs/topics/gateway-events#message-create> /// See <https://discord.com/developers/docs/topics/gateway-events#message-create>
pub struct MessageCreate { pub struct MessageCreate {
#[serde(flatten)] #[serde(flatten)]
@ -34,7 +32,7 @@ pub struct MessageCreate {
pub mentions: Option<Vec<MessageCreateUser>>, pub mentions: Option<Vec<MessageCreateUser>>,
} }
#[derive(Debug, Serialize, Deserialize, Default, Clone)] #[derive(Debug, Serialize, Deserialize, Default, Clone, WebSocketEvent)]
/// See <https://discord.com/developers/docs/topics/gateway-events#message-create-message-create-extra-fields> /// See <https://discord.com/developers/docs/topics/gateway-events#message-create-message-create-extra-fields>
pub struct MessageCreateUser { pub struct MessageCreateUser {
#[serde(flatten)] #[serde(flatten)]
@ -42,9 +40,7 @@ pub struct MessageCreateUser {
pub member: Option<GuildMember>, pub member: Option<GuildMember>,
} }
impl WebSocketEvent for MessageCreate {} #[derive(Debug, Serialize, Deserialize, Default, Clone, WebSocketEvent)]
#[derive(Debug, Serialize, Deserialize, Default, Clone)]
/// # Reference /// # Reference
/// See <https://discord.com/developers/docs/topics/gateway-events#message-update> /// See <https://discord.com/developers/docs/topics/gateway-events#message-update>
pub struct MessageUpdate { pub struct MessageUpdate {
@ -55,9 +51,20 @@ pub struct MessageUpdate {
pub mentions: Option<Vec<MessageCreateUser>>, pub mentions: Option<Vec<MessageCreateUser>>,
} }
impl WebSocketEvent for MessageUpdate {} #[derive(
Debug,
#[derive(Debug, Serialize, Deserialize, Default, Clone)] Serialize,
Deserialize,
Default,
Clone,
WebSocketEvent,
Copy,
PartialEq,
Eq,
Hash,
PartialOrd,
Ord,
)]
/// # Reference /// # Reference
/// See <https://discord.com/developers/docs/topics/gateway-events#message-delete> /// See <https://discord.com/developers/docs/topics/gateway-events#message-delete>
pub struct MessageDelete { pub struct MessageDelete {
@ -66,9 +73,19 @@ pub struct MessageDelete {
pub guild_id: Option<Snowflake>, pub guild_id: Option<Snowflake>,
} }
impl WebSocketEvent for MessageDelete {} #[derive(
Debug,
#[derive(Debug, Serialize, Deserialize, Default, Clone)] Serialize,
Deserialize,
Default,
Clone,
WebSocketEvent,
PartialEq,
Eq,
Hash,
PartialOrd,
Ord,
)]
/// # Reference /// # Reference
/// See <https://discord.com/developers/docs/topics/gateway-events#message-delete-bulk> /// See <https://discord.com/developers/docs/topics/gateway-events#message-delete-bulk>
pub struct MessageDeleteBulk { pub struct MessageDeleteBulk {
@ -77,9 +94,7 @@ pub struct MessageDeleteBulk {
pub guild_id: Option<Snowflake>, pub guild_id: Option<Snowflake>,
} }
impl WebSocketEvent for MessageDeleteBulk {} #[derive(Debug, Serialize, Deserialize, Default, Clone, WebSocketEvent)]
#[derive(Debug, Serialize, Deserialize, Default, Clone)]
/// # Reference /// # Reference
/// See <https://discord.com/developers/docs/topics/gateway-events#message-reaction-add> /// See <https://discord.com/developers/docs/topics/gateway-events#message-reaction-add>
pub struct MessageReactionAdd { pub struct MessageReactionAdd {
@ -91,9 +106,7 @@ pub struct MessageReactionAdd {
pub emoji: Emoji, pub emoji: Emoji,
} }
impl WebSocketEvent for MessageReactionAdd {} #[derive(Debug, Serialize, Deserialize, Default, Clone, WebSocketEvent)]
#[derive(Debug, Serialize, Deserialize, Default, Clone)]
/// # Reference /// # Reference
/// See <https://discord.com/developers/docs/topics/gateway-events#message-reaction-remove> /// See <https://discord.com/developers/docs/topics/gateway-events#message-reaction-remove>
pub struct MessageReactionRemove { pub struct MessageReactionRemove {
@ -104,9 +117,20 @@ pub struct MessageReactionRemove {
pub emoji: Emoji, pub emoji: Emoji,
} }
impl WebSocketEvent for MessageReactionRemove {} #[derive(
Debug,
#[derive(Debug, Serialize, Deserialize, Default, Clone)] Serialize,
Deserialize,
Default,
Clone,
WebSocketEvent,
Copy,
PartialEq,
Eq,
Hash,
PartialOrd,
Ord,
)]
/// # Reference /// # Reference
/// See <https://discord.com/developers/docs/topics/gateway-events#message-reaction-remove-all> /// See <https://discord.com/developers/docs/topics/gateway-events#message-reaction-remove-all>
pub struct MessageReactionRemoveAll { pub struct MessageReactionRemoveAll {
@ -115,9 +139,7 @@ pub struct MessageReactionRemoveAll {
pub guild_id: Option<Snowflake>, pub guild_id: Option<Snowflake>,
} }
impl WebSocketEvent for MessageReactionRemoveAll {} #[derive(Debug, Serialize, Deserialize, Default, Clone, WebSocketEvent)]
#[derive(Debug, Serialize, Deserialize, Default, Clone)]
/// # Reference /// # Reference
/// See <https://discord.com/developers/docs/topics/gateway-events#message-reaction-remove-emoji> /// See <https://discord.com/developers/docs/topics/gateway-events#message-reaction-remove-emoji>
pub struct MessageReactionRemoveEmoji { pub struct MessageReactionRemoveEmoji {
@ -127,9 +149,7 @@ pub struct MessageReactionRemoveEmoji {
pub emoji: Emoji, pub emoji: Emoji,
} }
impl WebSocketEvent for MessageReactionRemoveEmoji {} #[derive(Debug, Deserialize, Serialize, Default, Clone, WebSocketEvent)]
#[derive(Debug, Deserialize, Serialize, Default, Clone)]
/// Officially Undocumented /// Officially Undocumented
/// ///
/// Not documented anywhere unofficially /// Not documented anywhere unofficially
@ -139,8 +159,8 @@ impl WebSocketEvent for MessageReactionRemoveEmoji {}
/// ///
/// {"t":"MESSAGE_ACK","s":3,"op":0,"d":{"version":52,"message_id":"1107236673638633472","last_viewed":null,"flags":null,"channel_id":"967363950217936897"}} /// {"t":"MESSAGE_ACK","s":3,"op":0,"d":{"version":52,"message_id":"1107236673638633472","last_viewed":null,"flags":null,"channel_id":"967363950217936897"}}
pub struct MessageACK { pub struct MessageACK {
/// ? // No ideas. See 206933
pub version: u16, pub version: u32,
pub message_id: Snowflake, pub message_id: Snowflake,
/// This is an integer??? /// This is an integer???
/// Not even unix, see '3070'??? /// Not even unix, see '3070'???
@ -149,5 +169,3 @@ pub struct MessageACK {
pub flags: Option<serde_json::Value>, pub flags: Option<serde_json::Value>,
pub channel_id: Snowflake, pub channel_id: Snowflake,
} }
impl WebSocketEvent for MessageACK {}

View File

@ -33,6 +33,8 @@ pub use voice::*;
pub use voice_gateway::*; pub use voice_gateway::*;
pub use webhooks::*; pub use webhooks::*;
use chorus_macros::WebSocketEvent;
#[cfg(feature = "client")] #[cfg(feature = "client")]
use super::Snowflake; use super::Snowflake;
@ -46,7 +48,7 @@ use serde_json::{from_str, from_value, to_value, Value};
use std::collections::HashMap; use std::collections::HashMap;
#[cfg(feature = "client")] #[cfg(feature = "client")]
use crate::gateway::Shared; use crate::types::Shared;
use std::fmt::Debug; use std::fmt::Debug;
#[cfg(feature = "client")] #[cfg(feature = "client")]
@ -84,7 +86,7 @@ mod voice_gateway;
pub trait WebSocketEvent: Send + Sync + Debug {} pub trait WebSocketEvent: Send + Sync + Debug {}
#[derive(Debug, Default, Serialize, Clone)] #[derive(Debug, Default, Serialize, Clone, WebSocketEvent)]
/// The payload used for sending events to the gateway /// The payload used for sending events to the gateway
/// ///
/// Similar to [GatewayReceivePayload], except we send a [serde_json::value::Value] for d whilst we receive a [serde_json::value::RawValue] /// Similar to [GatewayReceivePayload], except we send a [serde_json::value::Value] for d whilst we receive a [serde_json::value::RawValue]
@ -102,8 +104,6 @@ pub struct GatewaySendPayload {
pub sequence_number: Option<u64>, pub sequence_number: Option<u64>,
} }
impl WebSocketEvent for GatewaySendPayload {}
#[derive(Debug, Default, Deserialize, Clone)] #[derive(Debug, Default, Deserialize, Clone)]
/// The payload used for receiving events from the gateway /// The payload used for receiving events from the gateway
pub struct GatewayReceivePayload<'a> { pub struct GatewayReceivePayload<'a> {

View File

@ -7,15 +7,17 @@ use serde::{Deserialize, Serialize};
use super::{ChannelUnreadUpdateObject, WebSocketEvent}; use super::{ChannelUnreadUpdateObject, WebSocketEvent};
use crate::types::{GuildMember, Snowflake, VoiceState}; use crate::types::{GuildMember, Snowflake, VoiceState};
#[derive(Debug, Deserialize, Serialize, Default)] #[derive(Debug, Deserialize, Serialize, Default, WebSocketEvent)]
/// Officially Undocumented /// Officially Undocumented
/// ///
/// Seems to be passively set to update the client on guild details (though, why not just send the update events?) /// Seems to be passively set to update the client on guild details (though, why not just send the update events?)
pub struct PassiveUpdateV1 { pub struct PassiveUpdateV1 {
#[serde(default)]
pub voice_states: Vec<VoiceState>, pub voice_states: Vec<VoiceState>,
pub members: Option<Vec<GuildMember>>, #[serde(default)]
pub members: Vec<GuildMember>,
pub guild_id: Snowflake, pub guild_id: Snowflake,
#[serde(default)]
pub channels: Vec<ChannelUnreadUpdateObject>, pub channels: Vec<ChannelUnreadUpdateObject>,
} }
impl WebSocketEvent for PassiveUpdateV1 {}

View File

@ -6,7 +6,7 @@ use crate::types::{events::WebSocketEvent, UserStatus};
use crate::types::{Activity, ClientStatusObject, PublicUser, Snowflake}; use crate::types::{Activity, ClientStatusObject, PublicUser, Snowflake};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
#[derive(Debug, Deserialize, Serialize, Default, Clone)] #[derive(Debug, Deserialize, Serialize, Default, Clone, WebSocketEvent)]
/// Sent by the client to update its status and presence; /// Sent by the client to update its status and presence;
/// See <https://discord.com/developers/docs/topics/gateway-events#update-presence> /// See <https://discord.com/developers/docs/topics/gateway-events#update-presence>
pub struct UpdatePresence { pub struct UpdatePresence {
@ -18,16 +18,18 @@ pub struct UpdatePresence {
pub afk: bool, pub afk: bool,
} }
#[derive(Debug, Deserialize, Serialize, Default, Clone, PartialEq)] #[derive(Debug, Deserialize, Serialize, Default, Clone, PartialEq, WebSocketEvent)]
/// Received to tell the client that a user updated their presence / status /// Received to tell the client that a user updated their presence / status
///
/// See <https://discord.com/developers/docs/topics/gateway-events#presence-update-presence-update-event-fields> /// See <https://discord.com/developers/docs/topics/gateway-events#presence-update-presence-update-event-fields>
/// (Same structure as <https://docs.discord.sex/resources/presence#presence-object>)
pub struct PresenceUpdate { pub struct PresenceUpdate {
pub user: PublicUser, pub user: PublicUser,
#[serde(default)] #[serde(default)]
pub guild_id: Option<Snowflake>, pub guild_id: Option<Snowflake>,
pub status: UserStatus, pub status: UserStatus,
#[serde(default)]
pub activities: Vec<Activity>, pub activities: Vec<Activity>,
pub client_status: ClientStatusObject, pub client_status: ClientStatusObject,
} }
impl WebSocketEvent for PresenceUpdate {}

View File

@ -6,13 +6,14 @@ use serde::{Deserialize, Serialize};
use crate::types::entities::{Guild, User}; use crate::types::entities::{Guild, User};
use crate::types::events::{Session, WebSocketEvent}; use crate::types::events::{Session, WebSocketEvent};
use crate::types::interfaces::ClientStatusObject; use crate::types::{Activity, Channel, ClientStatusObject, GuildMember, PresenceUpdate, Snowflake, VoiceState};
use crate::types::{Activity, GuildMember, PresenceUpdate, VoiceState};
#[derive(Debug, Deserialize, Serialize, Default, Clone)] #[derive(Debug, Deserialize, Serialize, Default, Clone, WebSocketEvent)]
/// 1/2 half documented; /// 1/2 officially documented;
/// Received after identifying, provides initial user info; /// Received after identifying, provides initial user info;
/// See <https://discord.com/developers/docs/topics/gateway-events#ready;> ///
/// See <https://docs.discord.sex/topics/gateway-events#ready> and <https://discord.com/developers/docs/topics/gateway-events#ready>
// TODO: There are a LOT of fields missing here
pub struct GatewayReady { pub struct GatewayReady {
pub analytics_token: Option<String>, pub analytics_token: Option<String>,
pub auth_session_id_hash: Option<String>, pub auth_session_id_hash: Option<String>,
@ -30,42 +31,49 @@ pub struct GatewayReady {
pub shard: Option<(u64, u64)>, pub shard: Option<(u64, u64)>,
} }
impl WebSocketEvent for GatewayReady {} #[derive(Debug, Deserialize, Serialize, Default, Clone, WebSocketEvent)]
#[derive(Debug, Deserialize, Serialize, Default, Clone)]
/// Officially Undocumented; /// Officially Undocumented;
/// Sent after the READY event when a client is a user, seems to somehow add onto the ready event; /// Sent after the READY event when a client is a user,
/// seems to somehow add onto the ready event;
///
/// See <https://docs.discord.sex/topics/gateway-events#ready-supplemental>
pub struct GatewayReadySupplemental { pub struct GatewayReadySupplemental {
/// The presences of the user's relationships and guild presences sent at startup
pub merged_presences: MergedPresences, pub merged_presences: MergedPresences,
pub merged_members: Vec<Vec<GuildMember>>, pub merged_members: Vec<Vec<GuildMember>>,
// ? pub lazy_private_channels: Vec<Channel>,
pub lazy_private_channels: Vec<serde_json::Value>,
pub guilds: Vec<SupplementalGuild>, pub guilds: Vec<SupplementalGuild>,
// ? pomelo // "Upcoming changes that the client should disclose to the user" (discord.sex)
pub disclose: Vec<String>, pub disclose: Vec<String>,
} }
impl WebSocketEvent for GatewayReadySupplemental {}
#[derive(Debug, Deserialize, Serialize, Default, Clone)] #[derive(Debug, Deserialize, Serialize, Default, Clone)]
/// See <https://docs.discord.sex/topics/gateway-events#merged-presences-structure>
pub struct MergedPresences { pub struct MergedPresences {
pub guilds: Vec<Vec<MergedPresenceGuild>>, /// "Presences of the user's guilds in the same order as the guilds array in ready"
/// (discord.sex)
pub guilds: Vec<Vec<MergedPresenceGuild>>,
/// "Presences of the user's friends and implicit relationships" (discord.sex)
pub friends: Vec<MergedPresenceFriend>, pub friends: Vec<MergedPresenceFriend>,
} }
#[derive(Debug, Deserialize, Serialize, Default, Clone)] #[derive(Debug, Deserialize, Serialize, Default, Clone)]
/// Not documented even unofficially
pub struct MergedPresenceFriend { pub struct MergedPresenceFriend {
pub user_id: String, pub user_id: Snowflake,
pub status: String, pub status: String,
/// Looks like ms?? // Looks like ms??
pub last_modified: u128, //
// Not always sent
pub last_modified: Option<u128>,
pub client_status: ClientStatusObject, pub client_status: ClientStatusObject,
pub activities: Vec<Activity>, pub activities: Vec<Activity>,
} }
#[derive(Debug, Deserialize, Serialize, Default, Clone)] #[derive(Debug, Deserialize, Serialize, Default, Clone)]
/// Not documented even unofficially
pub struct MergedPresenceGuild { pub struct MergedPresenceGuild {
pub user_id: String, pub user_id: Snowflake,
pub status: String, pub status: String,
// ? // ?
pub game: Option<serde_json::Value>, pub game: Option<serde_json::Value>,
@ -74,8 +82,10 @@ pub struct MergedPresenceGuild {
} }
#[derive(Debug, Deserialize, Serialize, Default, Clone)] #[derive(Debug, Deserialize, Serialize, Default, Clone)]
/// See <https://docs.discord.sex/topics/gateway-events#supplemental-guild-structure>
pub struct SupplementalGuild { pub struct SupplementalGuild {
pub id: Snowflake,
pub voice_states: Option<Vec<VoiceState>>, pub voice_states: Option<Vec<VoiceState>>,
pub id: String, /// Field not documented even unofficially
pub embedded_activities: Vec<serde_json::Value>, pub embedded_activities: Vec<serde_json::Value>,
} }

View File

@ -2,11 +2,10 @@ use serde::{Deserialize, Serialize};
use super::WebSocketEvent; use super::WebSocketEvent;
#[derive(Debug, Deserialize, Serialize, Default, Clone)] #[derive(Debug, Deserialize, Serialize, Default, Clone, WebSocketEvent, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
/// "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" /// "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 /// # Reference
/// See <https://docs.discord.sex/topics/gateway-events#reconnect> /// See <https://docs.discord.sex/topics/gateway-events#reconnect>
pub struct GatewayReconnect {} pub struct GatewayReconnect {}
impl WebSocketEvent for GatewayReconnect {}

View File

@ -5,7 +5,7 @@
use crate::types::{events::WebSocketEvent, Relationship, RelationshipType, Snowflake}; use crate::types::{events::WebSocketEvent, Relationship, RelationshipType, Snowflake};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
#[derive(Debug, Deserialize, Serialize, Default)] #[derive(Debug, Deserialize, Serialize, Default, WebSocketEvent)]
/// See <https://github.com/spacebarchat/server/issues/204> /// See <https://github.com/spacebarchat/server/issues/204>
pub struct RelationshipAdd { pub struct RelationshipAdd {
#[serde(flatten)] #[serde(flatten)]
@ -13,9 +13,7 @@ pub struct RelationshipAdd {
pub should_notify: bool, pub should_notify: bool,
} }
impl WebSocketEvent for RelationshipAdd {} #[derive(Debug, Deserialize, Serialize, Default, Clone, WebSocketEvent, PartialEq, Eq, Hash, PartialOrd, Ord, Copy)]
#[derive(Debug, Deserialize, Serialize, Default, Clone)]
/// See <https://github.com/spacebarchat/server/issues/203> /// See <https://github.com/spacebarchat/server/issues/203>
pub struct RelationshipRemove { pub struct RelationshipRemove {
pub id: Snowflake, pub id: Snowflake,
@ -23,4 +21,3 @@ pub struct RelationshipRemove {
pub relationship_type: RelationshipType, pub relationship_type: RelationshipType,
} }
impl WebSocketEvent for RelationshipRemove {}

View File

@ -5,7 +5,7 @@
use crate::types::{events::WebSocketEvent, Snowflake}; use crate::types::{events::WebSocketEvent, Snowflake};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
#[derive(Debug, Deserialize, Serialize, Default)] #[derive(Debug, Deserialize, Serialize, Default, WebSocketEvent)]
/// See <https://discord.com/developers/docs/topics/gateway-events#request-guild-members-request-guild-members-structure> /// See <https://discord.com/developers/docs/topics/gateway-events#request-guild-members-request-guild-members-structure>
pub struct GatewayRequestGuildMembers { pub struct GatewayRequestGuildMembers {
pub guild_id: Snowflake, pub guild_id: Snowflake,
@ -17,4 +17,3 @@ pub struct GatewayRequestGuildMembers {
pub nonce: Option<String>, pub nonce: Option<String>,
} }
impl WebSocketEvent for GatewayRequestGuildMembers {}

View File

@ -5,11 +5,10 @@
use crate::types::events::WebSocketEvent; use crate::types::events::WebSocketEvent;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Deserialize, Serialize, Default)] #[derive(Debug, Clone, Deserialize, Serialize, Default, WebSocketEvent)]
pub struct GatewayResume { pub struct GatewayResume {
pub token: String, pub token: String,
pub session_id: String, pub session_id: String,
pub seq: String, pub seq: String,
} }
impl WebSocketEvent for GatewayResume {}

View File

@ -2,11 +2,12 @@
// License, v. 2.0. If a copy of the MPL was not distributed with this // 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/. // file, You can obtain one at http://mozilla.org/MPL/2.0/.
use chorus_macros::WebSocketEvent;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::types::{Activity, WebSocketEvent}; use crate::types::{Activity, WebSocketEvent};
#[derive(Debug, Deserialize, Serialize, Default, Clone)] #[derive(Debug, Deserialize, Serialize, Default, Clone, WebSocketEvent)]
/// Officially Undocumented /// Officially Undocumented
/// Seems like it sends active session info to users on connect /// Seems like it sends active session info to users on connect
/// [{"activities":[],"client_info":{"client":"web","os":"other","version":0},"session_id":"ab5941b50d818b1f8d93b4b1b581b192","status":"online"}] /// [{"activities":[],"client_info":{"client":"web","os":"other","version":0},"session_id":"ab5941b50d818b1f8d93b4b1b581b192","status":"online"}]
@ -33,4 +34,3 @@ pub struct ClientInfo {
pub version: u8, pub version: u8,
} }
impl WebSocketEvent for SessionsReplace {}

View File

@ -5,30 +5,26 @@
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::types::{StageInstance, WebSocketEvent}; use crate::types::{StageInstance, WebSocketEvent};
use chorus_macros::WebSocketEvent;
#[derive(Debug, Deserialize, Serialize, Default, Clone)] #[derive(Debug, Deserialize, Serialize, Default, Clone, WebSocketEvent)]
/// See <https://discord.com/developers/docs/topics/gateway-events#stage-instance-create> /// See <https://discord.com/developers/docs/topics/gateway-events#stage-instance-create>
pub struct StageInstanceCreate { pub struct StageInstanceCreate {
#[serde(flatten)] #[serde(flatten)]
pub stage_instance: StageInstance, pub stage_instance: StageInstance,
} }
impl WebSocketEvent for StageInstanceCreate {} #[derive(Debug, Deserialize, Serialize, Default, Clone, WebSocketEvent)]
#[derive(Debug, Deserialize, Serialize, Default, Clone)]
/// See <https://discord.com/developers/docs/topics/gateway-events#stage-instance-update> /// See <https://discord.com/developers/docs/topics/gateway-events#stage-instance-update>
pub struct StageInstanceUpdate { pub struct StageInstanceUpdate {
#[serde(flatten)] #[serde(flatten)]
pub stage_instance: StageInstance, pub stage_instance: StageInstance,
} }
impl WebSocketEvent for StageInstanceUpdate {} #[derive(Debug, Deserialize, Serialize, Default, Clone, WebSocketEvent)]
#[derive(Debug, Deserialize, Serialize, Default, Clone)]
/// See <https://discord.com/developers/docs/topics/gateway-events#stage-instance-delete> /// See <https://discord.com/developers/docs/topics/gateway-events#stage-instance-delete>
pub struct StageInstanceDelete { pub struct StageInstanceDelete {
#[serde(flatten)] #[serde(flatten)]
pub stage_instance: StageInstance, pub stage_instance: StageInstance,
} }
impl WebSocketEvent for StageInstanceDelete {}

View File

@ -12,16 +12,14 @@ use crate::types::{JsonField, Snowflake, SourceUrlField};
#[cfg(feature = "client")] #[cfg(feature = "client")]
use super::UpdateMessage; use super::UpdateMessage;
#[derive(Debug, Default, Deserialize, Serialize, Clone)] #[derive(Debug, Default, Deserialize, Serialize, Clone, WebSocketEvent)]
/// See <https://discord.com/developers/docs/topics/gateway-events#thread-create> /// See <https://discord.com/developers/docs/topics/gateway-events#thread-create>
pub struct ThreadCreate { pub struct ThreadCreate {
#[serde(flatten)] #[serde(flatten)]
pub thread: Channel, pub thread: Channel,
} }
impl WebSocketEvent for ThreadCreate {} #[derive(Debug, Default, Deserialize, Serialize, Clone, JsonField, SourceUrlField, WebSocketEvent)]
#[derive(Debug, Default, Deserialize, Serialize, Clone, JsonField, SourceUrlField)]
/// See <https://discord.com/developers/docs/topics/gateway-events#thread-update> /// See <https://discord.com/developers/docs/topics/gateway-events#thread-update>
pub struct ThreadUpdate { pub struct ThreadUpdate {
#[serde(flatten)] #[serde(flatten)]
@ -32,8 +30,6 @@ pub struct ThreadUpdate {
pub source_url: String, pub source_url: String,
} }
impl WebSocketEvent for ThreadUpdate {}
#[cfg(feature = "client")] #[cfg(feature = "client")]
impl UpdateMessage<Channel> for ThreadUpdate { impl UpdateMessage<Channel> for ThreadUpdate {
#[cfg(not(tarpaulin_include))] #[cfg(not(tarpaulin_include))]
@ -42,16 +38,14 @@ impl UpdateMessage<Channel> for ThreadUpdate {
} }
} }
#[derive(Debug, Default, Deserialize, Serialize, Clone)] #[derive(Debug, Default, Deserialize, Serialize, Clone, WebSocketEvent)]
/// See <https://discord.com/developers/docs/topics/gateway-events#thread-delete> /// See <https://discord.com/developers/docs/topics/gateway-events#thread-delete>
pub struct ThreadDelete { pub struct ThreadDelete {
#[serde(flatten)] #[serde(flatten)]
pub thread: Channel, pub thread: Channel,
} }
impl WebSocketEvent for ThreadDelete {} #[derive(Debug, Default, Deserialize, Serialize, Clone, WebSocketEvent)]
#[derive(Debug, Default, Deserialize, Serialize, Clone)]
/// See <https://discord.com/developers/docs/topics/gateway-events#thread-list-sync> /// See <https://discord.com/developers/docs/topics/gateway-events#thread-list-sync>
pub struct ThreadListSync { pub struct ThreadListSync {
pub guild_id: Snowflake, pub guild_id: Snowflake,
@ -60,9 +54,7 @@ pub struct ThreadListSync {
pub members: Option<Vec<ThreadMember>>, pub members: Option<Vec<ThreadMember>>,
} }
impl WebSocketEvent for ThreadListSync {} #[derive(Debug, Default, Deserialize, Serialize, Clone, WebSocketEvent)]
#[derive(Debug, Default, Deserialize, Serialize, Clone)]
/// See <https://discord.com/developers/docs/topics/gateway-events#thread-member-update> /// See <https://discord.com/developers/docs/topics/gateway-events#thread-member-update>
/// The inner payload is a thread member object with an extra field. /// The inner payload is a thread member object with an extra field.
pub struct ThreadMemberUpdate { pub struct ThreadMemberUpdate {
@ -71,9 +63,7 @@ pub struct ThreadMemberUpdate {
pub guild_id: Snowflake, pub guild_id: Snowflake,
} }
impl WebSocketEvent for ThreadMemberUpdate {} #[derive(Debug, Default, Deserialize, Serialize, Clone, WebSocketEvent)]
#[derive(Debug, Default, Deserialize, Serialize, Clone)]
/// See <https://discord.com/developers/docs/topics/gateway-events#thread-members-update> /// See <https://discord.com/developers/docs/topics/gateway-events#thread-members-update>
pub struct ThreadMembersUpdate { pub struct ThreadMembersUpdate {
pub id: Snowflake, pub id: Snowflake,
@ -84,4 +74,3 @@ pub struct ThreadMembersUpdate {
pub removed_members: Option<Vec<Snowflake>>, pub removed_members: Option<Vec<Snowflake>>,
} }
impl WebSocketEvent for ThreadMembersUpdate {}

Some files were not shown because too many files have changed in this diff Show More