Merge branch 'main' into perpetual/gateway-dev

This commit is contained in:
kozabrada123 2023-06-20 19:12:39 +02:00
commit cfe4e2c7bb
48 changed files with 940 additions and 939 deletions

View File

@ -1,4 +1,4 @@
name: Rust name: Build and Test
on: on:
push: push:
@ -10,7 +10,7 @@ env:
CARGO_TERM_COLOR: always CARGO_TERM_COLOR: always
jobs: jobs:
build_and_test: rust:
runs-on: ubuntu-latest runs-on: ubuntu-latest

20
.github/workflows/clippy.yml vendored Normal file
View File

@ -0,0 +1,20 @@
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

View File

@ -29,9 +29,6 @@ openssl = "0.10.52"
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.2.1", features = ["serde"] }
atomic = "0.5.3"
bigdecimal = "0.3.1"
num-bigint = "0.4.3"
lazy_static = "1.4.0" lazy_static = "1.4.0"
poem = { version = "1.3.55", optional = true } poem = { version = "1.3.55", 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 }

171
README.md
View File

@ -5,6 +5,7 @@
[![Contributors][contributors-shield]][contributors-url] [![Contributors][contributors-shield]][contributors-url]
[![Forks][forks-shield]][forks-url] [![Forks][forks-shield]][forks-url]
[![Issues][issues-shield]][issues-url] [![Issues][issues-shield]][issues-url]
<img src="https://img.shields.io/static/v1?label=Status&message=Early%20Development&color=blue">
</br> </br>
<div align="center"> <div align="center">
@ -30,94 +31,106 @@
</div> </div>
## Progress Tracker/Roadmap: ## About
### Core Functionality
- [x] Rate Limiter (hint: couldn't be fully tested due to [an Issue with the Spacebar Server](https://github.com/spacebarchat/server/issues/1022))
- [x] [Login (the conventional way)](https://github.com/polyphony-chat/chorus/issues/1)
- [ ] [2FA](https://github.com/polyphony-chat/chorus/issues/40)
- [x] [Registration](https://github.com/polyphony-chat/chorus/issues/1)
### Messaging Chorus is a Rust library that allows developers to interact with multiple Spacebar-compatible APIs and Gateways simultaneously. The library provides a simple and efficient way to communicate with these services, making it easier for developers to build applications that rely on them. Chorus is open-source and welcomes contributions from the community.
- [x] [Sending messages](https://github.com/polyphony-chat/chorus/issues/23)
- [x] [Events (Message, User, Channel, etc.)](https://github.com/polyphony-chat/chorus/issues/51)
- [x] Channel creation
- [x] Channel deletion
- [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)
- [ ] [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)
- [ ] [Deleting messages](https://github.com/polyphony-chat/chorus/issues/91)
- [ ] [Message threads](https://github.com/polyphony-chat/chorus/issues/90)
- [x] [Reactions](https://github.com/polyphony-chat/chorus/issues/85)
- [ ] Message Search
- [ ] Message history
- [ ] Emoji
- [ ] Stickers
- [ ] [Forum channels](https://github.com/polyphony-chat/chorus/issues/90)
### User Management ## Contributing
- [ ] [User profile customization](https://github.com/polyphony-chat/chorus/issues/41)
- [x] Gettings users and user profiles
- [ ] [Friend requests](https://github.com/polyphony-chat/chorus/issues/92)
- [ ] [Blocking users](https://github.com/polyphony-chat/chorus/issues/92)
- [ ] User presence (online, offline, idle, etc.)
- [ ] User status (custom status, etc.)
- [x] Account deletion
### Additional Features If you would like to contribute, please feel free to open an Issue with the idea you have, or a
- [ ] Server discovery Pull Request. Please keep our [contribution guidelines](https://github.com/polyphony-chat/.github/blob/main/CONTRIBUTION_GUIDELINES.md) in mind. Your contribution might not be
- [ ] Server templates accepted, if it violates these guidelines or [our Code of Conduct](https://github.com/polyphony-chat/.github/blob/main/CODE_OF_CONDUCT.md).
### Voice and Video <details>
- [ ] [Voice chat support](https://github.com/polyphony-chat/chorus/issues/49) <summary>Progress Tracker/Roadmap</summary>
- [ ] [Video chat support](https://github.com/polyphony-chat/chorus/issues/49)
### Permissions and Roles ### Core Functionality
- [x] [Role management](https://github.com/polyphony-chat/chorus/issues/46) (creation, deletion, modification) - [x] Rate Limiter (hint: couldn't be fully tested due to [an Issue with the Spacebar Server](https://github.com/spacebarchat/server/issues/1022))
- [x] [Permission management](https://github.com/polyphony-chat/chorus/issues/46) (assigning and revoking permissions) - [x] [Login (the conventional way)](https://github.com/polyphony-chat/chorus/issues/1)
- [x] [Channel-specific permissions](https://github.com/polyphony-chat/chorus/issues/88) - [ ] [2FA](https://github.com/polyphony-chat/chorus/issues/40)
- [x] Role-based access control - [x] [Registration](https://github.com/polyphony-chat/chorus/issues/1)
### Guild Management ### Messaging
- [x] Guild creation - [x] [Sending messages](https://github.com/polyphony-chat/chorus/issues/23)
- [x] Guild deletion - [x] [Events (Message, User, Channel, etc.)](https://github.com/polyphony-chat/chorus/issues/51)
- [ ] [Guild settings (name, description, icon, etc.)](https://github.com/polyphony-chat/chorus/issues/43) - [x] Channel creation
- [ ] Guild invites - [x] Channel deletion
- [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)
- [ ] [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)
- [ ] [Deleting messages](https://github.com/polyphony-chat/chorus/issues/91)
- [ ] [Message threads](https://github.com/polyphony-chat/chorus/issues/90)
- [x] [Reactions](https://github.com/polyphony-chat/chorus/issues/85)
- [ ] Message Search
- [ ] Message history
- [ ] Emoji
- [ ] Stickers
- [ ] [Forum channels](https://github.com/polyphony-chat/chorus/issues/90)
### Moderation ### User Management
- [ ] Channel moderation (slow mode, etc.) - [ ] [User profile customization](https://github.com/polyphony-chat/chorus/issues/41)
- [ ] User sanctions (mute, kick, ban) - [x] Gettings users and user profiles
- [ ] Audit logs - [x] [Friend requests](https://github.com/polyphony-chat/chorus/issues/92)
- [x] [Blocking users](https://github.com/polyphony-chat/chorus/issues/92)
- [ ] User presence (online, offline, idle, etc.)
- [ ] User status (custom status, etc.)
- [x] Account deletion
### Embeds and Rich Content ### Additional Features
- [x] Sending rich content in messages (links, images, videos) - [ ] Server discovery
- [ ] Customizing embed appearance (title, description, color, fields) - [ ] Server templates
### Notifications and Push Notifications ### Voice and Video
- [ ] Notification settings management - [ ] [Voice chat support](https://github.com/polyphony-chat/chorus/issues/49)
- [ ] [Video chat support](https://github.com/polyphony-chat/chorus/issues/49)
### Webhooks ### Permissions and Roles
- [ ] Webhook creation and management - [x] [Role management](https://github.com/polyphony-chat/chorus/issues/46) (creation, deletion, modification)
- [ ] Handling incoming webhook events - [x] [Permission management](https://github.com/polyphony-chat/chorus/issues/46) (assigning and revoking permissions)
- [x] [Channel-specific permissions](https://github.com/polyphony-chat/chorus/issues/88)
- [x] Role-based access control
### Documentation and Examples ### Guild Management
- [ ] Comprehensive documentation - [x] Guild creation
- [ ] Example usage and code snippets - [x] Guild deletion
- [ ] Tutorials and guides - [ ] [Guild settings (name, description, icon, etc.)](https://github.com/polyphony-chat/chorus/issues/43)
- [ ] Guild invites
[Rust]: https://img.shields.io/badge/Rust-orange?style=plastic&logo=rust ### Moderation
[Rust-url]: https://www.rust-lang.org/ - [ ] Channel moderation (slow mode, etc.)
[build-shield]: https://img.shields.io/github/actions/workflow/status/polyphony-chat/chorus/rust.yml?style=flat - [ ] User sanctions (mute, kick, ban)
[build-url]: https://github.com/polyphony-chat/chorus/blob/main/.github/workflows/rust.yml - [ ] Audit logs
[contributors-shield]: https://img.shields.io/github/contributors/polyphony-chat/chorus.svg?style=flat
[contributors-url]: https://github.com/polyphony-chat/chorus/graphs/contributors ### Embeds and Rich Content
[forks-shield]: https://img.shields.io/github/forks/polyphony-chat/chorus.svg?style=flat - [x] Sending rich content in messages (links, images, videos)
[forks-url]: https://github.com/polyphony-chat/chorus/network/members - [ ] Customizing embed appearance (title, description, color, fields)
[stars-shield]: https://img.shields.io/github/stars/polyphony-chat/chorus.svg?style=flat
[stars-url]: https://github.com/polyphony-chat/chorus/stargazers ### Webhooks
[issues-shield]: https://img.shields.io/github/issues/polyphony-chat/chorus.svg?style=flat - [ ] Webhook creation and management
[issues-url]: https://github.com/polyphony-chat/chorus/issues - [ ] Handling incoming webhook events
[license-shield]: https://img.shields.io/github/license/polyphony-chat/chorus.svg?style=f;at
[license-url]: https://github.com/polyphony-chat/chorus/blob/master/LICENSE ### Documentation and Examples
[Discord]: https://dcbadge.vercel.app/api/server/m3FpcapGDD?style=flat - [ ] Comprehensive documentation
[Discord-invite]: https://discord.com/invite/m3FpcapGDD - [ ] Example usage and code snippets
- [ ] Tutorials and guides
[Rust]: https://img.shields.io/badge/Rust-orange?style=plastic&logo=rust
[Rust-url]: https://www.rust-lang.org/
[build-shield]: https://img.shields.io/github/actions/workflow/status/polyphony-chat/chorus/build_and_test.yml?style=flat
[build-url]: https://github.com/polyphony-chat/chorus/blob/main/.github/workflows/build_and_test.yml
[clippy-shield]: https://img.shields.io/github/actions/workflow/status/polyphony-chat/chorus/clippy.yml?style=flat
[clippy-url]: https://github.com/polyphony-chat/chorus/blob/main/.github/workflows/clippy.yml
[contributors-shield]: https://img.shields.io/github/contributors/polyphony-chat/chorus.svg?style=flat
[contributors-url]: https://github.com/polyphony-chat/chorus/graphs/contributors
[forks-shield]: https://img.shields.io/github/forks/polyphony-chat/chorus.svg?style=flat
[forks-url]: https://github.com/polyphony-chat/chorus/network/members
[stars-shield]: https://img.shields.io/github/stars/polyphony-chat/chorus.svg?style=flat
[stars-url]: https://github.com/polyphony-chat/chorus/stargazers
[issues-shield]: https://img.shields.io/github/issues/polyphony-chat/chorus.svg?style=flat
[issues-url]: https://github.com/polyphony-chat/chorus/issues
[license-shield]: https://img.shields.io/github/license/polyphony-chat/chorus.svg?style=f;at
[license-url]: https://github.com/polyphony-chat/chorus/blob/master/LICENSE
[Discord]: https://dcbadge.vercel.app/api/server/m3FpcapGDD?style=flat
[Discord-invite]: https://discord.com/invite/m3FpcapGDD
</details>

View File

@ -3,8 +3,8 @@ use chorus::{
gateway::{Gateway, Observer}, gateway::{Gateway, Observer},
types::{GatewayIdentifyPayload, GatewayReady}, types::{GatewayIdentifyPayload, GatewayReady},
}; };
use std::sync::Arc; use std::{sync::Arc, time::Duration};
use tokio::{self, sync::Mutex}; use tokio::{self, time::sleep};
// This example creates a simple gateway connection and a basic observer struct // This example creates a simple gateway connection and a basic observer struct
@ -17,7 +17,7 @@ pub struct ExampleObserver {}
// One struct can be an observer of multiple websocketevents, if needed // One struct can be an observer of multiple websocketevents, if needed
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(&mut self, _data: &GatewayReady) { fn update(&self, _data: &GatewayReady) {
println!("Observed Ready!"); println!("Observed Ready!");
} }
} }
@ -33,8 +33,8 @@ async fn main() {
// Create an instance of our observer // Create an instance of our observer
let observer = ExampleObserver {}; let observer = ExampleObserver {};
// Because observers have to reside in between the main and gateway thread, (they have to be shared between both) we need to put them in an Arc<Mutex> // Share ownership of the observer with the gateway
let shared_observer = Arc::new(Mutex::new(observer)); let shared_observer = Arc::new(observer);
// Subscribe our observer to the Ready event on this gateway // Subscribe our observer to the Ready event on this gateway
// From now on observer.update(data) will be called every time we receive the Ready event // From now on observer.update(data) will be called every time we receive the Ready event
@ -44,8 +44,7 @@ async fn main() {
.await .await
.session .session
.ready .ready
.subscribe(shared_observer) .subscribe(shared_observer);
.unwrap();
// Authenticate so we will receive any events // Authenticate so we will receive any events
let token = "SecretToken".to_string(); let token = "SecretToken".to_string();
@ -54,5 +53,7 @@ async fn main() {
gateway.send_identify(identify).await; gateway.send_identify(identify).await;
// Do something on the main thread so we don't quit // Do something on the main thread so we don't quit
loop {} loop {
sleep(Duration::MAX).await;
}
} }

View File

@ -1,4 +1,7 @@
use std::time::Duration;
use chorus::{self, gateway::Gateway, types::GatewayIdentifyPayload}; use chorus::{self, gateway::Gateway, types::GatewayIdentifyPayload};
use tokio::time::sleep;
/// This example creates a simple gateway connection and a session with an Identify event /// This example creates a simple gateway connection and a session with an Identify event
#[tokio::main] #[tokio::main]
@ -26,5 +29,7 @@ async fn main() {
gateway.send_identify(identify).await; gateway.send_identify(identify).await;
// Do something on the main thread so we don't quit // Do something on the main thread so we don't quit
loop {} loop {
sleep(Duration::MAX).await;
}
} }

View File

@ -17,7 +17,7 @@ impl Instance {
) -> Result<UserMeta, ChorusLibError> { ) -> Result<UserMeta, ChorusLibError> {
let json_schema = json!(login_schema); let json_schema = json!(login_schema);
let client = Client::new(); let client = Client::new();
let endpoint_url = self.urls.get_api().to_string() + "/auth/login"; let endpoint_url = self.urls.api.clone() + "/auth/login";
let request_builder = client.post(endpoint_url).body(json_schema.to_string()); let request_builder = client.post(endpoint_url).body(json_schema.to_string());
// 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

View File

@ -25,7 +25,7 @@ impl Instance {
) -> Result<UserMeta, ChorusLibError> { ) -> Result<UserMeta, ChorusLibError> {
let json_schema = json!(register_schema); let json_schema = json!(register_schema);
let client = Client::new(); let client = Client::new();
let endpoint_url = self.urls.get_api().to_string() + "/auth/register"; let endpoint_url = self.urls.api.clone() + "/auth/register";
let request_builder = client.post(endpoint_url).body(json_schema.to_string()); let request_builder = client.post(endpoint_url).body(json_schema.to_string());
// 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
@ -59,11 +59,10 @@ impl Instance {
return Err(ChorusLibError::InvalidFormBodyError { error_type, error }); 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 = let settings = UserMeta::get_settings(&token, &self.urls.api, &mut self.limits)
UserMeta::get_settings(&token, &self.urls.get_api().to_string(), &mut self.limits)
.await .await
.unwrap(); .unwrap();
let user: UserMeta = UserMeta::new( let user = UserMeta::new(
Rc::new(RefCell::new(self.clone())), Rc::new(RefCell::new(self.clone())),
token.clone(), token.clone(),
cloned_limits, cloned_limits,

View File

@ -10,9 +10,7 @@ use crate::{
impl Channel { impl Channel {
pub async fn get(user: &mut UserMeta, channel_id: &str) -> Result<Channel, ChorusLibError> { pub async fn get(user: &mut UserMeta, channel_id: &str) -> Result<Channel, ChorusLibError> {
let belongs_to = user.belongs_to.borrow_mut(); let url = user.belongs_to.borrow_mut().urls.api.clone();
let url = belongs_to.urls.get_api().to_string();
drop(belongs_to);
let request = Client::new() let request = Client::new()
.get(format!("{}/channels/{}/", url, channel_id)) .get(format!("{}/channels/{}/", url, channel_id))
.bearer_auth(user.token()); .bearer_auth(user.token());
@ -44,24 +42,19 @@ impl Channel {
/// ///
/// # Returns /// # Returns
/// ///
/// An `Option` that contains an `ChorusLibError` if an error occurred during the request, or `None` 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) -> Option<ChorusLibError> { pub async fn delete(self, user: &mut UserMeta) -> Result<(), ChorusLibError> {
let belongs_to = user.belongs_to.borrow_mut();
let request = Client::new() let request = Client::new()
.delete(format!( .delete(format!(
"{}/channels/{}/", "{}/channels/{}/",
belongs_to.urls.get_api(), user.belongs_to.borrow_mut().urls.api,
self.id.to_string() self.id
)) ))
.bearer_auth(user.token()); .bearer_auth(user.token());
drop(belongs_to);
let response = let response =
common::handle_request(request, user, crate::api::limits::LimitType::Channel).await; common::handle_request_as_result(request, user, crate::api::limits::LimitType::Channel)
if response.is_err() { .await;
return Some(response.err().unwrap()); response
} else {
return None;
}
} }
/// Modifies a channel. /// Modifies a channel.
@ -83,16 +76,14 @@ impl Channel {
channel_id: &str, channel_id: &str,
user: &mut UserMeta, user: &mut UserMeta,
) -> Result<Channel, ChorusLibError> { ) -> Result<Channel, ChorusLibError> {
let belongs_to = user.belongs_to.borrow();
let request = Client::new() let request = Client::new()
.patch(format!( .patch(format!(
"{}/channels/{}/", "{}/channels/{}/",
belongs_to.urls.get_api(), user.belongs_to.borrow().urls.api,
channel_id channel_id
)) ))
.bearer_auth(user.token()) .bearer_auth(user.token())
.body(to_string(&modify_data).unwrap()); .body(to_string(&modify_data).unwrap());
drop(belongs_to);
let channel = common::deserialize_response::<Channel>( let channel = common::deserialize_response::<Channel>(
request, request,
user, user,

View File

@ -25,9 +25,7 @@ impl Message {
message: &mut MessageSendSchema, message: &mut MessageSendSchema,
files: Option<Vec<PartialDiscordFileAttachment>>, files: Option<Vec<PartialDiscordFileAttachment>>,
) -> Result<Message, crate::errors::ChorusLibError> { ) -> Result<Message, crate::errors::ChorusLibError> {
let belongs_to = user.belongs_to.borrow(); let url_api = user.belongs_to.borrow().urls.api.clone();
let url_api = belongs_to.urls.get_api().to_string();
drop(belongs_to);
if files.is_none() { if files.is_none() {
let request = Client::new() let request = Client::new()

View File

@ -2,7 +2,7 @@ use reqwest::Client;
use serde_json::to_string; use serde_json::to_string;
use crate::{ use crate::{
api::handle_request, api::{handle_request, handle_request_as_result},
errors::ChorusLibError, errors::ChorusLibError,
instance::UserMeta, instance::UserMeta,
types::{self, PermissionOverwrite}, types::{self, PermissionOverwrite},
@ -19,35 +19,30 @@ impl types::Channel {
/// ///
/// # Returns /// # Returns
/// ///
/// This function returns [`None`] if the request is successful, otherwise it returns a [`ChorusLibError`] instance. /// This function returns a result that is either [`Ok(())`] if the request is successful, or an [`Err(ChorusLibError)`].
pub async fn edit_permissions( pub async fn edit_permissions(
user: &mut UserMeta, user: &mut UserMeta,
channel_id: &str, channel_id: &str,
overwrite: PermissionOverwrite, overwrite: PermissionOverwrite,
) -> Option<ChorusLibError> { ) -> Result<(), ChorusLibError> {
let belongs_to = user.belongs_to.borrow_mut(); let url = {
let url = format!( format!(
"{}/channels/{}/permissions/{}", "{}/channels/{}/permissions/{}",
belongs_to.urls.get_api(), user.belongs_to.borrow_mut().urls.api,
channel_id, channel_id,
overwrite.id overwrite.id
); )
drop(belongs_to); };
let body = match to_string(&overwrite) { let body = match to_string(&overwrite) {
Ok(string) => string, Ok(string) => string,
Err(e) => { Err(e) => {
return Some(ChorusLibError::FormCreationError { return Err(ChorusLibError::FormCreationError {
error: e.to_string(), error: e.to_string(),
}); });
} }
}; };
let request = Client::new().put(url).bearer_auth(user.token()).body(body); let request = Client::new().put(url).bearer_auth(user.token()).body(body);
match handle_request(request, user, crate::api::limits::LimitType::Channel).await { handle_request_as_result(request, user, crate::api::limits::LimitType::Channel).await
Ok(_) => None,
Err(e) => Some(ChorusLibError::InvalidResponseError {
error: e.to_string(),
}),
}
} }
/// Deletes a permission overwrite for a channel. /// Deletes a permission overwrite for a channel.
@ -60,26 +55,19 @@ impl types::Channel {
/// ///
/// # Returns /// # Returns
/// ///
/// This function returns [`None`] if the request is successful, otherwise it returns a [`ChorusLibError`] instance. /// This function returns a Result that is either [`Ok(())`] if the request is successfulm or an [`Err(ChorusLibError)`].
pub async fn delete_permission( pub async fn delete_permission(
user: &mut UserMeta, user: &mut UserMeta,
channel_id: &str, channel_id: &str,
overwrite_id: &str, overwrite_id: &str,
) -> Option<ChorusLibError> { ) -> Result<(), ChorusLibError> {
let belongs_to = user.belongs_to.borrow_mut();
let url = format!( let url = format!(
"{}/channels/{}/permissions/{}", "{}/channels/{}/permissions/{}",
belongs_to.urls.get_api(), user.belongs_to.borrow_mut().urls.api,
channel_id, channel_id,
overwrite_id overwrite_id
); );
drop(belongs_to);
let request = Client::new().delete(url).bearer_auth(user.token()); let request = Client::new().delete(url).bearer_auth(user.token());
match handle_request(request, user, crate::api::limits::LimitType::Channel).await { handle_request_as_result(request, user, crate::api::limits::LimitType::Channel).await
Ok(_) => None,
Err(e) => Some(ChorusLibError::InvalidResponseError {
error: e.to_string(),
}),
}
} }
} }

View File

@ -1,7 +1,7 @@
use reqwest::Client; use reqwest::Client;
use crate::{ use crate::{
api::{handle_request, handle_request_as_option}, api::{handle_request, handle_request_as_result},
errors::ChorusLibError, errors::ChorusLibError,
instance::UserMeta, instance::UserMeta,
types, types,
@ -24,28 +24,21 @@ impl ReactionMeta {
* `user` - A mutable reference to a [`UserMeta`] instance. * `user` - A mutable reference to a [`UserMeta`] instance.
# Returns # Returns
An `Option` [`crate::errors::ChorusLibError`] if something went wrong. A `Result` [`()`] [`crate::errors::ChorusLibError`] if something went wrong.
Fires a `Message Reaction Remove All` Gateway event. Fires a `Message Reaction Remove All` Gateway event.
# Reference # Reference
See [https://discord.com/developers/docs/resources/channel#delete-all-reactions](https://discord.com/developers/docs/resources/channel#delete-all-reactions) 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) -> Option<ChorusLibError> { pub async fn delete_all(&self, user: &mut UserMeta) -> Result<(), ChorusLibError> {
let belongs_to = user.belongs_to.borrow();
let url = format!( let url = format!(
"{}/channels/{}/messages/{}/reactions/", "{}/channels/{}/messages/{}/reactions/",
belongs_to.urls.get_api(), user.belongs_to.borrow().urls.api,
self.channel_id, self.channel_id,
self.message_id self.message_id
); );
drop(belongs_to);
let request = Client::new().delete(url).bearer_auth(user.token()); let request = Client::new().delete(url).bearer_auth(user.token());
match handle_request(request, user, crate::api::limits::LimitType::Channel).await { handle_request_as_result(request, user, crate::api::limits::LimitType::Channel).await
Ok(_) => None,
Err(e) => Some(ChorusLibError::InvalidResponseError {
error: e.to_string(),
}),
}
} }
/** /**
@ -58,28 +51,21 @@ impl ReactionMeta {
* `user` - A mutable reference to a [`UserMeta`] instance. * `user` - A mutable reference to a [`UserMeta`] instance.
# Returns # Returns
A [`crate::errors::ChorusLibError`] if something went wrong. A Result that is [`Err(crate::errors::ChorusLibError)`] if something went wrong.
# Reference # Reference
See [https://discord.com/developers/docs/resources/channel#get-reactions](https://discord.com/developers/docs/resources/channel#get-reactions) 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) -> Option<ChorusLibError> { pub async fn get(&self, emoji: &str, user: &mut UserMeta) -> Result<(), ChorusLibError> {
let belongs_to = user.belongs_to.borrow();
let url = format!( let url = format!(
"{}/channels/{}/messages/{}/reactions/{}/", "{}/channels/{}/messages/{}/reactions/{}/",
belongs_to.urls.get_api(), user.belongs_to.borrow().urls.api,
self.channel_id, self.channel_id,
self.message_id, self.message_id,
emoji emoji
); );
drop(belongs_to);
let request = Client::new().get(url).bearer_auth(user.token()); let request = Client::new().get(url).bearer_auth(user.token());
match handle_request(request, user, crate::api::limits::LimitType::Channel).await { handle_request_as_result(request, user, crate::api::limits::LimitType::Channel).await
Ok(_) => None,
Err(e) => Some(ChorusLibError::InvalidResponseError {
error: e.to_string(),
}),
}
} }
/** /**
@ -93,29 +79,26 @@ impl ReactionMeta {
* `user` - A mutable reference to a [`UserMeta`] instance. * `user` - A mutable reference to a [`UserMeta`] instance.
# Returns # Returns
A [`crate::errors::ChorusLibError`] if something went wrong. A Result that is [`Err(crate::errors::ChorusLibError)`] if something went wrong.
Fires a `Message Reaction Remove Emoji` Gateway event. Fires a `Message Reaction Remove Emoji` Gateway event.
# Reference # 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) 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) -> Option<ChorusLibError> { pub async fn delete_emoji(
let belongs_to = user.belongs_to.borrow(); &self,
emoji: &str,
user: &mut UserMeta,
) -> Result<(), ChorusLibError> {
let url = format!( let url = format!(
"{}/channels/{}/messages/{}/reactions/{}/", "{}/channels/{}/messages/{}/reactions/{}/",
belongs_to.urls.get_api(), user.belongs_to.borrow().urls.api,
self.channel_id, self.channel_id,
self.message_id, self.message_id,
emoji emoji
); );
drop(belongs_to);
let request = Client::new().delete(url).bearer_auth(user.token()); let request = Client::new().delete(url).bearer_auth(user.token());
match handle_request(request, user, crate::api::limits::LimitType::Channel).await { handle_request_as_result(request, user, crate::api::limits::LimitType::Channel).await
Ok(_) => None,
Err(e) => Some(ChorusLibError::InvalidResponseError {
error: e.to_string(),
}),
}
} }
/** /**
@ -132,25 +115,21 @@ impl ReactionMeta {
* `user` - A mutable reference to a [`UserMeta`] instance. * `user` - A mutable reference to a [`UserMeta`] instance.
# Returns # Returns
A `Result` containing a [`reqwest::Response`] or a [`crate::errors::ChorusLibError`]. A `Result` containing [`()`] or a [`crate::errors::ChorusLibError`].
Returns a 204 empty response on success.
Fires a Message Reaction Add Gateway event.
# Reference # Reference
See [https://discord.com/developers/docs/resources/channel#create-reaction](https://discord.com/developers/docs/resources/channel#create-reaction) 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) -> Option<ChorusLibError> { pub async fn create(&self, emoji: &str, user: &mut UserMeta) -> Result<(), ChorusLibError> {
let belongs_to = user.belongs_to.borrow();
let url = format!( let url = format!(
"{}/channels/{}/messages/{}/reactions/{}/@me/", "{}/channels/{}/messages/{}/reactions/{}/@me/",
belongs_to.urls.get_api(), user.belongs_to.borrow().urls.api,
self.channel_id, self.channel_id,
self.message_id, self.message_id,
emoji emoji
); );
drop(belongs_to);
let request = Client::new().put(url).bearer_auth(user.token()); let request = Client::new().put(url).bearer_auth(user.token());
handle_request_as_option(request, user, crate::api::limits::LimitType::Channel).await handle_request_as_result(request, user, crate::api::limits::LimitType::Channel).await
} }
/** /**
@ -163,25 +142,22 @@ impl ReactionMeta {
* `user` - A mutable reference to a [`UserMeta`] instance. * `user` - A mutable reference to a [`UserMeta`] instance.
# Returns # Returns
A `Result` containing a [`reqwest::Response`] or a [`crate::errors::ChorusLibError`]. A `Result` containing [`()`] or a [`crate::errors::ChorusLibError`].
Returns a 204 empty response on success.
Fires a `Message Reaction Remove` Gateway event. Fires a `Message Reaction Remove` Gateway event.
# Reference # Reference
See [https://discord.com/developers/docs/resources/channel#delete-own-reaction](https://discord.com/developers/docs/resources/channel#delete-own-reaction) See [https://discord.com/developers/docs/resources/channel#delete-own-reaction](https://discord.com/developers/docs/resources/channel#delete-own-reaction)
*/ */
pub async fn remove(&self, emoji: &str, user: &mut UserMeta) -> Option<ChorusLibError> { pub async fn remove(&self, emoji: &str, user: &mut UserMeta) -> Result<(), ChorusLibError> {
let belongs_to = user.belongs_to.borrow();
let url = format!( let url = format!(
"{}/channels/{}/messages/{}/reactions/{}/@me/", "{}/channels/{}/messages/{}/reactions/{}/@me/",
belongs_to.urls.get_api(), user.belongs_to.borrow().urls.api,
self.channel_id, self.channel_id,
self.message_id, self.message_id,
emoji emoji
); );
drop(belongs_to);
let request = Client::new().delete(url).bearer_auth(user.token()); let request = Client::new().delete(url).bearer_auth(user.token());
handle_request_as_option(request, user, crate::api::limits::LimitType::Channel).await handle_request_as_result(request, user, crate::api::limits::LimitType::Channel).await
} }
/** /**
@ -197,8 +173,7 @@ impl ReactionMeta {
* `user` - A mutable reference to a [`UserMeta`] instance. * `user` - A mutable reference to a [`UserMeta`] instance.
# Returns # Returns
A `Result` containing a [`reqwest::Response`] or a [`crate::errors::ChorusLibError`]. A `Result` containing [`()`] or a [`crate::errors::ChorusLibError`].
Returns a 204 empty response on success.
Fires a Message Reaction Remove Gateway event. Fires a Message Reaction Remove Gateway event.
# Reference # Reference
@ -209,18 +184,16 @@ impl ReactionMeta {
user_id: &str, user_id: &str,
emoji: &str, emoji: &str,
user: &mut UserMeta, user: &mut UserMeta,
) -> Option<ChorusLibError> { ) -> Result<(), ChorusLibError> {
let belongs_to = user.belongs_to.borrow();
let url = format!( let url = format!(
"{}/channels/{}/messages/{}/reactions/{}/{}", "{}/channels/{}/messages/{}/reactions/{}/{}",
belongs_to.urls.get_api(), user.belongs_to.borrow().urls.api,
self.channel_id, self.channel_id,
self.message_id, self.message_id,
emoji, emoji,
user_id user_id
); );
drop(belongs_to);
let request = Client::new().delete(url).bearer_auth(user.token()); let request = Client::new().delete(url).bearer_auth(user.token());
handle_request_as_option(request, user, crate::api::limits::LimitType::Channel).await handle_request_as_result(request, user, crate::api::limits::LimitType::Channel).await
} }
} }

View File

@ -12,30 +12,25 @@ pub async fn handle_request(
user: &mut UserMeta, user: &mut UserMeta,
limit_type: LimitType, limit_type: LimitType,
) -> Result<reqwest::Response, crate::errors::ChorusLibError> { ) -> Result<reqwest::Response, crate::errors::ChorusLibError> {
let mut belongs_to = user.belongs_to.borrow_mut(); LimitedRequester::send_request(
match LimitedRequester::send_request(
request, request,
limit_type, limit_type,
&mut belongs_to.limits, &mut user.belongs_to.borrow_mut().limits,
&mut user.limits, &mut user.limits,
) )
.await .await
{
Ok(response) => return Ok(response),
Err(e) => return Err(e),
}
} }
/// Sends a request to wherever it needs to go. Returns [`None`] on success and /// Sends a request to wherever it needs to go. Returns [`Ok(())`] on success and
/// [`Some(ChorusLibError)`] on failure. /// [`Err(ChorusLibError)`] on failure.
pub async fn handle_request_as_option( pub async fn handle_request_as_result(
request: RequestBuilder, request: RequestBuilder,
user: &mut UserMeta, user: &mut UserMeta,
limit_type: LimitType, limit_type: LimitType,
) -> Option<ChorusLibError> { ) -> Result<(), ChorusLibError> {
match handle_request(request, user, limit_type).await { match handle_request(request, user, limit_type).await {
Ok(_) => None, Ok(_) => Ok(()),
Err(e) => Some(ChorusLibError::InvalidResponseError { Err(e) => Err(ChorusLibError::InvalidResponseError {
error: e.to_string(), error: e.to_string(),
}), }),
} }
@ -53,7 +48,7 @@ pub async fn deserialize_response<T: for<'a> Deserialize<'a>>(
return Err(ChorusLibError::InvalidResponseError { return Err(ChorusLibError::InvalidResponseError {
error: format!( error: format!(
"Error while trying to process the HTTP response into a String: {}", "Error while trying to process the HTTP response into a String: {}",
e.to_string() e
), ),
}); });
} }
@ -64,7 +59,7 @@ pub async fn deserialize_response<T: for<'a> Deserialize<'a>>(
return Err(ChorusLibError::InvalidResponseError { return Err(ChorusLibError::InvalidResponseError {
error: format!( error: format!(
"Error while trying to deserialize the JSON response into T: {}", "Error while trying to deserialize the JSON response into T: {}",
e.to_string() e
), ),
}) })
} }

View File

@ -4,7 +4,7 @@ use serde_json::to_string;
use crate::api::deserialize_response; use crate::api::deserialize_response;
use crate::api::handle_request; use crate::api::handle_request;
use crate::api::handle_request_as_option; use crate::api::handle_request_as_result;
use crate::api::limits::Limits; use crate::api::limits::Limits;
use crate::errors::ChorusLibError; use crate::errors::ChorusLibError;
use crate::instance::UserMeta; use crate::instance::UserMeta;
@ -32,9 +32,7 @@ impl Guild {
user: &mut UserMeta, user: &mut UserMeta,
guild_create_schema: GuildCreateSchema, guild_create_schema: GuildCreateSchema,
) -> Result<Guild, ChorusLibError> { ) -> Result<Guild, ChorusLibError> {
let belongs_to = user.belongs_to.borrow(); let url = format!("{}/guilds/", user.belongs_to.borrow().urls.api);
let url = format!("{}/guilds/", belongs_to.urls.get_api());
drop(belongs_to);
let request = reqwest::Client::new() let request = reqwest::Client::new()
.post(url.clone()) .post(url.clone())
.bearer_auth(user.token.clone()) .bearer_auth(user.token.clone())
@ -52,7 +50,7 @@ impl Guild {
/// ///
/// # Returns /// # Returns
/// ///
/// An `Option` containing an `ChorusLibError` if an error occurred during the request, otherwise `None`. /// An `Result` containing an `ChorusLibError` if an error occurred during the request, otherwise `()`.
/// ///
/// # Example /// # Example
/// ///
@ -66,14 +64,16 @@ impl Guild {
/// None => println!("Guild deleted successfully"), /// None => println!("Guild deleted successfully"),
/// } /// }
/// ``` /// ```
pub async fn delete(user: &mut UserMeta, guild_id: &str) -> Option<ChorusLibError> { pub async fn delete(user: &mut UserMeta, guild_id: &str) -> Result<(), ChorusLibError> {
let belongs_to = user.belongs_to.borrow(); let url = format!(
let url = format!("{}/guilds/{}/delete/", belongs_to.urls.get_api(), guild_id); "{}/guilds/{}/delete/",
drop(belongs_to); user.belongs_to.borrow().urls.api,
guild_id
);
let request = reqwest::Client::new() let request = reqwest::Client::new()
.post(url.clone()) .post(url.clone())
.bearer_auth(user.token.clone()); .bearer_auth(user.token.clone());
handle_request_as_option(request, user, crate::api::limits::LimitType::Guild).await handle_request_as_result(request, user, crate::api::limits::LimitType::Guild).await
} }
/// Sends a request to create a new channel in the guild. /// Sends a request to create a new channel in the guild.
@ -97,7 +97,7 @@ impl Guild {
let mut belongs_to = user.belongs_to.borrow_mut(); let mut belongs_to = user.belongs_to.borrow_mut();
Channel::_create( Channel::_create(
&user.token, &user.token,
&format!("{}", belongs_to.urls.get_api()), &format!("{}", belongs_to.urls.api),
&self.id.to_string(), &self.id.to_string(),
schema, schema,
&mut user.limits, &mut user.limits,
@ -116,15 +116,13 @@ 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) -> Result<Vec<Channel>, ChorusLibError> { pub async fn channels(&self, user: &mut UserMeta) -> Result<Vec<Channel>, ChorusLibError> {
let belongs_to = user.belongs_to.borrow();
let request = Client::new() let request = Client::new()
.get(format!( .get(format!(
"{}/guilds/{}/channels/", "{}/guilds/{}/channels/",
belongs_to.urls.get_api(), user.belongs_to.borrow().urls.api,
self.id.to_string() self.id
)) ))
.bearer_auth(user.token()); .bearer_auth(user.token());
drop(belongs_to);
let result = handle_request(request, user, crate::api::limits::LimitType::Channel) let result = handle_request(request, user, crate::api::limits::LimitType::Channel)
.await .await
.unwrap(); .unwrap();
@ -159,7 +157,7 @@ impl Guild {
pub async fn get(user: &mut UserMeta, guild_id: &str) -> Result<Guild, ChorusLibError> { pub async fn get(user: &mut UserMeta, guild_id: &str) -> Result<Guild, ChorusLibError> {
let mut belongs_to = user.belongs_to.borrow_mut(); let mut belongs_to = user.belongs_to.borrow_mut();
Guild::_get( Guild::_get(
&format!("{}", belongs_to.urls.get_api()), &format!("{}", belongs_to.urls.api),
guild_id, guild_id,
&user.token, &user.token,
&mut user.limits, &mut user.limits,
@ -219,7 +217,7 @@ impl Channel {
let mut belongs_to = user.belongs_to.borrow_mut(); let mut belongs_to = user.belongs_to.borrow_mut();
Channel::_create( Channel::_create(
&user.token, &user.token,
&format!("{}", belongs_to.urls.get_api()), &format!("{}", belongs_to.urls.api),
guild_id, guild_id,
schema, schema,
&mut user.limits, &mut user.limits,

View File

@ -1,7 +1,7 @@
use reqwest::Client; use reqwest::Client;
use crate::{ use crate::{
api::{deserialize_response, handle_request_as_option}, api::{deserialize_response, handle_request_as_result},
errors::ChorusLibError, errors::ChorusLibError,
instance::UserMeta, instance::UserMeta,
types, types,
@ -24,14 +24,12 @@ impl types::GuildMember {
guild_id: &str, guild_id: &str,
member_id: &str, member_id: &str,
) -> Result<types::GuildMember, ChorusLibError> { ) -> Result<types::GuildMember, ChorusLibError> {
let belongs_to = user.belongs_to.borrow();
let url = format!( let url = format!(
"{}/guilds/{}/members/{}/", "{}/guilds/{}/members/{}/",
belongs_to.urls.get_api(), user.belongs_to.borrow().urls.api,
guild_id, guild_id,
member_id member_id
); );
drop(belongs_to);
let request = Client::new().get(url).bearer_auth(user.token()); let request = Client::new().get(url).bearer_auth(user.token());
deserialize_response::<types::GuildMember>( deserialize_response::<types::GuildMember>(
request, request,
@ -52,24 +50,22 @@ impl types::GuildMember {
/// ///
/// # Returns /// # Returns
/// ///
/// An `Option` containing a `ChorusLibError` if the request fails, or `None` if the request succeeds. /// An `Result` containing a `ChorusLibError` if the request fails, or `()` if the request succeeds.
pub async fn add_role( pub async fn add_role(
user: &mut UserMeta, user: &mut UserMeta,
guild_id: &str, guild_id: &str,
member_id: &str, member_id: &str,
role_id: &str, role_id: &str,
) -> Option<ChorusLibError> { ) -> Result<(), ChorusLibError> {
let belongs_to = user.belongs_to.borrow();
let url = format!( let url = format!(
"{}/guilds/{}/members/{}/roles/{}/", "{}/guilds/{}/members/{}/roles/{}/",
belongs_to.urls.get_api(), user.belongs_to.borrow().urls.api,
guild_id, guild_id,
member_id, member_id,
role_id role_id
); );
drop(belongs_to);
let request = Client::new().put(url).bearer_auth(user.token()); let request = Client::new().put(url).bearer_auth(user.token());
handle_request_as_option(request, user, crate::api::limits::LimitType::Guild).await handle_request_as_result(request, user, crate::api::limits::LimitType::Guild).await
} }
/// Removes a role from a guild member. /// Removes a role from a guild member.
@ -83,23 +79,21 @@ impl types::GuildMember {
/// ///
/// # Returns /// # Returns
/// ///
/// An `Option` containing a `ChorusLibError` if the request fails, or `None` if the request succeeds. /// A `Result` containing a `ChorusLibError` if the request fails, or `()` if the request succeeds.
pub async fn remove_role( pub async fn remove_role(
user: &mut UserMeta, user: &mut UserMeta,
guild_id: &str, guild_id: &str,
member_id: &str, member_id: &str,
role_id: &str, role_id: &str,
) -> Option<crate::errors::ChorusLibError> { ) -> Result<(), crate::errors::ChorusLibError> {
let belongs_to = user.belongs_to.borrow();
let url = format!( let url = format!(
"{}/guilds/{}/members/{}/roles/{}/", "{}/guilds/{}/members/{}/roles/{}/",
belongs_to.urls.get_api(), user.belongs_to.borrow().urls.api,
guild_id, guild_id,
member_id, member_id,
role_id role_id
); );
drop(belongs_to);
let request = Client::new().delete(url).bearer_auth(user.token()); let request = Client::new().delete(url).bearer_auth(user.token());
handle_request_as_option(request, user, crate::api::limits::LimitType::Guild).await handle_request_as_result(request, user, crate::api::limits::LimitType::Guild).await
} }
} }

View File

@ -27,9 +27,11 @@ impl types::RoleObject {
user: &mut UserMeta, user: &mut UserMeta,
guild_id: &str, guild_id: &str,
) -> Result<Option<Vec<RoleObject>>, ChorusLibError> { ) -> Result<Option<Vec<RoleObject>>, ChorusLibError> {
let belongs_to = user.belongs_to.borrow(); let url = format!(
let url = format!("{}/guilds/{}/roles/", belongs_to.urls.get_api(), guild_id); "{}/guilds/{}/roles/",
drop(belongs_to); user.belongs_to.borrow().urls.api,
guild_id
);
let request = Client::new().get(url).bearer_auth(user.token()); let request = Client::new().get(url).bearer_auth(user.token());
let roles = deserialize_response::<Vec<RoleObject>>( let roles = deserialize_response::<Vec<RoleObject>>(
request, request,
@ -64,14 +66,12 @@ impl types::RoleObject {
guild_id: &str, guild_id: &str,
role_id: &str, role_id: &str,
) -> Result<RoleObject, ChorusLibError> { ) -> Result<RoleObject, ChorusLibError> {
let belongs_to = user.belongs_to.borrow();
let url = format!( let url = format!(
"{}/guilds/{}/roles/{}/", "{}/guilds/{}/roles/{}/",
belongs_to.urls.get_api(), user.belongs_to.borrow().urls.api,
guild_id, guild_id,
role_id role_id
); );
drop(belongs_to);
let request = Client::new().get(url).bearer_auth(user.token()); let request = Client::new().get(url).bearer_auth(user.token());
deserialize_response(request, user, crate::api::limits::LimitType::Guild).await deserialize_response(request, user, crate::api::limits::LimitType::Guild).await
} }
@ -96,17 +96,16 @@ impl types::RoleObject {
guild_id: &str, guild_id: &str,
role_create_schema: RoleCreateModifySchema, role_create_schema: RoleCreateModifySchema,
) -> Result<RoleObject, ChorusLibError> { ) -> Result<RoleObject, ChorusLibError> {
let belongs_to = user.belongs_to.borrow(); let url = format!(
let url = format!("{}/guilds/{}/roles/", belongs_to.urls.get_api(), guild_id); "{}/guilds/{}/roles/",
drop(belongs_to); user.belongs_to.borrow().urls.api,
let body = match to_string::<RoleCreateModifySchema>(&role_create_schema) { guild_id
Ok(string) => string, );
Err(e) => { let body = to_string::<RoleCreateModifySchema>(&role_create_schema).map_err(|e| {
return Err(ChorusLibError::FormCreationError { ChorusLibError::FormCreationError {
error: e.to_string(), error: e.to_string(),
});
} }
}; })?;
let request = Client::new().post(url).bearer_auth(user.token()).body(body); let request = Client::new().post(url).bearer_auth(user.token()).body(body);
deserialize_response(request, user, crate::api::limits::LimitType::Guild).await deserialize_response(request, user, crate::api::limits::LimitType::Guild).await
} }
@ -131,17 +130,16 @@ impl types::RoleObject {
guild_id: &str, guild_id: &str,
role_position_update_schema: types::RolePositionUpdateSchema, role_position_update_schema: types::RolePositionUpdateSchema,
) -> Result<RoleObject, ChorusLibError> { ) -> Result<RoleObject, ChorusLibError> {
let belongs_to = user.belongs_to.borrow(); let url = format!(
let url = format!("{}/guilds/{}/roles/", belongs_to.urls.get_api(), guild_id); "{}/guilds/{}/roles/",
let body = match to_string(&role_position_update_schema) { user.belongs_to.borrow().urls.api,
Ok(body) => body, guild_id
Err(e) => { );
return Err(ChorusLibError::FormCreationError { let body = to_string(&role_position_update_schema).map_err(|e| {
ChorusLibError::FormCreationError {
error: e.to_string(), error: e.to_string(),
});
} }
}; })?;
drop(belongs_to);
let request = Client::new() let request = Client::new()
.patch(url) .patch(url)
.bearer_auth(user.token()) .bearer_auth(user.token())
@ -172,22 +170,17 @@ impl types::RoleObject {
role_id: &str, role_id: &str,
role_create_schema: RoleCreateModifySchema, role_create_schema: RoleCreateModifySchema,
) -> Result<RoleObject, ChorusLibError> { ) -> Result<RoleObject, ChorusLibError> {
let belongs_to = user.belongs_to.borrow();
let url = format!( let url = format!(
"{}/guilds/{}/roles/{}", "{}/guilds/{}/roles/{}",
belongs_to.urls.get_api(), user.belongs_to.borrow().urls.api,
guild_id, guild_id,
role_id role_id
); );
drop(belongs_to); let body = to_string::<RoleCreateModifySchema>(&role_create_schema).map_err(|e| {
let body = match to_string::<RoleCreateModifySchema>(&role_create_schema) { ChorusLibError::FormCreationError {
Ok(string) => string,
Err(e) => {
return Err(ChorusLibError::FormCreationError {
error: e.to_string(), error: e.to_string(),
});
} }
}; })?;
let request = Client::new() let request = Client::new()
.patch(url) .patch(url)
.bearer_auth(user.token()) .bearer_auth(user.token())

View File

@ -15,7 +15,7 @@ impl Instance {
&self, &self,
) -> Result<GeneralConfiguration, ChorusLibError> { ) -> Result<GeneralConfiguration, ChorusLibError> {
let client = Client::new(); let client = Client::new();
let endpoint_url = self.urls.get_api().to_string() + "/policies/instance/"; let endpoint_url = self.urls.api.clone() + "/policies/instance/";
let request = match client.get(&endpoint_url).send().await { let request = match client.get(&endpoint_url).send().await {
Ok(result) => result, Ok(result) => result,
Err(e) => { Err(e) => {
@ -33,7 +33,6 @@ impl Instance {
} }
let body = request.text().await.unwrap(); let body = request.text().await.unwrap();
let instance_policies_schema: GeneralConfiguration = from_str(&body).unwrap(); Ok(from_str::<GeneralConfiguration>(&body).unwrap())
Ok(instance_policies_schema)
} }
} }

View File

@ -195,31 +195,31 @@ pub mod limits {
pub fn get_limit_ref(&self, limit_type: &LimitType) -> &Limit { pub fn get_limit_ref(&self, limit_type: &LimitType) -> &Limit {
match limit_type { match limit_type {
&LimitType::AbsoluteMessage => self.limit_absolute_messages, LimitType::AbsoluteMessage => self.limit_absolute_messages,
&LimitType::AbsoluteRegister => self.limit_absolute_register, LimitType::AbsoluteRegister => self.limit_absolute_register,
&LimitType::AuthLogin => self.limit_auth_login, LimitType::AuthLogin => self.limit_auth_login,
&LimitType::AuthRegister => self.limit_auth_register, LimitType::AuthRegister => self.limit_auth_register,
&LimitType::Channel => self.limit_channel, LimitType::Channel => self.limit_channel,
&LimitType::Error => self.limit_error, LimitType::Error => self.limit_error,
&LimitType::Global => self.limit_global, LimitType::Global => self.limit_global,
&LimitType::Guild => self.limit_guild, LimitType::Guild => self.limit_guild,
&LimitType::Ip => self.limit_ip, LimitType::Ip => self.limit_ip,
&LimitType::Webhook => self.limit_webhook, LimitType::Webhook => self.limit_webhook,
} }
} }
pub fn get_limit_mut_ref(&mut self, limit_type: &LimitType) -> &mut Limit { pub fn get_limit_mut_ref(&mut self, limit_type: &LimitType) -> &mut Limit {
match limit_type { match limit_type {
&LimitType::AbsoluteMessage => self.limit_absolute_messages, LimitType::AbsoluteMessage => self.limit_absolute_messages,
&LimitType::AbsoluteRegister => self.limit_absolute_register, LimitType::AbsoluteRegister => self.limit_absolute_register,
&LimitType::AuthLogin => self.limit_auth_login, LimitType::AuthLogin => self.limit_auth_login,
&LimitType::AuthRegister => self.limit_auth_register, LimitType::AuthRegister => self.limit_auth_register,
&LimitType::Channel => self.limit_channel, LimitType::Channel => self.limit_channel,
&LimitType::Error => self.limit_error, LimitType::Error => self.limit_error,
&LimitType::Global => self.limit_global, LimitType::Global => self.limit_global,
&LimitType::Guild => self.limit_guild, LimitType::Guild => self.limit_guild,
&LimitType::Ip => self.limit_ip, LimitType::Ip => self.limit_ip,
&LimitType::Webhook => self.limit_webhook, LimitType::Webhook => self.limit_webhook,
} }
} }
} }
@ -256,31 +256,31 @@ pub mod limits {
pub fn get_limit_ref(&self, limit_type: &LimitType) -> &Limit { pub fn get_limit_ref(&self, limit_type: &LimitType) -> &Limit {
match limit_type { match limit_type {
&LimitType::AbsoluteMessage => &self.limit_absolute_messages, LimitType::AbsoluteMessage => &self.limit_absolute_messages,
&LimitType::AbsoluteRegister => &self.limit_absolute_register, LimitType::AbsoluteRegister => &self.limit_absolute_register,
&LimitType::AuthLogin => &self.limit_auth_login, LimitType::AuthLogin => &self.limit_auth_login,
&LimitType::AuthRegister => &self.limit_auth_register, LimitType::AuthRegister => &self.limit_auth_register,
&LimitType::Channel => &self.limit_channel, LimitType::Channel => &self.limit_channel,
&LimitType::Error => &self.limit_error, LimitType::Error => &self.limit_error,
&LimitType::Global => &self.limit_global, LimitType::Global => &self.limit_global,
&LimitType::Guild => &self.limit_guild, LimitType::Guild => &self.limit_guild,
&LimitType::Ip => &self.limit_ip, LimitType::Ip => &self.limit_ip,
&LimitType::Webhook => &self.limit_webhook, LimitType::Webhook => &self.limit_webhook,
} }
} }
pub fn get_limit_mut_ref(&mut self, limit_type: &LimitType) -> &mut Limit { pub fn get_limit_mut_ref(&mut self, limit_type: &LimitType) -> &mut Limit {
match limit_type { match limit_type {
&LimitType::AbsoluteMessage => &mut self.limit_absolute_messages, LimitType::AbsoluteMessage => &mut self.limit_absolute_messages,
&LimitType::AbsoluteRegister => &mut self.limit_absolute_register, LimitType::AbsoluteRegister => &mut self.limit_absolute_register,
&LimitType::AuthLogin => &mut self.limit_auth_login, LimitType::AuthLogin => &mut self.limit_auth_login,
&LimitType::AuthRegister => &mut self.limit_auth_register, LimitType::AuthRegister => &mut self.limit_auth_register,
&LimitType::Channel => &mut self.limit_channel, LimitType::Channel => &mut self.limit_channel,
&LimitType::Error => &mut self.limit_error, LimitType::Error => &mut self.limit_error,
&LimitType::Global => &mut self.limit_global, LimitType::Global => &mut self.limit_global,
&LimitType::Guild => &mut self.limit_guild, LimitType::Guild => &mut self.limit_guild,
&LimitType::Ip => &mut self.limit_ip, LimitType::Ip => &mut self.limit_ip,
&LimitType::Webhook => &mut self.limit_webhook, LimitType::Webhook => &mut self.limit_webhook,
} }
} }
@ -311,7 +311,7 @@ pub mod limits {
/// TODO: Change this to return a Result and handle the errors properly. /// TODO: Change this to return a Result and handle the errors properly.
pub async fn check_limits(api_url: String) -> Limits { pub async fn check_limits(api_url: String) -> Limits {
let client = Client::new(); let client = Client::new();
let url_parsed = crate::URLBundle::parse_url(api_url) + "/policies/instance/limits"; let url_parsed = crate::UrlBundle::parse_url(api_url) + "/policies/instance/limits";
let result = client let result = client
.get(url_parsed) .get(url_parsed)
.send() .send()

View File

@ -1,6 +1,12 @@
use reqwest::Client; use reqwest::Client;
use serde_json::to_string;
use crate::{api::deserialize_response, errors::ChorusLibError, instance::UserMeta, types}; use crate::{
api::{deserialize_response, handle_request_as_result},
errors::ChorusLibError,
instance::UserMeta,
types::{self, CreateUserRelationshipSchema, RelationshipType},
};
impl UserMeta { impl UserMeta {
/// Retrieves the mutual relationships between the authenticated user and the specified user. /// Retrieves the mutual relationships between the authenticated user and the specified user.
@ -10,24 +16,134 @@ impl UserMeta {
/// * `user_id` - A string slice that holds the ID of the user to retrieve the mutual relationships with. /// * `user_id` - A string slice that holds the ID of the user to retrieve the mutual relationships with.
/// ///
/// # Returns /// # Returns
/// This function returns a [`Option<Vec<Result<PublicUser, ChorusLibError>>>`]. /// This function returns a [`Result<Vec<PublicUser>, ChorusLibError>`].
pub async fn get_mutual_relationships( pub async fn get_mutual_relationships(
&mut self, &mut self,
user_id: &str, user_id: &str,
) -> Result<Option<Vec<types::PublicUser>>, ChorusLibError> { ) -> Result<Vec<types::PublicUser>, ChorusLibError> {
let belongs_to = self.belongs_to.borrow();
let url = format!( let url = format!(
"{}/users/{}/relationships/", "{}/users/{}/relationships/",
belongs_to.urls.get_api(), self.belongs_to.borrow().urls.api,
user_id user_id
); );
drop(belongs_to);
let request = Client::new().get(url).bearer_auth(self.token()); let request = Client::new().get(url).bearer_auth(self.token());
deserialize_response::<Option<Vec<types::PublicUser>>>( deserialize_response::<Vec<types::PublicUser>>(
request, request,
self, self,
crate::api::limits::LimitType::Global, crate::api::limits::LimitType::Global,
) )
.await .await
} }
/// Retrieves the authenticated user's relationships.
///
/// # Returns
/// This function returns a [`Result<Vec<types::Relationship>, ChorusLibError>`].
pub async fn get_relationships(&mut self) -> Result<Vec<types::Relationship>, ChorusLibError> {
let url = format!(
"{}/users/@me/relationships/",
self.belongs_to.borrow().urls.api
);
let request = Client::new().get(url).bearer_auth(self.token());
deserialize_response::<Vec<types::Relationship>>(
request,
self,
crate::api::limits::LimitType::Global,
)
.await
}
/// Sends a friend request to a user.
///
/// # Arguments
///
/// * `schema` - A [`FriendRequestSendSchema`] struct that holds the information about the friend request to be sent.
///
/// # Returns
/// This function returns a [`Result`] that holds a [`ChorusLibError`] if the request fails.
pub async fn send_friend_request(
&mut self,
schema: types::FriendRequestSendSchema,
) -> Result<(), ChorusLibError> {
let url = format!(
"{}/users/@me/relationships/",
self.belongs_to.borrow().urls.api
);
let body = to_string(&schema).unwrap();
let request = Client::new().post(url).bearer_auth(self.token()).body(body);
handle_request_as_result(request, self, crate::api::limits::LimitType::Global).await
}
/// Modifies the relationship between the authenticated user and the specified user.
///
/// # Arguments
///
/// * `user_id` - A string slice that holds the ID of the user to modify the relationship with.
/// * `relationship_type` - A [`RelationshipType`] enum that specifies the type of relationship to modify.
/// * [`RelationshipType::None`]: Removes the relationship between the two users.
/// * [`RelationshipType::Friends`] | [`RelationshipType::Incoming`] | [`RelationshipType::Outgoing`]:
/// Either accepts an incoming friend request, or sends a new friend request, if there is no
/// incoming friend request from the specified `user_id`.
/// * [`RelationshipType::Blocked`]: Blocks the specified user_id.
///
/// # Returns
/// This function returns an [`Result`] that holds a [`ChorusLibError`] if the request fails.
pub async fn modify_user_relationship(
&mut self,
user_id: &str,
relationship_type: RelationshipType,
) -> Result<(), ChorusLibError> {
let api_url = self.belongs_to.borrow().urls.api.clone();
match relationship_type {
RelationshipType::None => {
let request = Client::new()
.delete(format!("{}/users/@me/relationships/{}/", api_url, user_id))
.bearer_auth(self.token());
handle_request_as_result(request, self, crate::api::limits::LimitType::Global).await
}
RelationshipType::Friends | RelationshipType::Incoming | RelationshipType::Outgoing => {
let body = CreateUserRelationshipSchema {
relationship_type: None, // Selecting 'None' here will accept an incoming FR or send a new FR.
from_friend_suggestion: None,
friend_token: None,
};
let request = Client::new()
.put(format!("{}/users/@me/relationships/{}/", api_url, user_id))
.bearer_auth(self.token())
.body(to_string(&body).unwrap());
handle_request_as_result(request, self, crate::api::limits::LimitType::Global).await
}
RelationshipType::Blocked => {
let body = CreateUserRelationshipSchema {
relationship_type: Some(RelationshipType::Blocked),
from_friend_suggestion: None,
friend_token: None,
};
let request = Client::new()
.put(format!("{}/users/@me/relationships/{}/", api_url, user_id))
.bearer_auth(self.token())
.body(to_string(&body).unwrap());
handle_request_as_result(request, self, crate::api::limits::LimitType::Global).await
}
RelationshipType::Suggestion | RelationshipType::Implicit => Ok(()),
}
}
/// Removes the relationship between the authenticated user and the specified user.
///
/// # Arguments
///
/// * `user_id` - A string slice that holds the ID of the user to remove the relationship with.
///
/// # Returns
/// This function returns a [`Result`] that holds a [`ChorusLibError`] if the request fails.
pub async fn remove_relationship(&mut self, user_id: &str) -> Result<(), ChorusLibError> {
let url = format!(
"{}/users/@me/relationships/{}/",
self.belongs_to.borrow().urls.api,
user_id
);
let request = Client::new().delete(url).bearer_auth(self.token());
handle_request_as_result(request, self, crate::api::limits::LimitType::Global).await
}
} }

View File

@ -2,7 +2,7 @@ use reqwest::Client;
use serde_json::to_string; use serde_json::to_string;
use crate::{ use crate::{
api::{deserialize_response, handle_request_as_option, limits::Limits}, api::{deserialize_response, handle_request_as_result, limits::Limits},
errors::ChorusLibError, errors::ChorusLibError,
instance::{Instance, UserMeta}, instance::{Instance, UserMeta},
limit::LimitedRequester, limit::LimitedRequester,
@ -52,10 +52,7 @@ impl UserMeta {
return Err(ChorusLibError::PasswordRequiredError); return Err(ChorusLibError::PasswordRequiredError);
} }
let request = Client::new() let request = Client::new()
.patch(format!( .patch(format!("{}/users/@me/", self.belongs_to.borrow().urls.api))
"{}/users/@me/",
self.belongs_to.borrow_mut().urls.get_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 user_updated =
@ -74,14 +71,15 @@ impl UserMeta {
/// ///
/// # Returns /// # Returns
/// ///
/// Returns `None` if the user was successfully deleted, or an `ChorusLibError` if an error occurred. /// Returns `()` if the user was successfully deleted, or a `ChorusLibError` if an error occurred.
pub async fn delete(mut self) -> Option<ChorusLibError> { pub async fn delete(mut self) -> Result<(), ChorusLibError> {
let belongs_to = self.belongs_to.borrow();
let request = Client::new() let request = Client::new()
.post(format!("{}/users/@me/delete/", belongs_to.urls.get_api())) .post(format!(
"{}/users/@me/delete/",
self.belongs_to.borrow().urls.api
))
.bearer_auth(self.token()); .bearer_auth(self.token());
drop(belongs_to); handle_request_as_result(request, &mut self, crate::api::limits::LimitType::Ip).await
handle_request_as_option(request, &mut self, crate::api::limits::LimitType::Ip).await
} }
} }
@ -90,7 +88,7 @@ impl User {
let mut belongs_to = user.belongs_to.borrow_mut(); let mut belongs_to = user.belongs_to.borrow_mut();
User::_get( User::_get(
&user.token(), &user.token(),
&format!("{}", belongs_to.urls.get_api()), &format!("{}", belongs_to.urls.api),
&mut belongs_to.limits, &mut belongs_to.limits,
id, id,
) )
@ -103,12 +101,11 @@ impl User {
limits_instance: &mut Limits, limits_instance: &mut Limits,
id: Option<&String>, id: Option<&String>,
) -> Result<User, ChorusLibError> { ) -> Result<User, ChorusLibError> {
let url: String; let url = if id.is_none() {
if id.is_none() { format!("{}/users/@me/", url_api)
url = format!("{}/users/@me/", url_api);
} else { } else {
url = 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(token);
let mut cloned_limits = limits_instance.clone(); let mut cloned_limits = limits_instance.clone();
match LimitedRequester::send_request( match LimitedRequester::send_request(
@ -166,12 +163,6 @@ impl Instance {
token: String, token: String,
id: Option<&String>, id: Option<&String>,
) -> Result<User, ChorusLibError> { ) -> Result<User, ChorusLibError> {
User::_get( User::_get(&token, &self.urls.api, &mut self.limits, id).await
&token,
&self.urls.get_api().to_string(),
&mut self.limits,
id,
)
.await
} }
} }

View File

@ -18,13 +18,14 @@ custom_error! {
CantGetInfoError{error:String} = "Something seems to be wrong with the instance. Cannot get information about the instance: {error}", CantGetInfoError{error:String} = "Something seems to be wrong with the instance. Cannot get information about the instance: {error}",
InvalidFormBodyError{error_type: String, error:String} = "The server responded with: {error_type}: {error}", InvalidFormBodyError{error_type: String, error:String} = "The server responded with: {error_type}: {error}",
RateLimited{bucket:String} = "Ratelimited on Bucket {bucket}", RateLimited{bucket:String} = "Ratelimited on Bucket {bucket}",
MultipartCreationError{error: String} = "Got an error whilst creating the form: {}", MultipartCreationError{error: String} = "Got an error whilst creating the form: {error}",
FormCreationError{error: String} = "Got an error whilst creating the form: {}", FormCreationError{error: String} = "Got an error whilst creating the form: {error}",
TokenExpired = "Token expired, invalid or not found.", TokenExpired = "Token expired, invalid or not found.",
NoPermission = "You do not have the permissions needed to perform this action.", NoPermission = "You do not have the permissions needed to perform this action.",
NotFound{error: String} = "The provided resource hasn't been found: {}", NotFound{error: String} = "The provided resource hasn't been found: {error}",
PasswordRequiredError = "You need to provide your current password to authenticate for this action.", PasswordRequiredError = "You need to provide your current password to authenticate for this action.",
InvalidResponseError{error: String} = "The response is malformed and cannot be processed. Error: {}", InvalidResponseError{error: String} = "The response is malformed and cannot be processed. Error: {error}",
InvalidArgumentsError{error: String} = "Invalid arguments were provided. Error: {error}"
} }
custom_error! { custom_error! {

View File

@ -3,6 +3,7 @@ use crate::errors::ObserverError;
use crate::gateway::events::Events; use crate::gateway::events::Events;
use crate::types; use crate::types;
use crate::types::WebSocketEvent; use crate::types::WebSocketEvent;
use std::any::Any;
use std::sync::Arc; use std::sync::Arc;
use futures_util::stream::SplitSink; use futures_util::stream::SplitSink;
@ -72,11 +73,9 @@ const GATEWAY_LAZY_REQUEST: u8 = 14;
/// The amount of time we wait for a heartbeat ack before resending our heartbeat in ms /// The amount of time we wait for a heartbeat ack before resending our heartbeat in ms
const HEARTBEAT_ACK_TIMEOUT: u128 = 2000; const HEARTBEAT_ACK_TIMEOUT: u128 = 2000;
/// Represents a messsage received from the gateway. This will be either a [GatewayReceivePayload], containing events, or a [GatewayError].
/// This struct is used internally when handling messages.
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
/**
Represents a messsage received from the gateway. This will be either a [GatewayReceivePayload], containing events, or a [GatewayError].
This struct is used internally when handling messages.
*/
pub struct GatewayMessage { pub struct GatewayMessage {
/// The message we received from the server /// The message we received from the server
message: tokio_tungstenite::tungstenite::Message, message: tokio_tungstenite::tungstenite::Message,
@ -94,60 +93,36 @@ impl GatewayMessage {
let content = self.message.to_string(); let content = self.message.to_string();
// Some error strings have dots on the end, which we don't care about // Some error strings have dots on the end, which we don't care about
let processed_content = content.clone().to_lowercase().replace(".", ""); let processed_content = content.to_lowercase().replace('.', "");
match processed_content.as_str() { match processed_content.as_str() {
"unknown error" | "4000" => { "unknown error" | "4000" => Some(GatewayError::UnknownError),
return Some(GatewayError::UnknownError); "unknown opcode" | "4001" => Some(GatewayError::UnknownOpcodeError),
}
"unknown opcode" | "4001" => {
return Some(GatewayError::UnknownOpcodeError);
}
"decode error" | "error while decoding payload" | "4002" => { "decode error" | "error while decoding payload" | "4002" => {
return Some(GatewayError::DecodeError); Some(GatewayError::DecodeError)
}
"not authenticated" | "4003" => {
return Some(GatewayError::NotAuthenticatedError);
}
"authentication failed" | "4004" => {
return Some(GatewayError::AuthenticationFailedError);
}
"already authenticated" | "4005" => {
return Some(GatewayError::AlreadyAuthenticatedError);
}
"invalid seq" | "4007" => {
return Some(GatewayError::InvalidSequenceNumberError);
}
"rate limited" | "4008" => {
return Some(GatewayError::RateLimitedError);
}
"session timed out" | "4009" => {
return Some(GatewayError::SessionTimedOutError);
}
"invalid shard" | "4010" => {
return Some(GatewayError::InvalidShardError);
}
"sharding required" | "4011" => {
return Some(GatewayError::ShardingRequiredError);
}
"invalid api version" | "4012" => {
return Some(GatewayError::InvalidAPIVersionError);
} }
"not authenticated" | "4003" => Some(GatewayError::NotAuthenticatedError),
"authentication failed" | "4004" => Some(GatewayError::AuthenticationFailedError),
"already authenticated" | "4005" => Some(GatewayError::AlreadyAuthenticatedError),
"invalid seq" | "4007" => Some(GatewayError::InvalidSequenceNumberError),
"rate limited" | "4008" => Some(GatewayError::RateLimitedError),
"session timed out" | "4009" => Some(GatewayError::SessionTimedOutError),
"invalid shard" | "4010" => Some(GatewayError::InvalidShardError),
"sharding required" | "4011" => Some(GatewayError::ShardingRequiredError),
"invalid api version" | "4012" => Some(GatewayError::InvalidAPIVersionError),
"invalid intent(s)" | "invalid intent" | "4013" => { "invalid intent(s)" | "invalid intent" | "4013" => {
return Some(GatewayError::InvalidIntentsError); Some(GatewayError::InvalidIntentsError)
} }
"disallowed intent(s)" | "disallowed intents" | "4014" => { "disallowed intent(s)" | "disallowed intents" | "4014" => {
return Some(GatewayError::DisallowedIntentsError); Some(GatewayError::DisallowedIntentsError)
}
_ => {
return None;
} }
_ => None,
} }
} }
/// Returns whether or not the message is an error /// Returns whether or not the message is an error
pub fn is_error(&self) -> bool { pub fn is_error(&self) -> bool {
return self.error().is_some(); self.error().is_some()
} }
/// Parses the message as a payload; /// Parses the message as a payload;
@ -168,17 +143,15 @@ impl GatewayMessage {
/// Returns whether or not the message is empty /// Returns whether or not the message is empty
pub fn is_empty(&self) -> bool { pub fn is_empty(&self) -> bool {
return self.message.is_empty(); self.message.is_empty()
} }
} }
/// Represents a handle to a Gateway connection. A Gateway connection will create observable
/// [`GatewayEvents`](GatewayEvent), which you can subscribe to. Gateway events include all currently
/// implemented [Types] with the trait [`WebSocketEvent`]
/// Using this handle you can also send Gateway Events directly.
#[derive(Debug)] #[derive(Debug)]
/**
Represents a handle to a Gateway connection. A Gateway connection will create observable
[`GatewayEvents`](GatewayEvent), which you can subscribe to. Gateway events include all currently
implemented [Types] with the trait [`WebSocketEvent`]
Using this handle you can also send Gateway Events directly.
*/
pub struct GatewayHandle { pub struct GatewayHandle {
pub url: String, pub url: String,
pub events: Arc<Mutex<Events>>, pub events: Arc<Mutex<Events>>,
@ -308,6 +281,7 @@ pub struct Gateway {
} }
impl Gateway { impl Gateway {
#[allow(clippy::new_ret_no_self)]
pub async fn new(websocket_url: String) -> Result<GatewayHandle, GatewayError> { pub async fn new(websocket_url: String) -> Result<GatewayHandle, GatewayError> {
let (websocket_stream, _) = match connect_async_tls_with_config( let (websocket_stream, _) = match connect_async_tls_with_config(
&websocket_url, &websocket_url,
@ -371,13 +345,13 @@ impl Gateway {
gateway.gateway_listen_task().await; gateway.gateway_listen_task().await;
}); });
return Ok(GatewayHandle { Ok(GatewayHandle {
url: websocket_url.clone(), url: websocket_url.clone(),
events: shared_events, events: shared_events,
websocket_send: shared_websocket_send.clone(), websocket_send: shared_websocket_send.clone(),
handle, handle,
kill_send: kill_send.clone(), kill_send: kill_send.clone(),
}); })
} }
/// The main gateway listener task; /// The main gateway listener task;
@ -388,15 +362,11 @@ impl Gateway {
let msg = self.websocket_receive.next().await; let msg = self.websocket_receive.next().await;
// This if chain can be much better but if let is unstable on stable rust // This if chain can be much better but if let is unstable on stable rust
if msg.as_ref().is_some() { if let Some(Ok(message)) = msg {
if msg.as_ref().unwrap().is_ok() { self.handle_message(GatewayMessage::from_tungstenite_message(message))
let msg_unwrapped = msg.unwrap().unwrap();
self.handle_message(GatewayMessage::from_tungstenite_message(msg_unwrapped))
.await; .await;
continue; continue;
} }
}
// We couldn't receive the next message or it was an error, something is wrong with the websocket, close // We couldn't receive the next message or it was an error, something is wrong with the websocket, close
println!("GW: Websocket is broken, stopping gateway"); println!("GW: Websocket is broken, stopping gateway");
@ -422,8 +392,8 @@ impl Gateway {
return Err(data_deserialize_result.err().unwrap()); return Err(data_deserialize_result.err().unwrap());
} }
event.update_data(data_deserialize_result.unwrap()).await; event.notify(data_deserialize_result.unwrap()).await;
return Ok(()); Ok(())
} }
/// This handles a message as a websocket event and updates its events along with the events' observers /// This handles a message as a websocket event and updates its events along with the events' observers
@ -444,11 +414,9 @@ impl Gateway {
if msg.is_error() { if msg.is_error() {
println!("GW: Received error, connection will close.."); println!("GW: Received error, connection will close..");
let error = msg.error(); let _error = msg.error();
match error { {}
_ => {}
}
self.close().await; self.close().await;
return; return;
@ -1399,13 +1367,7 @@ impl Gateway {
sessions: result.unwrap(), sessions: result.unwrap(),
}; };
self.events self.events.lock().await.session.replace.notify(data).await;
.lock()
.await
.session
.replace
.update_data(data)
.await;
} }
"USER_UPDATE" => { "USER_UPDATE" => {
let event = &mut self.events.lock().await.user.update; let event = &mut self.events.lock().await.user.update;
@ -1561,9 +1523,7 @@ impl Gateway {
} }
} }
/** /// Handles sending heartbeats to the gateway in another thread
Handles sending heartbeats to the gateway in another thread
*/
struct HeartbeatHandler { struct HeartbeatHandler {
/// The heartbeat interval in milliseconds /// The heartbeat interval in milliseconds
pub heartbeat_interval: u128, pub heartbeat_interval: u128,
@ -1698,10 +1658,8 @@ impl HeartbeatHandler {
} }
} }
/** /// Used for communications between the heartbeat and gateway thread.
Used for communications between the heartbeat and gateway thread. /// Either signifies a sequence number update, a heartbeat ACK or a Heartbeat request by the server
Either signifies a sequence number update, a heartbeat ACK or a Heartbeat request by the server
*/
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
struct HeartbeatThreadCommunication { struct HeartbeatThreadCommunication {
/// The opcode for the communication we received, if relevant /// The opcode for the communication we received, if relevant
@ -1710,89 +1668,47 @@ struct HeartbeatThreadCommunication {
sequence_number: Option<u64>, sequence_number: Option<u64>,
} }
/** /// Trait which defines the behavior of an Observer. An Observer is an object which is subscribed to
Trait which defines the behavior of an Observer. An Observer is an object which is subscribed to /// an Observable. The Observer is notified when the Observable's data changes.
an Observable. The Observer is notified when the Observable's data changes. /// In this case, the Observable is a [`GatewayEvent`], which is a wrapper around a WebSocketEvent.
In this case, the Observable is a [`GatewayEvent`], which is a wrapper around a WebSocketEvent. /// Note that `Debug` is used to tell `Observer`s apart when unsubscribing.
*/ pub trait Observer<T>: Sync + Send + std::fmt::Debug {
pub trait Observer<T: types::WebSocketEvent>: std::fmt::Debug { fn update(&self, data: &T);
fn update(&mut self, data: &T);
} }
/** GatewayEvent is a wrapper around a WebSocketEvent. It is used to notify the observers of a /// GatewayEvent is a wrapper around a WebSocketEvent. It is used to notify the observers of a
change in the WebSocketEvent. GatewayEvents are observable. /// change in the WebSocketEvent. GatewayEvents are observable.
*/
#[derive(Default, Debug)] #[derive(Default, Debug)]
pub struct GatewayEvent<T: types::WebSocketEvent> { pub struct GatewayEvent<T: WebSocketEvent> {
observers: Vec<Arc<Mutex<dyn Observer<T> + Sync + Send>>>, observers: Vec<Arc<dyn Observer<T>>>,
pub event_data: T,
pub is_observed: bool,
} }
impl<T: types::WebSocketEvent> GatewayEvent<T> { impl<T: WebSocketEvent> GatewayEvent<T> {
fn new(event_data: T) -> Self { /// Returns true if the GatewayEvent is observed by at least one Observer.
Self {
is_observed: false,
observers: Vec::new(),
event_data,
}
}
/**
Returns true if the GatewayEvent is observed by at least one Observer.
*/
pub fn is_observed(&self) -> bool { pub fn is_observed(&self) -> bool {
self.is_observed !self.observers.is_empty()
} }
/** /// Subscribes an Observer to the GatewayEvent.
Subscribes an Observer to the GatewayEvent. Returns an error if the GatewayEvent is already pub fn subscribe(&mut self, observable: Arc<dyn Observer<T>>) {
observed.
# Errors
Returns an error if the GatewayEvent is already observed.
Error type: [`ObserverError::AlreadySubscribedError`]
*/
pub fn subscribe(
&mut self,
observable: Arc<Mutex<dyn Observer<T> + Sync + Send>>,
) -> Result<(), ObserverError> {
if self.is_observed {
return Err(ObserverError::AlreadySubscribedError);
}
self.is_observed = true;
self.observers.push(observable); self.observers.push(observable);
Ok(())
} }
/** /// Unsubscribes an Observer from the GatewayEvent.
Unsubscribes an Observer from the GatewayEvent. pub fn unsubscribe(&mut self, observable: &dyn Observer<T>) {
*/
pub fn unsubscribe(&mut self, observable: Arc<Mutex<dyn Observer<T> + Sync + Send>>) {
// .retain()'s closure retains only those elements of the vector, which have a different // .retain()'s closure retains only those elements of the vector, which have a different
// pointer value than observable. // pointer value than observable.
// The usage of the debug format to compare the generic T of observers is quite stupid, but the only thing to compare between them is T and if T == T they are the same // The usage of the debug format to compare the generic T of observers is quite stupid, but the only thing to compare between them is T and if T == T they are the same
// anddd there is no way to do that without using format // anddd there is no way to do that without using format
let to_remove = format!("{:?}", observable);
self.observers self.observers
.retain(|obs| !(format!("{:?}", obs) == format!("{:?}", &observable))); .retain(|obs| format!("{:?}", obs) != to_remove);
self.is_observed = !self.observers.is_empty();
} }
/** /// Notifies the observers of the GatewayEvent.
Updates the GatewayEvent's data and notifies the observers. async fn notify(&self, new_event_data: T) {
*/
async fn update_data(&mut self, new_event_data: T) {
self.event_data = new_event_data;
self.notify().await;
}
/**
Notifies the observers of the GatewayEvent.
*/
async fn notify(&self) {
for observer in &self.observers { for observer in &self.observers {
let mut observer_lock = observer.lock().await; observer.update(&new_event_data);
observer_lock.update(&self.event_data);
drop(observer_lock);
} }
} }
} }
@ -1961,23 +1877,23 @@ mod events {
#[cfg(test)] #[cfg(test)]
mod example { mod example {
use super::*; use super::*;
use std::sync::atomic::{AtomicI32, Ordering::Relaxed};
#[derive(Debug)] #[derive(Debug)]
struct Consumer; struct Consumer {
name: String,
events_received: AtomicI32,
}
impl Observer<types::GatewayResume> for Consumer { impl Observer<types::GatewayResume> for Consumer {
fn update(&mut self, data: &types::GatewayResume) { fn update(&self, _data: &types::GatewayResume) {
println!("{}", data.token) self.events_received.fetch_add(1, Relaxed);
} }
} }
#[tokio::test] #[tokio::test]
async fn test_observer_behavior() { async fn test_observer_behavior() {
let mut event = GatewayEvent::new(types::GatewayResume { let mut event = GatewayEvent::default();
token: "start".to_string(),
session_id: "start".to_string(),
seq: "start".to_string(),
});
let new_data = types::GatewayResume { let new_data = types::GatewayResume {
token: "token_3276ha37am3".to_string(), token: "token_3276ha37am3".to_string(),
@ -1985,25 +1901,23 @@ mod example {
seq: "3".to_string(), seq: "3".to_string(),
}; };
let consumer = Consumer; let consumer = Arc::new(Consumer {
let arc_mut_consumer = Arc::new(Mutex::new(consumer)); name: "first".into(),
events_received: 0.into(),
});
event.subscribe(consumer.clone());
event.subscribe(arc_mut_consumer.clone()).unwrap(); let second_consumer = Arc::new(Consumer {
name: "second".into(),
events_received: 0.into(),
});
event.subscribe(second_consumer.clone());
event.notify().await; event.notify(new_data.clone()).await;
event.unsubscribe(&*consumer);
event.notify(new_data).await;
event.update_data(new_data).await; assert_eq!(consumer.events_received.load(Relaxed), 1);
assert_eq!(second_consumer.events_received.load(Relaxed), 2);
let second_consumer = Consumer;
let arc_mut_second_consumer = Arc::new(Mutex::new(second_consumer));
match event.subscribe(arc_mut_second_consumer.clone()).err() {
None => assert!(false),
Some(err) => println!("You cannot subscribe twice: {}", err),
}
event.unsubscribe(arc_mut_consumer.clone());
event.subscribe(arc_mut_second_consumer.clone()).unwrap();
} }
} }

View File

@ -7,14 +7,14 @@ use serde::{Deserialize, Serialize};
use crate::api::limits::Limits; use crate::api::limits::Limits;
use crate::errors::{ChorusLibError, FieldFormatError}; use crate::errors::{ChorusLibError, FieldFormatError};
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.
*/ */
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: Limits,
} }
@ -26,20 +26,11 @@ 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) -> Result<Instance, ChorusLibError> { pub async fn new(urls: UrlBundle) -> Result<Instance, ChorusLibError> {
let mut instance = Instance { let mut instance = Instance {
urls: urls.clone(), urls: urls.clone(),
instance_info: GeneralConfiguration::new( // Will be overwritten in the next step
// This is okay, because the instance_info will be overwritten by the instance_policies_schema() function. instance_info: GeneralConfiguration::default(),
"".to_string(),
None,
None,
None,
None,
None,
None,
None,
),
limits: Limits::check_limits(urls.api).await, limits: Limits::check_limits(urls.api).await,
}; };
instance.instance_info = match instance.general_configuration_schema().await { instance.instance_info = match instance.general_configuration_schema().await {

View File

@ -1,3 +1,5 @@
#![allow(clippy::module_inception)]
use url::{ParseError, Url}; use url::{ParseError, Url};
#[cfg(feature = "client")] #[cfg(feature = "client")]
@ -16,18 +18,18 @@ pub mod voice;
#[derive(Clone, Default, Debug, PartialEq, Eq)] #[derive(Clone, Default, Debug, PartialEq, Eq)]
/// A URLBundle is a struct which bundles together the API-, Gateway- and CDN-URLs of a Spacebar /// A URLBundle is a struct which bundles together the API-, Gateway- and CDN-URLs of a Spacebar
/// instance. /// instance.
pub struct URLBundle { pub struct UrlBundle {
pub api: String, pub api: String,
pub wss: String, pub wss: String,
pub cdn: String, pub cdn: String,
} }
impl URLBundle { impl UrlBundle {
pub fn new(api: String, wss: String, cdn: String) -> Self { pub fn new(api: String, wss: String, cdn: String) -> Self {
Self { Self {
api: URLBundle::parse_url(api), api: UrlBundle::parse_url(api),
wss: URLBundle::parse_url(wss), wss: UrlBundle::parse_url(wss),
cdn: URLBundle::parse_url(cdn), cdn: UrlBundle::parse_url(cdn),
} }
} }
@ -42,13 +44,13 @@ impl URLBundle {
let url = match Url::parse(&url) { let url = match Url::parse(&url) {
Ok(url) => { Ok(url) => {
if url.scheme() == "localhost" { if url.scheme() == "localhost" {
return URLBundle::parse_url(format!("http://{}", url)); return UrlBundle::parse_url(format!("http://{}", url));
} }
url url
} }
Err(ParseError::RelativeUrlWithoutBase) => { Err(ParseError::RelativeUrlWithoutBase) => {
let url_fmt = format!("http://{}", url); let url_fmt = format!("http://{}", url);
return URLBundle::parse_url(url_fmt); return UrlBundle::parse_url(url_fmt);
} }
Err(_) => panic!("Invalid URL"), Err(_) => panic!("Invalid URL"),
}; };
@ -59,18 +61,6 @@ impl URLBundle {
} }
url_string url_string
} }
pub fn get_api(&self) -> &str {
&self.api
}
pub fn get_cdn(&self) -> &str {
&self.cdn
}
pub fn get_wss(&self) -> &str {
&self.wss
}
} }
#[cfg(test)] #[cfg(test)]
@ -79,13 +69,13 @@ mod lib {
#[test] #[test]
fn test_parse_url() { fn test_parse_url() {
let mut result = URLBundle::parse_url(String::from("localhost:3000/")); let mut result = UrlBundle::parse_url(String::from("localhost:3000/"));
assert_eq!(result, String::from("http://localhost:3000")); assert_eq!(result, String::from("http://localhost:3000"));
result = URLBundle::parse_url(String::from("https://some.url.com/")); result = UrlBundle::parse_url(String::from("https://some.url.com/"));
assert_eq!(result, String::from("https://some.url.com")); assert_eq!(result, String::from("https://some.url.com"));
result = URLBundle::parse_url(String::from("https://some.url.com/")); result = UrlBundle::parse_url(String::from("https://some.url.com/"));
assert_eq!(result, String::from("https://some.url.com")); assert_eq!(result, String::from("https://some.url.com"));
result = URLBundle::parse_url(String::from("https://some.url.com")); result = UrlBundle::parse_url(String::from("https://some.url.com"));
assert_eq!(result, String::from("https://some.url.com")); assert_eq!(result, String::from("https://some.url.com"));
} }
} }

View File

@ -82,13 +82,11 @@ impl LimitedRequester {
); );
if !response.status().is_success() { if !response.status().is_success() {
match response.status().as_u16() { match response.status().as_u16() {
401 => return Err(ChorusLibError::TokenExpired), 401 => Err(ChorusLibError::TokenExpired),
403 => return Err(ChorusLibError::TokenExpired), 403 => Err(ChorusLibError::TokenExpired),
_ => { _ => Err(ChorusLibError::ReceivedErrorCodeError {
return Err(ChorusLibError::ReceivedErrorCodeError {
error_code: response.status().as_str().to_string(), error_code: response.status().as_str().to_string(),
}); }),
}
} }
} else { } else {
Ok(response) Ok(response)
@ -258,13 +256,13 @@ impl LimitedRequester {
mod rate_limit { mod rate_limit {
use serde_json::from_str; use serde_json::from_str;
use crate::{api::limits::Config, URLBundle}; use crate::{api::limits::Config, UrlBundle};
use super::*; use super::*;
#[tokio::test] #[tokio::test]
async fn run_into_limit() { async fn run_into_limit() {
let urls = URLBundle::new( let urls = UrlBundle::new(
String::from("http://localhost:3001/api/"), String::from("http://localhost:3001/api/"),
String::from("wss://localhost:3001/"), String::from("wss://localhost:3001/"),
String::from("http://localhost:3001/cdn"), String::from("http://localhost:3001/cdn"),
@ -286,19 +284,12 @@ mod rate_limit {
.await, .await,
); );
} }
if request.is_some() { assert!(matches!(request, Some(Err(_))));
match request.unwrap() {
Ok(_) => assert!(false),
Err(_) => assert!(true),
}
} else {
assert!(false)
}
} }
#[tokio::test] #[tokio::test]
async fn test_send_request() { async fn test_send_request() {
let urls = URLBundle::new( let urls = UrlBundle::new(
String::from("http://localhost:3001/api/"), String::from("http://localhost:3001/api/"),
String::from("wss://localhost:3001/"), String::from("wss://localhost:3001/"),
String::from("http://localhost:3001/cdn"), String::from("http://localhost:3001/cdn"),

View File

@ -89,15 +89,15 @@ fn generate_pairs(obj: &Value, key: &str) -> Vec<ConfigEntity> {
fn pairs_to_config(pairs: Vec<ConfigEntity>) -> ConfigValue { fn pairs_to_config(pairs: Vec<ConfigEntity>) -> ConfigValue {
let mut value = Value::Object(Map::new()); let mut value = Value::Object(Map::new());
for p in pairs { for pair in pairs {
let keys: Vec<&str> = p.key.split('_').collect(); let keys: Vec<&str> = pair.key.split('_').collect();
let mut path = vec![]; let mut path = vec![];
for (i, &key) in keys.iter().enumerate() { for (i, &key) in keys.iter().enumerate() {
path.push(key); path.push(key);
if i == keys.len() - 1 { if i == keys.len() - 1 {
insert_into(&mut value, &path, p.value.clone().unwrap_or(Value::Null)); insert_into(&mut value, &path, pair.value.clone().unwrap_or(Value::Null));
} else if keys[i + 1].parse::<usize>().is_ok() { } else if keys[i + 1].parse::<usize>().is_ok() {
if !path_exists(&value, &path) { if !path_exists(&value, &path) {
insert_into(&mut value, &path, Value::Array(Vec::new())); insert_into(&mut value, &path, Value::Array(Vec::new()));
@ -182,6 +182,7 @@ mod test {
let pairs = generate_pairs(&v, ""); let pairs = generate_pairs(&v, "");
let cfg = pairs_to_config(pairs); let cfg = pairs_to_config(pairs);
assert_eq!(cfg, c) assert_eq!(cfg, c)
} }
} }

View File

@ -31,27 +31,3 @@ impl Default for GeneralConfiguration {
} }
} }
} }
impl GeneralConfiguration {
pub fn new(
instance_name: String,
instance_description: Option<String>,
front_page: Option<String>,
tos_page: Option<String>,
correspondence_email: Option<String>,
correspondence_user_id: Option<String>,
image: Option<String>,
instance_id: Option<Snowflake>,
) -> Self {
Self {
instance_name,
instance_description,
front_page,
tos_page,
correspondence_email,
correspondence_user_id,
image,
instance_id,
}
}
}

View File

@ -1,4 +1,5 @@
use std::fmt::{Display, Formatter}; use std::fmt::{Display, Formatter};
#[cfg(feature = "sqlx")]
use std::io::Write; use std::io::Write;
use std::ops::{Deref, DerefMut}; use std::ops::{Deref, DerefMut};
use std::str::FromStr; use std::str::FromStr;

View File

@ -1,4 +1,4 @@
use bitflags::{bitflags, Flags}; use bitflags::bitflags;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_json::Value; use serde_json::Value;
use serde_repr::{Deserialize_repr, Serialize_repr}; use serde_repr::{Deserialize_repr, Serialize_repr};

View File

@ -6,7 +6,7 @@ use crate::types::Snowflake;
use super::PublicUser; use super::PublicUser;
#[derive(Debug, Deserialize, Serialize, Clone, Default)] #[derive(Debug, Deserialize, Serialize, Clone, Default, PartialEq, Eq)]
/// See https://discord-userdoccers.vercel.app/resources/user#relationship-structure /// See https://discord-userdoccers.vercel.app/resources/user#relationship-structure
pub struct Relationship { pub struct Relationship {
pub id: Snowflake, pub id: Snowflake,
@ -17,7 +17,7 @@ pub struct Relationship {
pub since: Option<DateTime<Utc>>, pub since: Option<DateTime<Utc>>,
} }
#[derive(Serialize_repr, Deserialize_repr, Debug, Clone, Default)] #[derive(Serialize_repr, Deserialize_repr, Debug, Clone, Default, Eq, PartialEq)]
#[repr(u8)] #[repr(u8)]
/// See https://discord-userdoccers.vercel.app/resources/user#relationship-type /// See https://discord-userdoccers.vercel.app/resources/user#relationship-type
pub enum RelationshipType { pub enum RelationshipType {

View File

@ -108,6 +108,7 @@ impl PermissionFlags {
self.contains(permission) || self.contains(PermissionFlags::ADMINISTRATOR) self.contains(permission) || self.contains(PermissionFlags::ADMINISTRATOR)
} }
#[allow(clippy::inherent_to_string)]
pub fn to_string(&self) -> String { pub fn to_string(&self) -> String {
self.bits().to_string() self.bits().to_string()
} }
@ -129,7 +130,7 @@ impl PermissionFlags {
pub fn from_vec(flags: Vec<PermissionFlags>) -> String { pub fn from_vec(flags: Vec<PermissionFlags>) -> String {
let mut permissions: PermissionFlags = Default::default(); let mut permissions: PermissionFlags = Default::default();
for flag in flags.iter() { for flag in flags.iter() {
permissions = permissions | flag.clone(); permissions |= flag.clone();
} }
permissions.to_string() permissions.to_string()
} }

View File

@ -51,16 +51,18 @@ impl GatewayIdentifyPayload {
impl GatewayIdentifyPayload { impl GatewayIdentifyPayload {
/// Creates an identify payload with the same default capabilities as the official client /// Creates an identify payload with the same default capabilities as the official client
pub fn default_w_client_capabilities() -> Self { pub fn default_w_client_capabilities() -> Self {
let mut def = Self::default(); Self {
def.capabilities = Some(8189); // Default capabilities for a client capabilities: Some(8189), // Default capabilities for a client
def ..Self::default()
}
} }
/// Creates an identify payload with all possible capabilities /// Creates an identify payload with all possible capabilities
pub fn default_w_all_capabilities() -> Self { pub fn default_w_all_capabilities() -> Self {
let mut def = Self::default(); Self {
def.capabilities = Some(i32::MAX); // Since discord uses bitwise for capabilities, this has almost every bit as 1, so all capabilities capabilities: Some(i32::MAX), // Since discord uses bitwise for capabilities, this has almost every bit as 1, so all capabilities
def ..Self::default()
}
} }
} }
@ -148,22 +150,18 @@ impl GatewayIdentifyConnectionProps {
/// Returns the most common connection props so we can't be tracked /// Returns the most common connection props so we can't be tracked
pub fn common() -> Self { pub fn common() -> Self {
let mut default = Self::minimal(); Self {
// See https://www.useragents.me/#most-common-desktop-useragents // See https://www.useragents.me/#most-common-desktop-useragents
// 25% of the web // 25% of the web
//default.browser_user_agent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36".to_string(); //default.browser_user_agent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36".to_string();
default.browser = String::from("Chrome"); browser: String::from("Chrome"),
default.browser_version = String::from("113.0.0.0"); browser_version: String::from("113.0.0.0"),
system_locale: String::from("en-US"),
default.system_locale = String::from("en-US"); os: String::from("Windows"),
os_version: Some(String::from("10")),
default.os = String::from("Windows"); client_build_number: 199933,
default.os_version = Some(String::from("10")); release_channel: String::from("stable"),
..Self::minimal()
default.client_build_number = 199933; }
default.release_channel = String::from("stable");
return default;
} }
} }

View File

@ -1,5 +1,5 @@
use crate::types::{events::WebSocketEvent, UserStatus}; use crate::types::{events::WebSocketEvent, UserStatus};
use crate::types::{Activity, PublicUser, Snowflake}; use crate::types::{Activity, ClientStatusObject, PublicUser, Snowflake};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
#[derive(Debug, Deserialize, Serialize, Default, Clone)] #[derive(Debug, Deserialize, Serialize, Default, Clone)]
@ -26,12 +26,4 @@ pub struct PresenceUpdate {
pub client_status: ClientStatusObject, pub client_status: ClientStatusObject,
} }
#[derive(Debug, Deserialize, Serialize, Default, Clone)]
/// See https://discord.com/developers/docs/topics/gateway-events#client-status-object
pub struct ClientStatusObject {
pub desktop: Option<String>,
pub mobile: Option<String>,
pub web: Option<String>,
}
impl WebSocketEvent for PresenceUpdate {} impl WebSocketEvent for PresenceUpdate {}

View File

@ -1,7 +1,7 @@
use crate::types::events::WebSocketEvent; use crate::types::events::WebSocketEvent;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
#[derive(Debug, Deserialize, Serialize, Default)] #[derive(Debug, Clone, Deserialize, Serialize, Default)]
pub struct GatewayResume { pub struct GatewayResume {
pub token: String, pub token: String,
pub session_id: String, pub session_id: String,

View File

@ -116,9 +116,39 @@ pub struct RegisterSchema {
promotional_email_opt_in: Option<bool>, promotional_email_opt_in: Option<bool>,
} }
pub struct RegisterSchemaOptions {
pub username: String,
pub password: Option<String>,
pub consent: bool,
pub email: Option<String>,
pub fingerprint: Option<String>,
pub invite: Option<String>,
pub date_of_birth: Option<String>,
pub gift_code_sku_id: Option<String>,
pub captcha_key: Option<String>,
pub promotional_email_opt_in: Option<bool>,
}
impl RegisterSchema { 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 {
/** /**
Returns a new [`Result<RegisterSchema, FieldFormatError>`]. Create a new [`RegisterSchema`].
## Arguments ## Arguments
All but "String::username" and "bool::consent" are optional. All but "String::username" and "bool::consent" are optional.
@ -129,47 +159,36 @@ impl RegisterSchema {
These constraints have been defined [in the Spacebar-API](https://docs.spacebar.chat/routes/) These constraints have been defined [in the Spacebar-API](https://docs.spacebar.chat/routes/)
*/ */
pub fn new( pub fn build(self) -> Result<RegisterSchema, FieldFormatError> {
username: String, let username = AuthUsername::new(self.username)?.username;
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>,
) -> Result<RegisterSchema, FieldFormatError> {
let username = AuthUsername::new(username)?.username;
let email = if let Some(email) = email { let email = if let Some(email) = self.email {
Some(AuthEmail::new(email)?.email) Some(AuthEmail::new(email)?.email)
} else { } else {
None None
}; };
let password = if let Some(password) = password { let password = if let Some(password) = self.password {
Some(AuthPassword::new(password)?.password) Some(AuthPassword::new(password)?.password)
} else { } else {
None None
}; };
if !consent { if !self.consent {
return Err(FieldFormatError::ConsentError); return Err(FieldFormatError::ConsentError);
} }
Ok(RegisterSchema { Ok(RegisterSchema {
username, username,
password, password,
consent, consent: self.consent,
email, email,
fingerprint, fingerprint: self.fingerprint,
invite, invite: self.invite,
date_of_birth, date_of_birth: self.date_of_birth,
gift_code_sku_id, gift_code_sku_id: self.gift_code_sku_id,
captcha_key, captcha_key: self.captcha_key,
promotional_email_opt_in, promotional_email_opt_in: self.promotional_email_opt_in,
}) })
} }
} }

View File

@ -4,47 +4,18 @@ use crate::types::entities::{
AllowedMention, Component, Embed, MessageReference, PartialDiscordFileAttachment, AllowedMention, Component, Embed, MessageReference, PartialDiscordFileAttachment,
}; };
#[derive(Debug, Deserialize, Serialize)] #[derive(Debug, Default, Deserialize, Serialize)]
#[serde(rename_all = "snake_case")] #[serde(rename_all = "snake_case")]
pub struct MessageSendSchema { pub struct MessageSendSchema {
#[serde(rename = "type")] #[serde(rename = "type")]
message_type: Option<i32>, pub message_type: Option<i32>,
content: Option<String>, pub content: Option<String>,
nonce: Option<String>, pub nonce: Option<String>,
tts: Option<bool>, pub tts: Option<bool>,
embeds: Option<Vec<Embed>>, pub embeds: Option<Vec<Embed>>,
allowed_mentions: Option<AllowedMention>, pub allowed_mentions: Option<AllowedMention>,
message_reference: Option<MessageReference>, pub message_reference: Option<MessageReference>,
components: Option<Vec<Component>>, pub components: Option<Vec<Component>>,
sticker_ids: Option<Vec<String>>, pub sticker_ids: Option<Vec<String>>,
pub attachments: Option<Vec<PartialDiscordFileAttachment>>, pub attachments: Option<Vec<PartialDiscordFileAttachment>>,
} }
// make a new() method for MessageSendSchema
impl MessageSendSchema {
pub fn new(
message_type: Option<i32>,
content: Option<String>,
nonce: Option<String>,
tts: Option<bool>,
embeds: Option<Vec<Embed>>,
allowed_mentions: Option<AllowedMention>,
message_reference: Option<MessageReference>,
components: Option<Vec<Component>>,
sticker_ids: Option<Vec<String>>,
attachments: Option<Vec<PartialDiscordFileAttachment>>,
) -> MessageSendSchema {
MessageSendSchema {
message_type,
content,
nonce,
tts,
embeds,
allowed_mentions,
message_reference,
components,
sticker_ids,
attachments,
}
}
}

View File

@ -65,18 +65,7 @@ mod schemas_tests {
#[test] #[test]
fn consent_false() { fn consent_false() {
assert_eq!( assert_eq!(
RegisterSchema::new( RegisterSchema::builder("Test", false).build(),
"Test".to_string(),
None,
false,
None,
None,
None,
None,
None,
None,
None,
),
Err(FieldFormatError::ConsentError) Err(FieldFormatError::ConsentError)
); );
} }
@ -91,18 +80,11 @@ mod schemas_tests {
#[test] #[test]
fn valid_email() { fn valid_email() {
let reg = RegisterSchema::new( let reg = RegisterSchemaOptions {
"Testy".to_string(), email: Some("me@mail.de".to_string()),
None, ..RegisterSchema::builder("Testy", true)
true, }
Some("me@mail.de".to_string()), .build();
None,
None,
None,
None,
None,
None,
);
assert_ne!(reg, Err(FieldFormatError::EmailError)); assert_ne!(reg, Err(FieldFormatError::EmailError));
} }
} }

View File

@ -1 +1,25 @@
use serde::{Deserialize, Serialize};
use crate::types::RelationshipType;
#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct FriendRequestSendSchema {
pub username: String,
pub discriminator: Option<String>,
}
/// Represents the schema for the Create User Relationship route.
/// # Arguments
///
/// * relationship_type: The [`RelationshipType`] to create (defaults to -1, which accepts an existing or creates a new friend request)
/// * from_friend_suggestion: Whether the relationship was created from a friend suggestion (default false)
/// * friend_token: The friend token of the user to add a direct friend relationship to
///
/// See: [https://discord-userdoccers.vercel.app/resources/user#create-user-relationship](https://discord-userdoccers.vercel.app/resources/user#create-user-relationship)
#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct CreateUserRelationshipSchema {
#[serde(rename = "type")]
pub relationship_type: Option<RelationshipType>,
pub from_friend_suggestion: Option<bool>,
pub friend_token: Option<String>,
}

View File

@ -1,6 +1,6 @@
pub use regexes::*; pub use regexes::*;
pub use rights::Rights; pub use rights::Rights;
pub use snowflake::{DeconstructedSnowflake, Snowflake}; pub use snowflake::Snowflake;
pub mod jwt; pub mod jwt;
mod regexes; mod regexes;

View File

@ -1,22 +1,41 @@
use std::fmt::Display; use std::{
fmt::Display,
sync::atomic::{AtomicUsize, Ordering},
};
use atomic::Atomic; use chrono::{DateTime, TimeZone, Utc};
use bigdecimal::{Num, ToPrimitive, Zero};
use num_bigint::{BigInt, ToBigInt};
use serde::{Deserialize, Serialize};
#[cfg(feature = "sqlx")] #[cfg(feature = "sqlx")]
use sqlx::Type; use sqlx::Type;
/// 2015-01-01
const EPOCH: i64 = 1420070400000; const EPOCH: i64 = 1420070400000;
static WORKER_ID: u128 = 0;
static PROCESS_ID: u128 = 1; /// Unique identifier including a timestamp.
lazy_static::lazy_static! { /// See https://discord.com/developers/docs/reference#snowflakes
static ref INCREMENT: Atomic<u128> = Atomic::default(); #[derive(Debug, Copy, Clone, PartialEq, Eq)]
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[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(String); pub struct Snowflake(u64);
impl Snowflake {
pub fn generate() -> Self {
const WORKER_ID: u64 = 0;
const PROCESS_ID: u64 = 1;
static INCREMENT: AtomicUsize = AtomicUsize::new(0);
let time = (Utc::now().naive_utc().timestamp_millis() - EPOCH) << 22;
let worker = WORKER_ID << 17;
let process = PROCESS_ID << 12;
let increment = INCREMENT.fetch_add(1, Ordering::Relaxed) as u64 % 32;
Self(time as u64 | worker | process | increment)
}
pub fn timestamp(self) -> DateTime<Utc> {
Utc.timestamp_millis_opt((self.0 >> 22) as i64 + EPOCH)
.unwrap()
}
}
impl Default for Snowflake { impl Default for Snowflake {
fn default() -> Self { fn default() -> Self {
@ -30,131 +49,59 @@ impl Display for Snowflake {
} }
} }
impl Snowflake { impl serde::Serialize for Snowflake {
pub fn to_binary(&self) -> String { fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
let self_len = self.0.len(); where
let high = self.0[..self_len - 10].parse::<u64>().unwrap_or(0); S: serde::Serializer,
let low = self.0[self_len - 10..].parse::<u64>().unwrap(); {
let mut low = low; serializer.serialize_str(&self.0.to_string())
let mut high = high;
let mut bin = Vec::with_capacity(64);
while low > 0 || high > 0 {
bin.push((low & 1) as u8);
low >>= 1;
if high > 0 {
low += 5_000_000_000 * (high % 2);
high >>= 1;
}
}
bin.iter()
.rev()
.map(|b| char::from_digit(*b as u32, 10).unwrap())
.collect()
}
pub fn from_binary(num: &str) -> String {
let mut num = BigInt::from_str_radix(num, 2).unwrap();
let mut dec = Vec::with_capacity(18);
let ten = 10.to_bigint().unwrap();
let _two = 2.to_bigint().unwrap();
let _thirty_two = 32.to_bigint().unwrap();
while num.bits() > 50 {
let high: BigInt = &num >> 32;
let low: BigInt = (high.clone() % &ten) << 32 | &num & BigInt::from((1u64 << 32) - 1);
let next: BigInt = low.clone() % &ten;
dec.push(next.to_u8().unwrap());
num = (high / &ten) << 32 | (low / &ten);
}
while !num.is_zero() {
dec.push((num.clone() % &ten).to_u8().unwrap());
num /= &ten;
}
dec.iter()
.rev()
.map(|d| char::from_digit(*d as u32, 10).unwrap())
.collect()
}
pub fn generate_worker_process() -> u128 {
let time = (chrono::Utc::now().naive_utc().timestamp_millis() - EPOCH) << 22;
let worker = WORKER_ID << 17;
let process = PROCESS_ID << 12;
let increment = INCREMENT.load(atomic::Ordering::Relaxed);
INCREMENT.store(increment + 1, atomic::Ordering::Relaxed);
time as u128 | worker | process | increment
}
pub fn generate() -> Self {
Self(Self::generate_worker_process().to_string())
}
pub fn deconstruct(&self) -> DeconstructedSnowflake {
let binary = format!("{:0>64}", self.to_binary());
let ts = i64::from_str_radix(&binary[0..42], 2).unwrap() + EPOCH;
let wid = u64::from_str_radix(&binary[42..47], 2).unwrap();
let pid = u64::from_str_radix(&binary[47..52], 2).unwrap();
let increment = BigInt::from_str_radix(&binary[52..64], 2).unwrap();
DeconstructedSnowflake {
timestamp: ts,
worker_id: wid,
process_id: pid,
increment,
binary,
}
} }
} }
#[derive(Debug, Clone, PartialEq, Eq)] impl<'de> serde::Deserialize<'de> for Snowflake {
pub struct DeconstructedSnowflake { fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
pub timestamp: i64, where
pub worker_id: u64, D: serde::Deserializer<'de>,
pub process_id: u64, {
pub increment: BigInt, struct SnowflakeVisitor;
pub binary: String, impl<'de> serde::de::Visitor<'de> for SnowflakeVisitor {
type Value = Snowflake;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str("snowflake string")
}
fn visit_str<E>(self, value: &str) -> Result<Snowflake, E>
where
E: serde::de::Error,
{
match value.parse() {
Ok(value) => Ok(Snowflake(value)),
Err(_) => Err(serde::de::Error::custom("")),
}
}
}
deserializer.deserialize_str(SnowflakeVisitor)
}
} }
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use chrono::{DateTime, Utc};
use super::Snowflake; use super::Snowflake;
#[test] #[test]
fn test_new_snowflake() { fn generate() {
let snow = Snowflake::generate(); let snow_1 = Snowflake::generate();
println!("{snow}"); let snow_2 = Snowflake::generate();
assert!(snow_1.0 < snow_2.0)
} }
#[test] #[test]
fn snowflake_to_binary() { fn timestamp() {
let snowflake = super::Snowflake("1104339392517902336".to_string()); let snow: Snowflake = serde_json::from_str("\"175928847299117063\"").unwrap();
let timestamp = "2016-04-30 11:18:25.796Z".parse::<DateTime<Utc>>().unwrap();
let bin = snowflake.to_binary(); assert_eq!(snow.timestamp(), timestamp);
println!("{bin}");
}
#[test]
fn binary_to_snowflake() {
let snowflake = super::Snowflake::from_binary(
"111101010011011001101101001110010010100000000001000000000000",
);
println!("{snowflake}");
}
#[test]
fn test_deconstruct() {
let new = super::Snowflake::generate();
println!("{:?}", new.deconstruct());
} }
} }

View File

@ -1,22 +1,15 @@
use chorus::types; use chorus::types::{RegisterSchema, RegisterSchemaOptions};
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 = types::RegisterSchema::new( let reg = RegisterSchemaOptions {
"Hiiii".to_string(), date_of_birth: Some("2000-01-01".to_string()),
None, ..RegisterSchema::builder("Hiiii", true)
true, }
None, .build()
None,
None,
Some("2000-01-01".to_string()),
None,
None,
None,
)
.unwrap(); .unwrap();
bundle.instance.register_account(&reg).await.unwrap(); bundle.instance.register_account(&reg).await.unwrap();
common::teardown(bundle).await; common::teardown(bundle).await;

View File

@ -21,7 +21,7 @@ async fn get_channel() {
async fn delete_channel() { async fn delete_channel() {
let mut bundle = common::setup().await; let mut bundle = common::setup().await;
let result = bundle.channel.clone().delete(&mut bundle.user).await; let result = bundle.channel.clone().delete(&mut bundle.user).await;
assert!(result.is_none()); assert!(result.is_ok());
common::teardown(bundle).await common::teardown(bundle).await
} }
@ -72,14 +72,16 @@ async fn modify_channel() {
bundle.channel.id.to_string().as_str(), bundle.channel.id.to_string().as_str(),
permission_override.clone(), permission_override.clone(),
) )
.await; .await
.unwrap();
Channel::delete_permission( Channel::delete_permission(
&mut bundle.user, &mut bundle.user,
bundle.channel.id.to_string().as_str(), bundle.channel.id.to_string().as_str(),
&permission_override.id, &permission_override.id,
) )
.await; .await
.unwrap();
common::teardown(bundle).await common::teardown(bundle).await
} }

View File

@ -2,14 +2,14 @@ use chorus::{
instance::{Instance, UserMeta}, instance::{Instance, UserMeta},
types::{ types::{
Channel, ChannelCreateSchema, Guild, GuildCreateSchema, RegisterSchema, Channel, ChannelCreateSchema, Guild, GuildCreateSchema, RegisterSchema,
RoleCreateModifySchema, RoleObject, RegisterSchemaOptions, RoleCreateModifySchema, RoleObject,
}, },
URLBundle, UrlBundle,
}; };
#[derive(Debug)] #[derive(Debug)]
pub struct TestBundle { pub struct TestBundle {
pub urls: URLBundle, pub urls: UrlBundle,
pub user: UserMeta, pub user: UserMeta,
pub instance: Instance, pub instance: Instance,
pub guild: Guild, pub guild: Guild,
@ -19,25 +19,18 @@ pub struct TestBundle {
// 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 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()).await.unwrap();
// Requires the existance of the below user. // Requires the existance of the below user.
let reg = RegisterSchema::new( let reg = RegisterSchemaOptions {
"integrationtestuser".to_string(), date_of_birth: Some("2000-01-01".to_string()),
None, ..RegisterSchema::builder("integrationtestuser", true)
true, }
None, .build()
None,
None,
Some("2000-01-01".to_string()),
None,
None,
None,
)
.unwrap(); .unwrap();
let guild_create_schema = GuildCreateSchema { let guild_create_schema = GuildCreateSchema {
name: Some("Test-Guild!".to_string()), name: Some("Test-Guild!".to_string()),
@ -100,6 +93,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)]
pub async fn teardown(mut bundle: TestBundle) { pub async fn teardown(mut bundle: TestBundle) {
Guild::delete(&mut bundle.user, &bundle.guild.id.to_string()).await; Guild::delete(&mut bundle.user, &bundle.guild.id.to_string()).await;
bundle.user.delete().await; bundle.user.delete().await;

View File

@ -20,10 +20,9 @@ async fn guild_creation_deletion() {
.await .await
.unwrap(); .unwrap();
match Guild::delete(&mut bundle.user, &guild.id.to_string()).await { assert!(Guild::delete(&mut bundle.user, &guild.id.to_string())
None => assert!(true), .await
Some(_) => assert!(false), .is_ok());
}
common::teardown(bundle).await common::teardown(bundle).await
} }

View File

@ -18,7 +18,7 @@ async fn add_remove_role() {
} }
} }
if !role_found { if !role_found {
assert!(false) panic!()
} }
chorus::types::GuildMember::remove_role(&mut bundle.user, guild_id, user_id, role_id).await; chorus::types::GuildMember::remove_role(&mut bundle.user, guild_id, user_id, role_id).await;
let member = chorus::types::GuildMember::get(&mut bundle.user, guild_id, user_id) let member = chorus::types::GuildMember::get(&mut bundle.user, guild_id, user_id)
@ -28,11 +28,11 @@ async fn add_remove_role() {
if role != role_id { if role != role_id {
role_found = false; role_found = false;
} else { } else {
assert!(false); panic!();
} }
} }
if role_found { if role_found {
assert!(false) panic!()
} }
common::teardown(bundle).await common::teardown(bundle).await
} }

View File

@ -8,18 +8,10 @@ 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::new( let mut message = types::MessageSendSchema {
None, content: Some("A Message!".to_string()),
Some("A Message!".to_string()), ..Default::default()
None, };
None,
None,
None,
None,
None,
None,
None,
);
let _ = bundle let _ = bundle
.user .user
.send_message(&mut message, bundle.channel.id.to_string(), None) .send_message(&mut message, bundle.channel.id.to_string(), None)
@ -53,18 +45,11 @@ async fn send_message_attachment() {
content: buffer, content: buffer,
}; };
let mut message = types::MessageSendSchema::new( let mut message = types::MessageSendSchema {
None, content: Some("trans rights now".to_string()),
Some("trans rights now".to_string()), attachments: Some(vec![attachment.clone()]),
None, ..Default::default()
None, };
None,
None,
None,
None,
None,
Some(vec![attachment.clone()]),
);
let vec_attach = vec![attachment.clone()]; let vec_attach = vec![attachment.clone()];
let _arg = Some(&vec_attach); let _arg = Some(&vec_attach);
@ -79,3 +64,20 @@ async fn send_message_attachment() {
.unwrap(); .unwrap();
common::teardown(bundle).await common::teardown(bundle).await
} }
#[tokio::test]
async fn read_messages() {
let mut bundle = common::setup().await;
// First create some messages to read
let mut message = types::MessageSendSchema {
content: Some("A Message!".to_string()),
..Default::default()
};
let _ = bundle
.user
.send_message(&mut message, bundle.channel.id.to_string(), None)
.await
.unwrap();
common::teardown(bundle).await
}

144
tests/relationships.rs Normal file
View File

@ -0,0 +1,144 @@
use chorus::types::{self, RegisterSchema, RegisterSchemaOptions, Relationship, RelationshipType};
mod common;
#[tokio::test]
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 belongs_to = &mut bundle.instance;
let user = &mut bundle.user;
let mut other_user = belongs_to.register_account(&register_schema).await.unwrap();
let friend_request_schema = types::FriendRequestSendSchema {
username: user.object.username.clone(),
discriminator: Some(user.object.discriminator.clone()),
};
other_user.send_friend_request(friend_request_schema).await;
let relationships = user
.get_mutual_relationships(&other_user.object.id.to_string())
.await
.unwrap();
println!("{:?}", relationships);
common::teardown(bundle).await
}
#[tokio::test]
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 belongs_to = &mut bundle.instance;
let user = &mut bundle.user;
let mut other_user = belongs_to.register_account(&register_schema).await.unwrap();
let friend_request_schema = types::FriendRequestSendSchema {
username: user.object.username.clone(),
discriminator: Some(user.object.discriminator.clone()),
};
other_user.send_friend_request(friend_request_schema).await;
let relationships = user.get_relationships().await.unwrap();
assert_eq!(relationships.get(0).unwrap().id, other_user.object.id);
common::teardown(bundle).await
}
#[tokio::test]
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 belongs_to = &mut bundle.instance;
let user = &mut bundle.user;
let mut other_user = belongs_to.register_account(&register_schema).await.unwrap();
other_user
.modify_user_relationship(
&user.object.id.to_string(),
types::RelationshipType::Friends,
)
.await;
let relationships = user.get_relationships().await.unwrap();
assert_eq!(relationships.get(0).unwrap().id, other_user.object.id);
assert_eq!(
relationships.get(0).unwrap().relationship_type,
RelationshipType::Incoming
);
let relationships = other_user.get_relationships().await.unwrap();
assert_eq!(relationships.get(0).unwrap().id, user.object.id);
assert_eq!(
relationships.get(0).unwrap().relationship_type,
RelationshipType::Outgoing
);
user.modify_user_relationship(
other_user.object.id.to_string().as_str(),
RelationshipType::Friends,
)
.await;
assert_eq!(
other_user
.get_relationships()
.await
.unwrap()
.get(0)
.unwrap()
.relationship_type,
RelationshipType::Friends
);
user.remove_relationship(other_user.object.id.to_string().as_str())
.await;
assert_eq!(
other_user.get_relationships().await.unwrap(),
Vec::<Relationship>::new()
);
common::teardown(bundle).await
}
#[tokio::test]
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 belongs_to = &mut bundle.instance;
let user = &mut bundle.user;
let mut other_user = belongs_to.register_account(&register_schema).await.unwrap();
other_user
.modify_user_relationship(
&user.object.id.to_string(),
types::RelationshipType::Blocked,
)
.await;
let relationships = user.get_relationships().await.unwrap();
assert_eq!(relationships, Vec::<Relationship>::new());
let relationships = other_user.get_relationships().await.unwrap();
assert_eq!(relationships.get(0).unwrap().id, user.object.id);
assert_eq!(
relationships.get(0).unwrap().relationship_type,
RelationshipType::Blocked
);
other_user
.remove_relationship(user.object.id.to_string().as_str())
.await;
assert_eq!(
other_user.get_relationships().await.unwrap(),
Vec::<Relationship>::new()
);
common::teardown(bundle).await
}

View File

@ -25,10 +25,7 @@ async fn create_and_get_roles() {
let expected = types::RoleObject::get_all(&mut bundle.user, &guild_id) let expected = types::RoleObject::get_all(&mut bundle.user, &guild_id)
.await .await
.unwrap() .unwrap()
.unwrap() .unwrap()[2]
.iter()
.nth(2)
.unwrap()
.clone(); .clone();
assert_eq!(role, expected); assert_eq!(role, expected);