275 commits behind ono

This commit is contained in:
kozabrada123 2023-10-14 08:41:26 +02:00
commit 06ea17875f
111 changed files with 4829 additions and 1711 deletions

View File

@ -2,7 +2,7 @@ name: Build and Test
on:
push:
branches: [ "main" ]
branches: [ "main", "dev" ]
pull_request:
branches: [ "main" ]
@ -21,7 +21,7 @@ jobs:
git clone https://github.com/bitfl0wer/server.git
- uses: actions/setup-node@v3
with:
node-version: 16
node-version: 18
cache: 'npm'
cache-dependency-path: server/package-lock.json
- name: Prepare and start Spacebar server
@ -31,7 +31,17 @@ jobs:
npm run start &
working-directory: ./server
- uses: Swatinem/rust-cache@v2
- name: Build
run: cargo build --verbose
- name: Run tests
run: cargo test --verbose
with:
cache-all-crates: "true"
- name: Build, Test and Publish Coverage
run: |
if [ -n "${{ secrets.COVERALLS_REPO_TOKEN }}" ]; then
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 cargo-tarpaulin --force
cargo tarpaulin --all-features --avoid-cfg-tarpaulin --tests --verbose --skip-clean --coveralls ${{ secrets.COVERALLS_REPO_TOKEN }} --timeout 120
else
echo "Code Coverage step is skipped on forks!"
cargo build --verbose --all-features
cargo test --verbose --all-features
fi

View File

@ -11,7 +11,7 @@ name: rust-clippy analyze
on:
push:
branches: [ "main", "preserve/*" ]
branches: [ "main", "preserve/*", "dev" ]
pull_request:
# The branches below must be a subset of the branches above
branches: [ "main" ]

2
.gitignore vendored
View File

