Merge with main
This commit is contained in:
parent
32a42dae87
commit
942fecbec7
|
@ -15,20 +15,22 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- name: Install dependencies
|
- name: Clone spacebar server
|
||||||
run: |
|
run: |
|
||||||
sudo apt-get update
|
|
||||||
sudo apt-get install -y git python3 build-essential
|
|
||||||
curl -fsSL https://deb.nodesource.com/setup_16.x | sudo -E bash -
|
|
||||||
sudo apt-get install -y nodejs
|
|
||||||
git clone https://github.com/bitfl0wer/server.git
|
git clone https://github.com/bitfl0wer/server.git
|
||||||
|
- uses: actions/setup-node@v3
|
||||||
|
with:
|
||||||
|
node-version: 16
|
||||||
|
cache: 'npm'
|
||||||
|
cache-dependency-path: server/package-lock.json
|
||||||
- name: Prepare and start Spacebar server
|
- name: Prepare and start Spacebar server
|
||||||
run: |
|
run: |
|
||||||
npm install
|
npm install
|
||||||
npm run setup
|
npm run setup
|
||||||
npm run start &
|
npm run start &
|
||||||
working-directory: ./server
|
working-directory: ./server
|
||||||
|
- uses: Swatinem/rust-cache@v2
|
||||||
- name: Build
|
- name: Build
|
||||||
run: cargo build --verbose
|
run: cargo build --verbose
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
|
|
|
@ -1,20 +0,0 @@
|
||||||
name: Clippy check
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [ "main" ]
|
|
||||||
pull_request:
|
|
||||||
branches: [ "main" ]
|
|
||||||
|
|
||||||
|
|
||||||
# Make sure CI fails on all warnings, including Clippy lints
|
|
||||||
env:
|
|
||||||
RUSTFLAGS: "-Dwarnings"
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
clippy_check:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
- name: Run Clippy
|
|
||||||
run: cargo clippy --all-targets --all-features
|
|
|
@ -0,0 +1,53 @@
|
||||||
|
# This workflow uses actions that are not certified by GitHub.
|
||||||
|
# They are provided by a third-party and are governed by
|
||||||
|
# separate terms of service, privacy policy, and support
|
||||||
|
# documentation.
|
||||||
|
# rust-clippy is a tool that runs a bunch of lints to catch common
|
||||||
|
# mistakes in your Rust code and help improve your Rust code.
|
||||||
|
# More details at https://github.com/rust-lang/rust-clippy
|
||||||
|
# and https://rust-lang.github.io/rust-clippy/
|
||||||
|
|
||||||
|
name: rust-clippy analyze
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ "main", "preserve/*" ]
|
||||||
|
pull_request:
|
||||||
|
# The branches below must be a subset of the branches above
|
||||||
|
branches: [ "main" ]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
rust-clippy-analyze:
|
||||||
|
name: Run rust-clippy analyzing
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
security-events: write
|
||||||
|
actions: read # only required for a private repository by github/codeql-action/upload-sarif to get the Action run status
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: Install Rust toolchain
|
||||||
|
uses: actions-rs/toolchain@16499b5e05bf2e26879000db0c1d13f7e13fa3af #@v1
|
||||||
|
with:
|
||||||
|
profile: minimal
|
||||||
|
toolchain: stable
|
||||||
|
components: clippy
|
||||||
|
override: true
|
||||||
|
|
||||||
|
- name: Install required cargo
|
||||||
|
run: cargo install clippy-sarif sarif-fmt
|
||||||
|
|
||||||
|
- name: Run rust-clippy
|
||||||
|
run:
|
||||||
|
cargo clippy
|
||||||
|
--all-features
|
||||||
|
--message-format=json | clippy-sarif | tee rust-clippy-results.sarif | sarif-fmt
|
||||||
|
continue-on-error: true
|
||||||
|
|
||||||
|
- name: Upload analysis results to GitHub
|
||||||
|
uses: github/codeql-action/upload-sarif@v2
|
||||||
|
with:
|
||||||
|
sarif_file: rust-clippy-results.sarif
|
||||||
|
wait-for-processing: true
|
|
@ -2,19 +2,18 @@
|
||||||
# will have compiled files and executables
|
# will have compiled files and executables
|
||||||
/target/
|
/target/
|
||||||
|
|
||||||
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
|
|
||||||
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
|
|
||||||
Cargo.lock
|
|
||||||
|
|
||||||
# These are backup files generated by rustfmt
|
# These are backup files generated by rustfmt
|
||||||
**/*.rs.bk
|
**/*.rs.bk
|
||||||
|
|
||||||
|
|
||||||
# Added by cargo
|
# Added by cargo
|
||||||
|
|
||||||
/target
|
/target
|
||||||
|
|
||||||
###
|
# IDE specific folders and configs
|
||||||
|
|
||||||
.vscode/**
|
.vscode/**
|
||||||
.idea/**
|
.idea/**
|
||||||
|
|
||||||
|
# macOS
|
||||||
|
|
||||||
|
**/.DS_Store
|
File diff suppressed because it is too large
Load Diff
30
Cargo.toml
30
Cargo.toml
|
@ -10,31 +10,35 @@ backend = ["poem", "sqlx"]
|
||||||
client = []
|
client = []
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
tokio = {version = "1.28.1", features = ["rt", "macros", "rt-multi-thread", "full"]}
|
tokio = {version = "1.29.1", features = ["macros"]}
|
||||||
serde = {version = "1.0.163", features = ["derive"]}
|
serde = {version = "1.0.171", features = ["derive"]}
|
||||||
serde_json = {version= "1.0.96", features = ["raw_value"]}
|
serde_json = {version= "1.0.103", features = ["raw_value"]}
|
||||||
serde-aux = "4.2.0"
|
serde-aux = "4.2.0"
|
||||||
serde_with = "3.0.0"
|
serde_with = "3.0.0"
|
||||||
serde_repr = "0.1.12"
|
serde_repr = "0.1.14"
|
||||||
reqwest = {version = "0.11.16", features = ["multipart"]}
|
reqwest = {version = "0.11.18", features = ["multipart"]}
|
||||||
url = "2.3.1"
|
url = "2.4.0"
|
||||||
chrono = {version = "0.4.24", features = ["serde"]}
|
chrono = {version = "0.4.26", features = ["serde"]}
|
||||||
regex = "1.7.3"
|
regex = "1.9.1"
|
||||||
custom_error = "1.9.2"
|
custom_error = "1.9.2"
|
||||||
native-tls = "0.2.11"
|
native-tls = "0.2.11"
|
||||||
tokio-tungstenite = {version = "0.19.0", features = ["native-tls"]}
|
tokio-tungstenite = {version = "0.19.0", features = ["native-tls"]}
|
||||||
futures-util = "0.3.28"
|
futures-util = "0.3.28"
|
||||||
http = "0.2.9"
|
http = "0.2.9"
|
||||||
openssl = "0.10.52"
|
openssl = "0.10.55"
|
||||||
base64 = "0.21.2"
|
base64 = "0.21.2"
|
||||||
hostname = "0.3.1"
|
hostname = "0.3.1"
|
||||||
bitflags = { version = "2.2.1", features = ["serde"] }
|
bitflags = { version = "2.3.3", features = ["serde"] }
|
||||||
lazy_static = "1.4.0"
|
lazy_static = "1.4.0"
|
||||||
poem = { version = "1.3.55", optional = true }
|
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 }
|
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.40"
|
thiserror = "1.0.43"
|
||||||
jsonwebtoken = "8.3.0"
|
jsonwebtoken = "8.3.0"
|
||||||
|
log = "0.4.19"
|
||||||
|
async-trait = "0.1.71"
|
||||||
|
chorus-macros = {path = "chorus-macros"}
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
|
tokio = {version = "1.29.1", features = ["full"]}
|
||||||
lazy_static = "1.4.0"
|
lazy_static = "1.4.0"
|
||||||
rusty-hook = "0.11.2"
|
rusty-hook = "0.11.2"
|
||||||
|
|
|
@ -56,9 +56,9 @@ accepted, if it violates these guidelines or [our Code of Conduct](https://githu
|
||||||
- [x] Channel creation
|
- [x] Channel creation
|
||||||
- [x] Channel deletion
|
- [x] Channel deletion
|
||||||
- [x] [Channel management (name, description, icon, etc.)](https://github.com/polyphony-chat/chorus/issues/48)
|
- [x] [Channel management (name, description, icon, etc.)](https://github.com/polyphony-chat/chorus/issues/48)
|
||||||
- [ ] [Join and Leave Guilds](https://github.com/polyphony-chat/chorus/issues/45)
|
- [x] [Join and Leave Guilds](https://github.com/polyphony-chat/chorus/issues/45)
|
||||||
- [ ] [Start DMs](https://github.com/polyphony-chat/chorus/issues/45)
|
- [x] [Start DMs](https://github.com/polyphony-chat/chorus/issues/45)
|
||||||
- [ ] [Group DM creation, deletion and member management](https://github.com/polyphony-chat/chorus/issues/89)
|
- [x] [Group DM creation, deletion and member management](https://github.com/polyphony-chat/chorus/issues/89)
|
||||||
- [ ] [Deleting messages](https://github.com/polyphony-chat/chorus/issues/91)
|
- [ ] [Deleting messages](https://github.com/polyphony-chat/chorus/issues/91)
|
||||||
- [ ] [Message threads](https://github.com/polyphony-chat/chorus/issues/90)
|
- [ ] [Message threads](https://github.com/polyphony-chat/chorus/issues/90)
|
||||||
- [x] [Reactions](https://github.com/polyphony-chat/chorus/issues/85)
|
- [x] [Reactions](https://github.com/polyphony-chat/chorus/issues/85)
|
||||||
|
|
|
@ -0,0 +1,46 @@
|
||||||
|
# This file is automatically @generated by Cargo.
|
||||||
|
# It is not intended for manual editing.
|
||||||
|
version = 3
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "chorus-macros"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "proc-macro2"
|
||||||
|
version = "1.0.66"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9"
|
||||||
|
dependencies = [
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "quote"
|
||||||
|
version = "1.0.31"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5fe8a65d69dd0808184ebb5f836ab526bb259db23c657efa38711b1072ee47f0"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "syn"
|
||||||
|
version = "2.0.27"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b60f673f44a8255b9c8c657daf66a596d435f2da81a555b06dc644d080ba45e0"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-ident"
|
||||||
|
version = "1.0.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c"
|
|
@ -0,0 +1,11 @@
|
||||||
|
[package]
|
||||||
|
name = "chorus-macros"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
proc-macro = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
quote = "1"
|
||||||
|
syn = "2"
|
|
@ -0,0 +1,18 @@
|
||||||
|
use proc_macro::TokenStream;
|
||||||
|
use quote::quote;
|
||||||
|
|
||||||
|
#[proc_macro_derive(Updateable)]
|
||||||
|
pub fn updateable_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 Updateable for #name {
|
||||||
|
fn id(&self) -> Snowflake {
|
||||||
|
self.id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.into()
|
||||||
|
}
|
|
@ -1,3 +1,4 @@
|
||||||
|
use async_trait::async_trait;
|
||||||
use chorus::{
|
use chorus::{
|
||||||
self,
|
self,
|
||||||
gateway::{Gateway, Observer},
|
gateway::{Gateway, Observer},
|
||||||
|
@ -15,9 +16,10 @@ pub struct ExampleObserver {}
|
||||||
// This struct can observe GatewayReady events when subscribed, because it implements the trait Observer<GatewayReady>.
|
// This struct can observe GatewayReady events when subscribed, because it implements the trait Observer<GatewayReady>.
|
||||||
// The Observer trait can be implemented for a struct for a given websocketevent to handle observing it
|
// The Observer trait can be implemented for a struct for a given websocketevent to handle observing it
|
||||||
// One struct can be an observer of multiple websocketevents, if needed
|
// One struct can be an observer of multiple websocketevents, if needed
|
||||||
|
#[async_trait]
|
||||||
impl Observer<GatewayReady> for ExampleObserver {
|
impl Observer<GatewayReady> for ExampleObserver {
|
||||||
// After we subscribe to an event this function is called every time we receive it
|
// After we subscribe to an event this function is called every time we receive it
|
||||||
fn update(&self, _data: &GatewayReady) {
|
async fn update(&self, _data: &GatewayReady) {
|
||||||
println!("Observed Ready!");
|
println!("Observed Ready!");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,64 +2,48 @@ use std::cell::RefCell;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
use reqwest::Client;
|
use reqwest::Client;
|
||||||
use serde_json::{from_str, json};
|
use serde_json::to_string;
|
||||||
|
|
||||||
use crate::api::limits::LimitType;
|
use crate::api::LimitType;
|
||||||
use crate::errors::{ChorusLibError, ChorusResult};
|
use crate::errors::ChorusResult;
|
||||||
|
use crate::gateway::Gateway;
|
||||||
use crate::instance::{Instance, UserMeta};
|
use crate::instance::{Instance, UserMeta};
|
||||||
use crate::limit::LimitedRequester;
|
use crate::ratelimiter::ChorusRequest;
|
||||||
use crate::types::{ErrorResponse, LoginResult, LoginSchema};
|
use crate::types::{GatewayIdentifyPayload, LoginResult, LoginSchema};
|
||||||
|
|
||||||
impl Instance {
|
impl Instance {
|
||||||
pub async fn login_account(&mut self, login_schema: &LoginSchema) -> ChorusResult<UserMeta> {
|
pub async fn login_account(&mut self, login_schema: &LoginSchema) -> ChorusResult<UserMeta> {
|
||||||
let json_schema = json!(login_schema);
|
|
||||||
let client = Client::new();
|
|
||||||
let endpoint_url = self.urls.api.clone() + "/auth/login";
|
let endpoint_url = self.urls.api.clone() + "/auth/login";
|
||||||
let request_builder = client.post(endpoint_url).body(json_schema.to_string());
|
let chorus_request = ChorusRequest {
|
||||||
|
request: Client::new()
|
||||||
|
.post(endpoint_url)
|
||||||
|
.body(to_string(login_schema).unwrap()),
|
||||||
|
limit_type: LimitType::AuthLogin,
|
||||||
|
};
|
||||||
// We do not have a user yet, and the UserRateLimits will not be affected by a login
|
// We do not have a user yet, and the UserRateLimits will not be affected by a login
|
||||||
// request (since login is an instance wide limit), which is why we are just cloning the
|
// request (since login is an instance wide limit), which is why we are just cloning the
|
||||||
// instances' limits to pass them on as user_rate_limits later.
|
// instances' limits to pass them on as user_rate_limits later.
|
||||||
let mut cloned_limits = self.limits.clone();
|
let mut shell =
|
||||||
let response = LimitedRequester::send_request(
|
UserMeta::shell(Rc::new(RefCell::new(self.clone())), "None".to_string()).await;
|
||||||
request_builder,
|
let login_result = chorus_request
|
||||||
LimitType::AuthRegister,
|
.deserialize_response::<LoginResult>(&mut shell)
|
||||||
self,
|
.await?;
|
||||||
&mut cloned_limits,
|
let object = self.get_user(login_result.token.clone(), None).await?;
|
||||||
)
|
if self.limits_information.is_some() {
|
||||||
.await;
|
self.limits_information.as_mut().unwrap().ratelimits = shell.limits.clone().unwrap();
|
||||||
if response.is_err() {
|
|
||||||
return Err(ChorusLibError::NoResponse);
|
|
||||||
}
|
}
|
||||||
|
let mut identify = GatewayIdentifyPayload::common();
|
||||||
let response_unwrap = response.unwrap();
|
let gateway = Gateway::new(self.urls.wss.clone()).await.unwrap();
|
||||||
let status = response_unwrap.status();
|
identify.token = login_result.token.clone();
|
||||||
let response_text_string = response_unwrap.text().await.unwrap();
|
gateway.send_identify(identify).await;
|
||||||
if status.is_client_error() {
|
|
||||||
let json: ErrorResponse = serde_json::from_str(&response_text_string).unwrap();
|
|
||||||
let error_type = json.errors.errors.iter().next().unwrap().0.to_owned();
|
|
||||||
let mut error = "".to_string();
|
|
||||||
for (_, value) in json.errors.errors.iter() {
|
|
||||||
for error_item in value._errors.iter() {
|
|
||||||
error += &(error_item.message.to_string() + " (" + &error_item.code + ")");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Err(ChorusLibError::InvalidFormBodyError { error_type, error });
|
|
||||||
}
|
|
||||||
|
|
||||||
let cloned_limits = self.limits.clone();
|
|
||||||
let login_result: LoginResult = from_str(&response_text_string).unwrap();
|
|
||||||
let object = self
|
|
||||||
.get_user(login_result.token.clone(), None)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
let user = UserMeta::new(
|
let user = UserMeta::new(
|
||||||
Rc::new(RefCell::new(self.clone())),
|
Rc::new(RefCell::new(self.clone())),
|
||||||
login_result.token,
|
login_result.token,
|
||||||
cloned_limits,
|
self.clone_limits_if_some(),
|
||||||
login_result.settings,
|
login_result.settings,
|
||||||
object,
|
object,
|
||||||
|
gateway,
|
||||||
);
|
);
|
||||||
|
|
||||||
Ok(user)
|
Ok(user)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,16 @@
|
||||||
use std::{cell::RefCell, rc::Rc};
|
use std::{cell::RefCell, rc::Rc};
|
||||||
|
|
||||||
use reqwest::Client;
|
use reqwest::Client;
|
||||||
use serde_json::{from_str, json};
|
use serde_json::to_string;
|
||||||
|
|
||||||
|
use crate::gateway::Gateway;
|
||||||
|
use crate::types::GatewayIdentifyPayload;
|
||||||
use crate::{
|
use crate::{
|
||||||
api::limits::LimitType,
|
api::policies::instance::LimitType,
|
||||||
errors::{ChorusLibError, ChorusResult},
|
errors::ChorusResult,
|
||||||
instance::{Instance, Token, UserMeta},
|
instance::{Instance, Token, UserMeta},
|
||||||
limit::LimitedRequester,
|
ratelimiter::ChorusRequest,
|
||||||
types::{ErrorResponse, RegisterSchema},
|
types::RegisterSchema,
|
||||||
};
|
};
|
||||||
|
|
||||||
impl Instance {
|
impl Instance {
|
||||||
|
@ -25,51 +27,38 @@ impl Instance {
|
||||||
&mut self,
|
&mut self,
|
||||||
register_schema: &RegisterSchema,
|
register_schema: &RegisterSchema,
|
||||||
) -> ChorusResult<UserMeta> {
|
) -> ChorusResult<UserMeta> {
|
||||||
let json_schema = json!(register_schema);
|
|
||||||
let client = Client::new();
|
|
||||||
let endpoint_url = self.urls.api.clone() + "/auth/register";
|
let endpoint_url = self.urls.api.clone() + "/auth/register";
|
||||||
let request_builder = client.post(endpoint_url).body(json_schema.to_string());
|
let chorus_request = ChorusRequest {
|
||||||
|
request: Client::new()
|
||||||
|
.post(endpoint_url)
|
||||||
|
.body(to_string(register_schema).unwrap()),
|
||||||
|
limit_type: LimitType::AuthRegister,
|
||||||
|
};
|
||||||
// We do not have a user yet, and the UserRateLimits will not be affected by a login
|
// We do not have a user yet, and the UserRateLimits will not be affected by a login
|
||||||
// request (since register is an instance wide limit), which is why we are just cloning
|
// request (since register is an instance wide limit), which is why we are just cloning
|
||||||
// the instances' limits to pass them on as user_rate_limits later.
|
// the instances' limits to pass them on as user_rate_limits later.
|
||||||
let mut cloned_limits = self.limits.clone();
|
let mut shell =
|
||||||
let response = LimitedRequester::send_request(
|
UserMeta::shell(Rc::new(RefCell::new(self.clone())), "None".to_string()).await;
|
||||||
request_builder,
|
let token = chorus_request
|
||||||
LimitType::AuthRegister,
|
.deserialize_response::<Token>(&mut shell)
|
||||||
self,
|
.await?
|
||||||
&mut cloned_limits,
|
.token;
|
||||||
)
|
if self.limits_information.is_some() {
|
||||||
.await;
|
self.limits_information.as_mut().unwrap().ratelimits = shell.limits.unwrap();
|
||||||
if response.is_err() {
|
|
||||||
return Err(ChorusLibError::NoResponse);
|
|
||||||
}
|
|
||||||
|
|
||||||
let response_unwrap = response.unwrap();
|
|
||||||
let status = response_unwrap.status();
|
|
||||||
let response_unwrap_text = response_unwrap.text().await.unwrap();
|
|
||||||
let token = from_str::<Token>(&response_unwrap_text).unwrap();
|
|
||||||
let token = token.token;
|
|
||||||
if status.is_client_error() {
|
|
||||||
let json: ErrorResponse = serde_json::from_str(&token).unwrap();
|
|
||||||
let error_type = json.errors.errors.iter().next().unwrap().0.to_owned();
|
|
||||||
let mut error = "".to_string();
|
|
||||||
for (_, value) in json.errors.errors.iter() {
|
|
||||||
for error_item in value._errors.iter() {
|
|
||||||
error += &(error_item.message.to_string() + " (" + &error_item.code + ")");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Err(ChorusLibError::InvalidFormBodyError { error_type, error });
|
|
||||||
}
|
}
|
||||||
let user_object = self.get_user(token.clone(), None).await.unwrap();
|
let user_object = self.get_user(token.clone(), None).await.unwrap();
|
||||||
let settings = UserMeta::get_settings(&token, &self.urls.api.clone(), self)
|
let settings = UserMeta::get_settings(&token, &self.urls.api.clone(), self).await?;
|
||||||
.await
|
let mut identify = GatewayIdentifyPayload::common();
|
||||||
.unwrap();
|
let gateway = Gateway::new(self.urls.wss.clone()).await.unwrap();
|
||||||
|
identify.token = token.clone();
|
||||||
|
gateway.send_identify(identify).await;
|
||||||
let user = UserMeta::new(
|
let user = UserMeta::new(
|
||||||
Rc::new(RefCell::new(self.clone())),
|
Rc::new(RefCell::new(self.clone())),
|
||||||
token.clone(),
|
token.clone(),
|
||||||
cloned_limits,
|
self.clone_limits_if_some(),
|
||||||
settings,
|
settings,
|
||||||
user_object,
|
user_object,
|
||||||
|
gateway,
|
||||||
);
|
);
|
||||||
Ok(user)
|
Ok(user)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,33 +1,25 @@
|
||||||
use reqwest::Client;
|
use reqwest::Client;
|
||||||
use serde_json::to_string;
|
use serde_json::to_string;
|
||||||
|
|
||||||
|
use crate::types::AddChannelRecipientSchema;
|
||||||
use crate::{
|
use crate::{
|
||||||
api::common,
|
api::LimitType,
|
||||||
errors::{ChorusLibError, ChorusResult},
|
errors::{ChorusError, ChorusResult},
|
||||||
instance::UserMeta,
|
instance::UserMeta,
|
||||||
|
ratelimiter::ChorusRequest,
|
||||||
types::{Channel, ChannelModifySchema, GetChannelMessagesSchema, Message, Snowflake},
|
types::{Channel, ChannelModifySchema, GetChannelMessagesSchema, Message, Snowflake},
|
||||||
};
|
};
|
||||||
|
|
||||||
impl Channel {
|
impl Channel {
|
||||||
pub async fn get(user: &mut UserMeta, channel_id: Snowflake) -> ChorusResult<Channel> {
|
pub async fn get(user: &mut UserMeta, channel_id: Snowflake) -> ChorusResult<Channel> {
|
||||||
let url = user.belongs_to.borrow_mut().urls.api.clone();
|
let url = user.belongs_to.borrow().urls.api.clone();
|
||||||
let request = Client::new()
|
let chorus_request = ChorusRequest {
|
||||||
.get(format!("{}/channels/{}/", url, channel_id))
|
request: Client::new()
|
||||||
.bearer_auth(user.token());
|
.get(format!("{}/channels/{}/", url, channel_id))
|
||||||
|
.bearer_auth(user.token()),
|
||||||
let result = common::deserialize_response::<Channel>(
|
limit_type: LimitType::Channel(channel_id),
|
||||||
request,
|
};
|
||||||
user,
|
chorus_request.deserialize_response::<Channel>(user).await
|
||||||
crate::api::limits::LimitType::Channel,
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
if result.is_err() {
|
|
||||||
return Err(ChorusLibError::RequestErrorError {
|
|
||||||
url: format!("{}/channels/{}/", url, channel_id),
|
|
||||||
error: result.err().unwrap().to_string(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
Ok(result.unwrap())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Deletes a channel.
|
/// Deletes a channel.
|
||||||
|
@ -44,15 +36,17 @@ impl Channel {
|
||||||
///
|
///
|
||||||
/// A `Result` that contains a `ChorusLibError` if an error occurred during the request, or `()` if the request was successful.
|
/// 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<()> {
|
pub async fn delete(self, user: &mut UserMeta) -> ChorusResult<()> {
|
||||||
let request = Client::new()
|
let chorus_request = ChorusRequest {
|
||||||
.delete(format!(
|
request: Client::new()
|
||||||
"{}/channels/{}/",
|
.delete(format!(
|
||||||
user.belongs_to.borrow_mut().urls.api,
|
"{}/channels/{}/",
|
||||||
self.id
|
user.belongs_to.borrow().urls.api,
|
||||||
))
|
self.id
|
||||||
.bearer_auth(user.token());
|
))
|
||||||
common::handle_request_as_result(request, user, crate::api::limits::LimitType::Channel)
|
.bearer_auth(user.token()),
|
||||||
.await
|
limit_type: LimitType::Channel(self.id),
|
||||||
|
};
|
||||||
|
chorus_request.handle_request_as_result(user).await
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Modifies a channel.
|
/// Modifies a channel.
|
||||||
|
@ -70,43 +64,94 @@ impl Channel {
|
||||||
///
|
///
|
||||||
/// A `Result` that contains a `Channel` object if the request was successful, or an `ChorusLibError` if an error occurred during the request.
|
/// A `Result` that contains a `Channel` object if the request was successful, or an `ChorusLibError` if an error occurred during the request.
|
||||||
pub async fn modify(
|
pub async fn modify(
|
||||||
&mut self,
|
&self,
|
||||||
modify_data: ChannelModifySchema,
|
modify_data: ChannelModifySchema,
|
||||||
channel_id: Snowflake,
|
channel_id: Snowflake,
|
||||||
user: &mut UserMeta,
|
user: &mut UserMeta,
|
||||||
) -> ChorusResult<()> {
|
) -> ChorusResult<Channel> {
|
||||||
let request = Client::new()
|
let chorus_request = ChorusRequest {
|
||||||
.patch(format!(
|
request: Client::new()
|
||||||
"{}/channels/{}/",
|
.patch(format!(
|
||||||
user.belongs_to.borrow().urls.api,
|
"{}/channels/{}/",
|
||||||
channel_id
|
user.belongs_to.borrow().urls.api,
|
||||||
))
|
channel_id
|
||||||
.bearer_auth(user.token())
|
))
|
||||||
.body(to_string(&modify_data).unwrap());
|
.bearer_auth(user.token())
|
||||||
let new_channel = common::deserialize_response::<Channel>(
|
.body(to_string(&modify_data).unwrap()),
|
||||||
request,
|
limit_type: LimitType::Channel(channel_id),
|
||||||
user,
|
};
|
||||||
crate::api::limits::LimitType::Channel,
|
chorus_request.deserialize_response::<Channel>(user).await
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
let _ = std::mem::replace(self, new_channel);
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn messages(
|
pub async fn messages(
|
||||||
range: GetChannelMessagesSchema,
|
range: GetChannelMessagesSchema,
|
||||||
channel_id: Snowflake,
|
channel_id: Snowflake,
|
||||||
user: &mut UserMeta,
|
user: &mut UserMeta,
|
||||||
) -> Result<Vec<Message>, ChorusLibError> {
|
) -> Result<Vec<Message>, ChorusError> {
|
||||||
let request = Client::new()
|
let chorus_request = ChorusRequest {
|
||||||
.get(format!(
|
request: Client::new()
|
||||||
"{}/channels/{}/messages",
|
.get(format!(
|
||||||
user.belongs_to.borrow().urls.api,
|
"{}/channels/{}/messages",
|
||||||
channel_id
|
user.belongs_to.borrow().urls.api,
|
||||||
))
|
channel_id
|
||||||
.bearer_auth(user.token())
|
))
|
||||||
.query(&range);
|
.bearer_auth(user.token())
|
||||||
|
.query(&range),
|
||||||
|
limit_type: Default::default(),
|
||||||
|
};
|
||||||
|
|
||||||
common::deserialize_response::<Vec<Message>>(request, user, Default::default()).await
|
chorus_request
|
||||||
|
.deserialize_response::<Vec<Message>>(user)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// # Reference:
|
||||||
|
/// Read: <https://discord-userdoccers.vercel.app/resources/channel#add-channel-recipient>
|
||||||
|
pub async fn add_channel_recipient(
|
||||||
|
&self,
|
||||||
|
recipient_id: Snowflake,
|
||||||
|
user: &mut UserMeta,
|
||||||
|
add_channel_recipient_schema: Option<AddChannelRecipientSchema>,
|
||||||
|
) -> ChorusResult<()> {
|
||||||
|
let mut request = Client::new()
|
||||||
|
.put(format!(
|
||||||
|
"{}/channels/{}/recipients/{}/",
|
||||||
|
user.belongs_to.borrow().urls.api,
|
||||||
|
self.id,
|
||||||
|
recipient_id
|
||||||
|
))
|
||||||
|
.bearer_auth(user.token());
|
||||||
|
if let Some(schema) = add_channel_recipient_schema {
|
||||||
|
request = request.body(to_string(&schema).unwrap());
|
||||||
|
}
|
||||||
|
ChorusRequest {
|
||||||
|
request,
|
||||||
|
limit_type: LimitType::Channel(self.id),
|
||||||
|
}
|
||||||
|
.handle_request_as_result(user)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// # Reference:
|
||||||
|
/// Read: <https://discord-userdoccers.vercel.app/resources/channel#remove-channel-recipient>
|
||||||
|
pub async fn remove_channel_recipient(
|
||||||
|
&self,
|
||||||
|
recipient_id: Snowflake,
|
||||||
|
user: &mut UserMeta,
|
||||||
|
) -> ChorusResult<()> {
|
||||||
|
let request = Client::new()
|
||||||
|
.delete(format!(
|
||||||
|
"{}/channels/{}/recipients/{}/",
|
||||||
|
user.belongs_to.borrow().urls.api,
|
||||||
|
self.id,
|
||||||
|
recipient_id
|
||||||
|
))
|
||||||
|
.bearer_auth(user.token());
|
||||||
|
ChorusRequest {
|
||||||
|
request,
|
||||||
|
limit_type: LimitType::Channel(self.id),
|
||||||
|
}
|
||||||
|
.handle_request_as_result(user)
|
||||||
|
.await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,48 +3,39 @@ use http::HeaderMap;
|
||||||
use reqwest::{multipart, Client};
|
use reqwest::{multipart, Client};
|
||||||
use serde_json::to_string;
|
use serde_json::to_string;
|
||||||
|
|
||||||
use crate::api::deserialize_response;
|
use crate::api::LimitType;
|
||||||
use crate::instance::UserMeta;
|
use crate::instance::UserMeta;
|
||||||
use crate::types::{Message, MessageSendSchema, PartialDiscordFileAttachment, Snowflake};
|
use crate::ratelimiter::ChorusRequest;
|
||||||
|
use crate::types::{Message, MessageSendSchema, Snowflake};
|
||||||
|
|
||||||
impl Message {
|
impl Message {
|
||||||
/**
|
|
||||||
Sends a message to the Spacebar server.
|
|
||||||
# Arguments
|
|
||||||
* `url_api` - The URL of the Spacebar server's API.
|
|
||||||
* `message` - The [`Message`] that will be sent to the Spacebar server.
|
|
||||||
* `limits_user` - The [`Limits`] of the user.
|
|
||||||
* `limits_instance` - The [`Limits`] of the instance.
|
|
||||||
* `requester` - The [`LimitedRequester`] that will be used to make requests to the Spacebar server.
|
|
||||||
# Errors
|
|
||||||
* [`ChorusLibError`] - If the message cannot be sent.
|
|
||||||
*/
|
|
||||||
pub async fn send(
|
pub async fn send(
|
||||||
user: &mut UserMeta,
|
user: &mut UserMeta,
|
||||||
channel_id: Snowflake,
|
channel_id: Snowflake,
|
||||||
message: &mut MessageSendSchema,
|
mut message: MessageSendSchema,
|
||||||
files: Option<Vec<PartialDiscordFileAttachment>>,
|
) -> Result<Message, crate::errors::ChorusError> {
|
||||||
) -> Result<Message, crate::errors::ChorusLibError> {
|
|
||||||
let url_api = user.belongs_to.borrow().urls.api.clone();
|
let url_api = user.belongs_to.borrow().urls.api.clone();
|
||||||
|
|
||||||
if files.is_none() {
|
if message.attachments.is_none() {
|
||||||
let request = Client::new()
|
let chorus_request = ChorusRequest {
|
||||||
.post(format!("{}/channels/{}/messages/", url_api, channel_id))
|
request: Client::new()
|
||||||
.bearer_auth(user.token())
|
.post(format!("{}/channels/{}/messages/", url_api, channel_id))
|
||||||
.body(to_string(message).unwrap());
|
.bearer_auth(user.token())
|
||||||
deserialize_response::<Message>(request, user, crate::api::limits::LimitType::Channel)
|
.body(to_string(&message).unwrap()),
|
||||||
.await
|
limit_type: LimitType::Channel(channel_id),
|
||||||
|
};
|
||||||
|
chorus_request.deserialize_response::<Message>(user).await
|
||||||
} else {
|
} else {
|
||||||
for (index, attachment) in message.attachments.iter_mut().enumerate() {
|
for (index, attachment) in message.attachments.iter_mut().enumerate() {
|
||||||
attachment.get_mut(index).unwrap().set_id(index as i16);
|
attachment.get_mut(index).unwrap().set_id(index as i16);
|
||||||
}
|
}
|
||||||
let mut form = reqwest::multipart::Form::new();
|
let mut form = reqwest::multipart::Form::new();
|
||||||
let payload_json = to_string(message).unwrap();
|
let payload_json = to_string(&message).unwrap();
|
||||||
let payload_field = reqwest::multipart::Part::text(payload_json);
|
let payload_field = reqwest::multipart::Part::text(payload_json);
|
||||||
|
|
||||||
form = form.part("payload_json", payload_field);
|
form = form.part("payload_json", payload_field);
|
||||||
|
|
||||||
for (index, attachment) in files.unwrap().into_iter().enumerate() {
|
for (index, attachment) in message.attachments.unwrap().into_iter().enumerate() {
|
||||||
let (attachment_content, current_attachment) = attachment.move_content();
|
let (attachment_content, current_attachment) = attachment.move_content();
|
||||||
let (attachment_filename, _) = current_attachment.move_filename();
|
let (attachment_filename, _) = current_attachment.move_filename();
|
||||||
let part_name = format!("files[{}]", index);
|
let part_name = format!("files[{}]", index);
|
||||||
|
@ -62,36 +53,24 @@ impl Message {
|
||||||
form = form.part(part_name, part);
|
form = form.part(part_name, part);
|
||||||
}
|
}
|
||||||
|
|
||||||
let request = Client::new()
|
let chorus_request = ChorusRequest {
|
||||||
.post(format!("{}/channels/{}/messages/", url_api, channel_id))
|
request: Client::new()
|
||||||
.bearer_auth(user.token())
|
.post(format!("{}/channels/{}/messages/", url_api, channel_id))
|
||||||
.multipart(form);
|
.bearer_auth(user.token())
|
||||||
|
.multipart(form),
|
||||||
deserialize_response::<Message>(request, user, crate::api::limits::LimitType::Channel)
|
limit_type: LimitType::Channel(channel_id),
|
||||||
.await
|
};
|
||||||
|
chorus_request.deserialize_response::<Message>(user).await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl UserMeta {
|
impl UserMeta {
|
||||||
/// Shorthand call for Message::send()
|
|
||||||
/**
|
|
||||||
Sends a message to the Spacebar server.
|
|
||||||
# Arguments
|
|
||||||
* `url_api` - The URL of the Spacebar server's API.
|
|
||||||
* `message` - The [`Message`] that will be sent to the Spacebar server.
|
|
||||||
* `limits_user` - The [`Limits`] of the user.
|
|
||||||
* `limits_instance` - The [`Limits`] of the instance.
|
|
||||||
* `requester` - The [`LimitedRequester`] that will be used to make requests to the Spacebar server.
|
|
||||||
# Errors
|
|
||||||
* [`ChorusLibError`] - If the message cannot be sent.
|
|
||||||
*/
|
|
||||||
pub async fn send_message(
|
pub async fn send_message(
|
||||||
&mut self,
|
&mut self,
|
||||||
message: &mut MessageSendSchema,
|
message: MessageSendSchema,
|
||||||
channel_id: Snowflake,
|
channel_id: Snowflake,
|
||||||
files: Option<Vec<PartialDiscordFileAttachment>>,
|
) -> Result<Message, crate::errors::ChorusError> {
|
||||||
) -> Result<Message, crate::errors::ChorusLibError> {
|
Message::send(self, channel_id, message).await
|
||||||
Message::send(self, channel_id, message, files).await
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,9 +2,10 @@ use reqwest::Client;
|
||||||
use serde_json::to_string;
|
use serde_json::to_string;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
api::handle_request_as_result,
|
api::LimitType,
|
||||||
errors::{ChorusLibError, ChorusResult},
|
errors::{ChorusError, ChorusResult},
|
||||||
instance::UserMeta,
|
instance::UserMeta,
|
||||||
|
ratelimiter::ChorusRequest,
|
||||||
types::{self, PermissionOverwrite, Snowflake},
|
types::{self, PermissionOverwrite, Snowflake},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -25,24 +26,25 @@ impl types::Channel {
|
||||||
channel_id: Snowflake,
|
channel_id: Snowflake,
|
||||||
overwrite: PermissionOverwrite,
|
overwrite: PermissionOverwrite,
|
||||||
) -> ChorusResult<()> {
|
) -> ChorusResult<()> {
|
||||||
let url = {
|
let url = format!(
|
||||||
format!(
|
"{}/channels/{}/permissions/{}",
|
||||||
"{}/channels/{}/permissions/{}",
|
user.belongs_to.borrow_mut().urls.api,
|
||||||
user.belongs_to.borrow_mut().urls.api,
|
channel_id,
|
||||||
channel_id,
|
overwrite.id
|
||||||
overwrite.id
|
);
|
||||||
)
|
|
||||||
};
|
|
||||||
let body = match to_string(&overwrite) {
|
let body = match to_string(&overwrite) {
|
||||||
Ok(string) => string,
|
Ok(string) => string,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
return Err(ChorusLibError::FormCreationError {
|
return Err(ChorusError::FormCreation {
|
||||||
error: e.to_string(),
|
error: e.to_string(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let request = Client::new().put(url).bearer_auth(user.token()).body(body);
|
let chorus_request = ChorusRequest {
|
||||||
handle_request_as_result(request, user, crate::api::limits::LimitType::Channel).await
|
request: Client::new().put(url).bearer_auth(user.token()).body(body),
|
||||||
|
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 channel.
|
||||||
|
@ -67,7 +69,10 @@ impl types::Channel {
|
||||||
channel_id,
|
channel_id,
|
||||||
overwrite_id
|
overwrite_id
|
||||||
);
|
);
|
||||||
let request = Client::new().delete(url).bearer_auth(user.token());
|
let chorus_request = ChorusRequest {
|
||||||
handle_request_as_result(request, user, crate::api::limits::LimitType::Channel).await
|
request: Client::new().delete(url).bearer_auth(user.token()),
|
||||||
|
limit_type: LimitType::Channel(channel_id),
|
||||||
|
};
|
||||||
|
chorus_request.handle_request_as_result(user).await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
use reqwest::Client;
|
use reqwest::Client;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
api::handle_request_as_result,
|
api::LimitType,
|
||||||
errors::ChorusResult,
|
errors::ChorusResult,
|
||||||
instance::UserMeta,
|
instance::UserMeta,
|
||||||
types::{self, Snowflake},
|
ratelimiter::ChorusRequest,
|
||||||
|
types::{self, PublicUser, Snowflake},
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -16,20 +17,15 @@ pub struct ReactionMeta {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ReactionMeta {
|
impl ReactionMeta {
|
||||||
/**
|
/// Deletes all reactions for a message.
|
||||||
Deletes all reactions for a message.
|
/// This endpoint requires the `MANAGE_MESSAGES` permission to be present on the current user.
|
||||||
This endpoint requires the `MANAGE_MESSAGES` permission to be present on the current user.
|
/// # Arguments
|
||||||
|
/// * `user` - A mutable reference to a [`UserMeta`] instance.
|
||||||
# Arguments
|
/// # Returns
|
||||||
* `user` - A mutable reference to a [`UserMeta`] instance.
|
/// A `Result` [`()`] [`crate::errors::ChorusLibError`] if something went wrong.
|
||||||
|
/// Fires a `Message Reaction Remove All` Gateway event.
|
||||||
# Returns
|
/// # Reference
|
||||||
A `Result` [`()`] [`crate::errors::ChorusLibError`] if something went wrong.
|
/// See [https://discord.com/developers/docs/resources/channel#delete-all-reactions](https://discord.com/developers/docs/resources/channel#delete-all-reactions)
|
||||||
Fires a `Message Reaction Remove All` Gateway event.
|
|
||||||
|
|
||||||
# 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<()> {
|
pub async fn delete_all(&self, user: &mut UserMeta) -> ChorusResult<()> {
|
||||||
let url = format!(
|
let url = format!(
|
||||||
"{}/channels/{}/messages/{}/reactions/",
|
"{}/channels/{}/messages/{}/reactions/",
|
||||||
|
@ -37,26 +33,24 @@ impl ReactionMeta {
|
||||||
self.channel_id,
|
self.channel_id,
|
||||||
self.message_id
|
self.message_id
|
||||||
);
|
);
|
||||||
let request = Client::new().delete(url).bearer_auth(user.token());
|
let chorus_request = ChorusRequest {
|
||||||
handle_request_as_result(request, user, crate::api::limits::LimitType::Channel).await
|
request: Client::new().delete(url).bearer_auth(user.token()),
|
||||||
|
limit_type: LimitType::Channel(self.channel_id),
|
||||||
|
};
|
||||||
|
chorus_request.handle_request_as_result(user).await
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/// Gets a list of users that reacted with a specific emoji to a message.
|
||||||
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
|
||||||
# Arguments
|
/// the request will fail with 10014: Unknown Emoji. To use custom emoji, you must encode it in the
|
||||||
* `emoji` - A string slice containing the emoji to search for. The emoji must be URL Encoded or
|
/// format name:id with the emoji name and emoji id.
|
||||||
the request will fail with 10014: Unknown Emoji. To use custom emoji, you must encode it in the
|
/// * `user` - A mutable reference to a [`UserMeta`] instance.
|
||||||
format name:id with the emoji name and emoji id.
|
/// # Returns
|
||||||
* `user` - A mutable reference to a [`UserMeta`] instance.
|
/// A Result that is [`Err(crate::errors::ChorusLibError)`] if something went wrong.
|
||||||
|
/// # Reference
|
||||||
# Returns
|
/// See [https://discord.com/developers/docs/resources/channel#get-reactions](https://discord.com/developers/docs/resources/channel#get-reactions)
|
||||||
A Result that is [`Err(crate::errors::ChorusLibError)`] if something went wrong.
|
pub async fn get(&self, emoji: &str, user: &mut UserMeta) -> ChorusResult<Vec<PublicUser>> {
|
||||||
|
|
||||||
# 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<()> {
|
|
||||||
let url = format!(
|
let url = format!(
|
||||||
"{}/channels/{}/messages/{}/reactions/{}/",
|
"{}/channels/{}/messages/{}/reactions/{}/",
|
||||||
user.belongs_to.borrow().urls.api,
|
user.belongs_to.borrow().urls.api,
|
||||||
|
@ -64,27 +58,27 @@ impl ReactionMeta {
|
||||||
self.message_id,
|
self.message_id,
|
||||||
emoji
|
emoji
|
||||||
);
|
);
|
||||||
let request = Client::new().get(url).bearer_auth(user.token());
|
let chorus_request = ChorusRequest {
|
||||||
handle_request_as_result(request, user, crate::api::limits::LimitType::Channel).await
|
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
|
||||||
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.
|
||||||
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
|
||||||
# Arguments
|
/// the request will fail with 10014: Unknown Emoji. To use custom emoji, you must encode it in the
|
||||||
* `emoji` - A string slice containing the emoji to delete. The `emoji` must be URL Encoded or
|
/// format name:id with the emoji name and emoji id.
|
||||||
the request will fail with 10014: Unknown Emoji. To use custom emoji, you must encode it in the
|
/// * `user` - A mutable reference to a [`UserMeta`] instance.
|
||||||
format name:id with the emoji name and emoji id.
|
/// # Returns
|
||||||
* `user` - A mutable reference to a [`UserMeta`] instance.
|
/// A Result that is [`Err(crate::errors::ChorusLibError)`] if something went wrong.
|
||||||
|
/// Fires a `Message Reaction Remove Emoji` Gateway event.
|
||||||
# Returns
|
/// # Reference
|
||||||
A Result that is [`Err(crate::errors::ChorusLibError)`] if something went wrong.
|
/// 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)
|
||||||
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<()> {
|
pub async fn delete_emoji(&self, emoji: &str, user: &mut UserMeta) -> ChorusResult<()> {
|
||||||
let url = format!(
|
let url = format!(
|
||||||
"{}/channels/{}/messages/{}/reactions/{}/",
|
"{}/channels/{}/messages/{}/reactions/{}/",
|
||||||
|
@ -93,29 +87,28 @@ impl ReactionMeta {
|
||||||
self.message_id,
|
self.message_id,
|
||||||
emoji
|
emoji
|
||||||
);
|
);
|
||||||
let request = Client::new().delete(url).bearer_auth(user.token());
|
let chorus_request = ChorusRequest {
|
||||||
handle_request_as_result(request, user, crate::api::limits::LimitType::Channel).await
|
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.
|
||||||
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 endpoint requires the READ_MESSAGE_HISTORY permission
|
/// this emoji, this endpoint requires the ADD_REACTIONS permission to be present on the current
|
||||||
to be present on the current user. Additionally, if nobody else has reacted to the message using
|
/// user.
|
||||||
this emoji, this endpoint requires the ADD_REACTIONS permission to be present on the current
|
/// # Arguments
|
||||||
user.
|
/// * `emoji` - A string slice containing the emoji to delete. The `emoji` must be URL Encoded or
|
||||||
# Arguments
|
/// the request will fail with 10014: Unknown Emoji. To use custom emoji, you must encode it in the
|
||||||
* `emoji` - A string slice containing the emoji to delete. The `emoji` must be URL Encoded or
|
/// format name:id with the emoji name and emoji id.
|
||||||
the request will fail with 10014: Unknown Emoji. To use custom emoji, you must encode it in the
|
/// * `user` - A mutable reference to a [`UserMeta`] instance.
|
||||||
format name:id with the emoji name and emoji id.
|
/// # Returns
|
||||||
* `user` - A mutable reference to a [`UserMeta`] instance.
|
/// A `Result` containing [`()`] or a [`crate::errors::ChorusLibError`].
|
||||||
|
/// # Reference
|
||||||
# Returns
|
/// See [https://discord.com/developers/docs/resources/channel#create-reaction](https://discord.com/developers/docs/resources/channel#create-reaction)
|
||||||
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<()> {
|
pub async fn create(&self, emoji: &str, user: &mut UserMeta) -> ChorusResult<()> {
|
||||||
let url = format!(
|
let url = format!(
|
||||||
"{}/channels/{}/messages/{}/reactions/{}/@me/",
|
"{}/channels/{}/messages/{}/reactions/{}/@me/",
|
||||||
|
@ -124,26 +117,24 @@ impl ReactionMeta {
|
||||||
self.message_id,
|
self.message_id,
|
||||||
emoji
|
emoji
|
||||||
);
|
);
|
||||||
let request = Client::new().put(url).bearer_auth(user.token());
|
let chorus_request = ChorusRequest {
|
||||||
handle_request_as_result(request, user, crate::api::limits::LimitType::Channel).await
|
request: Client::new().put(url).bearer_auth(user.token()),
|
||||||
|
limit_type: LimitType::Channel(self.channel_id),
|
||||||
|
};
|
||||||
|
chorus_request.handle_request_as_result(user).await
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/// Delete a reaction the current user has made for the message.
|
||||||
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
|
||||||
# Arguments
|
/// the request will fail with 10014: Unknown Emoji. To use custom emoji, you must encode it in the
|
||||||
* `emoji` - A string slice containing the emoji to delete. The `emoji` must be URL Encoded or
|
/// format name:id with the emoji name and emoji id.
|
||||||
the request will fail with 10014: Unknown Emoji. To use custom emoji, you must encode it in the
|
/// * `user` - A mutable reference to a [`UserMeta`] instance.
|
||||||
format name:id with the emoji name and emoji id.
|
/// # Returns
|
||||||
* `user` - A mutable reference to a [`UserMeta`] instance.
|
/// A `Result` containing [`()`] or a [`crate::errors::ChorusLibError`].
|
||||||
|
/// Fires a `Message Reaction Remove` Gateway event.
|
||||||
# Returns
|
/// # Reference
|
||||||
A `Result` containing [`()`] or a [`crate::errors::ChorusLibError`].
|
/// See [https://discord.com/developers/docs/resources/channel#delete-own-reaction](https://discord.com/developers/docs/resources/channel#delete-own-reaction)
|
||||||
Fires a `Message Reaction Remove` Gateway event.
|
|
||||||
|
|
||||||
# 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<()> {
|
pub async fn remove(&self, emoji: &str, user: &mut UserMeta) -> ChorusResult<()> {
|
||||||
let url = format!(
|
let url = format!(
|
||||||
"{}/channels/{}/messages/{}/reactions/{}/@me/",
|
"{}/channels/{}/messages/{}/reactions/{}/@me/",
|
||||||
|
@ -152,29 +143,26 @@ impl ReactionMeta {
|
||||||
self.message_id,
|
self.message_id,
|
||||||
emoji
|
emoji
|
||||||
);
|
);
|
||||||
let request = Client::new().delete(url).bearer_auth(user.token());
|
let chorus_request = ChorusRequest {
|
||||||
handle_request_as_result(request, user, crate::api::limits::LimitType::Channel).await
|
request: Client::new().delete(url).bearer_auth(user.token()),
|
||||||
|
limit_type: LimitType::Channel(self.channel_id),
|
||||||
|
};
|
||||||
|
chorus_request.handle_request_as_result(user).await
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/// Delete a user's reaction to a message.
|
||||||
Delete a user's reaction to a message.
|
/// This endpoint requires the MANAGE_MESSAGES permission to be present on the current user.
|
||||||
|
/// # Arguments
|
||||||
This endpoint requires the MANAGE_MESSAGES permission to be present on the current user.
|
/// * `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
|
||||||
# Arguments
|
/// the request will fail with 10014: Unknown Emoji. To use custom emoji, you must encode it in the
|
||||||
* `user_id` - ID of the user whose reaction is to be deleted.
|
/// format name:id with the emoji name and emoji id.
|
||||||
* `emoji` - A string slice containing the emoji to delete. The `emoji` must be URL Encoded or
|
/// * `user` - A mutable reference to a [`UserMeta`] instance.
|
||||||
the request will fail with 10014: Unknown Emoji. To use custom emoji, you must encode it in the
|
/// # Returns
|
||||||
format name:id with the emoji name and emoji id.
|
/// A `Result` containing [`()`] or a [`crate::errors::ChorusLibError`].
|
||||||
* `user` - A mutable reference to a [`UserMeta`] instance.
|
/// Fires a Message Reaction Remove Gateway event.
|
||||||
|
/// # Reference
|
||||||
# Returns
|
/// See [https://discord.com/developers/docs/resources/channel#delete-own-reaction](https://discord.com/developers/docs/resources/channel#delete-own-reaction)
|
||||||
A `Result` containing [`()`] or a [`crate::errors::ChorusLibError`].
|
|
||||||
Fires a Message Reaction Remove Gateway event.
|
|
||||||
|
|
||||||
# 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 delete_user(
|
pub async fn delete_user(
|
||||||
&self,
|
&self,
|
||||||
user_id: Snowflake,
|
user_id: Snowflake,
|
||||||
|
@ -189,7 +177,10 @@ impl ReactionMeta {
|
||||||
emoji,
|
emoji,
|
||||||
user_id
|
user_id
|
||||||
);
|
);
|
||||||
let request = Client::new().delete(url).bearer_auth(user.token());
|
let chorus_request = ChorusRequest {
|
||||||
handle_request_as_result(request, user, crate::api::limits::LimitType::Channel).await
|
request: Client::new().delete(url).bearer_auth(user.token()),
|
||||||
|
limit_type: LimitType::Channel(self.channel_id),
|
||||||
|
};
|
||||||
|
chorus_request.handle_request_as_result(user).await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,72 +0,0 @@
|
||||||
use reqwest::RequestBuilder;
|
|
||||||
use serde::Deserialize;
|
|
||||||
use serde_json::from_str;
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
errors::{ChorusLibError, ChorusResult},
|
|
||||||
instance::UserMeta,
|
|
||||||
limit::LimitedRequester,
|
|
||||||
};
|
|
||||||
|
|
||||||
use super::limits::LimitType;
|
|
||||||
|
|
||||||
/// Sends a request to wherever it needs to go and performs some basic error handling.
|
|
||||||
pub async fn handle_request(
|
|
||||||
request: RequestBuilder,
|
|
||||||
user: &mut UserMeta,
|
|
||||||
limit_type: LimitType,
|
|
||||||
) -> Result<reqwest::Response, crate::errors::ChorusLibError> {
|
|
||||||
LimitedRequester::send_request(
|
|
||||||
request,
|
|
||||||
limit_type,
|
|
||||||
&mut user.belongs_to.borrow_mut(),
|
|
||||||
&mut user.limits,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sends a request to wherever it needs to go. Returns [`Ok(())`] on success and
|
|
||||||
/// [`Err(ChorusLibError)`] on failure.
|
|
||||||
pub async fn handle_request_as_result(
|
|
||||||
request: RequestBuilder,
|
|
||||||
user: &mut UserMeta,
|
|
||||||
limit_type: LimitType,
|
|
||||||
) -> ChorusResult<()> {
|
|
||||||
match handle_request(request, user, limit_type).await {
|
|
||||||
Ok(_) => Ok(()),
|
|
||||||
Err(e) => Err(ChorusLibError::InvalidResponseError {
|
|
||||||
error: e.to_string(),
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn deserialize_response<T: for<'a> Deserialize<'a>>(
|
|
||||||
request: RequestBuilder,
|
|
||||||
user: &mut UserMeta,
|
|
||||||
limit_type: LimitType,
|
|
||||||
) -> ChorusResult<T> {
|
|
||||||
let response = handle_request(request, user, limit_type).await.unwrap();
|
|
||||||
let response_text = match response.text().await {
|
|
||||||
Ok(string) => string,
|
|
||||||
Err(e) => {
|
|
||||||
return Err(ChorusLibError::InvalidResponseError {
|
|
||||||
error: format!(
|
|
||||||
"Error while trying to process the HTTP response into a String: {}",
|
|
||||||
e
|
|
||||||
),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let object = match from_str::<T>(&response_text) {
|
|
||||||
Ok(object) => object,
|
|
||||||
Err(e) => {
|
|
||||||
return Err(ChorusLibError::InvalidResponseError {
|
|
||||||
error: format!(
|
|
||||||
"Error while trying to deserialize the JSON response into T: {}",
|
|
||||||
e
|
|
||||||
),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
};
|
|
||||||
Ok(object)
|
|
||||||
}
|
|
|
@ -2,15 +2,11 @@ use reqwest::Client;
|
||||||
use serde_json::from_str;
|
use serde_json::from_str;
|
||||||
use serde_json::to_string;
|
use serde_json::to_string;
|
||||||
|
|
||||||
use crate::api::deserialize_response;
|
use crate::api::LimitType;
|
||||||
use crate::api::handle_request;
|
use crate::errors::ChorusError;
|
||||||
use crate::api::handle_request_as_result;
|
|
||||||
use crate::api::limits::Limits;
|
|
||||||
use crate::errors::ChorusLibError;
|
|
||||||
use crate::errors::ChorusResult;
|
use crate::errors::ChorusResult;
|
||||||
use crate::instance::Instance;
|
|
||||||
use crate::instance::UserMeta;
|
use crate::instance::UserMeta;
|
||||||
use crate::limit::LimitedRequester;
|
use crate::ratelimiter::ChorusRequest;
|
||||||
use crate::types::Snowflake;
|
use crate::types::Snowflake;
|
||||||
use crate::types::{Channel, ChannelCreateSchema, Guild, GuildCreateSchema};
|
use crate::types::{Channel, ChannelCreateSchema, Guild, GuildCreateSchema};
|
||||||
|
|
||||||
|
@ -36,11 +32,14 @@ impl Guild {
|
||||||
guild_create_schema: GuildCreateSchema,
|
guild_create_schema: GuildCreateSchema,
|
||||||
) -> ChorusResult<Guild> {
|
) -> ChorusResult<Guild> {
|
||||||
let url = format!("{}/guilds/", user.belongs_to.borrow().urls.api);
|
let url = format!("{}/guilds/", user.belongs_to.borrow().urls.api);
|
||||||
let request = reqwest::Client::new()
|
let chorus_request = ChorusRequest {
|
||||||
.post(url.clone())
|
request: Client::new()
|
||||||
.bearer_auth(user.token.clone())
|
.post(url.clone())
|
||||||
.body(to_string(&guild_create_schema).unwrap());
|
.bearer_auth(user.token.clone())
|
||||||
deserialize_response::<Guild>(request, user, crate::api::limits::LimitType::Guild).await
|
.body(to_string(&guild_create_schema).unwrap()),
|
||||||
|
limit_type: LimitType::Global,
|
||||||
|
};
|
||||||
|
chorus_request.deserialize_response::<Guild>(user).await
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Deletes a guild.
|
/// Deletes a guild.
|
||||||
|
@ -73,10 +72,13 @@ impl Guild {
|
||||||
user.belongs_to.borrow().urls.api,
|
user.belongs_to.borrow().urls.api,
|
||||||
guild_id
|
guild_id
|
||||||
);
|
);
|
||||||
let request = reqwest::Client::new()
|
let chorus_request = ChorusRequest {
|
||||||
.post(url.clone())
|
request: Client::new()
|
||||||
.bearer_auth(user.token.clone());
|
.post(url.clone())
|
||||||
handle_request_as_result(request, user, crate::api::limits::LimitType::Guild).await
|
.bearer_auth(user.token.clone()),
|
||||||
|
limit_type: LimitType::Global,
|
||||||
|
};
|
||||||
|
chorus_request.handle_request_as_result(user).await
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sends a request to create a new channel in the guild.
|
/// Sends a request to create a new channel in the guild.
|
||||||
|
@ -97,14 +99,7 @@ impl Guild {
|
||||||
user: &mut UserMeta,
|
user: &mut UserMeta,
|
||||||
schema: ChannelCreateSchema,
|
schema: ChannelCreateSchema,
|
||||||
) -> ChorusResult<Channel> {
|
) -> ChorusResult<Channel> {
|
||||||
Channel::_create(
|
Channel::create(user, self.id, schema).await
|
||||||
&user.token,
|
|
||||||
self.id,
|
|
||||||
schema,
|
|
||||||
&mut user.limits,
|
|
||||||
&mut user.belongs_to.borrow_mut(),
|
|
||||||
)
|
|
||||||
.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 `Result` containing a vector of `Channel` structs if the request was successful, or an `ChorusLibError` if there was an error.
|
||||||
|
@ -117,20 +112,21 @@ impl Guild {
|
||||||
/// * `limits_instance` - A mutable reference to a `Limits` struct containing the instance'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>> {
|
pub async fn channels(&self, user: &mut UserMeta) -> ChorusResult<Vec<Channel>> {
|
||||||
let request = Client::new()
|
let chorus_request = ChorusRequest {
|
||||||
.get(format!(
|
request: Client::new()
|
||||||
"{}/guilds/{}/channels/",
|
.get(format!(
|
||||||
user.belongs_to.borrow().urls.api,
|
"{}/guilds/{}/channels/",
|
||||||
self.id
|
user.belongs_to.borrow().urls.api,
|
||||||
))
|
self.id
|
||||||
.bearer_auth(user.token());
|
))
|
||||||
let result = handle_request(request, user, crate::api::limits::LimitType::Channel)
|
.bearer_auth(user.token()),
|
||||||
.await
|
limit_type: LimitType::Channel(self.id),
|
||||||
.unwrap();
|
};
|
||||||
|
let result = chorus_request.send_request(user).await?;
|
||||||
let stringed_response = match result.text().await {
|
let stringed_response = match result.text().await {
|
||||||
Ok(value) => value,
|
Ok(value) => value,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
return Err(ChorusLibError::InvalidResponseError {
|
return Err(ChorusError::InvalidResponse {
|
||||||
error: e.to_string(),
|
error: e.to_string(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -138,7 +134,7 @@ impl Guild {
|
||||||
let _: Vec<Channel> = match from_str(&stringed_response) {
|
let _: Vec<Channel> = match from_str(&stringed_response) {
|
||||||
Ok(result) => return Ok(result),
|
Ok(result) => return Ok(result),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
return Err(ChorusLibError::InvalidResponseError {
|
return Err(ChorusError::InvalidResponse {
|
||||||
error: e.to_string(),
|
error: e.to_string(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -155,35 +151,19 @@ impl Guild {
|
||||||
/// * `limits_user` - A mutable reference to a `Limits` struct containing the user's rate limits.
|
/// * `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.
|
/// * `limits_instance` - A mutable reference to a `Limits` struct containing the instance's rate limits.
|
||||||
///
|
///
|
||||||
pub async fn get(user: &mut UserMeta, guild_id: Snowflake) -> ChorusResult<Guild> {
|
pub async fn get(guild_id: Snowflake, user: &mut UserMeta) -> ChorusResult<Guild> {
|
||||||
let mut belongs_to = user.belongs_to.borrow_mut();
|
let chorus_request = ChorusRequest {
|
||||||
Guild::_get(guild_id, &user.token, &mut user.limits, &mut belongs_to).await
|
request: Client::new()
|
||||||
}
|
.get(format!(
|
||||||
|
"{}/guilds/{}/",
|
||||||
/// For internal use. Does the same as the public get method, but does not require a second, mutable
|
user.belongs_to.borrow().urls.api,
|
||||||
/// borrow of `UserMeta::belongs_to`, when used in conjunction with other methods, which borrow `UserMeta::belongs_to`.
|
guild_id
|
||||||
async fn _get(
|
))
|
||||||
guild_id: Snowflake,
|
.bearer_auth(user.token()),
|
||||||
token: &str,
|
limit_type: LimitType::Guild(guild_id),
|
||||||
limits_user: &mut Limits,
|
|
||||||
instance: &mut Instance,
|
|
||||||
) -> ChorusResult<Guild> {
|
|
||||||
let request = Client::new()
|
|
||||||
.get(format!("{}/guilds/{}/", instance.urls.api, guild_id))
|
|
||||||
.bearer_auth(token);
|
|
||||||
let response = match LimitedRequester::send_request(
|
|
||||||
request,
|
|
||||||
crate::api::limits::LimitType::Guild,
|
|
||||||
instance,
|
|
||||||
limits_user,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
Ok(response) => response,
|
|
||||||
Err(e) => return Err(e),
|
|
||||||
};
|
};
|
||||||
let guild: Guild = from_str(&response.text().await.unwrap()).unwrap();
|
let response = chorus_request.deserialize_response::<Guild>(user).await?;
|
||||||
Ok(guild)
|
Ok(response)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -207,48 +187,17 @@ impl Channel {
|
||||||
guild_id: Snowflake,
|
guild_id: Snowflake,
|
||||||
schema: ChannelCreateSchema,
|
schema: ChannelCreateSchema,
|
||||||
) -> ChorusResult<Channel> {
|
) -> ChorusResult<Channel> {
|
||||||
let mut belongs_to = user.belongs_to.borrow_mut();
|
let chorus_request = ChorusRequest {
|
||||||
Channel::_create(
|
request: Client::new()
|
||||||
&user.token,
|
.post(format!(
|
||||||
guild_id,
|
"{}/guilds/{}/channels/",
|
||||||
schema,
|
user.belongs_to.borrow().urls.api,
|
||||||
&mut user.limits,
|
guild_id
|
||||||
&mut belongs_to,
|
))
|
||||||
)
|
.bearer_auth(user.token())
|
||||||
.await
|
.body(to_string(&schema).unwrap()),
|
||||||
}
|
limit_type: LimitType::Guild(guild_id),
|
||||||
|
|
||||||
async fn _create(
|
|
||||||
token: &str,
|
|
||||||
guild_id: Snowflake,
|
|
||||||
schema: ChannelCreateSchema,
|
|
||||||
limits_user: &mut Limits,
|
|
||||||
instance: &mut Instance,
|
|
||||||
) -> ChorusResult<Channel> {
|
|
||||||
let request = Client::new()
|
|
||||||
.post(format!(
|
|
||||||
"{}/guilds/{}/channels/",
|
|
||||||
instance.urls.api, guild_id
|
|
||||||
))
|
|
||||||
.bearer_auth(token)
|
|
||||||
.body(to_string(&schema).unwrap());
|
|
||||||
let result = match LimitedRequester::send_request(
|
|
||||||
request,
|
|
||||||
crate::api::limits::LimitType::Guild,
|
|
||||||
instance,
|
|
||||||
limits_user,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
Ok(result) => result,
|
|
||||||
Err(e) => return Err(e),
|
|
||||||
};
|
};
|
||||||
match from_str::<Channel>(&result.text().await.unwrap()) {
|
chorus_request.deserialize_response::<Channel>(user).await
|
||||||
Ok(object) => Ok(object),
|
|
||||||
Err(e) => Err(ChorusLibError::RequestErrorError {
|
|
||||||
url: format!("{}/guilds/{}/channels/", instance.urls.api, guild_id),
|
|
||||||
error: e.to_string(),
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
use reqwest::Client;
|
use reqwest::Client;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
api::{deserialize_response, handle_request_as_result},
|
api::LimitType,
|
||||||
errors::ChorusResult,
|
errors::ChorusResult,
|
||||||
instance::UserMeta,
|
instance::UserMeta,
|
||||||
|
ratelimiter::ChorusRequest,
|
||||||
types::{self, Snowflake},
|
types::{self, Snowflake},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -30,13 +31,13 @@ impl types::GuildMember {
|
||||||
guild_id,
|
guild_id,
|
||||||
member_id
|
member_id
|
||||||
);
|
);
|
||||||
let request = Client::new().get(url).bearer_auth(user.token());
|
let chorus_request = ChorusRequest {
|
||||||
deserialize_response::<types::GuildMember>(
|
request: Client::new().get(url).bearer_auth(user.token()),
|
||||||
request,
|
limit_type: LimitType::Guild(guild_id),
|
||||||
user,
|
};
|
||||||
crate::api::limits::LimitType::Guild,
|
chorus_request
|
||||||
)
|
.deserialize_response::<types::GuildMember>(user)
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Adds a role to a guild member.
|
/// Adds a role to a guild member.
|
||||||
|
@ -64,8 +65,11 @@ impl types::GuildMember {
|
||||||
member_id,
|
member_id,
|
||||||
role_id
|
role_id
|
||||||
);
|
);
|
||||||
let request = Client::new().put(url).bearer_auth(user.token());
|
let chorus_request = ChorusRequest {
|
||||||
handle_request_as_result(request, user, crate::api::limits::LimitType::Guild).await
|
request: Client::new().put(url).bearer_auth(user.token()),
|
||||||
|
limit_type: LimitType::Guild(guild_id),
|
||||||
|
};
|
||||||
|
chorus_request.handle_request_as_result(user).await
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Removes a role from a guild member.
|
/// Removes a role from a guild member.
|
||||||
|
@ -85,7 +89,7 @@ impl types::GuildMember {
|
||||||
guild_id: Snowflake,
|
guild_id: Snowflake,
|
||||||
member_id: Snowflake,
|
member_id: Snowflake,
|
||||||
role_id: Snowflake,
|
role_id: Snowflake,
|
||||||
) -> Result<(), crate::errors::ChorusLibError> {
|
) -> Result<(), crate::errors::ChorusError> {
|
||||||
let url = format!(
|
let url = format!(
|
||||||
"{}/guilds/{}/members/{}/roles/{}/",
|
"{}/guilds/{}/members/{}/roles/{}/",
|
||||||
user.belongs_to.borrow().urls.api,
|
user.belongs_to.borrow().urls.api,
|
||||||
|
@ -93,7 +97,10 @@ impl types::GuildMember {
|
||||||
member_id,
|
member_id,
|
||||||
role_id
|
role_id
|
||||||
);
|
);
|
||||||
let request = Client::new().delete(url).bearer_auth(user.token());
|
let chorus_request = ChorusRequest {
|
||||||
handle_request_as_result(request, user, crate::api::limits::LimitType::Guild).await
|
request: Client::new().delete(url).bearer_auth(user.token()),
|
||||||
|
limit_type: LimitType::Guild(guild_id),
|
||||||
|
};
|
||||||
|
chorus_request.handle_request_as_result(user).await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,9 +2,10 @@ use reqwest::Client;
|
||||||
use serde_json::to_string;
|
use serde_json::to_string;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
api::deserialize_response,
|
api::LimitType,
|
||||||
errors::{ChorusLibError, ChorusResult},
|
errors::{ChorusError, ChorusResult},
|
||||||
instance::UserMeta,
|
instance::UserMeta,
|
||||||
|
ratelimiter::ChorusRequest,
|
||||||
types::{self, RoleCreateModifySchema, RoleObject, Snowflake},
|
types::{self, RoleCreateModifySchema, RoleObject, Snowflake},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -32,14 +33,14 @@ impl types::RoleObject {
|
||||||
user.belongs_to.borrow().urls.api,
|
user.belongs_to.borrow().urls.api,
|
||||||
guild_id
|
guild_id
|
||||||
);
|
);
|
||||||
let request = Client::new().get(url).bearer_auth(user.token());
|
let chorus_request = ChorusRequest {
|
||||||
let roles = deserialize_response::<Vec<RoleObject>>(
|
request: Client::new().get(url).bearer_auth(user.token()),
|
||||||
request,
|
limit_type: LimitType::Guild(guild_id),
|
||||||
user,
|
};
|
||||||
crate::api::limits::LimitType::Guild,
|
let roles = chorus_request
|
||||||
)
|
.deserialize_response::<Vec<RoleObject>>(user)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
if roles.is_empty() {
|
if roles.is_empty() {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
|
@ -72,8 +73,13 @@ impl types::RoleObject {
|
||||||
guild_id,
|
guild_id,
|
||||||
role_id
|
role_id
|
||||||
);
|
);
|
||||||
let request = Client::new().get(url).bearer_auth(user.token());
|
let chorus_request = ChorusRequest {
|
||||||
deserialize_response(request, user, crate::api::limits::LimitType::Guild).await
|
request: Client::new().get(url).bearer_auth(user.token()),
|
||||||
|
limit_type: LimitType::Guild(guild_id),
|
||||||
|
};
|
||||||
|
chorus_request
|
||||||
|
.deserialize_response::<RoleObject>(user)
|
||||||
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a new role for a given guild.
|
/// Creates a new role for a given guild.
|
||||||
|
@ -102,12 +108,17 @@ impl types::RoleObject {
|
||||||
guild_id
|
guild_id
|
||||||
);
|
);
|
||||||
let body = to_string::<RoleCreateModifySchema>(&role_create_schema).map_err(|e| {
|
let body = to_string::<RoleCreateModifySchema>(&role_create_schema).map_err(|e| {
|
||||||
ChorusLibError::FormCreationError {
|
ChorusError::FormCreation {
|
||||||
error: e.to_string(),
|
error: e.to_string(),
|
||||||
}
|
}
|
||||||
})?;
|
})?;
|
||||||
let request = Client::new().post(url).bearer_auth(user.token()).body(body);
|
let chorus_request = ChorusRequest {
|
||||||
deserialize_response(request, user, crate::api::limits::LimitType::Guild).await
|
request: Client::new().post(url).bearer_auth(user.token()).body(body),
|
||||||
|
limit_type: LimitType::Guild(guild_id),
|
||||||
|
};
|
||||||
|
chorus_request
|
||||||
|
.deserialize_response::<RoleObject>(user)
|
||||||
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Updates the position of a role in the guild's hierarchy.
|
/// Updates the position of a role in the guild's hierarchy.
|
||||||
|
@ -135,16 +146,19 @@ impl types::RoleObject {
|
||||||
user.belongs_to.borrow().urls.api,
|
user.belongs_to.borrow().urls.api,
|
||||||
guild_id
|
guild_id
|
||||||
);
|
);
|
||||||
let body = to_string(&role_position_update_schema).map_err(|e| {
|
let body =
|
||||||
ChorusLibError::FormCreationError {
|
to_string(&role_position_update_schema).map_err(|e| ChorusError::FormCreation {
|
||||||
error: e.to_string(),
|
error: e.to_string(),
|
||||||
}
|
})?;
|
||||||
})?;
|
let chorus_request = ChorusRequest {
|
||||||
let request = Client::new()
|
request: Client::new()
|
||||||
.patch(url)
|
.patch(url)
|
||||||
.bearer_auth(user.token())
|
.bearer_auth(user.token())
|
||||||
.body(body);
|
.body(body),
|
||||||
deserialize_response::<RoleObject>(request, user, crate::api::limits::LimitType::Guild)
|
limit_type: LimitType::Guild(guild_id),
|
||||||
|
};
|
||||||
|
chorus_request
|
||||||
|
.deserialize_response::<RoleObject>(user)
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -177,15 +191,19 @@ impl types::RoleObject {
|
||||||
role_id
|
role_id
|
||||||
);
|
);
|
||||||
let body = to_string::<RoleCreateModifySchema>(&role_create_schema).map_err(|e| {
|
let body = to_string::<RoleCreateModifySchema>(&role_create_schema).map_err(|e| {
|
||||||
ChorusLibError::FormCreationError {
|
ChorusError::FormCreation {
|
||||||
error: e.to_string(),
|
error: e.to_string(),
|
||||||
}
|
}
|
||||||
})?;
|
})?;
|
||||||
let request = Client::new()
|
let chorus_request = ChorusRequest {
|
||||||
.patch(url)
|
request: Client::new()
|
||||||
.bearer_auth(user.token())
|
.patch(url)
|
||||||
.body(body);
|
.bearer_auth(user.token())
|
||||||
deserialize_response::<RoleObject>(request, user, crate::api::limits::LimitType::Guild)
|
.body(body),
|
||||||
|
limit_type: LimitType::Guild(guild_id),
|
||||||
|
};
|
||||||
|
chorus_request
|
||||||
|
.deserialize_response::<RoleObject>(user)
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,73 @@
|
||||||
|
use reqwest::Client;
|
||||||
|
use serde_json::to_string;
|
||||||
|
|
||||||
|
use crate::errors::ChorusResult;
|
||||||
|
use crate::instance::UserMeta;
|
||||||
|
use crate::ratelimiter::ChorusRequest;
|
||||||
|
use crate::types::{CreateChannelInviteSchema, GuildInvite, Invite, 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.
|
||||||
|
///
|
||||||
|
/// # Reference:
|
||||||
|
/// Read <https://discord-userdoccers.vercel.app/resources/invite#accept-invite>
|
||||||
|
pub async fn accept_invite(
|
||||||
|
&mut self,
|
||||||
|
invite_code: &str,
|
||||||
|
session_id: Option<&str>,
|
||||||
|
) -> ChorusResult<Invite> {
|
||||||
|
let mut request = ChorusRequest {
|
||||||
|
request: Client::new()
|
||||||
|
.post(format!(
|
||||||
|
"{}/invites/{}/",
|
||||||
|
self.belongs_to.borrow().urls.api,
|
||||||
|
invite_code
|
||||||
|
))
|
||||||
|
.bearer_auth(self.token()),
|
||||||
|
limit_type: super::LimitType::Global,
|
||||||
|
};
|
||||||
|
if session_id.is_some() {
|
||||||
|
request.request = request
|
||||||
|
.request
|
||||||
|
.body(to_string(session_id.unwrap()).unwrap());
|
||||||
|
}
|
||||||
|
request.deserialize_response::<Invite>(self).await
|
||||||
|
}
|
||||||
|
/// Note: Spacebar does not yet implement this endpoint.
|
||||||
|
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
|
||||||
|
))
|
||||||
|
.body(to_string(&code).unwrap())
|
||||||
|
.bearer_auth(self.token()),
|
||||||
|
limit_type: super::LimitType::Global,
|
||||||
|
}
|
||||||
|
.deserialize_response::<Invite>(self)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn create_guild_invite(
|
||||||
|
&mut self,
|
||||||
|
create_channel_invite_schema: CreateChannelInviteSchema,
|
||||||
|
channel_id: Snowflake,
|
||||||
|
) -> ChorusResult<GuildInvite> {
|
||||||
|
ChorusRequest {
|
||||||
|
request: Client::new()
|
||||||
|
.post(format!(
|
||||||
|
"{}/channels/{}/invites/",
|
||||||
|
self.belongs_to.borrow().urls.api,
|
||||||
|
channel_id
|
||||||
|
))
|
||||||
|
.bearer_auth(self.token())
|
||||||
|
.body(to_string(&create_channel_invite_schema).unwrap()),
|
||||||
|
limit_type: super::LimitType::Channel(channel_id),
|
||||||
|
}
|
||||||
|
.deserialize_response::<GuildInvite>(self)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,12 +1,13 @@
|
||||||
pub use channels::messages::*;
|
pub use channels::messages::*;
|
||||||
pub use common::*;
|
|
||||||
pub use guilds::*;
|
pub use guilds::*;
|
||||||
|
pub use invites::*;
|
||||||
pub use policies::instance::instance::*;
|
pub use policies::instance::instance::*;
|
||||||
pub use policies::instance::limits::*;
|
pub use policies::instance::ratelimits::*;
|
||||||
|
pub use users::*;
|
||||||
|
|
||||||
pub mod auth;
|
pub mod auth;
|
||||||
pub mod channels;
|
pub mod channels;
|
||||||
pub mod common;
|
|
||||||
pub mod guilds;
|
pub mod guilds;
|
||||||
|
pub mod invites;
|
||||||
pub mod policies;
|
pub mod policies;
|
||||||
pub mod users;
|
pub mod users;
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
use reqwest::Client;
|
|
||||||
use serde_json::from_str;
|
use serde_json::from_str;
|
||||||
|
|
||||||
use crate::errors::{ChorusLibError, ChorusResult};
|
use crate::errors::{ChorusError, ChorusResult};
|
||||||
use crate::instance::Instance;
|
use crate::instance::Instance;
|
||||||
use crate::types::GeneralConfiguration;
|
use crate::types::GeneralConfiguration;
|
||||||
|
|
||||||
|
@ -10,21 +9,21 @@ impl Instance {
|
||||||
/// # Errors
|
/// # Errors
|
||||||
/// [`ChorusLibError`] - If the request fails.
|
/// [`ChorusLibError`] - If the request fails.
|
||||||
pub async fn general_configuration_schema(&self) -> ChorusResult<GeneralConfiguration> {
|
pub async fn general_configuration_schema(&self) -> ChorusResult<GeneralConfiguration> {
|
||||||
let client = Client::new();
|
|
||||||
let endpoint_url = self.urls.api.clone() + "/policies/instance/";
|
let endpoint_url = self.urls.api.clone() + "/policies/instance/";
|
||||||
let request = match client.get(&endpoint_url).send().await {
|
let request = match self.client.get(&endpoint_url).send().await {
|
||||||
Ok(result) => result,
|
Ok(result) => result,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
return Err(ChorusLibError::RequestErrorError {
|
return Err(ChorusError::RequestFailed {
|
||||||
url: endpoint_url,
|
url: endpoint_url,
|
||||||
error: e.to_string(),
|
error: e,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if !request.status().as_str().starts_with('2') {
|
if !request.status().as_str().starts_with('2') {
|
||||||
return Err(ChorusLibError::ReceivedErrorCodeError {
|
return Err(ChorusError::ReceivedErrorCode {
|
||||||
error_code: request.status().to_string(),
|
error_code: request.status().as_u16(),
|
||||||
|
error: request.text().await.unwrap(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,499 +0,0 @@
|
||||||
pub mod limits {
|
|
||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
use reqwest::Client;
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use serde_json::from_str;
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Eq, Hash, PartialEq, Debug, Default)]
|
|
||||||
pub enum LimitType {
|
|
||||||
AuthRegister,
|
|
||||||
AuthLogin,
|
|
||||||
AbsoluteMessage,
|
|
||||||
AbsoluteRegister,
|
|
||||||
#[default]
|
|
||||||
Global,
|
|
||||||
Ip,
|
|
||||||
Channel,
|
|
||||||
Error,
|
|
||||||
Guild,
|
|
||||||
Webhook,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ToString for LimitType {
|
|
||||||
fn to_string(&self) -> String {
|
|
||||||
match self {
|
|
||||||
LimitType::AuthRegister => "AuthRegister".to_string(),
|
|
||||||
LimitType::AuthLogin => "AuthLogin".to_string(),
|
|
||||||
LimitType::AbsoluteMessage => "AbsoluteMessage".to_string(),
|
|
||||||
LimitType::AbsoluteRegister => "AbsoluteRegister".to_string(),
|
|
||||||
LimitType::Global => "Global".to_string(),
|
|
||||||
LimitType::Ip => "Ip".to_string(),
|
|
||||||
LimitType::Channel => "Channel".to_string(),
|
|
||||||
LimitType::Error => "Error".to_string(),
|
|
||||||
LimitType::Guild => "Guild".to_string(),
|
|
||||||
LimitType::Webhook => "Webhook".to_string(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize)]
|
|
||||||
#[allow(non_snake_case)]
|
|
||||||
pub struct User {
|
|
||||||
pub maxGuilds: u64,
|
|
||||||
pub maxUsername: u64,
|
|
||||||
pub maxFriends: u64,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize)]
|
|
||||||
#[allow(non_snake_case)]
|
|
||||||
pub struct Guild {
|
|
||||||
pub maxRoles: u64,
|
|
||||||
pub maxEmojis: u64,
|
|
||||||
pub maxMembers: u64,
|
|
||||||
pub maxChannels: u64,
|
|
||||||
pub maxChannelsInCategory: u64,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize)]
|
|
||||||
#[allow(non_snake_case)]
|
|
||||||
pub struct Message {
|
|
||||||
pub maxCharacters: u64,
|
|
||||||
pub maxTTSCharacters: u64,
|
|
||||||
pub maxReactions: u64,
|
|
||||||
pub maxAttachmentSize: u64,
|
|
||||||
pub maxBulkDelete: u64,
|
|
||||||
pub maxEmbedDownloadSize: u64,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize)]
|
|
||||||
#[allow(non_snake_case)]
|
|
||||||
pub struct Channel {
|
|
||||||
pub maxPins: u64,
|
|
||||||
pub maxTopic: u64,
|
|
||||||
pub maxWebhooks: u64,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize)]
|
|
||||||
pub struct Rate {
|
|
||||||
pub enabled: bool,
|
|
||||||
pub ip: Window,
|
|
||||||
pub global: Window,
|
|
||||||
pub error: Window,
|
|
||||||
pub routes: Routes,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize)]
|
|
||||||
pub struct Window {
|
|
||||||
pub count: u64,
|
|
||||||
pub window: u64,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize)]
|
|
||||||
pub struct Routes {
|
|
||||||
pub guild: Window,
|
|
||||||
pub webhook: Window,
|
|
||||||
pub channel: Window,
|
|
||||||
pub auth: AuthRoutes,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize)]
|
|
||||||
#[allow(non_snake_case)]
|
|
||||||
pub struct AuthRoutes {
|
|
||||||
pub login: Window,
|
|
||||||
pub register: Window,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize)]
|
|
||||||
#[allow(non_snake_case)]
|
|
||||||
pub struct AbsoluteRate {
|
|
||||||
pub register: AbsoluteWindow,
|
|
||||||
pub sendMessage: AbsoluteWindow,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize)]
|
|
||||||
pub struct AbsoluteWindow {
|
|
||||||
pub limit: u64,
|
|
||||||
pub window: u64,
|
|
||||||
pub enabled: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize)]
|
|
||||||
#[allow(non_snake_case)]
|
|
||||||
pub struct Config {
|
|
||||||
pub user: User,
|
|
||||||
pub guild: Guild,
|
|
||||||
pub message: Message,
|
|
||||||
pub channel: Channel,
|
|
||||||
pub rate: Rate,
|
|
||||||
pub absoluteRate: AbsoluteRate,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
|
|
||||||
pub struct Limit {
|
|
||||||
pub bucket: LimitType,
|
|
||||||
pub limit: u64,
|
|
||||||
pub remaining: u64,
|
|
||||||
pub reset: u64,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::fmt::Display for Limit {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
write!(
|
|
||||||
f,
|
|
||||||
"Bucket: {:?}, Limit: {}, Remaining: {}, Reset: {}",
|
|
||||||
self.bucket, self.limit, self.remaining, self.reset
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Limit {
|
|
||||||
pub fn add_remaining(&mut self, remaining: i64) {
|
|
||||||
if remaining < 0 {
|
|
||||||
if (self.remaining as i64 + remaining) <= 0 {
|
|
||||||
self.remaining = 0;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
self.remaining -= remaining.unsigned_abs();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
self.remaining += remaining.unsigned_abs();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct LimitsMutRef<'a> {
|
|
||||||
pub limit_absolute_messages: &'a mut Limit,
|
|
||||||
pub limit_absolute_register: &'a mut Limit,
|
|
||||||
pub limit_auth_login: &'a mut Limit,
|
|
||||||
pub limit_auth_register: &'a mut Limit,
|
|
||||||
pub limit_ip: &'a mut Limit,
|
|
||||||
pub limit_global: &'a mut Limit,
|
|
||||||
pub limit_error: &'a mut Limit,
|
|
||||||
pub limit_guild: &'a mut Limit,
|
|
||||||
pub limit_webhook: &'a mut Limit,
|
|
||||||
pub limit_channel: &'a mut Limit,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl LimitsMutRef<'_> {
|
|
||||||
pub fn combine_mut_ref<'a>(
|
|
||||||
instance_rate_limits: &'a mut Limits,
|
|
||||||
user_rate_limits: &'a mut Limits,
|
|
||||||
) -> LimitsMutRef<'a> {
|
|
||||||
LimitsMutRef {
|
|
||||||
limit_absolute_messages: &mut instance_rate_limits.limit_absolute_messages,
|
|
||||||
limit_absolute_register: &mut instance_rate_limits.limit_absolute_register,
|
|
||||||
limit_auth_login: &mut instance_rate_limits.limit_auth_login,
|
|
||||||
limit_auth_register: &mut instance_rate_limits.limit_auth_register,
|
|
||||||
limit_channel: &mut user_rate_limits.limit_channel,
|
|
||||||
limit_error: &mut user_rate_limits.limit_error,
|
|
||||||
limit_global: &mut instance_rate_limits.limit_global,
|
|
||||||
limit_guild: &mut user_rate_limits.limit_guild,
|
|
||||||
limit_ip: &mut instance_rate_limits.limit_ip,
|
|
||||||
limit_webhook: &mut user_rate_limits.limit_webhook,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_limit_ref(&self, limit_type: &LimitType) -> &Limit {
|
|
||||||
match limit_type {
|
|
||||||
LimitType::AbsoluteMessage => self.limit_absolute_messages,
|
|
||||||
LimitType::AbsoluteRegister => self.limit_absolute_register,
|
|
||||||
LimitType::AuthLogin => self.limit_auth_login,
|
|
||||||
LimitType::AuthRegister => self.limit_auth_register,
|
|
||||||
LimitType::Channel => self.limit_channel,
|
|
||||||
LimitType::Error => self.limit_error,
|
|
||||||
LimitType::Global => self.limit_global,
|
|
||||||
LimitType::Guild => self.limit_guild,
|
|
||||||
LimitType::Ip => self.limit_ip,
|
|
||||||
LimitType::Webhook => self.limit_webhook,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_limit_mut_ref(&mut self, limit_type: &LimitType) -> &mut Limit {
|
|
||||||
match limit_type {
|
|
||||||
LimitType::AbsoluteMessage => self.limit_absolute_messages,
|
|
||||||
LimitType::AbsoluteRegister => self.limit_absolute_register,
|
|
||||||
LimitType::AuthLogin => self.limit_auth_login,
|
|
||||||
LimitType::AuthRegister => self.limit_auth_register,
|
|
||||||
LimitType::Channel => self.limit_channel,
|
|
||||||
LimitType::Error => self.limit_error,
|
|
||||||
LimitType::Global => self.limit_global,
|
|
||||||
LimitType::Guild => self.limit_guild,
|
|
||||||
LimitType::Ip => self.limit_ip,
|
|
||||||
LimitType::Webhook => self.limit_webhook,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Default)]
|
|
||||||
pub struct Limits {
|
|
||||||
pub limit_absolute_messages: Limit,
|
|
||||||
pub limit_absolute_register: Limit,
|
|
||||||
pub limit_auth_login: Limit,
|
|
||||||
pub limit_auth_register: Limit,
|
|
||||||
pub limit_ip: Limit,
|
|
||||||
pub limit_global: Limit,
|
|
||||||
pub limit_error: Limit,
|
|
||||||
pub limit_guild: Limit,
|
|
||||||
pub limit_webhook: Limit,
|
|
||||||
pub limit_channel: Limit,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Limits {
|
|
||||||
pub fn combine(instance_rate_limits: &Limits, user_rate_limits: &Limits) -> Limits {
|
|
||||||
Limits {
|
|
||||||
limit_absolute_messages: instance_rate_limits.limit_absolute_messages,
|
|
||||||
limit_absolute_register: instance_rate_limits.limit_absolute_register,
|
|
||||||
limit_auth_login: instance_rate_limits.limit_auth_login,
|
|
||||||
limit_auth_register: instance_rate_limits.limit_auth_register,
|
|
||||||
limit_channel: user_rate_limits.limit_channel,
|
|
||||||
limit_error: user_rate_limits.limit_error,
|
|
||||||
limit_global: instance_rate_limits.limit_global,
|
|
||||||
limit_guild: user_rate_limits.limit_guild,
|
|
||||||
limit_ip: instance_rate_limits.limit_ip,
|
|
||||||
limit_webhook: user_rate_limits.limit_webhook,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_limit_ref(&self, limit_type: &LimitType) -> &Limit {
|
|
||||||
match limit_type {
|
|
||||||
LimitType::AbsoluteMessage => &self.limit_absolute_messages,
|
|
||||||
LimitType::AbsoluteRegister => &self.limit_absolute_register,
|
|
||||||
LimitType::AuthLogin => &self.limit_auth_login,
|
|
||||||
LimitType::AuthRegister => &self.limit_auth_register,
|
|
||||||
LimitType::Channel => &self.limit_channel,
|
|
||||||
LimitType::Error => &self.limit_error,
|
|
||||||
LimitType::Global => &self.limit_global,
|
|
||||||
LimitType::Guild => &self.limit_guild,
|
|
||||||
LimitType::Ip => &self.limit_ip,
|
|
||||||
LimitType::Webhook => &self.limit_webhook,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_limit_mut_ref(&mut self, limit_type: &LimitType) -> &mut Limit {
|
|
||||||
match limit_type {
|
|
||||||
LimitType::AbsoluteMessage => &mut self.limit_absolute_messages,
|
|
||||||
LimitType::AbsoluteRegister => &mut self.limit_absolute_register,
|
|
||||||
LimitType::AuthLogin => &mut self.limit_auth_login,
|
|
||||||
LimitType::AuthRegister => &mut self.limit_auth_register,
|
|
||||||
LimitType::Channel => &mut self.limit_channel,
|
|
||||||
LimitType::Error => &mut self.limit_error,
|
|
||||||
LimitType::Global => &mut self.limit_global,
|
|
||||||
LimitType::Guild => &mut self.limit_guild,
|
|
||||||
LimitType::Ip => &mut self.limit_ip,
|
|
||||||
LimitType::Webhook => &mut self.limit_webhook,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn to_hash_map(&self) -> HashMap<LimitType, Limit> {
|
|
||||||
let mut map: HashMap<LimitType, Limit> = HashMap::new();
|
|
||||||
map.insert(LimitType::AbsoluteMessage, self.limit_absolute_messages);
|
|
||||||
map.insert(LimitType::AbsoluteRegister, self.limit_absolute_register);
|
|
||||||
map.insert(LimitType::AuthLogin, self.limit_auth_login);
|
|
||||||
map.insert(LimitType::AuthRegister, self.limit_auth_register);
|
|
||||||
map.insert(LimitType::Ip, self.limit_ip);
|
|
||||||
map.insert(LimitType::Global, self.limit_global);
|
|
||||||
map.insert(LimitType::Error, self.limit_error);
|
|
||||||
map.insert(LimitType::Guild, self.limit_guild);
|
|
||||||
map.insert(LimitType::Webhook, self.limit_webhook);
|
|
||||||
map.insert(LimitType::Channel, self.limit_channel);
|
|
||||||
map
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_as_mut(&mut self) -> &mut Limits {
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// check_limits uses the API to get the current request limits of the instance.
|
|
||||||
/// It returns a `Limits` struct containing all the limits.
|
|
||||||
/// If the rate limit is disabled, then the limit is set to `u64::MAX`.
|
|
||||||
/// # Errors
|
|
||||||
/// This function will panic if the request fails or if the response body cannot be parsed.
|
|
||||||
/// TODO: Change this to return a Result and handle the errors properly.
|
|
||||||
pub async fn check_limits(api_url: String) -> Limits {
|
|
||||||
let client = Client::new();
|
|
||||||
let url_parsed = crate::UrlBundle::parse_url(api_url) + "/policies/instance/limits";
|
|
||||||
let result = client
|
|
||||||
.get(url_parsed)
|
|
||||||
.send()
|
|
||||||
.await
|
|
||||||
.unwrap_or_else(|e| panic!("An error occured while performing the request: {}", e))
|
|
||||||
.text()
|
|
||||||
.await
|
|
||||||
.unwrap_or_else(|e| {
|
|
||||||
panic!(
|
|
||||||
"An error occured while parsing the request body string: {}",
|
|
||||||
e
|
|
||||||
)
|
|
||||||
});
|
|
||||||
let config: Config = from_str(&result).unwrap();
|
|
||||||
// If config.rate.enabled is false, then add return a Limits struct with all limits set to u64::MAX
|
|
||||||
let mut limits: Limits;
|
|
||||||
if !config.rate.enabled {
|
|
||||||
limits = Limits {
|
|
||||||
limit_absolute_messages: Limit {
|
|
||||||
bucket: LimitType::AbsoluteMessage,
|
|
||||||
limit: u64::MAX,
|
|
||||||
remaining: u64::MAX,
|
|
||||||
reset: u64::MAX,
|
|
||||||
},
|
|
||||||
limit_absolute_register: Limit {
|
|
||||||
bucket: LimitType::AbsoluteRegister,
|
|
||||||
limit: u64::MAX,
|
|
||||||
remaining: u64::MAX,
|
|
||||||
reset: u64::MAX,
|
|
||||||
},
|
|
||||||
limit_auth_login: Limit {
|
|
||||||
bucket: LimitType::AuthLogin,
|
|
||||||
limit: u64::MAX,
|
|
||||||
remaining: u64::MAX,
|
|
||||||
reset: u64::MAX,
|
|
||||||
},
|
|
||||||
limit_auth_register: Limit {
|
|
||||||
bucket: LimitType::AuthRegister,
|
|
||||||
limit: u64::MAX,
|
|
||||||
remaining: u64::MAX,
|
|
||||||
reset: u64::MAX,
|
|
||||||
},
|
|
||||||
limit_ip: Limit {
|
|
||||||
bucket: LimitType::Ip,
|
|
||||||
limit: u64::MAX,
|
|
||||||
remaining: u64::MAX,
|
|
||||||
reset: u64::MAX,
|
|
||||||
},
|
|
||||||
limit_global: Limit {
|
|
||||||
bucket: LimitType::Global,
|
|
||||||
limit: u64::MAX,
|
|
||||||
remaining: u64::MAX,
|
|
||||||
reset: u64::MAX,
|
|
||||||
},
|
|
||||||
limit_error: Limit {
|
|
||||||
bucket: LimitType::Error,
|
|
||||||
limit: u64::MAX,
|
|
||||||
remaining: u64::MAX,
|
|
||||||
reset: u64::MAX,
|
|
||||||
},
|
|
||||||
limit_guild: Limit {
|
|
||||||
bucket: LimitType::Guild,
|
|
||||||
limit: u64::MAX,
|
|
||||||
remaining: u64::MAX,
|
|
||||||
reset: u64::MAX,
|
|
||||||
},
|
|
||||||
limit_webhook: Limit {
|
|
||||||
bucket: LimitType::Webhook,
|
|
||||||
limit: u64::MAX,
|
|
||||||
remaining: u64::MAX,
|
|
||||||
reset: u64::MAX,
|
|
||||||
},
|
|
||||||
limit_channel: Limit {
|
|
||||||
bucket: LimitType::Channel,
|
|
||||||
limit: u64::MAX,
|
|
||||||
remaining: u64::MAX,
|
|
||||||
reset: u64::MAX,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
limits = Limits {
|
|
||||||
limit_absolute_messages: Limit {
|
|
||||||
bucket: LimitType::AbsoluteMessage,
|
|
||||||
limit: config.absoluteRate.sendMessage.limit,
|
|
||||||
remaining: config.absoluteRate.sendMessage.limit,
|
|
||||||
reset: config.absoluteRate.sendMessage.window,
|
|
||||||
},
|
|
||||||
limit_absolute_register: Limit {
|
|
||||||
bucket: LimitType::AbsoluteRegister,
|
|
||||||
limit: config.absoluteRate.register.limit,
|
|
||||||
remaining: config.absoluteRate.register.limit,
|
|
||||||
reset: config.absoluteRate.register.window,
|
|
||||||
},
|
|
||||||
limit_auth_login: Limit {
|
|
||||||
bucket: LimitType::AuthLogin,
|
|
||||||
limit: config.rate.routes.auth.login.count,
|
|
||||||
remaining: config.rate.routes.auth.login.count,
|
|
||||||
reset: config.rate.routes.auth.login.window,
|
|
||||||
},
|
|
||||||
limit_auth_register: Limit {
|
|
||||||
bucket: LimitType::AuthRegister,
|
|
||||||
limit: config.rate.routes.auth.register.count,
|
|
||||||
remaining: config.rate.routes.auth.register.count,
|
|
||||||
reset: config.rate.routes.auth.register.window,
|
|
||||||
},
|
|
||||||
limit_ip: Limit {
|
|
||||||
bucket: LimitType::Ip,
|
|
||||||
limit: config.rate.ip.count,
|
|
||||||
remaining: config.rate.ip.count,
|
|
||||||
reset: config.rate.ip.window,
|
|
||||||
},
|
|
||||||
limit_global: Limit {
|
|
||||||
bucket: LimitType::Global,
|
|
||||||
limit: config.rate.global.count,
|
|
||||||
remaining: config.rate.global.count,
|
|
||||||
reset: config.rate.global.window,
|
|
||||||
},
|
|
||||||
limit_error: Limit {
|
|
||||||
bucket: LimitType::Error,
|
|
||||||
limit: config.rate.error.count,
|
|
||||||
remaining: config.rate.error.count,
|
|
||||||
reset: config.rate.error.window,
|
|
||||||
},
|
|
||||||
limit_guild: Limit {
|
|
||||||
bucket: LimitType::Guild,
|
|
||||||
limit: config.rate.routes.guild.count,
|
|
||||||
remaining: config.rate.routes.guild.count,
|
|
||||||
reset: config.rate.routes.guild.window,
|
|
||||||
},
|
|
||||||
limit_webhook: Limit {
|
|
||||||
bucket: LimitType::Webhook,
|
|
||||||
limit: config.rate.routes.webhook.count,
|
|
||||||
remaining: config.rate.routes.webhook.count,
|
|
||||||
reset: config.rate.routes.webhook.window,
|
|
||||||
},
|
|
||||||
limit_channel: Limit {
|
|
||||||
bucket: LimitType::Channel,
|
|
||||||
limit: config.rate.routes.channel.count,
|
|
||||||
remaining: config.rate.routes.channel.count,
|
|
||||||
reset: config.rate.routes.channel.window,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if !config.absoluteRate.register.enabled {
|
|
||||||
limits.limit_absolute_register = Limit {
|
|
||||||
bucket: LimitType::AbsoluteRegister,
|
|
||||||
limit: u64::MAX,
|
|
||||||
remaining: u64::MAX,
|
|
||||||
reset: u64::MAX,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if !config.absoluteRate.sendMessage.enabled {
|
|
||||||
limits.limit_absolute_messages = Limit {
|
|
||||||
bucket: LimitType::AbsoluteMessage,
|
|
||||||
limit: u64::MAX,
|
|
||||||
remaining: u64::MAX,
|
|
||||||
reset: u64::MAX,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
limits
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod instance_limits {
|
|
||||||
use crate::api::limits::{Limit, LimitType};
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn limit_below_zero() {
|
|
||||||
let mut limit = Limit {
|
|
||||||
bucket: LimitType::AbsoluteMessage,
|
|
||||||
limit: 0,
|
|
||||||
remaining: 1,
|
|
||||||
reset: 0,
|
|
||||||
};
|
|
||||||
limit.add_remaining(-2);
|
|
||||||
assert_eq!(0_u64, limit.remaining);
|
|
||||||
limit.add_remaining(-2123123);
|
|
||||||
assert_eq!(0_u64, limit.remaining);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,5 +1,5 @@
|
||||||
pub use instance::*;
|
pub use instance::*;
|
||||||
pub use limits::*;
|
pub use ratelimits::*;
|
||||||
|
|
||||||
pub mod instance;
|
pub mod instance;
|
||||||
pub mod limits;
|
pub mod ratelimits;
|
||||||
|
|
|
@ -0,0 +1,37 @@
|
||||||
|
use std::hash::Hash;
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::types::Snowflake;
|
||||||
|
|
||||||
|
/// The different types of ratelimits that can be applied to a request. Includes "Baseline"-variants
|
||||||
|
/// for when the Snowflake is not yet known.
|
||||||
|
/// See <https://discord.com/developers/docs/topics/rate-limits#rate-limits> for more information.
|
||||||
|
#[derive(Clone, Copy, Eq, PartialEq, Debug, Default, Hash, Serialize, Deserialize)]
|
||||||
|
pub enum LimitType {
|
||||||
|
AuthRegister,
|
||||||
|
AuthLogin,
|
||||||
|
#[default]
|
||||||
|
Global,
|
||||||
|
Ip,
|
||||||
|
Channel(Snowflake),
|
||||||
|
ChannelBaseline,
|
||||||
|
Error,
|
||||||
|
Guild(Snowflake),
|
||||||
|
GuildBaseline,
|
||||||
|
Webhook(Snowflake),
|
||||||
|
WebhookBaseline,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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 {
|
||||||
|
pub bucket: LimitType,
|
||||||
|
pub limit: u64,
|
||||||
|
pub remaining: u64,
|
||||||
|
pub reset: u64,
|
||||||
|
pub window: u64,
|
||||||
|
}
|
|
@ -1,3 +1,3 @@
|
||||||
pub use instance::limits::*;
|
pub use instance::ratelimits::*;
|
||||||
|
|
||||||
pub mod instance;
|
pub mod instance;
|
||||||
|
|
|
@ -0,0 +1,32 @@
|
||||||
|
use reqwest::Client;
|
||||||
|
use serde_json::to_string;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
api::LimitType,
|
||||||
|
errors::ChorusResult,
|
||||||
|
instance::UserMeta,
|
||||||
|
ratelimiter::ChorusRequest,
|
||||||
|
types::{Channel, PrivateChannelCreateSchema},
|
||||||
|
};
|
||||||
|
|
||||||
|
impl UserMeta {
|
||||||
|
/// Creates a DM channel or group DM channel.
|
||||||
|
///
|
||||||
|
/// # Reference:
|
||||||
|
/// Read <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);
|
||||||
|
ChorusRequest {
|
||||||
|
request: Client::new()
|
||||||
|
.post(url)
|
||||||
|
.bearer_auth(self.token())
|
||||||
|
.body(to_string(&create_private_channel_schema).unwrap()),
|
||||||
|
limit_type: LimitType::Global,
|
||||||
|
}
|
||||||
|
.deserialize_response::<Channel>(self)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
use reqwest::Client;
|
||||||
|
use serde_json::to_string;
|
||||||
|
|
||||||
|
use crate::errors::ChorusResult;
|
||||||
|
use crate::instance::UserMeta;
|
||||||
|
use crate::ratelimiter::ChorusRequest;
|
||||||
|
use crate::types::Snowflake;
|
||||||
|
|
||||||
|
impl UserMeta {
|
||||||
|
/// # Arguments:
|
||||||
|
/// - lurking: Whether the user is lurking in the guild
|
||||||
|
///
|
||||||
|
/// # Reference:
|
||||||
|
/// Read <https://discord-userdoccers.vercel.app/resources/guild#leave-guild>
|
||||||
|
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,
|
||||||
|
guild_id
|
||||||
|
))
|
||||||
|
.bearer_auth(self.token())
|
||||||
|
.body(to_string(&lurking).unwrap()),
|
||||||
|
limit_type: crate::api::LimitType::Guild(*guild_id),
|
||||||
|
}
|
||||||
|
.handle_request_as_result(self)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,9 @@
|
||||||
|
pub use channels::*;
|
||||||
|
pub use guilds::*;
|
||||||
pub use relationships::*;
|
pub use relationships::*;
|
||||||
pub use users::*;
|
pub use users::*;
|
||||||
|
|
||||||
|
pub mod channels;
|
||||||
|
pub mod guilds;
|
||||||
pub mod relationships;
|
pub mod relationships;
|
||||||
pub mod users;
|
pub mod users;
|
||||||
|
|
|
@ -2,9 +2,10 @@ use reqwest::Client;
|
||||||
use serde_json::to_string;
|
use serde_json::to_string;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
api::{deserialize_response, handle_request_as_result},
|
api::LimitType,
|
||||||
errors::ChorusResult,
|
errors::ChorusResult,
|
||||||
instance::UserMeta,
|
instance::UserMeta,
|
||||||
|
ratelimiter::ChorusRequest,
|
||||||
types::{self, CreateUserRelationshipSchema, RelationshipType, Snowflake},
|
types::{self, CreateUserRelationshipSchema, RelationshipType, Snowflake},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -26,13 +27,13 @@ impl UserMeta {
|
||||||
self.belongs_to.borrow().urls.api,
|
self.belongs_to.borrow().urls.api,
|
||||||
user_id
|
user_id
|
||||||
);
|
);
|
||||||
let request = Client::new().get(url).bearer_auth(self.token());
|
let chorus_request = ChorusRequest {
|
||||||
deserialize_response::<Vec<types::PublicUser>>(
|
request: Client::new().get(url).bearer_auth(self.token()),
|
||||||
request,
|
limit_type: LimitType::Global,
|
||||||
self,
|
};
|
||||||
crate::api::limits::LimitType::Global,
|
chorus_request
|
||||||
)
|
.deserialize_response::<Vec<types::PublicUser>>(self)
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Retrieves the authenticated user's relationships.
|
/// Retrieves the authenticated user's relationships.
|
||||||
|
@ -44,13 +45,13 @@ impl UserMeta {
|
||||||
"{}/users/@me/relationships/",
|
"{}/users/@me/relationships/",
|
||||||
self.belongs_to.borrow().urls.api
|
self.belongs_to.borrow().urls.api
|
||||||
);
|
);
|
||||||
let request = Client::new().get(url).bearer_auth(self.token());
|
let chorus_request = ChorusRequest {
|
||||||
deserialize_response::<Vec<types::Relationship>>(
|
request: Client::new().get(url).bearer_auth(self.token()),
|
||||||
request,
|
limit_type: LimitType::Global,
|
||||||
self,
|
};
|
||||||
crate::api::limits::LimitType::Global,
|
chorus_request
|
||||||
)
|
.deserialize_response::<Vec<types::Relationship>>(self)
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sends a friend request to a user.
|
/// Sends a friend request to a user.
|
||||||
|
@ -70,8 +71,11 @@ impl UserMeta {
|
||||||
self.belongs_to.borrow().urls.api
|
self.belongs_to.borrow().urls.api
|
||||||
);
|
);
|
||||||
let body = to_string(&schema).unwrap();
|
let body = to_string(&schema).unwrap();
|
||||||
let request = Client::new().post(url).bearer_auth(self.token()).body(body);
|
let chorus_request = ChorusRequest {
|
||||||
handle_request_as_result(request, self, crate::api::limits::LimitType::Global).await
|
request: Client::new().post(url).bearer_auth(self.token()).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 the specified user.
|
||||||
|
@ -96,10 +100,13 @@ impl UserMeta {
|
||||||
let api_url = self.belongs_to.borrow().urls.api.clone();
|
let api_url = self.belongs_to.borrow().urls.api.clone();
|
||||||
match relationship_type {
|
match relationship_type {
|
||||||
RelationshipType::None => {
|
RelationshipType::None => {
|
||||||
let request = Client::new()
|
let chorus_request = ChorusRequest {
|
||||||
.delete(format!("{}/users/@me/relationships/{}/", api_url, user_id))
|
request: Client::new()
|
||||||
.bearer_auth(self.token());
|
.delete(format!("{}/users/@me/relationships/{}/", api_url, user_id))
|
||||||
handle_request_as_result(request, self, crate::api::limits::LimitType::Global).await
|
.bearer_auth(self.token()),
|
||||||
|
limit_type: LimitType::Global,
|
||||||
|
};
|
||||||
|
chorus_request.handle_request_as_result(self).await
|
||||||
}
|
}
|
||||||
RelationshipType::Friends | RelationshipType::Incoming | RelationshipType::Outgoing => {
|
RelationshipType::Friends | RelationshipType::Incoming | RelationshipType::Outgoing => {
|
||||||
let body = CreateUserRelationshipSchema {
|
let body = CreateUserRelationshipSchema {
|
||||||
|
@ -107,11 +114,14 @@ impl UserMeta {
|
||||||
from_friend_suggestion: None,
|
from_friend_suggestion: None,
|
||||||
friend_token: None,
|
friend_token: None,
|
||||||
};
|
};
|
||||||
let request = Client::new()
|
let chorus_request = ChorusRequest {
|
||||||
.put(format!("{}/users/@me/relationships/{}/", api_url, user_id))
|
request: Client::new()
|
||||||
.bearer_auth(self.token())
|
.put(format!("{}/users/@me/relationships/{}/", api_url, user_id))
|
||||||
.body(to_string(&body).unwrap());
|
.bearer_auth(self.token())
|
||||||
handle_request_as_result(request, self, crate::api::limits::LimitType::Global).await
|
.body(to_string(&body).unwrap()),
|
||||||
|
limit_type: LimitType::Global,
|
||||||
|
};
|
||||||
|
chorus_request.handle_request_as_result(self).await
|
||||||
}
|
}
|
||||||
RelationshipType::Blocked => {
|
RelationshipType::Blocked => {
|
||||||
let body = CreateUserRelationshipSchema {
|
let body = CreateUserRelationshipSchema {
|
||||||
|
@ -119,11 +129,14 @@ impl UserMeta {
|
||||||
from_friend_suggestion: None,
|
from_friend_suggestion: None,
|
||||||
friend_token: None,
|
friend_token: None,
|
||||||
};
|
};
|
||||||
let request = Client::new()
|
let chorus_request = ChorusRequest {
|
||||||
.put(format!("{}/users/@me/relationships/{}/", api_url, user_id))
|
request: Client::new()
|
||||||
.bearer_auth(self.token())
|
.put(format!("{}/users/@me/relationships/{}/", api_url, user_id))
|
||||||
.body(to_string(&body).unwrap());
|
.bearer_auth(self.token())
|
||||||
handle_request_as_result(request, self, crate::api::limits::LimitType::Global).await
|
.body(to_string(&body).unwrap()),
|
||||||
|
limit_type: LimitType::Global,
|
||||||
|
};
|
||||||
|
chorus_request.handle_request_as_result(self).await
|
||||||
}
|
}
|
||||||
RelationshipType::Suggestion | RelationshipType::Implicit => Ok(()),
|
RelationshipType::Suggestion | RelationshipType::Implicit => Ok(()),
|
||||||
}
|
}
|
||||||
|
@ -143,7 +156,10 @@ impl UserMeta {
|
||||||
self.belongs_to.borrow().urls.api,
|
self.belongs_to.borrow().urls.api,
|
||||||
user_id
|
user_id
|
||||||
);
|
);
|
||||||
let request = Client::new().delete(url).bearer_auth(self.token());
|
let chorus_request = ChorusRequest {
|
||||||
handle_request_as_result(request, self, crate::api::limits::LimitType::Global).await
|
request: Client::new().delete(url).bearer_auth(self.token()),
|
||||||
|
limit_type: LimitType::Global,
|
||||||
|
};
|
||||||
|
chorus_request.handle_request_as_result(self).await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
|
use std::{cell::RefCell, rc::Rc};
|
||||||
|
|
||||||
use reqwest::Client;
|
use reqwest::Client;
|
||||||
use serde_json::to_string;
|
use serde_json::to_string;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
api::{deserialize_response, handle_request_as_result},
|
api::LimitType,
|
||||||
errors::{ChorusLibError, ChorusResult},
|
errors::{ChorusError, ChorusResult},
|
||||||
instance::{Instance, UserMeta},
|
instance::{Instance, UserMeta},
|
||||||
limit::LimitedRequester,
|
ratelimiter::ChorusRequest,
|
||||||
types::{User, UserModifySchema, UserSettings},
|
types::{User, UserModifySchema, UserSettings},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -48,16 +50,20 @@ impl UserMeta {
|
||||||
|| modify_schema.email.is_some()
|
|| modify_schema.email.is_some()
|
||||||
|| modify_schema.code.is_some()
|
|| modify_schema.code.is_some()
|
||||||
{
|
{
|
||||||
return Err(ChorusLibError::PasswordRequiredError);
|
return Err(ChorusError::PasswordRequired);
|
||||||
}
|
}
|
||||||
let request = Client::new()
|
let request = Client::new()
|
||||||
.patch(format!("{}/users/@me/", self.belongs_to.borrow().urls.api))
|
.patch(format!("{}/users/@me/", self.belongs_to.borrow().urls.api))
|
||||||
.body(to_string(&modify_schema).unwrap())
|
.body(to_string(&modify_schema).unwrap())
|
||||||
.bearer_auth(self.token());
|
.bearer_auth(self.token());
|
||||||
let user_updated =
|
let chorus_request = ChorusRequest {
|
||||||
deserialize_response::<User>(request, self, crate::api::limits::LimitType::Ip)
|
request,
|
||||||
.await
|
limit_type: LimitType::default(),
|
||||||
.unwrap();
|
};
|
||||||
|
let user_updated = chorus_request
|
||||||
|
.deserialize_response::<User>(self)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
let _ = std::mem::replace(&mut self.object, user_updated.clone());
|
let _ = std::mem::replace(&mut self.object, user_updated.clone());
|
||||||
Ok(user_updated)
|
Ok(user_updated)
|
||||||
}
|
}
|
||||||
|
@ -78,43 +84,28 @@ impl UserMeta {
|
||||||
self.belongs_to.borrow().urls.api
|
self.belongs_to.borrow().urls.api
|
||||||
))
|
))
|
||||||
.bearer_auth(self.token());
|
.bearer_auth(self.token());
|
||||||
handle_request_as_result(request, &mut self, crate::api::limits::LimitType::Ip).await
|
let chorus_request = ChorusRequest {
|
||||||
|
request,
|
||||||
|
limit_type: LimitType::default(),
|
||||||
|
};
|
||||||
|
chorus_request.handle_request_as_result(&mut self).await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl User {
|
impl User {
|
||||||
pub async fn get(user: &mut UserMeta, id: Option<&String>) -> ChorusResult<User> {
|
pub async fn get(user: &mut UserMeta, id: Option<&String>) -> ChorusResult<User> {
|
||||||
let mut belongs_to = user.belongs_to.borrow_mut();
|
let url_api = user.belongs_to.borrow().urls.api.clone();
|
||||||
User::_get(
|
|
||||||
&user.token(),
|
|
||||||
&format!("{}", belongs_to.urls.api),
|
|
||||||
&mut belongs_to,
|
|
||||||
id,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn _get(
|
|
||||||
token: &str,
|
|
||||||
url_api: &str,
|
|
||||||
instance: &mut Instance,
|
|
||||||
id: Option<&String>,
|
|
||||||
) -> ChorusResult<User> {
|
|
||||||
let url = if id.is_none() {
|
let url = if id.is_none() {
|
||||||
format!("{}/users/@me/", url_api)
|
format!("{}/users/@me/", url_api)
|
||||||
} else {
|
} else {
|
||||||
format!("{}/users/{}", url_api, id.unwrap())
|
format!("{}/users/{}", url_api, id.unwrap())
|
||||||
};
|
};
|
||||||
let request = reqwest::Client::new().get(url).bearer_auth(token);
|
let request = reqwest::Client::new().get(url).bearer_auth(user.token());
|
||||||
let mut cloned_limits = instance.limits.clone();
|
let chorus_request = ChorusRequest {
|
||||||
match LimitedRequester::send_request(
|
|
||||||
request,
|
request,
|
||||||
crate::api::limits::LimitType::Ip,
|
limit_type: LimitType::Global,
|
||||||
instance,
|
};
|
||||||
&mut cloned_limits,
|
match chorus_request.send_request(user).await {
|
||||||
)
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
Ok(result) => {
|
Ok(result) => {
|
||||||
let result_text = result.text().await.unwrap();
|
let result_text = result.text().await.unwrap();
|
||||||
Ok(serde_json::from_str::<User>(&result_text).unwrap())
|
Ok(serde_json::from_str::<User>(&result_text).unwrap())
|
||||||
|
@ -131,18 +122,21 @@ impl User {
|
||||||
let request: reqwest::RequestBuilder = Client::new()
|
let request: reqwest::RequestBuilder = Client::new()
|
||||||
.get(format!("{}/users/@me/settings/", url_api))
|
.get(format!("{}/users/@me/settings/", url_api))
|
||||||
.bearer_auth(token);
|
.bearer_auth(token);
|
||||||
let mut cloned_limits = instance.limits.clone();
|
let mut user =
|
||||||
match LimitedRequester::send_request(
|
UserMeta::shell(Rc::new(RefCell::new(instance.clone())), token.clone()).await;
|
||||||
|
let chorus_request = ChorusRequest {
|
||||||
request,
|
request,
|
||||||
crate::api::limits::LimitType::Ip,
|
limit_type: LimitType::Global,
|
||||||
instance,
|
};
|
||||||
&mut cloned_limits,
|
let result = match chorus_request.send_request(&mut user).await {
|
||||||
)
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
Ok(result) => Ok(serde_json::from_str(&result.text().await.unwrap()).unwrap()),
|
Ok(result) => Ok(serde_json::from_str(&result.text().await.unwrap()).unwrap()),
|
||||||
Err(e) => Err(e),
|
Err(e) => Err(e),
|
||||||
|
};
|
||||||
|
if instance.limits_information.is_some() {
|
||||||
|
instance.limits_information.as_mut().unwrap().ratelimits =
|
||||||
|
user.belongs_to.borrow().clone_limits_if_some().unwrap();
|
||||||
}
|
}
|
||||||
|
result
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -158,6 +152,12 @@ impl Instance {
|
||||||
This function is a wrapper around [`User::get`].
|
This function is a wrapper around [`User::get`].
|
||||||
*/
|
*/
|
||||||
pub async fn get_user(&mut self, token: String, id: Option<&String>) -> ChorusResult<User> {
|
pub async fn get_user(&mut self, token: String, id: Option<&String>) -> ChorusResult<User> {
|
||||||
User::_get(&token, &self.urls.api.clone(), self, id).await
|
let mut user = UserMeta::shell(Rc::new(RefCell::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();
|
||||||
|
}
|
||||||
|
result
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,39 +1,50 @@
|
||||||
use custom_error::custom_error;
|
use custom_error::custom_error;
|
||||||
|
use reqwest::Error;
|
||||||
|
|
||||||
custom_error! {
|
custom_error! {
|
||||||
#[derive(PartialEq, Eq)]
|
#[derive(PartialEq, Eq)]
|
||||||
pub FieldFormatError
|
pub RegistrationError
|
||||||
PasswordError = "Password must be between 1 and 72 characters.",
|
Consent = "Consent must be 'true' to register.",
|
||||||
UsernameError = "Username must be between 2 and 32 characters.",
|
|
||||||
ConsentError = "Consent must be 'true' to register.",
|
|
||||||
EmailError = "The provided email address is in an invalid format.",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type ChorusResult<T> = std::result::Result<T, ChorusLibError>;
|
pub type ChorusResult<T> = std::result::Result<T, ChorusError>;
|
||||||
|
|
||||||
custom_error! {
|
custom_error! {
|
||||||
#[derive(PartialEq, Eq)]
|
pub ChorusError
|
||||||
pub ChorusLibError
|
/// Server did not respond.
|
||||||
NoResponse = "Did not receive a response from the Server.",
|
NoResponse = "Did not receive a response from the Server.",
|
||||||
RequestErrorError{url:String, error:String} = "An error occured while trying to GET from {url}: {error}",
|
/// Reqwest returned an Error instead of a Response object.
|
||||||
ReceivedErrorCodeError{error_code:String} = "Received the following error code while requesting from the route: {error_code}",
|
RequestFailed{url:String, error: Error} = "An error occured while trying to GET from {url}: {error}",
|
||||||
CantGetInfoError{error:String} = "Something seems to be wrong with the instance. Cannot get information about the instance: {error}",
|
/// Response received, however, it was not of the successful responses type. Used when no other, special case applies.
|
||||||
InvalidFormBodyError{error_type: String, error:String} = "The server responded with: {error_type}: {error}",
|
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.
|
||||||
|
CantGetInformation{error:String} = "Something seems to be wrong with the instance. Cannot get information about the instance: {error}",
|
||||||
|
/// The requests form body was malformed/invalid.
|
||||||
|
InvalidFormBody{error_type: String, error:String} = "The server responded with: {error_type}: {error}",
|
||||||
|
/// The request has not been processed by the server due to a relevant rate limit bucket being exhausted.
|
||||||
RateLimited{bucket:String} = "Ratelimited on Bucket {bucket}",
|
RateLimited{bucket:String} = "Ratelimited on Bucket {bucket}",
|
||||||
MultipartCreationError{error: String} = "Got an error whilst creating the form: {error}",
|
/// The multipart form could not be created.
|
||||||
FormCreationError{error: String} = "Got an error whilst creating the form: {error}",
|
MultipartCreation{error: String} = "Got an error whilst creating the form: {error}",
|
||||||
|
/// The regular form could not be created.
|
||||||
|
FormCreation{error: String} = "Got an error whilst creating the form: {error}",
|
||||||
|
/// The token is invalid.
|
||||||
TokenExpired = "Token expired, invalid or not found.",
|
TokenExpired = "Token expired, invalid or not found.",
|
||||||
|
/// No permission
|
||||||
NoPermission = "You do not have the permissions needed to perform this action.",
|
NoPermission = "You do not have the permissions needed to perform this action.",
|
||||||
|
/// Resource not found
|
||||||
NotFound{error: String} = "The provided resource hasn't been found: {error}",
|
NotFound{error: String} = "The provided resource hasn't been found: {error}",
|
||||||
PasswordRequiredError = "You need to provide your current password to authenticate for this action.",
|
/// Used when you, for example, try to change your spacebar account password without providing your old password for verification.
|
||||||
InvalidResponseError{error: String} = "The response is malformed and cannot be processed. Error: {error}",
|
PasswordRequired = "You need to provide your current password to authenticate for this action.",
|
||||||
InvalidArgumentsError{error: String} = "Invalid arguments were provided. Error: {error}"
|
/// Malformed or unexpected response.
|
||||||
|
InvalidResponse{error: String} = "The response is malformed and cannot be processed. Error: {error}",
|
||||||
|
/// Invalid, insufficient or too many arguments provided.
|
||||||
|
InvalidArguments{error: String} = "Invalid arguments were provided. Error: {error}"
|
||||||
}
|
}
|
||||||
|
|
||||||
custom_error! {
|
custom_error! {
|
||||||
#[derive(PartialEq, Eq)]
|
#[derive(PartialEq, Eq)]
|
||||||
pub ObserverError
|
pub ObserverError
|
||||||
AlreadySubscribedError = "Each event can only be subscribed to once."
|
AlreadySubscribed = "Each event can only be subscribed to once."
|
||||||
}
|
}
|
||||||
|
|
||||||
custom_error! {
|
custom_error! {
|
||||||
|
@ -45,27 +56,27 @@ custom_error! {
|
||||||
#[derive(Clone, PartialEq, Eq)]
|
#[derive(Clone, PartialEq, Eq)]
|
||||||
pub GatewayError
|
pub GatewayError
|
||||||
// Errors we have received from the gateway
|
// Errors we have received from the gateway
|
||||||
UnknownError = "We're not sure what went wrong. Try reconnecting?",
|
Unknown = "We're not sure what went wrong. Try reconnecting?",
|
||||||
UnknownOpcodeError = "You sent an invalid Gateway opcode or an invalid payload for an opcode",
|
UnknownOpcode = "You sent an invalid Gateway opcode or an invalid payload for an opcode",
|
||||||
DecodeError = "Gateway server couldn't decode payload",
|
Decode = "Gateway server couldn't decode payload",
|
||||||
NotAuthenticatedError = "You sent a payload prior to identifying",
|
NotAuthenticated = "You sent a payload prior to identifying",
|
||||||
AuthenticationFailedError = "The account token sent with your identify payload is invalid",
|
AuthenticationFailed = "The account token sent with your identify payload is invalid",
|
||||||
AlreadyAuthenticatedError = "You've already identified, no need to reauthenticate",
|
AlreadyAuthenticated = "You've already identified, no need to reauthenticate",
|
||||||
InvalidSequenceNumberError = "The sequence number sent when resuming the session was invalid. Reconnect and start a new session",
|
InvalidSequenceNumber = "The sequence number sent when resuming the session was invalid. Reconnect and start a new session",
|
||||||
RateLimitedError = "You are being rate limited!",
|
RateLimited = "You are being rate limited!",
|
||||||
SessionTimedOutError = "Your session timed out. Reconnect and start a new one",
|
SessionTimedOut = "Your session timed out. Reconnect and start a new one",
|
||||||
InvalidShardError = "You sent us an invalid shard when identifying",
|
InvalidShard = "You sent us an invalid shard when identifying",
|
||||||
ShardingRequiredError = "The session would have handled too many guilds - you are required to shard your connection in order to connect",
|
ShardingRequired = "The session would have handled too many guilds - you are required to shard your connection in order to connect",
|
||||||
InvalidAPIVersionError = "You sent an invalid Gateway version",
|
InvalidAPIVersion = "You sent an invalid Gateway version",
|
||||||
InvalidIntentsError = "You sent an invalid intent",
|
InvalidIntents = "You sent an invalid intent",
|
||||||
DisallowedIntentsError = "You sent a disallowed intent. You may have tried to specify an intent that you have not enabled or are not approved for",
|
DisallowedIntents = "You sent a disallowed intent. You may have tried to specify an intent that you have not enabled or are not approved for",
|
||||||
|
|
||||||
// Errors when initiating a gateway connection
|
// Errors when initiating a gateway connection
|
||||||
CannotConnectError{error: String} = "Cannot connect due to a tungstenite error: {error}",
|
CannotConnect{error: String} = "Cannot connect due to a tungstenite error: {error}",
|
||||||
NonHelloOnInitiateError{opcode: u8} = "Received non hello on initial gateway connection ({opcode}), something is definitely wrong",
|
NonHelloOnInitiate{opcode: u8} = "Received non hello on initial gateway connection ({opcode}), something is definitely wrong",
|
||||||
|
|
||||||
// Other misc errors
|
// Other misc errors
|
||||||
UnexpectedOpcodeReceivedError{opcode: u8} = "Received an opcode we weren't expecting to receive: {opcode}",
|
UnexpectedOpcodeReceived{opcode: u8} = "Received an opcode we weren't expecting to receive: {opcode}",
|
||||||
}
|
}
|
||||||
|
|
||||||
custom_error! {
|
custom_error! {
|
||||||
|
|
1353
src/gateway.rs
1353
src/gateway.rs
File diff suppressed because it is too large
Load Diff
|
@ -1,26 +1,37 @@
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
|
use std::collections::HashMap;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
use reqwest::Client;
|
use reqwest::Client;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::api::limits::Limits;
|
use crate::api::{Limit, LimitType};
|
||||||
use crate::errors::{ChorusLibError, ChorusResult, FieldFormatError};
|
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, User, UserSettings};
|
||||||
use crate::UrlBundle;
|
use crate::UrlBundle;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
/**
|
/**
|
||||||
The [`Instance`] what you will be using to perform all sorts of actions on the Spacebar server.
|
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 struct Instance {
|
||||||
pub urls: UrlBundle,
|
pub urls: UrlBundle,
|
||||||
pub instance_info: GeneralConfiguration,
|
pub instance_info: GeneralConfiguration,
|
||||||
pub limits: Limits,
|
pub limits_information: Option<LimitsInformation>,
|
||||||
pub client: Client,
|
pub client: Client,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct LimitsInformation {
|
||||||
|
pub ratelimits: HashMap<LimitType, Limit>,
|
||||||
|
pub configuration: RateLimits,
|
||||||
|
}
|
||||||
|
|
||||||
impl Instance {
|
impl Instance {
|
||||||
/// Creates a new [`Instance`].
|
/// Creates a new [`Instance`].
|
||||||
/// # Arguments
|
/// # Arguments
|
||||||
|
@ -28,24 +39,43 @@ impl Instance {
|
||||||
/// * `requester` - The [`LimitedRequester`] that will be used to make requests to the Spacebar server.
|
/// * `requester` - The [`LimitedRequester`] that will be used to make requests to the Spacebar server.
|
||||||
/// # Errors
|
/// # Errors
|
||||||
/// * [`InstanceError`] - If the instance cannot be created.
|
/// * [`InstanceError`] - If the instance cannot be created.
|
||||||
pub async fn new(urls: UrlBundle) -> ChorusResult<Instance> {
|
pub async fn new(urls: UrlBundle, limited: bool) -> ChorusResult<Instance> {
|
||||||
|
let limits_information;
|
||||||
|
if limited {
|
||||||
|
let limits_configuration =
|
||||||
|
Some(ChorusRequest::get_limits_config(&urls.api).await?.rate);
|
||||||
|
let limits = Some(ChorusRequest::limits_config_to_hashmap(
|
||||||
|
limits_configuration.as_ref().unwrap(),
|
||||||
|
));
|
||||||
|
limits_information = Some(LimitsInformation {
|
||||||
|
ratelimits: limits.unwrap(),
|
||||||
|
configuration: limits_configuration.unwrap(),
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
limits_information = None;
|
||||||
|
}
|
||||||
let mut instance = Instance {
|
let mut instance = Instance {
|
||||||
urls: urls.clone(),
|
urls: urls.clone(),
|
||||||
// Will be overwritten in the next step
|
// Will be overwritten in the next step
|
||||||
instance_info: GeneralConfiguration::default(),
|
instance_info: GeneralConfiguration::default(),
|
||||||
limits: Limits::check_limits(urls.api).await,
|
limits_information,
|
||||||
client: Client::new(),
|
client: Client::new(),
|
||||||
};
|
};
|
||||||
instance.instance_info = match instance.general_configuration_schema().await {
|
instance.instance_info = match instance.general_configuration_schema().await {
|
||||||
Ok(schema) => schema,
|
Ok(schema) => schema,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
return Err(ChorusLibError::CantGetInfoError {
|
log::warn!("Could not get instance configuration schema: {}", e);
|
||||||
error: e.to_string(),
|
GeneralConfiguration::default()
|
||||||
});
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
Ok(instance)
|
Ok(instance)
|
||||||
}
|
}
|
||||||
|
pub(crate) fn clone_limits_if_some(&self) -> Option<HashMap<LimitType, Limit>> {
|
||||||
|
if self.limits_information.is_some() {
|
||||||
|
return Some(self.limits_information.as_ref().unwrap().ratelimits.clone());
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
@ -59,32 +89,14 @@ impl fmt::Display for Token {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
|
||||||
pub struct Username {
|
|
||||||
pub username: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Username {
|
|
||||||
/// Creates a new [`Username`].
|
|
||||||
/// # Arguments
|
|
||||||
/// * `username` - The username that will be used to create the [`Username`].
|
|
||||||
/// # Errors
|
|
||||||
/// * [`UsernameFormatError`] - If the username is not between 2 and 32 characters.
|
|
||||||
pub fn new(username: String) -> Result<Username, FieldFormatError> {
|
|
||||||
if username.len() < 2 || username.len() > 32 {
|
|
||||||
return Err(FieldFormatError::UsernameError);
|
|
||||||
}
|
|
||||||
Ok(Username { username })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct UserMeta {
|
pub struct UserMeta {
|
||||||
pub belongs_to: Rc<RefCell<Instance>>,
|
pub belongs_to: Rc<RefCell<Instance>>,
|
||||||
pub token: String,
|
pub token: String,
|
||||||
pub limits: Limits,
|
pub limits: Option<HashMap<LimitType, Limit>>,
|
||||||
pub settings: UserSettings,
|
pub settings: UserSettings,
|
||||||
pub object: User,
|
pub object: User,
|
||||||
|
pub gateway: GatewayHandle,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl UserMeta {
|
impl UserMeta {
|
||||||
|
@ -99,9 +111,10 @@ impl UserMeta {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
belongs_to: Rc<RefCell<Instance>>,
|
belongs_to: Rc<RefCell<Instance>>,
|
||||||
token: String,
|
token: String,
|
||||||
limits: Limits,
|
limits: Option<HashMap<LimitType, Limit>>,
|
||||||
settings: UserSettings,
|
settings: UserSettings,
|
||||||
object: User,
|
object: User,
|
||||||
|
gateway: GatewayHandle,
|
||||||
) -> UserMeta {
|
) -> UserMeta {
|
||||||
UserMeta {
|
UserMeta {
|
||||||
belongs_to,
|
belongs_to,
|
||||||
|
@ -109,6 +122,32 @@ impl UserMeta {
|
||||||
limits,
|
limits,
|
||||||
settings,
|
settings,
|
||||||
object,
|
object,
|
||||||
|
gateway,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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
|
||||||
|
/// 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();
|
||||||
|
// Dummy gateway object
|
||||||
|
let gateway = Gateway::new(wss_url).await.unwrap();
|
||||||
|
UserMeta {
|
||||||
|
token,
|
||||||
|
belongs_to: instance.clone(),
|
||||||
|
limits: instance
|
||||||
|
.borrow()
|
||||||
|
.limits_information
|
||||||
|
.as_ref()
|
||||||
|
.map(|info| info.ratelimits.clone()),
|
||||||
|
settings,
|
||||||
|
object,
|
||||||
|
gateway,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,7 @@ pub mod gateway;
|
||||||
#[cfg(feature = "client")]
|
#[cfg(feature = "client")]
|
||||||
pub mod instance;
|
pub mod instance;
|
||||||
#[cfg(feature = "client")]
|
#[cfg(feature = "client")]
|
||||||
pub mod limit;
|
pub mod ratelimiter;
|
||||||
pub mod types;
|
pub mod types;
|
||||||
#[cfg(feature = "client")]
|
#[cfg(feature = "client")]
|
||||||
pub mod voice;
|
pub mod voice;
|
||||||
|
|
304
src/limit.rs
304
src/limit.rs
|
@ -1,304 +0,0 @@
|
||||||
use reqwest::{RequestBuilder, Response};
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
api::limits::{Limit, LimitType, Limits, LimitsMutRef},
|
|
||||||
errors::{ChorusLibError, ChorusResult},
|
|
||||||
instance::Instance,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct LimitedRequester;
|
|
||||||
|
|
||||||
impl LimitedRequester {
|
|
||||||
/// Checks if a request can be sent without hitting API rate limits and sends it, if true.
|
|
||||||
/// Will automatically update the rate limits of the LimitedRequester the request has been
|
|
||||||
/// sent with.
|
|
||||||
///
|
|
||||||
/// # Arguments
|
|
||||||
///
|
|
||||||
/// * `request`: A `RequestBuilder` that contains a request ready to be sent. Unfinished or
|
|
||||||
/// invalid requests will result in the method panicing.
|
|
||||||
/// * `limit_type`: Because this library does not yet implement a way to check for which rate
|
|
||||||
/// limit will be used when the request gets send, you will have to specify this manually using
|
|
||||||
/// a `LimitType` enum.
|
|
||||||
///
|
|
||||||
/// # Returns
|
|
||||||
///
|
|
||||||
/// * `Response`: The `Response` gotten from sending the request to the server. This will be
|
|
||||||
/// returned if the Request was built and send successfully. Is wrapped in an `Option`.
|
|
||||||
/// * `None`: `None` will be returned if the rate limit has been hit, and the request could
|
|
||||||
/// therefore not have been sent.
|
|
||||||
///
|
|
||||||
/// # Errors
|
|
||||||
///
|
|
||||||
/// This method will error if:
|
|
||||||
///
|
|
||||||
/// * The request does not return a success status code (200-299)
|
|
||||||
/// * The supplied `RequestBuilder` contains invalid or incomplete information
|
|
||||||
/// * There has been an error with processing (unwrapping) the `Response`
|
|
||||||
/// * The call to `update_limits` yielded errors. Read the methods' Errors section for more
|
|
||||||
/// information.
|
|
||||||
pub async fn send_request(
|
|
||||||
request: RequestBuilder,
|
|
||||||
limit_type: LimitType,
|
|
||||||
instance: &mut Instance,
|
|
||||||
user_rate_limits: &mut Limits,
|
|
||||||
) -> ChorusResult<Response> {
|
|
||||||
if LimitedRequester::can_send_request(limit_type, &instance.limits, user_rate_limits) {
|
|
||||||
let built_request = match request.build() {
|
|
||||||
Ok(request) => request,
|
|
||||||
Err(e) => {
|
|
||||||
return Err(ChorusLibError::RequestErrorError {
|
|
||||||
url: "".to_string(),
|
|
||||||
error: e.to_string(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let result = instance.client.execute(built_request).await;
|
|
||||||
let response = match result {
|
|
||||||
Ok(is_response) => is_response,
|
|
||||||
Err(e) => {
|
|
||||||
return Err(ChorusLibError::ReceivedErrorCodeError {
|
|
||||||
error_code: e.to_string(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
LimitedRequester::update_limits(
|
|
||||||
&response,
|
|
||||||
limit_type,
|
|
||||||
&mut instance.limits,
|
|
||||||
user_rate_limits,
|
|
||||||
);
|
|
||||||
if !response.status().is_success() {
|
|
||||||
match response.status().as_u16() {
|
|
||||||
401 => Err(ChorusLibError::TokenExpired),
|
|
||||||
403 => Err(ChorusLibError::TokenExpired),
|
|
||||||
_ => Err(ChorusLibError::ReceivedErrorCodeError {
|
|
||||||
error_code: response.status().as_str().to_string(),
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Ok(response)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Err(ChorusLibError::RateLimited {
|
|
||||||
bucket: limit_type.to_string(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn update_limit_entry(entry: &mut Limit, reset: u64, remaining: u64, limit: u64) {
|
|
||||||
if reset != entry.reset {
|
|
||||||
entry.reset = reset;
|
|
||||||
entry.remaining = limit;
|
|
||||||
entry.limit = limit;
|
|
||||||
} else {
|
|
||||||
entry.remaining = remaining;
|
|
||||||
entry.limit = limit;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn can_send_request(
|
|
||||||
limit_type: LimitType,
|
|
||||||
instance_rate_limits: &Limits,
|
|
||||||
user_rate_limits: &Limits,
|
|
||||||
) -> bool {
|
|
||||||
// Check if all of the limits in this vec have at least one remaining request
|
|
||||||
|
|
||||||
let rate_limits = Limits::combine(instance_rate_limits, user_rate_limits);
|
|
||||||
|
|
||||||
let constant_limits: Vec<&LimitType> = [
|
|
||||||
&LimitType::Error,
|
|
||||||
&LimitType::Global,
|
|
||||||
&LimitType::Ip,
|
|
||||||
&limit_type,
|
|
||||||
]
|
|
||||||
.to_vec();
|
|
||||||
for limit in constant_limits.iter() {
|
|
||||||
match rate_limits.to_hash_map().get(limit) {
|
|
||||||
Some(limit) => {
|
|
||||||
if limit.remaining == 0 {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// AbsoluteRegister and AuthRegister can cancel each other out.
|
|
||||||
if limit.bucket == LimitType::AbsoluteRegister
|
|
||||||
&& rate_limits
|
|
||||||
.to_hash_map()
|
|
||||||
.get(&LimitType::AuthRegister)
|
|
||||||
.unwrap()
|
|
||||||
.remaining
|
|
||||||
== 0
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if limit.bucket == LimitType::AuthRegister
|
|
||||||
&& rate_limits
|
|
||||||
.to_hash_map()
|
|
||||||
.get(&LimitType::AbsoluteRegister)
|
|
||||||
.unwrap()
|
|
||||||
.remaining
|
|
||||||
== 0
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None => return false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
fn update_limits(
|
|
||||||
response: &Response,
|
|
||||||
limit_type: LimitType,
|
|
||||||
instance_rate_limits: &mut Limits,
|
|
||||||
user_rate_limits: &mut Limits,
|
|
||||||
) {
|
|
||||||
let mut rate_limits = LimitsMutRef::combine_mut_ref(instance_rate_limits, user_rate_limits);
|
|
||||||
|
|
||||||
let remaining = match response.headers().get("X-RateLimit-Remaining") {
|
|
||||||
Some(remaining) => remaining.to_str().unwrap().parse::<u64>().unwrap(),
|
|
||||||
None => rate_limits.get_limit_mut_ref(&limit_type).remaining - 1,
|
|
||||||
};
|
|
||||||
let limit = match response.headers().get("X-RateLimit-Limit") {
|
|
||||||
Some(limit) => limit.to_str().unwrap().parse::<u64>().unwrap(),
|
|
||||||
None => rate_limits.get_limit_mut_ref(&limit_type).limit,
|
|
||||||
};
|
|
||||||
let reset = match response.headers().get("X-RateLimit-Reset") {
|
|
||||||
Some(reset) => reset.to_str().unwrap().parse::<u64>().unwrap(),
|
|
||||||
None => rate_limits.get_limit_mut_ref(&limit_type).reset,
|
|
||||||
};
|
|
||||||
|
|
||||||
let status = response.status();
|
|
||||||
let status_str = status.as_str();
|
|
||||||
|
|
||||||
if status_str.starts_with('4') {
|
|
||||||
rate_limits
|
|
||||||
.get_limit_mut_ref(&LimitType::Error)
|
|
||||||
.add_remaining(-1);
|
|
||||||
}
|
|
||||||
|
|
||||||
rate_limits
|
|
||||||
.get_limit_mut_ref(&LimitType::Global)
|
|
||||||
.add_remaining(-1);
|
|
||||||
|
|
||||||
rate_limits
|
|
||||||
.get_limit_mut_ref(&LimitType::Ip)
|
|
||||||
.add_remaining(-1);
|
|
||||||
|
|
||||||
match limit_type {
|
|
||||||
LimitType::Error => {
|
|
||||||
let entry = rate_limits.get_limit_mut_ref(&LimitType::Error);
|
|
||||||
LimitedRequester::update_limit_entry(entry, reset, remaining, limit);
|
|
||||||
}
|
|
||||||
LimitType::Global => {
|
|
||||||
let entry = rate_limits.get_limit_mut_ref(&LimitType::Global);
|
|
||||||
LimitedRequester::update_limit_entry(entry, reset, remaining, limit);
|
|
||||||
}
|
|
||||||
LimitType::Ip => {
|
|
||||||
let entry = rate_limits.get_limit_mut_ref(&LimitType::Ip);
|
|
||||||
LimitedRequester::update_limit_entry(entry, reset, remaining, limit);
|
|
||||||
}
|
|
||||||
LimitType::AuthLogin => {
|
|
||||||
let entry = rate_limits.get_limit_mut_ref(&LimitType::AuthLogin);
|
|
||||||
LimitedRequester::update_limit_entry(entry, reset, remaining, limit);
|
|
||||||
}
|
|
||||||
LimitType::AbsoluteRegister => {
|
|
||||||
let entry = rate_limits.get_limit_mut_ref(&LimitType::AbsoluteRegister);
|
|
||||||
LimitedRequester::update_limit_entry(entry, reset, remaining, limit);
|
|
||||||
// AbsoluteRegister and AuthRegister both need to be updated, if a Register event
|
|
||||||
// happens.
|
|
||||||
rate_limits
|
|
||||||
.get_limit_mut_ref(&LimitType::AuthRegister)
|
|
||||||
.remaining -= 1;
|
|
||||||
}
|
|
||||||
LimitType::AuthRegister => {
|
|
||||||
let entry = rate_limits.get_limit_mut_ref(&LimitType::AuthRegister);
|
|
||||||
LimitedRequester::update_limit_entry(entry, reset, remaining, limit);
|
|
||||||
// AbsoluteRegister and AuthRegister both need to be updated, if a Register event
|
|
||||||
// happens.
|
|
||||||
rate_limits
|
|
||||||
.get_limit_mut_ref(&LimitType::AbsoluteRegister)
|
|
||||||
.remaining -= 1;
|
|
||||||
}
|
|
||||||
LimitType::AbsoluteMessage => {
|
|
||||||
let entry = rate_limits.get_limit_mut_ref(&LimitType::AbsoluteMessage);
|
|
||||||
LimitedRequester::update_limit_entry(entry, reset, remaining, limit);
|
|
||||||
}
|
|
||||||
LimitType::Channel => {
|
|
||||||
let entry = rate_limits.get_limit_mut_ref(&LimitType::Channel);
|
|
||||||
LimitedRequester::update_limit_entry(entry, reset, remaining, limit);
|
|
||||||
}
|
|
||||||
LimitType::Guild => {
|
|
||||||
let entry = rate_limits.get_limit_mut_ref(&LimitType::Guild);
|
|
||||||
LimitedRequester::update_limit_entry(entry, reset, remaining, limit);
|
|
||||||
}
|
|
||||||
LimitType::Webhook => {
|
|
||||||
let entry = rate_limits.get_limit_mut_ref(&LimitType::Webhook);
|
|
||||||
LimitedRequester::update_limit_entry(entry, reset, remaining, limit);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod rate_limit {
|
|
||||||
use serde_json::from_str;
|
|
||||||
|
|
||||||
use crate::{api::limits::Config, UrlBundle};
|
|
||||||
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn run_into_limit() {
|
|
||||||
let urls = UrlBundle::new(
|
|
||||||
String::from("http://localhost:3001/api/"),
|
|
||||||
String::from("wss://localhost:3001/"),
|
|
||||||
String::from("http://localhost:3001/cdn"),
|
|
||||||
);
|
|
||||||
let mut request: Option<ChorusResult<Response>> = None;
|
|
||||||
let mut instance = Instance::new(urls.clone()).await.unwrap();
|
|
||||||
let mut user_rate_limits = Limits::check_limits(urls.api.clone()).await;
|
|
||||||
|
|
||||||
for _ in 0..=50 {
|
|
||||||
let request_path = urls.api.clone() + "/some/random/nonexisting/path";
|
|
||||||
let request_builder = instance.client.get(request_path);
|
|
||||||
request = Some(
|
|
||||||
LimitedRequester::send_request(
|
|
||||||
request_builder,
|
|
||||||
LimitType::Channel,
|
|
||||||
&mut instance,
|
|
||||||
&mut user_rate_limits,
|
|
||||||
)
|
|
||||||
.await,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
assert!(matches!(request, Some(Err(_))));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn test_send_request() {
|
|
||||||
let urls = UrlBundle::new(
|
|
||||||
String::from("http://localhost:3001/api/"),
|
|
||||||
String::from("wss://localhost:3001/"),
|
|
||||||
String::from("http://localhost:3001/cdn"),
|
|
||||||
);
|
|
||||||
let mut instance = Instance::new(urls.clone()).await.unwrap();
|
|
||||||
let mut user_rate_limits = Limits::check_limits(urls.api.clone()).await;
|
|
||||||
let _requester = LimitedRequester;
|
|
||||||
let request_path = urls.api.clone() + "/policies/instance/limits";
|
|
||||||
let request_builder = instance.client.get(request_path);
|
|
||||||
let request = LimitedRequester::send_request(
|
|
||||||
request_builder,
|
|
||||||
LimitType::Channel,
|
|
||||||
&mut instance,
|
|
||||||
&mut user_rate_limits,
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
let result = match request {
|
|
||||||
Ok(result) => result,
|
|
||||||
Err(_) => panic!("Request failed"),
|
|
||||||
};
|
|
||||||
let _config: Config = from_str(result.text().await.unwrap().as_str()).unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,466 @@
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use log::{self, debug};
|
||||||
|
use reqwest::{Client, RequestBuilder, Response};
|
||||||
|
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},
|
||||||
|
};
|
||||||
|
|
||||||
|
/// 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.
|
||||||
|
pub struct ChorusRequest {
|
||||||
|
pub request: RequestBuilder,
|
||||||
|
pub limit_type: LimitType,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ChorusRequest {
|
||||||
|
/// 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> {
|
||||||
|
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
|
||||||
|
{
|
||||||
|
Ok(result) => {
|
||||||
|
debug!("Request successful: {:?}", result);
|
||||||
|
result
|
||||||
|
}
|
||||||
|
Err(error) => {
|
||||||
|
log::warn!("Request failed: {:?}", error);
|
||||||
|
return Err(ChorusError::RequestFailed {
|
||||||
|
url: error.url().unwrap().to_string(),
|
||||||
|
error,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
drop(belongs_to);
|
||||||
|
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()
|
||||||
|
.limits_information
|
||||||
|
.as_mut()
|
||||||
|
.unwrap()
|
||||||
|
.ratelimits
|
||||||
|
.get_mut(&LimitType::Global)
|
||||||
|
.unwrap()
|
||||||
|
.remaining = 0;
|
||||||
|
return Err(ChorusError::RateLimited {
|
||||||
|
bucket: format!("{:?}", self.limit_type),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
log::warn!("Request failed: {:?}", result);
|
||||||
|
return Err(ChorusRequest::interpret_error(result).await);
|
||||||
|
}
|
||||||
|
ChorusRequest::update_rate_limits(user, &self.limit_type, !result.status().is_success());
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn can_send_request(user: &mut UserMeta, limit_type: &LimitType) -> bool {
|
||||||
|
log::trace!("Checking if user or instance is rate-limited...");
|
||||||
|
let mut belongs_to = user.belongs_to.borrow_mut();
|
||||||
|
if belongs_to.limits_information.is_none() {
|
||||||
|
log::trace!("Instance indicates no rate limits are configured. Continuing.");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
let instance_dictated_limits = [
|
||||||
|
&LimitType::AuthLogin,
|
||||||
|
&LimitType::AuthRegister,
|
||||||
|
&LimitType::Global,
|
||||||
|
&LimitType::Ip,
|
||||||
|
];
|
||||||
|
let limits = match instance_dictated_limits.contains(&limit_type) {
|
||||||
|
true => {
|
||||||
|
log::trace!(
|
||||||
|
"Limit type {:?} is dictated by the instance. Continuing.",
|
||||||
|
limit_type
|
||||||
|
);
|
||||||
|
belongs_to
|
||||||
|
.limits_information
|
||||||
|
.as_mut()
|
||||||
|
.unwrap()
|
||||||
|
.ratelimits
|
||||||
|
.clone()
|
||||||
|
}
|
||||||
|
false => {
|
||||||
|
log::trace!(
|
||||||
|
"Limit type {:?} is dictated by the user. Continuing.",
|
||||||
|
limit_type
|
||||||
|
);
|
||||||
|
ChorusRequest::ensure_limit_in_map(
|
||||||
|
&belongs_to
|
||||||
|
.limits_information
|
||||||
|
.as_ref()
|
||||||
|
.unwrap()
|
||||||
|
.configuration,
|
||||||
|
user.limits.as_mut().unwrap(),
|
||||||
|
limit_type,
|
||||||
|
);
|
||||||
|
user.limits.as_mut().unwrap().clone()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let global = belongs_to
|
||||||
|
.limits_information
|
||||||
|
.as_ref()
|
||||||
|
.unwrap()
|
||||||
|
.ratelimits
|
||||||
|
.get(&LimitType::Global)
|
||||||
|
.unwrap();
|
||||||
|
let ip = belongs_to
|
||||||
|
.limits_information
|
||||||
|
.as_ref()
|
||||||
|
.unwrap()
|
||||||
|
.ratelimits
|
||||||
|
.get(&LimitType::Ip)
|
||||||
|
.unwrap();
|
||||||
|
let limit_type_limit = limits.get(limit_type).unwrap();
|
||||||
|
global.remaining > 0 && ip.remaining > 0 && limit_type_limit.remaining > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ensure_limit_in_map(
|
||||||
|
rate_limits_config: &RateLimits,
|
||||||
|
map: &mut HashMap<LimitType, Limit>,
|
||||||
|
limit_type: &LimitType,
|
||||||
|
) {
|
||||||
|
log::trace!("Ensuring limit type {:?} is in the map.", limit_type);
|
||||||
|
let time: u64 = chrono::Utc::now().timestamp() as u64;
|
||||||
|
match limit_type {
|
||||||
|
LimitType::Channel(snowflake) => {
|
||||||
|
if map.get(&LimitType::Channel(*snowflake)).is_some() {
|
||||||
|
log::trace!(
|
||||||
|
"Limit type {:?} is already in the map. Returning.",
|
||||||
|
limit_type
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
log::trace!("Limit type {:?} is not in the map. Adding it.", limit_type);
|
||||||
|
let channel_limit = &rate_limits_config.routes.channel;
|
||||||
|
map.insert(
|
||||||
|
LimitType::Channel(*snowflake),
|
||||||
|
Limit {
|
||||||
|
bucket: LimitType::Channel(*snowflake),
|
||||||
|
limit: channel_limit.count,
|
||||||
|
remaining: channel_limit.count,
|
||||||
|
reset: channel_limit.window + time,
|
||||||
|
window: channel_limit.window,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
LimitType::Guild(snowflake) => {
|
||||||
|
if map.get(&LimitType::Guild(*snowflake)).is_some() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let guild_limit = &rate_limits_config.routes.guild;
|
||||||
|
map.insert(
|
||||||
|
LimitType::Guild(*snowflake),
|
||||||
|
Limit {
|
||||||
|
bucket: LimitType::Guild(*snowflake),
|
||||||
|
limit: guild_limit.count,
|
||||||
|
remaining: guild_limit.count,
|
||||||
|
reset: guild_limit.window + time,
|
||||||
|
window: guild_limit.window,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
LimitType::Webhook(snowflake) => {
|
||||||
|
if map.get(&LimitType::Webhook(*snowflake)).is_some() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let webhook_limit = &rate_limits_config.routes.webhook;
|
||||||
|
map.insert(
|
||||||
|
LimitType::Webhook(*snowflake),
|
||||||
|
Limit {
|
||||||
|
bucket: LimitType::Webhook(*snowflake),
|
||||||
|
limit: webhook_limit.count,
|
||||||
|
remaining: webhook_limit.count,
|
||||||
|
reset: webhook_limit.window + time,
|
||||||
|
window: webhook_limit.window,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
other_limit => {
|
||||||
|
if map.get(other_limit).is_some() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let limits_map = ChorusRequest::limits_config_to_hashmap(rate_limits_config);
|
||||||
|
map.insert(
|
||||||
|
*other_limit,
|
||||||
|
Limit {
|
||||||
|
bucket: *other_limit,
|
||||||
|
limit: limits_map.get(other_limit).as_ref().unwrap().limit,
|
||||||
|
remaining: limits_map.get(other_limit).as_ref().unwrap().remaining,
|
||||||
|
reset: limits_map.get(other_limit).as_ref().unwrap().reset,
|
||||||
|
window: limits_map.get(other_limit).as_ref().unwrap().window,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn interpret_error(response: reqwest::Response) -> ChorusError {
|
||||||
|
match response.status().as_u16() {
|
||||||
|
401..=403 | 407 => ChorusError::NoPermission,
|
||||||
|
404 => ChorusError::NotFound {
|
||||||
|
error: response.text().await.unwrap(),
|
||||||
|
},
|
||||||
|
405 | 408 | 409 => ChorusError::ReceivedErrorCode { error_code: response.status().as_u16(), error: response.text().await.unwrap() },
|
||||||
|
411..=421 | 426 | 428 | 431 => ChorusError::InvalidArguments {
|
||||||
|
error: response.text().await.unwrap(),
|
||||||
|
},
|
||||||
|
429 => panic!("Illegal state: Rate limit exception should have been caught before this function call."),
|
||||||
|
451 => ChorusError::NoResponse,
|
||||||
|
500..=599 => ChorusError::ReceivedErrorCode { error_code: response.status().as_u16(), error: response.text().await.unwrap() },
|
||||||
|
_ => ChorusError::ReceivedErrorCode { error_code: response.status().as_u16(), error: response.text().await.unwrap()},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Updates the rate limits of the user. The following steps are performed:
|
||||||
|
/// 1. If the current unix timestamp is greater than the reset timestamp, the reset timestamp is
|
||||||
|
/// 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) {
|
||||||
|
let instance_dictated_limits = [
|
||||||
|
&LimitType::AuthLogin,
|
||||||
|
&LimitType::AuthRegister,
|
||||||
|
&LimitType::Global,
|
||||||
|
&LimitType::Ip,
|
||||||
|
];
|
||||||
|
// modify this to store something to look up the value with later, instead of storing a reference to the actual data itself.
|
||||||
|
let mut relevant_limits = Vec::new();
|
||||||
|
if instance_dictated_limits.contains(&limit_type) {
|
||||||
|
relevant_limits.push((LimitOrigin::Instance, *limit_type));
|
||||||
|
} else {
|
||||||
|
relevant_limits.push((LimitOrigin::User, *limit_type));
|
||||||
|
}
|
||||||
|
relevant_limits.push((LimitOrigin::Instance, LimitType::Global));
|
||||||
|
relevant_limits.push((LimitOrigin::Instance, LimitType::Ip));
|
||||||
|
if response_was_err {
|
||||||
|
relevant_limits.push((LimitOrigin::User, LimitType::Error));
|
||||||
|
}
|
||||||
|
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 limit = match relevant_limit.0 {
|
||||||
|
LimitOrigin::Instance => {
|
||||||
|
log::trace!(
|
||||||
|
"Updating instance rate limit. Bucket: {:?}",
|
||||||
|
relevant_limit.1
|
||||||
|
);
|
||||||
|
belongs_to
|
||||||
|
.limits_information
|
||||||
|
.as_mut()
|
||||||
|
.unwrap()
|
||||||
|
.ratelimits
|
||||||
|
.get_mut(&relevant_limit.1)
|
||||||
|
.unwrap()
|
||||||
|
}
|
||||||
|
LimitOrigin::User => {
|
||||||
|
log::trace!("Updating user rate limit. Bucket: {:?}", relevant_limit.1);
|
||||||
|
user.limits
|
||||||
|
.as_mut()
|
||||||
|
.unwrap()
|
||||||
|
.get_mut(&relevant_limit.1)
|
||||||
|
.unwrap()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if time > limit.reset {
|
||||||
|
// Spacebar does not yet return rate limit information in its response headers. We
|
||||||
|
// therefore have to guess the next rate limit window. This is not ideal. Oh well!
|
||||||
|
log::trace!("Rate limit replenished. Bucket: {:?}", limit.bucket);
|
||||||
|
limit.reset += limit.window;
|
||||||
|
limit.remaining = limit.limit;
|
||||||
|
}
|
||||||
|
limit.remaining -= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) async fn get_limits_config(url_api: &str) -> ChorusResult<LimitsConfiguration> {
|
||||||
|
let request = Client::new()
|
||||||
|
.get(format!("{}/policies/instance/limits/", url_api))
|
||||||
|
.send()
|
||||||
|
.await;
|
||||||
|
let request = match request {
|
||||||
|
Ok(request) => request,
|
||||||
|
Err(e) => {
|
||||||
|
return Err(ChorusError::RequestFailed {
|
||||||
|
url: url_api.to_string(),
|
||||||
|
error: e,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let limits_configuration = match request.status().as_u16() {
|
||||||
|
200 => from_str::<LimitsConfiguration>(&request.text().await.unwrap()).unwrap(),
|
||||||
|
429 => {
|
||||||
|
return Err(ChorusError::RateLimited {
|
||||||
|
bucket: format!("{:?}", LimitType::Ip),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
404 => return Err(ChorusError::NotFound { error: "Route \"/policies/instance/limits/\" not found. Are you perhaps trying to request the Limits configuration from an unsupported server?".to_string() }),
|
||||||
|
400..=u16::MAX => {
|
||||||
|
return Err(ChorusError::ReceivedErrorCode { error_code: request.status().as_u16(), error: request.text().await.unwrap() })
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
return Err(ChorusError::InvalidResponse {
|
||||||
|
error: request.text().await.unwrap(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(limits_configuration)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn limits_config_to_hashmap(
|
||||||
|
limits_configuration: &RateLimits,
|
||||||
|
) -> HashMap<LimitType, Limit> {
|
||||||
|
let config = limits_configuration.clone();
|
||||||
|
let routes = config.routes;
|
||||||
|
let mut map: HashMap<LimitType, Limit> = HashMap::new();
|
||||||
|
let time: u64 = chrono::Utc::now().timestamp() as u64;
|
||||||
|
map.insert(
|
||||||
|
LimitType::AuthLogin,
|
||||||
|
Limit {
|
||||||
|
bucket: LimitType::AuthLogin,
|
||||||
|
limit: routes.auth.login.count,
|
||||||
|
remaining: routes.auth.login.count,
|
||||||
|
reset: routes.auth.login.window + time,
|
||||||
|
window: routes.auth.login.window,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
map.insert(
|
||||||
|
LimitType::AuthRegister,
|
||||||
|
Limit {
|
||||||
|
bucket: LimitType::AuthRegister,
|
||||||
|
limit: routes.auth.register.count,
|
||||||
|
remaining: routes.auth.register.count,
|
||||||
|
reset: routes.auth.register.window + time,
|
||||||
|
window: routes.auth.register.window,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
map.insert(
|
||||||
|
LimitType::ChannelBaseline,
|
||||||
|
Limit {
|
||||||
|
bucket: LimitType::ChannelBaseline,
|
||||||
|
limit: routes.channel.count,
|
||||||
|
remaining: routes.channel.count,
|
||||||
|
reset: routes.channel.window + time,
|
||||||
|
window: routes.channel.window,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
map.insert(
|
||||||
|
LimitType::Error,
|
||||||
|
Limit {
|
||||||
|
bucket: LimitType::Error,
|
||||||
|
limit: config.error.count,
|
||||||
|
remaining: config.error.count,
|
||||||
|
reset: config.error.window + time,
|
||||||
|
window: config.error.window,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
map.insert(
|
||||||
|
LimitType::Global,
|
||||||
|
Limit {
|
||||||
|
bucket: LimitType::Global,
|
||||||
|
limit: config.global.count,
|
||||||
|
remaining: config.global.count,
|
||||||
|
reset: config.global.window + time,
|
||||||
|
window: config.global.window,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
map.insert(
|
||||||
|
LimitType::Ip,
|
||||||
|
Limit {
|
||||||
|
bucket: LimitType::Ip,
|
||||||
|
limit: config.ip.count,
|
||||||
|
remaining: config.ip.count,
|
||||||
|
reset: config.ip.window + time,
|
||||||
|
window: config.ip.window,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
map.insert(
|
||||||
|
LimitType::GuildBaseline,
|
||||||
|
Limit {
|
||||||
|
bucket: LimitType::GuildBaseline,
|
||||||
|
limit: routes.guild.count,
|
||||||
|
remaining: routes.guild.count,
|
||||||
|
reset: routes.guild.window + time,
|
||||||
|
window: routes.guild.window,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
map.insert(
|
||||||
|
LimitType::WebhookBaseline,
|
||||||
|
Limit {
|
||||||
|
bucket: LimitType::WebhookBaseline,
|
||||||
|
limit: routes.webhook.count,
|
||||||
|
remaining: routes.webhook.count,
|
||||||
|
reset: routes.webhook.window + time,
|
||||||
|
window: routes.webhook.window,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
map
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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<()> {
|
||||||
|
match self.send_request(user).await {
|
||||||
|
Ok(_) => Ok(()),
|
||||||
|
Err(e) => Err(e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sends a [`ChorusRequest`] and returns a [`ChorusResult`] that contains a [`T`] if the request
|
||||||
|
/// was successful, or a [`ChorusError`] if the request failed.
|
||||||
|
pub(crate) async fn deserialize_response<T: for<'a> Deserialize<'a>>(
|
||||||
|
self,
|
||||||
|
user: &mut UserMeta,
|
||||||
|
) -> ChorusResult<T> {
|
||||||
|
let response = self.send_request(user).await?;
|
||||||
|
debug!("Got response: {:?}", response);
|
||||||
|
let response_text = match response.text().await {
|
||||||
|
Ok(string) => string,
|
||||||
|
Err(e) => {
|
||||||
|
return Err(ChorusError::InvalidResponse {
|
||||||
|
error: format!(
|
||||||
|
"Error while trying to process the HTTP response into a String: {}",
|
||||||
|
e
|
||||||
|
),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let object = match from_str::<T>(&response_text) {
|
||||||
|
Ok(object) => object,
|
||||||
|
Err(e) => {
|
||||||
|
return Err(ChorusError::InvalidResponse {
|
||||||
|
error: format!(
|
||||||
|
"Error while trying to deserialize the JSON response into requested type T: {}. JSON Response: {}",
|
||||||
|
e, response_text
|
||||||
|
),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Ok(object)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum LimitOrigin {
|
||||||
|
Instance,
|
||||||
|
User,
|
||||||
|
}
|
|
@ -18,10 +18,8 @@ pub struct GeneralConfiguration {
|
||||||
impl Default for GeneralConfiguration {
|
impl Default for GeneralConfiguration {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
instance_name: String::from("Spacebar Instance"),
|
instance_name: String::from("Spacebar-compatible Instance"),
|
||||||
instance_description: Some(String::from(
|
instance_description: Some(String::from("This is a spacebar-compatible instance.")),
|
||||||
"This is a Spacebar instance made in the pre-release days",
|
|
||||||
)),
|
|
||||||
front_page: None,
|
front_page: None,
|
||||||
tos_page: None,
|
tos_page: None,
|
||||||
correspondence_email: None,
|
correspondence_email: None,
|
||||||
|
|
|
@ -10,7 +10,7 @@ use sqlx::{
|
||||||
database::{HasArguments, HasValueRef},
|
database::{HasArguments, HasValueRef},
|
||||||
encode::IsNull,
|
encode::IsNull,
|
||||||
error::BoxDynError,
|
error::BoxDynError,
|
||||||
Decode, Encode, MySql,
|
Decode, MySql,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::types::config::types::subconfigs::guild::{
|
use crate::types::config::types::subconfigs::guild::{
|
||||||
|
@ -139,7 +139,7 @@ pub enum GuildFeatures {
|
||||||
InvitesClosed,
|
InvitesClosed,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize, Eq)]
|
||||||
pub struct GuildFeaturesList(Vec<GuildFeatures>);
|
pub struct GuildFeaturesList(Vec<GuildFeatures>);
|
||||||
|
|
||||||
impl Deref for GuildFeaturesList {
|
impl Deref for GuildFeaturesList {
|
||||||
|
|
|
@ -1,7 +1,12 @@
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::types::config::types::subconfigs::limits::ratelimits::{
|
use crate::{
|
||||||
route::RouteRateLimit, RateLimitOptions,
|
api::LimitType,
|
||||||
|
types::config::types::subconfigs::limits::ratelimits::{
|
||||||
|
route::RouteRateLimit, RateLimitOptions,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
@ -39,3 +44,18 @@ impl Default for RateLimits {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl RateLimits {
|
||||||
|
pub fn to_hash_map(&self) -> HashMap<LimitType, RateLimitOptions> {
|
||||||
|
let mut map = HashMap::new();
|
||||||
|
map.insert(LimitType::AuthLogin, self.routes.auth.login.clone());
|
||||||
|
map.insert(LimitType::AuthRegister, self.routes.auth.register.clone());
|
||||||
|
map.insert(LimitType::ChannelBaseline, self.routes.channel.clone());
|
||||||
|
map.insert(LimitType::Error, self.error.clone());
|
||||||
|
map.insert(LimitType::Global, self.global.clone());
|
||||||
|
map.insert(LimitType::Ip, self.ip.clone());
|
||||||
|
map.insert(LimitType::WebhookBaseline, self.routes.webhook.clone());
|
||||||
|
map.insert(LimitType::GuildBaseline, self.routes.guild.clone());
|
||||||
|
map
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,68 +1,70 @@
|
||||||
|
use chorus_macros::Updateable;
|
||||||
use chrono::Utc;
|
use chrono::Utc;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_aux::prelude::deserialize_string_from_number;
|
use serde_aux::prelude::deserialize_string_from_number;
|
||||||
use serde_repr::{Deserialize_repr, Serialize_repr};
|
use serde_repr::{Deserialize_repr, Serialize_repr};
|
||||||
|
|
||||||
|
use crate::gateway::Updateable;
|
||||||
use crate::types::{
|
use crate::types::{
|
||||||
entities::{GuildMember, User},
|
entities::{GuildMember, User},
|
||||||
utils::Snowflake,
|
utils::Snowflake,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Default, Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
|
#[derive(Default, Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Updateable)]
|
||||||
#[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))]
|
#[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))]
|
||||||
pub struct Channel {
|
pub struct Channel {
|
||||||
pub id: Snowflake,
|
|
||||||
pub created_at: Option<chrono::DateTime<Utc>>,
|
|
||||||
#[serde(rename = "type")]
|
|
||||||
pub channel_type: ChannelType,
|
|
||||||
pub guild_id: Option<Snowflake>,
|
|
||||||
pub position: Option<i32>,
|
|
||||||
#[cfg(feature = "sqlx")]
|
|
||||||
pub permission_overwrites: Option<sqlx::types::Json<Vec<PermissionOverwrite>>>,
|
|
||||||
#[cfg(not(feature = "sqlx"))]
|
|
||||||
pub permission_overwrites: Option<Vec<PermissionOverwrite>>,
|
|
||||||
pub name: Option<String>,
|
|
||||||
pub topic: Option<String>,
|
|
||||||
pub nsfw: Option<bool>,
|
|
||||||
pub last_message_id: Option<Snowflake>,
|
|
||||||
pub bitrate: Option<i32>,
|
|
||||||
pub user_limit: Option<i32>,
|
|
||||||
pub rate_limit_per_user: Option<i32>,
|
|
||||||
#[cfg_attr(feature = "sqlx", sqlx(skip))]
|
|
||||||
pub recipients: Option<Vec<User>>,
|
|
||||||
pub icon: Option<String>,
|
|
||||||
pub owner_id: Option<Snowflake>,
|
|
||||||
pub application_id: Option<Snowflake>,
|
pub application_id: Option<Snowflake>,
|
||||||
pub managed: Option<bool>,
|
|
||||||
pub parent_id: Option<Snowflake>,
|
|
||||||
pub last_pin_timestamp: Option<String>,
|
|
||||||
pub rtc_region: Option<String>,
|
|
||||||
pub video_quality_mode: Option<i32>,
|
|
||||||
pub message_count: Option<i32>,
|
|
||||||
pub member_count: Option<i32>,
|
|
||||||
#[cfg_attr(feature = "sqlx", sqlx(skip))]
|
|
||||||
pub thread_metadata: Option<ThreadMetadata>,
|
|
||||||
#[cfg_attr(feature = "sqlx", sqlx(skip))]
|
|
||||||
pub member: Option<ThreadMember>,
|
|
||||||
pub default_auto_archive_duration: Option<i32>,
|
|
||||||
pub permissions: Option<String>,
|
|
||||||
pub flags: Option<i32>,
|
|
||||||
pub total_message_sent: Option<i32>,
|
|
||||||
#[cfg(feature = "sqlx")]
|
|
||||||
pub available_tags: Option<sqlx::types::Json<Vec<Tag>>>,
|
|
||||||
#[cfg(not(feature = "sqlx"))]
|
|
||||||
pub available_tags: Option<Vec<Tag>>,
|
|
||||||
#[cfg(feature = "sqlx")]
|
#[cfg(feature = "sqlx")]
|
||||||
pub applied_tags: Option<sqlx::types::Json<Vec<String>>>,
|
pub applied_tags: Option<sqlx::types::Json<Vec<String>>>,
|
||||||
#[cfg(not(feature = "sqlx"))]
|
#[cfg(not(feature = "sqlx"))]
|
||||||
pub applied_tags: Option<Vec<String>>,
|
pub applied_tags: Option<Vec<String>>,
|
||||||
#[cfg(feature = "sqlx")]
|
#[cfg(feature = "sqlx")]
|
||||||
|
pub available_tags: Option<sqlx::types::Json<Vec<Tag>>>,
|
||||||
|
#[cfg(not(feature = "sqlx"))]
|
||||||
|
pub available_tags: Option<Vec<Tag>>,
|
||||||
|
pub bitrate: Option<i32>,
|
||||||
|
#[serde(rename = "type")]
|
||||||
|
pub channel_type: ChannelType,
|
||||||
|
pub created_at: Option<chrono::DateTime<Utc>>,
|
||||||
|
pub default_auto_archive_duration: Option<i32>,
|
||||||
|
pub default_forum_layout: Option<i32>,
|
||||||
|
#[cfg(feature = "sqlx")]
|
||||||
pub default_reaction_emoji: Option<sqlx::types::Json<DefaultReaction>>,
|
pub default_reaction_emoji: Option<sqlx::types::Json<DefaultReaction>>,
|
||||||
#[cfg(not(feature = "sqlx"))]
|
#[cfg(not(feature = "sqlx"))]
|
||||||
pub default_reaction_emoji: Option<DefaultReaction>,
|
pub default_reaction_emoji: Option<DefaultReaction>,
|
||||||
pub default_thread_rate_limit_per_user: Option<i32>,
|
|
||||||
pub default_sort_order: Option<i32>,
|
pub default_sort_order: Option<i32>,
|
||||||
pub default_forum_layout: Option<i32>,
|
pub default_thread_rate_limit_per_user: Option<i32>,
|
||||||
|
pub flags: Option<i32>,
|
||||||
|
pub guild_id: Option<Snowflake>,
|
||||||
|
pub icon: Option<String>,
|
||||||
|
pub id: Snowflake,
|
||||||
|
pub last_message_id: Option<Snowflake>,
|
||||||
|
pub last_pin_timestamp: Option<String>,
|
||||||
|
pub managed: Option<bool>,
|
||||||
|
#[cfg_attr(feature = "sqlx", sqlx(skip))]
|
||||||
|
pub member: Option<ThreadMember>,
|
||||||
|
pub member_count: Option<i32>,
|
||||||
|
pub message_count: Option<i32>,
|
||||||
|
pub name: Option<String>,
|
||||||
|
pub nsfw: Option<bool>,
|
||||||
|
pub owner_id: Option<Snowflake>,
|
||||||
|
pub parent_id: Option<Snowflake>,
|
||||||
|
#[cfg(feature = "sqlx")]
|
||||||
|
pub permission_overwrites: Option<sqlx::types::Json<Vec<PermissionOverwrite>>>,
|
||||||
|
#[cfg(not(feature = "sqlx"))]
|
||||||
|
pub permission_overwrites: Option<Vec<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>>,
|
||||||
|
pub rtc_region: Option<String>,
|
||||||
|
#[cfg_attr(feature = "sqlx", sqlx(skip))]
|
||||||
|
pub thread_metadata: Option<ThreadMetadata>,
|
||||||
|
pub topic: Option<String>,
|
||||||
|
pub total_message_sent: Option<i32>,
|
||||||
|
pub user_limit: Option<i32>,
|
||||||
|
pub video_quality_mode: Option<i32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)]
|
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)]
|
||||||
|
@ -74,7 +76,7 @@ pub struct Tag {
|
||||||
pub emoji_name: Option<String>,
|
pub emoji_name: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
|
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, PartialOrd)]
|
||||||
pub struct PermissionOverwrite {
|
pub struct PermissionOverwrite {
|
||||||
pub id: Snowflake,
|
pub id: Snowflake,
|
||||||
#[serde(rename = "type")]
|
#[serde(rename = "type")]
|
||||||
|
|
|
@ -3,7 +3,7 @@ use serde::{Deserialize, Serialize};
|
||||||
use crate::types::entities::User;
|
use crate::types::entities::User;
|
||||||
use crate::types::Snowflake;
|
use crate::types::Snowflake;
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Clone, Deserialize, Serialize, Default)]
|
#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize, Default)]
|
||||||
#[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))]
|
#[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))]
|
||||||
pub struct Emoji {
|
pub struct Emoji {
|
||||||
pub id: Option<Snowflake>,
|
pub id: Option<Snowflake>,
|
||||||
|
|
|
@ -91,7 +91,7 @@ pub struct Guild {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// See https://docs.spacebar.chat/routes/#get-/guilds/-guild_id-/bans/-user-
|
/// See https://docs.spacebar.chat/routes/#get-/guilds/-guild_id-/bans/-user-
|
||||||
#[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq)]
|
#[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq, Eq)]
|
||||||
#[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))]
|
#[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))]
|
||||||
pub struct GuildBan {
|
pub struct GuildBan {
|
||||||
pub user_id: Snowflake,
|
pub user_id: Snowflake,
|
||||||
|
|
|
@ -0,0 +1,75 @@
|
||||||
|
use chrono::{DateTime, Utc};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::types::{Snowflake, WelcomeScreenObject};
|
||||||
|
|
||||||
|
use super::guild::GuildScheduledEvent;
|
||||||
|
use super::{Application, Channel, GuildMember, User};
|
||||||
|
|
||||||
|
/// Represents a code that when used, adds a user to a guild or group DM channel, or creates a relationship between two users.
|
||||||
|
/// See <https://discord-userdoccers.vercel.app/resources/invite#invite-object>
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
pub struct Invite {
|
||||||
|
pub approximate_member_count: Option<i32>,
|
||||||
|
pub approximate_presence_count: Option<i32>,
|
||||||
|
pub channel: Option<Channel>,
|
||||||
|
pub code: String,
|
||||||
|
pub created_at: Option<DateTime<Utc>>,
|
||||||
|
pub expires_at: Option<DateTime<Utc>>,
|
||||||
|
pub flags: Option<i32>,
|
||||||
|
pub guild: Option<InviteGuild>,
|
||||||
|
pub guild_id: Option<Snowflake>,
|
||||||
|
pub guild_scheduled_event: Option<GuildScheduledEvent>,
|
||||||
|
#[serde(rename = "type")]
|
||||||
|
pub invite_type: Option<i32>,
|
||||||
|
pub inviter: Option<User>,
|
||||||
|
pub max_age: Option<i32>,
|
||||||
|
pub max_uses: Option<i32>,
|
||||||
|
pub stage_instance: Option<InviteStageInstance>,
|
||||||
|
pub target_application: Option<Application>,
|
||||||
|
pub target_type: Option<i32>,
|
||||||
|
pub target_user: Option<User>,
|
||||||
|
pub temporary: Option<bool>,
|
||||||
|
pub uses: Option<i32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The guild an invite is for.
|
||||||
|
/// See <https://discord-userdoccers.vercel.app/resources/invite#invite-guild-object>
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
pub struct InviteGuild {
|
||||||
|
pub id: Snowflake,
|
||||||
|
pub name: String,
|
||||||
|
pub icon: Option<String>,
|
||||||
|
pub splash: Option<String>,
|
||||||
|
pub verification_level: i32,
|
||||||
|
pub features: Vec<String>,
|
||||||
|
pub vanity_url_code: Option<String>,
|
||||||
|
pub description: Option<String>,
|
||||||
|
pub banner: Option<String>,
|
||||||
|
pub premium_subscription_count: Option<i32>,
|
||||||
|
#[serde(rename = "nsfw")]
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub nsfw_deprecated: Option<bool>,
|
||||||
|
pub nsfw_level: NSFWLevel,
|
||||||
|
pub welcome_screen: Option<WelcomeScreenObject>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// See <https://discord-userdoccers.vercel.app/resources/guild#nsfw-level> for an explanation on what
|
||||||
|
/// the levels mean.
|
||||||
|
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
||||||
|
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
|
||||||
|
pub enum NSFWLevel {
|
||||||
|
Default = 0,
|
||||||
|
Explicit = 1,
|
||||||
|
Safe = 2,
|
||||||
|
AgeRestricted = 3,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// See <https://discord-userdoccers.vercel.app/resources/invite#invite-stage-instance-object>
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
pub struct InviteStageInstance {
|
||||||
|
pub members: Vec<GuildMember>,
|
||||||
|
pub participant_count: i32,
|
||||||
|
pub speaker_count: i32,
|
||||||
|
pub topic: String,
|
||||||
|
}
|
|
@ -8,6 +8,7 @@ pub use emoji::*;
|
||||||
pub use guild::*;
|
pub use guild::*;
|
||||||
pub use guild_member::*;
|
pub use guild_member::*;
|
||||||
pub use integration::*;
|
pub use integration::*;
|
||||||
|
pub use invite::*;
|
||||||
pub use message::*;
|
pub use message::*;
|
||||||
pub use relationship::*;
|
pub use relationship::*;
|
||||||
pub use role::*;
|
pub use role::*;
|
||||||
|
@ -31,6 +32,7 @@ mod emoji;
|
||||||
mod guild;
|
mod guild;
|
||||||
mod guild_member;
|
mod guild_member;
|
||||||
mod integration;
|
mod integration;
|
||||||
|
mod invite;
|
||||||
mod message;
|
mod message;
|
||||||
mod relationship;
|
mod relationship;
|
||||||
mod role;
|
mod role;
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
|
use crate::types::utils::Snowflake;
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_aux::prelude::deserialize_option_number_from_string;
|
use serde_aux::prelude::deserialize_option_number_from_string;
|
||||||
|
|
||||||
use crate::types::utils::Snowflake;
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
|
||||||
#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
|
#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
|
||||||
pub struct UserData {
|
pub struct UserData {
|
||||||
|
@ -16,7 +15,6 @@ impl User {
|
||||||
PublicUser::from(self)
|
PublicUser::from(self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq, Eq)]
|
#[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq, Eq)]
|
||||||
#[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))]
|
#[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))]
|
||||||
pub struct User {
|
pub struct User {
|
||||||
|
@ -45,9 +43,9 @@ pub struct User {
|
||||||
pub bio: Option<String>,
|
pub bio: Option<String>,
|
||||||
pub theme_colors: Option<Vec<u8>>,
|
pub theme_colors: Option<Vec<u8>>,
|
||||||
pub phone: Option<String>,
|
pub phone: Option<String>,
|
||||||
pub nsfw_allowed: bool,
|
pub nsfw_allowed: Option<bool>,
|
||||||
pub premium: bool,
|
pub premium: Option<bool>,
|
||||||
pub purchased_flags: i32,
|
pub purchased_flags: Option<i32>,
|
||||||
pub premium_usage_flags: Option<i32>,
|
pub premium_usage_flags: Option<i32>,
|
||||||
pub disabled: Option<bool>,
|
pub disabled: Option<bool>,
|
||||||
}
|
}
|
||||||
|
@ -89,6 +87,7 @@ impl From<User> for PublicUser {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)] // FIXME: Remove this when we actually use this
|
||||||
const CUSTOM_USER_FLAG_OFFSET: u64 = 1 << 32;
|
const CUSTOM_USER_FLAG_OFFSET: u64 = 1 << 32;
|
||||||
|
|
||||||
bitflags::bitflags! {
|
bitflags::bitflags! {
|
||||||
|
|
|
@ -3,6 +3,8 @@ use crate::types::{entities::Channel, Snowflake};
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use super::UpdateMessage;
|
||||||
|
|
||||||
#[derive(Debug, Default, Deserialize, Serialize)]
|
#[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 struct ChannelPinsUpdate {
|
||||||
|
@ -31,6 +33,15 @@ pub struct ChannelUpdate {
|
||||||
|
|
||||||
impl WebSocketEvent for ChannelUpdate {}
|
impl WebSocketEvent for ChannelUpdate {}
|
||||||
|
|
||||||
|
impl UpdateMessage<Channel> for ChannelUpdate {
|
||||||
|
fn update(&self, object_to_update: &mut Channel) {
|
||||||
|
*object_to_update = self.channel.clone();
|
||||||
|
}
|
||||||
|
fn id(&self) -> Snowflake {
|
||||||
|
self.channel.id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default, Deserialize, Serialize, Clone)]
|
#[derive(Debug, Default, Deserialize, Serialize, Clone)]
|
||||||
/// Officially undocumented.
|
/// Officially undocumented.
|
||||||
/// Sends updates to client about a new message with its id
|
/// Sends updates to client about a new message with its id
|
||||||
|
|
|
@ -14,8 +14,7 @@ impl WebSocketEvent for GatewayHello {}
|
||||||
/// Contains info on how often the client should send heartbeats to the server;
|
/// Contains info on how often the client should send heartbeats to the server;
|
||||||
pub struct HelloData {
|
pub struct HelloData {
|
||||||
/// How often a client should send heartbeats, in milliseconds
|
/// How often a client should send heartbeats, in milliseconds
|
||||||
// u128 because std used u128s for milliseconds
|
pub heartbeat_interval: u64,
|
||||||
pub heartbeat_interval: u128,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WebSocketEvent for HelloData {}
|
impl WebSocketEvent for HelloData {}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::gateway::Updateable;
|
||||||
pub use application::*;
|
pub use application::*;
|
||||||
pub use auto_moderation::*;
|
pub use auto_moderation::*;
|
||||||
pub use call::*;
|
pub use call::*;
|
||||||
|
@ -25,9 +26,10 @@ pub use thread::*;
|
||||||
pub use user::*;
|
pub use user::*;
|
||||||
pub use voice::*;
|
pub use voice::*;
|
||||||
pub use webhooks::*;
|
pub use webhooks::*;
|
||||||
|
|
||||||
pub use webrtc::*;
|
pub use webrtc::*;
|
||||||
|
|
||||||
|
use super::Snowflake;
|
||||||
|
|
||||||
mod application;
|
mod application;
|
||||||
mod auto_moderation;
|
mod auto_moderation;
|
||||||
mod call;
|
mod call;
|
||||||
|
@ -99,3 +101,23 @@ pub struct GatewayReceivePayload<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> WebSocketEvent for GatewayReceivePayload<'a> {}
|
impl<'a> WebSocketEvent for GatewayReceivePayload<'a> {}
|
||||||
|
|
||||||
|
/// An [`UpdateMessage<T>`] represents a received Gateway Message which contains updated
|
||||||
|
/// information for an [`Updateable`] of Type T.
|
||||||
|
/// # Example:
|
||||||
|
/// ```rs
|
||||||
|
/// impl UpdateMessage<Channel> for ChannelUpdate {
|
||||||
|
/// fn update(...) {...}
|
||||||
|
/// fn id(...) {...}
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
/// 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
|
||||||
|
where
|
||||||
|
T: Updateable,
|
||||||
|
{
|
||||||
|
fn update(&self, object_to_update: &mut T);
|
||||||
|
fn id(&self) -> Snowflake;
|
||||||
|
}
|
||||||
|
|
|
@ -1,122 +1,8 @@
|
||||||
use regex::Regex;
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::errors::FieldFormatError;
|
#[derive(Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
|
||||||
|
|
||||||
/**
|
|
||||||
A struct that represents a well-formed email address.
|
|
||||||
*/
|
|
||||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
|
||||||
pub struct AuthEmail {
|
|
||||||
pub email: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AuthEmail {
|
|
||||||
/**
|
|
||||||
Returns a new [`Result<AuthEmail, FieldFormatError>`].
|
|
||||||
## Arguments
|
|
||||||
The email address you want to validate.
|
|
||||||
## Errors
|
|
||||||
You will receive a [`FieldFormatError`], if:
|
|
||||||
- The email address is not in a valid format.
|
|
||||||
|
|
||||||
*/
|
|
||||||
pub fn new(email: String) -> Result<AuthEmail, FieldFormatError> {
|
|
||||||
let regex = Regex::new(r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$").unwrap();
|
|
||||||
if !regex.is_match(email.as_str()) {
|
|
||||||
return Err(FieldFormatError::EmailError);
|
|
||||||
}
|
|
||||||
Ok(AuthEmail { email })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
A struct that represents a well-formed username.
|
|
||||||
## Arguments
|
|
||||||
Please use new() to create a new instance of this struct.
|
|
||||||
## Errors
|
|
||||||
You will receive a [`FieldFormatError`], if:
|
|
||||||
- The username is not between 2 and 32 characters.
|
|
||||||
*/
|
|
||||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
|
||||||
pub struct AuthUsername {
|
|
||||||
pub username: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AuthUsername {
|
|
||||||
/**
|
|
||||||
Returns a new [`Result<AuthUsername, FieldFormatError>`].
|
|
||||||
## Arguments
|
|
||||||
The username you want to validate.
|
|
||||||
## Errors
|
|
||||||
You will receive a [`FieldFormatError`], if:
|
|
||||||
- The username is not between 2 and 32 characters.
|
|
||||||
*/
|
|
||||||
pub fn new(username: String) -> Result<AuthUsername, FieldFormatError> {
|
|
||||||
if username.len() < 2 || username.len() > 32 {
|
|
||||||
Err(FieldFormatError::UsernameError)
|
|
||||||
} else {
|
|
||||||
Ok(AuthUsername { username })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
A struct that represents a well-formed password.
|
|
||||||
## Arguments
|
|
||||||
Please use new() to create a new instance of this struct.
|
|
||||||
## Errors
|
|
||||||
You will receive a [`FieldFormatError`], if:
|
|
||||||
- The password is not between 1 and 72 characters.
|
|
||||||
*/
|
|
||||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
|
||||||
pub struct AuthPassword {
|
|
||||||
pub password: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AuthPassword {
|
|
||||||
/**
|
|
||||||
Returns a new [`Result<AuthPassword, FieldFormatError>`].
|
|
||||||
## Arguments
|
|
||||||
The password you want to validate.
|
|
||||||
## Errors
|
|
||||||
You will receive a [`FieldFormatError`], if:
|
|
||||||
- The password is not between 1 and 72 characters.
|
|
||||||
*/
|
|
||||||
pub fn new(password: String) -> Result<AuthPassword, FieldFormatError> {
|
|
||||||
if password.is_empty() || password.len() > 72 {
|
|
||||||
Err(FieldFormatError::PasswordError)
|
|
||||||
} else {
|
|
||||||
Ok(AuthPassword { password })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
A struct that represents a well-formed register request.
|
|
||||||
## Arguments
|
|
||||||
Please use new() to create a new instance of this struct.
|
|
||||||
## Errors
|
|
||||||
You will receive a [`FieldFormatError`], if:
|
|
||||||
- The username is not between 2 and 32 characters.
|
|
||||||
- The password is not between 1 and 72 characters.
|
|
||||||
*/
|
|
||||||
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
|
|
||||||
#[serde(rename_all = "snake_case")]
|
#[serde(rename_all = "snake_case")]
|
||||||
pub struct RegisterSchema {
|
pub struct RegisterSchema {
|
||||||
username: String,
|
|
||||||
password: Option<String>,
|
|
||||||
consent: bool,
|
|
||||||
email: Option<String>,
|
|
||||||
fingerprint: Option<String>,
|
|
||||||
invite: Option<String>,
|
|
||||||
date_of_birth: Option<String>,
|
|
||||||
gift_code_sku_id: Option<String>,
|
|
||||||
captcha_key: Option<String>,
|
|
||||||
promotional_email_opt_in: Option<bool>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct RegisterSchemaOptions {
|
|
||||||
pub username: String,
|
pub username: String,
|
||||||
pub password: Option<String>,
|
pub password: Option<String>,
|
||||||
pub consent: bool,
|
pub consent: bool,
|
||||||
|
@ -129,123 +15,21 @@ pub struct RegisterSchemaOptions {
|
||||||
pub promotional_email_opt_in: Option<bool>,
|
pub promotional_email_opt_in: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RegisterSchema {
|
|
||||||
pub fn builder(username: impl Into<String>, consent: bool) -> RegisterSchemaOptions {
|
|
||||||
RegisterSchemaOptions {
|
|
||||||
username: username.into(),
|
|
||||||
password: None,
|
|
||||||
consent,
|
|
||||||
email: None,
|
|
||||||
fingerprint: None,
|
|
||||||
invite: None,
|
|
||||||
date_of_birth: None,
|
|
||||||
gift_code_sku_id: None,
|
|
||||||
captcha_key: None,
|
|
||||||
promotional_email_opt_in: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RegisterSchemaOptions {
|
|
||||||
/**
|
|
||||||
Create a new [`RegisterSchema`].
|
|
||||||
## Arguments
|
|
||||||
All but "String::username" and "bool::consent" are optional.
|
|
||||||
|
|
||||||
## Errors
|
|
||||||
You will receive a [`FieldFormatError`], if:
|
|
||||||
- The username is less than 2 or more than 32 characters in length
|
|
||||||
- You supply a `password` which is less than 1 or more than 72 characters in length.
|
|
||||||
|
|
||||||
These constraints have been defined [in the Spacebar-API](https://docs.spacebar.chat/routes/)
|
|
||||||
*/
|
|
||||||
pub fn build(self) -> Result<RegisterSchema, FieldFormatError> {
|
|
||||||
let username = AuthUsername::new(self.username)?.username;
|
|
||||||
|
|
||||||
let email = if let Some(email) = self.email {
|
|
||||||
Some(AuthEmail::new(email)?.email)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
let password = if let Some(password) = self.password {
|
|
||||||
Some(AuthPassword::new(password)?.password)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
if !self.consent {
|
|
||||||
return Err(FieldFormatError::ConsentError);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(RegisterSchema {
|
|
||||||
username,
|
|
||||||
password,
|
|
||||||
consent: self.consent,
|
|
||||||
email,
|
|
||||||
fingerprint: self.fingerprint,
|
|
||||||
invite: self.invite,
|
|
||||||
date_of_birth: self.date_of_birth,
|
|
||||||
gift_code_sku_id: self.gift_code_sku_id,
|
|
||||||
captcha_key: self.captcha_key,
|
|
||||||
promotional_email_opt_in: self.promotional_email_opt_in,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
A struct that represents a well-formed login request.
|
|
||||||
## Arguments
|
|
||||||
Please use new() to create a new instance of this struct.
|
|
||||||
## Errors
|
|
||||||
You will receive a [`FieldFormatError`], if:
|
|
||||||
- The username is not between 2 and 32 characters.
|
|
||||||
- The password is not between 1 and 72 characters.
|
|
||||||
*/
|
|
||||||
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
|
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||||||
#[serde(rename_all = "snake_case")]
|
#[serde(rename_all = "snake_case")]
|
||||||
pub struct LoginSchema {
|
pub struct LoginSchema {
|
||||||
|
/// For Discord, usernames must be between 2 and 32 characters,
|
||||||
|
/// but other servers may have different limits.
|
||||||
pub login: String,
|
pub login: String,
|
||||||
pub password: Option<String>,
|
/// For Discord, must be between 1 and 72 characters,
|
||||||
|
/// but other servers may have different limits.
|
||||||
|
pub password: String,
|
||||||
pub undelete: Option<bool>,
|
pub undelete: Option<bool>,
|
||||||
pub captcha_key: Option<String>,
|
pub captcha_key: Option<String>,
|
||||||
pub login_source: Option<String>,
|
pub login_source: Option<String>,
|
||||||
pub gift_code_sku_id: Option<String>,
|
pub gift_code_sku_id: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LoginSchema {
|
|
||||||
/**
|
|
||||||
Returns a new [`Result<LoginSchema, FieldFormatError>`].
|
|
||||||
## Arguments
|
|
||||||
login: The username you want to login with.
|
|
||||||
password: The password you want to login with.
|
|
||||||
undelete: Honestly no idea what this is for.
|
|
||||||
captcha_key: The captcha key you want to login with.
|
|
||||||
login_source: The login source.
|
|
||||||
gift_code_sku_id: The gift code sku id.
|
|
||||||
## Errors
|
|
||||||
You will receive a [`FieldFormatError`], if:
|
|
||||||
- The username is less than 2 or more than 32 characters in length
|
|
||||||
*/
|
|
||||||
pub fn new(
|
|
||||||
login: String,
|
|
||||||
password: Option<String>,
|
|
||||||
undelete: Option<bool>,
|
|
||||||
captcha_key: Option<String>,
|
|
||||||
login_source: Option<String>,
|
|
||||||
gift_code_sku_id: Option<String>,
|
|
||||||
) -> Result<LoginSchema, FieldFormatError> {
|
|
||||||
Ok(LoginSchema {
|
|
||||||
login,
|
|
||||||
password,
|
|
||||||
undelete,
|
|
||||||
captcha_key,
|
|
||||||
login_source,
|
|
||||||
gift_code_sku_id,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
#[serde(rename_all = "snake_case")]
|
#[serde(rename_all = "snake_case")]
|
||||||
pub struct TotpSchema {
|
pub struct TotpSchema {
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
|
use bitflags::bitflags;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::types::{entities::PermissionOverwrite, Snowflake};
|
use crate::types::{entities::PermissionOverwrite, Snowflake};
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize)]
|
#[derive(Debug, Deserialize, Serialize, Default, PartialEq, PartialOrd)]
|
||||||
#[serde(rename_all = "snake_case")]
|
#[serde(rename_all = "snake_case")]
|
||||||
pub struct ChannelCreateSchema {
|
pub struct ChannelCreateSchema {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
|
@ -26,7 +27,7 @@ pub struct ChannelCreateSchema {
|
||||||
pub video_quality_mode: Option<i32>,
|
pub video_quality_mode: Option<i32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize, Clone, Default)]
|
#[derive(Debug, Deserialize, Serialize, Clone, Default, PartialEq, PartialOrd)]
|
||||||
#[serde(rename_all = "snake_case")]
|
#[serde(rename_all = "snake_case")]
|
||||||
pub struct ChannelModifySchema {
|
pub struct ChannelModifySchema {
|
||||||
pub name: Option<String>,
|
pub name: Option<String>,
|
||||||
|
@ -48,7 +49,7 @@ pub struct ChannelModifySchema {
|
||||||
pub video_quality_mode: Option<i32>,
|
pub video_quality_mode: Option<i32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize, Clone)]
|
#[derive(Debug, Deserialize, Serialize, Clone, Copy, PartialEq, PartialOrd, Eq, Ord)]
|
||||||
pub struct GetChannelMessagesSchema {
|
pub struct GetChannelMessagesSchema {
|
||||||
/// Between 1 and 100, defaults to 50.
|
/// Between 1 and 100, defaults to 50.
|
||||||
pub limit: Option<i32>,
|
pub limit: Option<i32>,
|
||||||
|
@ -56,7 +57,7 @@ pub struct GetChannelMessagesSchema {
|
||||||
pub anchor: ChannelMessagesAnchor,
|
pub anchor: ChannelMessagesAnchor,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize, Clone)]
|
#[derive(Debug, Deserialize, Serialize, Clone, Copy, PartialEq, PartialOrd, Eq, Ord)]
|
||||||
#[serde(rename_all = "snake_case")]
|
#[serde(rename_all = "snake_case")]
|
||||||
pub enum ChannelMessagesAnchor {
|
pub enum ChannelMessagesAnchor {
|
||||||
Before(Snowflake),
|
Before(Snowflake),
|
||||||
|
@ -94,3 +95,56 @@ impl GetChannelMessagesSchema {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, PartialOrd)]
|
||||||
|
pub struct CreateChannelInviteSchema {
|
||||||
|
pub flags: Option<InviteFlags>,
|
||||||
|
pub max_age: Option<u32>,
|
||||||
|
pub max_uses: Option<u8>,
|
||||||
|
pub temporary: Option<bool>,
|
||||||
|
pub unique: Option<bool>,
|
||||||
|
pub validate: Option<String>,
|
||||||
|
pub target_type: Option<InviteType>,
|
||||||
|
pub target_user_id: Option<Snowflake>,
|
||||||
|
pub target_application_id: Option<Snowflake>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for CreateChannelInviteSchema {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
flags: None,
|
||||||
|
max_age: Some(86400),
|
||||||
|
max_uses: Some(0),
|
||||||
|
temporary: Some(false),
|
||||||
|
unique: Some(false),
|
||||||
|
validate: None,
|
||||||
|
target_type: None,
|
||||||
|
target_user_id: None,
|
||||||
|
target_application_id: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bitflags! {
|
||||||
|
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, PartialOrd, Ord)]
|
||||||
|
pub struct InviteFlags: u64 {
|
||||||
|
const GUEST = 1 << 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Serialize, Clone, Copy, Default, PartialOrd, Ord, PartialEq, Eq)]
|
||||||
|
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
|
||||||
|
pub enum InviteType {
|
||||||
|
#[default]
|
||||||
|
Stream = 1,
|
||||||
|
EmbeddedApplication = 2,
|
||||||
|
RoleSubscriptions = 3,
|
||||||
|
CreatorPage = 4,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// See <https://discord-userdoccers.vercel.app/resources/channel#add-channel-recipient>
|
||||||
|
#[derive(Debug, Deserialize, Serialize, Clone, Default, PartialOrd, Ord, PartialEq, Eq)]
|
||||||
|
pub struct AddChannelRecipientSchema {
|
||||||
|
pub access_token: Option<String>,
|
||||||
|
pub nick: Option<String>,
|
||||||
|
}
|
||||||
|
|
|
@ -15,76 +15,3 @@ mod message;
|
||||||
mod relationship;
|
mod relationship;
|
||||||
mod role;
|
mod role;
|
||||||
mod user;
|
mod user;
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod schemas_tests {
|
|
||||||
use crate::errors::FieldFormatError;
|
|
||||||
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn password_too_short() {
|
|
||||||
assert_eq!(
|
|
||||||
AuthPassword::new("".to_string()),
|
|
||||||
Err(FieldFormatError::PasswordError)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn password_too_long() {
|
|
||||||
let mut long_pw = String::new();
|
|
||||||
for _ in 0..73 {
|
|
||||||
long_pw += "a";
|
|
||||||
}
|
|
||||||
assert_eq!(
|
|
||||||
AuthPassword::new(long_pw),
|
|
||||||
Err(FieldFormatError::PasswordError)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn username_too_short() {
|
|
||||||
assert_eq!(
|
|
||||||
AuthUsername::new("T".to_string()),
|
|
||||||
Err(FieldFormatError::UsernameError)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn username_too_long() {
|
|
||||||
let mut long_un = String::new();
|
|
||||||
for _ in 0..33 {
|
|
||||||
long_un += "a";
|
|
||||||
}
|
|
||||||
assert_eq!(
|
|
||||||
AuthUsername::new(long_un),
|
|
||||||
Err(FieldFormatError::UsernameError)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn consent_false() {
|
|
||||||
assert_eq!(
|
|
||||||
RegisterSchema::builder("Test", false).build(),
|
|
||||||
Err(FieldFormatError::ConsentError)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn invalid_email() {
|
|
||||||
assert_eq!(
|
|
||||||
AuthEmail::new("p@p.p".to_string()),
|
|
||||||
Err(FieldFormatError::EmailError)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn valid_email() {
|
|
||||||
let reg = RegisterSchemaOptions {
|
|
||||||
email: Some("me@mail.de".to_string()),
|
|
||||||
..RegisterSchema::builder("Testy", true)
|
|
||||||
}
|
|
||||||
.build();
|
|
||||||
assert_ne!(reg, Err(FieldFormatError::EmailError));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::types::Snowflake;
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize)]
|
#[derive(Debug, Deserialize, Serialize)]
|
||||||
#[serde(rename_all = "snake_case")]
|
#[serde(rename_all = "snake_case")]
|
||||||
pub struct UserModifySchema {
|
pub struct UserModifySchema {
|
||||||
|
@ -14,3 +18,17 @@ pub struct UserModifySchema {
|
||||||
pub email: Option<String>,
|
pub email: Option<String>,
|
||||||
pub discriminator: Option<i16>,
|
pub discriminator: Option<i16>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// # 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).
|
||||||
|
/// - nicks: A mapping of user IDs to their respective nicknames. Only usable for OAuth2 requests (which can only create group DMs).
|
||||||
|
///
|
||||||
|
/// # Reference:
|
||||||
|
/// Read: <https://discord-userdoccers.vercel.app/resources/channel#json-params>
|
||||||
|
#[derive(Debug, Deserialize, Serialize)]
|
||||||
|
pub struct PrivateChannelCreateSchema {
|
||||||
|
pub recipients: Option<Vec<Snowflake>>,
|
||||||
|
pub access_tokens: Option<Vec<String>>,
|
||||||
|
pub nicks: Option<HashMap<Snowflake, String>>,
|
||||||
|
}
|
||||||
|
|
|
@ -73,6 +73,7 @@ impl Rights {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)] // FIXME: Remove this when we use this
|
||||||
fn all_rights() -> Rights {
|
fn all_rights() -> Rights {
|
||||||
Rights::OPERATOR
|
Rights::OPERATOR
|
||||||
| Rights::MANAGE_APPLICATIONS
|
| Rights::MANAGE_APPLICATIONS
|
||||||
|
|
|
@ -12,7 +12,7 @@ const EPOCH: i64 = 1420070400000;
|
||||||
|
|
||||||
/// Unique identifier including a timestamp.
|
/// Unique identifier including a timestamp.
|
||||||
/// See https://discord.com/developers/docs/reference#snowflakes
|
/// See https://discord.com/developers/docs/reference#snowflakes
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||||
#[cfg_attr(feature = "sqlx", derive(Type))]
|
#[cfg_attr(feature = "sqlx", derive(Type))]
|
||||||
#[cfg_attr(feature = "sqlx", sqlx(transparent))]
|
#[cfg_attr(feature = "sqlx", sqlx(transparent))]
|
||||||
pub struct Snowflake(u64);
|
pub struct Snowflake(u64);
|
||||||
|
|
|
@ -1,16 +1,16 @@
|
||||||
use chorus::types::{RegisterSchema, RegisterSchemaOptions};
|
use chorus::types::RegisterSchema;
|
||||||
|
|
||||||
mod common;
|
mod common;
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_registration() {
|
async fn test_registration() {
|
||||||
let mut bundle = common::setup().await;
|
let mut bundle = common::setup().await;
|
||||||
let reg = RegisterSchemaOptions {
|
let reg = RegisterSchema {
|
||||||
|
username: "Hiiii".into(),
|
||||||
date_of_birth: Some("2000-01-01".to_string()),
|
date_of_birth: Some("2000-01-01".to_string()),
|
||||||
..RegisterSchema::builder("Hiiii", true)
|
consent: true,
|
||||||
}
|
..Default::default()
|
||||||
.build()
|
};
|
||||||
.unwrap();
|
|
||||||
bundle.instance.register_account(®).await.unwrap();
|
bundle.instance.register_account(®).await.unwrap();
|
||||||
common::teardown(bundle).await;
|
common::teardown(bundle).await;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use chorus::types::{
|
use chorus::types::{
|
||||||
self, Channel, GetChannelMessagesSchema, MessageSendSchema, PermissionFlags,
|
self, Channel, GetChannelMessagesSchema, MessageSendSchema, PermissionFlags,
|
||||||
PermissionOverwrite, Snowflake,
|
PermissionOverwrite, PrivateChannelCreateSchema, RelationshipType, Snowflake,
|
||||||
};
|
};
|
||||||
|
|
||||||
mod common;
|
mod common;
|
||||||
|
@ -28,10 +28,11 @@ async fn delete_channel() {
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn modify_channel() {
|
async fn modify_channel() {
|
||||||
|
const CHANNEL_NAME: &str = "beepboop";
|
||||||
let mut bundle = common::setup().await;
|
let mut bundle = common::setup().await;
|
||||||
let channel = &mut bundle.channel;
|
let channel = &mut bundle.channel;
|
||||||
let modify_data: types::ChannelModifySchema = types::ChannelModifySchema {
|
let modify_data: types::ChannelModifySchema = types::ChannelModifySchema {
|
||||||
name: Some("beepboop".to_string()),
|
name: Some(CHANNEL_NAME.to_string()),
|
||||||
channel_type: None,
|
channel_type: None,
|
||||||
topic: None,
|
topic: None,
|
||||||
icon: None,
|
icon: None,
|
||||||
|
@ -49,10 +50,10 @@ async fn modify_channel() {
|
||||||
default_thread_rate_limit_per_user: None,
|
default_thread_rate_limit_per_user: None,
|
||||||
video_quality_mode: None,
|
video_quality_mode: None,
|
||||||
};
|
};
|
||||||
Channel::modify(channel, modify_data, channel.id, &mut bundle.user)
|
let modified_channel = Channel::modify(channel, modify_data, channel.id, &mut bundle.user)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(channel.name, Some("beepboop".to_string()));
|
assert_eq!(modified_channel.name, Some(CHANNEL_NAME.to_string()));
|
||||||
|
|
||||||
let permission_override = PermissionFlags::from_vec(Vec::from([
|
let permission_override = PermissionFlags::from_vec(Vec::from([
|
||||||
PermissionFlags::MANAGE_CHANNELS,
|
PermissionFlags::MANAGE_CHANNELS,
|
||||||
|
@ -89,12 +90,11 @@ async fn get_channel_messages() {
|
||||||
let _ = bundle
|
let _ = bundle
|
||||||
.user
|
.user
|
||||||
.send_message(
|
.send_message(
|
||||||
&mut MessageSendSchema {
|
MessageSendSchema {
|
||||||
content: Some("A Message!".to_string()),
|
content: Some("A Message!".to_string()),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
bundle.channel.id,
|
bundle.channel.id,
|
||||||
None,
|
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
@ -136,3 +136,81 @@ async fn get_channel_messages() {
|
||||||
|
|
||||||
common::teardown(bundle).await
|
common::teardown(bundle).await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn create_dm() {
|
||||||
|
let mut bundle = common::setup().await;
|
||||||
|
let other_user = bundle.create_user("integrationtestuser2").await;
|
||||||
|
let user = &mut bundle.user;
|
||||||
|
let private_channel_create_schema = PrivateChannelCreateSchema {
|
||||||
|
recipients: Some(Vec::from([other_user.object.id])),
|
||||||
|
access_tokens: None,
|
||||||
|
nicks: None,
|
||||||
|
};
|
||||||
|
let dm_channel = user
|
||||||
|
.create_private_channel(private_channel_create_schema)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
assert!(dm_channel.recipients.is_some());
|
||||||
|
assert_eq!(
|
||||||
|
dm_channel.recipients.as_ref().unwrap().get(0).unwrap().id,
|
||||||
|
other_user.object.id
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
dm_channel.recipients.as_ref().unwrap().get(1).unwrap().id,
|
||||||
|
user.object.id
|
||||||
|
);
|
||||||
|
common::teardown(bundle).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
// #[tokio::test]
|
||||||
|
// This test currently is broken due to an issue with the Spacebar Server.
|
||||||
|
#[allow(dead_code)]
|
||||||
|
async fn remove_add_person_from_to_dm() {
|
||||||
|
let mut bundle = common::setup().await;
|
||||||
|
let mut other_user = bundle.create_user("integrationtestuser2").await;
|
||||||
|
let mut third_user = bundle.create_user("integrationtestuser3").await;
|
||||||
|
let user = &mut bundle.user;
|
||||||
|
let private_channel_create_schema = PrivateChannelCreateSchema {
|
||||||
|
recipients: Some(Vec::from([other_user.object.id, third_user.object.id])),
|
||||||
|
access_tokens: None,
|
||||||
|
nicks: None,
|
||||||
|
};
|
||||||
|
let dm_channel = user
|
||||||
|
.create_private_channel(private_channel_create_schema)
|
||||||
|
.await
|
||||||
|
.unwrap(); // Creates the Channel and stores the response Channel object
|
||||||
|
dm_channel
|
||||||
|
.remove_channel_recipient(other_user.object.id, user)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
assert!(dm_channel.recipients.as_ref().unwrap().get(1).is_none());
|
||||||
|
other_user
|
||||||
|
.modify_user_relationship(user.object.id, RelationshipType::Friends)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
user.modify_user_relationship(other_user.object.id, RelationshipType::Friends)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
third_user
|
||||||
|
.modify_user_relationship(user.object.id, RelationshipType::Friends)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
user.modify_user_relationship(third_user.object.id, RelationshipType::Friends)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
// Users 1-2 and 1-3 are now friends
|
||||||
|
dm_channel
|
||||||
|
.add_channel_recipient(other_user.object.id, user, None)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
assert!(dm_channel.recipients.is_some());
|
||||||
|
assert_eq!(
|
||||||
|
dm_channel.recipients.as_ref().unwrap().get(0).unwrap().id,
|
||||||
|
other_user.object.id
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
dm_channel.recipients.as_ref().unwrap().get(1).unwrap().id,
|
||||||
|
user.object.id
|
||||||
|
);
|
||||||
|
}
|
|
@ -1,15 +1,16 @@
|
||||||
|
use chorus::gateway::Gateway;
|
||||||
use chorus::{
|
use chorus::{
|
||||||
errors::ChorusResult,
|
|
||||||
instance::{Instance, UserMeta},
|
instance::{Instance, UserMeta},
|
||||||
types::{
|
types::{
|
||||||
Channel, ChannelCreateSchema, Guild, GuildCreateSchema, RegisterSchema,
|
Channel, ChannelCreateSchema, Guild, GuildCreateSchema, RegisterSchema,
|
||||||
RegisterSchemaOptions, RoleCreateModifySchema, RoleObject,
|
RoleCreateModifySchema, RoleObject,
|
||||||
},
|
},
|
||||||
UrlBundle,
|
UrlBundle,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct TestBundle {
|
pub(crate) struct TestBundle {
|
||||||
pub urls: UrlBundle,
|
pub urls: UrlBundle,
|
||||||
pub user: UserMeta,
|
pub user: UserMeta,
|
||||||
pub instance: Instance,
|
pub instance: Instance,
|
||||||
|
@ -18,21 +19,47 @@ pub struct TestBundle {
|
||||||
pub channel: Channel,
|
pub channel: Channel,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(unused)]
|
||||||
|
impl TestBundle {
|
||||||
|
pub(crate) async fn create_user(&mut self, username: &str) -> UserMeta {
|
||||||
|
let register_schema = RegisterSchema {
|
||||||
|
username: username.to_string(),
|
||||||
|
consent: true,
|
||||||
|
date_of_birth: Some("2000-01-01".to_string()),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
self.instance
|
||||||
|
.register_account(®ister_schema)
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
}
|
||||||
|
pub(crate) async fn clone_user_without_gateway(&self) -> UserMeta {
|
||||||
|
UserMeta {
|
||||||
|
belongs_to: self.user.belongs_to.clone(),
|
||||||
|
token: self.user.token.clone(),
|
||||||
|
limits: self.user.limits.clone(),
|
||||||
|
settings: self.user.settings.clone(),
|
||||||
|
object: self.user.object.clone(),
|
||||||
|
gateway: Gateway::new(self.instance.urls.wss.clone()).await.unwrap(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Set up a test by creating an Instance and a User. Reduces Test boilerplate.
|
// Set up a test by creating an Instance and a User. Reduces Test boilerplate.
|
||||||
pub async fn setup() -> TestBundle {
|
pub(crate) async fn setup() -> TestBundle {
|
||||||
let urls = UrlBundle::new(
|
let urls = UrlBundle::new(
|
||||||
"http://localhost:3001/api".to_string(),
|
"http://localhost:3001/api".to_string(),
|
||||||
"ws://localhost:3001".to_string(),
|
"ws://localhost:3001".to_string(),
|
||||||
"http://localhost:3001".to_string(),
|
"http://localhost:3001".to_string(),
|
||||||
);
|
);
|
||||||
let mut instance = Instance::new(urls.clone()).await.unwrap();
|
let mut instance = Instance::new(urls.clone(), true).await.unwrap();
|
||||||
// Requires the existance of the below user.
|
// Requires the existance of the below user.
|
||||||
let reg = RegisterSchemaOptions {
|
let reg = RegisterSchema {
|
||||||
|
username: "integrationtestuser".into(),
|
||||||
|
consent: true,
|
||||||
date_of_birth: Some("2000-01-01".to_string()),
|
date_of_birth: Some("2000-01-01".to_string()),
|
||||||
..RegisterSchema::builder("integrationtestuser", true)
|
..Default::default()
|
||||||
}
|
};
|
||||||
.build()
|
|
||||||
.unwrap();
|
|
||||||
let guild_create_schema = GuildCreateSchema {
|
let guild_create_schema = GuildCreateSchema {
|
||||||
name: Some("Test-Guild!".to_string()),
|
name: Some("Test-Guild!".to_string()),
|
||||||
region: None,
|
region: None,
|
||||||
|
@ -94,7 +121,7 @@ pub async fn setup() -> TestBundle {
|
||||||
|
|
||||||
// Teardown method to clean up after a test.
|
// Teardown method to clean up after a test.
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub async fn teardown(mut bundle: TestBundle) {
|
pub(crate) async fn teardown(mut bundle: TestBundle) {
|
||||||
Guild::delete(&mut bundle.user, bundle.guild.id)
|
Guild::delete(&mut bundle.user, bundle.guild.id)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
|
@ -1,13 +1,15 @@
|
||||||
mod common;
|
mod common;
|
||||||
|
|
||||||
use chorus::gateway::*;
|
use chorus::gateway::*;
|
||||||
use chorus::types;
|
use chorus::types::{self, Channel};
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
/// Tests establishing a connection (hello and heartbeats) on the local gateway;
|
/// Tests establishing a connection (hello and heartbeats) on the local gateway;
|
||||||
async fn test_gateway_establish() {
|
async fn test_gateway_establish() {
|
||||||
let bundle = common::setup().await;
|
let bundle = common::setup().await;
|
||||||
|
|
||||||
Gateway::new(bundle.urls.wss).await.unwrap();
|
Gateway::new(bundle.urls.wss.clone()).await.unwrap();
|
||||||
|
common::teardown(bundle).await
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
|
@ -15,10 +17,30 @@ async fn test_gateway_establish() {
|
||||||
async fn test_gateway_authenticate() {
|
async fn test_gateway_authenticate() {
|
||||||
let bundle = common::setup().await;
|
let bundle = common::setup().await;
|
||||||
|
|
||||||
let gateway = Gateway::new(bundle.urls.wss).await.unwrap();
|
let gateway = Gateway::new(bundle.urls.wss.clone()).await.unwrap();
|
||||||
|
|
||||||
let mut identify = types::GatewayIdentifyPayload::common();
|
let mut identify = types::GatewayIdentifyPayload::common();
|
||||||
identify.token = bundle.user.token;
|
identify.token = bundle.user.token.clone();
|
||||||
|
|
||||||
gateway.send_identify(identify).await;
|
gateway.send_identify(identify).await;
|
||||||
|
common::teardown(bundle).await
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_self_updating_structs() {
|
||||||
|
let mut bundle = common::setup().await;
|
||||||
|
let channel_updater = bundle.user.gateway.observe(bundle.channel.clone()).await;
|
||||||
|
let received_channel = channel_updater.borrow().clone();
|
||||||
|
assert_eq!(received_channel, bundle.channel);
|
||||||
|
let channel = &mut bundle.channel;
|
||||||
|
let modify_data = types::ChannelModifySchema {
|
||||||
|
name: Some("beepboop".to_string()),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
Channel::modify(channel, modify_data, channel.id, &mut bundle.user)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
let received_channel = channel_updater.borrow();
|
||||||
|
assert_eq!(received_channel.name.as_ref().unwrap(), "beepboop");
|
||||||
|
common::teardown(bundle).await
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
mod common;
|
||||||
|
use chorus::types::CreateChannelInviteSchema;
|
||||||
|
#[tokio::test]
|
||||||
|
async fn create_accept_invite() {
|
||||||
|
let mut bundle = common::setup().await;
|
||||||
|
let channel = bundle.channel.clone();
|
||||||
|
let mut other_user = bundle.create_user("testuser1312").await;
|
||||||
|
let user = &mut bundle.user;
|
||||||
|
let create_channel_invite_schema = CreateChannelInviteSchema::default();
|
||||||
|
assert!(chorus::types::Guild::get(bundle.guild.id, &mut other_user)
|
||||||
|
.await
|
||||||
|
.is_err());
|
||||||
|
let invite = user
|
||||||
|
.create_guild_invite(create_channel_invite_schema, channel.id)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
other_user.accept_invite(&invite.code, None).await.unwrap();
|
||||||
|
assert!(chorus::types::Guild::get(bundle.guild.id, &mut other_user)
|
||||||
|
.await
|
||||||
|
.is_ok());
|
||||||
|
common::teardown(bundle).await;
|
||||||
|
}
|
|
@ -8,13 +8,13 @@ mod common;
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn send_message() {
|
async fn send_message() {
|
||||||
let mut bundle = common::setup().await;
|
let mut bundle = common::setup().await;
|
||||||
let mut message = types::MessageSendSchema {
|
let message = types::MessageSendSchema {
|
||||||
content: Some("A Message!".to_string()),
|
content: Some("A Message!".to_string()),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
let _ = bundle
|
let _ = bundle
|
||||||
.user
|
.user
|
||||||
.send_message(&mut message, bundle.channel.id, None)
|
.send_message(message, bundle.channel.id)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
common::teardown(bundle).await
|
common::teardown(bundle).await
|
||||||
|
@ -45,7 +45,7 @@ async fn send_message_attachment() {
|
||||||
content: buffer,
|
content: buffer,
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut message = types::MessageSendSchema {
|
let message = types::MessageSendSchema {
|
||||||
content: Some("trans rights now".to_string()),
|
content: Some("trans rights now".to_string()),
|
||||||
attachments: Some(vec![attachment.clone()]),
|
attachments: Some(vec![attachment.clone()]),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
@ -55,11 +55,7 @@ async fn send_message_attachment() {
|
||||||
let _arg = Some(&vec_attach);
|
let _arg = Some(&vec_attach);
|
||||||
bundle
|
bundle
|
||||||
.user
|
.user
|
||||||
.send_message(
|
.send_message(message, bundle.channel.id)
|
||||||
&mut message,
|
|
||||||
bundle.channel.id,
|
|
||||||
Some(vec![attachment.clone()]),
|
|
||||||
)
|
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
common::teardown(bundle).await
|
common::teardown(bundle).await
|
|
@ -1,25 +1,17 @@
|
||||||
use chorus::types::{self, RegisterSchema, RegisterSchemaOptions, Relationship, RelationshipType};
|
use chorus::types::{self, Relationship, RelationshipType};
|
||||||
|
|
||||||
mod common;
|
mod common;
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_get_mutual_relationships() {
|
async fn test_get_mutual_relationships() {
|
||||||
let register_schema = RegisterSchemaOptions {
|
|
||||||
date_of_birth: Some("2000-01-01".to_string()),
|
|
||||||
..RegisterSchema::builder("integrationtestuser2", true)
|
|
||||||
}
|
|
||||||
.build()
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let mut bundle = common::setup().await;
|
let mut bundle = common::setup().await;
|
||||||
let belongs_to = &mut bundle.instance;
|
let mut other_user = bundle.create_user("integrationtestuser2").await;
|
||||||
let user = &mut bundle.user;
|
let user = &mut bundle.user;
|
||||||
let mut other_user = belongs_to.register_account(®ister_schema).await.unwrap();
|
|
||||||
let friend_request_schema = types::FriendRequestSendSchema {
|
let friend_request_schema = types::FriendRequestSendSchema {
|
||||||
username: user.object.username.clone(),
|
username: user.object.username.clone(),
|
||||||
discriminator: Some(user.object.discriminator.clone()),
|
discriminator: Some(user.object.discriminator.clone()),
|
||||||
};
|
};
|
||||||
other_user.send_friend_request(friend_request_schema).await;
|
let _ = other_user.send_friend_request(friend_request_schema).await;
|
||||||
let relationships = user
|
let relationships = user
|
||||||
.get_mutual_relationships(other_user.object.id)
|
.get_mutual_relationships(other_user.object.id)
|
||||||
.await
|
.await
|
||||||
|
@ -30,22 +22,17 @@ async fn test_get_mutual_relationships() {
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_get_relationships() {
|
async fn test_get_relationships() {
|
||||||
let register_schema = RegisterSchemaOptions {
|
|
||||||
date_of_birth: Some("2000-01-01".to_string()),
|
|
||||||
..RegisterSchema::builder("integrationtestuser2", true)
|
|
||||||
}
|
|
||||||
.build()
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let mut bundle = common::setup().await;
|
let mut bundle = common::setup().await;
|
||||||
let belongs_to = &mut bundle.instance;
|
let mut other_user = bundle.create_user("integrationtestuser2").await;
|
||||||
let user = &mut bundle.user;
|
let user = &mut bundle.user;
|
||||||
let mut other_user = belongs_to.register_account(®ister_schema).await.unwrap();
|
|
||||||
let friend_request_schema = types::FriendRequestSendSchema {
|
let friend_request_schema = types::FriendRequestSendSchema {
|
||||||
username: user.object.username.clone(),
|
username: user.object.username.clone(),
|
||||||
discriminator: Some(user.object.discriminator.clone()),
|
discriminator: Some(user.object.discriminator.clone()),
|
||||||
};
|
};
|
||||||
other_user.send_friend_request(friend_request_schema).await;
|
other_user
|
||||||
|
.send_friend_request(friend_request_schema)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
let relationships = user.get_relationships().await.unwrap();
|
let relationships = user.get_relationships().await.unwrap();
|
||||||
assert_eq!(relationships.get(0).unwrap().id, other_user.object.id);
|
assert_eq!(relationships.get(0).unwrap().id, other_user.object.id);
|
||||||
common::teardown(bundle).await
|
common::teardown(bundle).await
|
||||||
|
@ -53,18 +40,10 @@ async fn test_get_relationships() {
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_modify_relationship_friends() {
|
async fn test_modify_relationship_friends() {
|
||||||
let register_schema = RegisterSchemaOptions {
|
|
||||||
date_of_birth: Some("2000-01-01".to_string()),
|
|
||||||
..RegisterSchema::builder("integrationtestuser2", true)
|
|
||||||
}
|
|
||||||
.build()
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let mut bundle = common::setup().await;
|
let mut bundle = common::setup().await;
|
||||||
let belongs_to = &mut bundle.instance;
|
let mut other_user = bundle.create_user("integrationtestuser2").await;
|
||||||
let user = &mut bundle.user;
|
let user = &mut bundle.user;
|
||||||
let mut other_user = belongs_to.register_account(®ister_schema).await.unwrap();
|
let _ = other_user
|
||||||
other_user
|
|
||||||
.modify_user_relationship(user.object.id, types::RelationshipType::Friends)
|
.modify_user_relationship(user.object.id, types::RelationshipType::Friends)
|
||||||
.await;
|
.await;
|
||||||
let relationships = user.get_relationships().await.unwrap();
|
let relationships = user.get_relationships().await.unwrap();
|
||||||
|
@ -79,7 +58,8 @@ async fn test_modify_relationship_friends() {
|
||||||
relationships.get(0).unwrap().relationship_type,
|
relationships.get(0).unwrap().relationship_type,
|
||||||
RelationshipType::Outgoing
|
RelationshipType::Outgoing
|
||||||
);
|
);
|
||||||
user.modify_user_relationship(other_user.object.id, RelationshipType::Friends)
|
let _ = user
|
||||||
|
.modify_user_relationship(other_user.object.id, RelationshipType::Friends)
|
||||||
.await;
|
.await;
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
other_user
|
other_user
|
||||||
|
@ -91,7 +71,7 @@ async fn test_modify_relationship_friends() {
|
||||||
.relationship_type,
|
.relationship_type,
|
||||||
RelationshipType::Friends
|
RelationshipType::Friends
|
||||||
);
|
);
|
||||||
user.remove_relationship(other_user.object.id).await;
|
let _ = user.remove_relationship(other_user.object.id).await;
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
other_user.get_relationships().await.unwrap(),
|
other_user.get_relationships().await.unwrap(),
|
||||||
Vec::<Relationship>::new()
|
Vec::<Relationship>::new()
|
||||||
|
@ -101,18 +81,10 @@ async fn test_modify_relationship_friends() {
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_modify_relationship_block() {
|
async fn test_modify_relationship_block() {
|
||||||
let register_schema = RegisterSchemaOptions {
|
|
||||||
date_of_birth: Some("2000-01-01".to_string()),
|
|
||||||
..RegisterSchema::builder("integrationtestuser2", true)
|
|
||||||
}
|
|
||||||
.build()
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let mut bundle = common::setup().await;
|
let mut bundle = common::setup().await;
|
||||||
let belongs_to = &mut bundle.instance;
|
let mut other_user = bundle.create_user("integrationtestuser2").await;
|
||||||
let user = &mut bundle.user;
|
let user = &mut bundle.user;
|
||||||
let mut other_user = belongs_to.register_account(®ister_schema).await.unwrap();
|
let _ = other_user
|
||||||
other_user
|
|
||||||
.modify_user_relationship(user.object.id, types::RelationshipType::Blocked)
|
.modify_user_relationship(user.object.id, types::RelationshipType::Blocked)
|
||||||
.await;
|
.await;
|
||||||
let relationships = user.get_relationships().await.unwrap();
|
let relationships = user.get_relationships().await.unwrap();
|
||||||
|
@ -123,7 +95,7 @@ async fn test_modify_relationship_block() {
|
||||||
relationships.get(0).unwrap().relationship_type,
|
relationships.get(0).unwrap().relationship_type,
|
||||||
RelationshipType::Blocked
|
RelationshipType::Blocked
|
||||||
);
|
);
|
||||||
other_user.remove_relationship(user.object.id).await;
|
let _ = other_user.remove_relationship(user.object.id).await;
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
other_user.get_relationships().await.unwrap(),
|
other_user.get_relationships().await.unwrap(),
|
||||||
Vec::<Relationship>::new()
|
Vec::<Relationship>::new()
|
||||||
|
|
Loading…
Reference in New Issue