Merge branch 'main' into perpetual/gateway-dev
This commit is contained in:
commit
7f726deb19
|
@ -1,4 +1,4 @@
|
|||
name: Rust
|
||||
name: Build and Test
|
||||
|
||||
on:
|
||||
push:
|
||||
|
@ -10,7 +10,7 @@ env:
|
|||
CARGO_TERM_COLOR: always
|
||||
|
||||
jobs:
|
||||
build_and_test:
|
||||
rust:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
|
@ -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
|
|
@ -29,9 +29,6 @@ openssl = "0.10.52"
|
|||
base64 = "0.21.2"
|
||||
hostname = "0.3.1"
|
||||
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"
|
||||
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 }
|
||||
|
|
29
README.md
29
README.md
|
@ -5,6 +5,7 @@
|
|||
[![Contributors][contributors-shield]][contributors-url]
|
||||
[![Forks][forks-shield]][forks-url]
|
||||
[![Issues][issues-shield]][issues-url]
|
||||
<img src="https://img.shields.io/static/v1?label=Status&message=Early%20Development&color=blue">
|
||||
|
||||
</br>
|
||||
<div align="center">
|
||||
|
@ -30,7 +31,19 @@
|
|||
|
||||
</div>
|
||||
|
||||
## Progress Tracker/Roadmap:
|
||||
## About
|
||||
|
||||
Chorus is a Rust library that allows developers to interact with multiple Spacebar-compatible APIs and Gateways simultaneously. The library provides a simple and efficient way to communicate with these services, making it easier for developers to build applications that rely on them. Chorus is open-source and welcomes contributions from the community.
|
||||
|
||||
## Contributing
|
||||
|
||||
If you would like to contribute, please feel free to open an Issue with the idea you have, or a
|
||||
Pull Request. Please keep our [contribution guidelines](https://github.com/polyphony-chat/.github/blob/main/CONTRIBUTION_GUIDELINES.md) in mind. Your contribution might not be
|
||||
accepted, if it violates these guidelines or [our Code of Conduct](https://github.com/polyphony-chat/.github/blob/main/CODE_OF_CONDUCT.md).
|
||||
|
||||
<details>
|
||||
<summary>Progress Tracker/Roadmap</summary>
|
||||
|
||||
### 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)
|
||||
|
@ -58,8 +71,8 @@
|
|||
### User Management
|
||||
- [ ] [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)
|
||||
- [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
|
||||
|
@ -93,9 +106,6 @@
|
|||
- [x] Sending rich content in messages (links, images, videos)
|
||||
- [ ] Customizing embed appearance (title, description, color, fields)
|
||||
|
||||
### Notifications and Push Notifications
|
||||
- [ ] Notification settings management
|
||||
|
||||
### Webhooks
|
||||
- [ ] Webhook creation and management
|
||||
- [ ] Handling incoming webhook events
|
||||
|
@ -107,8 +117,10 @@
|
|||
|
||||
[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/rust.yml?style=flat
|
||||
[build-url]: https://github.com/polyphony-chat/chorus/blob/main/.github/workflows/rust.yml
|
||||
[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
|
||||
|
@ -121,3 +133,4 @@
|
|||
[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>
|
|
@ -3,8 +3,8 @@ use chorus::{
|
|||
gateway::{Gateway, Observer},
|
||||
types::{GatewayIdentifyPayload, GatewayReady},
|
||||
};
|
||||
use std::sync::Arc;
|
||||
use tokio::{self, sync::Mutex};
|
||||
use std::{sync::Arc, time::Duration};
|
||||
use tokio::{self, time::sleep};
|
||||
|
||||
// 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
|
||||
impl Observer<GatewayReady> for ExampleObserver {
|
||||
// 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!");
|
||||
}
|
||||
}
|
||||
|
@ -33,8 +33,8 @@ async fn main() {
|
|||
// Create an instance of our observer
|
||||
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>
|
||||
let shared_observer = Arc::new(Mutex::new(observer));
|
||||
// Share ownership of the observer with the gateway
|
||||
let shared_observer = Arc::new(observer);
|
||||
|
||||
// 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
|
||||
|
@ -44,8 +44,7 @@ async fn main() {
|
|||
.await
|
||||
.session
|
||||
.ready
|
||||
.subscribe(shared_observer)
|
||||
.unwrap();
|
||||
.subscribe(shared_observer);
|
||||
|
||||
// Authenticate so we will receive any events
|
||||
let token = "SecretToken".to_string();
|
||||
|
@ -54,5 +53,7 @@ async fn main() {
|
|||
gateway.send_identify(identify).await;
|
||||
|
||||
// Do something on the main thread so we don't quit
|
||||
loop {}
|
||||
loop {
|
||||
sleep(Duration::MAX).await;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
use std::time::Duration;
|
||||
|
||||
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
|
||||
#[tokio::main]
|
||||
|
@ -26,5 +29,7 @@ async fn main() {
|
|||
gateway.send_identify(identify).await;
|
||||
|
||||
// Do something on the main thread so we don't quit
|
||||
loop {}
|
||||
loop {
|
||||
sleep(Duration::MAX).await;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ impl Instance {
|
|||
) -> Result<UserMeta, ChorusLibError> {
|
||||
let json_schema = json!(login_schema);
|
||||
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());
|
||||
// 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
|
||||
|
|
|
@ -25,7 +25,7 @@ impl Instance {
|
|||
) -> Result<UserMeta, ChorusLibError> {
|
||||
let json_schema = json!(register_schema);
|
||||
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());
|
||||
// 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
|
||||
|
@ -59,11 +59,10 @@ impl Instance {
|
|||
return Err(ChorusLibError::InvalidFormBodyError { error_type, error });
|
||||
}
|
||||
let user_object = self.get_user(token.clone(), None).await.unwrap();
|
||||
let settings =
|
||||
UserMeta::get_settings(&token, &self.urls.get_api().to_string(), &mut self.limits)
|
||||
let settings = UserMeta::get_settings(&token, &self.urls.api, &mut self.limits)
|
||||
.await
|
||||
.unwrap();
|
||||
let user: UserMeta = UserMeta::new(
|
||||
let user = UserMeta::new(
|
||||
Rc::new(RefCell::new(self.clone())),
|
||||
token.clone(),
|
||||
cloned_limits,
|
||||
|
|
|
@ -10,9 +10,7 @@ use crate::{
|
|||
|
||||
impl Channel {
|
||||
pub async fn get(user: &mut UserMeta, channel_id: &str) -> Result<Channel, ChorusLibError> {
|
||||
let belongs_to = user.belongs_to.borrow_mut();
|
||||
let url = belongs_to.urls.get_api().to_string();
|
||||
drop(belongs_to);
|
||||
let url = user.belongs_to.borrow_mut().urls.api.clone();
|
||||
let request = Client::new()
|
||||
.get(format!("{}/channels/{}/", url, channel_id))
|
||||
.bearer_auth(user.token());
|
||||
|
@ -44,24 +42,19 @@ impl Channel {
|
|||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// An `Option` that contains an `ChorusLibError` if an error occurred during the request, or `None` if the request was successful.
|
||||
pub async fn delete(self, user: &mut UserMeta) -> Option<ChorusLibError> {
|
||||
let belongs_to = user.belongs_to.borrow_mut();
|
||||
/// 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) -> Result<(), ChorusLibError> {
|
||||
let request = Client::new()
|
||||
.delete(format!(
|
||||
"{}/channels/{}/",
|
||||
belongs_to.urls.get_api(),
|
||||
self.id.to_string()
|
||||
user.belongs_to.borrow_mut().urls.api,
|
||||
self.id
|
||||
))
|
||||
.bearer_auth(user.token());
|
||||
drop(belongs_to);
|
||||
let response =
|
||||
common::handle_request(request, user, crate::api::limits::LimitType::Channel).await;
|
||||
if response.is_err() {
|
||||
return Some(response.err().unwrap());
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
common::handle_request_as_result(request, user, crate::api::limits::LimitType::Channel)
|
||||
.await;
|
||||
response
|
||||
}
|
||||
|
||||
/// Modifies a channel.
|
||||
|
@ -83,16 +76,14 @@ impl Channel {
|
|||
channel_id: &str,
|
||||
user: &mut UserMeta,
|
||||
) -> Result<Channel, ChorusLibError> {
|
||||
let belongs_to = user.belongs_to.borrow();
|
||||
let request = Client::new()
|
||||
.patch(format!(
|
||||
"{}/channels/{}/",
|
||||
belongs_to.urls.get_api(),
|
||||
user.belongs_to.borrow().urls.api,
|
||||
channel_id
|
||||
))
|
||||
.bearer_auth(user.token())
|
||||
.body(to_string(&modify_data).unwrap());
|
||||
drop(belongs_to);
|
||||
let channel = common::deserialize_response::<Channel>(
|
||||
request,
|
||||
user,
|
||||
|
|
|
@ -25,9 +25,7 @@ impl Message {
|
|||
message: &mut MessageSendSchema,
|
||||
files: Option<Vec<PartialDiscordFileAttachment>>,
|
||||
) -> Result<Message, crate::errors::ChorusLibError> {
|
||||
let belongs_to = user.belongs_to.borrow();
|
||||
let url_api = belongs_to.urls.get_api().to_string();
|
||||
drop(belongs_to);
|
||||
let url_api = user.belongs_to.borrow().urls.api.clone();
|
||||
|
||||
if files.is_none() {
|
||||
let request = Client::new()
|
||||
|
|
|
@ -2,7 +2,7 @@ use reqwest::Client;
|
|||
use serde_json::to_string;
|
||||
|
||||
use crate::{
|
||||
api::handle_request,
|
||||
api::{handle_request, handle_request_as_result},
|
||||
errors::ChorusLibError,
|
||||
instance::UserMeta,
|
||||
types::{self, PermissionOverwrite},
|
||||
|
@ -19,35 +19,30 @@ impl types::Channel {
|
|||
///
|
||||
/// # 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(
|
||||
user: &mut UserMeta,
|
||||
channel_id: &str,
|
||||
overwrite: PermissionOverwrite,
|
||||
) -> Option<ChorusLibError> {
|
||||
let belongs_to = user.belongs_to.borrow_mut();
|
||||
let url = format!(
|
||||
) -> Result<(), ChorusLibError> {
|
||||
let url = {
|
||||
format!(
|
||||
"{}/channels/{}/permissions/{}",
|
||||
belongs_to.urls.get_api(),
|
||||
user.belongs_to.borrow_mut().urls.api,
|
||||
channel_id,
|
||||
overwrite.id
|
||||
);
|
||||
drop(belongs_to);
|
||||
)
|
||||
};
|
||||
let body = match to_string(&overwrite) {
|
||||
Ok(string) => string,
|
||||
Err(e) => {
|
||||
return Some(ChorusLibError::FormCreationError {
|
||||
return Err(ChorusLibError::FormCreationError {
|
||||
error: e.to_string(),
|
||||
});
|
||||
}
|
||||
};
|
||||
let request = Client::new().put(url).bearer_auth(user.token()).body(body);
|
||||
match handle_request(request, user, crate::api::limits::LimitType::Channel).await {
|
||||
Ok(_) => None,
|
||||
Err(e) => Some(ChorusLibError::InvalidResponseError {
|
||||
error: e.to_string(),
|
||||
}),
|
||||
}
|
||||
handle_request_as_result(request, user, crate::api::limits::LimitType::Channel).await
|
||||
}
|
||||
|
||||
/// Deletes a permission overwrite for a channel.
|
||||
|
@ -60,26 +55,19 @@ impl types::Channel {
|
|||
///
|
||||
/// # 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(
|
||||
user: &mut UserMeta,
|
||||
channel_id: &str,
|
||||
overwrite_id: &str,
|
||||
) -> Option<ChorusLibError> {
|
||||
let belongs_to = user.belongs_to.borrow_mut();
|
||||
) -> Result<(), ChorusLibError> {
|
||||
let url = format!(
|
||||
"{}/channels/{}/permissions/{}",
|
||||
belongs_to.urls.get_api(),
|
||||
user.belongs_to.borrow_mut().urls.api,
|
||||
channel_id,
|
||||
overwrite_id
|
||||
);
|
||||
drop(belongs_to);
|
||||
let request = Client::new().delete(url).bearer_auth(user.token());
|
||||
match handle_request(request, user, crate::api::limits::LimitType::Channel).await {
|
||||
Ok(_) => None,
|
||||
Err(e) => Some(ChorusLibError::InvalidResponseError {
|
||||
error: e.to_string(),
|
||||
}),
|
||||
}
|
||||
handle_request_as_result(request, user, crate::api::limits::LimitType::Channel).await
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use reqwest::Client;
|
||||
|
||||
use crate::{
|
||||
api::{handle_request, handle_request_as_option},
|
||||
api::{handle_request, handle_request_as_result},
|
||||
errors::ChorusLibError,
|
||||
instance::UserMeta,
|
||||
types,
|
||||
|
@ -24,28 +24,21 @@ impl ReactionMeta {
|
|||
* `user` - A mutable reference to a [`UserMeta`] instance.
|
||||
|
||||
# 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.
|
||||
|
||||
# Reference
|
||||
See [https://discord.com/developers/docs/resources/channel#delete-all-reactions](https://discord.com/developers/docs/resources/channel#delete-all-reactions)
|
||||
*/
|
||||
pub async fn delete_all(&self, user: &mut UserMeta) -> Option<ChorusLibError> {
|
||||
let belongs_to = user.belongs_to.borrow();
|
||||
pub async fn delete_all(&self, user: &mut UserMeta) -> Result<(), ChorusLibError> {
|
||||
let url = format!(
|
||||
"{}/channels/{}/messages/{}/reactions/",
|
||||
belongs_to.urls.get_api(),
|
||||
user.belongs_to.borrow().urls.api,
|
||||
self.channel_id,
|
||||
self.message_id
|
||||
);
|
||||
drop(belongs_to);
|
||||
let request = Client::new().delete(url).bearer_auth(user.token());
|
||||
match handle_request(request, user, crate::api::limits::LimitType::Channel).await {
|
||||
Ok(_) => None,
|
||||
Err(e) => Some(ChorusLibError::InvalidResponseError {
|
||||
error: e.to_string(),
|
||||
}),
|
||||
}
|
||||
handle_request_as_result(request, user, crate::api::limits::LimitType::Channel).await
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -58,28 +51,21 @@ impl ReactionMeta {
|
|||
* `user` - A mutable reference to a [`UserMeta`] instance.
|
||||
|
||||
# Returns
|
||||
A [`crate::errors::ChorusLibError`] if something went wrong.
|
||||
A Result that is [`Err(crate::errors::ChorusLibError)`] if something went wrong.
|
||||
|
||||
# Reference
|
||||
See [https://discord.com/developers/docs/resources/channel#get-reactions](https://discord.com/developers/docs/resources/channel#get-reactions)
|
||||
*/
|
||||
pub async fn get(&self, emoji: &str, user: &mut UserMeta) -> Option<ChorusLibError> {
|
||||
let belongs_to = user.belongs_to.borrow();
|
||||
pub async fn get(&self, emoji: &str, user: &mut UserMeta) -> Result<(), ChorusLibError> {
|
||||
let url = format!(
|
||||
"{}/channels/{}/messages/{}/reactions/{}/",
|
||||
belongs_to.urls.get_api(),
|
||||
user.belongs_to.borrow().urls.api,
|
||||
self.channel_id,
|
||||
self.message_id,
|
||||
emoji
|
||||
);
|
||||
drop(belongs_to);
|
||||
let request = Client::new().get(url).bearer_auth(user.token());
|
||||
match handle_request(request, user, crate::api::limits::LimitType::Channel).await {
|
||||
Ok(_) => None,
|
||||
Err(e) => Some(ChorusLibError::InvalidResponseError {
|
||||
error: e.to_string(),
|
||||
}),
|
||||
}
|
||||
handle_request_as_result(request, user, crate::api::limits::LimitType::Channel).await
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -93,29 +79,26 @@ impl ReactionMeta {
|
|||
* `user` - A mutable reference to a [`UserMeta`] instance.
|
||||
|
||||
# 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.
|
||||
|
||||
# Reference
|
||||
See [https://discord.com/developers/docs/resources/channel#delete-all-reactions-for-emoji](https://discord.com/developers/docs/resources/channel#delete-all-reactions-for-emoji)
|
||||
*/
|
||||
pub async fn delete_emoji(&self, emoji: &str, user: &mut UserMeta) -> Option<ChorusLibError> {
|
||||
let belongs_to = user.belongs_to.borrow();
|
||||
pub async fn delete_emoji(
|
||||
&self,
|
||||
emoji: &str,
|
||||
user: &mut UserMeta,
|
||||
) -> Result<(), ChorusLibError> {
|
||||
let url = format!(
|
||||
"{}/channels/{}/messages/{}/reactions/{}/",
|
||||
belongs_to.urls.get_api(),
|
||||
user.belongs_to.borrow().urls.api,
|
||||
self.channel_id,
|
||||
self.message_id,
|
||||
emoji
|
||||
);
|
||||
drop(belongs_to);
|
||||
let request = Client::new().delete(url).bearer_auth(user.token());
|
||||
match handle_request(request, user, crate::api::limits::LimitType::Channel).await {
|
||||
Ok(_) => None,
|
||||
Err(e) => Some(ChorusLibError::InvalidResponseError {
|
||||
error: e.to_string(),
|
||||
}),
|
||||
}
|
||||
handle_request_as_result(request, user, crate::api::limits::LimitType::Channel).await
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -132,25 +115,21 @@ impl ReactionMeta {
|
|||
* `user` - A mutable reference to a [`UserMeta`] instance.
|
||||
|
||||
# Returns
|
||||
A `Result` containing a [`reqwest::Response`] or a [`crate::errors::ChorusLibError`].
|
||||
Returns a 204 empty response on success.
|
||||
Fires a Message Reaction Add Gateway event.
|
||||
A `Result` containing [`()`] or a [`crate::errors::ChorusLibError`].
|
||||
|
||||
# Reference
|
||||
See [https://discord.com/developers/docs/resources/channel#create-reaction](https://discord.com/developers/docs/resources/channel#create-reaction)
|
||||
*/
|
||||
pub async fn create(&self, emoji: &str, user: &mut UserMeta) -> Option<ChorusLibError> {
|
||||
let belongs_to = user.belongs_to.borrow();
|
||||
pub async fn create(&self, emoji: &str, user: &mut UserMeta) -> Result<(), ChorusLibError> {
|
||||
let url = format!(
|
||||
"{}/channels/{}/messages/{}/reactions/{}/@me/",
|
||||
belongs_to.urls.get_api(),
|
||||
user.belongs_to.borrow().urls.api,
|
||||
self.channel_id,
|
||||
self.message_id,
|
||||
emoji
|
||||
);
|
||||
drop(belongs_to);
|
||||
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.
|
||||
|
||||
# Returns
|
||||
A `Result` containing a [`reqwest::Response`] or a [`crate::errors::ChorusLibError`].
|
||||
Returns a 204 empty response on success.
|
||||
A `Result` containing [`()`] or a [`crate::errors::ChorusLibError`].
|
||||
Fires a `Message Reaction Remove` Gateway event.
|
||||
|
||||
# Reference
|
||||
See [https://discord.com/developers/docs/resources/channel#delete-own-reaction](https://discord.com/developers/docs/resources/channel#delete-own-reaction)
|
||||
*/
|
||||
pub async fn remove(&self, emoji: &str, user: &mut UserMeta) -> Option<ChorusLibError> {
|
||||
let belongs_to = user.belongs_to.borrow();
|
||||
pub async fn remove(&self, emoji: &str, user: &mut UserMeta) -> Result<(), ChorusLibError> {
|
||||
let url = format!(
|
||||
"{}/channels/{}/messages/{}/reactions/{}/@me/",
|
||||
belongs_to.urls.get_api(),
|
||||
user.belongs_to.borrow().urls.api,
|
||||
self.channel_id,
|
||||
self.message_id,
|
||||
emoji
|
||||
);
|
||||
drop(belongs_to);
|
||||
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.
|
||||
|
||||
# Returns
|
||||
A `Result` containing a [`reqwest::Response`] or a [`crate::errors::ChorusLibError`].
|
||||
Returns a 204 empty response on success.
|
||||
A `Result` containing [`()`] or a [`crate::errors::ChorusLibError`].
|
||||
Fires a Message Reaction Remove Gateway event.
|
||||
|
||||
# Reference
|
||||
|
@ -209,18 +184,16 @@ impl ReactionMeta {
|
|||
user_id: &str,
|
||||
emoji: &str,
|
||||
user: &mut UserMeta,
|
||||
) -> Option<ChorusLibError> {
|
||||
let belongs_to = user.belongs_to.borrow();
|
||||
) -> Result<(), ChorusLibError> {
|
||||
let url = format!(
|
||||
"{}/channels/{}/messages/{}/reactions/{}/{}",
|
||||
belongs_to.urls.get_api(),
|
||||
user.belongs_to.borrow().urls.api,
|
||||
self.channel_id,
|
||||
self.message_id,
|
||||
emoji,
|
||||
user_id
|
||||
);
|
||||
drop(belongs_to);
|
||||
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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,30 +12,25 @@ pub async fn handle_request(
|
|||
user: &mut UserMeta,
|
||||
limit_type: LimitType,
|
||||
) -> Result<reqwest::Response, crate::errors::ChorusLibError> {
|
||||
let mut belongs_to = user.belongs_to.borrow_mut();
|
||||
match LimitedRequester::send_request(
|
||||
LimitedRequester::send_request(
|
||||
request,
|
||||
limit_type,
|
||||
&mut belongs_to.limits,
|
||||
&mut user.belongs_to.borrow_mut().limits,
|
||||
&mut user.limits,
|
||||
)
|
||||
.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
|
||||
/// [`Some(ChorusLibError)`] on failure.
|
||||
pub async fn handle_request_as_option(
|
||||
/// Sends a request to wherever it needs to go. Returns [`Ok(())`] on success and
|
||||
/// [`Err(ChorusLibError)`] on failure.
|
||||
pub async fn handle_request_as_result(
|
||||
request: RequestBuilder,
|
||||
user: &mut UserMeta,
|
||||
limit_type: LimitType,
|
||||
) -> Option<ChorusLibError> {
|
||||
) -> Result<(), ChorusLibError> {
|
||||
match handle_request(request, user, limit_type).await {
|
||||
Ok(_) => None,
|
||||
Err(e) => Some(ChorusLibError::InvalidResponseError {
|
||||
Ok(_) => Ok(()),
|
||||
Err(e) => Err(ChorusLibError::InvalidResponseError {
|
||||
error: e.to_string(),
|
||||
}),
|
||||
}
|
||||
|
@ -53,7 +48,7 @@ pub async fn deserialize_response<T: for<'a> Deserialize<'a>>(
|
|||
return Err(ChorusLibError::InvalidResponseError {
|
||||
error: format!(
|
||||
"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 {
|
||||
error: format!(
|
||||
"Error while trying to deserialize the JSON response into T: {}",
|
||||
e.to_string()
|
||||
e
|
||||
),
|
||||
})
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@ use serde_json::to_string;
|
|||
|
||||
use crate::api::deserialize_response;
|
||||
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::errors::ChorusLibError;
|
||||
use crate::instance::UserMeta;
|
||||
|
@ -32,9 +32,7 @@ impl Guild {
|
|||
user: &mut UserMeta,
|
||||
guild_create_schema: GuildCreateSchema,
|
||||
) -> Result<Guild, ChorusLibError> {
|
||||
let belongs_to = user.belongs_to.borrow();
|
||||
let url = format!("{}/guilds/", belongs_to.urls.get_api());
|
||||
drop(belongs_to);
|
||||
let url = format!("{}/guilds/", user.belongs_to.borrow().urls.api);
|
||||
let request = reqwest::Client::new()
|
||||
.post(url.clone())
|
||||
.bearer_auth(user.token.clone())
|
||||
|
@ -52,7 +50,7 @@ impl Guild {
|
|||
///
|
||||
/// # 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
|
||||
///
|
||||
|
@ -66,14 +64,16 @@ impl Guild {
|
|||
/// None => println!("Guild deleted successfully"),
|
||||
/// }
|
||||
/// ```
|
||||
pub async fn delete(user: &mut UserMeta, guild_id: &str) -> Option<ChorusLibError> {
|
||||
let belongs_to = user.belongs_to.borrow();
|
||||
let url = format!("{}/guilds/{}/delete/", belongs_to.urls.get_api(), guild_id);
|
||||
drop(belongs_to);
|
||||
pub async fn delete(user: &mut UserMeta, guild_id: &str) -> Result<(), ChorusLibError> {
|
||||
let url = format!(
|
||||
"{}/guilds/{}/delete/",
|
||||
user.belongs_to.borrow().urls.api,
|
||||
guild_id
|
||||
);
|
||||
let request = reqwest::Client::new()
|
||||
.post(url.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.
|
||||
|
@ -97,7 +97,7 @@ impl Guild {
|
|||
let mut belongs_to = user.belongs_to.borrow_mut();
|
||||
Channel::_create(
|
||||
&user.token,
|
||||
&format!("{}", belongs_to.urls.get_api()),
|
||||
&format!("{}", belongs_to.urls.api),
|
||||
&self.id.to_string(),
|
||||
schema,
|
||||
&mut user.limits,
|
||||
|
@ -116,15 +116,13 @@ impl Guild {
|
|||
/// * `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> {
|
||||
let belongs_to = user.belongs_to.borrow();
|
||||
let request = Client::new()
|
||||
.get(format!(
|
||||
"{}/guilds/{}/channels/",
|
||||
belongs_to.urls.get_api(),
|
||||
self.id.to_string()
|
||||
user.belongs_to.borrow().urls.api,
|
||||
self.id
|
||||
))
|
||||
.bearer_auth(user.token());
|
||||
drop(belongs_to);
|
||||
let result = handle_request(request, user, crate::api::limits::LimitType::Channel)
|
||||
.await
|
||||
.unwrap();
|
||||
|
@ -159,7 +157,7 @@ impl Guild {
|
|||
pub async fn get(user: &mut UserMeta, guild_id: &str) -> Result<Guild, ChorusLibError> {
|
||||
let mut belongs_to = user.belongs_to.borrow_mut();
|
||||
Guild::_get(
|
||||
&format!("{}", belongs_to.urls.get_api()),
|
||||
&format!("{}", belongs_to.urls.api),
|
||||
guild_id,
|
||||
&user.token,
|
||||
&mut user.limits,
|
||||
|
@ -219,7 +217,7 @@ impl Channel {
|
|||
let mut belongs_to = user.belongs_to.borrow_mut();
|
||||
Channel::_create(
|
||||
&user.token,
|
||||
&format!("{}", belongs_to.urls.get_api()),
|
||||
&format!("{}", belongs_to.urls.api),
|
||||
guild_id,
|
||||
schema,
|
||||
&mut user.limits,
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use reqwest::Client;
|
||||
|
||||
use crate::{
|
||||
api::{deserialize_response, handle_request_as_option},
|
||||
api::{deserialize_response, handle_request_as_result},
|
||||
errors::ChorusLibError,
|
||||
instance::UserMeta,
|
||||
types,
|
||||
|
@ -24,14 +24,12 @@ impl types::GuildMember {
|
|||
guild_id: &str,
|
||||
member_id: &str,
|
||||
) -> Result<types::GuildMember, ChorusLibError> {
|
||||
let belongs_to = user.belongs_to.borrow();
|
||||
let url = format!(
|
||||
"{}/guilds/{}/members/{}/",
|
||||
belongs_to.urls.get_api(),
|
||||
user.belongs_to.borrow().urls.api,
|
||||
guild_id,
|
||||
member_id
|
||||
);
|
||||
drop(belongs_to);
|
||||
let request = Client::new().get(url).bearer_auth(user.token());
|
||||
deserialize_response::<types::GuildMember>(
|
||||
request,
|
||||
|
@ -52,24 +50,22 @@ impl types::GuildMember {
|
|||
///
|
||||
/// # 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(
|
||||
user: &mut UserMeta,
|
||||
guild_id: &str,
|
||||
member_id: &str,
|
||||
role_id: &str,
|
||||
) -> Option<ChorusLibError> {
|
||||
let belongs_to = user.belongs_to.borrow();
|
||||
) -> Result<(), ChorusLibError> {
|
||||
let url = format!(
|
||||
"{}/guilds/{}/members/{}/roles/{}/",
|
||||
belongs_to.urls.get_api(),
|
||||
user.belongs_to.borrow().urls.api,
|
||||
guild_id,
|
||||
member_id,
|
||||
role_id
|
||||
);
|
||||
drop(belongs_to);
|
||||
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.
|
||||
|
@ -83,23 +79,21 @@ impl types::GuildMember {
|
|||
///
|
||||
/// # 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(
|
||||
user: &mut UserMeta,
|
||||
guild_id: &str,
|
||||
member_id: &str,
|
||||
role_id: &str,
|
||||
) -> Option<crate::errors::ChorusLibError> {
|
||||
let belongs_to = user.belongs_to.borrow();
|
||||
) -> Result<(), crate::errors::ChorusLibError> {
|
||||
let url = format!(
|
||||
"{}/guilds/{}/members/{}/roles/{}/",
|
||||
belongs_to.urls.get_api(),
|
||||
user.belongs_to.borrow().urls.api,
|
||||
guild_id,
|
||||
member_id,
|
||||
role_id
|
||||
);
|
||||
drop(belongs_to);
|
||||
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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,9 +27,11 @@ impl types::RoleObject {
|
|||
user: &mut UserMeta,
|
||||
guild_id: &str,
|
||||
) -> Result<Option<Vec<RoleObject>>, ChorusLibError> {
|
||||
let belongs_to = user.belongs_to.borrow();
|
||||
let url = format!("{}/guilds/{}/roles/", belongs_to.urls.get_api(), guild_id);
|
||||
drop(belongs_to);
|
||||
let url = format!(
|
||||
"{}/guilds/{}/roles/",
|
||||
user.belongs_to.borrow().urls.api,
|
||||
guild_id
|
||||
);
|
||||
let request = Client::new().get(url).bearer_auth(user.token());
|
||||
let roles = deserialize_response::<Vec<RoleObject>>(
|
||||
request,
|
||||
|
@ -64,14 +66,12 @@ impl types::RoleObject {
|
|||
guild_id: &str,
|
||||
role_id: &str,
|
||||
) -> Result<RoleObject, ChorusLibError> {
|
||||
let belongs_to = user.belongs_to.borrow();
|
||||
let url = format!(
|
||||
"{}/guilds/{}/roles/{}/",
|
||||
belongs_to.urls.get_api(),
|
||||
user.belongs_to.borrow().urls.api,
|
||||
guild_id,
|
||||
role_id
|
||||
);
|
||||
drop(belongs_to);
|
||||
let request = Client::new().get(url).bearer_auth(user.token());
|
||||
deserialize_response(request, user, crate::api::limits::LimitType::Guild).await
|
||||
}
|
||||
|
@ -96,17 +96,16 @@ impl types::RoleObject {
|
|||
guild_id: &str,
|
||||
role_create_schema: RoleCreateModifySchema,
|
||||
) -> Result<RoleObject, ChorusLibError> {
|
||||
let belongs_to = user.belongs_to.borrow();
|
||||
let url = format!("{}/guilds/{}/roles/", belongs_to.urls.get_api(), guild_id);
|
||||
drop(belongs_to);
|
||||
let body = match to_string::<RoleCreateModifySchema>(&role_create_schema) {
|
||||
Ok(string) => string,
|
||||
Err(e) => {
|
||||
return Err(ChorusLibError::FormCreationError {
|
||||
let url = format!(
|
||||
"{}/guilds/{}/roles/",
|
||||
user.belongs_to.borrow().urls.api,
|
||||
guild_id
|
||||
);
|
||||
let body = to_string::<RoleCreateModifySchema>(&role_create_schema).map_err(|e| {
|
||||
ChorusLibError::FormCreationError {
|
||||
error: e.to_string(),
|
||||
});
|
||||
}
|
||||
};
|
||||
})?;
|
||||
let request = Client::new().post(url).bearer_auth(user.token()).body(body);
|
||||
deserialize_response(request, user, crate::api::limits::LimitType::Guild).await
|
||||
}
|
||||
|
@ -131,17 +130,16 @@ impl types::RoleObject {
|
|||
guild_id: &str,
|
||||
role_position_update_schema: types::RolePositionUpdateSchema,
|
||||
) -> Result<RoleObject, ChorusLibError> {
|
||||
let belongs_to = user.belongs_to.borrow();
|
||||
let url = format!("{}/guilds/{}/roles/", belongs_to.urls.get_api(), guild_id);
|
||||
let body = match to_string(&role_position_update_schema) {
|
||||
Ok(body) => body,
|
||||
Err(e) => {
|
||||
return Err(ChorusLibError::FormCreationError {
|
||||
let url = format!(
|
||||
"{}/guilds/{}/roles/",
|
||||
user.belongs_to.borrow().urls.api,
|
||||
guild_id
|
||||
);
|
||||
let body = to_string(&role_position_update_schema).map_err(|e| {
|
||||
ChorusLibError::FormCreationError {
|
||||
error: e.to_string(),
|
||||
});
|
||||
}
|
||||
};
|
||||
drop(belongs_to);
|
||||
})?;
|
||||
let request = Client::new()
|
||||
.patch(url)
|
||||
.bearer_auth(user.token())
|
||||
|
@ -172,22 +170,17 @@ impl types::RoleObject {
|
|||
role_id: &str,
|
||||
role_create_schema: RoleCreateModifySchema,
|
||||
) -> Result<RoleObject, ChorusLibError> {
|
||||
let belongs_to = user.belongs_to.borrow();
|
||||
let url = format!(
|
||||
"{}/guilds/{}/roles/{}",
|
||||
belongs_to.urls.get_api(),
|
||||
user.belongs_to.borrow().urls.api,
|
||||
guild_id,
|
||||
role_id
|
||||
);
|
||||
drop(belongs_to);
|
||||
let body = match to_string::<RoleCreateModifySchema>(&role_create_schema) {
|
||||
Ok(string) => string,
|
||||
Err(e) => {
|
||||
return Err(ChorusLibError::FormCreationError {
|
||||
let body = to_string::<RoleCreateModifySchema>(&role_create_schema).map_err(|e| {
|
||||
ChorusLibError::FormCreationError {
|
||||
error: e.to_string(),
|
||||
});
|
||||
}
|
||||
};
|
||||
})?;
|
||||
let request = Client::new()
|
||||
.patch(url)
|
||||
.bearer_auth(user.token())
|
||||
|
|
|
@ -15,7 +15,7 @@ impl Instance {
|
|||
&self,
|
||||
) -> Result<GeneralConfiguration, ChorusLibError> {
|
||||
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 {
|
||||
Ok(result) => result,
|
||||
Err(e) => {
|
||||
|
@ -33,7 +33,6 @@ impl Instance {
|
|||
}
|
||||
|
||||
let body = request.text().await.unwrap();
|
||||
let instance_policies_schema: GeneralConfiguration = from_str(&body).unwrap();
|
||||
Ok(instance_policies_schema)
|
||||
Ok(from_str::<GeneralConfiguration>(&body).unwrap())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -195,31 +195,31 @@ pub mod limits {
|
|||
|
||||
pub fn get_limit_ref(&self, limit_type: &LimitType) -> &Limit {
|
||||
match limit_type {
|
||||
&LimitType::AbsoluteMessage => self.limit_absolute_messages,
|
||||
&LimitType::AbsoluteRegister => self.limit_absolute_register,
|
||||
&LimitType::AuthLogin => self.limit_auth_login,
|
||||
&LimitType::AuthRegister => self.limit_auth_register,
|
||||
&LimitType::Channel => self.limit_channel,
|
||||
&LimitType::Error => self.limit_error,
|
||||
&LimitType::Global => self.limit_global,
|
||||
&LimitType::Guild => self.limit_guild,
|
||||
&LimitType::Ip => self.limit_ip,
|
||||
&LimitType::Webhook => self.limit_webhook,
|
||||
LimitType::AbsoluteMessage => self.limit_absolute_messages,
|
||||
LimitType::AbsoluteRegister => self.limit_absolute_register,
|
||||
LimitType::AuthLogin => self.limit_auth_login,
|
||||
LimitType::AuthRegister => self.limit_auth_register,
|
||||
LimitType::Channel => self.limit_channel,
|
||||
LimitType::Error => self.limit_error,
|
||||
LimitType::Global => self.limit_global,
|
||||
LimitType::Guild => self.limit_guild,
|
||||
LimitType::Ip => self.limit_ip,
|
||||
LimitType::Webhook => self.limit_webhook,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_limit_mut_ref(&mut self, limit_type: &LimitType) -> &mut Limit {
|
||||
match limit_type {
|
||||
&LimitType::AbsoluteMessage => self.limit_absolute_messages,
|
||||
&LimitType::AbsoluteRegister => self.limit_absolute_register,
|
||||
&LimitType::AuthLogin => self.limit_auth_login,
|
||||
&LimitType::AuthRegister => self.limit_auth_register,
|
||||
&LimitType::Channel => self.limit_channel,
|
||||
&LimitType::Error => self.limit_error,
|
||||
&LimitType::Global => self.limit_global,
|
||||
&LimitType::Guild => self.limit_guild,
|
||||
&LimitType::Ip => self.limit_ip,
|
||||
&LimitType::Webhook => self.limit_webhook,
|
||||
LimitType::AbsoluteMessage => self.limit_absolute_messages,
|
||||
LimitType::AbsoluteRegister => self.limit_absolute_register,
|
||||
LimitType::AuthLogin => self.limit_auth_login,
|
||||
LimitType::AuthRegister => self.limit_auth_register,
|
||||
LimitType::Channel => self.limit_channel,
|
||||
LimitType::Error => self.limit_error,
|
||||
LimitType::Global => self.limit_global,
|
||||
LimitType::Guild => self.limit_guild,
|
||||
LimitType::Ip => self.limit_ip,
|
||||
LimitType::Webhook => self.limit_webhook,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -256,31 +256,31 @@ pub mod limits {
|
|||
|
||||
pub fn get_limit_ref(&self, limit_type: &LimitType) -> &Limit {
|
||||
match limit_type {
|
||||
&LimitType::AbsoluteMessage => &self.limit_absolute_messages,
|
||||
&LimitType::AbsoluteRegister => &self.limit_absolute_register,
|
||||
&LimitType::AuthLogin => &self.limit_auth_login,
|
||||
&LimitType::AuthRegister => &self.limit_auth_register,
|
||||
&LimitType::Channel => &self.limit_channel,
|
||||
&LimitType::Error => &self.limit_error,
|
||||
&LimitType::Global => &self.limit_global,
|
||||
&LimitType::Guild => &self.limit_guild,
|
||||
&LimitType::Ip => &self.limit_ip,
|
||||
&LimitType::Webhook => &self.limit_webhook,
|
||||
LimitType::AbsoluteMessage => &self.limit_absolute_messages,
|
||||
LimitType::AbsoluteRegister => &self.limit_absolute_register,
|
||||
LimitType::AuthLogin => &self.limit_auth_login,
|
||||
LimitType::AuthRegister => &self.limit_auth_register,
|
||||
LimitType::Channel => &self.limit_channel,
|
||||
LimitType::Error => &self.limit_error,
|
||||
LimitType::Global => &self.limit_global,
|
||||
LimitType::Guild => &self.limit_guild,
|
||||
LimitType::Ip => &self.limit_ip,
|
||||
LimitType::Webhook => &self.limit_webhook,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_limit_mut_ref(&mut self, limit_type: &LimitType) -> &mut Limit {
|
||||
match limit_type {
|
||||
&LimitType::AbsoluteMessage => &mut self.limit_absolute_messages,
|
||||
&LimitType::AbsoluteRegister => &mut self.limit_absolute_register,
|
||||
&LimitType::AuthLogin => &mut self.limit_auth_login,
|
||||
&LimitType::AuthRegister => &mut self.limit_auth_register,
|
||||
&LimitType::Channel => &mut self.limit_channel,
|
||||
&LimitType::Error => &mut self.limit_error,
|
||||
&LimitType::Global => &mut self.limit_global,
|
||||
&LimitType::Guild => &mut self.limit_guild,
|
||||
&LimitType::Ip => &mut self.limit_ip,
|
||||
&LimitType::Webhook => &mut self.limit_webhook,
|
||||
LimitType::AbsoluteMessage => &mut self.limit_absolute_messages,
|
||||
LimitType::AbsoluteRegister => &mut self.limit_absolute_register,
|
||||
LimitType::AuthLogin => &mut self.limit_auth_login,
|
||||
LimitType::AuthRegister => &mut self.limit_auth_register,
|
||||
LimitType::Channel => &mut self.limit_channel,
|
||||
LimitType::Error => &mut self.limit_error,
|
||||
LimitType::Global => &mut self.limit_global,
|
||||
LimitType::Guild => &mut self.limit_guild,
|
||||
LimitType::Ip => &mut self.limit_ip,
|
||||
LimitType::Webhook => &mut self.limit_webhook,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -311,7 +311,7 @@ pub mod limits {
|
|||
/// TODO: Change this to return a Result and handle the errors properly.
|
||||
pub async fn check_limits(api_url: String) -> Limits {
|
||||
let client = Client::new();
|
||||
let url_parsed = crate::URLBundle::parse_url(api_url) + "/policies/instance/limits";
|
||||
let url_parsed = crate::UrlBundle::parse_url(api_url) + "/policies/instance/limits";
|
||||
let result = client
|
||||
.get(url_parsed)
|
||||
.send()
|
||||
|
|
|
@ -1,6 +1,12 @@
|
|||
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 {
|
||||
/// 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.
|
||||
///
|
||||
/// # Returns
|
||||
/// This function returns a [`Option<Vec<Result<PublicUser, ChorusLibError>>>`].
|
||||
/// This function returns a [`Result<Vec<PublicUser>, ChorusLibError>`].
|
||||
pub async fn get_mutual_relationships(
|
||||
&mut self,
|
||||
user_id: &str,
|
||||
) -> Result<Option<Vec<types::PublicUser>>, ChorusLibError> {
|
||||
let belongs_to = self.belongs_to.borrow();
|
||||
) -> Result<Vec<types::PublicUser>, ChorusLibError> {
|
||||
let url = format!(
|
||||
"{}/users/{}/relationships/",
|
||||
belongs_to.urls.get_api(),
|
||||
self.belongs_to.borrow().urls.api,
|
||||
user_id
|
||||
);
|
||||
drop(belongs_to);
|
||||
let request = Client::new().get(url).bearer_auth(self.token());
|
||||
deserialize_response::<Option<Vec<types::PublicUser>>>(
|
||||
deserialize_response::<Vec<types::PublicUser>>(
|
||||
request,
|
||||
self,
|
||||
crate::api::limits::LimitType::Global,
|
||||
)
|
||||
.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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ use reqwest::Client;
|
|||
use serde_json::to_string;
|
||||
|
||||
use crate::{
|
||||
api::{deserialize_response, handle_request_as_option, limits::Limits},
|
||||
api::{deserialize_response, handle_request_as_result, limits::Limits},
|
||||
errors::ChorusLibError,
|
||||
instance::{Instance, UserMeta},
|
||||
limit::LimitedRequester,
|
||||
|
@ -52,10 +52,7 @@ impl UserMeta {
|
|||
return Err(ChorusLibError::PasswordRequiredError);
|
||||
}
|
||||
let request = Client::new()
|
||||
.patch(format!(
|
||||
"{}/users/@me/",
|
||||
self.belongs_to.borrow_mut().urls.get_api()
|
||||
))
|
||||
.patch(format!("{}/users/@me/", self.belongs_to.borrow().urls.api))
|
||||
.body(to_string(&modify_schema).unwrap())
|
||||
.bearer_auth(self.token());
|
||||
let user_updated =
|
||||
|
@ -74,14 +71,15 @@ impl UserMeta {
|
|||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// Returns `None` if the user was successfully deleted, or an `ChorusLibError` if an error occurred.
|
||||
pub async fn delete(mut self) -> Option<ChorusLibError> {
|
||||
let belongs_to = self.belongs_to.borrow();
|
||||
/// Returns `()` if the user was successfully deleted, or a `ChorusLibError` if an error occurred.
|
||||
pub async fn delete(mut self) -> Result<(), ChorusLibError> {
|
||||
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());
|
||||
drop(belongs_to);
|
||||
handle_request_as_option(request, &mut self, crate::api::limits::LimitType::Ip).await
|
||||
handle_request_as_result(request, &mut self, crate::api::limits::LimitType::Ip).await
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -90,7 +88,7 @@ impl User {
|
|||
let mut belongs_to = user.belongs_to.borrow_mut();
|
||||
User::_get(
|
||||
&user.token(),
|
||||
&format!("{}", belongs_to.urls.get_api()),
|
||||
&format!("{}", belongs_to.urls.api),
|
||||
&mut belongs_to.limits,
|
||||
id,
|
||||
)
|
||||
|
@ -103,12 +101,11 @@ impl User {
|
|||
limits_instance: &mut Limits,
|
||||
id: Option<&String>,
|
||||
) -> Result<User, ChorusLibError> {
|
||||
let url: String;
|
||||
if id.is_none() {
|
||||
url = format!("{}/users/@me/", url_api);
|
||||
let url = if id.is_none() {
|
||||
format!("{}/users/@me/", url_api)
|
||||
} 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 mut cloned_limits = limits_instance.clone();
|
||||
match LimitedRequester::send_request(
|
||||
|
@ -166,12 +163,6 @@ impl Instance {
|
|||
token: String,
|
||||
id: Option<&String>,
|
||||
) -> Result<User, ChorusLibError> {
|
||||
User::_get(
|
||||
&token,
|
||||
&self.urls.get_api().to_string(),
|
||||
&mut self.limits,
|
||||
id,
|
||||
)
|
||||
.await
|
||||
User::_get(&token, &self.urls.api, &mut self.limits, id).await
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,13 +18,14 @@ custom_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}",
|
||||
RateLimited{bucket:String} = "Ratelimited on Bucket {bucket}",
|
||||
MultipartCreationError{error: String} = "Got an error whilst creating the form: {}",
|
||||
FormCreationError{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: {error}",
|
||||
TokenExpired = "Token expired, invalid or not found.",
|
||||
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.",
|
||||
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! {
|
||||
|
|
252
src/gateway.rs
252
src/gateway.rs
|
@ -3,6 +3,7 @@ use crate::errors::ObserverError;
|
|||
use crate::gateway::events::Events;
|
||||
use crate::types;
|
||||
use crate::types::WebSocketEvent;
|
||||
use std::any::Any;
|
||||
use std::sync::Arc;
|
||||
|
||||
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
|
||||
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)]
|
||||
/**
|
||||
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 {
|
||||
/// The message we received from the server
|
||||
message: tokio_tungstenite::tungstenite::Message,
|
||||
|
@ -94,60 +93,36 @@ impl GatewayMessage {
|
|||
let content = self.message.to_string();
|
||||
|
||||
// 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() {
|
||||
"unknown error" | "4000" => {
|
||||
return Some(GatewayError::UnknownError);
|
||||
}
|
||||
"unknown opcode" | "4001" => {
|
||||
return Some(GatewayError::UnknownOpcodeError);
|
||||
}
|
||||
"unknown error" | "4000" => Some(GatewayError::UnknownError),
|
||||
"unknown opcode" | "4001" => Some(GatewayError::UnknownOpcodeError),
|
||||
"decode error" | "error while decoding payload" | "4002" => {
|
||||
return 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);
|
||||
Some(GatewayError::DecodeError)
|
||||
}
|
||||
"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" => {
|
||||
return Some(GatewayError::InvalidIntentsError);
|
||||
Some(GatewayError::InvalidIntentsError)
|
||||
}
|
||||
"disallowed intent(s)" | "disallowed intents" | "4014" => {
|
||||
return Some(GatewayError::DisallowedIntentsError);
|
||||
}
|
||||
_ => {
|
||||
return None;
|
||||
Some(GatewayError::DisallowedIntentsError)
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns whether or not the message is an error
|
||||
pub fn is_error(&self) -> bool {
|
||||
return self.error().is_some();
|
||||
self.error().is_some()
|
||||
}
|
||||
|
||||
/// Parses the message as a payload;
|
||||
|
@ -168,17 +143,15 @@ impl GatewayMessage {
|
|||
|
||||
/// Returns whether or not the message is empty
|
||||
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)]
|
||||
/**
|
||||
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 url: String,
|
||||
pub events: Arc<Mutex<Events>>,
|
||||
|
@ -308,6 +281,7 @@ pub struct Gateway {
|
|||
}
|
||||
|
||||
impl Gateway {
|
||||
#[allow(clippy::new_ret_no_self)]
|
||||
pub async fn new(websocket_url: String) -> Result<GatewayHandle, GatewayError> {
|
||||
let (websocket_stream, _) = match connect_async_tls_with_config(
|
||||
&websocket_url,
|
||||
|
@ -371,13 +345,13 @@ impl Gateway {
|
|||
gateway.gateway_listen_task().await;
|
||||
});
|
||||
|
||||
return Ok(GatewayHandle {
|
||||
Ok(GatewayHandle {
|
||||
url: websocket_url.clone(),
|
||||
events: shared_events,
|
||||
websocket_send: shared_websocket_send.clone(),
|
||||
handle,
|
||||
kill_send: kill_send.clone(),
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
/// The main gateway listener task;
|
||||
|
@ -388,15 +362,11 @@ impl Gateway {
|
|||
let msg = self.websocket_receive.next().await;
|
||||
|
||||
// This if chain can be much better but if let is unstable on stable rust
|
||||
if msg.as_ref().is_some() {
|
||||
if msg.as_ref().unwrap().is_ok() {
|
||||
let msg_unwrapped = msg.unwrap().unwrap();
|
||||
self.handle_message(GatewayMessage::from_tungstenite_message(msg_unwrapped))
|
||||
if let Some(Ok(message)) = msg {
|
||||
self.handle_message(GatewayMessage::from_tungstenite_message(message))
|
||||
.await;
|
||||
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// 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");
|
||||
|
@ -422,8 +392,8 @@ impl Gateway {
|
|||
return Err(data_deserialize_result.err().unwrap());
|
||||
}
|
||||
|
||||
event.update_data(data_deserialize_result.unwrap()).await;
|
||||
return Ok(());
|
||||
event.notify(data_deserialize_result.unwrap()).await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// This handles a message as a websocket event and updates its events along with the events' observers
|
||||
|
@ -444,11 +414,9 @@ impl Gateway {
|
|||
if msg.is_error() {
|
||||
println!("GW: Received error, connection will close..");
|
||||
|
||||
let error = msg.error();
|
||||
let _error = msg.error();
|
||||
|
||||
match error {
|
||||
_ => {}
|
||||
}
|
||||
{}
|
||||
|
||||
self.close().await;
|
||||
return;
|
||||
|
@ -1399,13 +1367,7 @@ impl Gateway {
|
|||
sessions: result.unwrap(),
|
||||
};
|
||||
|
||||
self.events
|
||||
.lock()
|
||||
.await
|
||||
.session
|
||||
.replace
|
||||
.update_data(data)
|
||||
.await;
|
||||
self.events.lock().await.session.replace.notify(data).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 {
|
||||
/// The heartbeat interval in milliseconds
|
||||
pub heartbeat_interval: u128,
|
||||
|
@ -1698,10 +1658,8 @@ impl HeartbeatHandler {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
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
|
||||
*/
|
||||
/// 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
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
struct HeartbeatThreadCommunication {
|
||||
/// The opcode for the communication we received, if relevant
|
||||
|
@ -1710,89 +1668,47 @@ struct HeartbeatThreadCommunication {
|
|||
sequence_number: Option<u64>,
|
||||
}
|
||||
|
||||
/**
|
||||
Trait which defines the behavior of an Observer. An Observer is an object which is subscribed to
|
||||
an Observable. The Observer is notified when the Observable's data changes.
|
||||
In this case, the Observable is a [`GatewayEvent`], which is a wrapper around a WebSocketEvent.
|
||||
*/
|
||||
pub trait Observer<T: types::WebSocketEvent>: std::fmt::Debug {
|
||||
fn update(&mut self, data: &T);
|
||||
/// Trait which defines the behavior of an Observer. An Observer is an object which is subscribed to
|
||||
/// an Observable. The Observer is notified when the Observable's data changes.
|
||||
/// In this case, the Observable is a [`GatewayEvent`], which is a wrapper around a WebSocketEvent.
|
||||
/// Note that `Debug` is used to tell `Observer`s apart when unsubscribing.
|
||||
pub trait Observer<T>: Sync + Send + std::fmt::Debug {
|
||||
fn update(&self, data: &T);
|
||||
}
|
||||
|
||||
/** GatewayEvent is a wrapper around a WebSocketEvent. It is used to notify the observers of a
|
||||
change in the WebSocketEvent. GatewayEvents are observable.
|
||||
*/
|
||||
/// GatewayEvent is a wrapper around a WebSocketEvent. It is used to notify the observers of a
|
||||
/// change in the WebSocketEvent. GatewayEvents are observable.
|
||||
#[derive(Default, Debug)]
|
||||
pub struct GatewayEvent<T: types::WebSocketEvent> {
|
||||
observers: Vec<Arc<Mutex<dyn Observer<T> + Sync + Send>>>,
|
||||
pub event_data: T,
|
||||
pub is_observed: bool,
|
||||
pub struct GatewayEvent<T: WebSocketEvent> {
|
||||
observers: Vec<Arc<dyn Observer<T>>>,
|
||||
}
|
||||
|
||||
impl<T: types::WebSocketEvent> GatewayEvent<T> {
|
||||
fn new(event_data: T) -> Self {
|
||||
Self {
|
||||
is_observed: false,
|
||||
observers: Vec::new(),
|
||||
event_data,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Returns true if the GatewayEvent is observed by at least one Observer.
|
||||
*/
|
||||
impl<T: WebSocketEvent> GatewayEvent<T> {
|
||||
/// Returns true if the GatewayEvent is observed by at least one Observer.
|
||||
pub fn is_observed(&self) -> bool {
|
||||
self.is_observed
|
||||
!self.observers.is_empty()
|
||||
}
|
||||
|
||||
/**
|
||||
Subscribes an Observer to the GatewayEvent. Returns an error if the GatewayEvent is already
|
||||
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;
|
||||
/// Subscribes an Observer to the GatewayEvent.
|
||||
pub fn subscribe(&mut self, observable: Arc<dyn Observer<T>>) {
|
||||
self.observers.push(observable);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/**
|
||||
Unsubscribes an Observer from the GatewayEvent.
|
||||
*/
|
||||
pub fn unsubscribe(&mut self, observable: Arc<Mutex<dyn Observer<T> + Sync + Send>>) {
|
||||
/// Unsubscribes an Observer from the GatewayEvent.
|
||||
pub fn unsubscribe(&mut self, observable: &dyn Observer<T>) {
|
||||
// .retain()'s closure retains only those elements of the vector, which have a different
|
||||
// pointer value than observable.
|
||||
// The usage of the debug format to compare the generic T of observers is quite stupid, but the only thing to compare between them is T and if T == T they are the same
|
||||
// anddd there is no way to do that without using format
|
||||
let to_remove = format!("{:?}", observable);
|
||||
self.observers
|
||||
.retain(|obs| !(format!("{:?}", obs) == format!("{:?}", &observable)));
|
||||
self.is_observed = !self.observers.is_empty();
|
||||
.retain(|obs| format!("{:?}", obs) != to_remove);
|
||||
}
|
||||
|
||||
/**
|
||||
Updates the GatewayEvent's data and notifies the observers.
|
||||
*/
|
||||
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) {
|
||||
/// Notifies the observers of the GatewayEvent.
|
||||
async fn notify(&self, new_event_data: T) {
|
||||
for observer in &self.observers {
|
||||
let mut observer_lock = observer.lock().await;
|
||||
observer_lock.update(&self.event_data);
|
||||
drop(observer_lock);
|
||||
observer.update(&new_event_data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1961,23 +1877,23 @@ mod events {
|
|||
#[cfg(test)]
|
||||
mod example {
|
||||
use super::*;
|
||||
use std::sync::atomic::{AtomicI32, Ordering::Relaxed};
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Consumer;
|
||||
struct Consumer {
|
||||
name: String,
|
||||
events_received: AtomicI32,
|
||||
}
|
||||
|
||||
impl Observer<types::GatewayResume> for Consumer {
|
||||
fn update(&mut self, data: &types::GatewayResume) {
|
||||
println!("{}", data.token)
|
||||
fn update(&self, _data: &types::GatewayResume) {
|
||||
self.events_received.fetch_add(1, Relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_observer_behavior() {
|
||||
let mut event = GatewayEvent::new(types::GatewayResume {
|
||||
token: "start".to_string(),
|
||||
session_id: "start".to_string(),
|
||||
seq: "start".to_string(),
|
||||
});
|
||||
let mut event = GatewayEvent::default();
|
||||
|
||||
let new_data = types::GatewayResume {
|
||||
token: "token_3276ha37am3".to_string(),
|
||||
|
@ -1985,25 +1901,23 @@ mod example {
|
|||
seq: "3".to_string(),
|
||||
};
|
||||
|
||||
let consumer = Consumer;
|
||||
let arc_mut_consumer = Arc::new(Mutex::new(consumer));
|
||||
let consumer = Arc::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;
|
||||
|
||||
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();
|
||||
assert_eq!(consumer.events_received.load(Relaxed), 1);
|
||||
assert_eq!(second_consumer.events_received.load(Relaxed), 2);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,14 +7,14 @@ use serde::{Deserialize, Serialize};
|
|||
use crate::api::limits::Limits;
|
||||
use crate::errors::{ChorusLibError, FieldFormatError};
|
||||
use crate::types::{GeneralConfiguration, User, UserSettings};
|
||||
use crate::URLBundle;
|
||||
use crate::UrlBundle;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
/**
|
||||
The [`Instance`] what you will be using to perform all sorts of actions on the Spacebar server.
|
||||
*/
|
||||
pub struct Instance {
|
||||
pub urls: URLBundle,
|
||||
pub urls: UrlBundle,
|
||||
pub instance_info: GeneralConfiguration,
|
||||
pub limits: Limits,
|
||||
}
|
||||
|
@ -26,20 +26,11 @@ impl Instance {
|
|||
/// * `requester` - The [`LimitedRequester`] that will be used to make requests to the Spacebar server.
|
||||
/// # Errors
|
||||
/// * [`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 {
|
||||
urls: urls.clone(),
|
||||
instance_info: GeneralConfiguration::new(
|
||||
// This is okay, because the instance_info will be overwritten by the instance_policies_schema() function.
|
||||
"".to_string(),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
),
|
||||
// Will be overwritten in the next step
|
||||
instance_info: GeneralConfiguration::default(),
|
||||
limits: Limits::check_limits(urls.api).await,
|
||||
};
|
||||
instance.instance_info = match instance.general_configuration_schema().await {
|
||||
|
|
36
src/lib.rs
36
src/lib.rs
|
@ -1,3 +1,5 @@
|
|||
#![allow(clippy::module_inception)]
|
||||
|
||||
use url::{ParseError, Url};
|
||||
|
||||
#[cfg(feature = "client")]
|
||||
|
@ -16,18 +18,18 @@ pub mod voice;
|
|||
#[derive(Clone, Default, Debug, PartialEq, Eq)]
|
||||
/// A URLBundle is a struct which bundles together the API-, Gateway- and CDN-URLs of a Spacebar
|
||||
/// instance.
|
||||
pub struct URLBundle {
|
||||
pub struct UrlBundle {
|
||||
pub api: String,
|
||||
pub wss: String,
|
||||
pub cdn: String,
|
||||
}
|
||||
|
||||
impl URLBundle {
|
||||
impl UrlBundle {
|
||||
pub fn new(api: String, wss: String, cdn: String) -> Self {
|
||||
Self {
|
||||
api: URLBundle::parse_url(api),
|
||||
wss: URLBundle::parse_url(wss),
|
||||
cdn: URLBundle::parse_url(cdn),
|
||||
api: UrlBundle::parse_url(api),
|
||||
wss: UrlBundle::parse_url(wss),
|
||||
cdn: UrlBundle::parse_url(cdn),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -42,13 +44,13 @@ impl URLBundle {
|
|||
let url = match Url::parse(&url) {
|
||||
Ok(url) => {
|
||||
if url.scheme() == "localhost" {
|
||||
return URLBundle::parse_url(format!("http://{}", url));
|
||||
return UrlBundle::parse_url(format!("http://{}", url));
|
||||
}
|
||||
url
|
||||
}
|
||||
Err(ParseError::RelativeUrlWithoutBase) => {
|
||||
let url_fmt = format!("http://{}", url);
|
||||
return URLBundle::parse_url(url_fmt);
|
||||
return UrlBundle::parse_url(url_fmt);
|
||||
}
|
||||
Err(_) => panic!("Invalid URL"),
|
||||
};
|
||||
|
@ -59,18 +61,6 @@ impl URLBundle {
|
|||
}
|
||||
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)]
|
||||
|
@ -79,13 +69,13 @@ mod lib {
|
|||
|
||||
#[test]
|
||||
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"));
|
||||
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"));
|
||||
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"));
|
||||
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"));
|
||||
}
|
||||
}
|
||||
|
|
25
src/limit.rs
25
src/limit.rs
|
@ -82,13 +82,11 @@ impl LimitedRequester {
|
|||
);
|
||||
if !response.status().is_success() {
|
||||
match response.status().as_u16() {
|
||||
401 => return Err(ChorusLibError::TokenExpired),
|
||||
403 => return Err(ChorusLibError::TokenExpired),
|
||||
_ => {
|
||||
return Err(ChorusLibError::ReceivedErrorCodeError {
|
||||
401 => Err(ChorusLibError::TokenExpired),
|
||||
403 => Err(ChorusLibError::TokenExpired),
|
||||
_ => Err(ChorusLibError::ReceivedErrorCodeError {
|
||||
error_code: response.status().as_str().to_string(),
|
||||
});
|
||||
}
|
||||
}),
|
||||
}
|
||||
} else {
|
||||
Ok(response)
|
||||
|
@ -258,13 +256,13 @@ impl LimitedRequester {
|
|||
mod rate_limit {
|
||||
use serde_json::from_str;
|
||||
|
||||
use crate::{api::limits::Config, URLBundle};
|
||||
use crate::{api::limits::Config, UrlBundle};
|
||||
|
||||
use super::*;
|
||||
|
||||
#[tokio::test]
|
||||
async fn run_into_limit() {
|
||||
let urls = URLBundle::new(
|
||||
let urls = UrlBundle::new(
|
||||
String::from("http://localhost:3001/api/"),
|
||||
String::from("wss://localhost:3001/"),
|
||||
String::from("http://localhost:3001/cdn"),
|
||||
|
@ -286,19 +284,12 @@ mod rate_limit {
|
|||
.await,
|
||||
);
|
||||
}
|
||||
if request.is_some() {
|
||||
match request.unwrap() {
|
||||
Ok(_) => assert!(false),
|
||||
Err(_) => assert!(true),
|
||||
}
|
||||
} else {
|
||||
assert!(false)
|
||||
}
|
||||
assert!(matches!(request, Some(Err(_))));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_send_request() {
|
||||
let urls = URLBundle::new(
|
||||
let urls = UrlBundle::new(
|
||||
String::from("http://localhost:3001/api/"),
|
||||
String::from("wss://localhost:3001/"),
|
||||
String::from("http://localhost:3001/cdn"),
|
||||
|
|
|
@ -89,15 +89,15 @@ fn generate_pairs(obj: &Value, key: &str) -> Vec<ConfigEntity> {
|
|||
fn pairs_to_config(pairs: Vec<ConfigEntity>) -> ConfigValue {
|
||||
let mut value = Value::Object(Map::new());
|
||||
|
||||
for p in pairs {
|
||||
let keys: Vec<&str> = p.key.split('_').collect();
|
||||
for pair in pairs {
|
||||
let keys: Vec<&str> = pair.key.split('_').collect();
|
||||
let mut path = vec![];
|
||||
|
||||
for (i, &key) in keys.iter().enumerate() {
|
||||
path.push(key);
|
||||
|
||||
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() {
|
||||
if !path_exists(&value, &path) {
|
||||
insert_into(&mut value, &path, Value::Array(Vec::new()));
|
||||
|
@ -182,6 +182,7 @@ mod test {
|
|||
let pairs = generate_pairs(&v, "");
|
||||
|
||||
let cfg = pairs_to_config(pairs);
|
||||
|
||||
assert_eq!(cfg, c)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
use std::fmt::{Display, Formatter};
|
||||
#[cfg(feature = "sqlx")]
|
||||
use std::io::Write;
|
||||
use std::ops::{Deref, DerefMut};
|
||||
use std::str::FromStr;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use bitflags::{bitflags, Flags};
|
||||
use bitflags::bitflags;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Value;
|
||||
use serde_repr::{Deserialize_repr, Serialize_repr};
|
||||
|
|
|
@ -6,7 +6,7 @@ use crate::types::Snowflake;
|
|||
|
||||
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
|
||||
pub struct Relationship {
|
||||
pub id: Snowflake,
|
||||
|
@ -17,7 +17,7 @@ pub struct Relationship {
|
|||
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)]
|
||||
/// See https://discord-userdoccers.vercel.app/resources/user#relationship-type
|
||||
pub enum RelationshipType {
|
||||
|
|
|
@ -108,6 +108,7 @@ impl PermissionFlags {
|
|||
self.contains(permission) || self.contains(PermissionFlags::ADMINISTRATOR)
|
||||
}
|
||||
|
||||
#[allow(clippy::inherent_to_string)]
|
||||
pub fn to_string(&self) -> String {
|
||||
self.bits().to_string()
|
||||
}
|
||||
|
@ -129,7 +130,7 @@ impl PermissionFlags {
|
|||
pub fn from_vec(flags: Vec<PermissionFlags>) -> String {
|
||||
let mut permissions: PermissionFlags = Default::default();
|
||||
for flag in flags.iter() {
|
||||
permissions = permissions | flag.clone();
|
||||
permissions |= flag.clone();
|
||||
}
|
||||
permissions.to_string()
|
||||
}
|
||||
|
|
|
@ -51,16 +51,18 @@ impl GatewayIdentifyPayload {
|
|||
impl GatewayIdentifyPayload {
|
||||
/// Creates an identify payload with the same default capabilities as the official client
|
||||
pub fn default_w_client_capabilities() -> Self {
|
||||
let mut def = Self::default();
|
||||
def.capabilities = Some(8189); // Default capabilities for a client
|
||||
def
|
||||
Self {
|
||||
capabilities: Some(8189), // Default capabilities for a client
|
||||
..Self::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates an identify payload with all possible capabilities
|
||||
pub fn default_w_all_capabilities() -> Self {
|
||||
let mut def = Self::default();
|
||||
def.capabilities = Some(i32::MAX); // Since discord uses bitwise for capabilities, this has almost every bit as 1, so all capabilities
|
||||
def
|
||||
Self {
|
||||
capabilities: Some(i32::MAX), // Since discord uses bitwise for capabilities, this has almost every bit as 1, so all capabilities
|
||||
..Self::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -148,22 +150,18 @@ impl GatewayIdentifyConnectionProps {
|
|||
|
||||
/// Returns the most common connection props so we can't be tracked
|
||||
pub fn common() -> Self {
|
||||
let mut default = Self::minimal();
|
||||
|
||||
Self {
|
||||
// See https://www.useragents.me/#most-common-desktop-useragents
|
||||
// 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 = String::from("Chrome");
|
||||
default.browser_version = String::from("113.0.0.0");
|
||||
|
||||
default.system_locale = String::from("en-US");
|
||||
|
||||
default.os = String::from("Windows");
|
||||
default.os_version = Some(String::from("10"));
|
||||
|
||||
default.client_build_number = 199933;
|
||||
default.release_channel = String::from("stable");
|
||||
|
||||
return default;
|
||||
browser: String::from("Chrome"),
|
||||
browser_version: String::from("113.0.0.0"),
|
||||
system_locale: String::from("en-US"),
|
||||
os: String::from("Windows"),
|
||||
os_version: Some(String::from("10")),
|
||||
client_build_number: 199933,
|
||||
release_channel: String::from("stable"),
|
||||
..Self::minimal()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use crate::types::{events::WebSocketEvent, UserStatus};
|
||||
use crate::types::{Activity, PublicUser, Snowflake};
|
||||
use crate::types::{Activity, ClientStatusObject, PublicUser, Snowflake};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, Default, Clone)]
|
||||
|
@ -26,12 +26,4 @@ pub struct PresenceUpdate {
|
|||
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 {}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use crate::types::events::WebSocketEvent;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, Default)]
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, Default)]
|
||||
pub struct GatewayResume {
|
||||
pub token: String,
|
||||
pub session_id: String,
|
||||
|
|
|
@ -116,9 +116,39 @@ pub struct RegisterSchema {
|
|||
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 {
|
||||
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
|
||||
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/)
|
||||
*/
|
||||
pub fn new(
|
||||
username: String,
|
||||
password: Option<String>,
|
||||
consent: bool,
|
||||
email: Option<String>,
|
||||
fingerprint: Option<String>,
|
||||
invite: Option<String>,
|
||||
date_of_birth: Option<String>,
|
||||
gift_code_sku_id: Option<String>,
|
||||
captcha_key: Option<String>,
|
||||
promotional_email_opt_in: Option<bool>,
|
||||
) -> Result<RegisterSchema, FieldFormatError> {
|
||||
let username = AuthUsername::new(username)?.username;
|
||||
pub fn build(self) -> Result<RegisterSchema, FieldFormatError> {
|
||||
let username = AuthUsername::new(self.username)?.username;
|
||||
|
||||
let email = if let Some(email) = email {
|
||||
let email = if let Some(email) = self.email {
|
||||
Some(AuthEmail::new(email)?.email)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let password = if let Some(password) = password {
|
||||
let password = if let Some(password) = self.password {
|
||||
Some(AuthPassword::new(password)?.password)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
if !consent {
|
||||
if !self.consent {
|
||||
return Err(FieldFormatError::ConsentError);
|
||||
}
|
||||
|
||||
Ok(RegisterSchema {
|
||||
username,
|
||||
password,
|
||||
consent,
|
||||
consent: self.consent,
|
||||
email,
|
||||
fingerprint,
|
||||
invite,
|
||||
date_of_birth,
|
||||
gift_code_sku_id,
|
||||
captcha_key,
|
||||
promotional_email_opt_in,
|
||||
fingerprint: self.fingerprint,
|
||||
invite: self.invite,
|
||||
date_of_birth: self.date_of_birth,
|
||||
gift_code_sku_id: self.gift_code_sku_id,
|
||||
captcha_key: self.captcha_key,
|
||||
promotional_email_opt_in: self.promotional_email_opt_in,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,47 +4,18 @@ use crate::types::entities::{
|
|||
AllowedMention, Component, Embed, MessageReference, PartialDiscordFileAttachment,
|
||||
};
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
#[derive(Debug, Default, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub struct MessageSendSchema {
|
||||
#[serde(rename = "type")]
|
||||
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>>,
|
||||
pub message_type: Option<i32>,
|
||||
pub content: Option<String>,
|
||||
pub nonce: Option<String>,
|
||||
pub tts: Option<bool>,
|
||||
pub embeds: Option<Vec<Embed>>,
|
||||
pub allowed_mentions: Option<AllowedMention>,
|
||||
pub message_reference: Option<MessageReference>,
|
||||
pub components: Option<Vec<Component>>,
|
||||
pub sticker_ids: Option<Vec<String>>,
|
||||
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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -65,18 +65,7 @@ mod schemas_tests {
|
|||
#[test]
|
||||
fn consent_false() {
|
||||
assert_eq!(
|
||||
RegisterSchema::new(
|
||||
"Test".to_string(),
|
||||
None,
|
||||
false,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
),
|
||||
RegisterSchema::builder("Test", false).build(),
|
||||
Err(FieldFormatError::ConsentError)
|
||||
);
|
||||
}
|
||||
|
@ -91,18 +80,11 @@ mod schemas_tests {
|
|||
|
||||
#[test]
|
||||
fn valid_email() {
|
||||
let reg = RegisterSchema::new(
|
||||
"Testy".to_string(),
|
||||
None,
|
||||
true,
|
||||
Some("me@mail.de".to_string()),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
);
|
||||
let reg = RegisterSchemaOptions {
|
||||
email: Some("me@mail.de".to_string()),
|
||||
..RegisterSchema::builder("Testy", true)
|
||||
}
|
||||
.build();
|
||||
assert_ne!(reg, Err(FieldFormatError::EmailError));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>,
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
pub use regexes::*;
|
||||
pub use rights::Rights;
|
||||
pub use snowflake::{DeconstructedSnowflake, Snowflake};
|
||||
pub use snowflake::Snowflake;
|
||||
|
||||
pub mod jwt;
|
||||
mod regexes;
|
||||
|
|
|
@ -1,22 +1,41 @@
|
|||
use std::fmt::Display;
|
||||
use std::{
|
||||
fmt::Display,
|
||||
sync::atomic::{AtomicUsize, Ordering},
|
||||
};
|
||||
|
||||
use atomic::Atomic;
|
||||
use bigdecimal::{Num, ToPrimitive, Zero};
|
||||
use num_bigint::{BigInt, ToBigInt};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use chrono::{DateTime, TimeZone, Utc};
|
||||
#[cfg(feature = "sqlx")]
|
||||
use sqlx::Type;
|
||||
|
||||
/// 2015-01-01
|
||||
const EPOCH: i64 = 1420070400000;
|
||||
static WORKER_ID: u128 = 0;
|
||||
static PROCESS_ID: u128 = 1;
|
||||
lazy_static::lazy_static! {
|
||||
static ref INCREMENT: Atomic<u128> = Atomic::default();
|
||||
}
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
|
||||
/// Unique identifier including a timestamp.
|
||||
/// See https://discord.com/developers/docs/reference#snowflakes
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "sqlx", derive(Type))]
|
||||
#[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 {
|
||||
fn default() -> Self {
|
||||
|
@ -30,131 +49,59 @@ impl Display for Snowflake {
|
|||
}
|
||||
}
|
||||
|
||||
impl Snowflake {
|
||||
pub fn to_binary(&self) -> String {
|
||||
let self_len = self.0.len();
|
||||
let high = self.0[..self_len - 10].parse::<u64>().unwrap_or(0);
|
||||
let low = self.0[self_len - 10..].parse::<u64>().unwrap();
|
||||
let mut low = low;
|
||||
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;
|
||||
impl serde::Serialize for Snowflake {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
serializer.serialize_str(&self.0.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
bin.iter()
|
||||
.rev()
|
||||
.map(|b| char::from_digit(*b as u32, 10).unwrap())
|
||||
.collect()
|
||||
impl<'de> serde::Deserialize<'de> for Snowflake {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
struct SnowflakeVisitor;
|
||||
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")
|
||||
}
|
||||
|
||||
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,
|
||||
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("")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct DeconstructedSnowflake {
|
||||
pub timestamp: i64,
|
||||
pub worker_id: u64,
|
||||
pub process_id: u64,
|
||||
pub increment: BigInt,
|
||||
pub binary: String,
|
||||
deserializer.deserialize_str(SnowflakeVisitor)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use chrono::{DateTime, Utc};
|
||||
|
||||
use super::Snowflake;
|
||||
|
||||
#[test]
|
||||
fn test_new_snowflake() {
|
||||
let snow = Snowflake::generate();
|
||||
println!("{snow}");
|
||||
fn generate() {
|
||||
let snow_1 = Snowflake::generate();
|
||||
let snow_2 = Snowflake::generate();
|
||||
assert!(snow_1.0 < snow_2.0)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn snowflake_to_binary() {
|
||||
let snowflake = super::Snowflake("1104339392517902336".to_string());
|
||||
|
||||
let bin = snowflake.to_binary();
|
||||
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());
|
||||
fn timestamp() {
|
||||
let snow: Snowflake = serde_json::from_str("\"175928847299117063\"").unwrap();
|
||||
let timestamp = "2016-04-30 11:18:25.796Z".parse::<DateTime<Utc>>().unwrap();
|
||||
assert_eq!(snow.timestamp(), timestamp);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,22 +1,15 @@
|
|||
use chorus::types;
|
||||
use chorus::types::{RegisterSchema, RegisterSchemaOptions};
|
||||
|
||||
mod common;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_registration() {
|
||||
let mut bundle = common::setup().await;
|
||||
let reg = types::RegisterSchema::new(
|
||||
"Hiiii".to_string(),
|
||||
None,
|
||||
true,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
Some("2000-01-01".to_string()),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
let reg = RegisterSchemaOptions {
|
||||
date_of_birth: Some("2000-01-01".to_string()),
|
||||
..RegisterSchema::builder("Hiiii", true)
|
||||
}
|
||||
.build()
|
||||
.unwrap();
|
||||
bundle.instance.register_account(®).await.unwrap();
|
||||
common::teardown(bundle).await;
|
||||
|
|
|
@ -21,7 +21,7 @@ async fn get_channel() {
|
|||
async fn delete_channel() {
|
||||
let mut bundle = common::setup().await;
|
||||
let result = bundle.channel.clone().delete(&mut bundle.user).await;
|
||||
assert!(result.is_none());
|
||||
assert!(result.is_ok());
|
||||
common::teardown(bundle).await
|
||||
}
|
||||
|
||||
|
@ -72,14 +72,16 @@ async fn modify_channel() {
|
|||
bundle.channel.id.to_string().as_str(),
|
||||
permission_override.clone(),
|
||||
)
|
||||
.await;
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
Channel::delete_permission(
|
||||
&mut bundle.user,
|
||||
bundle.channel.id.to_string().as_str(),
|
||||
&permission_override.id,
|
||||
)
|
||||
.await;
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
common::teardown(bundle).await
|
||||
}
|
||||
|
|
|
@ -2,14 +2,14 @@ use chorus::{
|
|||
instance::{Instance, UserMeta},
|
||||
types::{
|
||||
Channel, ChannelCreateSchema, Guild, GuildCreateSchema, RegisterSchema,
|
||||
RoleCreateModifySchema, RoleObject,
|
||||
RegisterSchemaOptions, RoleCreateModifySchema, RoleObject,
|
||||
},
|
||||
URLBundle,
|
||||
UrlBundle,
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct TestBundle {
|
||||
pub urls: URLBundle,
|
||||
pub urls: UrlBundle,
|
||||
pub user: UserMeta,
|
||||
pub instance: Instance,
|
||||
pub guild: Guild,
|
||||
|
@ -19,25 +19,18 @@ pub struct TestBundle {
|
|||
|
||||
// Set up a test by creating an Instance and a User. Reduces Test boilerplate.
|
||||
pub async fn setup() -> TestBundle {
|
||||
let urls = URLBundle::new(
|
||||
let urls = UrlBundle::new(
|
||||
"http://localhost:3001/api".to_string(),
|
||||
"ws://localhost:3001".to_string(),
|
||||
"http://localhost:3001".to_string(),
|
||||
);
|
||||
let mut instance = Instance::new(urls.clone()).await.unwrap();
|
||||
// Requires the existance of the below user.
|
||||
let reg = RegisterSchema::new(
|
||||
"integrationtestuser".to_string(),
|
||||
None,
|
||||
true,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
Some("2000-01-01".to_string()),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
let reg = RegisterSchemaOptions {
|
||||
date_of_birth: Some("2000-01-01".to_string()),
|
||||
..RegisterSchema::builder("integrationtestuser", true)
|
||||
}
|
||||
.build()
|
||||
.unwrap();
|
||||
let guild_create_schema = GuildCreateSchema {
|
||||
name: Some("Test-Guild!".to_string()),
|
||||
|
@ -100,6 +93,7 @@ pub async fn setup() -> TestBundle {
|
|||
}
|
||||
|
||||
// Teardown method to clean up after a test.
|
||||
#[allow(dead_code)]
|
||||
pub async fn teardown(mut bundle: TestBundle) {
|
||||
Guild::delete(&mut bundle.user, &bundle.guild.id.to_string()).await;
|
||||
bundle.user.delete().await;
|
||||
|
|
|
@ -20,10 +20,9 @@ async fn guild_creation_deletion() {
|
|||
.await
|
||||
.unwrap();
|
||||
|
||||
match Guild::delete(&mut bundle.user, &guild.id.to_string()).await {
|
||||
None => assert!(true),
|
||||
Some(_) => assert!(false),
|
||||
}
|
||||
assert!(Guild::delete(&mut bundle.user, &guild.id.to_string())
|
||||
.await
|
||||
.is_ok());
|
||||
common::teardown(bundle).await
|
||||
}
|
||||
|
||||
|
|
|
@ -18,7 +18,7 @@ async fn add_remove_role() {
|
|||
}
|
||||
}
|
||||
if !role_found {
|
||||
assert!(false)
|
||||
panic!()
|
||||
}
|
||||
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)
|
||||
|
@ -28,11 +28,11 @@ async fn add_remove_role() {
|
|||
if role != role_id {
|
||||
role_found = false;
|
||||
} else {
|
||||
assert!(false);
|
||||
panic!();
|
||||
}
|
||||
}
|
||||
if role_found {
|
||||
assert!(false)
|
||||
panic!()
|
||||
}
|
||||
common::teardown(bundle).await
|
||||
}
|
||||
|
|
|
@ -8,18 +8,10 @@ mod common;
|
|||
#[tokio::test]
|
||||
async fn send_message() {
|
||||
let mut bundle = common::setup().await;
|
||||
let mut message = types::MessageSendSchema::new(
|
||||
None,
|
||||
Some("A Message!".to_string()),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
);
|
||||
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)
|
||||
|
@ -53,18 +45,11 @@ async fn send_message_attachment() {
|
|||
content: buffer,
|
||||
};
|
||||
|
||||
let mut message = types::MessageSendSchema::new(
|
||||
None,
|
||||
Some("trans rights now".to_string()),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
Some(vec![attachment.clone()]),
|
||||
);
|
||||
let mut message = types::MessageSendSchema {
|
||||
content: Some("trans rights now".to_string()),
|
||||
attachments: Some(vec![attachment.clone()]),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let vec_attach = vec![attachment.clone()];
|
||||
let _arg = Some(&vec_attach);
|
||||
|
@ -79,3 +64,20 @@ async fn send_message_attachment() {
|
|||
.unwrap();
|
||||
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
|
||||
}
|
||||
|
|
|
@ -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(®ister_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(®ister_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(®ister_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(®ister_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
|
||||
}
|
|
@ -25,10 +25,7 @@ async fn create_and_get_roles() {
|
|||
let expected = types::RoleObject::get_all(&mut bundle.user, &guild_id)
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.nth(2)
|
||||
.unwrap()
|
||||
.unwrap()[2]
|
||||
.clone();
|
||||
|
||||
assert_eq!(role, expected);
|
||||
|
|
Loading…
Reference in New Issue