@ -1,6 +1,6 @@
# Generated by Cargo
# will have compiled files and executables
/target/
/**/target/
# These are backup files generated by rustfmt
**/*.rs.bk

820
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,8 +1,14 @@
[package]
name = "chorus"
version = "0.1.0"
license = "AGPL-3"
description = "A library for interacting with multiple Spacebar-compatible Instances at once."
version = "0.9.0"
license = "AGPL-3.0"
edition = "2021"
repository = "https://github.com/polyphony-chat/chorus"
readme = "README.md"
keywords = ["spacebar", "discord", "polyphony"]
website = ["https://discord.com/invite/m3FpcapGDD"]
[features]
default = ["client"]
@ -10,35 +16,43 @@ backend = ["poem", "sqlx"]
client = []
[dependencies]
tokio = {version = "1.29.1", features = ["macros"]}
serde = {version = "1.0.171", features = ["derive"]}
serde_json = {version= "1.0.103", features = ["raw_value"]}
tokio = { version = "1.29.1", features = ["macros"] }
serde = { version = "1.0.188", features = ["derive", "rc"] }
serde_json = { version = "1.0.105", features = ["raw_value"] }
serde-aux = "4.2.0"
serde_with = "3.0.0"
serde_repr = "0.1.14"
reqwest = {version = "0.11.18", features = ["multipart"]}
serde_with = "3.3.0"
serde_repr = "0.1.16"
reqwest = { version = "0.11.20", features = ["multipart", "json"] }
url = "2.4.0"
chrono = {version = "0.4.26", features = ["serde"]}
regex = "1.9.1"
chrono = { version = "0.4.26", features = ["serde"] }
regex = "1.9.4"
custom_error = "1.9.2"
native-tls = "0.2.11"
tokio-tungstenite = {version = "0.19.0", features = ["native-tls"]}
tokio-tungstenite = { version = "0.20.0", features = ["native-tls"] }
futures-util = "0.3.28"
http = "0.2.9"
openssl = "0.10.55"
base64 = "0.21.2"
openssl = "0.10.56"
base64 = "0.21.3"
hostname = "0.3.1"
bitflags = { version = "2.3.3", features = ["serde"] }
bitflags = { version = "2.4.0", features = ["serde"] }
lazy_static = "1.4.0"
poem = { version = "1.3.56", optional = true }
sqlx = { git = "https://github.com/zert3x/sqlx", branch="feature/skip", features = ["mysql", "sqlite", "json", "chrono", "ipnetwork", "runtime-tokio-native-tls", "any"], optional = true }
thiserror = "1.0.43"
poem = { version = "1.3.57", optional = true }
sqlx = { version = "0.7.1", features = [
"mysql",
"sqlite",
"json",
"chrono",
"ipnetwork",
"runtime-tokio-native-tls",
"any",
], optional = true }
thiserror = "1.0.47"
jsonwebtoken = "8.3.0"
log = "0.4.19"
async-trait = "0.1.71"
chorus-macros = {path = "chorus-macros"}
log = "0.4.20"
async-trait = "0.1.73"
chorus-macros = "0.2.0"
[dev-dependencies]
tokio = {version = "1.29.1", features = ["full"]}
tokio = { version = "1.32.0", features = ["full"] }
lazy_static = "1.4.0"
rusty-hook = "0.11.2"

View File

@ -2,10 +2,11 @@
[![Discord]][Discord-invite]
[![Build][build-shield]][build-url]
[![Coverage][coverage-shield]][coverage-url]
[![Contributors][contributors-shield]][contributors-url]
[![Forks][forks-shield]][forks-url]
[![Issues][issues-shield]][issues-url]
<img src="https://img.shields.io/static/v1?label=Status&message=Early%20Development&color=blue">
<img src="https://img.shields.io/static/v1?label=Status&message=Alpha&color=blue">
</br>
<div align="center">
@ -37,6 +38,7 @@ Chorus is a Rust library that allows developers to interact with multiple Spaceb
## Contributing
If you'd like to contribute new functionality, check out [The 'Meta'-issues.](https://github.com/polyphony-chat/chorus/issues?q=is%3Aissue+label%3A%22Type%3A+Meta%22+) They contain a comprehensive list of all features which are yet missing for full Discord.com compatibility.
If you would like to contribute, please feel free to open an Issue with the idea you have, or a
Pull Request. Please keep our [contribution guidelines](https://github.com/polyphony-chat/.github/blob/main/CONTRIBUTION_GUIDELINES.md) in mind. Your contribution might not be
accepted, if it violates these guidelines or [our Code of Conduct](https://github.com/polyphony-chat/.github/blob/main/CODE_OF_CONDUCT.md).
@ -123,6 +125,8 @@ accepted, if it violates these guidelines or [our Code of Conduct](https://githu
[clippy-url]: https://github.com/polyphony-chat/chorus/blob/main/.github/workflows/clippy.yml
[contributors-shield]: https://img.shields.io/github/contributors/polyphony-chat/chorus.svg?style=flat
[contributors-url]: https://github.com/polyphony-chat/chorus/graphs/contributors
[coverage-shield]: https://coveralls.io/repos/github/polyphony-chat/chorus/badge.svg?branch=main
[coverage-url]: https://coveralls.io/github/polyphony-chat/chorus?branch=main
[forks-shield]: https://img.shields.io/github/forks/polyphony-chat/chorus.svg?style=flat
[forks-url]: https://github.com/polyphony-chat/chorus/network/members
[stars-shield]: https://img.shields.io/github/stars/polyphony-chat/chorus.svg?style=flat

View File

@ -2,10 +2,22 @@
# It is not intended for manual editing.
version = 3
[[package]]
name = "async-trait"
version = "0.1.73"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc00ceb34980c03614e35a3a4e218276a0a824e911d07651cd0d858a51e8c0f0"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "chorus-macros"
version = "0.1.0"
dependencies = [
"async-trait",
"quote",
"syn",
]
@ -21,18 +33,18 @@ dependencies = [
[[package]]
name = "quote"
version = "1.0.31"
version = "1.0.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5fe8a65d69dd0808184ebb5f836ab526bb259db23c657efa38711b1072ee47f0"
checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae"
dependencies = [
"proc-macro2",
]
[[package]]
name = "syn"
version = "2.0.27"
version = "2.0.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b60f673f44a8255b9c8c657daf66a596d435f2da81a555b06dc644d080ba45e0"
checksum = "c324c494eba9d92503e6f1ef2e6df781e78f6a7705a0202d9801b198807d518a"
dependencies = [
"proc-macro2",
"quote",

View File

@ -1,11 +1,14 @@
[package]
name = "chorus-macros"
version = "0.1.0"
version = "0.2.0"
edition = "2021"
license = "AGPL-3.0"
description = "Macros for the chorus crate."
[lib]
proc-macro = true
[dependencies]
quote = "1"
syn = "2"
quote = "1.0.33"
syn = "2.0.29"
async-trait = "0.1.73"

View File

@ -1,5 +1,6 @@
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, Data, DeriveInput, Field, Fields, FieldsNamed};
#[proc_macro_derive(Updateable)]
pub fn updateable_macro_derive(input: TokenStream) -> TokenStream {
@ -16,3 +17,125 @@ pub fn updateable_macro_derive(input: TokenStream) -> TokenStream {
}
.into()
}
#[proc_macro_derive(JsonField)]
pub fn jsonfield_macro_derive(input: TokenStream) -> TokenStream {
let ast: syn::DeriveInput = syn::parse(input).unwrap();
let name = &ast.ident;
// No need for macro hygiene, we're only using this in chorus
quote! {
impl JsonField for #name {
fn get_json(&self) -> String {
self.json.clone()
}
fn set_json(&mut self, json: String) {
self.json = json;
}
}
}
.into()
}
#[proc_macro_derive(SourceUrlField)]
pub fn source_url_macro_derive(input: TokenStream) -> TokenStream {
let ast: syn::DeriveInput = syn::parse(input).unwrap();
let name = &ast.ident;
// No need for macro hygiene, we're only using this in chorus
quote! {
impl SourceUrlField for #name {
fn get_source_url(&self) -> String {
self.source_url.clone()
}
fn set_source_url(&mut self, url: String) {
self.source_url = url;
}
}
}
.into()
}
#[proc_macro_attribute]
pub fn observe_option(_args: TokenStream, input: TokenStream) -> TokenStream {
input
}
#[proc_macro_attribute]
pub fn observe_option_vec(_args: TokenStream, input: TokenStream) -> TokenStream {
input
}
#[proc_macro_attribute]
pub fn observe(_args: TokenStream, input: TokenStream) -> TokenStream {
input
}
#[proc_macro_attribute]
pub fn observe_vec(_args: TokenStream, input: TokenStream) -> TokenStream {
input
}
#[proc_macro_derive(
Composite,
attributes(observe_option_vec, observe_option, observe, observe_vec)
)]
pub fn composite_derive(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
let process_field = |field: &Field| {
let field_name = &field.ident;
let attrs = &field.attrs;
let observe_option = attrs
.iter()
.any(|attr| attr.path().is_ident("observe_option"));
let observe_option_vec = attrs
.iter()
.any(|attr| attr.path().is_ident("observe_option_vec"));
let observe = attrs.iter().any(|attr| attr.path().is_ident("observe"));
let observe_vec = attrs.iter().any(|attr| attr.path().is_ident("observe_vec"));
match (observe_option, observe_option_vec, observe, observe_vec) {
(true, _, _, _) => quote! {
#field_name: Self::option_observe_fn(self.#field_name, gateway).await
},
(_, true, _, _) => quote! {
#field_name: Self::option_vec_observe_fn(self.#field_name, gateway).await
},
(_, _, true, _) => quote! {
#field_name: Self::value_observe_fn(self.#field_name, gateway).await
},
(_, _, _, true) => quote! {
#field_name: Self::vec_observe_fn(self.#field_name, gateway).await
},
_ => quote! {
#field_name: self.#field_name
},
}
};
match &input.data {
Data::Struct(data) => match &data.fields {
Fields::Named(FieldsNamed { named, .. }) => {
let field_exprs = named.iter().map(process_field);
let ident = &input.ident;
let expanded = quote! {
#[async_trait::async_trait(?Send)]
impl<T: Updateable + Clone + Debug> Composite<T> for #ident {
async fn watch_whole(self, gateway: &GatewayHandle) -> Self {
Self {
#(#field_exprs,)*
}
}
}
};
TokenStream::from(expanded)
}
_ => panic!("Composite derive macro only supports named fields"),
},
_ => panic!("Composite derive macro only supports structs"),
}
}

View File

@ -1,30 +1,33 @@
use std::cell::RefCell;
use std::rc::Rc;
use std::sync::{Arc, RwLock};
use reqwest::Client;
use serde_json::to_string;
use crate::api::LimitType;
use crate::errors::ChorusResult;
use crate::gateway::Gateway;
use crate::instance::{Instance, UserMeta};
use crate::instance::{ChorusUser, Instance};
use crate::ratelimiter::ChorusRequest;
use crate::types::{GatewayIdentifyPayload, LoginResult, LoginSchema};
use crate::types::{GatewayIdentifyPayload, LimitType, LoginResult, LoginSchema};
impl Instance {
pub async fn login_account(&mut self, login_schema: &LoginSchema) -> ChorusResult<UserMeta> {
/// Logs into an existing account on the spacebar server.
///
/// # Reference
/// See <https://docs.spacebar.chat/routes/#post-/auth/login/>
pub async fn login_account(mut self, login_schema: LoginSchema) -> ChorusResult<ChorusUser> {
let endpoint_url = self.urls.api.clone() + "/auth/login";
let chorus_request = ChorusRequest {
request: Client::new()
.post(endpoint_url)
.body(to_string(login_schema).unwrap()),
.body(to_string(&login_schema).unwrap())
.header("Content-Type", "application/json"),
limit_type: LimitType::AuthLogin,
};
// 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
// instances' limits to pass them on as user_rate_limits later.
let mut shell =
UserMeta::shell(Rc::new(RefCell::new(self.clone())), "None".to_string()).await;
ChorusUser::shell(Arc::new(RwLock::new(self.clone())), "None".to_string()).await;
let login_result = chorus_request
.deserialize_response::<LoginResult>(&mut shell)
.await?;
@ -36,12 +39,12 @@ impl Instance {
let gateway = Gateway::new(self.urls.wss.clone()).await.unwrap();
identify.token = login_result.token.clone();
gateway.send_identify(identify).await;
let user = UserMeta::new(
Rc::new(RefCell::new(self.clone())),
let user = ChorusUser::new(
Arc::new(RwLock::new(self.clone())),
login_result.token,
self.clone_limits_if_some(),
login_result.settings,
object,
Arc::new(RwLock::new(object)),
gateway,
);
Ok(user)

View File

@ -1,5 +1,41 @@
use std::sync::{Arc, RwLock};
pub use login::*;
pub use register::*;
use crate::{
errors::ChorusResult,
gateway::Gateway,
instance::{ChorusUser, Instance},
types::{GatewayIdentifyPayload, User},
};
pub mod login;
pub mod register;
impl Instance {
/// 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> {
let object_result = self.get_user(token.clone(), None).await;
if let Err(e) = object_result {
return Result::Err(e);
}
let user_settings = User::get_settings(&token, &self.urls.api, &mut self.clone())
.await
.unwrap();
let mut identify = GatewayIdentifyPayload::common();
let gateway = Gateway::new(self.urls.wss.clone()).await.unwrap();
identify.token = token.clone();
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)
}
}

View File

@ -1,4 +1,4 @@
use std::{cell::RefCell, rc::Rc};
use std::sync::{Arc, RwLock};
use reqwest::Client;
use serde_json::to_string;
@ -6,39 +6,35 @@ use serde_json::to_string;
use crate::gateway::Gateway;
use crate::types::GatewayIdentifyPayload;
use crate::{
api::policies::instance::LimitType,
errors::ChorusResult,
instance::{Instance, Token, UserMeta},
instance::{ChorusUser, Instance, Token},
ratelimiter::ChorusRequest,
types::LimitType,
types::RegisterSchema,
};
impl Instance {
/// Registers a new user on the Spacebar server.
/// Registers a new user on the server.
///
/// # Arguments
///
/// * `register_schema` - The [`RegisterSchema`] that contains all the information that is needed to register a new user.
///
/// # Errors
///
/// * [`ChorusLibError`] - If the server does not respond.
/// # Reference
/// See <https://docs.spacebar.chat/routes/#post-/auth/register/>
pub async fn register_account(
&mut self,
register_schema: &RegisterSchema,
) -> ChorusResult<UserMeta> {
mut self,
register_schema: RegisterSchema,
) -> ChorusResult<ChorusUser> {
let endpoint_url = self.urls.api.clone() + "/auth/register";
let chorus_request = ChorusRequest {
request: Client::new()
.post(endpoint_url)
.body(to_string(register_schema).unwrap()),
.body(to_string(&register_schema).unwrap())
.header("Content-Type", "application/json"),
limit_type: LimitType::AuthRegister,
};
// 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
// the instances' limits to pass them on as user_rate_limits later.
let mut shell =
UserMeta::shell(Rc::new(RefCell::new(self.clone())), "None".to_string()).await;
ChorusUser::shell(Arc::new(RwLock::new(self.clone())), "None".to_string()).await;
let token = chorus_request
.deserialize_response::<Token>(&mut shell)
.await?
@ -47,17 +43,17 @@ impl Instance {
self.limits_information.as_mut().unwrap().ratelimits = shell.limits.unwrap();
}
let user_object = self.get_user(token.clone(), None).await.unwrap();
let settings = UserMeta::get_settings(&token, &self.urls.api.clone(), self).await?;
let settings = ChorusUser::get_settings(&token, &self.urls.api.clone(), &mut self).await?;
let mut identify = GatewayIdentifyPayload::common();
let gateway = Gateway::new(self.urls.wss.clone()).await.unwrap();
identify.token = token.clone();
gateway.send_identify(identify).await;
let user = UserMeta::new(
Rc::new(RefCell::new(self.clone())),
let user = ChorusUser::new(
Arc::new(RwLock::new(self.clone())),
token.clone(),
self.clone_limits_if_some(),
settings,
user_object,
Arc::new(RwLock::new(settings)),
Arc::new(RwLock::new(user_object)),
gateway,
);
Ok(user)

View File

@ -1,126 +1,165 @@
use reqwest::Client;
use serde_json::to_string;
use crate::types::AddChannelRecipientSchema;
use crate::types::{AddChannelRecipientSchema, ModifyChannelPositionsSchema};
use crate::{
api::LimitType,
errors::{ChorusError, ChorusResult},
instance::UserMeta,
instance::ChorusUser,
ratelimiter::ChorusRequest,
types::{Channel, ChannelModifySchema, GetChannelMessagesSchema, Message, Snowflake},
types::{
Channel, ChannelModifySchema, GetChannelMessagesSchema, LimitType, Message, Snowflake,
},
};
impl Channel {
pub async fn get(user: &mut UserMeta, channel_id: Snowflake) -> ChorusResult<Channel> {
let url = user.belongs_to.borrow().urls.api.clone();
let chorus_request = ChorusRequest {
request: Client::new()
.get(format!("{}/channels/{}/", url, channel_id))
.bearer_auth(user.token()),
limit_type: LimitType::Channel(channel_id),
};
/// Retrieves a channel from the server.
///
/// # Reference
/// See <https://discord-userdoccers.vercel.app/resources/channel#get-channel>
pub async fn get(user: &mut ChorusUser, channel_id: Snowflake) -> ChorusResult<Channel> {
let chorus_request = ChorusRequest::new(
http::Method::GET,
&format!(
"{}/channels/{}",
user.belongs_to.read().unwrap().urls.api.clone(),
channel_id
),
None,
None,
None,
Some(user),
LimitType::Channel(channel_id),
);
chorus_request.deserialize_response::<Channel>(user).await
}
/// Deletes a channel.
/// Deletes self.
///
/// # Arguments
/// Requires the [`MANAGE_CHANNELS`](crate::types::PermissionFlags::MANAGE_CHANNELS) permission in a guild, or
/// the [`MANAGE_THREADS`](crate::types::PermissionFlags::MANAGE_THREADS) permission if the channel is a thread.
///
/// * `token` - A string slice that holds the authorization token.
/// * `url_api` - A string slice that holds the URL of the API.
/// * `channel` - A `Channel` object that represents the channel to be deleted.
/// * `limits_user` - A mutable reference to a `Limits` object that represents the user's rate limits.
/// * `limits_instance` - A mutable reference to a `Limits` object that represents the instance's rate limits.
///
/// # Returns
///
/// A `Result` that contains a `ChorusLibError` if an error occurred during the request, or `()` if the request was successful.
pub async fn delete(self, user: &mut UserMeta) -> ChorusResult<()> {
let chorus_request = ChorusRequest {
request: Client::new()
.delete(format!(
"{}/channels/{}/",
user.belongs_to.borrow().urls.api,
self.id
))
.bearer_auth(user.token()),
limit_type: LimitType::Channel(self.id),
};
chorus_request.handle_request_as_result(user).await
/// # Reference
/// See <https://discord-userdoccers.vercel.app/resources/channel#delete-channel>
pub async fn delete(
self,
audit_log_reason: Option<String>,
user: &mut ChorusUser,
) -> ChorusResult<()> {
let url = format!(
"{}/channels/{}",
user.belongs_to.read().unwrap().urls.api,
self.id,
);
let request = ChorusRequest::new(
http::Method::DELETE,
&url,
None,
audit_log_reason.as_deref(),
None,
Some(user),
LimitType::Channel(self.id),
);
request.handle_request_as_result(user).await
}
/// Modifies a channel.
/// Modifies a channel with the provided data.
/// Returns the new Channel.
///
/// # Arguments
/// Requires the [`MANAGE_CHANNELS`](crate::types::PermissionFlags::MANAGE_CHANNELS) permission in a guild.
///
/// * `modify_data` - A `ChannelModifySchema` object that represents the modifications to be made to the channel.
/// * `token` - A string slice that holds the authorization token.
/// * `url_api` - A string slice that holds the URL of the API.
/// * `channel_id` - A string slice that holds the ID of the channel to be modified.
/// * `limits_user` - A mutable reference to a `Limits` object that represents the user's rate limits.
/// * `limits_instance` - A mutable reference to a `Limits` object that represents the instance's rate limits.
/// If modifying permission overwrites, the [`MANAGE_ROLES`](crate::types::PermissionFlags::MANAGE_ROLES) permission is required.
/// Only permissions you have in the guild or parent channel (if applicable) can be allowed/denied
/// (unless you have a [`MANAGE_ROLES`](crate::types::PermissionFlags::MANAGE_ROLES) overwrite in the channel).
///
/// # Returns
/// If modifying a thread and setting `archived` to `false`, when `locked` is also `false`, only the [`SEND_MESSAGES`](crate::types::PermissionFlags::SEND_MESSAGES) permission is required.
/// Otherwise, requires the [`MANAGE_THREADS`](crate::types::PermissionFlags::MANAGE_THREADS) permission. Requires the thread to have `archived` set to `false` or be set to `false` in the request.
///
/// A `Result` that contains a `Channel` object if the request was successful, or an `ChorusLibError` if an error occurred during the request.
/// # Reference
/// See <https://discord-userdoccers.vercel.app/resources/channel#modify-channel>
pub async fn modify(
&self,
modify_data: ChannelModifySchema,
channel_id: Snowflake,
user: &mut UserMeta,
audit_log_reason: Option<String>,
user: &mut ChorusUser,
) -> ChorusResult<Channel> {
let chorus_request = ChorusRequest {
request: Client::new()
.patch(format!(
"{}/channels/{}/",
user.belongs_to.borrow().urls.api,
let channel_id = self.id;
let url = format!(
"{}/channels/{}",
user.belongs_to.read().unwrap().urls.api,
channel_id
))
.bearer_auth(user.token())
.body(to_string(&modify_data).unwrap()),
limit_type: LimitType::Channel(channel_id),
};
chorus_request.deserialize_response::<Channel>(user).await
);
let request = ChorusRequest::new(
http::Method::PATCH,
&url,
Some(to_string(&modify_data).unwrap()),
audit_log_reason.as_deref(),
None,
Some(user),
LimitType::Channel(channel_id),
);
request.deserialize_response::<Channel>(user).await
}
/// Fetches recent messages from a channel.
///
/// If operating on a guild channel, this endpoint requires the [`VIEW_CHANNEL`](crate::types::PermissionFlags::VIEW_CHANNEL) permission.
///
/// If the user is missing the [`READ_MESSAGE_HISTORY`](crate::types::PermissionFlags::READ_MESSAGE_HISTORY) permission,
/// this method returns an empty list.
///
/// # Reference
/// See <https://discord-userdoccers.vercel.app/resources/message#get-messages>
pub async fn messages(
range: GetChannelMessagesSchema,
channel_id: Snowflake,
user: &mut UserMeta,
user: &mut ChorusUser,
) -> Result<Vec<Message>, ChorusError> {
let chorus_request = ChorusRequest {
request: Client::new()
.get(format!(
let url = format!(
"{}/channels/{}/messages",
user.belongs_to.borrow().urls.api,
user.belongs_to.read().unwrap().urls.api,
channel_id
))
.bearer_auth(user.token())
.query(&range),
limit_type: Default::default(),
};
);
let mut chorus_request = ChorusRequest::new(
http::Method::GET,
&url,
None,
None,
None,
Some(user),
Default::default(),
);
chorus_request.request = chorus_request.request.query(&range);
chorus_request
.deserialize_response::<Vec<Message>>(user)
.await
}
/// Adds a recipient to a group DM.
///
/// # Reference:
/// Read: <https://discord-userdoccers.vercel.app/resources/channel#add-channel-recipient>
/// See <https://discord-userdoccers.vercel.app/resources/channel#add-channel-recipient>
pub async fn add_channel_recipient(
&self,
recipient_id: Snowflake,
user: &mut UserMeta,
user: &mut ChorusUser,
add_channel_recipient_schema: Option<AddChannelRecipientSchema>,
) -> ChorusResult<()> {
let mut request = Client::new()
.put(format!(
"{}/channels/{}/recipients/{}/",
user.belongs_to.borrow().urls.api,
"{}/channels/{}/recipients/{}",
user.belongs_to.read().unwrap().urls.api,
self.id,
recipient_id
))
.bearer_auth(user.token());
.header("Authorization", user.token())
.header("Content-Type", "application/json");
if let Some(schema) = add_channel_recipient_schema {
request = request.body(to_string(&schema).unwrap());
}
@ -132,26 +171,61 @@ impl Channel {
.await
}
/// Removes a recipient from a group DM.
///
/// # Reference:
/// Read: <https://discord-userdoccers.vercel.app/resources/channel#remove-channel-recipient>
/// See <https://discord-userdoccers.vercel.app/resources/channel#remove-channel-recipient>
pub async fn remove_channel_recipient(
&self,
recipient_id: Snowflake,
user: &mut UserMeta,
user: &mut ChorusUser,
) -> ChorusResult<()> {
let request = Client::new()
.delete(format!(
"{}/channels/{}/recipients/{}/",
user.belongs_to.borrow().urls.api,
let url = format!(
"{}/channels/{}/recipients/{}",
user.belongs_to.read().unwrap().urls.api,
self.id,
recipient_id
))
.bearer_auth(user.token());
ChorusRequest {
request,
limit_type: LimitType::Channel(self.id),
);
let request = ChorusRequest::new(
http::Method::DELETE,
&url,
None,
None,
None,
Some(user),
LimitType::Channel(self.id),
);
request.handle_request_as_result(user).await
}
.handle_request_as_result(user)
.await
/// Modifies the positions of a set of channel objects for the guild. Requires the `MANAGE_CHANNELS` permission.
/// Only channels to be modified are required.
///
/// # Reference:
/// See <https://discord-userdoccers.vercel.app/resources/channel#modify-guild-channel-positions>
pub async fn modify_positions(
schema: Vec<ModifyChannelPositionsSchema>,
guild_id: Snowflake,
user: &mut ChorusUser,
) -> ChorusResult<()> {
let url = format!(
"{}/guilds/{}/channels",
user.belongs_to.read().unwrap().urls.api,
guild_id
);
let request = ChorusRequest::new(
http::Method::PATCH,
&url,
Some(to_string(&schema).unwrap()),
None,
None,
Some(user),
LimitType::Guild(guild_id),
);
request.handle_request_as_result(user).await
}
}

View File

@ -1,27 +1,36 @@
use http::header::CONTENT_DISPOSITION;
use http::HeaderMap;
use reqwest::{multipart, Client};
use serde_json::to_string;
use serde_json::{from_value, to_string, Value};
use crate::api::LimitType;
use crate::instance::UserMeta;
use crate::errors::{ChorusError, ChorusResult};
use crate::instance::ChorusUser;
use crate::ratelimiter::ChorusRequest;
use crate::types::{Message, MessageSendSchema, Snowflake};
use crate::types::{
Channel, CreateGreetMessage, LimitType, Message, MessageAck, MessageModifySchema,
MessageSearchEndpoint, MessageSearchQuery, MessageSendSchema, Snowflake,
};
impl Message {
/// Sends a message in the channel with the provided channel_id.
/// Returns the sent message.
///
/// # Reference
/// See <https://discord-userdoccers.vercel.app/resources/message#create-message>
pub async fn send(
user: &mut UserMeta,
user: &mut ChorusUser,
channel_id: Snowflake,
mut message: MessageSendSchema,
) -> Result<Message, crate::errors::ChorusError> {
let url_api = user.belongs_to.borrow().urls.api.clone();
) -> ChorusResult<Message> {
let url_api = user.belongs_to.read().unwrap().urls.api.clone();
if message.attachments.is_none() {
let chorus_request = ChorusRequest {
request: Client::new()
.post(format!("{}/channels/{}/messages/", url_api, channel_id))
.bearer_auth(user.token())
.body(to_string(&message).unwrap()),
.post(format!("{}/channels/{}/messages", url_api, channel_id))
.header("Authorization", user.token())
.body(to_string(&message).unwrap())
.header("Content-Type", "application/json"),
limit_type: LimitType::Channel(channel_id),
};
chorus_request.deserialize_response::<Message>(user).await
@ -55,22 +64,462 @@ impl Message {
let chorus_request = ChorusRequest {
request: Client::new()
.post(format!("{}/channels/{}/messages/", url_api, channel_id))
.bearer_auth(user.token())
.post(format!("{}/channels/{}/messages", url_api, channel_id))
.header("Authorization", user.token())
.multipart(form),
limit_type: LimitType::Channel(channel_id),
};
chorus_request.deserialize_response::<Message>(user).await
}
}
/// Returns messages without the reactions key that match a search query in the guild or channel.
/// The messages that are direct results will have an extra hit key set to true.
/// If operating on a guild channel, this endpoint requires the `READ_MESSAGE_HISTORY`
/// permission to be present on the current user.
///
/// If the guild/channel you are searching is not yet indexed, the endpoint will return a 202 accepted response.
/// In this case, the method will return a [`ChorusError::InvalidResponse`] error.
///
/// # Reference:
/// See <https://discord-userdoccers.vercel.app/resources/message#search-messages>
pub(crate) async fn search(
endpoint: MessageSearchEndpoint,
query: MessageSearchQuery,
user: &mut ChorusUser,
) -> ChorusResult<Vec<Message>> {
let limit_type = match &endpoint {
MessageSearchEndpoint::Channel(id) => LimitType::Channel(*id),
MessageSearchEndpoint::GuildChannel(id) => LimitType::Guild(*id),
};
let request = ChorusRequest {
limit_type,
request: Client::new()
.get(format!(
"{}/{}/messages/search",
&user.belongs_to.read().unwrap().urls.api,
endpoint
))
.header("Authorization", user.token())
.header("Content-Type", "application/json")
.body(to_string(&query).unwrap()),
};
let result = request.send_request(user).await?;
let result_json = result.json::<Value>().await.unwrap();
if !result_json.is_object() {
return Err(search_error(result_json.to_string()));
}
let value_map = result_json.as_object().unwrap();
if let Some(messages) = value_map.get("messages") {
if let Ok(response) = from_value::<Vec<Vec<Message>>>(messages.clone()) {
let result_messages: Vec<Message> = response.into_iter().flatten().collect();
return Ok(result_messages);
}
}
// The code below might be incorrect. We'll cross that bridge when we come to it
if !value_map.contains_key("code") || !value_map.contains_key("retry_after") {
return Err(search_error(result_json.to_string()));
}
let code = value_map.get("code").unwrap().as_u64().unwrap();
let retry_after = value_map.get("retry_after").unwrap().as_u64().unwrap();
Err(ChorusError::NotFound {
error: format!(
"Index not yet available. Try again later. Code: {}. Retry after {}s",
code, retry_after
),
})
}
/// Returns all pinned messages in the channel as a Vector of message objects without the reactions key.
/// # Reference:
/// See: <https://discord-userdoccers.vercel.app/resources/message#get-pinned-messages>
pub async fn get_sticky(
channel_id: Snowflake,
user: &mut ChorusUser,
) -> ChorusResult<Vec<Message>> {
let chorus_request = ChorusRequest::new(
http::Method::GET,
format!(
"{}/channels/{}/pins",
user.belongs_to.read().unwrap().urls.api,
channel_id
)
.as_str(),
None,
None,
None,
Some(user),
LimitType::Channel(channel_id),
);
chorus_request
.deserialize_response::<Vec<Message>>(user)
.await
}
/// Pins a message in a channel. Requires the `MANAGE_MESSAGES` permission. Returns a 204 empty response on success.
/// The max pinned messages is 50.
///
/// # Reference:
/// See: <https://discord-userdoccers.vercel.app/resources/message#pin-message>
pub async fn sticky(
channel_id: Snowflake,
message_id: Snowflake,
audit_log_reason: Option<&str>,
user: &mut ChorusUser,
) -> ChorusResult<()> {
let request = ChorusRequest::new(
http::Method::PUT,
format!(
"{}/channels/{}/pins/{}",
user.belongs_to.read().unwrap().urls.api,
channel_id,
message_id
)
.as_str(),
None,
audit_log_reason,
None,
Some(user),
LimitType::Channel(channel_id),
);
request.handle_request_as_result(user).await
}
/// Unpins a message in a channel. Requires the `MANAGE_MESSAGES` permission. Returns a 204 empty response on success.
/// # Reference:
/// See: <https://discord-userdoccers.vercel.app/resources/message#unpin-message>
pub async fn unsticky(
channel_id: Snowflake,
message_id: Snowflake,
audit_log_reason: Option<&str>,
user: &mut ChorusUser,
) -> ChorusResult<()> {
let request = ChorusRequest::new(
http::Method::DELETE,
format!(
"{}/channels/{}/pins/{}",
user.belongs_to.read().unwrap().urls.api,
channel_id,
message_id
)
.as_str(),
None,
audit_log_reason,
None,
Some(user),
LimitType::Channel(channel_id),
);
request.handle_request_as_result(user).await
}
/// Returns a specific message object in the channel.
/// If operating on a guild channel, this endpoint requires the `READ_MESSAGE_HISTORY` permission to be present on the current user.
/// # Reference:
/// See: <https://discord-userdoccers.vercel.app/resources/message#get-message>
pub async fn get(
channel_id: Snowflake,
message_id: Snowflake,
user: &mut ChorusUser,
) -> ChorusResult<Message> {
let chorus_request = ChorusRequest {
request: Client::new()
.get(format!(
"{}/channels/{}/messages/{}",
user.belongs_to.read().unwrap().urls.api,
channel_id,
message_id
))
.header("Authorization", user.token())
.header("Content-Type", "application/json"),
limit_type: LimitType::Channel(channel_id),
};
chorus_request.deserialize_response::<Message>(user).await
}
/// Posts a greet message to a channel. This endpoint requires the channel is a DM channel or you reply to a system message.
/// # Reference:
/// See: <https://discord-userdoccers.vercel.app/resources/message#create-greet-message>
pub async fn create_greet(
channel_id: Snowflake,
schema: CreateGreetMessage,
user: &mut ChorusUser,
) -> ChorusResult<Message> {
let request = ChorusRequest::new(
http::Method::POST,
format!(
"{}/channels/{}/messages/greet",
user.belongs_to.read().unwrap().urls.api,
channel_id,
)
.as_str(),
Some(to_string(&schema).unwrap()),
None,
None,
Some(user),
LimitType::Channel(channel_id),
);
request.deserialize_response::<Message>(user).await
}
/// Sets the channel's latest acknowledged message (marks a message as read) for the current user.
/// The message ID parameter does not need to be a valid message ID, but it must be a valid snowflake.
/// If the message ID is being set to a message sent prior to the latest acknowledged one,
/// manual should be true or the resulting read state update should be ignored by clients (but is still saved), resulting in undefined behavior.
/// In this case, mention_count should also be set to the amount of mentions unacknowledged as it is not automatically calculated by Discord.
///
/// Returns an optional token, which can be used as the new `ack` token for following `ack`s.
///
/// # Reference:
/// See: <https://discord-userdoccers.vercel.app/resources/message#acknowledge-message>
pub async fn acknowledge(
channel_id: Snowflake,
message_id: Snowflake,
schema: MessageAck,
user: &mut ChorusUser,
) -> ChorusResult<Option<String>> {
let request = ChorusRequest::new(
http::Method::POST,
format!(
"{}/channels/{}/messages/{}/ack",
user.belongs_to.read().unwrap().urls.api,
channel_id,
message_id
)
.as_str(),
Some(to_string(&schema).unwrap()),
None,
None,
Some(user),
LimitType::Channel(channel_id),
);
request.deserialize_response::<Option<String>>(user).await
}
/// Crossposts a message in a News Channel to following channels.
/// This endpoint requires the `SEND_MESSAGES` permission, if the current user sent the message,
/// or additionally the `MANAGE_MESSAGES` permission, for all other messages, to be present for the current user.
///
/// # Reference:
/// See <https://discord-userdoccers.vercel.app/resources/message#crosspost-message>
pub async fn crosspost(
channel_id: Snowflake,
message_id: Snowflake,
user: &mut ChorusUser,
) -> ChorusResult<Message> {
let request = ChorusRequest::new(
http::Method::POST,
format!(
"{}/channels/{}/messages/{}/crosspost",
user.belongs_to.read().unwrap().urls.api,
channel_id,
message_id
)
.as_str(),
None,
None,
None,
Some(user),
LimitType::Channel(channel_id),
);
request.deserialize_response::<Message>(user).await
}
/// Hides a message from the feed of the guild the channel belongs to. Returns a 204 empty response on success.
///
/// # Reference:
/// See <https://discord-userdoccers.vercel.app/resources/message#hide-message-from-guild-feed>
pub async fn hide_from_guild_feed(
channel_id: Snowflake,
message_id: Snowflake,
user: &mut ChorusUser,
) -> ChorusResult<()> {
let url = format!(
"{}/channels/{}/messages/{}/hide-guild-feed",
user.belongs_to.read().unwrap().urls.api,
channel_id,
message_id
);
let chorus_request = ChorusRequest::new(
http::Method::DELETE,
&url,
None,
None,
None,
Some(user),
LimitType::Channel(channel_id),
);
chorus_request.handle_request_as_result(user).await
}
/// Edits a previously sent message. All fields can be edited by the original message author.
/// Other users can only edit flags and only if they have the MANAGE_MESSAGES permission in the corresponding channel.
/// When specifying flags, ensure to include all previously set flags/bits in addition to ones that you are modifying.
/// When the content field is edited, the mentions array in the message object will be reconstructed from scratch based on the new content.
/// The allowed_mentions field of the edit request controls how this happens.
/// If there is no explicit allowed_mentions in the edit request, the content will be parsed with default allowances, that is,
/// without regard to whether or not an allowed_mentions was present in the request that originally created the message.
///
/// # Reference:
/// See: <https://discord-userdoccers.vercel.app/resources/message#edit-message>
pub async fn modify(
channel_id: Snowflake,
message_id: Snowflake,
schema: MessageModifySchema,
user: &mut ChorusUser,
) -> ChorusResult<Message> {
let url = format!(
"{}/channels/{}/messages/{}",
user.belongs_to.read().unwrap().urls.api,
channel_id,
message_id
);
let chorus_request = ChorusRequest::new(
http::Method::PATCH,
&url,
Some(to_string(&schema).unwrap()),
None,
None,
Some(user),
LimitType::Channel(channel_id),
);
chorus_request.deserialize_response::<Message>(user).await
}
/// Deletes a message. If operating on a guild channel and trying to delete a message that was not sent by the current user,
/// this endpoint requires the `MANAGE_MESSAGES` permission. Returns a 204 empty response on success.
pub async fn delete(
channel_id: Snowflake,
message_id: Snowflake,
audit_log_reason: Option<String>,
user: &mut ChorusUser,
) -> ChorusResult<()> {
let url = format!(
"{}/channels/{}/messages/{}",
user.belongs_to.read().unwrap().urls.api,
channel_id,
message_id
);
let chorus_request = ChorusRequest::new(
http::Method::DELETE,
&url,
None,
audit_log_reason.as_deref(),
None,
Some(user),
LimitType::Channel(channel_id),
);
chorus_request.handle_request_as_result(user).await
}
/// Deletes multiple messages in a single request. This endpoint can only be used on guild channels and requires the MANAGE_MESSAGES permission.
/// Returns a 204 empty response on success.
///
/// **This endpoint will not delete messages older than 2 weeks, and will fail if any message provided is older than that or if any duplicate message IDs are provided.**
///
/// **This endpoint is not usable by user accounts.** (At least according to Discord.com. Spacebar behaviour may differ.)
///
/// # Reference:
/// See: <https://discord-userdoccers.vercel.app/resources/message#bulk-delete-messages>
pub async fn bulk_delete(
channel_id: Snowflake,
messages: Vec<Snowflake>,
audit_log_reason: Option<String>,
user: &mut ChorusUser,
) -> ChorusResult<()> {
if messages.len() < 2 {
return Err(ChorusError::InvalidArguments {
error: "`messages` must contain at least 2 entries.".to_string(),
});
}
let request = ChorusRequest::new(
http::Method::POST,
format!(
"{}/channels/{}/messages/bulk-delete",
user.belongs_to.read().unwrap().urls.api,
channel_id,
)
.as_str(),
Some(to_string(&messages).unwrap()),
audit_log_reason.as_deref(),
None,
Some(user),
LimitType::Channel(channel_id),
);
request.handle_request_as_result(user).await
}
/// Acknowledges the currently pinned messages in a channel. Returns a 204 empty response on success.
///
/// # Reference:
/// See: <https://discord-userdoccers.vercel.app/resources/message#acknowledge-pinned-messages>
pub async fn acknowledge_pinned(
channel_id: Snowflake,
user: &mut ChorusUser,
) -> ChorusResult<()> {
let chorus_request = ChorusRequest::new(
http::Method::POST,
format!(
"{}/channels/{}/pins/ack",
user.belongs_to.read().unwrap().urls.api,
channel_id,
)
.as_str(),
None,
None,
None,
Some(user),
LimitType::Channel(channel_id),
);
chorus_request.handle_request_as_result(user).await
}
}
impl UserMeta {
fn search_error(result_text: String) -> ChorusError {
ChorusError::InvalidResponse {
error: format!(
"Got unexpected Response, or Response which is not valid JSON. Response: \n{}",
result_text
),
}
}
impl ChorusUser {
/// Sends a message in the channel with the provided channel_id.
/// Returns the sent message.
///
/// # Notes
/// Shorthand call for [`Message::send`]
///
/// # Reference
/// See <https://discord-userdoccers.vercel.app/resources/message#create-message>
pub async fn send_message(
&mut self,
message: MessageSendSchema,
channel_id: Snowflake,
) -> Result<Message, crate::errors::ChorusError> {
) -> ChorusResult<Message> {
Message::send(self, channel_id, message).await
}
}
impl Channel {
/// Returns messages without the reactions key that match a search query in the channel.
/// The messages that are direct results will have an extra hit key set to true.
/// If operating on a guild channel, this endpoint requires the `READ_MESSAGE_HISTORY`
/// permission to be present on the current user.
///
/// If the guild/channel you are searching is not yet indexed, the endpoint will return a 202 accepted response.
/// In this case, the method will return a [`ChorusError::InvalidResponse`] error.
///
/// # Reference:
/// See <https://discord-userdoccers.vercel.app/resources/message#search-messages>
pub async fn search_messages(
channel_id: Snowflake,
query: MessageSearchQuery,
user: &mut ChorusUser,
) -> ChorusResult<Vec<Message>> {
Message::search(MessageSearchEndpoint::Channel(channel_id), query, user).await
}
}

View File

@ -2,33 +2,32 @@ use reqwest::Client;
use serde_json::to_string;
use crate::{
api::LimitType,
errors::{ChorusError, ChorusResult},
instance::UserMeta,
instance::ChorusUser,
ratelimiter::ChorusRequest,
types::{self, PermissionOverwrite, Snowflake},
types::{self, LimitType, PermissionOverwrite, Snowflake},
};
impl types::Channel {
/// Edits the permission overwrites for a channel.
/// Edits the permission overwrites for a user or role in a channel.
///
/// # Arguments
/// Only usable for guild channels.
///
/// * `user` - A mutable reference to a [`UserMeta`] instance.
/// * `channel_id` - A string slice representing the ID of the channel.
/// * `overwrite` - A [`PermissionOverwrite`] instance representing the new permission overwrites.
/// Requires the [`MANAGE_ROLES`](crate::types::PermissionFlags::MANAGE_ROLES) permission.
/// Only permissions you have in the guild or parent channel (if applicable) can be allowed/denied
/// (unless you have a [`MANAGE_ROLES`](crate::types::PermissionFlags::MANAGE_ROLES) overwrite in the channel).
///
/// # Returns
///
/// This function returns a result that is either [`Ok(())`] if the request is successful, or an [`Err(ChorusLibError)`].
pub async fn edit_permissions(
user: &mut UserMeta,
/// # Reference
/// See <https://discord-userdoccers.vercel.app/resources/channel#modify-channel-permissions>
pub async fn modify_permissions(
user: &mut ChorusUser,
channel_id: Snowflake,
audit_log_reason: Option<String>,
overwrite: PermissionOverwrite,
) -> ChorusResult<()> {
let url = format!(
"{}/channels/{}/permissions/{}",
user.belongs_to.borrow_mut().urls.api,
user.belongs_to.read().unwrap().urls.api,
channel_id,
overwrite.id
);
@ -40,39 +39,51 @@ impl types::Channel {
});
}
};
let mut request = Client::new()
.put(url)
.header("Authorization", user.token())
.header("Content-Type", "application/json")
.body(body);
if let Some(reason) = audit_log_reason {
request = request.header("X-Audit-Log-Reason", reason);
}
let chorus_request = ChorusRequest {
request: Client::new().put(url).bearer_auth(user.token()).body(body),
request,
limit_type: LimitType::Channel(channel_id),
};
chorus_request.handle_request_as_result(user).await
}
/// Deletes a permission overwrite for a channel.
/// Deletes a permission overwrite for a user or role in a channel.
///
/// # Arguments
/// Only usable for guild channels.
///
/// * `user` - A mutable reference to a [`UserMeta`] instance.
/// * `channel_id` - A string slice representing the ID of the channel.
/// * `overwrite_id` - A string slice representing the ID of the permission overwrite to delete.
/// Requires the [`MANAGE_ROLES`](crate::types::PermissionFlags::MANAGE_ROLES) permission.
///
/// # Returns
///
/// This function returns a Result that is either [`Ok(())`] if the request is successfulm or an [`Err(ChorusLibError)`].
/// # Reference
/// See <https://discord-userdoccers.vercel.app/resources/channel#delete-channel-permission>
pub async fn delete_permission(
user: &mut UserMeta,
user: &mut ChorusUser,
channel_id: Snowflake,
overwrite_id: Snowflake,
) -> ChorusResult<()> {
let url = format!(
"{}/channels/{}/permissions/{}",
user.belongs_to.borrow_mut().urls.api,
user.belongs_to.read().unwrap().urls.api,
channel_id,
overwrite_id
);
let chorus_request = ChorusRequest {
request: Client::new().delete(url).bearer_auth(user.token()),
limit_type: LimitType::Channel(channel_id),
};
chorus_request.handle_request_as_result(user).await
let request = ChorusRequest::new(
http::Method::DELETE,
&url,
None,
None,
None,
Some(user),
LimitType::Channel(channel_id),
);
request.handle_request_as_result(user).await
}
}

View File

@ -1,16 +1,12 @@
use reqwest::Client;
use crate::{
api::LimitType,
errors::ChorusResult,
instance::UserMeta,
instance::ChorusUser,
ratelimiter::ChorusRequest,
types::{self, PublicUser, Snowflake},
types::{self, LimitType, PublicUser, Snowflake},
};
/**
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)]
pub struct ReactionMeta {
pub message_id: types::Snowflake,
pub channel_id: types::Snowflake,
@ -18,169 +14,189 @@ pub struct ReactionMeta {
impl ReactionMeta {
/// Deletes all reactions for a message.
/// This endpoint requires the `MANAGE_MESSAGES` permission to be present on the current user.
/// # Arguments
/// * `user` - A mutable reference to a [`UserMeta`] instance.
/// # Returns
/// A `Result` [`()`] [`crate::errors::ChorusLibError`] if something went wrong.
/// Fires a `Message Reaction Remove All` Gateway event.
///
/// This endpoint requires the [`MANAGE_MESSAGES`](crate::types::PermissionFlags::MANAGE_MESSAGES) permission.
///
/// # Reference
/// See [https://discord.com/developers/docs/resources/channel#delete-all-reactions](https://discord.com/developers/docs/resources/channel#delete-all-reactions)
pub async fn delete_all(&self, user: &mut UserMeta) -> ChorusResult<()> {
/// See <https://discord.com/developers/docs/resources/channel#delete-all-reactions>
pub async fn delete_all(&self, user: &mut ChorusUser) -> ChorusResult<()> {
let url = format!(
"{}/channels/{}/messages/{}/reactions/",
user.belongs_to.borrow().urls.api,
"{}/channels/{}/messages/{}/reactions",
user.belongs_to.read().unwrap().urls.api,
self.channel_id,
self.message_id
);
let chorus_request = ChorusRequest {
request: Client::new().delete(url).bearer_auth(user.token()),
limit_type: LimitType::Channel(self.channel_id),
};
chorus_request.handle_request_as_result(user).await
let request = ChorusRequest::new(
http::Method::DELETE,
&url,
None,
None,
None,
Some(user),
LimitType::Channel(self.channel_id),
);
request.handle_request_as_result(user).await
}
/// Gets a list of users that reacted with a specific emoji to a message.
/// # Arguments
/// * `emoji` - A string slice containing the emoji to search for. The emoji must be URL Encoded or
/// the request will fail with 10014: Unknown Emoji. To use custom emoji, you must encode it in the
/// format name:id with the emoji name and emoji id.
/// * `user` - A mutable reference to a [`UserMeta`] instance.
/// # Returns
/// A Result that is [`Err(crate::errors::ChorusLibError)`] if something went wrong.
/// # Reference
/// See [https://discord.com/developers/docs/resources/channel#get-reactions](https://discord.com/developers/docs/resources/channel#get-reactions)
pub async fn get(&self, emoji: &str, user: &mut UserMeta) -> ChorusResult<Vec<PublicUser>> {
let url = format!(
"{}/channels/{}/messages/{}/reactions/{}/",
user.belongs_to.borrow().urls.api,
self.channel_id,
self.message_id,
emoji
);
let chorus_request = ChorusRequest {
request: Client::new().get(url).bearer_auth(user.token()),
limit_type: LimitType::Channel(self.channel_id),
};
chorus_request
.deserialize_response::<Vec<PublicUser>>(user)
.await
}
/// Deletes all the reactions for a given `emoji` on a message. This endpoint requires the
/// MANAGE_MESSAGES permission to be present on the current user.
/// # Arguments
/// * `emoji` - A string slice containing the emoji to delete. The `emoji` must be URL Encoded or
/// the request will fail with 10014: Unknown Emoji. To use custom emoji, you must encode it in the
/// format name:id with the emoji name and emoji id.
/// * `user` - A mutable reference to a [`UserMeta`] instance.
/// # Returns
/// A Result that is [`Err(crate::errors::ChorusLibError)`] if something went wrong.
/// Fires a `Message Reaction Remove Emoji` Gateway event.
/// # Reference
/// See [https://discord.com/developers/docs/resources/channel#delete-all-reactions-for-emoji](https://discord.com/developers/docs/resources/channel#delete-all-reactions-for-emoji)
pub async fn delete_emoji(&self, emoji: &str, user: &mut UserMeta) -> ChorusResult<()> {
let url = format!(
"{}/channels/{}/messages/{}/reactions/{}/",
user.belongs_to.borrow().urls.api,
self.channel_id,
self.message_id,
emoji
);
let chorus_request = ChorusRequest {
request: Client::new().delete(url).bearer_auth(user.token()),
limit_type: LimitType::Channel(self.channel_id),
};
chorus_request.handle_request_as_result(user).await
}
/// Create a reaction for the message.
/// This endpoint requires the READ_MESSAGE_HISTORY permission
/// to be present on the current user. Additionally, if nobody else has reacted to the message using
/// this emoji, this endpoint requires the ADD_REACTIONS permission to be present on the current
/// user.
/// # Arguments
/// * `emoji` - A string slice containing the emoji to delete. The `emoji` must be URL Encoded or
/// the request will fail with 10014: Unknown Emoji. To use custom emoji, you must encode it in the
/// format name:id with the emoji name and emoji id.
/// * `user` - A mutable reference to a [`UserMeta`] instance.
/// # Returns
/// A `Result` containing [`()`] or a [`crate::errors::ChorusLibError`].
/// # Reference
/// See [https://discord.com/developers/docs/resources/channel#create-reaction](https://discord.com/developers/docs/resources/channel#create-reaction)
///
pub async fn create(&self, emoji: &str, user: &mut UserMeta) -> ChorusResult<()> {
/// The emoji must be URL Encoded or the request will fail with 10014: Unknown Emoji.
/// To use custom emoji, the format of the emoji string must be name:id.
///
/// # Reference
/// See <https://discord.com/developers/docs/resources/channel#get-reactions>
pub async fn get(&self, emoji: &str, user: &mut ChorusUser) -> ChorusResult<Vec<PublicUser>> {
let url = format!(
"{}/channels/{}/messages/{}/reactions/{}/@me/",
user.belongs_to.borrow().urls.api,
"{}/channels/{}/messages/{}/reactions/{}",
user.belongs_to.read().unwrap().urls.api,
self.channel_id,
self.message_id,
emoji
);
let chorus_request = ChorusRequest {
request: Client::new().put(url).bearer_auth(user.token()),
limit_type: LimitType::Channel(self.channel_id),
};
chorus_request.handle_request_as_result(user).await
let request = ChorusRequest::new(
http::Method::GET,
&url,
None,
None,
None,
Some(user),
LimitType::Channel(self.channel_id),
);
request.deserialize_response::<Vec<PublicUser>>(user).await
}
/// Delete a reaction the current user has made for the message.
/// # Arguments
/// * `emoji` - A string slice containing the emoji to delete. The `emoji` must be URL Encoded or
/// the request will fail with 10014: Unknown Emoji. To use custom emoji, you must encode it in the
/// format name:id with the emoji name and emoji id.
/// * `user` - A mutable reference to a [`UserMeta`] instance.
/// # Returns
/// A `Result` containing [`()`] or a [`crate::errors::ChorusLibError`].
/// Fires a `Message Reaction Remove` Gateway event.
/// Deletes all the reactions for a given emoji on a message.
///
/// This endpoint requires the [`MANAGE_MESSAGES`](crate::types::PermissionFlags::MANAGE_MESSAGES) permission.
///
/// The emoji must be URL Encoded or the request will fail with 10014: Unknown Emoji.
/// To use custom emoji, the format of the emoji string must be name:id.
///
/// # Reference
/// See [https://discord.com/developers/docs/resources/channel#delete-own-reaction](https://discord.com/developers/docs/resources/channel#delete-own-reaction)
pub async fn remove(&self, emoji: &str, user: &mut UserMeta) -> ChorusResult<()> {
/// See <https://discord.com/developers/docs/resources/channel#delete-all-reactions-for-emoji>
pub async fn delete_emoji(&self, emoji: &str, user: &mut ChorusUser) -> ChorusResult<()> {
let url = format!(
"{}/channels/{}/messages/{}/reactions/{}/@me/",
user.belongs_to.borrow().urls.api,
"{}/channels/{}/messages/{}/reactions/{}",
user.belongs_to.read().unwrap().urls.api,
self.channel_id,
self.message_id,
emoji
);
let chorus_request = ChorusRequest {
request: Client::new().delete(url).bearer_auth(user.token()),
limit_type: LimitType::Channel(self.channel_id),
};
chorus_request.handle_request_as_result(user).await
let request = ChorusRequest::new(
http::Method::DELETE,
&url,
None,
None,
None,
Some(user),
LimitType::Channel(self.channel_id),
);
request.handle_request_as_result(user).await
}
/// Delete a user's reaction to a message.
/// This endpoint requires the MANAGE_MESSAGES permission to be present on the current user.
/// # Arguments
/// * `user_id` - ID of the user whose reaction is to be deleted.
/// * `emoji` - A string slice containing the emoji to delete. The `emoji` must be URL Encoded or
/// the request will fail with 10014: Unknown Emoji. To use custom emoji, you must encode it in the
/// format name:id with the emoji name and emoji id.
/// * `user` - A mutable reference to a [`UserMeta`] instance.
/// # Returns
/// A `Result` containing [`()`] or a [`crate::errors::ChorusLibError`].
/// Fires a Message Reaction Remove Gateway event.
/// Create a reaction on a message.
///
/// This endpoint requires the [`READ_MESSAGE_HISTORY`](crate::types::PermissionFlags::READ_MESSAGE_HISTORY) permission.
///
/// Additionally, if nobody else has reacted to the message using this emoji,
/// this endpoint requires the [`ADD_REACTIONS`](crate::types::PermissionFlags::ADD_REACTIONS) permission.
///
/// The emoji must be URL Encoded or the request will fail with 10014: Unknown Emoji.
/// To use custom emoji, the format of the emoji string must be `name:id`.
///
/// # Reference
/// See [https://discord.com/developers/docs/resources/channel#delete-own-reaction](https://discord.com/developers/docs/resources/channel#delete-own-reaction)
/// See <https://discord.com/developers/docs/resources/channel#create-reaction>
pub async fn create(&self, emoji: &str, user: &mut ChorusUser) -> ChorusResult<()> {
let url = format!(
"{}/channels/{}/messages/{}/reactions/{}/@me",
user.belongs_to.read().unwrap().urls.api,
self.channel_id,
self.message_id,
emoji
);
let request = ChorusRequest::new(
http::Method::PUT,
&url,
None,
None,
None,
Some(user),
LimitType::Channel(self.channel_id),
);
request.handle_request_as_result(user).await
}
/// Deletes a reaction the current user has made to the message.
///
/// The reaction emoji must be URL Encoded or the request will fail with 10014: Unknown Emoji.
/// To use custom emoji, the format of the emoji string must be name:id.
///
/// # Reference
/// See <https://discord.com/developers/docs/resources/channel#delete-own-reaction>
pub async fn remove(&self, emoji: &str, user: &mut ChorusUser) -> ChorusResult<()> {
let url = format!(
"{}/channels/{}/messages/{}/reactions/{}/@me",
user.belongs_to.read().unwrap().urls.api,
self.channel_id,
self.message_id,
emoji
);
let request = ChorusRequest::new(
http::Method::DELETE,
&url,
None,
None,
None,
Some(user),
LimitType::Channel(self.channel_id),
);
request.handle_request_as_result(user).await
}
/// Deletes a user's reaction to a message.
///
/// This endpoint requires the [`MANAGE_MESSAGES`](crate::types::PermissionFlags::MANAGE_MESSAGES) permission.
///
/// The reaction emoji must be URL Encoded or the request will fail with 10014: Unknown Emoji.
/// To use custom emoji, the format of the emoji string must be name:id.
///
/// # Reference
/// See <https://discord.com/developers/docs/resources/channel#delete-user-reaction>
pub async fn delete_user(
&self,
user_id: Snowflake,
emoji: &str,
user: &mut UserMeta,
user: &mut ChorusUser,
) -> ChorusResult<()> {
let url = format!(
"{}/channels/{}/messages/{}/reactions/{}/{}",
user.belongs_to.borrow().urls.api,
user.belongs_to.read().unwrap().urls.api,
self.channel_id,
self.message_id,
emoji,
user_id
);
let chorus_request = ChorusRequest {
request: Client::new().delete(url).bearer_auth(user.token()),
limit_type: LimitType::Channel(self.channel_id),
};
chorus_request.handle_request_as_result(user).await
let request = ChorusRequest::new(
http::Method::DELETE,
&url,
None,
None,
None,
Some(user),
LimitType::Channel(self.channel_id),
);
request.handle_request_as_result(user).await
}
}

View File

@ -2,57 +2,41 @@ use reqwest::Client;
use serde_json::from_str;
use serde_json::to_string;
use crate::api::LimitType;
use crate::errors::ChorusError;
use crate::errors::ChorusResult;
use crate::instance::UserMeta;
use crate::instance::ChorusUser;
use crate::ratelimiter::ChorusRequest;
use crate::types::Snowflake;
use crate::types::{Channel, ChannelCreateSchema, Guild, GuildCreateSchema};
use crate::types::{
Channel, ChannelCreateSchema, Guild, GuildBanCreateSchema, GuildBansQuery, GuildCreateSchema,
GuildMember, GuildMemberSearchSchema, GuildModifySchema, GuildPreview, LimitType,
ModifyGuildMemberProfileSchema, ModifyGuildMemberSchema, UserProfileMetadata,
};
use crate::types::{GuildBan, Snowflake};
impl Guild {
/// Creates a new guild with the given parameters.
///
/// # Arguments
///
/// * `user` - A mutable reference to the user creating the guild.
/// * `instance` - A mutable reference to the instance where the guild will be created.
/// * `guild_create_schema` - A reference to the schema containing the guild creation parameters.
///
/// # Returns
///
/// A `Result<Guild>` containing the object of the newly created guild, or an error if the request fails.
///
/// # Errors
///
/// Returns an `ChorusLibError` if the request fails.
/// Creates a new guild.
///
/// # Reference
/// See <https://discord-userdoccers.vercel.app/resources/guild#create-guild>
pub async fn create(
user: &mut UserMeta,
user: &mut ChorusUser,
guild_create_schema: GuildCreateSchema,
) -> ChorusResult<Guild> {
let url = format!("{}/guilds/", user.belongs_to.borrow().urls.api);
let url = format!("{}/guilds", user.belongs_to.read().unwrap().urls.api);
let chorus_request = ChorusRequest {
request: Client::new()
.post(url.clone())
.bearer_auth(user.token.clone())
.header("Authorization", user.token.clone())
.header("Content-Type", "application/json")
.body(to_string(&guild_create_schema).unwrap()),
limit_type: LimitType::Global,
};
chorus_request.deserialize_response::<Guild>(user).await
}
/// Deletes a guild.
/// Deletes a guild by its id.
///
/// # Arguments
///
/// * `user` - A mutable reference to a `User` instance.
/// * `instance` - A mutable reference to an `Instance` instance.
/// * `guild_id` - ID of the guild to delete.
///
/// # Returns
///
/// An `Result` containing an `ChorusLibError` if an error occurred during the request, otherwise `()`.
/// User must be the owner.
///
/// # Example
///
@ -61,65 +45,63 @@ impl Guild {
/// let mut instance = Instance::new();
/// let guild_id = String::from("1234567890");
///
/// match Guild::delete(&mut user, &mut instance, guild_id) {
/// Some(e) => println!("Error deleting guild: {:?}", e),
/// None => println!("Guild deleted successfully"),
/// match Guild::delete(&mut user, guild_id) {
/// Err(e) => println!("Error deleting guild: {:?}", e),
/// Ok(_) => println!("Guild deleted successfully"),
/// }
/// ```
pub async fn delete(user: &mut UserMeta, guild_id: Snowflake) -> ChorusResult<()> {
///
/// # Reference
/// See <https://discord-userdoccers.vercel.app/resources/guild#delete-guild>
pub async fn delete(user: &mut ChorusUser, guild_id: Snowflake) -> ChorusResult<()> {
let url = format!(
"{}/guilds/{}/delete/",
user.belongs_to.borrow().urls.api,
"{}/guilds/{}/delete",
user.belongs_to.read().unwrap().urls.api,
guild_id
);
let chorus_request = ChorusRequest {
request: Client::new()
.post(url.clone())
.bearer_auth(user.token.clone()),
.header("Authorization", user.token.clone())
.header("Content-Type", "application/json"),
limit_type: LimitType::Global,
};
chorus_request.handle_request_as_result(user).await
}
/// Sends a request to create a new channel in the guild.
/// Creates a new channel in a guild.
///
/// # Arguments
/// Requires the [MANAGE_CHANNELS](crate::types::PermissionFlags::MANAGE_CHANNELS) permission.
///
/// * `url_api` - The base URL for the Discord API.
/// * `token` - A Discord bot token.
/// * `schema` - A `ChannelCreateSchema` struct containing the properties of the new channel.
/// * `limits_user` - A mutable reference to a `Limits` struct containing the user's rate limits.
/// * `limits_instance` - A mutable reference to a `Limits` struct containing the instance's rate limits.
/// # Notes
/// This method is a wrapper for [Channel::create].
///
/// # Returns
///
/// A `Result` containing a `reqwest::Response` if the request was successful, or an `ChorusLibError` if there was an error.
/// # Reference
/// See <https://discord-userdoccers.vercel.app/resources/channel#create-guild-channel>
pub async fn create_channel(
&self,
user: &mut UserMeta,
user: &mut ChorusUser,
audit_log_reason: Option<String>,
schema: ChannelCreateSchema,
) -> ChorusResult<Channel> {
Channel::create(user, self.id, schema).await
Channel::create(user, self.id, audit_log_reason, schema).await
}
/// Returns a `Result` containing a vector of `Channel` structs if the request was successful, or an `ChorusLibError` if there was an error.
/// Returns a list of the guild's channels.
///
/// # Arguments
/// Doesn't include threads.
///
/// * `url_api` - A string slice that holds the URL of the API.
/// * `token` - A string slice that holds the authorization token.
/// * `limits_user` - A mutable reference to a `Limits` struct containing the user's rate limits.
/// * `limits_instance` - A mutable reference to a `Limits` struct containing the instance's rate limits.
///
pub async fn channels(&self, user: &mut UserMeta) -> ChorusResult<Vec<Channel>> {
/// # Reference
/// See <https://discord-userdoccers.vercel.app/resources/channel#get-guild-channels>
pub async fn channels(&self, user: &mut ChorusUser) -> ChorusResult<Vec<Channel>> {
let chorus_request = ChorusRequest {
request: Client::new()
.get(format!(
"{}/guilds/{}/channels/",
user.belongs_to.borrow().urls.api,
"{}/guilds/{}/channels",
user.belongs_to.read().unwrap().urls.api,
self.id
))
.bearer_auth(user.token()),
.header("Authorization", user.token()),
limit_type: LimitType::Channel(self.id),
};
let result = chorus_request.send_request(user).await?;
@ -141,61 +123,381 @@ impl Guild {
};
}
/// Returns a `Result` containing a `Guild` struct if the request was successful, or an `ChorusLibError` if there was an error.
/// Fetches a guild by its id.
///
/// # Arguments
///
/// * `url_api` - A string slice that holds the URL of the API.
/// * `guild_id` - ID of the guild.
/// * `token` - A string slice that holds the authorization token.
/// * `limits_user` - A mutable reference to a `Limits` struct containing the user's rate limits.
/// * `limits_instance` - A mutable reference to a `Limits` struct containing the instance's rate limits.
///
pub async fn get(guild_id: Snowflake, user: &mut UserMeta) -> ChorusResult<Guild> {
/// # Reference
/// See <https://discord-userdoccers.vercel.app/resources/guild#get-guild>
pub async fn get(guild_id: Snowflake, user: &mut ChorusUser) -> ChorusResult<Guild> {
let chorus_request = ChorusRequest {
request: Client::new()
.get(format!(
"{}/guilds/{}/",
user.belongs_to.borrow().urls.api,
"{}/guilds/{}",
user.belongs_to.read().unwrap().urls.api,
guild_id
))
.bearer_auth(user.token()),
.header("Authorization", user.token()),
limit_type: LimitType::Guild(guild_id),
};
let response = chorus_request.deserialize_response::<Guild>(user).await?;
Ok(response)
}
pub async fn create_ban(
guild_id: Snowflake,
user_id: Snowflake,
audit_log_reason: Option<String>,
schema: GuildBanCreateSchema,
user: &mut ChorusUser,
) -> ChorusResult<()> {
// FIXME: Return GuildBan instead of (). Requires <https://github.com/spacebarchat/server/issues/1096> to be resolved.
let request = ChorusRequest::new(
http::Method::PUT,
format!(
"{}/guilds/{}/bans/{}",
user.belongs_to.read().unwrap().urls.api,
guild_id,
user_id
)
.as_str(),
Some(to_string(&schema).unwrap()),
audit_log_reason.as_deref(),
None,
Some(user),
LimitType::Guild(guild_id),
);
request.handle_request_as_result(user).await
}
/// # Reference
/// <https://discord-userdoccers.vercel.app/resources/guild#modify-guild>
pub async fn modify(
guild_id: Snowflake,
schema: GuildModifySchema,
user: &mut ChorusUser,
) -> ChorusResult<Guild> {
let chorus_request = ChorusRequest {
request: Client::new()
.patch(format!(
"{}/guilds/{}",
user.belongs_to.read().unwrap().urls.api,
guild_id,
))
.header("Authorization", user.token())
.header("Content-Type", "application/json")
.body(to_string(&schema).unwrap()),
limit_type: LimitType::Guild(guild_id),
};
let response = chorus_request.deserialize_response::<Guild>(user).await?;
Ok(response)
}
/// Returns a guild preview object for the given guild ID. If the user is not in the guild, the guild must be discoverable.
/// # Reference:
///
/// See <https://discord-userdoccers.vercel.app/resources/guild#get-guild-preview>
pub async fn get_preview(
guild_id: Snowflake,
user: &mut ChorusUser,
) -> ChorusResult<GuildPreview> {
let chorus_request = ChorusRequest {
request: Client::new()
.patch(format!(
"{}/guilds/{}/preview",
user.belongs_to.read().unwrap().urls.api,
guild_id,
))
.header("Authorization", user.token())
.header("Content-Type", "application/json"),
limit_type: LimitType::Guild(guild_id),
};
let response = chorus_request
.deserialize_response::<GuildPreview>(user)
.await?;
Ok(response)
}
/// Returns a list of guild member objects that are members of the guild.
///
/// # Reference
/// See <https://discord-userdoccers.vercel.app/resources/guild#get-guild-members>
pub async fn get_members(
guild_id: Snowflake,
user: &mut ChorusUser,
) -> ChorusResult<Vec<GuildMember>> {
let request = ChorusRequest::new(
http::Method::GET,
format!(
"{}/guilds/{}/members",
user.belongs_to.read().unwrap().urls.api,
guild_id,
)
.as_str(),
None,
None,
None,
Some(user),
LimitType::Guild(guild_id),
);
request.deserialize_response::<Vec<GuildMember>>(user).await
}
/// Returns a list of guild member objects whose username or nickname starts with a provided string.
///
/// # Reference:
/// See <https://discord-userdoccers.vercel.app/resources/guild#search-guild-members>
pub async fn search_members(
guild_id: Snowflake,
query: GuildMemberSearchSchema,
user: &mut ChorusUser,
) -> ChorusResult<Vec<GuildMember>> {
let mut request = ChorusRequest::new(
http::Method::GET,
format!(
"{}/guilds/{}/members/search",
user.belongs_to.read().unwrap().urls.api,
guild_id,
)
.as_str(),
None,
None,
None,
Some(user),
LimitType::Guild(guild_id),
);
request.request = request
.request
.query(&[("query", to_string(&query).unwrap())]);
request.deserialize_response::<Vec<GuildMember>>(user).await
}
/// Removes a member from a guild. Requires the KICK_MEMBERS permission. Returns a 204 empty response on success.
///
/// # Reference
/// See <https://discord-userdoccers.vercel.app/resources/guild#remove-guild-member>
pub async fn remove_member(
guild_id: Snowflake,
member_id: Snowflake,
audit_log_reason: Option<String>,
user: &mut ChorusUser,
) -> ChorusResult<()> {
let request = ChorusRequest::new(
http::Method::DELETE,
format!(
"{}/guilds/{}/members/{}",
user.belongs_to.read().unwrap().urls.api,
guild_id,
member_id,
)
.as_str(),
None,
audit_log_reason.as_deref(),
None,
Some(user),
LimitType::Guild(guild_id),
);
request.handle_request_as_result(user).await
}
/// Modifies attributes of a guild member. Returns the updated guild member object on success.
/// For required Permissions and an API reference, see:
///
/// # Reference:
/// <https://discord-userdoccers.vercel.app/resources/guild#modify-guild-member>
pub async fn modify_member(
guild_id: Snowflake,
member_id: Snowflake,
schema: ModifyGuildMemberSchema,
audit_log_reason: Option<String>,
user: &mut ChorusUser,
) -> ChorusResult<GuildMember> {
let request = ChorusRequest::new(
http::Method::PATCH,
format!(
"{}/guilds/{}/members/{}",
user.belongs_to.read().unwrap().urls.api,
guild_id,
member_id,
)
.as_str(),
Some(to_string(&schema).unwrap()),
audit_log_reason.as_deref(),
None,
Some(user),
LimitType::Guild(guild_id),
);
request.deserialize_response::<GuildMember>(user).await
}
/// Modifies the current user's member in the guild.
///
/// # Reference:
/// See <https://discord-userdoccers.vercel.app/resources/guild#modify-current-guild-member>
pub async fn modify_current_member(
guild_id: Snowflake,
schema: ModifyGuildMemberSchema,
audit_log_reason: Option<String>,
user: &mut ChorusUser,
) -> ChorusResult<GuildMember> {
let request = ChorusRequest::new(
http::Method::PATCH,
format!(
"{}/guilds/{}/members/@me",
user.belongs_to.read().unwrap().urls.api,
guild_id,
)
.as_str(),
Some(to_string(&schema).unwrap()),
audit_log_reason.as_deref(),
None,
Some(user),
LimitType::Guild(guild_id),
);
request.deserialize_response::<GuildMember>(user).await
}
/// Modifies the current user's profile in the guild.
///
/// # Reference:
/// See <https://discord-userdoccers.vercel.app/resources/guild#modify-guild-member-profile>
pub async fn modify_current_member_profile(
guild_id: Snowflake,
schema: ModifyGuildMemberProfileSchema,
user: &mut ChorusUser,
) -> ChorusResult<UserProfileMetadata> {
let request = ChorusRequest::new(
http::Method::PATCH,
format!(
"{}/guilds/{}/profile/@me",
user.belongs_to.read().unwrap().urls.api,
guild_id,
)
.as_str(),
Some(to_string(&schema).unwrap()),
None,
None,
Some(user),
LimitType::Guild(guild_id),
);
request
.deserialize_response::<UserProfileMetadata>(user)
.await
}
/// Returns a list of ban objects for the guild. Requires the `BAN_MEMBERS` permission.
///
/// # Reference:
/// See <https://discord-userdoccers.vercel.app/resources/guild#get-guild-bans>
pub async fn get_bans(
user: &mut ChorusUser,
guild_id: Snowflake,
query: Option<GuildBansQuery>,
) -> ChorusResult<Vec<GuildBan>> {
let url = format!(
"{}/guilds/{}/bans",
user.belongs_to.read().unwrap().urls.api,
guild_id,
);
let mut request = ChorusRequest::new(
http::Method::GET,
&url,
None,
None,
None,
Some(user),
LimitType::Guild(guild_id),
);
if let Some(query) = query {
request.request = request.request.query(&to_string(&query).unwrap());
}
request.deserialize_response::<Vec<GuildBan>>(user).await
}
/// Returns a ban object for the given user. Requires the `BAN_MEMBERS` permission.
///
/// # Reference:
/// See <https://discord-userdoccers.vercel.app/resources/guild#get-guild-ban>
pub async fn get_ban(
user: &mut ChorusUser,
guild_id: Snowflake,
user_id: Snowflake,
) -> ChorusResult<GuildBan> {
let url = format!(
"{}/guilds/{}/bans/{}",
user.belongs_to.read().unwrap().urls.api,
guild_id,
user_id
);
let request = ChorusRequest::new(
http::Method::GET,
&url,
None,
None,
None,
Some(user),
LimitType::Guild(guild_id),
);
request.deserialize_response::<GuildBan>(user).await
}
/// Removes the ban for a user. Requires the BAN_MEMBERS permissions. Returns a 204 empty response on success.
///
/// # Reference:
/// See <https://discord-userdoccers.vercel.app/resources/guild#delete-guild-ban>
pub async fn delete_ban(
user: &mut ChorusUser,
guild_id: Snowflake,
user_id: Snowflake,
audit_log_reason: Option<String>,
) -> ChorusResult<()> {
let url = format!(
"{}/guilds/{}/bans/{}",
user.belongs_to.read().unwrap().urls.api,
guild_id,
user_id
);
let request = ChorusRequest::new(
http::Method::DELETE,
&url,
None,
audit_log_reason.as_deref(),
None,
Some(user),
LimitType::Guild(guild_id),
);
request.handle_request_as_result(user).await
}
}
impl Channel {
/// Sends a request to create a new channel in a guild.
/// Creates a new channel in a guild.
///
/// # Arguments
/// Requires the [MANAGE_CHANNELS](crate::types::PermissionFlags::MANAGE_CHANNELS) permission.
///
/// * `token` - A Discord bot token.
/// * `url_api` - The base URL for the Discord API.
/// * `guild_id` - The ID of the guild where the channel will be created.
/// * `schema` - A `ChannelCreateSchema` struct containing the properties of the new channel.
/// * `limits_user` - A mutable reference to a `Limits` struct containing the user's rate limits.
/// * `limits_instance` - A mutable reference to a `Limits` struct containing the instance's rate limits.
///
/// # Returns
///
/// A `Result` containing a `reqwest::Response` if the request was successful, or an `ChorusLibError` if there was an error.
/// # Reference
/// See <https://discord-userdoccers.vercel.app/resources/channel#create-guild-channel>
pub async fn create(
user: &mut UserMeta,
user: &mut ChorusUser,
guild_id: Snowflake,
audit_log_reason: Option<String>,
schema: ChannelCreateSchema,
) -> ChorusResult<Channel> {
let chorus_request = ChorusRequest {
request: Client::new()
let mut request = Client::new()
.post(format!(
"{}/guilds/{}/channels/",
user.belongs_to.borrow().urls.api,
"{}/guilds/{}/channels",
user.belongs_to.read().unwrap().urls.api,
guild_id
))
.bearer_auth(user.token())
.body(to_string(&schema).unwrap()),
.header("Authorization", user.token())
.header("Content-Type", "application/json")
.body(to_string(&schema).unwrap());
if let Some(reason) = audit_log_reason {
request = request.header("X-Audit-Log-Reason", reason);
}
let chorus_request = ChorusRequest {
request,
limit_type: LimitType::Guild(guild_id),
};
chorus_request.deserialize_response::<Channel>(user).await

View File

@ -1,72 +1,61 @@
use reqwest::Client;
use crate::{
api::LimitType,
errors::ChorusResult,
instance::UserMeta,
instance::ChorusUser,
ratelimiter::ChorusRequest,
types::{self, Snowflake},
types::{self, GuildMember, LimitType, Snowflake},
};
impl types::GuildMember {
/// Retrieves a guild member by their ID.
/// Retrieves a guild member.
///
/// # Arguments
///
/// * `user` - A mutable reference to a [`UserMeta`] instance.
/// * `guild_id` - The ID of the guild.
/// * `member_id` - The ID of the member.
///
/// # Returns
///
/// A [`Result`] containing a [`GuildMember`] if the request succeeds, or a [`ChorusLibError`] if the request fails.
/// # Reference
/// See <https://discord-userdoccers.vercel.app/resources/guild#get-guild-member>
pub async fn get(
user: &mut UserMeta,
user: &mut ChorusUser,
guild_id: Snowflake,
member_id: Snowflake,
) -> ChorusResult<types::GuildMember> {
) -> ChorusResult<GuildMember> {
let url = format!(
"{}/guilds/{}/members/{}/",
user.belongs_to.borrow().urls.api,
"{}/guilds/{}/members/{}",
user.belongs_to.read().unwrap().urls.api,
guild_id,
member_id
);
let chorus_request = ChorusRequest {
request: Client::new().get(url).bearer_auth(user.token()),
request: Client::new().get(url).header("Authorization", user.token()),
limit_type: LimitType::Guild(guild_id),
};
chorus_request
.deserialize_response::<types::GuildMember>(user)
.deserialize_response::<GuildMember>(user)
.await
}
/// Adds a role to a guild member.
///
/// # Arguments
/// Requires the [`MANAGE_ROLES`](crate::types::PermissionFlags::MANAGE_ROLES) permission.
///
/// * `user` - A mutable reference to a `UserMeta` instance.
/// * `guild_id` - The ID of the guild.
/// * `member_id` - The ID of the member.
/// * `role_id` - The ID of the role to add.
///
/// # Returns
///
/// An `Result` containing a `ChorusLibError` if the request fails, or `()` if the request succeeds.
/// # Reference
/// See <https://discord-userdoccers.vercel.app/resources/guild#add-guild-member-role>
pub async fn add_role(
user: &mut UserMeta,
user: &mut ChorusUser,
guild_id: Snowflake,
member_id: Snowflake,
role_id: Snowflake,
) -> ChorusResult<()> {
let url = format!(
"{}/guilds/{}/members/{}/roles/{}/",
user.belongs_to.borrow().urls.api,
"{}/guilds/{}/members/{}/roles/{}",
user.belongs_to.read().unwrap().urls.api,
guild_id,
member_id,
role_id
);
let chorus_request = ChorusRequest {
request: Client::new().put(url).bearer_auth(user.token()),
request: Client::new()
.put(url)
.header("Authorization", user.token())
.header("Content-Type", "application/json"),
limit_type: LimitType::Guild(guild_id),
};
chorus_request.handle_request_as_result(user).await
@ -74,31 +63,27 @@ impl types::GuildMember {
/// Removes a role from a guild member.
///
/// # Arguments
/// Requires the [`MANAGE_ROLES`](crate::types::PermissionFlags::MANAGE_ROLES) permission.
///
/// * `user` - A mutable reference to a `UserMeta` instance.
/// * `guild_id` - The ID of the guild.
/// * `member_id` - The ID of the member.
/// * `role_id` - The ID of the role to remove.
///
/// # Returns
///
/// A `Result` containing a `ChorusLibError` if the request fails, or `()` if the request succeeds.
/// # Reference
/// See <https://discord-userdoccers.vercel.app/resources/guild#remove-guild-member-role>
pub async fn remove_role(
user: &mut UserMeta,
user: &mut ChorusUser,
guild_id: Snowflake,
member_id: Snowflake,
role_id: Snowflake,
) -> Result<(), crate::errors::ChorusError> {
let url = format!(
"{}/guilds/{}/members/{}/roles/{}/",
user.belongs_to.borrow().urls.api,
"{}/guilds/{}/members/{}/roles/{}",
user.belongs_to.read().unwrap().urls.api,
guild_id,
member_id,
role_id
);
let chorus_request = ChorusRequest {
request: Client::new().delete(url).bearer_auth(user.token()),
request: Client::new()
.delete(url)
.header("Authorization", user.token()),
limit_type: LimitType::Guild(guild_id),
};
chorus_request.handle_request_as_result(user).await

View File

@ -0,0 +1,28 @@
use crate::errors::ChorusResult;
use crate::instance::ChorusUser;
use crate::types::{Guild, Message, MessageSearchQuery, Snowflake};
impl Guild {
/// Returns messages without the reactions key that match a search query in the guild.
/// The messages that are direct results will have an extra hit key set to true.
/// If operating on a guild channel, this endpoint requires the `READ_MESSAGE_HISTORY`
/// permission to be present on the current user.
///
/// If the guild/channel you are searching is not yet indexed, the endpoint will return a 202 accepted response.
/// In this case, the method will return a [`ChorusError::InvalidResponse`] error.
///
/// # Reference:
/// See <https://discord-userdoccers.vercel.app/resources/message#search-messages>
pub async fn search_messages(
guild_id: Snowflake,
query: MessageSearchQuery,
user: &mut ChorusUser,
) -> ChorusResult<Vec<Message>> {
Message::search(
crate::types::MessageSearchEndpoint::GuildChannel(guild_id),
query,
user,
)
.await
}
}

View File

@ -1,7 +1,9 @@
pub use guilds::*;
pub use messages::*;
pub use roles::*;
pub use roles::*;
pub mod guilds;
pub mod member;
pub mod messages;
pub mod roles;

View File

@ -2,79 +2,56 @@ use reqwest::Client;
use serde_json::to_string;
use crate::{
api::LimitType,
errors::{ChorusError, ChorusResult},
instance::UserMeta,
instance::ChorusUser,
ratelimiter::ChorusRequest,
types::{self, RoleCreateModifySchema, RoleObject, Snowflake},
types::{
self, LimitType, RoleCreateModifySchema, RoleObject, RolePositionUpdateSchema, Snowflake,
},
};
impl types::RoleObject {
/// Retrieves all roles for a given guild.
/// Retrieves a list of roles for a given guild.
///
/// # Arguments
///
/// * `user` - A mutable reference to a [`UserMeta`] instance.
/// * `guild_id` - The ID of the guild to retrieve roles from.
///
/// # Returns
///
/// An `Option` containing a `Vec` of [`RoleObject`]s if roles were found, or `None` if no roles were found.
///
/// # Errors
///
/// Returns a [`ChorusLibError`] if the request fails or if the response is invalid.
/// # Reference
/// See <https://discord-userdoccers.vercel.app/resources/guild#get-guild-roles>
pub async fn get_all(
user: &mut UserMeta,
user: &mut ChorusUser,
guild_id: Snowflake,
) -> ChorusResult<Option<Vec<RoleObject>>> {
) -> ChorusResult<Vec<RoleObject>> {
let url = format!(
"{}/guilds/{}/roles/",
user.belongs_to.borrow().urls.api,
"{}/guilds/{}/roles",
user.belongs_to.read().unwrap().urls.api,
guild_id
);
let chorus_request = ChorusRequest {
request: Client::new().get(url).bearer_auth(user.token()),
request: Client::new().get(url).header("Authorization", user.token()),
limit_type: LimitType::Guild(guild_id),
};
let roles = chorus_request
.deserialize_response::<Vec<RoleObject>>(user)
.await
.unwrap();
if roles.is_empty() {
return Ok(None);
}
Ok(Some(roles))
Ok(roles)
}
/// Retrieves a single role for a given guild.
///
/// # Arguments
///
/// * `user` - A mutable reference to a [`UserMeta`] instance.
/// * `guild_id` - The ID of the guild to retrieve the role from.
/// * `role_id` - The ID of the role to retrieve.
///
/// # Returns
///
/// A `Result` containing the retrieved [`RoleObject`] if successful, or a [`ChorusLibError`] if the request fails or if the response is invalid.
///
/// # Errors
///
/// Returns a [`ChorusLibError`] if the request fails or if the response is invalid.
/// # Reference
/// See <https://docs.spacebar.chat/routes/#get-/guilds/-guild_id-/roles/-role_id-/>
pub async fn get(
user: &mut UserMeta,
user: &mut ChorusUser,
guild_id: Snowflake,
role_id: Snowflake,
) -> ChorusResult<RoleObject> {
let url = format!(
"{}/guilds/{}/roles/{}/",
user.belongs_to.borrow().urls.api,
"{}/guilds/{}/roles/{}",
user.belongs_to.read().unwrap().urls.api,
guild_id,
role_id
);
let chorus_request = ChorusRequest {
request: Client::new().get(url).bearer_auth(user.token()),
request: Client::new().get(url).header("Authorization", user.token()),
limit_type: LimitType::Guild(guild_id),
};
chorus_request
@ -84,27 +61,18 @@ impl types::RoleObject {
/// Creates a new role for a given guild.
///
/// # Arguments
/// Requires the [`MANAGE_ROLES`](crate::types::PermissionFlags::MANAGE_ROLES) permission.
///
/// * `user` - A mutable reference to a [`UserMeta`] instance.
/// * `guild_id` - The ID of the guild to create the role in.
/// * `role_create_schema` - A [`RoleCreateModifySchema`] instance containing the properties of the role to be created.
///
/// # Returns
///
/// A `Result` containing the newly created [`RoleObject`] if successful, or a [`ChorusLibError`] if the request fails or if the response is invalid.
///
/// # Errors
///
/// Returns a [`ChorusLibError`] if the request fails or if the response is invalid.
/// # Reference
/// See <https://discord-userdoccers.vercel.app/resources/guild#create-guild-role>
pub async fn create(
user: &mut UserMeta,
user: &mut ChorusUser,
guild_id: Snowflake,
role_create_schema: RoleCreateModifySchema,
) -> ChorusResult<RoleObject> {
let url = format!(
"{}/guilds/{}/roles/",
user.belongs_to.borrow().urls.api,
"{}/guilds/{}/roles",
user.belongs_to.read().unwrap().urls.api,
guild_id
);
let body = to_string::<RoleCreateModifySchema>(&role_create_schema).map_err(|e| {
@ -113,7 +81,11 @@ impl types::RoleObject {
}
})?;
let chorus_request = ChorusRequest {
request: Client::new().post(url).bearer_auth(user.token()).body(body),
request: Client::new()
.post(url)
.header("Authorization", user.token())
.header("Content-Type", "application/json")
.body(body),
limit_type: LimitType::Guild(guild_id),
};
chorus_request
@ -121,29 +93,20 @@ impl types::RoleObject {
.await
}
/// Updates the position of a role in the guild's hierarchy.
/// Updates the position of a role in a given guild's hierarchy.
///
/// # Arguments
/// Requires the [`MANAGE_ROLES`](crate::types::PermissionFlags::MANAGE_ROLES) permission.
///
/// * `user` - A mutable reference to a [`UserMeta`] instance.
/// * `guild_id` - The ID of the guild to update the role position in.
/// * `role_position_update_schema` - A [`RolePositionUpdateSchema`] instance containing the new position of the role.
///
/// # Returns
///
/// A `Result` containing the updated [`RoleObject`] if successful, or a [`ChorusLibError`] if the request fails or if the response is invalid.
///
/// # Errors
///
/// Returns a [`ChorusLibError`] if the request fails or if the response is invalid.
/// # Reference
/// See <https://discord-userdoccers.vercel.app/resources/guild#modify-guild-role-positions>
pub async fn position_update(
user: &mut UserMeta,
user: &mut ChorusUser,
guild_id: Snowflake,
role_position_update_schema: types::RolePositionUpdateSchema,
role_position_update_schema: RolePositionUpdateSchema,
) -> ChorusResult<RoleObject> {
let url = format!(
"{}/guilds/{}/roles/",
user.belongs_to.borrow().urls.api,
"{}/guilds/{}/roles",
user.belongs_to.read().unwrap().urls.api,
guild_id
);
let body =
@ -153,7 +116,8 @@ impl types::RoleObject {
let chorus_request = ChorusRequest {
request: Client::new()
.patch(url)
.bearer_auth(user.token())
.header("Authorization", user.token())
.header("Content-Type", "application/json")
.body(body),
limit_type: LimitType::Guild(guild_id),
};
@ -162,31 +126,21 @@ impl types::RoleObject {
.await
}
/// Updates a role in a guild.
/// Modifies a role in a guild.
///
/// # Arguments
/// Requires the [`MANAGE_ROLES`](crate::types::PermissionFlags::MANAGE_ROLES) permission.
///
/// * `user` - A mutable reference to a [`UserMeta`] instance.
/// * `guild_id` - The ID of the guild to update the role in.
/// * `role_id` - The ID of the role to update.
/// * `role_create_schema` - A [`RoleCreateModifySchema`] instance containing the new properties of the role.
///
/// # Returns
///
/// A `Result` containing the updated [`RoleObject`] if successful, or a [`ChorusLibError`] if the request fails or if the response is invalid.
///
/// # Errors
///
/// Returns a [`ChorusLibError`] if the request fails or if the response is invalid.
pub async fn update(
user: &mut UserMeta,
/// # Reference
/// See <https://discord-userdoccers.vercel.app/resources/guild#modify-guild-role>
pub async fn modify(
user: &mut ChorusUser,
guild_id: Snowflake,
role_id: Snowflake,
role_create_schema: RoleCreateModifySchema,
) -> ChorusResult<RoleObject> {
let url = format!(
"{}/guilds/{}/roles/{}",
user.belongs_to.borrow().urls.api,
user.belongs_to.read().unwrap().urls.api,
guild_id,
role_id
);
@ -198,7 +152,8 @@ impl types::RoleObject {
let chorus_request = ChorusRequest {
request: Client::new()
.patch(url)
.bearer_auth(user.token())
.header("Authorization", user.token())
.header("Content-Type", "application/json")
.body(body),
limit_type: LimitType::Guild(guild_id),
};
@ -206,4 +161,33 @@ impl types::RoleObject {
.deserialize_response::<RoleObject>(user)
.await
}
/// Deletes a guild role. Requires the `MANAGE_ROLES` permission. Returns a 204 empty response on success.
///
/// # Reference:
/// See <https://discord.com/developers/docs/resources/guild#delete-guild-role>
pub async fn delete_role(
user: &mut ChorusUser,
guild_id: Snowflake,
role_id: Snowflake,
audit_log_reason: Option<String>,
) -> ChorusResult<()> {
let url = format!(
"{}/guilds/{}/roles/{}",
user.belongs_to.read().unwrap().urls.api,
guild_id,
role_id
);
let request = ChorusRequest::new(
http::Method::DELETE,
&url,
None,
audit_log_reason.as_deref(),
None,
Some(user),
LimitType::Guild(guild_id),
);
request.handle_request_as_result(user).await
}
}

View File

@ -2,17 +2,17 @@ use reqwest::Client;
use serde_json::to_string;
use crate::errors::ChorusResult;
use crate::instance::UserMeta;
use crate::instance::ChorusUser;
use crate::ratelimiter::ChorusRequest;
use crate::types::{CreateChannelInviteSchema, GuildInvite, Invite, Snowflake};
use crate::types::{CreateChannelInviteSchema, GuildInvite, Invite, LimitType, Snowflake};
impl UserMeta {
/// # Arguments
/// - invite_code: The invite code to accept the invite for.
/// - session_id: The session ID that is accepting the invite, required for guest invites.
impl ChorusUser {
/// Accepts an invite to a guild, group DM, or DM.
///
/// Note that the session ID is required for guest invites.
///
/// # Reference:
/// Read <https://discord-userdoccers.vercel.app/resources/invite#accept-invite>
/// See <https://discord-userdoccers.vercel.app/resources/invite#accept-invite>
pub async fn accept_invite(
&mut self,
invite_code: &str,
@ -21,37 +21,52 @@ impl UserMeta {
let mut request = ChorusRequest {
request: Client::new()
.post(format!(
"{}/invites/{}/",
self.belongs_to.borrow().urls.api,
"{}/invites/{}",
self.belongs_to.read().unwrap().urls.api,
invite_code
))
.bearer_auth(self.token()),
limit_type: super::LimitType::Global,
.header("Authorization", self.token()),
limit_type: LimitType::Global,
};
if session_id.is_some() {
request.request = request
.request
.header("Content-Type", "application/json")
.body(to_string(session_id.unwrap()).unwrap());
}
request.deserialize_response::<Invite>(self).await
}
/// Creates a new friend invite.
///
/// Note: Spacebar does not yet implement this endpoint.
///
/// # Reference:
/// See <https://discord-userdoccers.vercel.app/resources/invite#create-user-invite>
pub async fn create_user_invite(&mut self, code: Option<&str>) -> ChorusResult<Invite> {
ChorusRequest {
request: Client::new()
.post(format!(
"{}/users/@me/invites/",
self.belongs_to.borrow().urls.api
"{}/users/@me/invites",
self.belongs_to.read().unwrap().urls.api
))
.body(to_string(&code).unwrap())
.bearer_auth(self.token()),
limit_type: super::LimitType::Global,
.header("Authorization", self.token())
.header("Content-Type", "application/json"),
limit_type: LimitType::Global,
}
.deserialize_response::<Invite>(self)
.await
}
pub async fn create_guild_invite(
/// Creates a new invite for a guild channel or group DM.
///
/// # Guild Channels
/// For guild channels, the endpoint requires the [`CREATE_INSTANT_INVITE`](crate::types::PermissionFlags::CREATE_INSTANT_INVITE) permission.
///
/// # Reference
/// See <https://discord-userdoccers.vercel.app/resources/invite#create-channel-invite>
pub async fn create_channel_invite(
&mut self,
create_channel_invite_schema: CreateChannelInviteSchema,
channel_id: Snowflake,
@ -59,13 +74,14 @@ impl UserMeta {
ChorusRequest {
request: Client::new()
.post(format!(
"{}/channels/{}/invites/",
self.belongs_to.borrow().urls.api,
"{}/channels/{}/invites",
self.belongs_to.read().unwrap().urls.api,
channel_id
))
.bearer_auth(self.token())
.header("Authorization", self.token())
.header("Content-Type", "application/json")
.body(to_string(&create_channel_invite_schema).unwrap()),
limit_type: super::LimitType::Channel(channel_id),
limit_type: LimitType::Channel(channel_id),
}
.deserialize_response::<GuildInvite>(self)
.await

View File

@ -1,8 +1,8 @@
//! All of the API's endpoints.
pub use channels::messages::*;
pub use guilds::*;
pub use invites::*;
pub use policies::instance::instance::*;
pub use policies::instance::ratelimits::*;
pub use users::*;
pub mod auth;

View File

@ -6,16 +6,20 @@ use crate::types::GeneralConfiguration;
impl Instance {
/// Gets the instance policies schema.
/// # Errors
/// [`ChorusLibError`] - If the request fails.
///
/// # Notes
/// This is a Spacebar only endpoint.
///
/// # Reference
/// See <https://docs.spacebar.chat/routes/#get-/policies/instance/>
pub async fn general_configuration_schema(&self) -> ChorusResult<GeneralConfiguration> {
let endpoint_url = self.urls.api.clone() + "/policies/instance/";
let endpoint_url = self.urls.api.clone() + "/policies/instance";
let request = match self.client.get(&endpoint_url).send().await {
Ok(result) => result,
Err(e) => {
return Err(ChorusError::RequestFailed {
url: endpoint_url,
error: e,
error: e.to_string(),
});
}
};

View File

@ -1,5 +1,3 @@
pub use instance::*;
pub use ratelimits::*;
pub mod instance;
pub mod ratelimits;

View File

@ -1,3 +1 @@
pub use instance::ratelimits::*;
pub mod instance;

View File

@ -2,27 +2,33 @@ use reqwest::Client;
use serde_json::to_string;
use crate::{
api::LimitType,
errors::ChorusResult,
instance::UserMeta,
instance::ChorusUser,
ratelimiter::ChorusRequest,
types::{Channel, PrivateChannelCreateSchema},
types::{Channel, LimitType, PrivateChannelCreateSchema},
};
impl UserMeta {
impl ChorusUser {
/// Creates a DM channel or group DM channel.
///
/// One recipient creates or returns an existing DM channel,
/// none or multiple recipients create a group DM channel.
///
/// # Reference:
/// Read <https://discord-userdoccers.vercel.app/resources/channel#create-private-channel>
/// See <https://discord-userdoccers.vercel.app/resources/channel#create-private-channel>
pub async fn create_private_channel(
&mut self,
create_private_channel_schema: PrivateChannelCreateSchema,
) -> ChorusResult<Channel> {
let url = format!("{}/users/@me/channels", self.belongs_to.borrow().urls.api);
let url = format!(
"{}/users/@me/channels",
self.belongs_to.read().unwrap().urls.api
);
ChorusRequest {
request: Client::new()
.post(url)
.bearer_auth(self.token())
.header("Authorization", self.token())
.header("Content-Type", "application/json")
.body(to_string(&create_private_channel_schema).unwrap()),
limit_type: LimitType::Global,
}

View File

@ -2,29 +2,59 @@ use reqwest::Client;
use serde_json::to_string;
use crate::errors::ChorusResult;
use crate::instance::UserMeta;
use crate::instance::ChorusUser;
use crate::ratelimiter::ChorusRequest;
use crate::types::Snowflake;
use crate::types::{GetUserGuildSchema, Guild, LimitType, Snowflake};
impl UserMeta {
/// # Arguments:
/// - lurking: Whether the user is lurking in the guild
impl ChorusUser {
/// Leaves a given guild.
///
/// # Reference:
/// Read <https://discord-userdoccers.vercel.app/resources/guild#leave-guild>
/// See <https://discord-userdoccers.vercel.app/resources/guild#leave-guild>
// TODO: Docs: What is "lurking" here?
// It is documented as "Whether the user is lurking in the guild",
// but that says nothing about what this field actually does / means
pub async fn leave_guild(&mut self, guild_id: &Snowflake, lurking: bool) -> ChorusResult<()> {
ChorusRequest {
request: Client::new()
.delete(format!(
"{}/users/@me/guilds/{}/",
self.belongs_to.borrow().urls.api,
"{}/users/@me/guilds/{}",
self.belongs_to.read().unwrap().urls.api,
guild_id
))
.bearer_auth(self.token())
.header("Authorization", self.token())
.header("Content-Type", "application/json")
.body(to_string(&lurking).unwrap()),
limit_type: crate::api::LimitType::Guild(*guild_id),
limit_type: LimitType::Guild(*guild_id),
}
.handle_request_as_result(self)
.await
}
/// Returns a list of user guild objects representing the guilds the current user is a member of.
/// This endpoint returns 200 guilds by default
///
/// # Reference:
/// See: <https://discord-userdoccers.vercel.app/resources/guild#get-user-guilds>
pub async fn get_guilds(
&mut self,
query: Option<GetUserGuildSchema>,
) -> ChorusResult<Vec<Guild>> {
let url = format!(
"{}/users/@me/guilds",
self.belongs_to.read().unwrap().urls.api,
);
let chorus_request = ChorusRequest {
request: Client::new()
.get(url)
.header("Authorization", self.token())
.header("Content-Type", "application/json")
.body(to_string(&query).unwrap()),
limit_type: LimitType::Global,
};
chorus_request
.deserialize_response::<Vec<Guild>>(self)
.await
}
}

View File

@ -2,33 +2,31 @@ use reqwest::Client;
use serde_json::to_string;
use crate::{
api::LimitType,
errors::ChorusResult,
instance::UserMeta,
instance::ChorusUser,
ratelimiter::ChorusRequest,
types::{self, CreateUserRelationshipSchema, RelationshipType, Snowflake},
types::{
self, CreateUserRelationshipSchema, FriendRequestSendSchema, LimitType, RelationshipType,
Snowflake,
},
};
impl UserMeta {
/// Retrieves the mutual relationships between the authenticated user and the specified user.
impl ChorusUser {
/// Retrieves a list of mutual friends between the authenticated user and a given user.
///
/// # Arguments
///
/// * `user_id` - ID of the user to retrieve the mutual relationships with.
///
/// # Returns
/// This function returns a [`ChorusResult<Vec<PublicUser>>`].
/// # Reference
/// See <https://luna.gitlab.io/discord-unofficial-docs/relationships.html#get-users-peer-id-relationships>
pub async fn get_mutual_relationships(
&mut self,
user_id: Snowflake,
) -> ChorusResult<Vec<types::PublicUser>> {
let url = format!(
"{}/users/{}/relationships/",
self.belongs_to.borrow().urls.api,
"{}/users/{}/relationships",
self.belongs_to.read().unwrap().urls.api,
user_id
);
let chorus_request = ChorusRequest {
request: Client::new().get(url).bearer_auth(self.token()),
request: Client::new().get(url).header("Authorization", self.token()),
limit_type: LimitType::Global,
};
chorus_request
@ -36,17 +34,17 @@ impl UserMeta {
.await
}
/// Retrieves the authenticated user's relationships.
/// Retrieves the user's relationships.
///
/// # Returns
/// This function returns a [`ChorusResult<Vec<types::Relationship>>`].
/// # Reference
/// See <https://luna.gitlab.io/discord-unofficial-docs/relationships.html#get-users-me-relationships>
pub async fn get_relationships(&mut self) -> ChorusResult<Vec<types::Relationship>> {
let url = format!(
"{}/users/@me/relationships/",
self.belongs_to.borrow().urls.api
"{}/users/@me/relationships",
self.belongs_to.read().unwrap().urls.api
);
let chorus_request = ChorusRequest {
request: Client::new().get(url).bearer_auth(self.token()),
request: Client::new().get(url).header("Authorization", self.token()),
limit_type: LimitType::Global,
};
chorus_request
@ -56,54 +54,43 @@ impl UserMeta {
/// Sends a friend request to a user.
///
/// # Arguments
///
/// * `schema` - A [`FriendRequestSendSchema`] struct that holds the information about the friend request to be sent.
///
/// # Returns
/// This function returns a [`Result`] that holds a [`ChorusLibError`] if the request fails.
/// # Reference
/// See <https://luna.gitlab.io/discord-unofficial-docs/relationships.html#post-users-me-relationships>
pub async fn send_friend_request(
&mut self,
schema: types::FriendRequestSendSchema,
schema: FriendRequestSendSchema,
) -> ChorusResult<()> {
let url = format!(
"{}/users/@me/relationships/",
self.belongs_to.borrow().urls.api
"{}/users/@me/relationships",
self.belongs_to.read().unwrap().urls.api
);
let body = to_string(&schema).unwrap();
let chorus_request = ChorusRequest {
request: Client::new().post(url).bearer_auth(self.token()).body(body),
request: Client::new()
.post(url)
.header("Authorization", self.token())
.header("Content-Type", "application/json")
.body(body),
limit_type: LimitType::Global,
};
chorus_request.handle_request_as_result(self).await
}
/// Modifies the relationship between the authenticated user and the specified user.
/// Modifies the relationship between the authenticated user and a given user.
///
/// # Arguments
///
/// * `user_id` - ID of the user to modify the relationship with.
/// * `relationship_type` - A [`RelationshipType`] enum that specifies the type of relationship to modify.
/// * [`RelationshipType::None`]: Removes the relationship between the two users.
/// * [`RelationshipType::Friends`] | [`RelationshipType::Incoming`] | [`RelationshipType::Outgoing`]:
/// Either accepts an incoming friend request, or sends a new friend request, if there is no
/// incoming friend request from the specified `user_id`.
/// * [`RelationshipType::Blocked`]: Blocks the specified user_id.
///
/// # Returns
/// This function returns an [`Result`] that holds a [`ChorusLibError`] if the request fails.
/// Can be used to unfriend users, accept or send friend requests and block or unblock users.
pub async fn modify_user_relationship(
&mut self,
user_id: Snowflake,
relationship_type: RelationshipType,
) -> ChorusResult<()> {
let api_url = self.belongs_to.borrow().urls.api.clone();
let api_url = self.belongs_to.read().unwrap().urls.api.clone();
match relationship_type {
RelationshipType::None => {
let chorus_request = ChorusRequest {
request: Client::new()
.delete(format!("{}/users/@me/relationships/{}/", api_url, user_id))
.bearer_auth(self.token()),
.delete(format!("{}/users/@me/relationships/{}", api_url, user_id))
.header("Authorization", self.token()),
limit_type: LimitType::Global,
};
chorus_request.handle_request_as_result(self).await
@ -116,8 +103,8 @@ impl UserMeta {
};
let chorus_request = ChorusRequest {
request: Client::new()
.put(format!("{}/users/@me/relationships/{}/", api_url, user_id))
.bearer_auth(self.token())
.put(format!("{}/users/@me/relationships/{}", api_url, user_id))
.header("Authorization", self.token())
.body(to_string(&body).unwrap()),
limit_type: LimitType::Global,
};
@ -131,8 +118,8 @@ impl UserMeta {
};
let chorus_request = ChorusRequest {
request: Client::new()
.put(format!("{}/users/@me/relationships/{}/", api_url, user_id))
.bearer_auth(self.token())
.put(format!("{}/users/@me/relationships/{}", api_url, user_id))
.header("Authorization", self.token())
.body(to_string(&body).unwrap()),
limit_type: LimitType::Global,
};
@ -142,22 +129,20 @@ impl UserMeta {
}
}
/// Removes the relationship between the authenticated user and the specified user.
/// Removes the relationship between the authenticated user and a given user.
///
/// # Arguments
///
/// * `user_id` - ID of the user to remove the relationship with.
///
/// # Returns
/// This function returns a [`Result`] that holds a [`ChorusLibError`] if the request fails.
/// # Reference
/// See <https://luna.gitlab.io/discord-unofficial-docs/relationships.html#delete-users-me-relationships-peer-id>
pub async fn remove_relationship(&mut self, user_id: Snowflake) -> ChorusResult<()> {
let url = format!(
"{}/users/@me/relationships/{}/",
self.belongs_to.borrow().urls.api,
"{}/users/@me/relationships/{}",
self.belongs_to.read().unwrap().urls.api,
user_id
);
let chorus_request = ChorusRequest {
request: Client::new().delete(url).bearer_auth(self.token()),
request: Client::new()
.delete(url)
.header("Authorization", self.token()),
limit_type: LimitType::Global,
};
chorus_request.handle_request_as_result(self).await

View File

@ -1,33 +1,32 @@
use std::{cell::RefCell, rc::Rc};
use std::sync::{Arc, RwLock};
use reqwest::Client;
use serde_json::to_string;
use crate::{
api::LimitType,
errors::{ChorusError, ChorusResult},
instance::{Instance, UserMeta},
instance::{ChorusUser, Instance},
ratelimiter::ChorusRequest,
types::{User, UserModifySchema, UserSettings},
types::{LimitType, User, UserModifySchema, UserSettings},
};
impl UserMeta {
/// Get a user object by id, or get the current user.
impl ChorusUser {
/// Gets a user by id, or if the id is None, gets the current user.
///
/// # Arguments
/// # Notes
/// This function is a wrapper around [`User::get`].
///
/// * `token` - A valid access token for the API.
/// * `url_api` - The URL to the API.
/// * `id` - The id of the user that will be retrieved. If this is None, the current user will be retrieved.
/// * `instance_limits` - The [`Limits`] of the instance.
///
/// # Errors
///
/// * [`ChorusLibError`] - If the request fails.
pub async fn get(user: &mut UserMeta, id: Option<&String>) -> ChorusResult<User> {
User::get(user, id).await
/// # 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, id: Option<&String>) -> ChorusResult<User> {
User::get(self, id).await
}
/// Gets the user's settings.
///
/// # Notes
/// This functions is a wrapper around [`User::get_settings`].
pub async fn get_settings(
token: &String,
url_api: &String,
@ -36,15 +35,10 @@ impl UserMeta {
User::get_settings(token, url_api, instance).await
}
/// Modify the current user's `UserObject`.
/// Modifies the current user's representation. (See [`User`])
///
/// # Arguments
///
/// * `modify_schema` - A `UserModifySchema` object containing the fields to modify.
///
/// # Errors
///
/// Returns an `ChorusLibError` if the request fails or if a password is required but not provided.
/// # Reference
/// See <https://discord-userdoccers.vercel.app/resources/user#modify-current-user>
pub async fn modify(&mut self, modify_schema: UserModifySchema) -> ChorusResult<User> {
if modify_schema.new_password.is_some()
|| modify_schema.email.is_some()
@ -53,37 +47,32 @@ impl UserMeta {
return Err(ChorusError::PasswordRequired);
}
let request = Client::new()
.patch(format!("{}/users/@me/", self.belongs_to.borrow().urls.api))
.patch(format!(
"{}/users/@me",
self.belongs_to.read().unwrap().urls.api
))
.body(to_string(&modify_schema).unwrap())
.bearer_auth(self.token());
.header("Authorization", self.token())
.header("Content-Type", "application/json");
let chorus_request = ChorusRequest {
request,
limit_type: LimitType::default(),
};
let user_updated = chorus_request
.deserialize_response::<User>(self)
.await
.unwrap();
let _ = std::mem::replace(&mut self.object, user_updated.clone());
Ok(user_updated)
chorus_request.deserialize_response::<User>(self).await
}
/// Sends a request to the server which deletes the user from the Instance.
/// Deletes the user from the Instance.
///
/// # Arguments
///
/// * `self` - The `User` object to delete.
///
/// # Returns
///
/// Returns `()` if the user was successfully deleted, or a `ChorusLibError` if an error occurred.
/// # Reference
/// See <https://discord-userdoccers.vercel.app/resources/user#disable-user>
pub async fn delete(mut self) -> ChorusResult<()> {
let request = Client::new()
.post(format!(
"{}/users/@me/delete/",
self.belongs_to.borrow().urls.api
"{}/users/@me/delete",
self.belongs_to.read().unwrap().urls.api
))
.bearer_auth(self.token());
.header("Authorization", self.token())
.header("Content-Type", "application/json");
let chorus_request = ChorusRequest {
request,
limit_type: LimitType::default(),
@ -93,14 +82,21 @@ impl UserMeta {
}
impl User {
pub async fn get(user: &mut UserMeta, id: Option<&String>) -> ChorusResult<User> {
let url_api = user.belongs_to.borrow().urls.api.clone();
/// Gets a user by id, or if the id is None, gets the current user.
///
/// # 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 ChorusUser, id: Option<&String>) -> ChorusResult<User> {
let url_api = user.belongs_to.read().unwrap().urls.api.clone();
let url = if id.is_none() {
format!("{}/users/@me/", url_api)
format!("{}/users/@me", url_api)
} else {
format!("{}/users/{}", url_api, id.unwrap())
};
let request = reqwest::Client::new().get(url).bearer_auth(user.token());
let request = reqwest::Client::new()
.get(url)
.header("Authorization", user.token());
let chorus_request = ChorusRequest {
request,
limit_type: LimitType::Global,
@ -114,16 +110,20 @@ impl User {
}
}
/// Gets the user's settings.
///
/// # Reference
/// See <https://luna.gitlab.io/discord-unofficial-docs/user_settings.html#get-users-me-settings>
pub async fn get_settings(
token: &String,
url_api: &String,
instance: &mut Instance,
) -> ChorusResult<UserSettings> {
let request: reqwest::RequestBuilder = Client::new()
.get(format!("{}/users/@me/settings/", url_api))
.bearer_auth(token);
.get(format!("{}/users/@me/settings", url_api))
.header("Authorization", token);
let mut user =
UserMeta::shell(Rc::new(RefCell::new(instance.clone())), token.clone()).await;
ChorusUser::shell(Arc::new(RwLock::new(instance.clone())), token.clone()).await;
let chorus_request = ChorusRequest {
request,
limit_type: LimitType::Global,
@ -133,30 +133,36 @@ impl User {
Err(e) => Err(e),
};
if instance.limits_information.is_some() {
instance.limits_information.as_mut().unwrap().ratelimits =
user.belongs_to.borrow().clone_limits_if_some().unwrap();
instance.limits_information.as_mut().unwrap().ratelimits = user
.belongs_to
.read()
.unwrap()
.clone_limits_if_some()
.unwrap();
}
result
}
}
impl Instance {
/**
Get a user object by id, or get the current user.
# Arguments
* `token` - A valid access token for the API.
* `id` - The id of the user that will be retrieved. If this is None, the current user will be retrieved.
# Errors
* [`ChorusLibError`] - If the request fails.
# Notes
This function is a wrapper around [`User::get`].
*/
/// 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 = UserMeta::shell(Rc::new(RefCell::new(self.clone())), token).await;
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.borrow().clone_limits_if_some().unwrap();
self.limits_information.as_mut().unwrap().ratelimits = user
.belongs_to
.read()
.unwrap()
.clone_limits_if_some()
.unwrap();
}
result
}

View File

@ -1,8 +1,10 @@
//! Contains all the errors that can be returned by the library.
use custom_error::custom_error;
use reqwest::Error;
use crate::types::WebSocketEvent;
custom_error! {
#[derive(PartialEq, Eq)]
#[derive(PartialEq, Eq, Clone, Hash)]
pub RegistrationError
Consent = "Consent must be 'true' to register.",
}
@ -10,11 +12,12 @@ custom_error! {
pub type ChorusResult<T> = std::result::Result<T, ChorusError>;
custom_error! {
#[derive(Clone, Hash, PartialEq, Eq)]
pub ChorusError
/// Server did not respond.
NoResponse = "Did not receive a response from the Server.",
/// Reqwest returned an Error instead of a Response object.
RequestFailed{url:String, error: Error} = "An error occured while trying to GET from {url}: {error}",
RequestFailed{url:String, error: String} = "An error occured while trying to GET from {url}: {error}",
/// Response received, however, it was not of the successful responses type. Used when no other, special case applies.
ReceivedErrorCode{error_code: u16, error: String} = "Received the following error code while requesting from the route: {error_code}",
/// Used when there is likely something wrong with the instance, the request was directed to.
@ -53,9 +56,10 @@ custom_error! {
/// 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
#[derive(Clone, PartialEq, Eq)]
#[derive(PartialEq, Eq, Default, Clone)]
pub GatewayError
// Errors we have received from the gateway
#[default]
Unknown = "We're not sure what went wrong. Try reconnecting?",
UnknownOpcode = "You sent an invalid Gateway opcode or an invalid payload for an opcode",
Decode = "Gateway server couldn't decode payload",
@ -79,25 +83,29 @@ custom_error! {
UnexpectedOpcodeReceived{opcode: u8} = "Received an opcode we weren't expecting to receive: {opcode}",
}
impl WebSocketEvent for GatewayError {}
custom_error! {
// Like GatewayError for webrtc errors
// See https://discord.com/developers/docs/topics/opcodes-and-status-codes#voice;
// Also supposed to be sent by numbers, but discord is asdfghgfjkkjldf when it comes to their errors
/// Voice Gateway errors
///
/// Similar to [GatewayError].
///
/// See https://discord.com/developers/docs/topics/opcodes-and-status-codes#voice;
#[derive(Clone, PartialEq, Eq)]
pub VoiceGatewayError
// Errors we receive
UnknownOpcodeError = "You sent an invalid opcode",
FailedToDecodePayloadError = "You sent an invalid payload in your identifying to the (Webrtc) Gateway",
NotAuthenticatedError = "You sent a payload before identifying with the (Webrtc) Gateway",
AuthenticationFailedError = "The token you sent in your identify payload is incorrect",
AlreadyAuthenticatedError = "You sent more than one identify payload",
SessionNoLongerValidError = "Your session is no longer valid",
SessionTimeoutError = "Your session has timed out",
ServerNotFoundError = "We can't find the server you're trying to connect to",
UnknownProtocolError = "We didn't recognize the protocol you sent",
DisconnectedError = "Channel was deleted, you were kicked, voice server changed, or the main gateway session was dropped. Should not reconnect.",
VoiceServerCrashedError = "The server crashed, try resuming",
UnknownEncryptionModeError = "Server failed to decrypt data",
UnknownOpcode = "You sent an invalid opcode",
FailedToDecodePayload = "You sent an invalid payload in your identifying to the (Webrtc) Gateway",
NotAuthenticated = "You sent a payload before identifying with the (Webrtc) Gateway",
AuthenticationFailed = "The token you sent in your identify payload is incorrect",
AlreadyAuthenticated = "You sent more than one identify payload",
SessionNoLongerValid = "Your session is no longer valid",
SessionTimeout = "Your session has timed out",
ServerNotFound = "We can't find the server you're trying to connect to",
UnknownProtocol = "We didn't recognize the protocol you sent",
Disconnected = "Channel was deleted, you were kicked, voice server changed, or the main gateway session was dropped. Should not reconnect.",
VoiceServerCrashed = "The server crashed, try resuming",
UnknownEncryptionMode = "Server failed to decrypt data",
// Errors when initiating a gateway connection
CannotConnect{error: String} = "Cannot connect due to a tungstenite error: {error}",

View File

@ -1,14 +1,18 @@
//! Gateway connection, communication and handling, as well as object caching and updating.
use crate::errors::GatewayError;
use crate::gateway::events::Events;
use crate::types::{self, Channel, ChannelUpdate, Snowflake};
use crate::types::{UpdateMessage, WebSocketEvent};
use crate::types::{
self, AutoModerationRule, AutoModerationRuleUpdate, Channel, ChannelCreate, ChannelDelete,
ChannelUpdate, Composite, Guild, GuildRoleCreate, GuildRoleUpdate, JsonField, RoleObject,
Snowflake, SourceUrlField, ThreadUpdate, UpdateMessage, WebSocketEvent,
};
use async_trait::async_trait;
use std::any::Any;
use std::collections::HashMap;
use std::fmt::Debug;
use std::sync::Arc;
use std::sync::{Arc, RwLock};
use std::time::Duration;
use tokio::sync::watch;
use tokio::time::sleep_until;
use futures_util::stream::SplitSink;
@ -78,7 +82,7 @@ const GATEWAY_LAZY_REQUEST: u8 = 14;
/// The amount of time we wait for a heartbeat ack before resending our heartbeat in ms
pub(crate) const HEARTBEAT_ACK_TIMEOUT: u64 = 2000;
/// Represents a messsage received from the gateway. This will be either a [GatewayReceivePayload], containing events, or a [GatewayError].
/// Represents a messsage received from the gateway. This will be either a [types::GatewayReceivePayload], containing events, or a [GatewayError].
/// This struct is used internally when handling messages.
#[derive(Clone, Debug)]
pub struct GatewayMessage {
@ -148,11 +152,13 @@ impl GatewayMessage {
}
}
pub type ObservableObject = dyn Send + Sync + Any;
/// 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
/// implemented [Types] with the trait [`WebSocketEvent`]
/// implemented types with the trait [`WebSocketEvent`]
/// Using this handle you can also send Gateway Events directly.
#[derive(Debug)]
#[derive(Debug, Clone)]
pub struct GatewayHandle {
pub url: String,
pub events: Arc<Mutex<Events>>,
@ -164,10 +170,9 @@ pub struct GatewayHandle {
>,
>,
>,
pub handle: JoinHandle<()>,
/// Tells gateway tasks to close
kill_send: tokio::sync::broadcast::Sender<()>,
store: Arc<Mutex<HashMap<Snowflake, Box<dyn Send + Any>>>>,
pub(crate) store: Arc<Mutex<HashMap<Snowflake, Arc<RwLock<ObservableObject>>>>>,
}
/// An entity type which is supposed to be updateable via the Gateway. This is implemented for all such types chorus supports, implementing it for your own types is likely a mistake.
@ -196,27 +201,57 @@ impl GatewayHandle {
.unwrap();
}
pub async fn observe<T: Updateable>(&self, object: T) -> watch::Receiver<T> {
pub async fn observe<T: Updateable + Clone + Debug + Composite<T>>(
&self,
object: Arc<RwLock<T>>,
) -> Arc<RwLock<T>> {
let mut store = self.store.lock().await;
if let Some(channel) = store.get(&object.id()) {
let (_, rx) = channel
.downcast_ref::<(watch::Sender<T>, watch::Receiver<T>)>()
let id = object.read().unwrap().id();
if let Some(channel) = store.get(&id) {
let object = channel.clone();
drop(store);
object
.read()
.unwrap()
.downcast_ref::<T>()
.unwrap_or_else(|| {
panic!(
"Snowflake {} already exists in the store, but it is not of type T.",
object.id()
id
)
});
rx.clone()
let ptr = Arc::into_raw(object.clone());
// SAFETY:
// - We have just checked that the typeid of the `dyn Any ...` matches that of `T`.
// - This operation doesn't read or write any shared data, and thus cannot cause a data race
// - The reference count is not being modified
let downcasted = unsafe { Arc::from_raw(ptr as *const RwLock<T>).clone() };
let object = downcasted.read().unwrap().clone();
let watched_object = object.watch_whole(self).await;
*downcasted.write().unwrap() = watched_object;
downcasted
} else {
let id = object.id();
let channel = watch::channel(object);
let receiver = channel.1.clone();
store.insert(id, Box::new(channel));
receiver
let id = object.read().unwrap().id();
let object = object.read().unwrap().clone();
let object = object.clone().watch_whole(self).await;
let wrapped = Arc::new(RwLock::new(object));
store.insert(id, wrapped.clone());
wrapped
}
}
/// Recursively observes and updates all updateable fields on the struct T. Returns an object `T`
/// with all of its observable fields being observed.
pub async fn observe_and_into_inner<T: Updateable + Clone + Debug + Composite<T>>(
&self,
object: Arc<RwLock<T>>,
) -> T {
let channel = self.observe(object.clone()).await;
let object = channel.read().unwrap().clone();
object
}
/// Sends an identify event to the gateway
pub async fn send_identify(&self, to_send: types::GatewayIdentifyPayload) {
let to_send_value = serde_json::to_value(&to_send).unwrap();
@ -257,7 +292,7 @@ impl GatewayHandle {
/// Sends an update voice state to the server
pub async fn send_update_voice_state(&self, to_send: types::UpdateVoiceState) {
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 Update Voice State..");
@ -293,6 +328,7 @@ impl GatewayHandle {
}
}
#[derive(Debug)]
pub struct Gateway {
events: Arc<Mutex<Events>>,
heartbeat_handler: HeartbeatHandler,
@ -306,7 +342,8 @@ pub struct Gateway {
>,
websocket_receive: SplitStream<WebSocketStream<MaybeTlsStream<TcpStream>>>,
kill_send: tokio::sync::broadcast::Sender<()>,
store: Arc<Mutex<HashMap<Snowflake, Box<dyn Send + Any>>>>,
store: Arc<Mutex<HashMap<Snowflake, Arc<RwLock<ObservableObject>>>>>,
url: String,
}
impl Gateway {
@ -370,10 +407,11 @@ impl Gateway {
websocket_receive,
kill_send: kill_send.clone(),
store: store.clone(),
url: websocket_url.clone(),
};
// Now we can continuously check for messages in a different task, since we aren't going to receive another hello
let handle: JoinHandle<()> = task::spawn(async move {
task::spawn(async move {
gateway.gateway_listen_task().await;
});
@ -381,7 +419,6 @@ impl Gateway {
url: websocket_url.clone(),
events: shared_events,
websocket_send: shared_websocket_send.clone(),
handle,
kill_send: kill_send.clone(),
store,
})
@ -444,13 +481,15 @@ impl Gateway {
return;
}
// Todo: handle errors in a good way, maybe observers like events?
if msg.is_error() {
warn!("GW: Received error, connection will close..");
let error = msg.error().unwrap();
let _error = msg.error();
warn!("GW: Received error {:?}, connection will close..", error);
self.close().await;
self.events.lock().await.error.notify(error).await;
return;
}
@ -462,7 +501,7 @@ impl Gateway {
GATEWAY_DISPATCH => {
let Some(event_name) = gateway_payload.event_name else {
warn!("Gateway dispatch op without event_name");
return
return;
};
trace!("Gateway: Received {event_name}");
@ -472,16 +511,35 @@ impl Gateway {
match event_name.as_str() {
$($name => {
let event = &mut self.events.lock().await.$($path).+;
match serde_json::from_str(gateway_payload.event_data.unwrap().get()) {
let json = gateway_payload.event_data.unwrap().get();
match serde_json::from_str(json) {
Err(err) => warn!("Failed to parse gateway event {event_name} ({err})"),
Ok(message) => {
$(
let message: $message_type = message;
if let Some(to_update) = self.store.lock().await.get(&message.id()) {
if let Some((tx, _)) = to_update.downcast_ref::<(watch::Sender<$update_type>, watch::Receiver<$update_type>)>() {
tx.send_modify(|object| message.update(object));
let mut message: $message_type = message;
let store = self.store.lock().await;
let id = if message.id().is_some() {
message.id().unwrap()
} else {
warn!("Received {} for {}, but it has been observed to be a different type!", $name, message.id())
event.notify(message).await;
return;
};
if let Some(to_update) = store.get(&id) {
let object = to_update.clone();
let inner_object = object.read().unwrap();
if let Some(_) = inner_object.downcast_ref::<$update_type>() {
let ptr = Arc::into_raw(object.clone());
// SAFETY:
// - We have just checked that the typeid of the `dyn Any ...` matches that of `T`.
// - This operation doesn't read or write any shared data, and thus cannot cause a data race
// - The reference count is not being modified
let downcasted = unsafe { Arc::from_raw(ptr as *const RwLock<$update_type>).clone() };
drop(inner_object);
message.set_json(json.to_string());
message.set_source_url(self.url.clone());
message.update(downcasted.clone());
} else {
warn!("Received {} for {}, but it has been observed to be a different type!", $name, id)
}
}
)?
@ -523,69 +581,70 @@ impl Gateway {
"READY_SUPPLEMENTAL" => session.ready_supplemental,
"APPLICATION_COMMAND_PERMISSIONS_UPDATE" => application.command_permissions_update,
"AUTO_MODERATION_RULE_CREATE" =>auto_moderation.rule_create,
"AUTO_MODERATION_RULE_UPDATE" =>auto_moderation.rule_update,
"AUTO_MODERATION_RULE_UPDATE" =>auto_moderation.rule_update AutoModerationRuleUpdate: AutoModerationRule,
"AUTO_MODERATION_RULE_DELETE" => auto_moderation.rule_delete,
"AUTO_MODERATION_ACTION_EXECUTION" => auto_moderation.action_execution,
"CHANNEL_CREATE" => channel.create,
"CHANNEL_CREATE" => channel.create ChannelCreate: Guild,
"CHANNEL_UPDATE" => channel.update ChannelUpdate: Channel,
"CHANNEL_UNREAD_UPDATE" => channel.unread_update,
"CHANNEL_DELETE" => channel.delete,
"CHANNEL_DELETE" => channel.delete ChannelDelete: Guild,
"CHANNEL_PINS_UPDATE" => channel.pins_update,
"CALL_CREATE" => call.create,
"CALL_UPDATE" => call.update,
"CALL_DELETE" => call.delete,
"THREAD_CREATE" => thread.create,
"THREAD_UPDATE" => thread.update,
"THREAD_DELETE" => thread.delete,
"THREAD_LIST_SYNC" => thread.list_sync,
"THREAD_MEMBER_UPDATE" => thread.member_update,
"THREAD_MEMBERS_UPDATE" => thread.members_update,
"GUILD_CREATE" => guild.create,
"GUILD_UPDATE" => guild.update,
"GUILD_DELETE" => guild.delete,
"THREAD_CREATE" => thread.create, // TODO
"THREAD_UPDATE" => thread.update ThreadUpdate: Channel,
"THREAD_DELETE" => thread.delete, // TODO
"THREAD_LIST_SYNC" => thread.list_sync, // TODO
"THREAD_MEMBER_UPDATE" => thread.member_update, // TODO
"THREAD_MEMBERS_UPDATE" => thread.members_update, // TODO
"GUILD_CREATE" => guild.create, // TODO
"GUILD_UPDATE" => guild.update, // TODO
"GUILD_DELETE" => guild.delete, // TODO
"GUILD_AUDIT_LOG_ENTRY_CREATE" => guild.audit_log_entry_create,
"GUILD_BAN_ADD" => guild.ban_add,
"GUILD_BAN_REMOVE" => guild.ban_remove,
"GUILD_EMOJIS_UPDATE" => guild.emojis_update,
"GUILD_STICKERS_UPDATE" => guild.stickers_update,
"GUILD_BAN_ADD" => guild.ban_add, // TODO
"GUILD_BAN_REMOVE" => guild.ban_remove, // TODO
"GUILD_EMOJIS_UPDATE" => guild.emojis_update, // TODO
"GUILD_STICKERS_UPDATE" => guild.stickers_update, // TODO
"GUILD_INTEGRATIONS_UPDATE" => guild.integrations_update,
"GUILD_MEMBER_ADD" => guild.member_add,
"GUILD_MEMBER_REMOVE" => guild.member_remove,
"GUILD_MEMBER_UPDATE" => guild.member_update,
"GUILD_MEMBERS_CHUNK" => guild.members_chunk,
"GUILD_ROLE_CREATE" => guild.role_create,
"GUILD_ROLE_UPDATE" => guild.role_update,
"GUILD_ROLE_DELETE" => guild.role_delete,
"GUILD_SCHEDULED_EVENT_CREATE" => guild.role_scheduled_event_create,
"GUILD_SCHEDULED_EVENT_UPDATE" => guild.role_scheduled_event_update,
"GUILD_SCHEDULED_EVENT_DELETE" => guild.role_scheduled_event_delete,
"GUILD_MEMBER_UPDATE" => guild.member_update, // TODO
"GUILD_MEMBERS_CHUNK" => guild.members_chunk, // TODO
"GUILD_ROLE_CREATE" => guild.role_create GuildRoleCreate: Guild,
"GUILD_ROLE_UPDATE" => guild.role_update GuildRoleUpdate: RoleObject,
"GUILD_ROLE_DELETE" => guild.role_delete, // TODO
"GUILD_SCHEDULED_EVENT_CREATE" => guild.role_scheduled_event_create, // TODO
"GUILD_SCHEDULED_EVENT_UPDATE" => guild.role_scheduled_event_update, // TODO
"GUILD_SCHEDULED_EVENT_DELETE" => guild.role_scheduled_event_delete, // TODO
"GUILD_SCHEDULED_EVENT_USER_ADD" => guild.role_scheduled_event_user_add,
"GUILD_SCHEDULED_EVENT_USER_REMOVE" => guild.role_scheduled_event_user_remove,
"PASSIVE_UPDATE_V1" => guild.passive_update_v1,
"INTEGRATION_CREATE" => integration.create,
"INTEGRATION_UPDATE" => integration.update,
"INTEGRATION_DELETE" => integration.delete,
"INTERACTION_CREATE" => interaction.create,
"INVITE_CREATE" => invite.create,
"INVITE_DELETE" => invite.delete,
"PASSIVE_UPDATE_V1" => guild.passive_update_v1, // TODO
"INTEGRATION_CREATE" => integration.create, // TODO
"INTEGRATION_UPDATE" => integration.update, // TODO
"INTEGRATION_DELETE" => integration.delete, // TODO
"INTERACTION_CREATE" => interaction.create, // TODO
"INVITE_CREATE" => invite.create, // TODO
"INVITE_DELETE" => invite.delete, // TODO
"MESSAGE_CREATE" => message.create,
"MESSAGE_UPDATE" => message.update,
"MESSAGE_UPDATE" => message.update, // TODO
"MESSAGE_DELETE" => message.delete,
"MESSAGE_DELETE_BULK" => message.delete_bulk,
"MESSAGE_REACTION_ADD" => message.reaction_add,
"MESSAGE_REACTION_REMOVE" => message.reaction_remove,
"MESSAGE_REACTION_REMOVE_ALL" => message.reaction_remove_all,
"MESSAGE_REACTION_REMOVE_EMOJI" => message.reaction_remove_emoji,
"MESSAGE_REACTION_ADD" => message.reaction_add, // TODO
"MESSAGE_REACTION_REMOVE" => message.reaction_remove, // TODO
"MESSAGE_REACTION_REMOVE_ALL" => message.reaction_remove_all, // TODO
"MESSAGE_REACTION_REMOVE_EMOJI" => message.reaction_remove_emoji, // TODO
"MESSAGE_ACK" => message.ack,
"PRESENCE_UPDATE" => user.presence_update,
"PRESENCE_UPDATE" => user.presence_update, // TODO
"RELATIONSHIP_ADD" => relationship.add,
"RELATIONSHIP_REMOVE" => relationship.remove,
"STAGE_INSTANCE_CREATE" => stage_instance.create,
"STAGE_INSTANCE_UPDATE" => stage_instance.update,
"STAGE_INSTANCE_UPDATE" => stage_instance.update, // TODO
"STAGE_INSTANCE_DELETE" => stage_instance.delete,
"USER_UPDATE" => user.update,
"TYPING_START" => user.typing_start,
"USER_UPDATE" => user.update, // TODO
"USER_GUILD_SETTINGS_UPDATE" => user.guild_settings_update,
"VOICE_STATE_UPDATE" => voice.state_update,
"VOICE_STATE_UPDATE" => voice.state_update, // TODO
"VOICE_SERVER_UPDATE" => voice.server_update,
"WEBHOOKS_UPDATE" => webhooks.update
);
@ -671,6 +730,7 @@ impl Gateway {
/// Handles sending heartbeats to the gateway in another thread
#[allow(dead_code)] // FIXME: Remove this, once HeartbeatHandler is used
#[derive(Debug)]
struct HeartbeatHandler {
/// How ofter heartbeats need to be sent at a minimum
pub heartbeat_interval: Duration,
@ -857,7 +917,7 @@ impl<T: WebSocketEvent> GatewayEvent<T> {
}
}
mod events {
pub mod events {
use super::*;
#[derive(Default, Debug)]
@ -880,6 +940,7 @@ mod events {
pub webhooks: Webhooks,
pub gateway_identify_payload: GatewayEvent<types::GatewayIdentifyPayload>,
pub gateway_resume: GatewayEvent<types::GatewayResume>,
pub error: GatewayEvent<GatewayError>,
}
#[derive(Default, Debug)]
@ -927,7 +988,7 @@ mod events {
pub update: GatewayEvent<types::UserUpdate>,
pub guild_settings_update: GatewayEvent<types::UserGuildSettingsUpdate>,
pub presence_update: GatewayEvent<types::PresenceUpdate>,
pub typing_start_event: GatewayEvent<types::TypingStartEvent>,
pub typing_start: GatewayEvent<types::TypingStartEvent>,
}
#[derive(Default, Debug)]

View File

@ -1,24 +1,23 @@
use std::cell::RefCell;
//! Instance and ChorusUser objects.
use std::collections::HashMap;
use std::fmt;
use std::rc::Rc;
use std::sync::{Arc, RwLock};
use reqwest::Client;
use serde::{Deserialize, Serialize};
use crate::api::{Limit, LimitType};
use crate::errors::ChorusResult;
use crate::gateway::{Gateway, GatewayHandle};
use crate::ratelimiter::ChorusRequest;
use crate::types::types::subconfigs::limits::rates::RateLimits;
use crate::types::{GeneralConfiguration, User, UserSettings};
use crate::types::{GeneralConfiguration, Limit, LimitType, User, UserSettings};
use crate::UrlBundle;
#[derive(Debug, Clone)]
/**
The [`Instance`] what you will be using to perform all sorts of actions on the Spacebar server.
If `limits_information` is `None`, then the instance will not be rate limited.
*/
#[derive(Debug, Clone, Default)]
/// The [`Instance`]; what you will be using to perform all sorts of actions on the Spacebar server.
/// If `limits_information` is `None`, then the instance will not be rate limited.
pub struct Instance {
pub urls: UrlBundle,
pub instance_info: GeneralConfiguration,
@ -26,19 +25,14 @@ pub struct Instance {
pub client: Client,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct LimitsInformation {
pub ratelimits: HashMap<LimitType, Limit>,
pub configuration: RateLimits,
}
impl Instance {
/// Creates a new [`Instance`].
/// # Arguments
/// * `urls` - The [`URLBundle`] that contains all the URLs that are needed to connect to the Spacebar server.
/// * `requester` - The [`LimitedRequester`] that will be used to make requests to the Spacebar server.
/// # Errors
/// * [`InstanceError`] - If the instance cannot be created.
/// Creates a new [`Instance`] from the [relevant instance urls](UrlBundle), where `limited` is whether or not to automatically use rate limits.
pub async fn new(urls: UrlBundle, limited: bool) -> ChorusResult<Instance> {
let limits_information;
if limited {
@ -89,17 +83,20 @@ impl fmt::Display for Token {
}
}
#[derive(Debug)]
pub struct UserMeta {
pub belongs_to: Rc<RefCell<Instance>>,
#[derive(Debug, Clone)]
/// A ChorusUser is a representation of an authenticated user on an [Instance].
/// It is used for most authenticated actions on a Spacebar server.
/// It also has its own [Gateway] connection.
pub struct ChorusUser {
pub belongs_to: Arc<RwLock<Instance>>,
pub token: String,
pub limits: Option<HashMap<LimitType, Limit>>,
pub settings: UserSettings,
pub object: User,
pub settings: Arc<RwLock<UserSettings>>,
pub object: Arc<RwLock<User>>,
pub gateway: GatewayHandle,
}
impl UserMeta {
impl ChorusUser {
pub fn token(&self) -> String {
self.token.clone()
}
@ -108,15 +105,20 @@ impl UserMeta {
self.token = token;
}
/// Creates a new [ChorusUser] from existing data.
///
/// # Notes
/// This isn't the prefered way to create a ChorusUser.
/// See [Instance::login_account] and [Instance::register_account] instead.
pub fn new(
belongs_to: Rc<RefCell<Instance>>,
belongs_to: Arc<RwLock<Instance>>,
token: String,
limits: Option<HashMap<LimitType, Limit>>,
settings: UserSettings,
object: User,
settings: Arc<RwLock<UserSettings>>,
object: Arc<RwLock<User>>,
gateway: GatewayHandle,
) -> UserMeta {
UserMeta {
) -> ChorusUser {
ChorusUser {
belongs_to,
token,
limits,
@ -127,21 +129,22 @@ impl UserMeta {
}
/// Creates a new 'shell' of a user. The user does not exist as an object, and exists so that you have
/// a UserMeta object to make Rate Limited requests with. This is useful in scenarios like
/// a ChorusUser object to make Rate Limited requests with. This is useful in scenarios like
/// registering or logging in to the Instance, where you do not yet have a User object, but still
/// need to make a RateLimited request. To use the [`GatewayHandle`], you will have to identify
/// first.
pub(crate) async fn shell(instance: Rc<RefCell<Instance>>, token: String) -> UserMeta {
let settings = UserSettings::default();
let object = User::default();
let wss_url = instance.borrow().urls.wss.clone();
pub(crate) async fn shell(instance: Arc<RwLock<Instance>>, token: String) -> ChorusUser {
let settings = Arc::new(RwLock::new(UserSettings::default()));
let object = Arc::new(RwLock::new(User::default()));
let wss_url = instance.read().unwrap().urls.wss.clone();
// Dummy gateway object
let gateway = Gateway::new(wss_url).await.unwrap();
UserMeta {
ChorusUser {
token,
belongs_to: instance.clone(),
limits: instance
.borrow()
.read()
.unwrap()
.limits_information
.as_ref()
.map(|info| info.ratelimits.clone()),

View File

@ -1,4 +1,19 @@
//! A library for interacting with one or multiple Spacebar-compatible APIs and Gateways.
//!
//! # About
//!Chorus is a Rust library that allows developers to interact with multiple Spacebar-compatible APIs and Gateways simultaneously. The library provides a simple and efficient way to communicate with these services, making it easier for developers to build applications that rely on them. Chorus is open-source and welcomes contributions from the community.
#![doc(
html_logo_url = "https://raw.githubusercontent.com/polyphony-chat/design/main/branding/polyphony-chorus-round-8bit.png"
)]
#![allow(clippy::module_inception)]
#![deny(
missing_debug_implementations,
clippy::extra_unused_lifetimes,
clippy::from_over_into,
clippy::needless_borrow,
clippy::new_without_default,
clippy::useless_conversion
)]
use url::{ParseError, Url};
@ -15,16 +30,26 @@ pub mod types;
#[cfg(feature = "client")]
pub mod voice;
#[derive(Clone, Default, Debug, PartialEq, Eq)]
/// A URLBundle is a struct which bundles together the API-, Gateway- and CDN-URLs of a Spacebar
/// instance.
#[derive(Clone, Default, Debug, PartialEq, Eq, Hash)]
/// A URLBundle bundles together the API-, Gateway- and CDN-URLs of a Spacebar instance.
///
/// # Notes
/// All the urls can be found on the /api/policies/instance/domains endpoint of a spacebar server
pub struct UrlBundle {
/// The api's url.
/// Ex: `https://old.server.spacebar.chat/api`
pub api: String,
/// The gateway websocket url.
/// Note that because this is a websocket url, it will always start with `wss://` or `ws://`
/// Ex: `wss://gateway.old.server.spacebar.chat`
pub wss: String,
/// The CDN's url.
/// Ex: `https://cdn.old.server.spacebar.chat`
pub cdn: String,
}
impl UrlBundle {
/// Creates a new UrlBundle from the relevant urls.
pub fn new(api: String, wss: String, cdn: String) -> Self {
Self {
api: UrlBundle::parse_url(api),
@ -33,9 +58,10 @@ impl UrlBundle {
}
}
/// parse(url: String) parses a URL using the Url library and formats it in a standardized
/// way. If no protocol is given, HTTP (not HTTPS) is assumed.
/// # Example:
/// Parses a URL using the Url library and formats it in a standardized way.
/// If no protocol is given, HTTP (not HTTPS) is assumed.
///
/// # Examples:
/// ```rs
/// let url = parse_url("localhost:3000");
/// ```

View File

@ -1,3 +1,5 @@
//! Ratelimiter and request handling functionality.
use std::collections::HashMap;
use log::{self, debug};
@ -6,37 +8,81 @@ use serde::Deserialize;
use serde_json::from_str;
use crate::{
api::{Limit, LimitType},
errors::{ChorusError, ChorusResult},
instance::UserMeta,
types::{types::subconfigs::limits::rates::RateLimits, LimitsConfiguration},
instance::ChorusUser,
types::{types::subconfigs::limits::rates::RateLimits, Limit, LimitType, LimitsConfiguration},
};
/// Chorus' request struct. This struct is used to send rate-limited requests to the Spacebar server.
/// See <https://discord.com/developers/docs/topics/rate-limits#rate-limits> for more information.
#[derive(Debug)]
pub struct ChorusRequest {
pub request: RequestBuilder,
pub limit_type: LimitType,
}
impl ChorusRequest {
/// Makes a new [`ChorusRequest`].
/// # Arguments
/// * `method` - The HTTP method to use. Must be one of the following:
/// * [`http::Method::GET`]
/// * [`http::Method::POST`]
/// * [`http::Method::PUT`]
/// * [`http::Method::DELETE`]
/// * [`http::Method::PATCH`]
/// * [`http::Method::HEAD`]
#[allow(unused_variables)] // TODO: Add mfa_token to request, once we figure out *how* to do so correctly
pub fn new(
method: http::Method,
url: &str,
body: Option<String>,
audit_log_reason: Option<&str>,
mfa_token: Option<&str>,
chorus_user: Option<&mut ChorusUser>,
limit_type: LimitType,
) -> ChorusRequest {
let request = Client::new();
let mut request = match method {
http::Method::GET => request.get(url),
http::Method::POST => request.post(url),
http::Method::PUT => request.put(url),
http::Method::DELETE => request.delete(url),
http::Method::PATCH => request.patch(url),
http::Method::HEAD => request.head(url),
_ => panic!("Illegal state: Method not supported."),
};
if let Some(user) = chorus_user {
request = request.header("Authorization", user.token());
}
if let Some(body) = body {
// ONCE TOLD ME THE WORLD WAS GONNA ROLL ME
request = request
.body(body)
.header("Content-Type", "application/json");
}
if let Some(reason) = audit_log_reason {
request = request.header("X-Audit-Log-Reason", reason);
}
ChorusRequest {
request,
limit_type,
}
}
/// Sends a [`ChorusRequest`]. Checks if the user is rate limited, and if not, sends the request.
/// If the user is not rate limited and the instance has rate limits enabled, it will update the
/// rate limits.
#[allow(clippy::await_holding_refcell_ref)]
pub(crate) async fn send_request(self, user: &mut UserMeta) -> ChorusResult<Response> {
pub(crate) async fn send_request(self, user: &mut ChorusUser) -> ChorusResult<Response> {
if !ChorusRequest::can_send_request(user, &self.limit_type) {
log::info!("Rate limit hit. Bucket: {:?}", self.limit_type);
return Err(ChorusError::RateLimited {
bucket: format!("{:?}", self.limit_type),
});
}
let belongs_to = user.belongs_to.borrow();
let result = match belongs_to
.client
.execute(self.request.build().unwrap())
.await
{
let client = user.belongs_to.read().unwrap().client.clone();
let result = match client.execute(self.request.build().unwrap()).await {
Ok(result) => {
debug!("Request successful: {:?}", result);
result
@ -45,16 +91,17 @@ impl ChorusRequest {
log::warn!("Request failed: {:?}", error);
return Err(ChorusError::RequestFailed {
url: error.url().unwrap().to_string(),
error,
error: error.to_string(),
});
}
};
drop(belongs_to);
drop(client);
if !result.status().is_success() {
if result.status().as_u16() == 429 {
log::warn!("Rate limit hit unexpectedly. Bucket: {:?}. Setting the instances' remaining global limit to 0 to have cooldown.", self.limit_type);
user.belongs_to
.borrow_mut()
.write()
.unwrap()
.limits_information
.as_mut()
.unwrap()
@ -73,9 +120,9 @@ impl ChorusRequest {
Ok(result)
}
fn can_send_request(user: &mut UserMeta, limit_type: &LimitType) -> bool {
fn can_send_request(user: &mut ChorusUser, limit_type: &LimitType) -> bool {
log::trace!("Checking if user or instance is rate-limited...");
let mut belongs_to = user.belongs_to.borrow_mut();
let mut belongs_to = user.belongs_to.write().unwrap();
if belongs_to.limits_information.is_none() {
log::trace!("Instance indicates no rate limits are configured. Continuing.");
return true;
@ -236,7 +283,10 @@ impl ChorusRequest {
/// set to the current unix timestamp + the rate limit window. The remaining rate limit is
/// reset to the rate limit limit.
/// 2. The remaining rate limit is decreased by 1.
fn update_rate_limits(user: &mut UserMeta, limit_type: &LimitType, response_was_err: bool) {
fn update_rate_limits(user: &mut ChorusUser, limit_type: &LimitType, response_was_err: bool) {
if user.belongs_to.read().unwrap().limits_information.is_none() {
return;
}
let instance_dictated_limits = [
&LimitType::AuthLogin,
&LimitType::AuthRegister,
@ -257,7 +307,7 @@ impl ChorusRequest {
}
let time: u64 = chrono::Utc::now().timestamp() as u64;
for relevant_limit in relevant_limits.iter() {
let mut belongs_to = user.belongs_to.borrow_mut();
let mut belongs_to = user.belongs_to.write().unwrap();
let limit = match relevant_limit.0 {
LimitOrigin::Instance => {
log::trace!(
@ -292,6 +342,13 @@ impl ChorusRequest {
}
}
/// Gets the ratelimit configuration.
///
/// # Notes
/// This is a spacebar only endpoint.
///
/// # Reference
/// See <https://docs.spacebar.chat/routes/#get-/policies/instance/limits/>
pub(crate) async fn get_limits_config(url_api: &str) -> ChorusResult<LimitsConfiguration> {
let request = Client::new()
.get(format!("{}/policies/instance/limits/", url_api))
@ -302,7 +359,7 @@ impl ChorusRequest {
Err(e) => {
return Err(ChorusError::RequestFailed {
url: url_api.to_string(),
error: e,
error: e.to_string(),
})
}
};
@ -419,7 +476,7 @@ impl ChorusRequest {
/// Sends a [`ChorusRequest`] and returns a [`ChorusResult`] that contains nothing if the request
/// was successful, or a [`ChorusError`] if the request failed.
pub(crate) async fn handle_request_as_result(self, user: &mut UserMeta) -> ChorusResult<()> {
pub(crate) async fn handle_request_as_result(self, user: &mut ChorusUser) -> ChorusResult<()> {
match self.send_request(user).await {
Ok(_) => Ok(()),
Err(e) => Err(e),
@ -430,7 +487,7 @@ impl ChorusRequest {
/// was successful, or a [`ChorusError`] if the request failed.
pub(crate) async fn deserialize_response<T: for<'a> Deserialize<'a>>(
self,
user: &mut UserMeta,
user: &mut ChorusUser,
) -> ChorusResult<T> {
let response = self.send_request(user).await?;
debug!("Got response: {:?}", response);

View File

@ -18,7 +18,7 @@ use crate::types::config::types::subconfigs::guild::{
};
use crate::types::{Error, GuildError};
#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)]
#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize, Hash)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum GuildFeatures {
ActivitiesAlpha,
@ -139,7 +139,7 @@ pub enum GuildFeatures {
InvitesClosed,
}
#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize, Eq)]
#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize, Eq, Hash)]
pub struct GuildFeaturesList(Vec<GuildFeatures>);
impl Deref for GuildFeaturesList {

View File

@ -2,11 +2,9 @@ use std::collections::HashMap;
use serde::{Deserialize, Serialize};
use crate::{
api::LimitType,
types::config::types::subconfigs::limits::ratelimits::{
route::RouteRateLimit, RateLimitOptions,
},
use crate::types::{
config::types::subconfigs::limits::ratelimits::{route::RouteRateLimit, RateLimitOptions},
LimitType,
};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]

View File

@ -1,3 +1,5 @@
use std::sync::{Arc, RwLock};
use bitflags::bitflags;
use serde::{Deserialize, Serialize};
use serde_json::Value;
@ -6,8 +8,10 @@ use serde_repr::{Deserialize_repr, Serialize_repr};
use crate::types::utils::Snowflake;
use crate::types::{Team, User};
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))]
/// # Reference
/// See <https://discord.com/developers/docs/resources/application#application-resource>
pub struct Application {
pub id: Snowflake,
pub name: String,
@ -23,7 +27,7 @@ pub struct Application {
pub bot_require_code_grant: bool,
pub verify_key: String,
#[cfg_attr(feature = "sqlx", sqlx(skip))]
pub owner: User,
pub owner: Arc<RwLock<User>>,
pub flags: u64,
#[cfg(feature = "sqlx")]
pub redirect_uris: Option<sqlx::types::Json<Vec<String>>>,
@ -45,7 +49,7 @@ pub struct Application {
#[cfg(feature = "sqlx")]
pub install_params: Option<sqlx::types::Json<InstallParams>>,
#[cfg(not(feature = "sqlx"))]
pub install_params: Option<InstallParams>,
pub install_params: Option<Arc<RwLock<InstallParams>>>,
pub terms_of_service_url: Option<String>,
pub privacy_policy_url: Option<String>,
#[cfg_attr(feature = "sqlx", sqlx(skip))]
@ -93,44 +97,64 @@ impl Application {
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
/// # Reference
/// See <https://discord.com/developers/docs/resources/application#install-params-object>
pub struct InstallParams {
pub scopes: Vec<String>,
pub permissions: String,
}
bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash)]
/// # Reference
/// See <https://discord.com/developers/docs/resources/application#application-object-application-flags>
pub struct ApplicationFlags: u64 {
/// Indicates if an app uses the Auto Moderation API
const APPLICATION_AUTO_MODERATION_RULE_CREATE_BADGE = 1 << 6;
/// Intent required for bots in 100 or more servers to receive presence_update events
const GATEWAY_PRESENCE = 1 << 12;
/// Intent required for bots in under 100 servers to receive presence_update events, found on the Bot page in your app's settings on discord.com
const GATEWAY_PRESENCE_LIMITED = 1 << 13;
/// Intent required for bots in 100 or more servers to receive member-related events like guild_member_add.
/// See the list of member-related events under GUILD_MEMBERS
const GATEWAY_GUILD_MEMBERS = 1 << 14;
/// Intent required for bots in under 100 servers to receive member-related events like guild_member_add, found on the Bot page in your app's settings on discord.com.
/// See the list of member-related events under GUILD_MEMBERS
const GATEWAY_GUILD_MEMBERS_LIMITED = 1 << 15;
/// Indicates unusual growth of an app that prevents verification
const VERIFICATION_PENDING_GUILD_LIMIT = 1 << 16;
/// Indicates if an app is embedded within the Discord client (currently unavailable publicly)
const EMBEDDED = 1 << 17;
/// Intent required for bots in 100 or more servers to receive message content
const GATEWAY_MESSAGE_CONTENT = 1 << 18;
/// Intent required for bots in under 100 servers to receive message content, found on the Bot page in your app's settings on discord.com
const GATEWAY_MESSAGE_CONTENT_LIMITED = 1 << 19;
/// Indicates if an app has registered slash commands
const APPLICATION_COMMAND_BADGE = 1 << 23;
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[derive(Debug, Clone, Serialize, Deserialize)]
/// # Reference
/// See <https://discord.com/developers/docs/interactions/application-commands#application-command-object>
pub struct ApplicationCommand {
pub id: Snowflake,
pub application_id: Snowflake,
pub name: String,
pub description: String,
pub options: Vec<ApplicationCommandOption>,
pub options: Vec<Arc<RwLock<ApplicationCommandOption>>>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[derive(Debug, Clone, Serialize, Deserialize)]
/// Reference
/// See <https://discord.com/developers/docs/interactions/application-commands#application-command-object-application-command-option-structure>
pub struct ApplicationCommandOption {
pub r#type: ApplicationCommandOptionType,
pub name: String,
pub description: String,
pub required: bool,
pub choices: Vec<ApplicationCommandOptionChoice>,
pub options: Vec<ApplicationCommandOption>,
pub options: Arc<RwLock<Vec<ApplicationCommandOption>>>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
@ -139,45 +163,54 @@ pub struct ApplicationCommandOptionChoice {
pub value: Value,
}
#[derive(Debug, Clone, Copy, PartialEq, Serialize_repr, Deserialize_repr)]
#[derive(Debug, Clone, Copy, Serialize_repr, Deserialize_repr, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
#[repr(i32)]
/// # Reference
/// See <https://discord.com/developers/docs/interactions/application-commands#application-command-object-application-command-types>
pub enum ApplicationCommandOptionType {
SubCommand = 1,
SubCommandGroup = 2,
String = 3,
/// Any integer between -2^53 and 2^53
Integer = 4,
Boolean = 5,
User = 6,
/// Includes all channel types + categories
Channel = 7,
Role = 8,
/// Includes users and roles
Mentionable = 9,
/// Any double between -2^53 and 2^53
Number = 10,
Attachment = 11,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ApplicationCommandInteractionData {
pub id: Snowflake,
pub name: String,
pub options: Vec<ApplicationCommandInteractionDataOption>,
pub options: Vec<Arc<RwLock<ApplicationCommandInteractionDataOption>>>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ApplicationCommandInteractionDataOption {
pub name: String,
pub value: Value,
pub options: Vec<ApplicationCommandInteractionDataOption>,
pub options: Vec<Arc<RwLock<ApplicationCommandInteractionDataOption>>>,
}
#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)]
/// See https://discord.com/developers/docs/interactions/application-commands#application-command-permissions-object-guild-application-command-permissions-structure
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
/// See <https://discord.com/developers/docs/interactions/application-commands#application-command-permissions-object-guild-application-command-permissions-structure>
pub struct GuildApplicationCommandPermissions {
pub id: Snowflake,
pub application_id: Snowflake,
pub guild_id: Snowflake,
pub permissions: Vec<ApplicationCommandPermission>,
pub permissions: Vec<Arc<RwLock<ApplicationCommandPermission>>>,
}
#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)]
/// 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 id: Snowflake,
#[serde(rename = "type")]
@ -186,10 +219,10 @@ pub struct ApplicationCommandPermission {
pub permission: bool,
}
#[derive(Serialize_repr, Deserialize_repr, Debug, Default, Clone, PartialEq)]
#[derive(Serialize_repr, Deserialize_repr, Debug, Default, Clone, PartialEq, Eq, Hash)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
#[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>
pub enum ApplicationCommandPermissionType {
#[default]
Role = 1,

View File

@ -2,11 +2,14 @@ use serde::{Deserialize, Serialize};
use crate::types::utils::Snowflake;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, PartialOrd)]
#[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))]
/// # Reference
/// See <https://discord.com/developers/docs/resources/channel#attachment-object>
pub struct Attachment {
pub id: Snowflake,
pub filename: String,
/// Max 1024 characters
pub description: Option<String>,
pub content_type: Option<String>,
pub size: u64,
@ -15,17 +18,24 @@ pub struct Attachment {
pub height: Option<u64>,
pub width: Option<u64>,
pub ephemeral: Option<bool>,
/// The duration of the audio file (only for voice messages)
pub duration_secs: Option<f32>,
/// A Base64 encoded bytearray representing a sampled waveform (only for voice messages)
///
/// # Notes
/// Note that this is computed on the client side.
/// This means it can be spoofed and isn't necessarily accurate.
pub waveform: Option<String>,
#[serde(skip_serializing)]
#[cfg_attr(feature = "sqlx", sqlx(default))]
pub content: Option<Vec<u8>>,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
pub struct PartialDiscordFileAttachment {
pub id: Option<i16>,
pub filename: String,
/// Max 1024 characters
pub description: Option<String>,
pub content_type: Option<String>,
pub size: Option<i64>,
@ -34,18 +44,20 @@ pub struct PartialDiscordFileAttachment {
pub height: Option<i32>,
pub width: Option<i32>,
pub ephemeral: Option<bool>,
/// The duration of the audio file (only for voice messages)
pub duration_secs: Option<f32>,
/// A Base64 encoded bytearray representing a sampled waveform (only for voice messages)
///
/// # Notes
/// Note that this is computed on the client side.
/// This means it can be spoofed and isn't necessarily accurate.
pub waveform: Option<String>,
#[serde(skip_serializing)]
pub content: Vec<u8>,
}
impl PartialDiscordFileAttachment {
/**
Moves `self.content` out of `self` and returns it.
# Returns
Vec<u8>
*/
/// Moves `self.content` out of `self` and returns it.
pub fn move_content(self) -> (Vec<u8>, PartialDiscordFileAttachment) {
let content = self.content;
let updated_struct = PartialDiscordFileAttachment {
@ -66,6 +78,7 @@ impl PartialDiscordFileAttachment {
(content, updated_struct)
}
/// Moves `self.filename` out of `self` and returns it.
pub fn move_filename(self) -> (String, PartialDiscordFileAttachment) {
let filename = self.filename;
let updated_struct = PartialDiscordFileAttachment {
@ -87,6 +100,7 @@ impl PartialDiscordFileAttachment {
(filename, updated_struct)
}
/// Moves `self.content_type` out of `self` and returns it.
pub fn move_content_type(self) -> (Option<String>, PartialDiscordFileAttachment) {
let content_type = self.content_type;
let updated_struct = PartialDiscordFileAttachment {

View File

@ -1,12 +1,14 @@
use std::sync::{Arc, RwLock};
use serde::{Deserialize, Serialize};
use crate::types::utils::Snowflake;
#[derive(Serialize, Deserialize, Debug, Default, Clone)]
/// 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 target_id: Option<String>,
pub changes: Option<Vec<AuditLogChange>>,
pub changes: Option<Vec<Arc<RwLock<AuditLogChange>>>>,
pub user_id: Option<Snowflake>,
pub id: Snowflake,
// to:do implement an enum for these types
@ -17,7 +19,7 @@ pub struct AuditLogEntry {
}
#[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 new_value: Option<serde_json::Value>,
pub old_value: Option<serde_json::Value>,

View File

@ -1,10 +1,19 @@
use std::sync::{Arc, RwLock};
#[cfg(feature = "client")]
use crate::gateway::Updateable;
#[cfg(feature = "client")]
use chorus_macros::Updateable;
use serde::{Deserialize, Serialize};
use serde_repr::{Deserialize_repr, Serialize_repr};
use crate::types::utils::Snowflake;
#[cfg_attr(feature = "client", derive(Updateable))]
#[derive(Serialize, Deserialize, Debug, Default, Clone)]
/// See https://discord.com/developers/docs/resources/auto-moderation#auto-moderation-rule-object
/// See <https://discord.com/developers/docs/resources/auto-moderation#auto-moderation-rule-object>
pub struct AutoModerationRule {
pub id: Snowflake,
pub guild_id: Snowflake,
@ -12,8 +21,8 @@ pub struct AutoModerationRule {
pub creator_id: Snowflake,
pub event_type: AutoModerationRuleEventType,
pub trigger_type: AutoModerationRuleTriggerType,
pub trigger_metadata: AutoModerationRuleTriggerMetadata,
pub actions: Vec<AutoModerationAction>,
pub trigger_metadata: Arc<RwLock<AutoModerationRuleTriggerMetadata>>,
pub actions: Vec<Arc<RwLock<AutoModerationAction>>>,
pub enabled: bool,
pub exempt_roles: Vec<Snowflake>,
pub exempt_channels: Vec<Snowflake>,
@ -22,7 +31,7 @@ pub struct AutoModerationRule {
#[derive(Serialize_repr, Deserialize_repr, Debug, Clone, Default)]
#[repr(u8)]
#[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>
pub enum AutoModerationRuleEventType {
#[default]
MessageSend = 1,
@ -31,7 +40,7 @@ pub enum AutoModerationRuleEventType {
#[derive(Serialize_repr, Deserialize_repr, Debug, Clone, Default)]
#[repr(u8)]
#[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>
pub enum AutoModerationRuleTriggerType {
#[default]
Keyword = 1,
@ -42,7 +51,7 @@ pub enum AutoModerationRuleTriggerType {
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
#[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 {
ForKeyword(AutoModerationRuleTriggerMetadataForKeyword),
ForKeywordPreset(AutoModerationRuleTriggerMetadataForKeywordPreset),
@ -52,7 +61,7 @@ pub enum AutoModerationRuleTriggerMetadata {
}
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
/// 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 keyword_filter: Vec<String>,
pub regex_patterns: Vec<String>,
@ -60,14 +69,14 @@ pub struct AutoModerationRuleTriggerMetadataForKeyword {
}
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
/// 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 presets: Vec<AutoModerationRuleKeywordPresetType>,
pub allow_list: Vec<String>,
}
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
/// 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 {
/// Max 50
pub mention_total_limit: u8,
@ -77,7 +86,7 @@ pub struct AutoModerationRuleTriggerMetadataForMentionSpam {
#[derive(Serialize_repr, Deserialize_repr, Debug, Clone, Default)]
#[repr(u8)]
#[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>
pub enum AutoModerationRuleKeywordPresetType {
#[default]
Profanity = 1,
@ -86,17 +95,17 @@ pub enum AutoModerationRuleKeywordPresetType {
}
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
/// See https://discord.com/developers/docs/resources/auto-moderation#auto-moderation-action-object
/// See <https://discord.com/developers/docs/resources/auto-moderation#auto-moderation-action-object>
pub struct AutoModerationAction {
#[serde(rename = "type")]
pub action_type: AutoModerationActionType,
pub metadata: Option<AutoModerationActionMetadata>,
pub metadata: Option<Arc<RwLock<AutoModerationActionMetadata>>>,
}
#[derive(Serialize_repr, Deserialize_repr, Debug, Clone, Default)]
#[repr(u8)]
#[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>
pub enum AutoModerationActionType {
#[default]
BlockMessage = 1,
@ -106,7 +115,7 @@ pub enum AutoModerationActionType {
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
#[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 {
ForBlockMessage(AutoModerationActionMetadataForBlockMessage),
ForSendAlertMessage(AutoModerationActionMetadataForSendAlertMessage),
@ -116,19 +125,19 @@ pub enum AutoModerationActionMetadata {
}
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
/// 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 custom_message: Option<String>,
}
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
/// 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 channel_id: Snowflake,
}
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
/// 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 {
/// Max 2419200
pub duration_seconds: u32,

View File

@ -1,17 +1,32 @@
use chorus_macros::Updateable;
use chrono::Utc;
use std::sync::{Arc, RwLock};
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use serde_aux::prelude::deserialize_string_from_number;
use serde_repr::{Deserialize_repr, Serialize_repr};
use std::fmt::Debug;
use crate::gateway::Updateable;
use crate::types::{
entities::{GuildMember, User},
utils::Snowflake,
};
#[derive(Default, Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Updateable)]
#[cfg(feature = "client")]
use crate::types::Composite;
#[cfg(feature = "client")]
use crate::gateway::{GatewayHandle, Updateable};
#[cfg(feature = "client")]
use chorus_macros::{observe_option_vec, Composite, Updateable};
#[derive(Default, Debug, Serialize, Deserialize, Clone)]
#[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))]
#[cfg_attr(feature = "client", derive(Updateable, Composite))]
/// Represents a guild or private channel
///
/// # Reference
/// See <https://discord-userdoccers.vercel.app/resources/channel#channels-resource>
pub struct Channel {
pub application_id: Option<Snowflake>,
#[cfg(feature = "sqlx")]
@ -39,7 +54,7 @@ pub struct Channel {
pub icon: Option<String>,
pub id: Snowflake,
pub last_message_id: Option<Snowflake>,
pub last_pin_timestamp: Option<String>,
pub last_pin_timestamp: Option<DateTime<Utc>>,
pub managed: Option<bool>,
#[cfg_attr(feature = "sqlx", sqlx(skip))]
pub member: Option<ThreadMember>,
@ -52,12 +67,14 @@ pub struct Channel {
#[cfg(feature = "sqlx")]
pub permission_overwrites: Option<sqlx::types::Json<Vec<PermissionOverwrite>>>,
#[cfg(not(feature = "sqlx"))]
pub permission_overwrites: Option<Vec<PermissionOverwrite>>,
#[cfg_attr(feature = "client", observe_option_vec)]
pub permission_overwrites: Option<Vec<Arc<RwLock<PermissionOverwrite>>>>,
pub permissions: Option<String>,
pub position: Option<i32>,
pub rate_limit_per_user: Option<i32>,
#[cfg_attr(feature = "sqlx", sqlx(skip))]
pub recipients: Option<Vec<User>>,
#[cfg_attr(feature = "client", observe_option_vec)]
pub recipients: Option<Vec<Arc<RwLock<User>>>>,
pub rtc_region: Option<String>,
#[cfg_attr(feature = "sqlx", sqlx(skip))]
pub thread_metadata: Option<ThreadMetadata>,
@ -67,16 +84,57 @@ pub struct Channel {
pub video_quality_mode: Option<i32>,
}
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)]
impl PartialEq for Channel {
fn eq(&self, other: &Self) -> bool {
self.application_id == other.application_id
&& self.bitrate == other.bitrate
&& self.channel_type == other.channel_type
&& self.created_at == other.created_at
&& self.default_auto_archive_duration == other.default_auto_archive_duration
&& self.default_forum_layout == other.default_forum_layout
&& self.default_sort_order == other.default_sort_order
&& self.default_thread_rate_limit_per_user == other.default_thread_rate_limit_per_user
&& self.flags == other.flags
&& self.guild_id == other.guild_id
&& self.icon == other.icon
&& self.id == other.id
&& self.last_message_id == other.last_message_id
&& self.last_pin_timestamp == other.last_pin_timestamp
&& self.managed == other.managed
&& self.member_count == other.member_count
&& self.message_count == other.message_count
&& self.name == other.name
&& self.nsfw == other.nsfw
&& self.owner_id == other.owner_id
&& self.parent_id == other.parent_id
&& self.permissions == other.permissions
&& self.position == other.position
&& self.rate_limit_per_user == other.rate_limit_per_user
&& self.rtc_region == other.rtc_region
&& self.topic == other.topic
&& self.total_message_sent == other.total_message_sent
&& self.user_limit == other.user_limit
&& self.video_quality_mode == other.video_quality_mode
}
}
#[derive(Debug, Deserialize, Serialize, Clone)]
/// A tag that can be applied to a thread in a [ChannelType::GuildForum] or [ChannelType::GuildMedia] channel.
///
/// # Reference
/// See <https://discord-userdoccers.vercel.app/resources/channel#forum-tag-object>
pub struct Tag {
pub id: Snowflake,
/// The name of the tag (max 20 characters)
pub name: String,
/// Whether this tag can only be added to or removed from threads by members with the [MANAGE_THREADS](crate::types::PermissionFlags::MANAGE_THREADS) permission
pub moderated: bool,
pub emoji_id: Option<Snowflake>,
pub emoji_name: Option<String>,
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, PartialOrd)]
#[cfg_attr(feature = "client", derive(Updateable, Composite))]
pub struct PermissionOverwrite {
pub id: Snowflake,
#[serde(rename = "type")]
@ -91,6 +149,8 @@ pub struct PermissionOverwrite {
}
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)]
/// # Reference
/// See <https://discord-userdoccers.vercel.app/resources/channel#thread-metadata-object>
pub struct ThreadMetadata {
pub archived: bool,
pub auto_archive_duration: i32,
@ -100,47 +160,93 @@ pub struct ThreadMetadata {
pub create_timestamp: Option<String>,
}
#[derive(Default, Debug, Deserialize, Serialize, Clone, PartialEq, Eq)]
#[derive(Default, Debug, Deserialize, Serialize, Clone)]
/// # Reference
/// See <https://discord-userdoccers.vercel.app/resources/channel#thread-member-object>
pub struct ThreadMember {
pub id: Option<Snowflake>,
pub user_id: Option<Snowflake>,
pub join_timestamp: Option<String>,
pub flags: Option<u64>,
pub member: Option<GuildMember>,
pub member: Option<Arc<RwLock<GuildMember>>>,
}
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)]
/// Specifies the emoji to use as the default way to react to a [ChannelType::GuildForum] or [ChannelType::GuildMedia] channel post.
///
/// # Reference
/// See <https://discord-userdoccers.vercel.app/resources/channel#default-reaction-object>
pub struct DefaultReaction {
#[serde(default)]
pub emoji_id: Option<Snowflake>,
pub emoji_name: Option<String>,
}
#[derive(Default, Clone, Copy, Debug, Serialize_repr, Deserialize_repr, PartialEq, Eq)]
#[derive(
Default,
Clone,
Copy,
Debug,
Serialize_repr,
Deserialize_repr,
PartialEq,
Eq,
Hash,
PartialOrd,
Ord,
)]
#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
#[repr(i32)]
#[repr(u32)]
/// # Reference
/// See <https://discord-userdoccers.vercel.app/resources/channel#channel-type>
pub enum ChannelType {
#[default]
/// A text channel within a guild
GuildText = 0,
/// A private channel between two users
Dm = 1,
/// A voice channel within a guild
GuildVoice = 2,
/// A private channel between multiple users
GroupDm = 3,
/// An organizational category that contains up to 50 channels
GuildCategory = 4,
/// Similar to [GuildText](ChannelType::GuildText), a channel that users can follow and crosspost into their own guild
GuildNews = 5,
/// A channel in which game developers can sell their game on Discord
///
/// # Note
/// Deprecated.
GuildStore = 6,
// FIXME userdoccers says 7 is GuildLfg, is this a spacebar specific thing?
Encrypted = 7,
// FIXME userdoccers says 8 is LfgGuildDm, is this a spacebar specific thing?
EncryptedThreads = 8,
// FIXME userdoccers says 9 is ThreadAlpha, was this changed?
Transactional = 9,
/// A thread within a [GuildNews](ChannelType::GuildNews) channel
GuildNewsThread = 10,
/// A thread within a [GuildText](ChannelType::GuildText), [GuildForum](ChannelType::GuildForum), or [GuildMedia](ChannelType::GuildMedia) channel
GuildPublicThread = 11,
/// A thread within a [GuildText](ChannelType::GuildText) channel, that is only viewable by those invited and those with the [MANAGE_THREADS](crate::types::entities::PermissionFlags::MANAGE_THREADS) permission
GuildPrivateThread = 12,
/// A voice channel for hosting events with an audience in a guild
GuildStageVoice = 13,
/// The main channel in a hub containing the listed guilds
Directory = 14,
/// A channel that can only contain threads
GuildForum = 15,
/// A channel that can only contain threads in a gallery view
GuildMedia = 16,
// TODO: Couldn't find reference
TicketTracker = 33,
// TODO: Couldn't find reference
Kanban = 34,
// TODO: Couldn't find reference
VoicelessWhiteboard = 35,
// TODO: Couldn't find reference
CustomStart = 64,
// TODO: Couldn't find reference
Unhandled = 255,
}

View File

@ -1,21 +1,95 @@
use std::fmt::Debug;
use std::sync::{Arc, RwLock};
use serde::{Deserialize, Serialize};
use crate::types::entities::User;
use crate::types::Snowflake;
#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize, Default)]
#[cfg(feature = "client")]
use crate::types::Composite;
#[cfg(feature = "client")]
use crate::gateway::{GatewayHandle, Updateable};
#[cfg(feature = "client")]
use chorus_macros::{Composite, Updateable};
#[derive(Debug, Clone, Deserialize, Serialize, Default)]
#[cfg_attr(feature = "client", derive(Updateable, Composite))]
#[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))]
/// # Reference
/// See <https://discord-userdoccers.vercel.app/resources/emoji#emoji-object>
pub struct Emoji {
pub id: Option<Snowflake>,
pub id: Snowflake,
pub name: Option<String>,
#[cfg(feature = "sqlx")]
pub roles: Option<sqlx::types::Json<Vec<Snowflake>>>,
#[cfg(not(feature = "sqlx"))]
pub roles: Option<Vec<Snowflake>>,
#[cfg_attr(feature = "sqlx", sqlx(skip))]
pub user: Option<User>,
pub user: Option<Arc<RwLock<User>>>,
pub require_colons: Option<bool>,
pub managed: Option<bool>,
pub animated: Option<bool>,
pub available: Option<bool>,
}
impl std::hash::Hash for Emoji {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.id.hash(state);
self.name.hash(state);
self.roles.hash(state);
self.roles.hash(state);
self.require_colons.hash(state);
self.managed.hash(state);
self.animated.hash(state);
self.available.hash(state);
}
}
impl PartialEq for Emoji {
fn eq(&self, other: &Self) -> bool {
!(self.id != other.id
|| self.name != other.name
|| self.roles != other.roles
|| self.require_colons != other.require_colons
|| self.managed != other.managed
|| self.animated != other.animated
|| self.available != other.available)
}
}
impl PartialOrd for Emoji {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
match self.id.partial_cmp(&other.id) {
Some(core::cmp::Ordering::Equal) => {}
ord => return ord,
}
match self.name.partial_cmp(&other.name) {
Some(core::cmp::Ordering::Equal) => {}
ord => return ord,
}
match self.roles.partial_cmp(&other.roles) {
Some(core::cmp::Ordering::Equal) => {}
ord => return ord,
}
match self.roles.partial_cmp(&other.roles) {
Some(core::cmp::Ordering::Equal) => {}
ord => return ord,
}
match self.require_colons.partial_cmp(&other.require_colons) {
Some(core::cmp::Ordering::Equal) => {}
ord => return ord,
}
match self.managed.partial_cmp(&other.managed) {
Some(core::cmp::Ordering::Equal) => {}
ord => return ord,
}
match self.animated.partial_cmp(&other.animated) {
Some(core::cmp::Ordering::Equal) => {}
ord => return ord,
}
self.available.partial_cmp(&other.available)
}
}

View File

@ -1,3 +1,6 @@
use std::fmt::Debug;
use std::sync::{Arc, RwLock};
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use serde_repr::{Deserialize_repr, Serialize_repr};
@ -8,9 +11,22 @@ use crate::types::{
interfaces::WelcomeScreenObject,
utils::Snowflake,
};
use bitflags::bitflags;
/// See https://discord.com/developers/docs/resources/guild
#[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq)]
use super::PublicUser;
#[cfg(feature = "client")]
use crate::gateway::{GatewayHandle, Updateable};
#[cfg(feature = "client")]
use chorus_macros::{observe_option_vec, observe_vec, Composite, Updateable};
#[cfg(feature = "client")]
use crate::types::Composite;
/// See <https://discord.com/developers/docs/resources/guild>
#[derive(Serialize, Deserialize, Debug, Default, Clone)]
#[cfg_attr(feature = "client", derive(Updateable, Composite))]
#[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))]
pub struct Guild {
pub afk_channel_id: Option<Snowflake>,
@ -25,13 +41,15 @@ pub struct Guild {
#[cfg_attr(feature = "sqlx", sqlx(skip))]
pub bans: Option<Vec<GuildBan>>,
#[cfg_attr(feature = "sqlx", sqlx(skip))]
pub channels: Option<Vec<Channel>>,
pub default_message_notifications: Option<i32>,
#[cfg_attr(feature = "client", observe_option_vec)]
pub channels: Option<Vec<Arc<RwLock<Channel>>>>,
pub default_message_notifications: Option<MessageNotificationLevel>,
pub description: Option<String>,
pub discovery_splash: Option<String>,
#[cfg_attr(feature = "sqlx", sqlx(skip))]
#[cfg_attr(feature = "client", observe_vec)]
#[serde(default)]
pub emojis: Vec<Emoji>,
pub emojis: Vec<Arc<RwLock<Emoji>>>,
pub explicit_content_filter: Option<i32>,
//#[cfg_attr(feature = "sqlx", sqlx(try_from = "String"))]
pub features: Option<GuildFeaturesList>,
@ -49,9 +67,9 @@ pub struct Guild {
#[cfg_attr(feature = "sqlx", sqlx(skip))]
pub max_stage_video_channel_users: Option<i32>,
pub max_video_channel_users: Option<i32>,
pub mfa_level: Option<i32>,
pub mfa_level: Option<MFALevel>,
pub name: Option<String>,
pub nsfw_level: Option<i32>,
pub nsfw_level: Option<NSFWLevel>,
#[cfg_attr(feature = "sqlx", sqlx(skip))]
pub owner: Option<bool>,
// True if requesting user is owner
@ -61,27 +79,30 @@ pub struct Guild {
pub preferred_locale: Option<String>,
pub premium_progress_bar_enabled: Option<bool>,
pub premium_subscription_count: Option<i32>,
pub premium_tier: Option<i32>,
pub premium_tier: Option<PremiumTier>,
pub primary_category_id: Option<Snowflake>,
pub public_updates_channel_id: Option<Snowflake>,
pub region: Option<String>,
#[cfg_attr(feature = "sqlx", sqlx(skip))]
pub roles: Option<Vec<RoleObject>>,
#[cfg_attr(feature = "client", observe_option_vec)]
pub roles: Option<Vec<Arc<RwLock<RoleObject>>>>,
#[cfg_attr(feature = "sqlx", sqlx(skip))]
pub rules_channel: Option<String>,
pub rules_channel_id: Option<Snowflake>,
pub splash: Option<String>,
#[cfg_attr(feature = "sqlx", sqlx(skip))]
pub stickers: Option<Vec<Sticker>>,
pub system_channel_flags: Option<i32>,
pub system_channel_flags: Option<u64>,
pub system_channel_id: Option<Snowflake>,
#[cfg_attr(feature = "sqlx", sqlx(skip))]
pub vanity_url_code: Option<String>,
pub verification_level: Option<i32>,
pub verification_level: Option<VerificationLevel>,
#[cfg_attr(feature = "sqlx", sqlx(skip))]
pub voice_states: Option<Vec<VoiceState>>,
#[cfg_attr(feature = "client", observe_option_vec)]
pub voice_states: Option<Vec<Arc<RwLock<VoiceState>>>>,
#[cfg_attr(feature = "sqlx", sqlx(skip))]
pub webhooks: Option<Vec<Webhook>>,
#[cfg_attr(feature = "client", observe_option_vec)]
pub webhooks: Option<Vec<Arc<RwLock<Webhook>>>>,
#[cfg(feature = "sqlx")]
pub welcome_screen: Option<sqlx::types::Json<WelcomeScreenObject>>,
#[cfg(not(feature = "sqlx"))]
@ -90,17 +111,121 @@ pub struct Guild {
pub widget_enabled: Option<bool>,
}
/// See https://docs.spacebar.chat/routes/#get-/guilds/-guild_id-/bans/-user-
#[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq, Eq)]
impl std::hash::Hash for Guild {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
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 {
self.afk_channel_id == other.afk_channel_id
&& self.afk_timeout == other.afk_timeout
&& self.application_id == other.application_id
&& self.approximate_member_count == other.approximate_member_count
&& self.approximate_presence_count == other.approximate_presence_count
&& self.banner == other.banner
&& self.bans == other.bans
&& self.default_message_notifications == other.default_message_notifications
&& self.description == other.description
&& self.discovery_splash == other.discovery_splash
&& self.explicit_content_filter == other.explicit_content_filter
&& self.features == other.features
&& self.icon == other.icon
&& self.icon_hash == other.icon_hash
&& self.id == other.id
&& self.joined_at == other.joined_at
&& self.large == other.large
&& self.max_members == other.max_members
&& self.max_presences == other.max_presences
&& self.max_stage_video_channel_users == other.max_stage_video_channel_users
&& self.max_video_channel_users == other.max_video_channel_users
&& self.mfa_level == other.mfa_level
&& self.name == other.name
&& self.nsfw_level == other.nsfw_level
&& self.owner == other.owner
&& self.owner_id == other.owner_id
&& self.permissions == other.permissions
&& self.preferred_locale == other.preferred_locale
&& self.premium_progress_bar_enabled == other.premium_progress_bar_enabled
&& self.premium_subscription_count == other.premium_subscription_count
&& self.premium_tier == other.premium_tier
&& self.primary_category_id == other.primary_category_id
&& self.public_updates_channel_id == other.public_updates_channel_id
&& self.region == other.region
&& self.rules_channel == other.rules_channel
&& self.rules_channel_id == other.rules_channel_id
&& self.splash == other.splash
&& self.stickers == other.stickers
&& self.system_channel_flags == other.system_channel_flags
&& self.system_channel_id == other.system_channel_id
&& self.vanity_url_code == other.vanity_url_code
&& self.verification_level == other.verification_level
&& self.welcome_screen == other.welcome_screen
&& self.welcome_screen == other.welcome_screen
&& self.widget_channel_id == other.widget_channel_id
&& self.widget_enabled == other.widget_enabled
}
}
impl std::cmp::Eq for Guild {}
/// See <https://docs.spacebar.chat/routes/#get-/guilds/-guild_id-/bans/-user->
#[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))]
pub struct GuildBan {
pub user_id: Snowflake,
pub guild_id: Snowflake,
pub user: PublicUser,
pub reason: Option<String>,
}
/// See https://docs.spacebar.chat/routes/#cmp--schemas-invite
#[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq)]
/// See <https://docs.spacebar.chat/routes/#cmp--schemas-invite>
#[derive(Serialize, Deserialize, Debug, Default, Clone)]
#[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))]
pub struct GuildInvite {
pub code: String,
@ -111,21 +236,40 @@ pub struct GuildInvite {
pub created_at: DateTime<Utc>,
pub expires_at: Option<DateTime<Utc>>,
pub guild_id: Snowflake,
pub guild: Option<Guild>,
pub guild: Option<Arc<RwLock<Guild>>>,
pub channel_id: Snowflake,
pub channel: Option<Channel>,
pub channel: Option<Arc<RwLock<Channel>>>,
pub inviter_id: Option<Snowflake>,
pub inviter: Option<User>,
pub inviter: Option<Arc<RwLock<User>>>,
pub target_user_id: Option<Snowflake>,
pub target_user: Option<String>,
pub target_user_type: Option<i32>,
pub vanity_url: Option<bool>,
}
#[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq)]
impl std::hash::Hash for GuildInvite {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.code.hash(state);
self.temporary.hash(state);
self.uses.hash(state);
self.max_uses.hash(state);
self.max_age.hash(state);
self.created_at.hash(state);
self.expires_at.hash(state);
self.guild_id.hash(state);
self.channel_id.hash(state);
self.inviter_id.hash(state);
self.target_user_id.hash(state);
self.target_user.hash(state);
self.target_user_type.hash(state);
self.vanity_url.hash(state);
}
}
#[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq, Hash)]
pub struct UnavailableGuild {
id: Snowflake,
unavailable: bool,
pub id: Snowflake,
pub unavailable: bool,
}
#[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq)]
@ -134,7 +278,7 @@ pub struct GuildCreateResponse {
}
#[derive(Serialize, Deserialize, Debug, Default, Clone)]
/// See https://discord.com/developers/docs/resources/guild-scheduled-event#guild-scheduled-event-object
/// See <https://discord.com/developers/docs/resources/guild-scheduled-event#guild-scheduled-event-object>
pub struct GuildScheduledEvent {
pub id: Snowflake,
pub guild_id: Snowflake,
@ -149,14 +293,14 @@ pub struct GuildScheduledEvent {
pub entity_type: GuildScheduledEventEntityType,
pub entity_id: Option<Snowflake>,
pub entity_metadata: Option<GuildScheduledEventEntityMetadata>,
pub creator: Option<User>,
pub creator: Option<Arc<RwLock<User>>>,
pub user_count: Option<u64>,
pub image: Option<String>,
}
#[derive(Serialize_repr, Deserialize_repr, Debug, Default, Clone)]
#[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 {
#[default]
GuildOnly = 2,
@ -164,7 +308,7 @@ pub enum GuildScheduledEventPrivacyLevel {
#[derive(Serialize_repr, Deserialize_repr, Debug, Default, Clone)]
#[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 {
#[default]
Scheduled = 1,
@ -175,7 +319,7 @@ pub enum GuildScheduledEventStatus {
#[derive(Serialize_repr, Deserialize_repr, Debug, Default, Clone)]
#[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 {
#[default]
StageInstance = 1,
@ -184,7 +328,99 @@ pub enum GuildScheduledEventEntityType {
}
#[derive(Serialize, Deserialize, Debug, Default, Clone)]
/// 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 location: Option<String>,
}
#[derive(Serialize, Deserialize, Debug, Default, Clone, Eq, PartialEq, Hash)]
pub struct VoiceRegion {
id: String,
name: String,
optimal: bool,
deprecated: bool,
custom: bool,
}
#[derive(Serialize_repr, Deserialize_repr, Debug, Default, Clone, Eq, PartialEq, Hash, Copy)]
#[repr(u8)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
/// See <https://discord-userdoccers.vercel.app/resources/guild#message-notification-level>
pub enum MessageNotificationLevel {
#[default]
AllMessages = 0,
OnlyMentions = 1,
}
#[derive(Serialize_repr, Deserialize_repr, Debug, Default, Clone, Eq, PartialEq, Hash, Copy)]
#[repr(u8)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
/// See <https://discord-userdoccers.vercel.app/resources/guild#explicit-content-filter-level>
pub enum ExplicitContentFilterLevel {
#[default]
Disabled = 0,
MembersWithoutRoles = 1,
AllMembers = 2,
}
#[derive(Serialize_repr, Deserialize_repr, Debug, Default, Clone, Eq, PartialEq, Hash, Copy)]
#[repr(u8)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
/// See <https://discord-userdoccers.vercel.app/resources/guild#verification-level>
pub enum VerificationLevel {
#[default]
None = 0,
Low = 1,
Medium = 2,
High = 3,
VeryHigh = 4,
}
#[derive(Serialize_repr, Deserialize_repr, Debug, Default, Clone, Eq, PartialEq, Hash, Copy)]
#[repr(u8)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
/// See <https://discord-userdoccers.vercel.app/resources/guild#verification-level>
pub enum MFALevel {
#[default]
None = 0,
Elevated = 1,
}
#[derive(Serialize_repr, Deserialize_repr, Debug, Default, Clone, Eq, PartialEq, Hash, Copy)]
#[repr(u8)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
/// See <https://discord-userdoccers.vercel.app/resources/guild#verification-level>
pub enum NSFWLevel {
#[default]
Default = 0,
Explicit = 1,
Safe = 2,
AgeRestricted = 3,
}
#[derive(Serialize_repr, Deserialize_repr, Debug, Default, Clone, Eq, PartialEq, Hash, Copy)]
#[repr(u8)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
/// See <https://discord-userdoccers.vercel.app/resources/guild#verification-level>
pub enum PremiumTier {
#[default]
None = 0,
Tier1 = 1,
Tier2 = 2,
Tier3 = 3,
}
bitflags! {
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash)]
/// # Reference
/// See <https://discord-userdoccers.vercel.app/resources/guild#system-channel-flags>
pub struct SystemChannelFlags: u64 {
/// Indicates if an app uses the Auto Moderation API
const SUPPRESS_JOIN_NOTIFICATIONS = 1 << 0;
const SUPPRESS_PREMIUM_SUBSCRIPTIONS = 1 << 1;
const SUPPRESS_GUILD_REMINDER_NOTIFICATIONS = 1 << 2;
const SUPPRESS_JOIN_NOTIFICATION_REPLIES = 1 << 3;
const SUPPRESS_ROLE_SUBSCRIPTION_PURCHASE_NOTIFICATIONS = 1 << 4;
const SUPPRESS_ROLE_SUBSCRIPTION_PURCHASE_NOTIFICATIONS_REPLIES = 1 << 5;
}
}

View File

@ -1,10 +1,16 @@
use std::sync::{Arc, RwLock};
use serde::{Deserialize, Serialize};
use crate::types::{entities::PublicUser, Snowflake};
#[derive(Debug, Deserialize, Default, Serialize, Clone, PartialEq, Eq)]
#[derive(Debug, Deserialize, Default, Serialize, Clone)]
/// Represents a participating user in a guild.
///
/// # Reference
/// See <https://discord-userdoccers.vercel.app/resources/guild#guild-member-object>
pub struct GuildMember {
pub user: Option<PublicUser>,
pub user: Option<Arc<RwLock<PublicUser>>>,
pub nick: Option<String>,
pub avatar: Option<String>,
pub roles: Vec<Snowflake>,

View File

@ -1,3 +1,5 @@
use std::sync::{Arc, RwLock};
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
@ -8,7 +10,7 @@ use crate::types::{
#[derive(Default, Debug, Deserialize, Serialize, Clone)]
#[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))]
/// See https://discord.com/developers/docs/resources/guild#integration-object-integration-structure
/// See <https://discord.com/developers/docs/resources/guild#integration-object-integration-structure>
pub struct Integration {
pub id: Snowflake,
pub name: String,
@ -21,19 +23,19 @@ pub struct Integration {
pub expire_behaviour: Option<u8>,
pub expire_grace_period: Option<u16>,
#[cfg_attr(feature = "sqlx", sqlx(skip))]
pub user: Option<User>,
pub user: Option<Arc<RwLock<User>>>,
#[cfg_attr(feature = "sqlx", sqlx(skip))]
pub account: IntegrationAccount,
pub synced_at: Option<DateTime<Utc>>,
pub subscriber_count: Option<f64>,
pub revoked: Option<bool>,
#[cfg_attr(feature = "sqlx", sqlx(skip))]
pub application: Option<Application>,
pub application: Option<Arc<RwLock<Application>>>,
pub scopes: Option<Vec<String>>,
}
#[derive(Default, Debug, Deserialize, Serialize, Clone)]
/// See https://discord.com/developers/docs/resources/guild#integration-account-object-integration-account-structure
/// See <https://discord.com/developers/docs/resources/guild#integration-account-object-integration-account-structure>
pub struct IntegrationAccount {
pub id: String,
pub name: String,

View File

@ -1,3 +1,5 @@
use std::sync::{Arc, RwLock};
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
@ -19,7 +21,7 @@ pub struct Invite {
pub flags: Option<i32>,
pub guild: Option<InviteGuild>,
pub guild_id: Option<Snowflake>,
pub guild_scheduled_event: Option<GuildScheduledEvent>,
pub guild_scheduled_event: Option<Arc<RwLock<GuildScheduledEvent>>>,
#[serde(rename = "type")]
pub invite_type: Option<i32>,
pub inviter: Option<User>,
@ -68,7 +70,7 @@ pub enum NSFWLevel {
/// See <https://discord-userdoccers.vercel.app/resources/invite#invite-stage-instance-object>
#[derive(Debug, Serialize, Deserialize)]
pub struct InviteStageInstance {
pub members: Vec<GuildMember>,
pub members: Vec<Arc<RwLock<GuildMember>>>,
pub participant_count: i32,
pub speaker_count: i32,
pub topic: String,

View File

@ -1,3 +1,5 @@
use std::sync::{Arc, RwLock};
use serde::{Deserialize, Serialize};
use crate::types::{
@ -8,13 +10,17 @@ use crate::types::{
utils::Snowflake,
};
#[derive(Debug, Serialize, Deserialize, Default, Clone, PartialEq)]
#[derive(Debug, Serialize, Deserialize, Default, Clone)]
#[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))]
/// Represents a message sent in a channel.
///
/// # Reference
/// See <https://discord-userdoccers.vercel.app/resources/message#message-object>
pub struct Message {
pub id: Snowflake,
pub channel_id: Snowflake,
#[cfg_attr(feature = "sqlx", sqlx(skip))]
pub author: PublicUser,
pub author: Option<PublicUser>,
pub content: Option<String>,
pub timestamp: String,
pub edited_timestamp: Option<String>,
@ -23,15 +29,15 @@ pub struct Message {
#[cfg_attr(feature = "sqlx", sqlx(skip))]
pub mentions: Option<Vec<User>>,
#[cfg_attr(feature = "sqlx", sqlx(skip))]
pub mention_roles: Vec<Snowflake>,
pub mention_roles: Option<Vec<Snowflake>>,
#[cfg_attr(feature = "sqlx", sqlx(skip))]
pub mention_channels: Option<Vec<ChannelMention>>,
#[cfg_attr(feature = "sqlx", sqlx(skip))]
pub attachments: Vec<Attachment>,
pub attachments: Option<Vec<Attachment>>,
#[cfg(feature = "sqlx")]
pub embeds: Vec<sqlx::types::Json<Embed>>,
#[cfg(not(feature = "sqlx"))]
pub embeds: Vec<Embed>,
pub embeds: Option<Vec<Embed>>,
#[cfg(feature = "sqlx")]
pub reactions: Option<sqlx::types::Json<Vec<Reaction>>>,
#[cfg(not(feature = "sqlx"))]
@ -63,7 +69,44 @@ pub struct Message {
pub role_subscription_data: Option<RoleSubscriptionData>,
}
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
impl PartialEq for Message {
fn eq(&self, other: &Self) -> bool {
self.id == other.id
&& self.channel_id == other.channel_id
&& self.author == other.author
&& self.content == other.content
&& self.timestamp == other.timestamp
&& self.edited_timestamp == other.edited_timestamp
&& self.tts == other.tts
&& self.mention_everyone == other.mention_everyone
&& self.mentions == other.mentions
&& self.mention_roles == other.mention_roles
&& self.mention_channels == other.mention_channels
&& self.attachments == other.attachments
&& self.embeds == other.embeds
&& self.embeds == other.embeds
&& self.nonce == other.nonce
&& self.pinned == other.pinned
&& self.webhook_id == other.webhook_id
&& self.message_type == other.message_type
&& self.activity == other.activity
&& self.activity == other.activity
&& self.application_id == other.application_id
&& self.message_reference == other.message_reference
&& self.message_reference == other.message_reference
&& self.flags == other.flags
&& self.referenced_message == other.referenced_message
&& self.thread == other.thread
&& self.components == other.components
&& self.sticker_items == other.sticker_items
&& self.position == other.position
&& self.role_subscription_data == other.role_subscription_data
}
}
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Eq, Ord, PartialOrd)]
/// # Reference
/// See <https://discord-userdoccers.vercel.app/resources/message#message-reference-object>
pub struct MessageReference {
pub message_id: Snowflake,
pub channel_id: Snowflake,
@ -71,17 +114,17 @@ pub struct MessageReference {
pub fail_if_not_exists: Option<bool>,
}
#[derive(Debug, PartialEq, Clone, Deserialize, Serialize)]
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct MessageInteraction {
pub id: Snowflake,
#[serde(rename = "type")]
pub interaction_type: u8,
pub name: String,
pub user: User,
pub member: Option<GuildMember>,
pub member: Option<Arc<RwLock<GuildMember>>>,
}
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
#[derive(Debug, Default, PartialEq, Clone, Serialize, Deserialize, Eq, PartialOrd, Ord)]
pub struct AllowedMention {
parse: Vec<AllowedMentionType>,
roles: Vec<Snowflake>,
@ -89,7 +132,7 @@ pub struct AllowedMention {
replied_user: bool,
}
#[derive(Debug, PartialEq, Clone, Copy, Serialize, Deserialize)]
#[derive(Debug, PartialEq, Clone, Copy, Serialize, Deserialize, Eq, PartialOrd, Ord)]
#[serde(rename_all = "snake_case")]
pub enum AllowedMentionType {
Roles,
@ -106,7 +149,7 @@ pub struct ChannelMention {
name: String,
}
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, PartialOrd)]
pub struct Embed {
title: Option<String>,
#[serde(rename = "type")]
@ -124,14 +167,14 @@ pub struct Embed {
fields: Option<Vec<EmbedField>>,
}
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
pub struct EmbedFooter {
text: String,
icon_url: Option<String>,
proxy_icon_url: Option<String>,
}
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, PartialOrd, Ord)]
pub struct EmbedImage {
url: String,
proxy_url: String,
@ -139,7 +182,7 @@ pub struct EmbedImage {
width: Option<i32>,
}
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, PartialOrd, Ord)]
pub struct EmbedThumbnail {
url: String,
proxy_url: Option<String>,
@ -147,7 +190,7 @@ pub struct EmbedThumbnail {
width: Option<i32>,
}
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, PartialOrd, Ord)]
struct EmbedVideo {
url: Option<String>,
proxy_url: Option<String>,
@ -155,13 +198,13 @@ struct EmbedVideo {
width: Option<i32>,
}
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, PartialOrd, Ord)]
pub struct EmbedProvider {
name: Option<String>,
url: Option<String>,
}
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, PartialOrd, Ord)]
pub struct EmbedAuthor {
name: String,
url: Option<String>,
@ -169,21 +212,24 @@ pub struct EmbedAuthor {
proxy_icon_url: Option<String>,
}
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, PartialOrd, Ord)]
pub struct EmbedField {
name: String,
value: String,
inline: Option<bool>,
}
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
#[derive(Debug, Clone, Serialize, Deserialize, PartialOrd, PartialEq)]
pub struct Reaction {
pub count: i32,
pub count: u32,
pub burst_count: u32,
pub me: bool,
pub burst_me: bool,
pub burst_colors: Vec<String>,
pub emoji: Emoji,
}
#[derive(Debug, PartialEq, Clone, Copy, Serialize, Deserialize)]
#[derive(Debug, PartialEq, Clone, Copy, Serialize, Deserialize, Eq, PartialOrd, Ord)]
pub enum Component {
ActionRow = 1,
Button = 2,
@ -196,6 +242,8 @@ pub enum Component {
}
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
/// # Reference
/// See <https://discord-userdoccers.vercel.app/resources/message#message-activity-object>
pub struct MessageActivity {
#[serde(rename = "type")]
pub activity_type: i64,

View File

@ -10,6 +10,7 @@ pub use guild_member::*;
pub use integration::*;
pub use invite::*;
pub use message::*;
pub use ratelimits::*;
pub use relationship::*;
pub use role::*;
pub use security_key::*;
@ -22,6 +23,18 @@ pub use user_settings::*;
pub use voice_state::*;
pub use webhook::*;
#[cfg(feature = "client")]
use crate::gateway::{GatewayHandle, Updateable};
#[cfg(feature = "client")]
use async_trait::async_trait;
#[cfg(feature = "client")]
use std::fmt::Debug;
#[cfg(feature = "client")]
use std::sync::{Arc, RwLock};
mod application;
mod attachment;
mod audit_log;
@ -34,6 +47,7 @@ mod guild_member;
mod integration;
mod invite;
mod message;
mod ratelimits;
mod relationship;
mod role;
mod security_key;
@ -45,3 +59,63 @@ mod user;
mod user_settings;
mod voice_state;
mod webhook;
#[cfg(feature = "client")]
#[async_trait(?Send)]
pub trait Composite<T: Updateable + Clone + Debug> {
async fn watch_whole(self, gateway: &GatewayHandle) -> Self;
async fn option_observe_fn(
value: Option<Arc<RwLock<T>>>,
gateway: &GatewayHandle,
) -> Option<Arc<RwLock<T>>>
where
T: Composite<T> + Debug,
{
if let Some(value) = value {
let value = value.clone();
Some(gateway.observe(value).await)
} else {
None
}
}
async fn option_vec_observe_fn(
value: Option<Vec<Arc<RwLock<T>>>>,
gateway: &GatewayHandle,
) -> Option<Vec<Arc<RwLock<T>>>>
where
T: Composite<T>,
{
if let Some(value) = value {
let mut vec = Vec::new();
for component in value.into_iter() {
vec.push(gateway.observe(component).await);
}
Some(vec)
} else {
None
}
}
async fn value_observe_fn(value: Arc<RwLock<T>>, gateway: &GatewayHandle) -> Arc<RwLock<T>>
where
T: Composite<T>,
{
gateway.observe(value).await
}
async fn vec_observe_fn(
value: Vec<Arc<RwLock<T>>>,
gateway: &GatewayHandle,
) -> Vec<Arc<RwLock<T>>>
where
T: Composite<T>,
{
let mut vec = Vec::new();
for component in value.into_iter() {
vec.push(gateway.observe(component).await);
}
vec
}
}

View File

@ -24,8 +24,6 @@ pub enum LimitType {
}
/// A struct that represents the current ratelimits, either instance-wide or user-wide.
/// Unlike [`RateLimits`], this struct shows the current ratelimits, not the rate limit
/// configuration for the instance.
/// See <https://discord.com/developers/docs/topics/rate-limits#rate-limits> for more information.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Limit {

View File

@ -1,3 +1,5 @@
use std::sync::{Arc, RwLock};
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use serde_repr::{Deserialize_repr, Serialize_repr};
@ -6,20 +8,29 @@ use crate::types::Snowflake;
use super::PublicUser;
#[derive(Debug, Deserialize, Serialize, Clone, Default, PartialEq, Eq)]
/// See https://discord-userdoccers.vercel.app/resources/user#relationship-structure
#[derive(Debug, Deserialize, Serialize, Clone, Default)]
/// See <https://discord-userdoccers.vercel.app/resources/user#relationship-structure>
pub struct Relationship {
pub id: Snowflake,
#[serde(rename = "type")]
pub relationship_type: RelationshipType,
pub nickname: Option<String>,
pub user: PublicUser,
pub user: Arc<RwLock<PublicUser>>,
pub since: Option<DateTime<Utc>>,
}
impl PartialEq for Relationship {
fn eq(&self, other: &Self) -> bool {
self.id == other.id
&& self.relationship_type == other.relationship_type
&& self.since == other.since
&& self.nickname == other.nickname
}
}
#[derive(Serialize_repr, Deserialize_repr, Debug, Clone, Default, Eq, PartialEq)]
#[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 {
Suggestion = 6,
Implicit = 5,

View File

@ -1,12 +1,23 @@
use bitflags::bitflags;
use serde::{Deserialize, Serialize};
use serde_aux::prelude::{deserialize_option_number_from_string, deserialize_string_from_number};
use std::fmt::Debug;
use crate::types::utils::Snowflake;
#[cfg(feature = "client")]
use chorus_macros::{Composite, Updateable};
#[cfg(feature = "client")]
use crate::gateway::{GatewayHandle, Updateable};
#[cfg(feature = "client")]
use crate::types::Composite;
#[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq)]
#[cfg_attr(feature = "client", derive(Updateable, Composite))]
#[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))]
/// See https://discord.com/developers/docs/topics/permissions#role-object
/// See <https://discord.com/developers/docs/topics/permissions#role-object>
pub struct RoleObject {
pub id: Snowflake,
pub name: String,
@ -34,8 +45,8 @@ pub struct RoleSubscriptionData {
pub is_renewal: bool,
}
#[derive(Serialize, Deserialize, Debug, Default, Clone, Eq, PartialEq)]
/// See https://discord.com/developers/docs/topics/permissions#role-object-role-tags-structure
#[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq, Eq, Hash)]
/// See <https://discord.com/developers/docs/topics/permissions#role-object-role-tags-structure>
pub struct RoleTags {
#[serde(default)]
#[serde(deserialize_with = "deserialize_option_number_from_string")]
@ -53,57 +64,110 @@ pub struct RoleTags {
}
bitflags! {
#[derive(Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
#[derive(Debug, Default, Clone, Hash, Serialize, Deserialize, PartialEq, Eq)]
/// Permissions limit what users of certain roles can do on a Guild to Guild basis.
///
/// # Reference:
/// See <https://discord.com/developers/docs/topics/permissions#permissions>
pub struct PermissionFlags: u64 {
/// Allows creation of instant invites
const CREATE_INSTANT_INVITE = 1 << 0;
/// Allows kicking members
const KICK_MEMBERS = 1 << 1;
/// Allows banning members
const BAN_MEMBERS = 1 << 2;
/// Allows all permissions and bypasses channel permission overwrites
const ADMINISTRATOR = 1 << 3;
/// Allows management and editing of channels
const MANAGE_CHANNELS = 1 << 4;
/// Allows management and editing of the guild and guild settings
const MANAGE_GUILD = 1 << 5;
/// Allows for the addition of reactions to messages
const ADD_REACTIONS = 1 << 6;
/// Allows viewing of the audit log
const VIEW_AUDIT_LOG = 1 << 7;
/// Allows using priority speaker in a voice channel
const PRIORITY_SPEAKER = 1 << 8;
/// Allows the user to go live and share their screen
const STREAM = 1 << 9;
/// Allows guild members to view a channel, which includes reading messages in text channels and joining voice channels
const VIEW_CHANNEL = 1 << 10;
/// Allows sending messages in a channel and creating threads in a forum (does not allow sending messages in threads)
const SEND_MESSAGES = 1 << 11;
/// Allows sending /tts messages
const SEND_TTS_MESSAGES = 1 << 12;
/// Allows deletion of other users' messages
const MANAGE_MESSAGES = 1 << 13;
/// Links sent by users with this permission will be auto-embedded
const EMBED_LINKS = 1 << 14;
/// Allows uploading images and files
const ATTACH_FILES = 1 << 15;
/// Allows reading of message history
const READ_MESSAGE_HISTORY = 1 << 16;
/// Allows using the @everyone tag to notify all users in a channel, and the @here tag to notify all online users in a channel
const MENTION_EVERYONE = 1 << 17;
/// Allows the usage of custom emojis from other servers
const USE_EXTERNAL_EMOJIS = 1 << 18;
/// Allows viewing guild insights
const VIEW_GUILD_INSIGHTS = 1 << 19;
/// Allows joining of a voice channel
const CONNECT = 1 << 20;
/// Allows speaking in a voice channel
const SPEAK = 1 << 21;
/// Allows muting members in a voice channel
const MUTE_MEMBERS = 1 << 22;
/// Allows deafening of members in a voice channel
const DEAFEN_MEMBERS = 1 << 23;
/// Allows moving of members between voice channels
const MOVE_MEMBERS = 1 << 24;
/// Allows using voice activity (VAD = voice-activity-detection) in a voice channel
const USE_VAD = 1 << 25;
/// Allows modification of own nickname
const CHANGE_NICKNAME = 1 << 26;
/// Allows modification of other users' nicknames
const MANAGE_NICKNAMES = 1 << 27;
/// Allows management and editing of roles
const MANAGE_ROLES = 1 << 28;
/// Allows management and editing of webhooks
const MANAGE_WEBHOOKS = 1 << 29;
/// Allows management and editing of emojis, stickers, and soundboard sounds
const MANAGE_GUILD_EXPRESSIONS = 1 << 30;
/// Allows members to use application commands, including slash commands and context menu commands.
const USE_APPLICATION_COMMANDS = 1 << 31;
/// Allows requesting to speak in stage channels. (*This permission is under active development and may be changed or removed.*)
const REQUEST_TO_SPEAK = 1 << 32;
/// Allows creating, editing, and deleting scheduled events
const MANAGE_EVENTS = 1 << 33;
/// Allows deleting and archiving threads, and viewing all private threads
const MANAGE_THREADS = 1 << 34;
/// Allows creating public and announcement threads
const CREATE_PUBLIC_THREADS = 1 << 35;
/// Allows creating private threads
const CREATE_PRIVATE_THREADS = 1 << 36;
/// Allows the usage of custom stickers from other servers
const USE_EXTERNAL_STICKERS = 1 << 37;
/// Allows sending messages in threads
const SEND_MESSAGES_IN_THREADS = 1 << 38;
/// Allows using Activities in a voice channel
const USE_EMBEDDED_ACTIVITIES = 1 << 39;
/// Allows timing out users to prevent them from sending or reacting to messages in chat and threads, and from speaking in voice and stage channels
const MODERATE_MEMBERS = 1 << 40;
/// Allows viewing role subscription insights
const VIEW_CREATOR_MONETIZATION_ANALYTICS = 1 << 41;
/// Allows using the soundboard in a voice channel
const USE_SOUNDBOARD = 1 << 42;
/// Allows using custom soundboard sounds from other servers
const USE_EXTERNAL_SOUNDS = 1 << 45;
/// Allows sending voice messages
const SEND_VOICE_MESSAGES = 1 << 46;
}
}
impl PermissionFlags {
/// Returns if the PermissionFlags object has specific permissions
///
/// # Notes
/// Note that if the object has the [PermissionFlags::ADMINISTRATOR] permission, this always returns true
pub fn has_permission(&self, permission: PermissionFlags) -> bool {
self.contains(permission) || self.contains(PermissionFlags::ADMINISTRATOR)
}
@ -114,6 +178,7 @@ impl PermissionFlags {
}
/// Creates a String of Permissions from a given [`Vec`] of [`PermissionFlags`].
///
/// # Example:
/// ```
/// use chorus::types::{PermissionFlags};

View File

@ -4,12 +4,12 @@ use serde_repr::{Deserialize_repr, Serialize_repr};
use crate::types::Snowflake;
#[derive(Serialize, Deserialize, Debug, Default, Clone)]
/// See https://discord.com/developers/docs/resources/stage-instance
/// See <https://discord.com/developers/docs/resources/stage-instance>
pub struct StageInstance {
pub id: Snowflake,
pub guild_id: Snowflake,
pub channel_id: Snowflake,
/// 1 - 120 chars
/// 1 - 120 characters
pub topic: String,
pub privacy_level: StageInstancePrivacyLevel,
/// deprecated, apparently
@ -20,7 +20,7 @@ pub struct StageInstance {
#[derive(Serialize_repr, Deserialize_repr, Debug, Clone, Default)]
#[repr(u8)]
#[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>
pub enum StageInstancePrivacyLevel {
/// deprecated, apparently
Public = 1,

View File

@ -1,9 +1,15 @@
use std::sync::{Arc, RwLock};
use serde::{Deserialize, Serialize};
use crate::types::{entities::User, utils::Snowflake};
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
#[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))]
/// Represents a sticker that can be sent in messages.
///
/// # Reference
/// See <https://discord-userdoccers.vercel.app/resources/sticker#sticker-object>
pub struct Sticker {
#[serde(default)]
pub id: Snowflake,
@ -18,11 +24,95 @@ pub struct Sticker {
pub available: Option<bool>,
pub guild_id: Option<Snowflake>,
#[cfg_attr(feature = "sqlx", sqlx(skip))]
pub user: Option<User>,
pub user: Option<Arc<RwLock<User>>>,
pub sort_value: Option<u8>,
}
impl std::hash::Hash for Sticker {
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 {
fn eq(&self, other: &Self) -> bool {
self.id == other.id
&& self.pack_id == other.pack_id
&& self.name == other.name
&& self.description == other.description
&& self.tags == other.tags
&& self.asset == other.asset
&& self.sticker_type == other.sticker_type
&& self.format_type == other.format_type
&& self.available == other.available
&& self.guild_id == other.guild_id
&& self.sort_value == other.sort_value
}
}
impl PartialOrd for Sticker {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
match self.id.partial_cmp(&other.id) {
Some(core::cmp::Ordering::Equal) => {}
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)
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
/// A partial sticker object.
///
/// Represents the smallest amount of data required to render a sticker.
///
/// # Reference
/// See <https://discord-userdoccers.vercel.app/resources/sticker#sticker-item-object>
pub struct StickerItem {
pub id: Snowflake,
pub name: String,

View File

@ -1,9 +1,11 @@
use std::sync::{Arc, RwLock};
use serde::{Deserialize, Serialize};
use crate::types::entities::User;
use crate::types::Snowflake;
#[derive(Debug, PartialEq, Deserialize, Serialize, Clone)]
#[derive(Debug, Deserialize, Serialize, Clone)]
#[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))]
pub struct Team {
pub icon: Option<String>,
@ -14,10 +16,10 @@ pub struct Team {
pub owner_user_id: Snowflake,
}
#[derive(Debug, PartialEq, Deserialize, Serialize, Clone)]
#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct TeamMember {
pub membership_state: u8,
pub permissions: Vec<String>,
pub team_id: Snowflake,
pub user: User,
pub user: Arc<RwLock<User>>,
}

View File

@ -1,3 +1,5 @@
use std::sync::{Arc, RwLock};
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
@ -6,8 +8,8 @@ use crate::types::{
utils::Snowflake,
};
/// See https://docs.spacebar.chat/routes/#cmp--schemas-template
#[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq)]
/// See <https://docs.spacebar.chat/routes/#cmp--schemas-template>
#[derive(Serialize, Deserialize, Debug, Default, Clone)]
#[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))]
pub struct GuildTemplate {
pub code: String,
@ -16,13 +18,13 @@ pub struct GuildTemplate {
pub usage_count: Option<u64>,
pub creator_id: Snowflake,
#[cfg_attr(feature = "sqlx", sqlx(skip))]
pub creator: User,
pub creator: Arc<RwLock<User>>,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
pub source_guild_id: Snowflake,
#[cfg_attr(feature = "sqlx", sqlx(skip))]
pub source_guild: Vec<Guild>,
pub source_guild: Vec<Arc<RwLock<Guild>>>,
// Unsure how a {recursive: Guild} looks like, might be a Vec?
#[cfg_attr(feature = "sqlx", sqlx(skip))]
pub serialized_source_guild: Vec<Guild>,
pub serialized_source_guild: Vec<Arc<RwLock<Guild>>>,
}

View File

@ -2,6 +2,18 @@ use crate::types::utils::Snowflake;
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use serde_aux::prelude::deserialize_option_number_from_string;
use std::fmt::Debug;
#[cfg(feature = "client")]
use crate::gateway::{GatewayHandle, Updateable};
#[cfg(feature = "client")]
use crate::types::Composite;
#[cfg(feature = "client")]
use chorus_macros::{Composite, Updateable};
use super::Emoji;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
@ -15,7 +27,8 @@ impl User {
PublicUser::from(self)
}
}
#[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq, Eq)]
#[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "client", derive(Updateable, Composite))]
#[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))]
pub struct User {
pub id: Snowflake,
@ -50,7 +63,7 @@ pub struct User {
pub disabled: Option<bool>,
}
#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize, Hash)]
pub struct PublicUser {
pub id: Snowflake,
pub username: Option<String>,
@ -91,7 +104,7 @@ impl From<User> for PublicUser {
const CUSTOM_USER_FLAG_OFFSET: u64 = 1 << 32;
bitflags::bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
pub struct UserFlags: u64 {
const DISCORD_EMPLOYEE = 1 << 0;
@ -116,3 +129,15 @@ bitflags::bitflags! {
const BOT_HTTP_INTERACTIONS = 1 << 19;
}
}
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, PartialOrd)]
pub struct UserProfileMetadata {
pub guild_id: Option<Snowflake>,
pub pronouns: String,
pub bio: Option<String>,
pub banner: Option<String>,
pub accent_color: Option<i32>,
pub theme_colors: Option<Vec<i32>>,
pub popout_animation_particle_type: Option<Snowflake>,
pub emoji: Option<Emoji>,
}

View File

@ -1,3 +1,5 @@
use std::sync::{Arc, RwLock};
use chrono::{serde::ts_milliseconds_option, Utc};
use serde::{Deserialize, Serialize};
@ -28,7 +30,7 @@ pub enum UserTheme {
Light,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))]
pub struct UserSettings {
pub afk_timeout: u16,
@ -51,15 +53,17 @@ pub struct UserSettings {
pub friend_source_flags: sqlx::types::Json<FriendSourceFlags>,
#[cfg(not(feature = "sqlx"))]
pub friend_source_flags: FriendSourceFlags,
pub gateway_connected: bool,
pub gateway_connected: Option<bool>,
pub gif_auto_play: bool,
#[cfg(feature = "sqlx")]
pub guild_folders: sqlx::types::Json<Vec<GuildFolder>>,
#[cfg(not(feature = "sqlx"))]
pub guild_folders: Vec<GuildFolder>,
#[cfg(feature = "sqlx")]
#[serde(default)]
pub guild_positions: sqlx::types::Json<Vec<String>>,
#[cfg(not(feature = "sqlx"))]
#[serde(default)]
pub guild_positions: Vec<String>,
pub inline_attachment_media: bool,
pub inline_embed_media: bool,
@ -73,7 +77,7 @@ pub struct UserSettings {
#[cfg(not(feature = "sqlx"))]
pub restricted_guilds: Vec<String>,
pub show_current_game: bool,
pub status: UserStatus,
pub status: Arc<RwLock<UserStatus>>,
pub stream_notifications_enabled: bool,
pub theme: UserTheme,
pub timezone_offset: i16,
@ -96,7 +100,7 @@ impl Default for UserSettings {
enable_tts_command: false,
explicit_content_filter: 0,
friend_source_flags: Default::default(),
gateway_connected: false,
gateway_connected: Some(false),
gif_auto_play: false,
guild_folders: Default::default(),
guild_positions: Default::default(),
@ -109,7 +113,7 @@ impl Default for UserSettings {
render_reactions: true,
restricted_guilds: Default::default(),
show_current_game: true,
status: UserStatus::Online,
status: Arc::new(RwLock::new(UserStatus::Online)),
stream_notifications_enabled: false,
theme: UserTheme::Dark,
timezone_offset: 0,
@ -138,7 +142,7 @@ impl Default for FriendSourceFlags {
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GuildFolder {
pub color: u32,
pub guild_ids: Vec<String>,
@ -149,5 +153,5 @@ pub struct GuildFolder {
#[derive(Debug, Serialize, Deserialize)]
pub struct LoginResult {
pub token: String,
pub settings: UserSettings,
pub settings: Arc<RwLock<UserSettings>>,
}

View File

@ -1,20 +1,33 @@
use std::sync::{Arc, RwLock};
#[cfg(feature = "client")]
use chorus_macros::{Composite, Updateable};
#[cfg(feature = "client")]
use crate::types::Composite;
#[cfg(feature = "client")]
use crate::gateway::{GatewayHandle, Updateable};
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use std::fmt::Debug;
use crate::types::{
entities::{Guild, GuildMember},
utils::Snowflake,
};
/// See https://docs.spacebar.chat/routes/#cmp--schemas-voicestate
#[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq)]
/// See <https://docs.spacebar.chat/routes/#cmp--schemas-voicestate>
#[derive(Serialize, Deserialize, Debug, Default, Clone)]
#[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))]
#[cfg_attr(feature = "client", derive(Updateable, Composite))]
pub struct VoiceState {
pub guild_id: Option<Snowflake>,
pub guild: Option<Guild>,
pub channel_id: Option<Snowflake>,
pub user_id: Snowflake,
pub member: Option<GuildMember>,
pub member: Option<Arc<RwLock<GuildMember>>>,
pub session_id: Snowflake,
pub token: Option<String>,
pub deaf: bool,
@ -25,5 +38,5 @@ pub struct VoiceState {
pub self_video: bool,
pub suppress: bool,
pub request_to_speak_timestamp: Option<DateTime<Utc>>,
pub id: Option<Snowflake>,
pub id: Snowflake,
}

View File

@ -1,12 +1,25 @@
use std::fmt::Debug;
use std::sync::{Arc, RwLock};
use serde::{Deserialize, Serialize};
#[cfg(feature = "client")]
use crate::gateway::{GatewayHandle, Updateable};
#[cfg(feature = "client")]
use chorus_macros::{Composite, Updateable};
#[cfg(feature = "client")]
use crate::types::Composite;
use crate::types::{
entities::{Guild, User},
utils::Snowflake,
};
/// See https://docs.spacebar.chat/routes/#cmp--schemas-webhook
#[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq)]
/// See <https://docs.spacebar.chat/routes/#cmp--schemas-webhook>
#[derive(Serialize, Deserialize, Debug, Default, Clone)]
#[cfg_attr(feature = "client", derive(Updateable, Composite))]
#[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))]
pub struct Webhook {
pub id: Snowflake,
@ -20,10 +33,10 @@ pub struct Webhook {
pub application_id: Snowflake,
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "sqlx", sqlx(skip))]
pub user: Option<User>,
pub user: Option<Arc<RwLock<User>>>,
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "sqlx", sqlx(skip))]
pub source_guild: Option<Guild>,
pub source_guild: Option<Arc<RwLock<Guild>>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub url: Option<String>,
}

View File

@ -3,7 +3,7 @@ use serde::{Deserialize, Serialize};
use crate::types::{GuildApplicationCommandPermissions, WebSocketEvent};
#[derive(Debug, Deserialize, Serialize, Default, Clone)]
/// 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 {
#[serde(flatten)]
pub permissions: GuildApplicationCommandPermissions,

View File

@ -1,3 +1,5 @@
use crate::types::{JsonField, SourceUrlField};
use chorus_macros::{JsonField, SourceUrlField};
use serde::{Deserialize, Serialize};
use crate::types::{
@ -5,8 +7,11 @@ use crate::types::{
WebSocketEvent,
};
#[cfg(feature = "client")]
use super::UpdateMessage;
#[derive(Debug, Deserialize, Serialize, Default, Clone)]
/// 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 {
#[serde(flatten)]
pub rule: AutoModerationRule,
@ -14,17 +19,28 @@ pub struct AutoModerationRuleCreate {
impl WebSocketEvent for AutoModerationRuleCreate {}
#[derive(Debug, Deserialize, Serialize, Default, Clone)]
/// See https://discord.com/developers/docs/topics/gateway-events#auto-moderation-rule-update
#[derive(Debug, Deserialize, Serialize, Default, Clone, JsonField, SourceUrlField)]
/// See <https://discord.com/developers/docs/topics/gateway-events#auto-moderation-rule-update>
pub struct AutoModerationRuleUpdate {
#[serde(flatten)]
pub rule: AutoModerationRule,
#[serde(skip)]
pub json: String,
#[serde(skip)]
pub source_url: String,
}
#[cfg(feature = "client")]
impl UpdateMessage<AutoModerationRule> for AutoModerationRuleUpdate {
fn id(&self) -> Option<Snowflake> {
Some(self.rule.id)
}
}
impl WebSocketEvent for AutoModerationRuleUpdate {}
#[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 {
#[serde(flatten)]
pub rule: AutoModerationRule,
@ -33,7 +49,7 @@ pub struct AutoModerationRuleDelete {
impl WebSocketEvent for AutoModerationRuleDelete {}
#[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 guild_id: Snowflake,
pub action: AutoModerationAction,

View File

@ -21,7 +21,7 @@ pub struct CallCreate {
impl WebSocketEvent for CallCreate {}
#[derive(Debug, Deserialize, Serialize, Default, Clone)]
#[derive(Debug, Deserialize, Serialize, Default, Clone, PartialEq, Eq)]
/// Officially Undocumented;
/// Updates the client on which calls are ringing, along with a specific call?;
///
@ -38,7 +38,7 @@ pub struct CallUpdate {
impl WebSocketEvent for CallUpdate {}
#[derive(Debug, Deserialize, Serialize, Default, Clone)]
#[derive(Debug, Deserialize, Serialize, Default, Clone, PartialEq, Eq)]
/// Officially Undocumented;
/// Deletes a ringing call;
/// Ex: {"t":"CALL_DELETE","s":8,"op":0,"d":{"channel_id":"837609115475771392"}}
@ -48,9 +48,9 @@ pub struct CallDelete {
impl WebSocketEvent for CallDelete {}
#[derive(Debug, Deserialize, Serialize, Default, Clone)]
#[derive(Debug, Deserialize, Serialize, Default, Clone, PartialEq, Eq)]
/// Officially Undocumented;
/// See https://unofficial-discord-docs.vercel.app/gateway/op13;
/// See <https://unofficial-discord-docs.vercel.app/gateway/op13>;
///
/// Ex: {"op":13,"d":{"channel_id":"837609115475771392"}}
pub struct CallSync {

View File

@ -1,12 +1,20 @@
use crate::types::events::WebSocketEvent;
use crate::types::{entities::Channel, Snowflake};
use crate::types::{entities::Channel, JsonField, Snowflake, SourceUrlField};
use chorus_macros::{JsonField, SourceUrlField};
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
#[cfg(feature = "client")]
use super::UpdateMessage;
#[cfg(feature = "client")]
use std::sync::{Arc, RwLock};
#[cfg(feature = "client")]
use crate::types::Guild;
#[derive(Debug, Default, Deserialize, Serialize)]
/// 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 guild_id: Option<Snowflake>,
pub channel_id: Snowflake,
@ -15,30 +23,57 @@ pub struct ChannelPinsUpdate {
impl WebSocketEvent for ChannelPinsUpdate {}
#[derive(Debug, Default, Deserialize, Serialize, Clone)]
/// See https://discord.com/developers/docs/topics/gateway-events#channel-create
#[derive(Debug, Default, Deserialize, Serialize, Clone, JsonField, SourceUrlField)]
/// See <https://discord.com/developers/docs/topics/gateway-events#channel-create>
pub struct ChannelCreate {
#[serde(flatten)]
pub channel: Channel,
#[serde(skip)]
pub json: String,
#[serde(skip)]
pub source_url: String,
}
impl WebSocketEvent for ChannelCreate {}
#[derive(Debug, Default, Deserialize, Serialize, Clone)]
/// See https://discord.com/developers/docs/topics/gateway-events#channel-update
#[cfg(feature = "client")]
impl UpdateMessage<Guild> for ChannelCreate {
fn id(&self) -> Option<Snowflake> {
self.channel.guild_id
}
fn update(&mut self, object_to_update: Arc<RwLock<Guild>>) {
let mut write = object_to_update.write().unwrap();
let update = Arc::new(RwLock::new(self.channel.clone()));
if write.channels.is_some() {
write.channels.as_mut().unwrap().push(update);
} else {
write.channels = Some(Vec::from([update]));
}
}
}
#[derive(Debug, Default, Deserialize, Serialize, Clone, JsonField, SourceUrlField)]
/// See <https://discord.com/developers/docs/topics/gateway-events#channel-update>
pub struct ChannelUpdate {
#[serde(flatten)]
pub channel: Channel,
#[serde(skip)]
pub json: String,
#[serde(skip)]
pub source_url: String,
}
impl WebSocketEvent for ChannelUpdate {}
#[cfg(feature = "client")]
impl UpdateMessage<Channel> for ChannelUpdate {
fn update(&self, object_to_update: &mut Channel) {
*object_to_update = self.channel.clone();
fn update(&mut self, object_to_update: Arc<RwLock<Channel>>) {
let mut write = object_to_update.write().unwrap();
*write = self.channel.clone();
}
fn id(&self) -> Snowflake {
self.channel.id
fn id(&self) -> Option<Snowflake> {
Some(self.channel.id)
}
}
@ -53,7 +88,7 @@ pub struct ChannelUnreadUpdate {
#[derive(Debug, Default, Deserialize, Serialize, Clone)]
/// Contains very few fields from [Channel]
/// See also [ChannelUnreadUpdates]
/// See also [ChannelUnreadUpdate]
pub struct ChannelUnreadUpdateObject {
pub id: Snowflake,
pub last_message_id: Snowflake,
@ -62,11 +97,38 @@ pub struct ChannelUnreadUpdateObject {
impl WebSocketEvent for ChannelUnreadUpdate {}
#[derive(Debug, Default, Deserialize, Serialize, Clone)]
/// See https://discord.com/developers/docs/topics/gateway-events#channel-delete
#[derive(Debug, Default, Deserialize, Serialize, Clone, JsonField, SourceUrlField)]
/// See <https://discord.com/developers/docs/topics/gateway-events#channel-delete>
pub struct ChannelDelete {
#[serde(flatten)]
pub channel: Channel,
#[serde(skip)]
pub json: String,
#[serde(skip)]
pub source_url: String,
}
#[cfg(feature = "client")]
impl UpdateMessage<Guild> for ChannelDelete {
fn id(&self) -> Option<Snowflake> {
self.channel.guild_id
}
fn update(&mut self, object_to_update: Arc<RwLock<Guild>>) {
if self.id().is_none() {
return;
}
let mut write = object_to_update.write().unwrap();
if write.channels.is_none() {
return;
}
for (iteration, item) in (0_u32..).zip(write.channels.as_mut().unwrap().iter()) {
if item.read().unwrap().id == self.id().unwrap() {
write.channels.as_mut().unwrap().remove(iteration as usize);
return;
}
}
}
}
impl WebSocketEvent for ChannelDelete {}

View File

@ -1,21 +1,43 @@
use chorus_macros::{JsonField, SourceUrlField};
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use crate::types::entities::{Guild, PublicUser, UnavailableGuild};
use crate::types::events::WebSocketEvent;
use crate::types::{
AuditLogEntry, Emoji, GuildMember, GuildScheduledEvent, RoleObject, Snowflake, Sticker,
AuditLogEntry, Emoji, GuildMember, GuildScheduledEvent, JsonField, RoleObject, Snowflake,
SourceUrlField, Sticker,
};
use super::PresenceUpdate;
#[derive(Debug, Deserialize, Serialize, Default, Clone)]
/// See https://discord.com/developers/docs/topics/gateway-events#guild-create;
#[cfg(feature = "client")]
use super::UpdateMessage;
#[cfg(feature = "client")]
use std::sync::{Arc, RwLock};
#[derive(Debug, Deserialize, Serialize, Default, Clone, SourceUrlField, JsonField)]
/// See <https://discord.com/developers/docs/topics/gateway-events#guild-create>;
/// 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
pub struct GuildCreate {
#[serde(flatten)]
pub d: GuildCreateDataOption,
#[serde(skip)]
pub source_url: String,
#[serde(skip)]
pub json: String,
}
impl UpdateMessage<Guild> for GuildCreate {
fn id(&self) -> Option<Snowflake> {
match &self.d {
GuildCreateDataOption::UnavailableGuild(unavailable) => Some(unavailable.id),
GuildCreateDataOption::Guild(guild) => Some(guild.id),
}
}
fn update(&mut self, _: Arc<RwLock<Guild>>) {}
}
#[derive(Debug, Deserialize, Serialize, Clone)]
@ -34,7 +56,7 @@ impl Default for GuildCreateDataOption {
impl WebSocketEvent for GuildCreate {}
#[derive(Debug, Default, Deserialize, Serialize, Clone)]
/// 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;
pub struct GuildBanAdd {
pub guild_id: Snowflake,
@ -44,7 +66,7 @@ pub struct GuildBanAdd {
impl WebSocketEvent for GuildBanAdd {}
#[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;
pub struct GuildBanRemove {
pub guild_id: Snowflake,
@ -53,28 +75,49 @@ pub struct GuildBanRemove {
impl WebSocketEvent for GuildBanRemove {}
#[derive(Debug, Default, Deserialize, Serialize, Clone)]
/// See https://discord.com/developers/docs/topics/gateway-events#guild-update;
#[derive(Debug, Default, Deserialize, Serialize, Clone, SourceUrlField, JsonField)]
/// See <https://discord.com/developers/docs/topics/gateway-events#guild-update>;
/// Received to give info about a guild being updated;
pub struct GuildUpdate {
#[serde(flatten)]
pub guild: Guild,
#[serde(skip)]
pub source_url: String,
#[serde(skip)]
pub json: String,
}
impl WebSocketEvent for GuildUpdate {}
#[derive(Debug, Default, Deserialize, Serialize, Clone)]
/// See https://discord.com/developers/docs/topics/gateway-events#guild-delete;
impl UpdateMessage<Guild> for GuildUpdate {
fn id(&self) -> Option<Snowflake> {
Some(self.guild.id)
}
}
#[derive(Debug, Default, Deserialize, Serialize, Clone, SourceUrlField, JsonField)]
/// See <https://discord.com/developers/docs/topics/gateway-events#guild-delete>;
/// Received to tell the client about a guild being deleted;
pub struct GuildDelete {
#[serde(flatten)]
pub guild: UnavailableGuild,
#[serde(skip)]
pub source_url: String,
#[serde(skip)]
pub json: String,
}
impl UpdateMessage<Guild> for GuildDelete {
fn id(&self) -> Option<Snowflake> {
Some(self.guild.id)
}
fn update(&mut self, _: Arc<RwLock<Guild>>) {}
}
impl WebSocketEvent for GuildDelete {}
#[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;
pub struct GuildAuditLogEntryCreate {
#[serde(flatten)]
@ -84,7 +127,7 @@ pub struct GuildAuditLogEntryCreate {
impl WebSocketEvent for GuildAuditLogEntryCreate {}
#[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;
pub struct GuildEmojisUpdate {
pub guild_id: Snowflake,
@ -94,7 +137,7 @@ pub struct GuildEmojisUpdate {
impl WebSocketEvent for GuildEmojisUpdate {}
#[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;
pub struct GuildStickersUpdate {
pub guild_id: Snowflake,
@ -104,7 +147,7 @@ pub struct GuildStickersUpdate {
impl WebSocketEvent for GuildStickersUpdate {}
#[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 guild_id: Snowflake,
}
@ -112,7 +155,7 @@ pub struct GuildIntegrationsUpdate {
impl WebSocketEvent for GuildIntegrationsUpdate {}
#[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;
pub struct GuildMemberAdd {
#[serde(flatten)]
@ -123,7 +166,7 @@ pub struct GuildMemberAdd {
impl WebSocketEvent for GuildMemberAdd {}
#[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;
pub struct GuildMemberRemove {
pub guild_id: Snowflake,
@ -133,7 +176,7 @@ pub struct GuildMemberRemove {
impl WebSocketEvent for GuildMemberRemove {}
#[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 guild_id: Snowflake,
pub roles: Vec<Snowflake>,
@ -151,7 +194,7 @@ pub struct GuildMemberUpdate {
impl WebSocketEvent for GuildMemberUpdate {}
#[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 guild_id: Snowflake,
pub members: Vec<GuildMember>,
@ -164,26 +207,66 @@ pub struct GuildMembersChunk {
impl WebSocketEvent for GuildMembersChunk {}
#[derive(Debug, Default, Deserialize, Serialize, Clone)]
/// See https://discord.com/developers/docs/topics/gateway-events#guild-role-create
#[derive(Debug, Default, Deserialize, Serialize, Clone, JsonField, SourceUrlField)]
/// See <https://discord.com/developers/docs/topics/gateway-events#guild-role-create>
pub struct GuildRoleCreate {
pub guild_id: Snowflake,
pub role: RoleObject,
#[serde(skip)]
pub json: String,
#[serde(skip)]
pub source_url: String,
}
impl WebSocketEvent for GuildRoleCreate {}
#[derive(Debug, Default, Deserialize, Serialize, Clone)]
/// See https://discord.com/developers/docs/topics/gateway-events#guild-role-update
#[cfg(feature = "client")]
impl UpdateMessage<Guild> for GuildRoleCreate {
fn id(&self) -> Option<Snowflake> {
Some(self.guild_id)
}
fn update(&mut self, object_to_update: Arc<RwLock<Guild>>) {
let mut object_to_update = object_to_update.write().unwrap();
if object_to_update.roles.is_some() {
object_to_update
.roles
.as_mut()
.unwrap()
.push(Arc::new(RwLock::new(self.role.clone())));
} else {
object_to_update.roles = Some(Vec::from([Arc::new(RwLock::new(self.role.clone()))]));
}
}
}
#[derive(Debug, Default, Deserialize, Serialize, Clone, JsonField, SourceUrlField)]
/// See <https://discord.com/developers/docs/topics/gateway-events#guild-role-update>
pub struct GuildRoleUpdate {
pub guild_id: Snowflake,
pub role: RoleObject,
#[serde(skip)]
pub json: String,
#[serde(skip)]
pub source_url: String,
}
impl WebSocketEvent for GuildRoleUpdate {}
#[cfg(feature = "client")]
impl UpdateMessage<RoleObject> for GuildRoleUpdate {
fn id(&self) -> Option<Snowflake> {
Some(self.role.id)
}
fn update(&mut self, object_to_update: Arc<RwLock<RoleObject>>) {
let mut write = object_to_update.write().unwrap();
*write = self.role.clone();
}
}
#[derive(Debug, Default, Deserialize, Serialize, Clone)]
/// 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 guild_id: Snowflake,
pub role_id: Snowflake,
@ -192,7 +275,7 @@ pub struct GuildRoleDelete {
impl WebSocketEvent for GuildRoleDelete {}
#[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 {
#[serde(flatten)]
pub event: GuildScheduledEvent,
@ -201,7 +284,7 @@ pub struct GuildScheduledEventCreate {
impl WebSocketEvent for GuildScheduledEventCreate {}
#[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 {
#[serde(flatten)]
pub event: GuildScheduledEvent,
@ -210,7 +293,7 @@ pub struct GuildScheduledEventUpdate {
impl WebSocketEvent for GuildScheduledEventUpdate {}
#[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 {
#[serde(flatten)]
pub event: GuildScheduledEvent,
@ -219,7 +302,7 @@ pub struct GuildScheduledEventDelete {
impl WebSocketEvent for GuildScheduledEventDelete {}
#[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 guild_scheduled_event_id: Snowflake,
pub user_id: Snowflake,
@ -229,7 +312,7 @@ pub struct GuildScheduledEventUserAdd {
impl WebSocketEvent for GuildScheduledEventUserAdd {}
#[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 guild_scheduled_event_id: Snowflake,
pub user_id: Snowflake,

View File

@ -2,7 +2,7 @@ use crate::types::WebSocketEvent;
use serde::{Deserialize, Serialize};
/// Received on gateway init, tells the client how often to send heartbeats;
#[derive(Debug, Default, Deserialize, Serialize)]
#[derive(Debug, Default, Deserialize, Serialize, Clone, PartialEq, Eq)]
pub struct GatewayHello {
pub op: i32,
pub d: HelloData,
@ -10,7 +10,7 @@ pub struct GatewayHello {
impl WebSocketEvent for GatewayHello {}
#[derive(Debug, Default, Deserialize, Serialize, Clone)]
#[derive(Debug, Default, Deserialize, Serialize, Clone, PartialEq, Eq, Copy)]
/// Contains info on how often the client should send heartbeats to the server;
pub struct HelloData {
/// How often a client should send heartbeats, in milliseconds

View File

@ -2,7 +2,7 @@ use crate::types::events::{PresenceUpdate, WebSocketEvent};
use serde::{Deserialize, Serialize};
use serde_with::serde_as;
#[derive(Debug, Deserialize, Serialize, Clone)]
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq)]
pub struct GatewayIdentifyPayload {
pub token: String,
pub properties: GatewayIdentifyConnectionProps,
@ -68,7 +68,7 @@ impl GatewayIdentifyPayload {
impl WebSocketEvent for GatewayIdentifyPayload {}
#[derive(Debug, Deserialize, Serialize, Clone)]
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)]
#[serde_as]
pub struct GatewayIdentifyConnectionProps {
/// Almost always sent
@ -144,7 +144,7 @@ impl GatewayIdentifyConnectionProps {
referring_domain: None,
referrer_current: None,
release_channel: String::from("stable"),
client_build_number: 199933,
client_build_number: 0,
}
}
@ -159,7 +159,7 @@ impl GatewayIdentifyConnectionProps {
system_locale: String::from("en-US"),
os: String::from("Windows"),
os_version: Some(String::from("10")),
client_build_number: 199933,
client_build_number: 222963,
release_channel: String::from("stable"),
..Self::minimal()
}

View File

@ -3,7 +3,7 @@ use serde::{Deserialize, Serialize};
use crate::types::{Integration, Snowflake, WebSocketEvent};
#[derive(Debug, Default, Deserialize, Serialize, Clone)]
/// 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 {
#[serde(flatten)]
pub integration: Integration,
@ -13,7 +13,7 @@ pub struct IntegrationCreate {
impl WebSocketEvent for IntegrationCreate {}
#[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 {
#[serde(flatten)]
pub integration: Integration,
@ -23,7 +23,7 @@ pub struct IntegrationUpdate {
impl WebSocketEvent for IntegrationUpdate {}
#[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 id: Snowflake,
pub guild_id: Snowflake,

View File

@ -3,7 +3,7 @@ use serde::{Deserialize, Serialize};
use crate::types::{Interaction, WebSocketEvent};
#[derive(Debug, Default, Deserialize, Serialize, Clone)]
/// 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 {
#[serde(flatten)]
pub interaction: Interaction,

View File

@ -3,7 +3,7 @@ use serde::{Deserialize, Serialize};
use crate::types::{GuildInvite, Snowflake, WebSocketEvent};
#[derive(Debug, Default, Deserialize, Serialize, Clone)]
/// 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 {
#[serde(flatten)]
pub invite: GuildInvite,
@ -12,7 +12,7 @@ pub struct InviteCreate {
impl WebSocketEvent for InviteCreate {}
#[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 channel_id: Snowflake,
pub guild_id: Option<Snowflake>,

View File

@ -13,7 +13,7 @@ use super::WebSocketEvent;
/// Sent by the official client when switching to a guild or channel;
/// After this, you should recieve message updates
///
/// See https://luna.gitlab.io/discord-unofficial-docs/lazy_guilds.html#op-14-lazy-request
/// See <https://luna.gitlab.io/discord-unofficial-docs/lazy_guilds.html#op-14-lazy-request>
///
/// {"op":14,"d":{"guild_id":"848582562217590824","typing":true,"activities":true,"threads":true}}
pub struct LazyRequest {

View File

@ -8,6 +8,8 @@ use crate::types::{
use super::WebSocketEvent;
#[derive(Debug, Deserialize, Serialize, Default, Clone)]
/// # Reference
/// See <https://discord.com/developers/docs/topics/gateway-events#typing-start>
pub struct TypingStartEvent {
pub channel_id: Snowflake,
pub guild_id: Option<Snowflake>,
@ -19,92 +21,106 @@ pub struct TypingStartEvent {
impl WebSocketEvent for TypingStartEvent {}
#[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 {
#[serde(flatten)]
message: Message,
guild_id: Option<Snowflake>,
member: Option<GuildMember>,
mentions: Option<Vec<MessageCreateUser>>,
pub message: Message,
pub guild_id: Option<Snowflake>,
pub member: Option<GuildMember>,
pub mentions: Option<Vec<MessageCreateUser>>,
}
#[derive(Debug, Serialize, Deserialize, Default, Clone)]
/// 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 {
#[serde(flatten)]
user: PublicUser,
member: Option<GuildMember>,
pub user: PublicUser,
pub member: Option<GuildMember>,
}
impl WebSocketEvent for MessageCreate {}
#[derive(Debug, Serialize, Deserialize, Default, Clone)]
/// # Reference
/// See <https://discord.com/developers/docs/topics/gateway-events#message-update>
pub struct MessageUpdate {
#[serde(flatten)]
message: Message,
guild_id: Option<Snowflake>,
member: Option<GuildMember>,
mentions: Option<Vec<MessageCreateUser>>,
pub message: Message,
pub guild_id: Option<Snowflake>,
pub member: Option<GuildMember>,
pub mentions: Option<Vec<MessageCreateUser>>,
}
impl WebSocketEvent for MessageUpdate {}
#[derive(Debug, Serialize, Deserialize, Default, Clone)]
/// # Reference
/// See <https://discord.com/developers/docs/topics/gateway-events#message-delete>
pub struct MessageDelete {
id: Snowflake,
channel_id: Snowflake,
guild_id: Option<Snowflake>,
pub id: Snowflake,
pub channel_id: Snowflake,
pub guild_id: Option<Snowflake>,
}
impl WebSocketEvent for MessageDelete {}
#[derive(Debug, Serialize, Deserialize, Default, Clone)]
/// # Reference
/// See <https://discord.com/developers/docs/topics/gateway-events#message-delete-bulk>
pub struct MessageDeleteBulk {
ids: Vec<Snowflake>,
channel_id: Snowflake,
guild_id: Option<Snowflake>,
pub ids: Vec<Snowflake>,
pub channel_id: Snowflake,
pub guild_id: Option<Snowflake>,
}
impl WebSocketEvent for MessageDeleteBulk {}
#[derive(Debug, Serialize, Deserialize, Default, Clone)]
/// # Reference
/// See <https://discord.com/developers/docs/topics/gateway-events#message-reaction-add>
pub struct MessageReactionAdd {
user_id: Snowflake,
channel_id: Snowflake,
message_id: Snowflake,
guild_id: Option<Snowflake>,
member: Option<GuildMember>,
emoji: Emoji,
pub user_id: Snowflake,
pub channel_id: Snowflake,
pub message_id: Snowflake,
pub guild_id: Option<Snowflake>,
pub member: Option<GuildMember>,
pub emoji: Emoji,
}
impl WebSocketEvent for MessageReactionAdd {}
#[derive(Debug, Serialize, Deserialize, Default, Clone)]
/// # Reference
/// See <https://discord.com/developers/docs/topics/gateway-events#message-reaction-remove>
pub struct MessageReactionRemove {
user_id: Snowflake,
channel_id: Snowflake,
message_id: Snowflake,
guild_id: Option<Snowflake>,
emoji: Emoji,
pub user_id: Snowflake,
pub channel_id: Snowflake,
pub message_id: Snowflake,
pub guild_id: Option<Snowflake>,
pub emoji: Emoji,
}
impl WebSocketEvent for MessageReactionRemove {}
#[derive(Debug, Serialize, Deserialize, Default, Clone)]
/// # Reference
/// See <https://discord.com/developers/docs/topics/gateway-events#message-reaction-remove-all>
pub struct MessageReactionRemoveAll {
channel_id: Snowflake,
message_id: Snowflake,
guild_id: Option<Snowflake>,
pub channel_id: Snowflake,
pub message_id: Snowflake,
pub guild_id: Option<Snowflake>,
}
impl WebSocketEvent for MessageReactionRemoveAll {}
#[derive(Debug, Serialize, Deserialize, Default, Clone)]
/// # Reference
/// See <https://discord.com/developers/docs/topics/gateway-events#message-reaction-remove-emoji>
pub struct MessageReactionRemoveEmoji {
channel_id: Snowflake,
message_id: Snowflake,
guild_id: Option<Snowflake>,
emoji: Emoji,
pub channel_id: Snowflake,
pub message_id: Snowflake,
pub guild_id: Option<Snowflake>,
pub emoji: Emoji,
}
impl WebSocketEvent for MessageReactionRemoveEmoji {}
@ -114,7 +130,7 @@ impl WebSocketEvent for MessageReactionRemoveEmoji {}
///
/// Not documented anywhere unofficially
///
/// Apparently "Message ACK refers to marking a message as read for Discord's API." (https://github.com/Rapptz/discord.py/issues/1851)
/// Apparently "Message ACK refers to marking a message as read for Discord's API." (<https://github.com/Rapptz/discord.py/issues/1851>)
/// I suspect this is sent and recieved from the gateway to let clients on other devices know the user has read a message
///
/// {"t":"MESSAGE_ACK","s":3,"op":0,"d":{"version":52,"message_id":"1107236673638633472","last_viewed":null,"flags":null,"channel_id":"967363950217936897"}}

View File

@ -1,6 +1,5 @@
use serde::{Deserialize, Serialize};
use crate::gateway::Updateable;
pub use application::*;
pub use auto_moderation::*;
pub use call::*;
@ -28,8 +27,25 @@ pub use voice::*;
pub use webhooks::*;
pub use webrtc::*;
#[cfg(feature = "client")]
use super::Snowflake;
#[cfg(feature = "client")]
use crate::gateway::Updateable;
#[cfg(feature = "client")]
use serde_json::{from_str, from_value, to_value, Value};
#[cfg(feature = "client")]
use std::collections::HashMap;
use std::fmt::Debug;
#[cfg(feature = "client")]
use std::sync::{Arc, RwLock};
#[cfg(feature = "client")]
use serde::de::DeserializeOwned;
mod application;
mod auto_moderation;
mod call;
@ -58,12 +74,12 @@ mod webhooks;
mod webrtc;
pub trait WebSocketEvent {}
pub trait WebSocketEvent: Send + Sync + Debug {}
#[derive(Debug, Default, Serialize, Clone)]
/// The payload used for sending events to the gateway
///
/// Similar to [GatewayReceivePayload], except we send a [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]
/// Also, we never need to send the event name
pub struct GatewaySendPayload {
#[serde(rename = "op")]
@ -82,9 +98,6 @@ impl WebSocketEvent for GatewaySendPayload {}
#[derive(Debug, Default, Deserialize, Clone)]
/// The payload used for receiving events from the gateway
///
/// Similar to [GatewaySendPayload], except we send a [Value] for d whilst we receive a [serde_json::value::RawValue]
/// Also, we never need to sent the event name
pub struct GatewayReceivePayload<'a> {
#[serde(rename = "op")]
pub op_code: u8,
@ -102,6 +115,7 @@ pub struct GatewayReceivePayload<'a> {
impl<'a> WebSocketEvent for GatewayReceivePayload<'a> {}
#[cfg(feature = "client")]
/// An [`UpdateMessage<T>`] represents a received Gateway Message which contains updated
/// information for an [`Updateable`] of Type T.
/// # Example:
@ -114,10 +128,37 @@ impl<'a> WebSocketEvent for GatewayReceivePayload<'a> {}
/// This would imply, that the [`WebSocketEvent`] "[`ChannelUpdate`]" contains new/updated information
/// about a [`Channel`]. The update method describes how this new information will be turned into
/// a [`Channel`] object.
pub(crate) trait UpdateMessage<T>: Clone
pub(crate) trait UpdateMessage<T>: Clone + JsonField + SourceUrlField
where
T: Updateable,
T: Updateable + Serialize + DeserializeOwned + Clone,
{
fn update(&self, object_to_update: &mut T);
fn id(&self) -> Snowflake;
fn update(&mut self, object_to_update: Arc<RwLock<T>>) {
update_object(self.get_json(), object_to_update)
}
fn id(&self) -> Option<Snowflake>;
}
pub(crate) trait JsonField: Clone {
fn set_json(&mut self, json: String);
fn get_json(&self) -> String;
}
pub trait SourceUrlField: Clone {
fn set_source_url(&mut self, url: String);
fn get_source_url(&self) -> String;
}
#[cfg(feature = "client")]
/// Only applicable for events where the Update struct is the same as the Entity struct
pub(crate) fn update_object(
value: String,
object: Arc<RwLock<(impl Updateable + Serialize + DeserializeOwned + Clone)>>,
) {
let data_from_event: HashMap<String, Value> = from_str(&value).unwrap();
let mut original_data: HashMap<String, Value> =
from_value(to_value(object.clone()).unwrap()).unwrap();
for (updated_entry_key, updated_entry_value) in data_from_event.into_iter() {
original_data.insert(updated_entry_key.clone(), updated_entry_value);
}
*object.write().unwrap() = from_value(to_value(original_data).unwrap()).unwrap();
}

View File

@ -4,9 +4,9 @@ use serde::{Deserialize, Serialize};
#[derive(Debug, Deserialize, Serialize, Default, Clone)]
/// 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 {
/// unix time of when the client went idle, or none if client is not idle
/// Unix time of when the client went idle, or none if client is not idle.
pub since: Option<u128>,
/// the client's status (online, invisible, offline, dnd, idle..)
pub status: UserStatus,
@ -14,9 +14,9 @@ pub struct UpdatePresence {
pub afk: bool,
}
#[derive(Debug, Deserialize, Serialize, Default, Clone)]
#[derive(Debug, Deserialize, Serialize, Default, Clone, PartialEq)]
/// 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>
pub struct PresenceUpdate {
pub user: PublicUser,
#[serde(default)]

View File

@ -8,7 +8,7 @@ use crate::types::{Activity, GuildMember, PresenceUpdate, VoiceState};
#[derive(Debug, Deserialize, Serialize, Default, Clone)]
/// 1/2 half documented;
/// Received after identifying, provides initial user info;
/// See https://discord.com/developers/docs/topics/gateway-events#ready;
/// See <https://discord.com/developers/docs/topics/gateway-events#ready;>
pub struct GatewayReady {
pub analytics_token: Option<String>,
pub auth_session_id_hash: Option<String>,
@ -16,7 +16,7 @@ pub struct GatewayReady {
pub v: u8,
pub user: User,
/// For bots these are [UnavailableGuild]s, for users they are [Guild]
/// For bots these are [crate::types::UnavailableGuild]s, for users they are [Guild]
pub guilds: Vec<Guild>,
pub presences: Option<Vec<PresenceUpdate>>,
pub sessions: Option<Vec<Session>>,

View File

@ -2,7 +2,7 @@ use crate::types::{events::WebSocketEvent, Relationship, RelationshipType, Snowf
use serde::{Deserialize, Serialize};
#[derive(Debug, Deserialize, Serialize, Default)]
/// See https://github.com/spacebarchat/server/issues/204
/// See <https://github.com/spacebarchat/server/issues/204>
pub struct RelationshipAdd {
#[serde(flatten)]
pub relationship: Relationship,
@ -12,7 +12,7 @@ pub struct RelationshipAdd {
impl WebSocketEvent for RelationshipAdd {}
#[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 id: Snowflake,
#[serde(rename = "type")]

View File

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

View File

@ -13,7 +13,7 @@ pub struct SessionsReplace {
#[derive(Debug, Deserialize, Serialize, Default, Clone)]
/// Session info for the current user
pub struct Session {
pub activities: Vec<Activity>,
pub activities: Option<Vec<Activity>>,
pub client_info: ClientInfo,
pub session_id: String,
pub status: String,
@ -24,7 +24,7 @@ pub struct Session {
/// {"client":"web","os":"other","version":0}
// Note: I don't think this one exists yet? Though I might've made a mistake and this might be a duplicate
pub struct ClientInfo {
pub client: String,
pub client: Option<String>,
pub os: String,
pub version: u8,
}

View File

@ -3,7 +3,7 @@ use serde::{Deserialize, Serialize};
use crate::types::{StageInstance, WebSocketEvent};
#[derive(Debug, Deserialize, Serialize, Default, Clone)]
/// 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 {
#[serde(flatten)]
pub stage_instance: StageInstance,
@ -12,7 +12,7 @@ pub struct StageInstanceCreate {
impl WebSocketEvent for StageInstanceCreate {}
#[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 {
#[serde(flatten)]
pub stage_instance: StageInstance,
@ -21,7 +21,7 @@ pub struct StageInstanceUpdate {
impl WebSocketEvent for StageInstanceUpdate {}
#[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 {
#[serde(flatten)]
pub stage_instance: StageInstance,

View File

@ -1,11 +1,15 @@
use chorus_macros::{JsonField, SourceUrlField};
use serde::{Deserialize, Serialize};
use crate::types::entities::{Channel, ThreadMember};
use crate::types::events::WebSocketEvent;
use crate::types::Snowflake;
use crate::types::{JsonField, Snowflake, SourceUrlField};
#[cfg(feature = "client")]
use super::UpdateMessage;
#[derive(Debug, Default, Deserialize, Serialize, Clone)]
/// 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 {
#[serde(flatten)]
pub thread: Channel,
@ -13,17 +17,28 @@ pub struct ThreadCreate {
impl WebSocketEvent for ThreadCreate {}
#[derive(Debug, Default, Deserialize, Serialize, Clone)]
/// See https://discord.com/developers/docs/topics/gateway-events#thread-update
#[derive(Debug, Default, Deserialize, Serialize, Clone, JsonField, SourceUrlField)]
/// See <https://discord.com/developers/docs/topics/gateway-events#thread-update>
pub struct ThreadUpdate {
#[serde(flatten)]
pub thread: Channel,
#[serde(skip)]
pub json: String,
#[serde(skip)]
pub source_url: String,
}
impl WebSocketEvent for ThreadUpdate {}
#[cfg(feature = "client")]
impl UpdateMessage<Channel> for ThreadUpdate {
fn id(&self) -> Option<Snowflake> {
Some(self.thread.id)
}
}
#[derive(Debug, Default, Deserialize, Serialize, Clone)]
/// 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 {
#[serde(flatten)]
pub thread: Channel,
@ -32,7 +47,7 @@ pub struct ThreadDelete {
impl WebSocketEvent for ThreadDelete {}
#[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 guild_id: Snowflake,
pub channel_ids: Option<Vec<Snowflake>>,
@ -43,7 +58,7 @@ pub struct ThreadListSync {
impl WebSocketEvent for ThreadListSync {}
#[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.
pub struct ThreadMemberUpdate {
#[serde(flatten)]
@ -54,7 +69,7 @@ pub struct ThreadMemberUpdate {
impl WebSocketEvent for ThreadMemberUpdate {}
#[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 id: Snowflake,
pub guild_id: Snowflake,

View File

@ -4,8 +4,8 @@ use crate::types::entities::PublicUser;
use crate::types::events::WebSocketEvent;
use crate::types::utils::Snowflake;
#[derive(Debug, Default, Deserialize, Serialize, Clone)]
/// See https://discord.com/developers/docs/topics/gateway-events#user-update;
#[derive(Debug, Default, Deserialize, Serialize, Clone, PartialEq, Eq)]
/// See <https://discord.com/developers/docs/topics/gateway-events#user-update>;
/// Sent to indicate updates to a user object; (name changes, discriminator changes, etc);
pub struct UserUpdate {
#[serde(flatten)]
@ -14,7 +14,7 @@ pub struct UserUpdate {
impl WebSocketEvent for UserUpdate {}
#[derive(Debug, Default, Deserialize, Serialize, Clone)]
#[derive(Debug, Default, Deserialize, Serialize, Clone, PartialEq, Eq)]
/// Undocumented;
///
/// Possibly an update for muted guild / channel settings for the current user;
@ -39,7 +39,7 @@ pub struct UserGuildSettingsUpdate {
impl WebSocketEvent for UserGuildSettingsUpdate {}
#[derive(Debug, Default, Deserialize, Serialize, Clone)]
#[derive(Debug, Default, Deserialize, Serialize, Clone, PartialEq, Eq)]
/// Undocumented;
///
/// Received in [UserGuildSettingsUpdate];

View File

@ -1,7 +1,7 @@
use crate::types::{events::WebSocketEvent, Snowflake, VoiceState};
use serde::{Deserialize, Serialize};
#[derive(Debug, Deserialize, Serialize, Default)]
#[derive(Debug, Deserialize, Serialize, Default, Clone, Copy, PartialEq, Eq)]
///
/// Sent to the server to indicate an update of the voice state (leave voice channel, join voice channel, mute, deafen);
///
@ -16,7 +16,7 @@ pub struct UpdateVoiceState {
impl WebSocketEvent for UpdateVoiceState {}
#[derive(Debug, Deserialize, Serialize, Default, Clone)]
/// See https://discord.com/developers/docs/topics/gateway-events#voice-state-update;
/// See <https://discord.com/developers/docs/topics/gateway-events#voice-state-update>;
///
/// Received from the server to indicate an update in a user's voice state (leave voice channel, join voice channel, mute, deafen, etc);
///
@ -28,8 +28,8 @@ pub struct VoiceStateUpdate {
impl WebSocketEvent for VoiceStateUpdate {}
#[derive(Debug, Deserialize, Serialize, Default, Clone)]
/// See https://discord.com/developers/docs/topics/gateway-events#voice-server-update;
#[derive(Debug, Deserialize, Serialize, Default, Clone, PartialEq, Eq)]
/// See <https://discord.com/developers/docs/topics/gateway-events#voice-server-update>;
///
/// Received to indicate which voice endpoint, token and guild_id to use;
pub struct VoiceServerUpdate {

View File

@ -5,7 +5,7 @@ use crate::types::Snowflake;
use super::WebSocketEvent;
#[derive(Debug, Deserialize, Serialize, Default, Clone)]
/// See https://discord.com/developers/docs/topics/gateway-events#webhooks-update
/// See <https://discord.com/developers/docs/topics/gateway-events#webhooks-update>
pub struct WebhooksUpdate {
pub guild_id: Snowflake,
pub channel_id: Snowflake,

View File

@ -1,7 +1,7 @@
use crate::types::{Snowflake, WebSocketEvent};
use serde::{Deserialize, Serialize};
#[derive(Debug, Deserialize, Serialize, Default, Clone)]
#[derive(Debug, Deserialize, Serialize, Default, Clone, PartialEq, Eq)]
/// The identify payload for the webrtc stream;
/// Contains info to begin a webrtc connection;
/// See https://discord.com/developers/docs/topics/voice-connections#establishing-a-voice-websocket-connection-example-voice-identify-payload;

View File

@ -48,7 +48,7 @@ impl<'a> WebSocketEvent for VoiceGatewayReceivePayload<'a> {}
/// The modes of encryption available in webrtc connections;
/// See https://discord.com/developers/docs/topics/voice-connections#establishing-a-voice-udp-connection-encryption-modes;
#[derive(Debug, Default, Serialize, Deserialize, Clone, Copy)]
#[derive(Debug, Default, Serialize, Deserialize, Clone, Copy, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum WebrtcEncryptionMode {
#[default]

View File

@ -5,7 +5,7 @@ use serde::{Deserialize, Serialize};
use super::WebrtcEncryptionMode;
#[derive(Debug, Deserialize, Serialize, Clone)]
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)]
/// The ready event for the webrtc stream;
/// Used to give info after the identify event;
/// See https://discord.com/developers/docs/topics/voice-connections#establishing-a-voice-websocket-connection-example-voice-ready-payload;

View File

@ -2,7 +2,7 @@ use serde::{Deserialize, Serialize};
use crate::types::{entities::Emoji, Snowflake};
#[derive(Debug, Deserialize, Serialize, Clone)]
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq)]
pub struct Activity {
name: String,
#[serde(rename = "type")]
@ -22,19 +22,19 @@ pub struct Activity {
buttons: Option<Vec<ActivityButton>>,
}
#[derive(Debug, Deserialize, Serialize, Clone)]
#[derive(Debug, Deserialize, Serialize, Clone, Copy, PartialEq, Eq)]
struct ActivityTimestamps {
start: Option<i64>,
end: Option<i64>,
}
#[derive(Debug, Deserialize, Serialize, Clone)]
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)]
struct ActivityParty {
id: Option<String>,
size: Option<Vec<(i32, i32)>>,
}
#[derive(Debug, Deserialize, Serialize, Clone)]
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)]
struct ActivityAssets {
large_image: Option<String>,
large_text: Option<String>,
@ -42,7 +42,7 @@ struct ActivityAssets {
small_text: Option<String>,
}
#[derive(Debug, Deserialize, Serialize, Clone)]
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)]
struct ActivitySecrets {
join: Option<String>,
spectate: Option<String>,
@ -50,7 +50,7 @@ struct ActivitySecrets {
match_string: Option<String>,
}
#[derive(Debug, Deserialize, Serialize, Clone)]
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)]
struct ActivityButton {
label: String,
url: String,

View File

@ -2,14 +2,14 @@ use serde::{Deserialize, Serialize};
use crate::types::utils::Snowflake;
#[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq)]
#[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq, Hash)]
pub struct WelcomeScreenObject {
pub enabled: bool,
pub description: Option<String>,
pub welcome_channels: Vec<WelcomeScreenChannel>,
}
#[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq)]
#[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq, Hash)]
pub struct WelcomeScreenChannel {
pub channel_id: Snowflake,
pub description: String,

View File

@ -24,6 +24,7 @@ pub enum InteractionType {
ApplicationCommand = 2,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum InteractionResponseType {
SelfCommandResponse = 0,
Pong = 1,
@ -33,6 +34,7 @@ pub enum InteractionResponseType {
AcknowledgeWithSource = 5,
}
#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)]
pub struct InteractionApplicationCommandCallbackData {
pub tts: bool,
pub content: String,

View File

@ -1,7 +1,7 @@
use serde::{Deserialize, Serialize};
#[derive(Debug, Deserialize, Serialize, Default, Clone)]
/// See https://discord.com/developers/docs/topics/gateway-events#client-status-object
#[derive(Debug, Deserialize, Serialize, Default, Clone, PartialEq, Eq)]
/// See <https://discord.com/developers/docs/topics/gateway-events#client-status-object>
pub struct ClientStatusObject {
pub desktop: Option<String>,
pub mobile: Option<String>,

View File

@ -1,3 +1,5 @@
//! All the types, entities, events and interfaces of the Spacebar API.
pub use config::*;
pub use entities::*;
pub use errors::*;

View File

@ -1,6 +1,6 @@
use serde::{Deserialize, Serialize};
#[derive(Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
#[derive(Debug, Default, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub struct RegisterSchema {
pub username: String,
@ -15,7 +15,7 @@ pub struct RegisterSchema {
pub promotional_email_opt_in: Option<bool>,
}
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
#[derive(Debug, Default, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub struct LoginSchema {
/// For Discord, usernames must be between 2 and 32 characters,
@ -30,7 +30,7 @@ pub struct LoginSchema {
pub gift_code_sku_id: Option<String>,
}
#[derive(Debug, Serialize, Deserialize)]
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub struct TotpSchema {
code: String,

View File

@ -1,6 +1,7 @@
use bitflags::bitflags;
use serde::{Deserialize, Serialize};
use crate::types::ChannelType;
use crate::types::{entities::PermissionOverwrite, Snowflake};
#[derive(Debug, Deserialize, Serialize, Default, PartialEq, PartialOrd)]
@ -8,7 +9,7 @@ use crate::types::{entities::PermissionOverwrite, Snowflake};
pub struct ChannelCreateSchema {
pub name: String,
#[serde(rename = "type")]
pub channel_type: Option<u8>,
pub channel_type: Option<ChannelType>,
pub topic: Option<String>,
pub icon: Option<String>,
pub bitrate: Option<i32>,
@ -148,3 +149,12 @@ pub struct AddChannelRecipientSchema {
pub access_token: Option<String>,
pub nick: Option<String>,
}
/// See <https://discord-userdoccers.vercel.app/resources/channel#add-channel-recipient>
#[derive(Debug, Deserialize, Serialize, Clone, Default, PartialOrd, Ord, PartialEq, Eq)]
pub struct ModifyChannelPositionsSchema {
pub id: Snowflake,
pub position: Option<u32>,
pub lock_permissions: Option<bool>,
pub parent_id: Option<Snowflake>,
}

View File

@ -1,11 +1,18 @@
use bitflags::bitflags;
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use crate::types::entities::Channel;
use crate::types::types::guild_configuration::GuildFeatures;
use crate::types::{
Emoji, ExplicitContentFilterLevel, MessageNotificationLevel, Snowflake, Sticker,
SystemChannelFlags, VerificationLevel,
};
#[derive(Debug, Deserialize, Serialize)]
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq)]
#[serde(rename_all = "snake_case")]
/// Represents the schema which needs to be sent to create a Guild.
/// See: [https://docs.spacebar.chat/routes/#cmp--schemas-guildcreateschema](https://docs.spacebar.chat/routes/#cmp--schemas-guildcreateschema)
/// See: <https://docs.spacebar.chat/routes/#cmp--schemas-guildcreateschema>
pub struct GuildCreateSchema {
pub name: Option<String>,
pub region: Option<String>,
@ -15,3 +22,144 @@ pub struct GuildCreateSchema {
pub system_channel_id: Option<String>,
pub rules_channel_id: Option<String>,
}
#[derive(Debug, Deserialize, Serialize, Default, Clone, Copy, Eq, PartialEq)]
#[serde(rename_all = "snake_case")]
/// Represents the schema which needs to be sent to create a Guild Ban.
/// See: <https://discord-userdoccers.vercel.app/resources/guild#create-guild-ban>
pub struct GuildBanCreateSchema {
pub delete_message_days: Option<u8>,
pub delete_message_seconds: Option<u32>,
}
#[derive(Debug, Deserialize, Serialize, Default, Clone, Eq, PartialEq)]
#[serde(rename_all = "snake_case")]
pub struct GuildModifySchema {
pub name: Option<String>,
pub icon: Option<Vec<u8>>,
pub banner: Option<Vec<u8>>,
pub home_header: Option<Vec<u8>>,
pub splash: Option<Vec<u8>>,
pub discovery_splash: Option<Vec<u8>>,
pub owner_id: Option<Snowflake>,
pub description: Option<String>,
pub region: Option<String>,
pub afk_channel_id: Option<Snowflake>,
pub afk_timeout: Option<u16>,
pub verification_level: Option<VerificationLevel>,
pub default_message_notifications: Option<MessageNotificationLevel>,
pub explicit_content_filter: Option<ExplicitContentFilterLevel>,
pub features: Option<Vec<GuildFeatures>>,
pub system_channel_id: Option<Snowflake>,
pub system_channel_flags: Option<SystemChannelFlags>,
pub rules_channel_id: Option<Snowflake>,
pub public_updates_channel_id: Option<Snowflake>,
pub safety_alerts_channel_id: Option<Snowflake>,
pub preferred_locale: Option<String>,
pub premium_progress_bar_enabled: Option<bool>,
}
#[derive(Debug, Deserialize, Serialize, Clone, Eq, PartialEq, Ord, PartialOrd)]
pub struct GetUserGuildSchema {
pub before: Option<Snowflake>,
pub after: Option<Snowflake>,
pub limit: Option<u8>,
pub with_counts: Option<bool>,
}
impl std::default::Default for GetUserGuildSchema {
fn default() -> Self {
Self {
before: Default::default(),
after: Default::default(),
limit: Some(200),
with_counts: Some(false),
}
}
}
#[derive(Debug, Default, Deserialize, Serialize, Clone, PartialEq, PartialOrd)]
pub struct GuildPreview {
pub id: Snowflake,
pub name: String,
pub icon: Option<String>,
pub description: Option<String>,
pub splash: Option<String>,
pub discovery_splash: Option<String>,
pub home_header: Option<String>,
pub features: Vec<String>,
pub emojis: Vec<Emoji>,
pub stickers: Vec<Sticker>,
pub approximate_member_count: u32,
pub approximate_presence_count: u32,
}
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, PartialOrd, Eq, Ord)]
pub struct GuildMemberSearchSchema {
pub query: String,
pub limit: Option<u16>,
}
impl Default for GuildMemberSearchSchema {
fn default() -> Self {
Self {
query: Default::default(),
limit: Some(1),
}
}
}
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, PartialOrd, Eq, Ord)]
pub struct ModifyGuildMemberSchema {
pub nick: Option<String>,
pub roles: Option<Vec<Snowflake>>,
pub mute: Option<bool>,
pub deaf: Option<bool>,
pub channel_id: Option<Snowflake>,
pub communication_disabled_until: Option<DateTime<Utc>>,
pub flags: Option<GuildMemberFlags>,
}
bitflags! {
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, PartialOrd, Ord)]
/// Represents the flags of a Guild Member.
///
/// # Reference:
/// See <https://discord-userdoccers.vercel.app/resources/guild#guild-member-flags>
pub struct GuildMemberFlags: u64 {
const DID_REJOIN = 1 << 0;
const COMPLETED_ONBOARDING = 1 << 1;
const BYPASSES_VERIFICATION = 1 << 2;
const STARTED_ONBOARDING = 1 << 3;
const GUEST = 1 << 3;
const AUTOMOD_QUARANTINED_NAME = 1 << 7;
const AUTOMOD_QUARANTINED_BIO = 1 << 8;
}
}
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, PartialOrd, Eq, Ord)]
pub struct ModifyCurrentGuildMemberSchema {
pub nick: Option<String>,
pub avatar: Option<String>,
pub bio: Option<String>,
pub banner: Option<String>,
}
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, PartialOrd, Eq, Ord)]
pub struct ModifyGuildMemberProfileSchema {
pub pronouns: Option<String>,
pub bio: Option<String>,
pub banner: Option<String>,
pub accent_color: Option<String>,
pub theme_colors: Option<Vec<i32>>,
pub popout_animation_particle_type: Option<Snowflake>,
pub emoji_id: Option<Snowflake>,
}
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, PartialOrd, Eq, Ord)]
/// The limit argument is a number between 1 and 1000.
pub struct GuildBansQuery {
pub before: Option<Snowflake>,
pub after: Option<Snowflake>,
pub limit: Option<u16>,
}

View File

@ -3,8 +3,9 @@ use serde::{Deserialize, Serialize};
use crate::types::entities::{
AllowedMention, Component, Embed, MessageReference, PartialDiscordFileAttachment,
};
use crate::types::{Attachment, Snowflake};
#[derive(Debug, Default, Deserialize, Serialize)]
#[derive(Debug, Default, Deserialize, Serialize, Clone, PartialEq)]
#[serde(rename_all = "snake_case")]
pub struct MessageSendSchema {
#[serde(rename = "type")]
@ -19,3 +20,107 @@ pub struct MessageSendSchema {
pub sticker_ids: Option<Vec<String>>,
pub attachments: Option<Vec<PartialDiscordFileAttachment>>,
}
#[derive(Debug)]
pub enum MessageSearchEndpoint {
GuildChannel(Snowflake),
Channel(Snowflake),
}
impl std::fmt::Display for MessageSearchEndpoint {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
MessageSearchEndpoint::Channel(id) => {
write!(f, "channels/{}", &id.to_string())
}
MessageSearchEndpoint::GuildChannel(id) => {
write!(f, "guilds/{}", &id.to_string())
}
}
}
}
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq, PartialOrd, Ord)]
/// Represents a Message Search Query JSON Body.
/// The `channel_id` field is not applicable when using the `GET /channels/{channel.id}/messages/search` endpoint.
///
/// # Reference:
/// See <https://discord-userdoccers.vercel.app/resources/message#search-messages>
pub struct MessageSearchQuery {
pub attachment_extension: Option<Vec<String>>,
pub attachment_filename: Option<Vec<String>>,
pub author_id: Option<Vec<Snowflake>>,
pub author_type: Option<Vec<String>>,
pub channel_id: Option<Vec<Snowflake>>,
pub command_id: Option<Vec<Snowflake>>,
pub content: Option<String>,
pub embed_provider: Option<Vec<String>>,
pub embed_type: Option<Vec<String>>,
pub has: Option<Vec<String>>,
pub include_nsfw: Option<bool>,
pub limit: Option<i32>,
pub link_hostname: Option<Vec<String>>,
pub max_id: Option<String>,
pub mention_everyone: Option<bool>,
pub mentions: Option<Vec<Snowflake>>,
pub min_id: Option<String>,
pub offset: Option<i32>,
pub pinned: Option<bool>,
pub sort_by: Option<String>,
pub sort_order: Option<String>,
}
impl std::default::Default for MessageSearchQuery {
fn default() -> Self {
Self {
attachment_extension: Default::default(),
attachment_filename: Default::default(),
author_id: Default::default(),
author_type: Default::default(),
channel_id: Default::default(),
command_id: Default::default(),
content: Default::default(),
embed_provider: Default::default(),
embed_type: Default::default(),
has: Default::default(),
include_nsfw: Some(false),
limit: Some(25),
link_hostname: Default::default(),
max_id: Default::default(),
mention_everyone: Default::default(),
mentions: Default::default(),
min_id: Default::default(),
offset: Some(0),
pinned: Default::default(),
sort_by: Default::default(),
sort_order: Default::default(),
}
}
}
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct CreateGreetMessage {
pub sticker_ids: Vec<Snowflake>,
pub allowed_mentions: Option<AllowedMention>,
pub message_reference: Option<MessageReference>,
}
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct MessageAck {
pub token: Option<String>,
pub manual: Option<bool>,
pub mention_count: Option<u32>,
}
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, PartialOrd)]
pub struct MessageModifySchema {
content: Option<String>,
embeds: Option<Vec<Embed>>,
embed: Option<Embed>,
allowed_mentions: Option<AllowedMention>,
components: Option<Vec<Component>>,
flags: Option<i32>,
files: Option<Vec<u8>>,
payload_json: Option<String>,
attachments: Option<Vec<Attachment>>,
}

View File

@ -1,6 +1,6 @@
use serde::{Deserialize, Serialize};
#[derive(Debug, Deserialize, Serialize)]
#[derive(Debug, Deserialize, Serialize, Clone)]
#[serde(rename_all = "snake_case")]
/// Represents the schema which needs to be sent to create or modify a Role.
/// See: [https://docs.spacebar.chat/routes/#cmp--schemas-rolemodifyschema](https://docs.spacebar.chat/routes/#cmp--schemas-rolemodifyschema)

View File

@ -4,8 +4,9 @@ use serde::{Deserialize, Serialize};
use crate::types::Snowflake;
#[derive(Debug, Deserialize, Serialize)]
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
/// A schema used to modify a user.
pub struct UserModifySchema {
pub username: Option<String>,
pub avatar: Option<String>,
@ -19,6 +20,8 @@ pub struct UserModifySchema {
pub discriminator: Option<i16>,
}
/// A schema used to create a private channel.
///
/// # Attributes:
/// - recipients: The users to include in the private channel
/// - access_tokens: The access tokens of users that have granted your app the `gdm.join` scope. Only usable for OAuth2 requests (which can only create group DMs).
@ -26,7 +29,7 @@ pub struct UserModifySchema {
///
/// # Reference:
/// Read: <https://discord-userdoccers.vercel.app/resources/channel#json-params>
#[derive(Debug, Deserialize, Serialize)]
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)]
pub struct PrivateChannelCreateSchema {
pub recipients: Option<Vec<Snowflake>>,
pub access_tokens: Option<Vec<String>>,

View File

@ -1,8 +1,7 @@
use crate::types::utils::Snowflake;
use jsonwebtoken::{encode, EncodingKey, Header};
use serde::{Deserialize, Serialize};
use crate::types::utils::Snowflake;
pub fn generate_token(id: &Snowflake, email: String, jwt_key: &str) -> String {
let claims = Claims::new(&email, id);
@ -11,7 +10,9 @@ pub fn generate_token(id: &Snowflake, email: String, jwt_key: &str) -> String {
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Claims {
/// When the token expires, unix epoch
pub exp: i64,
/// When the token was issued
pub iat: i64,
pub email: String,
pub id: String,

View File

@ -1,56 +1,124 @@
use bitflags::bitflags;
bitflags! {
/// Rights are instance-wide, per-user permissions for everything you may perform on the instance,
/// such as sending messages, editing messages, or shutting down the server.
/// They are separate from guild member permissions, which only apply to a given guild.
///
/// # Notes
/// The default rights on Discord.com are 648540060672 ([source](https://github.com/spacebarchat/server/issues/878#issuecomment-1234669715))
///
/// # Reference
/// See <https://docs.spacebar.chat/setup/server/security/rights/>
pub struct Rights: u64 {
/// All rights
const OPERATOR = 1 << 0;
/// Ability to alter or remove others' applications
const MANAGE_APPLICATIONS = 1 << 1;
/// Same as the per-guild [MANAGE_GUILD] permission, but applies to all guilds and DM channels, can join any guild without invite
const MANAGE_GUILDS = 1 << 2;
/// Can delete or edit any message they can read
const MANAGE_MESSAGES = 1 << 3;
/// Can add, change, define rate limits of other users,
/// can also grant others [BYPASS_RATE_LIMITS] when combined
/// with [BYPASS_RATE_LIMITS] and [MANAGE_USERS].
const MANAGE_RATE_LIMITS = 1 << 4;
/// Can create, alter, enable and disable custom message routing rules in any channel/guild
const MANAGE_ROUTING = 1 << 5;
/// Respond to or resolve other users' support tickets
const MANAGE_TICKETS = 1 << 6;
/// Can create, alter, remove and ban users; can also create, modify and remove user groups
const MANAGE_USERS = 1 << 7;
/// Can manually add members into their guilds and group DMs
const ADD_MEMBERS = 1 << 8;
/// Makes the user exempt from all rate limits
const BYPASS_RATE_LIMITS = 1 << 9;
/// Can create, edit and remove own applications
const CREATE_APPLICATIONS = 1 << 10;
/// Can create guild channels and custom channels
const CREATE_CHANNELS = 1 << 11;
/// Can create 1:1 DMs
///
/// # Notes
/// A user without [SEND_MESSAGES] cannot be added to a DM
const CREATE_DMS = 1 << 12;
/// Can create group DMs
///
/// # Notes
/// A user without [SEND_MESSAGES] cannot be added to a DM
const CREATE_DM_GROUPS = 1 << 13;
/// Can create guilds
const CREATE_GUILDS = 1 << 14;
/// Can create mass invites in guilds where they have [CREATE_INSTANT_INVITE]
const CREATE_INVITES = 1 << 15;
/// Can create roles and per-guild or per-channel permission
/// overrides in the guilds that they have permissions
const CREATE_ROLES = 1 << 16;
/// Can create templates for guilds, custom channels and channels with custom routing
const CREATE_TEMPLATES = 1 << 17;
/// Can create webhooks in the guilds that they have permissions
const CREATE_WEBHOOKS = 1 << 18;
/// Can join guilds by using invites or vanity names
const JOIN_GUILDS = 1 << 19;
/// Can modify the pinned messages in the guilds that they have permission
const PIN_MESSAGES = 1 << 20;
/// Can react to messages, subject to permissions
const SELF_ADD_REACTIONS = 1 << 21;
/// Can delete own messages
const SELF_DELETE_MESSAGES = 1 << 22;
/// Can edit own messages
const SELF_EDIT_MESSAGES = 1 << 23;
/// Can edit own username, nickname and avatar
const SELF_EDIT_NAME = 1 << 24;
/// Can send messages in the channels that they have permissions
const SEND_MESSAGES = 1 << 25;
/// Can use voice activities, such as watch together or whiteboard
const USE_ACTIVITIES = 1 << 26;
/// Can use video and screenshare in guilds/channels that they have permissions
const USE_VIDEO = 1 << 27;
/// Can use voice in guilds/channels that they have permissions
const USE_VOICE = 1 << 28;
/// Can create user-specific invites in guilds that they have the [`INVITE_USERS`] right in.
const INVITE_USERS = 1 << 29;
/// Can delete/disable own account
const SELF_DELETE_DISABLE = 1 << 30;
/// Can use pay-to-use features once paid
const DEBTABLE = 1 << 31;
/// Can earn money using monetization features in guilds that have [`MonetizationEnabled`](crate::types::types::guild_configuration::GuildFeatures::MonetizationEnabled)
const CREDITABLE = 1 << 32;
/// Can kick or ban guild or group DM members in the guilds/groups where they have [`KICK_MEMBERS`](crate::types::PermissionFlags::KICK_MEMBERS) or [`BAN_MEMBERS`](crate::types::PermissionFlags::BAN_MEMBERS)
const KICK_BAN_MEMBERS = 1 << 33;
/// Can leave the guilds or group DMs that they joined on their own (one can always leave a guild or group DMs where they have been force-added)
const SELF_LEAVE_GROUPS = 1 << 34;
/// Inverts the presence confidentiality default ([`OPERATOR`]'s presence is not routed by default, others' are) for a given user
const PRESENCE = 1 << 35;
/// Can mark discoverable guilds where they have permissions to mark as discoverable
const SELF_ADD_DISCOVERABLE = 1 << 36;
/// Can change anything in the primary guild directory
const MANAGE_GUILD_DIRECTORY = 1 << 37;
/// Can send confetti, screenshake and use the random user mention (@someone)
const POGGERS = 1 << 38;
/// Can use achievements and cheers
const USE_ACHIEVEMENTS = 1 << 39;
/// Can initiate interactions
const INITIATE_INTERACTIONS = 1 << 40;
/// Can respond to interactions
const RESPOND_TO_INTERACTIONS = 1 << 41;
/// Can send backdated events
const SEND_BACKDATED_EVENTS = 1 << 42;
/// Can accept mass (guild) invites
const USE_MASS_INVITES = 1 << 43;
/// Can accept user-specific invites and DM requests
const ACCEPT_INVITES = 1 << 44;
/// Can modify own flags
const SELF_EDIT_FLAGS = 1 << 45;
/// Can modify other's flags
const EDIT_FLAGS = 1 << 46;
/// Can manage other's groups
const MANAGE_GROUPS = 1 << 47;
/// Can view server stats at /api/policies/stats
const VIEW_SERVER_STATS = 1 << 48;
/// Can resend verification emails using /auth/verify/resend
const RESEND_VERIFICATION_EMAIL = 1 << 49;
}
}
@ -60,10 +128,16 @@ impl Rights {
(check_operator && self.contains(Rights::OPERATOR)) || self.contains(permission)
}
/// Returns whether or not the Rights object has specific rights
pub fn has(&self, permission: Rights, check_operator: bool) -> bool {
(check_operator && self.contains(Rights::OPERATOR)) || self.contains(permission)
}
/// Returns whether or not the Rights object has specific rights.
///
/// # Notes
/// Unlike has, this returns an Error if we are missing rights
/// and Ok(true) otherwise
pub fn has_throw(&self, permission: Rights) -> Result<bool, &'static str> {
if self.has(permission, true) {
Ok(true)

View File

@ -11,13 +11,16 @@ use sqlx::Type;
const EPOCH: i64 = 1420070400000;
/// Unique identifier including a timestamp.
/// See https://discord.com/developers/docs/reference#snowflakes
///
/// # Reference
/// See <https://discord.com/developers/docs/reference#snowflakes>
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[cfg_attr(feature = "sqlx", derive(Type))]
#[cfg_attr(feature = "sqlx", sqlx(transparent))]
pub struct Snowflake(u64);
pub struct Snowflake(pub u64);
impl Snowflake {
/// Generates a snowflake for the current timestamp, with worker id 0 and process id 1.
pub fn generate() -> Self {
const WORKER_ID: u64 = 0;
const PROCESS_ID: u64 = 1;
@ -31,6 +34,7 @@ impl Snowflake {
Self(time as u64 | worker | process | increment)
}
/// Returns the snowflake's timestamp
pub fn timestamp(self) -> DateTime<Utc> {
Utc.timestamp_millis_opt((self.0 >> 22) as i64 + EPOCH)
.unwrap()
@ -49,6 +53,15 @@ impl Display for Snowflake {
}
}
impl<T> From<T> for Snowflake
where
T: Into<u64>,
{
fn from(item: T) -> Self {
Self(item.into())
}
}
impl serde::Serialize for Snowflake {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where

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