Compare commits

..

1 Commits

Author SHA1 Message Date
kozabrada123 5fc1457f9c
Merge 53f08ff1f9 into ec9541f38e 2024-08-21 18:29:51 +02:00
51 changed files with 541 additions and 781 deletions

View File

@ -101,7 +101,7 @@ jobs:
run: |
rustup target add wasm32-unknown-unknown
curl -L --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/cargo-bins/cargo-binstall/main/install-from-binstall-release.sh | bash
cargo binstall --no-confirm wasm-bindgen-cli --version "0.2.93" --force
cargo binstall --no-confirm wasm-bindgen-cli --version "0.2.92" --force
GECKODRIVER=$(which geckodriver) cargo test --target wasm32-unknown-unknown --no-default-features --features="client, rt, voice_gateway"
wasm-chrome:
runs-on: ubuntu-latest
@ -130,5 +130,5 @@ jobs:
run: |
rustup target add wasm32-unknown-unknown
curl -L --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/cargo-bins/cargo-binstall/main/install-from-binstall-release.sh | bash
cargo binstall --no-confirm wasm-bindgen-cli --version "0.2.93" --force
cargo binstall --no-confirm wasm-bindgen-cli --version "0.2.92" --force
CHROMEDRIVER=$(which chromedriver) cargo test --target wasm32-unknown-unknown --no-default-features --features="client, rt, voice_gateway"

444
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,7 @@
[package]
name = "chorus"
description = "A library for interacting with multiple Spacebar-compatible Instances at once."
version = "0.16.0"
version = "0.15.0"
license = "MPL-2.0"
edition = "2021"
repository = "https://github.com/polyphony-chat/chorus"
@ -13,19 +13,18 @@ rust-version = "1.70.0"
[features]
default = ["client", "rt-multi-thread"]
backend = ["poem", "sqlx", "sqlx-pg-uint"]
backend = ["poem", "sqlx"]
rt-multi-thread = ["tokio/rt-multi-thread"]
rt = ["tokio/rt"]
client = ["flate2"]
voice = ["voice_udp", "voice_gateway"]
voice_udp = ["dep:discortp", "dep:crypto_secretbox"]
voice_gateway = []
sqlx-pg-uint = ["dep:sqlx-pg-uint", "sqlx-pg-uint/serde"]
[dependencies]
tokio = { version = "1.39.3", features = ["macros", "sync"] }
serde = { version = "1.0.209", features = ["derive", "rc"] }
serde_json = { version = "1.0.127", features = ["raw_value"] }
tokio = { version = "1.38.1", features = ["macros", "sync"] }
serde = { version = "1.0.204", features = ["derive", "rc"] }
serde_json = { version = "1.0.120", features = ["raw_value"] }
serde-aux = "4.5.0"
serde_with = "3.9.0"
serde_repr = "0.1.19"
@ -36,7 +35,7 @@ reqwest = { features = [
], version = "=0.11.26", default-features = false }
url = "2.5.2"
chrono = { version = "0.4.38", features = ["serde"] }
regex = "1.10.6"
regex = "1.10.5"
custom_error = "1.9.2"
futures-util = "0.3.30"
http = "0.2.12"
@ -49,13 +48,14 @@ jsonwebtoken = "8.3.0"
log = "0.4.22"
async-trait = "0.1.81"
chorus-macros = { path = "./chorus-macros", version = "0" } # Note: version here is used when releasing. This will use the latest release. Make sure to republish the crate when code in macros is changed!
sqlx = { version = "0.8.1", features = [
sqlx = { version = "0.8.0", features = [
"mysql",
"sqlite",
"json",
"chrono",
"ipnetwork",
"runtime-tokio-rustls",
"postgres",
"bigdecimal",
"any",
], optional = true }
discortp = { version = "0.5.0", optional = true, features = [
"rtp",
@ -64,10 +64,9 @@ discortp = { version = "0.5.0", optional = true, features = [
] }
crypto_secretbox = { version = "0.1.1", optional = true }
rand = "0.8.5"
flate2 = { version = "1.0.33", optional = true }
flate2 = { version = "1.0.30", optional = true }
webpki-roots = "0.26.3"
pubserve = { version = "1.1.0", features = ["async", "send"] }
sqlx-pg-uint = { version = "0.5.0", features = ["serde"], optional = true }
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
rustls = "0.21.12"
@ -80,13 +79,13 @@ getrandom = { version = "0.2.15" }
[target.'cfg(target_arch = "wasm32")'.dependencies]
getrandom = { version = "0.2.15", features = ["js"] }
ws_stream_wasm = "0.7.4"
wasm-bindgen-futures = "0.4.43"
wasm-bindgen-futures = "0.4.42"
wasmtimer = "0.2.0"
[dev-dependencies]
lazy_static = "1.5.0"
wasm-bindgen-test = "0.3.43"
wasm-bindgen = "0.2.93"
wasm-bindgen-test = "0.3.42"
wasm-bindgen = "0.2.92"
simple_logger = { version = "5.0.0", default-features = false }
[lints.rust]

130
README.md
View File

@ -28,15 +28,14 @@
</div>
Chorus is a Rust library which poses as an API wrapper for [Spacebar Chat](https://github.com/spacebarchat/),
Discord and our own Polyphony. Its high-level API is designed to be easy to use, while still providing the
flexibility one would expect from a library like this.
Chorus is a Rust library which poses as an API wrapper for [Spacebar Chat](https://github.com/spacebarchat/)
and Discord. It is designed to be easy to use, and to be compatible with both Discord and Spacebar Chat.
You can establish as many connections to as many servers as you want, and you can use them all at the same time.
## A Tour of Chorus
Chorus combines all the required functionalities of an API wrapper for chat services into one modular library.
Chorus combines all the required functionalities of a user-centric Spacebar library into one package.
The library handles various aspects on your behalf, such as rate limiting, authentication and maintaining
a WebSocket connection to the Gateway. This means that you can focus on building your application,
instead of worrying about the underlying implementation details.
@ -45,19 +44,19 @@ To get started with Chorus, import it into your project by adding the following
```toml
[dependencies]
chorus = "0.16.0"
chorus = "0.15.0"
```
### Establishing a Connection
To connect to a Polyphony/Spacebar compatible server, you'll need to create an [`Instance`](https://docs.rs/chorus/latest/chorus/instance/struct.Instance.html) like this:
To connect to a Spacebar compatible server, you need to create an [`Instance`](https://docs.rs/chorus/latest/chorus/instance/struct.Instance.html) like this:
```rs
use chorus::instance::Instance;
#[tokio::main]
async fn main() {
let instance = Instance::new("https://example.com", None)
let instance = Instance::new("https://example.com")
.await
.expect("Failed to connect to the Spacebar server");
// You can create as many instances of `Instance` as you want, but each `Instance` should likely be unique.
@ -82,7 +81,7 @@ let login_schema = LoginSchema {
password: "Correct-Horse-Battery-Staple".to_string(),
..Default::default()
};
// Each user connects to the Gateway. Each users' Gateway connection lives on a separate thread. Depending on
// Each user connects to the Gateway. The Gateway connection lives on a separate thread. Depending on
// the runtime feature you choose, this can potentially take advantage of all of your computers' threads.
let user = instance
.login_account(login_schema)
@ -149,23 +148,98 @@ This crate uses Semantic Versioning 2.0.0 as its versioning scheme. You can read
See [CONTRIBUTING.md](./CONTRIBUTING.md).
[Rust]: https://img.shields.io/badge/Rust-orange?style=plastic&logo=rust
[Rust-url]: https://www.rust-lang.org/
[build-shield]: https://img.shields.io/github/actions/workflow/status/polyphony-chat/chorus/build_and_test.yml?style=flat
[build-url]: https://github.com/polyphony-chat/chorus/blob/main/.github/workflows/build_and_test.yml
[clippy-shield]: https://img.shields.io/github/actions/workflow/status/polyphony-chat/chorus/clippy.yml?style=flat
[clippy-url]: https://github.com/polyphony-chat/chorus/blob/main/.github/workflows/clippy.yml
[contributors-shield]: https://img.shields.io/github/contributors/polyphony-chat/chorus.svg?style=flat
[contributors-url]: https://github.com/polyphony-chat/chorus/graphs/contributors
[coverage-shield]: https://coveralls.io/repos/github/polyphony-chat/chorus/badge.svg?branch=main
[coverage-url]: https://coveralls.io/github/polyphony-chat/chorus?branch=main
[forks-shield]: https://img.shields.io/github/forks/polyphony-chat/chorus.svg?style=flat
[forks-url]: https://github.com/polyphony-chat/chorus/network/members
[stars-shield]: https://img.shields.io/github/stars/polyphony-chat/chorus.svg?style=flat
[stars-url]: https://github.com/polyphony-chat/chorus/stargazers
[issues-shield]: https://img.shields.io/github/issues/polyphony-chat/chorus.svg?style=flat
[issues-url]: https://github.com/polyphony-chat/chorus/issues
[license-shield]: https://img.shields.io/github/license/polyphony-chat/chorus.svg?style=f;at
[license-url]: https://github.com/polyphony-chat/chorus/blob/master/LICENSE
[Discord]: https://dcbadge.vercel.app/api/server/m3FpcapGDD?style=flat
[Discord-invite]: https://discord.com/invite/m3FpcapGDD
<details>
<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)
- [ ] [2FA](https://github.com/polyphony-chat/chorus/issues/40)
- [x] [Registration](https://github.com/polyphony-chat/chorus/issues/1)
### Messaging
- [x] [Sending messages](https://github.com/polyphony-chat/chorus/issues/23)
- [x] [Events (Message, User, Channel, etc.)](https://github.com/polyphony-chat/chorus/issues/51)
- [x] Channel creation
- [x] Channel deletion
- [x] [Channel management (name, description, icon, etc.)](https://github.com/polyphony-chat/chorus/issues/48)
- [x] [Join and Leave Guilds](https://github.com/polyphony-chat/chorus/issues/45)
- [x] [Start DMs](https://github.com/polyphony-chat/chorus/issues/45)
- [x] [Group DM creation, deletion and member management](https://github.com/polyphony-chat/chorus/issues/89)
- [ ] [Deleting messages](https://github.com/polyphony-chat/chorus/issues/91)
- [ ] [Message threads](https://github.com/polyphony-chat/chorus/issues/90)
- [x] [Reactions](https://github.com/polyphony-chat/chorus/issues/85)
- [ ] Message Search
- [ ] Message history
- [ ] Emoji
- [ ] Stickers
- [ ] [Forum channels](https://github.com/polyphony-chat/chorus/issues/90)
### User Management
- [ ] [User profile customization](https://github.com/polyphony-chat/chorus/issues/41)
- [x] Gettings users and user profiles
- [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
### Additional Features
- [ ] Server discovery
- [ ] Server templates
### Voice and Video
- [ ] [Voice chat support](https://github.com/polyphony-chat/chorus/issues/49)
- [ ] [Video chat support](https://github.com/polyphony-chat/chorus/issues/49)
### Permissions and Roles
- [x] [Role management](https://github.com/polyphony-chat/chorus/issues/46) (creation, deletion, modification)
- [x] [Permission management](https://github.com/polyphony-chat/chorus/issues/46) (assigning and revoking permissions)
- [x] [Channel-specific permissions](https://github.com/polyphony-chat/chorus/issues/88)
- [x] Role-based access control
### Guild Management
- [x] Guild creation
- [x] Guild deletion
- [ ] [Guild settings (name, description, icon, etc.)](https://github.com/polyphony-chat/chorus/issues/43)
- [ ] Guild invites
### Moderation
- [ ] Channel moderation (slow mode, etc.)
- [ ] User sanctions (mute, kick, ban)
- [ ] Audit logs
### Embeds and Rich Content
- [x] Sending rich content in messages (links, images, videos)
- [ ] Customizing embed appearance (title, description, color, fields)
### Webhooks
- [ ] Webhook creation and management
- [ ] Handling incoming webhook events
### Documentation and Examples
- [ ] Comprehensive documentation
- [ ] Example usage and code snippets
- [ ] Tutorials and guides
[Rust]: https://img.shields.io/badge/Rust-orange?style=plastic&logo=rust
[Rust-url]: https://www.rust-lang.org/
[build-shield]: https://img.shields.io/github/actions/workflow/status/polyphony-chat/chorus/build_and_test.yml?style=flat
[build-url]: https://github.com/polyphony-chat/chorus/blob/main/.github/workflows/build_and_test.yml
[clippy-shield]: https://img.shields.io/github/actions/workflow/status/polyphony-chat/chorus/clippy.yml?style=flat
[clippy-url]: https://github.com/polyphony-chat/chorus/blob/main/.github/workflows/clippy.yml
[contributors-shield]: https://img.shields.io/github/contributors/polyphony-chat/chorus.svg?style=flat
[contributors-url]: https://github.com/polyphony-chat/chorus/graphs/contributors
[coverage-shield]: https://coveralls.io/repos/github/polyphony-chat/chorus/badge.svg?branch=main
[coverage-url]: https://coveralls.io/github/polyphony-chat/chorus?branch=main
[forks-shield]: https://img.shields.io/github/forks/polyphony-chat/chorus.svg?style=flat
[forks-url]: https://github.com/polyphony-chat/chorus/network/members
[stars-shield]: https://img.shields.io/github/stars/polyphony-chat/chorus.svg?style=flat
[stars-url]: https://github.com/polyphony-chat/chorus/stargazers
[issues-shield]: https://img.shields.io/github/issues/polyphony-chat/chorus.svg?style=flat
[issues-url]: https://github.com/polyphony-chat/chorus/issues
[license-shield]: https://img.shields.io/github/license/polyphony-chat/chorus.svg?style=f;at
[license-url]: https://github.com/polyphony-chat/chorus/blob/master/LICENSE
[Discord]: https://dcbadge.vercel.app/api/server/m3FpcapGDD?style=flat
[Discord-invite]: https://discord.com/invite/m3FpcapGDD
</details>

View File

@ -1,6 +1,6 @@
[package]
name = "chorus-macros"
version = "0.5.0"
version = "0.4.1"
edition = "2021"
license = "MPL-2.0"
description = "Macros for the chorus crate."

View File

@ -164,23 +164,24 @@ pub fn sqlx_bitflag_derive(input: TokenStream) -> TokenStream {
quote!{
#[cfg(feature = "sqlx")]
impl sqlx::Type<sqlx::Postgres> for #name {
fn type_info() -> sqlx::postgres::PgTypeInfo {
<sqlx_pg_uint::PgU64 as sqlx::Type<sqlx::Postgres>>::type_info()
impl sqlx::Type<sqlx::Any> for #name {
fn type_info() -> sqlx::any::AnyTypeInfo {
<Vec<u8> as sqlx::Type<sqlx::Any>>::type_info()
}
}
#[cfg(feature = "sqlx")]
impl<'q> sqlx::Encode<'q, sqlx::Postgres> for #name {
fn encode_by_ref(&self, buf: &mut <sqlx::Postgres as sqlx::Database>::ArgumentBuffer<'q>) -> Result<sqlx::encode::IsNull, sqlx::error::BoxDynError> {
<sqlx_pg_uint::PgU64 as sqlx::Encode<sqlx::Postgres>>::encode_by_ref(&self.bits().into(), buf)
impl<'q> sqlx::Encode<'q, sqlx::Any> for #name {
fn encode_by_ref(&self, buf: &mut <sqlx::Any as sqlx::Database>::ArgumentBuffer<'q>) -> Result<sqlx::encode::IsNull, sqlx::error::BoxDynError> {
<Vec<u8> as sqlx::Encode<sqlx::Any>>::encode_by_ref(&self.bits().to_be_bytes().into(), buf)
}
}
#[cfg(feature = "sqlx")]
impl<'q> sqlx::Decode<'q, sqlx::Postgres> for #name {
fn decode(value: <sqlx::Postgres as sqlx::Database>::ValueRef<'q>) -> Result<Self, sqlx::error::BoxDynError> {
<sqlx_pg_uint::PgU64 as sqlx::Decode<sqlx::Postgres>>::decode(value).map(|v| Self::from_bits_truncate(v.to_uint()))
impl<'q> sqlx::Decode<'q, sqlx::Any> for #name {
fn decode(value: <sqlx::Any as sqlx::Database>::ValueRef<'q>) -> Result<Self, sqlx::error::BoxDynError> {
let vec = <Vec<u8> as sqlx::Decode<sqlx::Any>>::decode(value)?;
Ok(Self::from_bits(vec_u8_to_u64(vec)).unwrap())
}
}

View File

@ -52,7 +52,7 @@ impl Subscriber<GatewayReady> for ExampleObserver {
#[tokio::main(flavor = "current_thread")]
async fn main() {
let gateway_websocket_url = GATEWAY_URL;
let gateway_websocket_url = GATEWAY_URL.to_string();
// These options specify the encoding format, compression, etc
//

View File

@ -25,7 +25,7 @@ use wasmtimer::tokio::sleep;
/// This example creates a simple gateway connection and a session with an Identify event
#[tokio::main(flavor = "current_thread")]
async fn main() {
let gateway_websocket_url = GATEWAY_URL;
let gateway_websocket_url = GATEWAY_URL.to_string();
// These options specify the encoding format, compression, etc
//
@ -34,9 +34,7 @@ async fn main() {
let options = GatewayOptions::default();
// Initiate the gateway connection, starting a listener in one thread and a heartbeat handler in another
let gateway = Gateway::spawn(gateway_websocket_url, options)
.await
.unwrap();
let gateway = Gateway::spawn(gateway_websocket_url, options).await.unwrap();
// At this point, we are connected to the server and are sending heartbeats, however we still haven't authenticated

View File

@ -6,7 +6,7 @@ use chorus::instance::Instance;
#[tokio::main(flavor = "current_thread")]
async fn main() {
let instance = Instance::new("https://example.com/", None)
let instance = Instance::new("https://example.com/")
.await
.expect("Failed to connect to the Spacebar server");
dbg!(instance.instance_info);

View File

@ -7,7 +7,7 @@ use chorus::types::LoginSchema;
#[tokio::main(flavor = "current_thread")]
async fn main() {
let mut instance = Instance::new("https://example.com/", None)
let mut instance = Instance::new("https://example.com/")
.await
.expect("Failed to connect to the Spacebar server");
// Assume, you already have an account created on this instance. Registering an account works

View File

@ -30,12 +30,13 @@ impl Instance {
// We do not have a user yet, and the UserRateLimits will not be affected by a login
// request (since login is an instance wide limit), which is why we are just cloning the
// instances' limits to pass them on as user_rate_limits later.
let mut user = ChorusUser::shell(Arc::new(RwLock::new(self.clone())), "None").await;
let mut user =
ChorusUser::shell(Arc::new(RwLock::new(self.clone())), "None".to_string()).await;
let login_result = chorus_request
.deserialize_response::<LoginResult>(&mut user)
.await?;
user.set_token(&login_result.token);
user.set_token(login_result.token);
user.settings = login_result.settings;
let object = User::get_current(&mut user).await?;

View File

@ -22,8 +22,9 @@ pub mod register;
impl Instance {
/// Logs into an existing account on the spacebar server, using only a token.
pub async fn login_with_token(&mut self, token: &str) -> ChorusResult<ChorusUser> {
let mut user = ChorusUser::shell(Arc::new(RwLock::new(self.clone())), token).await;
pub async fn login_with_token(&mut self, token: String) -> ChorusResult<ChorusUser> {
let mut user =
ChorusUser::shell(Arc::new(RwLock::new(self.clone())), token).await;
let object = User::get_current(&mut user).await?;
let settings = User::get_settings(&mut user).await?;

View File

@ -37,14 +37,14 @@ impl Instance {
// We do not have a user yet, and the UserRateLimits will not be affected by a login
// request (since register is an instance wide limit), which is why we are just cloning
// the instances' limits to pass them on as user_rate_limits later.
let mut user = ChorusUser::shell(Arc::new(RwLock::new(self.clone())), "None").await;
let mut user =
ChorusUser::shell(Arc::new(RwLock::new(self.clone())), "None".to_string()).await;
let token = chorus_request
.deserialize_response::<Token>(&mut user)
.await?
.token;
user.set_token(&token);
user.set_token(token);
let object = User::get_current(&mut user).await?;
let settings = User::get_settings(&mut user).await?;

View File

@ -16,7 +16,6 @@ use crate::types::{
};
impl Message {
#[allow(clippy::useless_conversion)]
/// Sends a message in the channel with the provided channel_id.
/// Returns the sent message.
///
@ -41,7 +40,7 @@ impl Message {
chorus_request.deserialize_response::<Message>(user).await
} else {
for (index, attachment) in message.attachments.iter_mut().enumerate() {
attachment.get_mut(index).unwrap().id = Some((index as u64).into());
attachment.get_mut(index).unwrap().id = Some(index as i16);
}
let mut form = reqwest::multipart::Form::new();
let payload_json = to_string(&message).unwrap();
@ -112,7 +111,7 @@ impl Message {
let result = request.send_request(user).await?;
let result_json = result.json::<Value>().await.unwrap();
if !result_json.is_object() {
return Err(search_error(result_json.to_string().as_str()));
return Err(search_error(result_json.to_string()));
}
let value_map = result_json.as_object().unwrap();
if let Some(messages) = value_map.get("messages") {
@ -123,7 +122,7 @@ impl Message {
}
// The code below might be incorrect. We'll cross that bridge when we come to it
if !value_map.contains_key("code") || !value_map.contains_key("retry_after") {
return Err(search_error(result_json.to_string().as_str()));
return Err(search_error(result_json.to_string()));
}
let code = value_map.get("code").unwrap().as_u64().unwrap();
let retry_after = value_map.get("retry_after").unwrap().as_u64().unwrap();
@ -482,7 +481,7 @@ impl Message {
}
}
fn search_error(result_text: &str) -> ChorusError {
fn search_error(result_text: String) -> ChorusError {
ChorusError::InvalidResponse {
error: format!(
"Got unexpected Response, or Response which is not valid JSON. Response: \n{}",

View File

@ -9,10 +9,8 @@ use futures_util::{
};
use tokio::net::TcpStream;
use tokio_tungstenite::{
connect_async_tls_with_config, connect_async_with_config, tungstenite, Connector,
MaybeTlsStream, WebSocketStream,
connect_async_tls_with_config, tungstenite, Connector, MaybeTlsStream, WebSocketStream,
};
use url::Url;
use crate::gateway::{GatewayMessage, RawGatewayMessage};
@ -34,21 +32,6 @@ impl TungsteniteBackend {
pub async fn connect(
websocket_url: &str,
) -> Result<(TungsteniteSink, TungsteniteStream), TungsteniteBackendError> {
let websocket_url_parsed =
Url::parse(websocket_url).map_err(|_| TungsteniteBackendError::TungsteniteError {
error: tungstenite::error::Error::Url(
tungstenite::error::UrlError::UnsupportedUrlScheme,
),
})?;
if websocket_url_parsed.scheme() == "ws" {
let (websocket_stream, _) =
match connect_async_with_config(websocket_url, None, false).await {
Ok(websocket_stream) => websocket_stream,
Err(e) => return Err(TungsteniteBackendError::TungsteniteError { error: e }),
};
Ok(websocket_stream.split())
} else if websocket_url_parsed.scheme() == "wss" {
let certs = webpki_roots::TLS_SERVER_ROOTS;
let roots = rustls::RootCertStore {
roots: certs
@ -81,13 +64,6 @@ impl TungsteniteBackend {
};
Ok(websocket_stream.split())
} else {
Err(TungsteniteBackendError::TungsteniteError {
error: tungstenite::error::Error::Url(
tungstenite::error::UrlError::UnsupportedUrlScheme,
),
})
}
}
}

View File

@ -48,7 +48,7 @@ impl Gateway {
/// # Note
/// The websocket url should begin with the prefix wss:// or ws:// (for unsecure connections)
pub async fn spawn(
websocket_url: &str,
websocket_url: String,
options: GatewayOptions,
) -> Result<GatewayHandle, GatewayError> {
let url = options.add_to_url(websocket_url);

View File

@ -10,10 +10,9 @@ use std::fmt::Debug;
use super::{events::Events, *};
use crate::types::{self, Composite, Shared};
/// Represents a handle to a Gateway connection.
///
/// A Gateway connection will create observable [`Events`], which you can subscribe to.
///
/// 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, Clone)]
pub struct GatewayHandle {

View File

@ -25,8 +25,8 @@ impl GatewayOptions {
/// Adds the options to an existing gateway url
///
/// Returns the new url
pub(crate) fn add_to_url(&self, url: &str) -> String {
let mut url = url.to_string();
pub(crate) fn add_to_url(&self, url: String) -> String {
let mut url = url;
let mut parameters = Vec::with_capacity(2);

View File

@ -69,13 +69,8 @@ impl Instance {
/// Creates a new [`Instance`] from the [relevant instance urls](UrlBundle).
///
/// If `options` is `None`, the default [`GatewayOptions`] will be used.
///
/// To create an Instance from one singular url, use [`Instance::new()`].
pub async fn from_url_bundle(
urls: UrlBundle,
options: Option<GatewayOptions>,
) -> ChorusResult<Instance> {
pub async fn from_url_bundle(urls: UrlBundle) -> ChorusResult<Instance> {
let is_limited: Option<LimitsConfiguration> = Instance::is_limited(&urls.api).await?;
let limit_information;
@ -94,7 +89,7 @@ impl Instance {
instance_info: GeneralConfiguration::default(),
limits_information: limit_information,
client: Client::new(),
gateway_options: options.unwrap_or_default(),
gateway_options: GatewayOptions::default(),
};
instance.instance_info = match instance.general_configuration_schema().await {
Ok(schema) => schema,
@ -108,16 +103,14 @@ impl Instance {
/// Creates a new [`Instance`] by trying to get the [relevant instance urls](UrlBundle) from a root url.
///
/// If `options` is `None`, the default [`GatewayOptions`] will be used.
///
/// Shorthand for `Instance::from_url_bundle(UrlBundle::from_root_domain(root_domain).await?)`.
pub async fn new(root_url: &str, options: Option<GatewayOptions>) -> ChorusResult<Instance> {
pub async fn new(root_url: &str) -> ChorusResult<Instance> {
let urls = UrlBundle::from_root_url(root_url).await?;
Instance::from_url_bundle(urls, options).await
Instance::from_url_bundle(urls).await
}
pub async fn is_limited(api_url: &str) -> ChorusResult<Option<LimitsConfiguration>> {
let api_url = UrlBundle::parse_url(api_url);
let api_url = UrlBundle::parse_url(api_url.to_string());
let client = Client::new();
let request = client
.get(format!("{}/policies/instance/limits", &api_url))
@ -170,8 +163,8 @@ impl ChorusUser {
self.token.clone()
}
pub fn set_token(&mut self, token: &str) {
self.token = token.to_string();
pub fn set_token(&mut self, token: String) {
self.token = token;
}
/// Creates a new [ChorusUser] from existing data.
@ -202,15 +195,16 @@ impl ChorusUser {
/// registering or logging in to the Instance, where you do not yet have a User object, but still
/// need to make a RateLimited request. To use the [`GatewayHandle`], you will have to identify
/// first.
pub(crate) async fn shell(instance: Shared<Instance>, token: &str) -> ChorusUser {
pub(crate) async fn shell(instance: Shared<Instance>, token: String) -> ChorusUser {
let settings = Arc::new(RwLock::new(UserSettings::default()));
let object = Arc::new(RwLock::new(User::default()));
let wss_url = &instance.read().unwrap().urls.wss.clone();
let gateway_options = instance.read().unwrap().gateway_options;
let wss_url = instance.read().unwrap().urls.wss.clone();
// Dummy gateway object
let gateway = Gateway::spawn(wss_url, gateway_options).await.unwrap();
let gateway = Gateway::spawn(wss_url, GatewayOptions::default())
.await
.unwrap();
ChorusUser {
token: token.to_string(),
token,
belongs_to: instance.clone(),
limits: instance
.read()

View File

@ -3,29 +3,27 @@
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
/*!
Chorus is a Rust library which poses as an API wrapper for [Spacebar Chat](https://github.com/spacebarchat/),
Discord and our own Polyphony. Its high-level API is designed to be easy to use, while still providing the
flexibility one would expect from a library like this.
You can establish as many connections to as many servers as you want, and you can use them all at the same time.
## A Tour of Chorus
Chorus combines all the required functionalities of an API wrapper for chat services into one modular library.
Chorus combines all the required functionalities of a user-centric Spacebar library into one package.
The library handles various aspects on your behalf, such as rate limiting, authentication and maintaining
a WebSocket connection to the Gateway. This means that you can focus on building your application,
instead of worrying about the underlying implementation details.
### Establishing a Connection
To connect to a Polyphony/Spacebar compatible server, you'll need to create an [`Instance`](https://docs.rs/chorus/latest/chorus/instance/struct.Instance.html) like this:
To connect to a Spacebar compatible server, you need to create an [`Instance`](https://docs.rs/chorus/latest/chorus/instance/struct.Instance.html) like this:
```rs
use chorus::instance::Instance;
use chorus::UrlBundle;
#[tokio::main]
async fn main() {
let instance = Instance::new("https://example.com")
let bundle = UrlBundle::new(
"https://example.com/api".to_string(),
"wss://example.com/".to_string(),
"https://example.com/cdn".to_string(),
);
let instance = Instance::new(bundle)
.await
.expect("Failed to connect to the Spacebar server");
// You can create as many instances of `Instance` as you want, but each `Instance` should likely be unique.
@ -38,7 +36,7 @@ This Instance can now be used to log in, register and from there on, interact wi
### Logging In
Logging in correctly provides you with an instance of `ChorusUser`, with which you can interact with the server and
Logging in correctly provides you with an instance of [`ChorusUser`](https://docs.rs/chorus/latest/chorus/instance/struct.ChorusUser.html), with which you can interact with the server and
manipulate the account. Assuming you already have an account on the server, you can log in like this:
```rs
@ -50,7 +48,7 @@ let login_schema = LoginSchema {
password: "Correct-Horse-Battery-Staple".to_string(),
..Default::default()
};
// Each user connects to the Gateway. Each users' Gateway connection lives on a separate thread. Depending on
// Each user connects to the Gateway. The Gateway connection lives on a separate thread. Depending on
// the runtime feature you choose, this can potentially take advantage of all of your computers' threads.
let user = instance
.login_account(login_schema)
@ -66,33 +64,15 @@ All major desktop operating systems (Windows, macOS (aarch64/x86_64), Linux (aar
`wasm32-unknown-unknown` is a supported compilation target on versions `0.12.0` and up. This allows you to use
Chorus in your browser, or in any other environment that supports WebAssembly.
To compile for `wasm32-unknown-unknown`, execute the following command:
```sh
cargo build --target=wasm32-unknown-unknown --no-default-features
```
The following features are supported on `wasm32-unknown-unknown`:
| Feature | WASM Support |
| ----------------- | ------------ |
| `client` | |
| `rt` | |
| `rt-multi-thread` | |
| `backend` | |
| `voice` | |
| `voice_udp` | |
| `voice_gateway` | |
We recommend checking out the "examples" directory, as well as the documentation for more information.
We recommend checking out the examples directory, as well as the documentation for more information.
## MSRV (Minimum Supported Rust Version)
Rust **1.70.0**. This number might change at any point while Chorus is not yet at version 1.0.0.
Rust **1.67.1**. This number might change at any point while Chorus is not yet at version 1.0.0.
## Development Setup
Make sure that you have at least Rust 1.70.0 installed. You can check your Rust version by running `cargo --version`
Make sure that you have at least Rust 1.67.1 installed. You can check your Rust version by running `cargo --version`
in your terminal. To compile for `wasm32-unknown-unknown`, you need to install the `wasm32-unknown-unknown` target.
You can do this by running `rustup target add wasm32-unknown-unknown`.
@ -106,16 +86,12 @@ like "proxy connection checking" are already disabled on this version, which oth
### wasm
To test for wasm, you will need to `cargo install wasm-pack`. You can then run
`wasm-pack test --<chrome/firefox/safari> --headless -- --target wasm32-unknown-unknown --features="rt, client, voice_gateway" --no-default-features`
`wasm-pack test --<chrome/firefox/safari> --headless -- --target wasm32-unknown-unknown --features="rt, client" --no-default-features`
to run the tests for wasm.
## Versioning
This crate uses Semantic Versioning 2.0.0 as its versioning scheme. You can read the specification [here](https://semver.org/spec/v2.0.0.html).
## Contributing
See [CONTRIBUTING.md](./CONTRIBUTING.md).
!*/
#![doc(
html_logo_url = "https://raw.githubusercontent.com/polyphony-chat/design/main/branding/polyphony-chorus-round-8bit.png"
@ -125,7 +101,8 @@ See [CONTRIBUTING.md](./CONTRIBUTING.md).
clippy::extra_unused_lifetimes,
clippy::from_over_into,
clippy::needless_borrow,
clippy::new_without_default
clippy::new_without_default,
clippy::useless_conversion
)]
#![warn(
clippy::todo,
@ -134,8 +111,7 @@ See [CONTRIBUTING.md](./CONTRIBUTING.md).
clippy::print_stdout,
clippy::print_stderr,
missing_debug_implementations,
missing_copy_implementations,
clippy::useless_conversion
missing_copy_implementations
)]
#[cfg(all(feature = "rt", feature = "rt_multi_thread"))]
compile_error!("feature \"rt\" and feature \"rt_multi_thread\" cannot be enabled at the same time");
@ -163,27 +139,6 @@ pub mod types;
))]
pub mod voice;
#[cfg(not(feature = "sqlx"))]
pub type UInt128 = u128;
#[cfg(feature = "sqlx")]
pub type UInt128 = sqlx_pg_uint::PgU128;
#[cfg(not(feature = "sqlx"))]
pub type UInt64 = u64;
#[cfg(feature = "sqlx")]
pub type UInt64 = sqlx_pg_uint::PgU64;
#[cfg(not(feature = "sqlx"))]
pub type UInt32 = u32;
#[cfg(feature = "sqlx")]
pub type UInt32 = sqlx_pg_uint::PgU32;
#[cfg(not(feature = "sqlx"))]
pub type UInt16 = u16;
#[cfg(feature = "sqlx")]
pub type UInt16 = sqlx_pg_uint::PgU16;
#[cfg(not(feature = "sqlx"))]
pub type UInt8 = u8;
#[cfg(feature = "sqlx")]
pub type UInt8 = sqlx_pg_uint::PgU8;
#[derive(Clone, Default, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
/// A URLBundle bundles together the API-, Gateway- and CDN-URLs of a Spacebar instance.
///
@ -210,7 +165,7 @@ pub struct UrlBundle {
impl UrlBundle {
/// Creates a new UrlBundle from the relevant urls.
pub fn new(root: &str, api: &str, wss: &str, cdn: &str) -> Self {
pub fn new(root: String, api: String, wss: String, cdn: String) -> Self {
Self {
root: UrlBundle::parse_url(root),
api: UrlBundle::parse_url(api),
@ -227,17 +182,17 @@ impl UrlBundle {
/// let url = parse_url("localhost:3000");
/// ```
/// `-> Outputs "http://localhost:3000".`
pub fn parse_url(url: &str) -> String {
let url = match Url::parse(url) {
pub fn parse_url(url: String) -> String {
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"), // TODO: should not panic here
};
@ -260,7 +215,7 @@ impl UrlBundle {
/// of the above approaches fail, it is very likely that the instance is misconfigured, unreachable, or that
/// a wrong URL was provided.
pub async fn from_root_url(url: &str) -> ChorusResult<UrlBundle> {
let parsed = UrlBundle::parse_url(url);
let parsed = UrlBundle::parse_url(url.to_string());
let client = reqwest::Client::new();
let request_wellknown = client
.get(format!("{}/.well-known/spacebar", &parsed))
@ -298,10 +253,10 @@ impl UrlBundle {
.await
{
Ok(UrlBundle::new(
url,
&body.api_endpoint,
&body.gateway,
&body.cdn,
url.to_string(),
body.api_endpoint,
body.gateway,
body.cdn,
))
} else {
Err(ChorusError::RequestFailed {
@ -318,13 +273,13 @@ mod lib {
#[test]
fn test_parse_url() {
let mut result = UrlBundle::parse_url("localhost:3000/");
assert_eq!(result, "http://localhost:3000");
result = UrlBundle::parse_url("https://some.url.com/");
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/"));
assert_eq!(result, 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"));
assert_eq!(result, String::from("https://some.url.com"));
result = UrlBundle::parse_url("https://some.url.com/");
assert_eq!(result, "https://some.url.com");
result = UrlBundle::parse_url("https://some.url.com");
assert_eq!(result, "https://some.url.com");
}
}

View File

@ -162,11 +162,11 @@ impl Display for GuildFeaturesList {
}
#[cfg(feature = "sqlx")]
impl<'r> sqlx::Decode<'r, sqlx::Postgres> for GuildFeaturesList {
impl<'r> sqlx::Decode<'r, sqlx::Any> for GuildFeaturesList {
fn decode(
value: <sqlx::Postgres as sqlx::Database>::ValueRef<'r>,
value: <sqlx::Any as sqlx::Database>::ValueRef<'r>,
) -> Result<Self, sqlx::error::BoxDynError> {
let v = <String as sqlx::Decode<sqlx::Postgres>>::decode(value)?;
let v = <String as sqlx::Decode<sqlx::Any>>::decode(value)?;
Ok(Self(
v.split(',')
.filter(|f| !f.is_empty())
@ -177,10 +177,10 @@ impl<'r> sqlx::Decode<'r, sqlx::Postgres> for GuildFeaturesList {
}
#[cfg(feature = "sqlx")]
impl<'q> sqlx::Encode<'q, sqlx::Postgres> for GuildFeaturesList {
impl<'q> sqlx::Encode<'q, sqlx::Any> for GuildFeaturesList {
fn encode_by_ref(
&self,
buf: &mut <sqlx::Postgres as sqlx::Database>::ArgumentBuffer<'q>,
buf: &mut <sqlx::Any as sqlx::Database>::ArgumentBuffer<'q>,
) -> Result<sqlx::encode::IsNull, Box<dyn std::error::Error + Send + Sync>> {
if self.is_empty() {
return Ok(sqlx::encode::IsNull::Yes);
@ -191,18 +191,18 @@ impl<'q> sqlx::Encode<'q, sqlx::Postgres> for GuildFeaturesList {
.collect::<Vec<_>>()
.join(",");
<String as sqlx::Encode<sqlx::Postgres>>::encode_by_ref(&features, buf)
<String as sqlx::Encode<sqlx::Any>>::encode_by_ref(&features, buf)
}
}
#[cfg(feature = "sqlx")]
impl sqlx::Type<sqlx::Postgres> for GuildFeaturesList {
fn type_info() -> <sqlx::Postgres as sqlx::Database>::TypeInfo {
<String as sqlx::Type<sqlx::Postgres>>::type_info()
impl sqlx::Type<sqlx::Any> for GuildFeaturesList {
fn type_info() -> sqlx::any::AnyTypeInfo {
<String as sqlx::Type<sqlx::Any>>::type_info()
}
fn compatible(ty: &<sqlx::Postgres as sqlx::Database>::TypeInfo) -> bool {
<String as sqlx::Type<sqlx::Postgres>>::compatible(ty)
fn compatible(ty: &sqlx::any::AnyTypeInfo) -> bool {
<String as sqlx::Type<sqlx::Any>>::compatible(ty)
}
}

View File

@ -224,8 +224,7 @@ pub struct ApplicationCommandOptionChoice {
#[derive(Debug, Clone, Copy, Serialize_repr, Deserialize_repr, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
#[cfg_attr(not(feature = "sqlx"), repr(u8))]
#[cfg_attr(feature = "sqlx", repr(i16))]
#[repr(i32)]
/// # Reference
/// See <https://discord.com/developers/docs/interactions/application-commands#application-command-object-application-command-types>
pub enum ApplicationCommandOptionType {
@ -295,8 +294,7 @@ pub struct ApplicationCommandPermission {
Ord,
)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
#[cfg_attr(not(feature = "sqlx"), repr(u8))]
#[cfg_attr(feature = "sqlx", repr(i16))]
#[repr(u8)]
/// See <https://discord.com/developers/docs/interactions/application-commands#application-command-permissions-object-application-command-permission-type>
pub enum ApplicationCommandPermissionType {
#[default]

View File

@ -5,7 +5,6 @@
use serde::{Deserialize, Serialize};
use crate::types::utils::Snowflake;
use crate::UInt64;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, PartialOrd)]
#[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))]
@ -17,11 +16,11 @@ pub struct Attachment {
/// Max 1024 characters
pub description: Option<String>,
pub content_type: Option<String>,
pub size: UInt64,
pub size: u64,
pub url: String,
pub proxy_url: String,
pub height: Option<UInt64>,
pub width: Option<UInt64>,
pub height: Option<u64>,
pub width: Option<u64>,
pub ephemeral: Option<bool>,
/// The duration of the audio file (only for voice messages)
pub duration_secs: Option<f32>,
@ -38,12 +37,12 @@ pub struct Attachment {
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
pub struct PartialDiscordFileAttachment {
pub id: Option<UInt64>,
pub id: Option<i16>,
pub filename: String,
/// Max 1024 characters
pub description: Option<String>,
pub content_type: Option<String>,
pub size: Option<UInt64>,
pub size: Option<i64>,
pub url: Option<String>,
pub proxy_url: Option<String>,
pub height: Option<i32>,

View File

@ -12,7 +12,6 @@ use crate::types::utils::Snowflake;
use crate::types::{
AutoModerationRuleTriggerType, IntegrationType, PermissionOverwriteType, Shared,
};
use crate::UInt64;
#[derive(Serialize, Deserialize, Debug, Default, Clone)]
#[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))]
@ -109,8 +108,7 @@ pub struct AuditLogChange {
PartialOrd,
Ord,
)]
#[cfg_attr(not(feature = "sqlx"), repr(u8))]
#[cfg_attr(feature = "sqlx", repr(i16))]
#[repr(u8)]
#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
/// # Reference:
/// See <https://docs.discord.sex/resources/audit-log#audit-log-events>
@ -253,16 +251,16 @@ pub struct AuditEntryInfo {
pub auto_moderation_rule_trigger_type: Option<AutoModerationRuleTriggerType>,
pub channel_id: Option<Snowflake>,
// #[serde(option_string)]
pub count: Option<UInt64>,
pub count: Option<u64>,
// #[serde(option_string)]
pub delete_member_days: Option<UInt64>,
pub delete_member_days: Option<u64>,
/// The ID of the overwritten entity
pub id: Option<Snowflake>,
pub integration_type: Option<IntegrationType>,
// #[serde(option_string)]
pub members_removed: Option<UInt64>,
pub members_removed: Option<u64>,
// #[serde(option_string)]
pub message_id: Option<UInt64>,
pub message_id: Option<u64>,
pub role_name: Option<String>,
#[serde(rename = "type")]
pub overwrite_type: Option<PermissionOverwriteType>,

View File

@ -5,7 +5,6 @@
#[cfg(feature = "client")]
use crate::gateway::Updateable;
use crate::types::Shared;
use crate::UInt8;
#[cfg(feature = "client")]
use chorus_macros::Updateable;
@ -33,8 +32,7 @@ pub struct AutoModerationRule {
}
#[derive(Serialize_repr, Deserialize_repr, Debug, Clone, Default, Copy)]
#[cfg_attr(not(feature = "sqlx"), repr(u8))]
#[cfg_attr(feature = "sqlx", repr(i16))]
#[repr(u8)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
/// See <https://discord.com/developers/docs/resources/auto-moderation#auto-moderation-rule-object-event-types>
pub enum AutoModerationRuleEventType {
@ -45,8 +43,7 @@ pub enum AutoModerationRuleEventType {
#[derive(
Serialize_repr, Deserialize_repr, Debug, Clone, Default, PartialEq, Eq, PartialOrd, Ord, Copy,
)]
#[cfg_attr(not(feature = "sqlx"), repr(u8))]
#[cfg_attr(feature = "sqlx", repr(i16))]
#[repr(u8)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
/// See <https://discord.com/developers/docs/resources/auto-moderation#auto-moderation-rule-object-trigger-types>
pub enum AutoModerationRuleTriggerType {
@ -83,20 +80,18 @@ pub struct AutoModerationRuleTriggerMetadataForKeywordPreset {
pub allow_list: Vec<String>,
}
#[allow(missing_copy_implementations)]
#[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq, Eq, PartialOrd, Ord)]
#[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq, Eq, PartialOrd, Ord, Copy)]
/// See <https://discord.com/developers/docs/resources/auto-moderation#auto-moderation-rule-object-trigger-metadata>
pub struct AutoModerationRuleTriggerMetadataForMentionSpam {
/// Max 50
pub mention_total_limit: UInt8,
pub mention_total_limit: u8,
pub mention_raid_protection_enabled: bool,
}
#[derive(
Serialize_repr, Deserialize_repr, Debug, Clone, Default, PartialEq, Eq, PartialOrd, Ord, Copy,
)]
#[cfg_attr(not(feature = "sqlx"), repr(u8))]
#[cfg_attr(feature = "sqlx", repr(i16))]
#[repr(u8)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
/// See <https://discord.com/developers/docs/resources/auto-moderation#auto-moderation-rule-object-keyword-preset-types>
pub enum AutoModerationRuleKeywordPresetType {
@ -115,20 +110,9 @@ pub struct AutoModerationAction {
}
#[derive(
Serialize_repr,
Deserialize_repr,
Debug,
Clone,
Default,
PartialEq,
Eq,
PartialOrd,
Ord,
Copy,
Hash,
Serialize_repr, Deserialize_repr, Debug, Clone, Default, PartialEq, Eq, PartialOrd, Ord, Copy, Hash
)]
#[cfg_attr(not(feature = "sqlx"), repr(u8))]
#[cfg_attr(feature = "sqlx", repr(i16))]
#[repr(u8)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
/// See <https://discord.com/developers/docs/resources/auto-moderation#auto-moderation-action-object-action-types>
pub enum AutoModerationActionType {

View File

@ -22,7 +22,6 @@ use crate::gateway::GatewayHandle;
#[cfg(feature = "client")]
use crate::gateway::Updateable;
use crate::UInt64;
#[cfg(feature = "client")]
use chorus_macros::{observe_option_vec, Composite, Updateable};
@ -42,7 +41,13 @@ use super::{option_arc_rwlock_ptr_eq, option_vec_arc_rwlock_ptr_eq};
/// See <https://discord-userdoccers.vercel.app/resources/channel#channels-resource>
pub struct Channel {
pub application_id: Option<Snowflake>,
#[cfg(feature = "sqlx")]
pub applied_tags: Option<sqlx::types::Json<Vec<String>>>,
#[cfg(not(feature = "sqlx"))]
pub applied_tags: Option<Vec<String>>,
#[cfg(feature = "sqlx")]
pub available_tags: Option<sqlx::types::Json<Vec<Tag>>>,
#[cfg(not(feature = "sqlx"))]
pub available_tags: Option<Vec<Tag>>,
pub bitrate: Option<i32>,
#[serde(rename = "type")]
@ -50,7 +55,9 @@ pub struct Channel {
pub created_at: Option<chrono::DateTime<Utc>>,
pub default_auto_archive_duration: Option<i32>,
pub default_forum_layout: Option<i32>,
// DefaultReaction could be stored in a separate table. However, there are a lot of default emojis. How would we handle that?
#[cfg(feature = "sqlx")]
pub default_reaction_emoji: Option<sqlx::types::Json<DefaultReaction>>,
#[cfg(not(feature = "sqlx"))]
pub default_reaction_emoji: Option<DefaultReaction>,
pub default_sort_order: Option<i32>,
pub default_thread_rate_limit_per_user: Option<i32>,
@ -172,8 +179,6 @@ fn compare_permission_overwrites(
///
/// # Reference
/// See <https://discord-userdoccers.vercel.app/resources/channel#forum-tag-object>
#[cfg_attr(feature = "sqlx", derive(sqlx::FromRow, sqlx::Type))]
#[cfg_attr(feature = "sqlx", sqlx(type_name = "interface_type"))]
pub struct Tag {
pub id: Snowflake,
/// The name of the tag (max 20 characters)
@ -197,8 +202,7 @@ pub struct PermissionOverwrite {
}
#[derive(Debug, Serialize_repr, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Copy)]
#[cfg_attr(not(feature = "sqlx"), repr(u8))]
#[cfg_attr(feature = "sqlx", repr(i16))]
#[repr(u8)]
/// # Reference
///
/// See <https://docs.discord.sex/resources/channel#permission-overwrite-type>
@ -297,7 +301,7 @@ pub struct ThreadMember {
pub id: Option<Snowflake>,
pub user_id: Option<Snowflake>,
pub join_timestamp: Option<DateTime<Utc>>,
pub flags: Option<UInt64>,
pub flags: Option<u64>,
pub member: Option<Shared<GuildMember>>,
}
@ -317,8 +321,6 @@ impl PartialEq for ThreadMember {
///
/// # Reference
/// See <https://discord-userdoccers.vercel.app/resources/channel#default-reaction-object>
#[cfg_attr(feature = "sqlx", derive(sqlx::FromRow, sqlx::Type))]
#[cfg_attr(feature = "sqlx", sqlx(type_name = "interface_type"))]
pub struct DefaultReaction {
#[serde(default)]
pub emoji_id: Option<Snowflake>,
@ -340,7 +342,7 @@ pub struct DefaultReaction {
)]
#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
#[repr(i32)]
#[repr(u32)]
/// # Reference
/// See <https://discord-userdoccers.vercel.app/resources/channel#channel-type>
pub enum ChannelType {

View File

@ -32,6 +32,9 @@ use super::option_arc_rwlock_ptr_eq;
pub struct Emoji {
pub id: Snowflake,
pub name: Option<String>,
#[cfg(feature = "sqlx")]
pub roles: Option<sqlx::types::Json<Vec<Snowflake>>>,
#[cfg(not(feature = "sqlx"))]
pub roles: Option<Vec<Snowflake>>,
#[cfg_attr(feature = "sqlx", sqlx(skip))]
pub user: Option<Shared<User>>,

View File

@ -17,7 +17,6 @@ use crate::types::{
interfaces::WelcomeScreenObject,
utils::Snowflake,
};
use crate::UInt64;
use super::{option_arc_rwlock_ptr_eq, vec_arc_rwlock_ptr_eq, PublicUser};
@ -274,7 +273,7 @@ pub struct GuildScheduledEvent {
pub entity_id: Option<Snowflake>,
pub entity_metadata: Option<GuildScheduledEventEntityMetadata>,
pub creator: Option<Shared<User>>,
pub user_count: Option<UInt64>,
pub user_count: Option<u64>,
pub image: Option<String>,
}
@ -301,8 +300,7 @@ impl PartialEq for GuildScheduledEvent {
}
#[derive(Serialize_repr, Deserialize_repr, Debug, Default, Clone, PartialEq, Copy)]
#[cfg_attr(not(feature = "sqlx"), repr(u8))]
#[cfg_attr(feature = "sqlx", repr(i16))]
#[repr(u8)]
/// See <https://discord.com/developers/docs/resources/guild-scheduled-event#guild-scheduled-event-object-guild-scheduled-event-privacy-level>
pub enum GuildScheduledEventPrivacyLevel {
#[default]
@ -310,8 +308,7 @@ pub enum GuildScheduledEventPrivacyLevel {
}
#[derive(Serialize_repr, Deserialize_repr, Debug, Default, Clone, PartialEq, Copy)]
#[cfg_attr(not(feature = "sqlx"), repr(u8))]
#[cfg_attr(feature = "sqlx", repr(i16))]
#[repr(u8)]
/// See <https://discord.com/developers/docs/resources/guild-scheduled-event#guild-scheduled-event-object-guild-scheduled-event-status>
pub enum GuildScheduledEventStatus {
#[default]
@ -334,8 +331,7 @@ pub enum GuildScheduledEventStatus {
Copy,
Hash,
)]
#[cfg_attr(not(feature = "sqlx"), repr(u8))]
#[cfg_attr(feature = "sqlx", repr(i16))]
#[repr(u8)]
/// See <https://discord.com/developers/docs/resources/guild-scheduled-event#guild-scheduled-event-object-guild-scheduled-event-entity-types>
pub enum GuildScheduledEventEntityType {
#[default]
@ -373,8 +369,7 @@ pub struct VoiceRegion {
Ord,
)]
#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
#[cfg_attr(not(feature = "sqlx"), repr(u8))]
#[cfg_attr(feature = "sqlx", repr(i16))]
#[repr(u8)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
/// See <https://discord-userdoccers.vercel.app/resources/guild#message-notification-level>
pub enum MessageNotificationLevel {
@ -397,8 +392,7 @@ pub enum MessageNotificationLevel {
Ord,
)]
#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
#[cfg_attr(not(feature = "sqlx"), repr(u8))]
#[cfg_attr(feature = "sqlx", repr(i16))]
#[repr(u8)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
/// See <https://discord-userdoccers.vercel.app/resources/guild#explicit-content-filter-level>
pub enum ExplicitContentFilterLevel {
@ -422,8 +416,7 @@ pub enum ExplicitContentFilterLevel {
Ord,
)]
#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
#[cfg_attr(not(feature = "sqlx"), repr(u8))]
#[cfg_attr(feature = "sqlx", repr(i16))]
#[repr(u8)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
/// See <https://discord-userdoccers.vercel.app/resources/guild#verification-level>
pub enum VerificationLevel {
@ -449,8 +442,7 @@ pub enum VerificationLevel {
Ord,
)]
#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
#[cfg_attr(not(feature = "sqlx"), repr(u8))]
#[cfg_attr(feature = "sqlx", repr(i16))]
#[repr(u8)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
/// See <https://docs.discord.sex/resources/guild#mfa-level>
pub enum MFALevel {
@ -473,8 +465,7 @@ pub enum MFALevel {
Ord,
)]
#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
#[cfg_attr(not(feature = "sqlx"), repr(u8))]
#[cfg_attr(feature = "sqlx", repr(i16))]
#[repr(u8)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
/// See <https://docs.discord.sex/resources/guild#nsfw-level>
pub enum NSFWLevel {
@ -499,8 +490,7 @@ pub enum NSFWLevel {
Ord,
)]
#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
#[cfg_attr(not(feature = "sqlx"), repr(u8))]
#[cfg_attr(feature = "sqlx", repr(i16))]
#[repr(u8)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
// Note: Maybe rename this to GuildPremiumTier?
/// **Guild** premium (Boosting) tier

View File

@ -11,7 +11,6 @@ use crate::types::{
utils::Snowflake,
Shared,
};
use crate::{UInt16, UInt8};
#[derive(Default, Debug, Deserialize, Serialize, Clone)]
#[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))]
@ -26,7 +25,7 @@ pub struct Integration {
pub role_id: Option<String>,
pub enabled_emoticons: Option<bool>,
pub expire_behaviour: Option<IntegrationExpireBehaviour>,
pub expire_grace_period: Option<UInt16>,
pub expire_grace_period: Option<u16>,
#[cfg_attr(feature = "sqlx", sqlx(skip))]
pub user: Option<Shared<User>>,
#[cfg_attr(feature = "sqlx", sqlx(skip))]

View File

@ -5,12 +5,8 @@
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use crate::types::{Snowflake, WelcomeScreenObject, Shared, InviteFlags, InviteType, InviteTargetType, Guild, VerificationLevel};
use crate::types::types::guild_configuration::GuildFeaturesList;
use crate::types::{
Guild, InviteFlags, InviteTargetType, InviteType, Shared, Snowflake, VerificationLevel,
WelcomeScreenObject,
};
use crate::{UInt32, UInt8};
use super::guild::GuildScheduledEvent;
use super::{Application, Channel, GuildMember, NSFWLevel, User};
@ -40,8 +36,8 @@ pub struct Invite {
pub invite_type: Option<InviteType>,
#[cfg_attr(feature = "sqlx", sqlx(skip))]
pub inviter: Option<User>,
pub max_age: Option<UInt32>,
pub max_uses: Option<UInt8>,
pub max_age: Option<u32>,
pub max_uses: Option<u8>,
#[cfg_attr(feature = "sqlx", sqlx(skip))]
pub stage_instance: Option<InviteStageInstance>,
#[cfg_attr(feature = "sqlx", sqlx(skip))]
@ -51,7 +47,7 @@ pub struct Invite {
#[cfg_attr(feature = "sqlx", sqlx(skip))]
pub target_user: Option<User>,
pub temporary: Option<bool>,
pub uses: Option<UInt32>,
pub uses: Option<u32>,
}
/// The guild an invite is for.

View File

@ -15,7 +15,6 @@ use crate::types::{
utils::Snowflake,
Shared,
};
use crate::{UInt32, UInt8};
use super::option_arc_rwlock_ptr_eq;
@ -151,7 +150,7 @@ pub enum MessageReferenceType {
pub struct MessageInteraction {
pub id: Snowflake,
#[serde(rename = "type")]
pub interaction_type: UInt8,
pub interaction_type: u8,
pub name: String,
pub user: User,
pub member: Option<Shared<GuildMember>>,
@ -283,8 +282,8 @@ pub struct EmbedField {
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct Reaction {
pub count: UInt32,
pub burst_count: UInt32,
pub count: u32,
pub burst_count: u32,
#[serde(default)]
pub me: bool,
#[serde(default)]
@ -297,8 +296,6 @@ pub struct Reaction {
}
#[derive(Debug, PartialEq, Clone, Copy, Serialize, Deserialize, Eq, PartialOrd, Ord)]
#[cfg_attr(not(feature = "sqlx"), repr(u8))]
#[cfg_attr(feature = "sqlx", repr(i16))]
pub enum Component {
ActionRow = 1,
Button = 2,
@ -323,8 +320,7 @@ pub struct MessageActivity {
Debug, Default, PartialEq, Clone, Copy, Serialize_repr, Deserialize_repr, Eq, PartialOrd, Ord,
)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
#[cfg_attr(not(feature = "sqlx"), repr(u8))]
#[cfg_attr(feature = "sqlx", repr(i16))]
#[repr(u8)]
#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
/// # Reference
/// See <https://docs.discord.sex/resources/message#message-type>
@ -468,8 +464,7 @@ pub struct PartialEmoji {
#[derive(Debug, PartialEq, Clone, Copy, Serialize, Deserialize, PartialOrd, Ord, Eq, Hash)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
#[cfg_attr(not(feature = "sqlx"), repr(u8))]
#[cfg_attr(feature = "sqlx", repr(i16))]
#[repr(u8)]
pub enum ReactionType {
Normal = 0,
Burst = 1, // The dreaded super reactions

View File

@ -132,10 +132,10 @@ pub trait Composite<T: Updateable + Clone + Debug> {
pub trait IntoShared {
/// Uses [`Shared`] to provide an ergonomic alternative to `Arc::new(RwLock::new(obj))`.
///
/// [`Shared<Self>`] can then be observed using the gateway, turning the underlying
/// [`Shared<Self>`] can then be observed using the [`Gateway`], turning the underlying
/// `dyn Composite<Self>` into a self-updating struct, which is a tracked variant of a chorus
/// entity struct, updating its' held information when new information concerning itself arrives
/// over the gateway connection, reducing the need for expensive network-API calls.
/// over the [`Gateway`] connection, reducing the need for expensive network-API calls.
fn into_shared(self) -> Shared<Self>;
}

View File

@ -45,8 +45,7 @@ impl PartialEq for Relationship {
Copy,
Hash,
)]
#[cfg_attr(not(feature = "sqlx"), repr(u8))]
#[cfg_attr(feature = "sqlx", repr(i16))]
#[repr(u8)]
/// See <https://discord-userdoccers.vercel.app/resources/user#relationship-type>
pub enum RelationshipType {
Suggestion = 6,

View File

@ -8,7 +8,6 @@ use serde_aux::prelude::deserialize_option_number_from_string;
use std::fmt::Debug;
use crate::types::utils::Snowflake;
use crate::{UInt16, UInt32};
#[cfg(feature = "client")]
use chorus_macros::{Composite, Updateable};
@ -33,7 +32,7 @@ pub struct RoleObject {
pub hoist: bool,
pub icon: Option<String>,
pub unicode_emoji: Option<String>,
pub position: UInt16,
pub position: u16,
#[serde(default)]
pub permissions: PermissionFlags,
pub managed: bool,
@ -48,13 +47,11 @@ pub struct RoleObject {
pub struct RoleSubscriptionData {
pub role_subscription_listing_id: Snowflake,
pub tier_name: String,
pub total_months_subscribed: UInt32,
pub total_months_subscribed: u32,
pub is_renewal: bool,
}
#[derive(
Serialize, Deserialize, Debug, Default, Clone, PartialEq, Eq, Hash, Copy, PartialOrd, Ord,
)]
#[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq, Eq, Hash, Copy, PartialOrd, Ord)]
/// See <https://discord.com/developers/docs/topics/permissions#role-object-role-tags-structure>
pub struct RoleTags {
#[serde(default)]

View File

@ -5,7 +5,6 @@
use serde::{Deserialize, Serialize};
use crate::types::utils::Snowflake;
use crate::UInt64;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))]
@ -14,7 +13,7 @@ pub struct SecurityKey {
pub user_id: String,
pub key_id: String,
pub public_key: String,
pub counter: UInt64,
pub counter: u64,
pub name: String,
}
@ -25,8 +24,7 @@ impl Default for SecurityKey {
user_id: String::new(),
key_id: String::new(),
public_key: String::new(),
#[allow(clippy::useless_conversion)]
counter: 0u64.into(),
counter: 0,
name: String::new(),
}
}

View File

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

View File

@ -77,8 +77,7 @@ pub struct StickerItem {
#[derive(
Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Hash, Serialize_repr, Deserialize_repr,
)]
#[cfg_attr(not(feature = "sqlx"), repr(u8))]
#[cfg_attr(feature = "sqlx", repr(i16))]
#[repr(u8)]
#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
#[serde(rename = "SCREAMING_SNAKE_CASE")]
/// # Reference
@ -94,8 +93,7 @@ pub enum StickerType {
#[derive(
Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Hash, Serialize_repr, Deserialize_repr,
)]
#[cfg_attr(not(feature = "sqlx"), repr(u8))]
#[cfg_attr(feature = "sqlx", repr(i16))]
#[repr(u8)]
#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
/// # Reference
/// See <https://docs.discord.sex/resources/sticker#sticker-format-types>

View File

@ -7,7 +7,6 @@ use serde::{Deserialize, Serialize};
use crate::types::entities::User;
use crate::types::Shared;
use crate::types::Snowflake;
use crate::UInt8;
use super::arc_rwlock_ptr_eq;
@ -35,7 +34,7 @@ impl PartialEq for Team {
#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct TeamMember {
pub membership_state: UInt8,
pub membership_state: u8,
pub permissions: Vec<String>,
pub team_id: Snowflake,
pub user: Shared<User>,

View File

@ -6,11 +6,10 @@ use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use crate::types::{
Shared,
entities::{Guild, User},
utils::Snowflake,
Shared,
};
use crate::UInt64;
/// See <https://docs.spacebar.chat/routes/#cmp--schemas-template>
#[derive(Serialize, Deserialize, Debug, Default, Clone)]
@ -19,7 +18,7 @@ pub struct GuildTemplate {
pub code: String,
pub name: String,
pub description: Option<String>,
pub usage_count: Option<UInt64>,
pub usage_count: Option<u64>,
pub creator_id: Snowflake,
#[cfg_attr(feature = "sqlx", sqlx(skip))]
pub creator: Shared<User>,

View File

@ -4,7 +4,6 @@
use crate::errors::ChorusError;
use crate::types::utils::Snowflake;
use crate::{UInt32, UInt8};
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use serde_aux::prelude::{deserialize_option_number_from_string, deserialize_default_from_null};
@ -51,7 +50,7 @@ pub struct User {
pub bot: Option<bool>,
pub system: Option<bool>,
pub mfa_enabled: Option<bool>,
pub accent_color: Option<UInt32>,
pub accent_color: Option<u32>,
#[cfg_attr(feature = "sqlx", sqlx(default))]
pub locale: Option<String>,
pub verified: Option<bool>,
@ -117,32 +116,32 @@ impl TryFrom<Vec<u8>> for ThemeColors {
#[cfg(feature = "sqlx")]
// TODO: Add tests for Encode and Decode.
impl<'q> sqlx::Encode<'q, sqlx::Postgres> for ThemeColors {
impl<'q> sqlx::Encode<'q, sqlx::Any> for ThemeColors {
fn encode_by_ref(
&self,
buf: &mut <sqlx::Postgres as sqlx::Database>::ArgumentBuffer<'q>,
buf: &mut <sqlx::Any as sqlx::Database>::ArgumentBuffer<'q>,
) -> Result<sqlx::encode::IsNull, Box<dyn std::error::Error + Send + Sync>> {
let mut vec_u8 = Vec::new();
vec_u8.extend_from_slice(&self.inner.0.to_be_bytes());
vec_u8.extend_from_slice(&self.inner.1.to_be_bytes());
<Vec<u8> as sqlx::Encode<sqlx::Postgres>>::encode_by_ref(&vec_u8, buf)
<Vec<u8> as sqlx::Encode<sqlx::Any>>::encode_by_ref(&vec_u8, buf)
}
}
#[cfg(feature = "sqlx")]
impl<'d> sqlx::Decode<'d, sqlx::Postgres> for ThemeColors {
impl<'d> sqlx::Decode<'d, sqlx::Any> for ThemeColors {
fn decode(
value: <sqlx::Postgres as sqlx::Database>::ValueRef<'d>,
value: <sqlx::Any as sqlx::Database>::ValueRef<'d>,
) -> Result<Self, sqlx::error::BoxDynError> {
let value_vec = <Vec<u8> as sqlx::Decode<'d, sqlx::Postgres>>::decode(value)?;
let value_vec = <Vec<u8> as sqlx::Decode<'d, sqlx::Any>>::decode(value)?;
value_vec.try_into().map_err(|e: ChorusError| e.into())
}
}
#[cfg(feature = "sqlx")]
impl sqlx::Type<sqlx::Postgres> for ThemeColors {
fn type_info() -> <sqlx::Postgres as sqlx::Database>::TypeInfo {
<String as sqlx::Type<sqlx::Postgres>>::type_info()
impl sqlx::Type<sqlx::Any> for ThemeColors {
fn type_info() -> <sqlx::Any as sqlx::Database>::TypeInfo {
<String as sqlx::Type<sqlx::Any>>::type_info()
}
}
@ -154,7 +153,7 @@ pub struct PublicUser {
pub username: Option<String>,
pub discriminator: Option<String>,
pub avatar: Option<String>,
pub accent_color: Option<UInt32>,
pub accent_color: Option<u32>,
pub banner: Option<String>,
pub theme_colors: Option<ThemeColors>,
pub pronouns: Option<String>,

View File

@ -6,12 +6,9 @@ use chrono::{serde::ts_milliseconds_option, Utc};
use serde::{Deserialize, Serialize};
use crate::types::Shared;
use crate::{UInt16, UInt32, UInt8};
use serde_aux::field_attributes::deserialize_option_number_from_string;
#[derive(
Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default, Copy, PartialOrd, Ord, Hash,
)]
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default, Copy, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
#[serde(rename_all = "lowercase")]
pub enum UserStatus {
@ -29,9 +26,7 @@ impl std::fmt::Display for UserStatus {
}
}
#[derive(
Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default, Copy, PartialOrd, Ord, Hash,
)]
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default, Copy, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
#[serde(rename_all = "lowercase")]
pub enum UserTheme {
@ -43,23 +38,36 @@ pub enum UserTheme {
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))]
pub struct UserSettings {
pub afk_timeout: Option<UInt16>,
pub afk_timeout: Option<u16>,
pub allow_accessibility_detection: bool,
pub animate_emoji: bool,
pub animate_stickers: UInt8,
pub animate_stickers: u8,
pub contact_sync_enabled: bool,
pub convert_emoticons: bool,
#[cfg(feature = "sqlx")]
pub custom_status: Option<sqlx::types::Json<CustomStatus>>,
#[cfg(not(feature = "sqlx"))]
pub custom_status: Option<CustomStatus>,
pub default_guilds_restricted: bool,
pub detect_platform_accounts: bool,
pub developer_mode: bool,
pub disable_games_tab: bool,
pub enable_tts_command: bool,
pub explicit_content_filter: UInt8,
pub explicit_content_filter: u8,
#[cfg(feature = "sqlx")]
pub friend_source_flags: sqlx::types::Json<FriendSourceFlags>,
#[cfg(not(feature = "sqlx"))]
pub friend_source_flags: FriendSourceFlags,
pub gateway_connected: Option<bool>,
pub gif_auto_play: bool,
#[cfg(feature = "sqlx")]
pub guild_folders: sqlx::types::Json<Vec<GuildFolder>>,
#[cfg(not(feature = "sqlx"))]
pub guild_folders: Vec<GuildFolder>,
#[cfg(feature = "sqlx")]
#[serde(default)]
pub guild_positions: sqlx::types::Json<Vec<String>>,
#[cfg(not(feature = "sqlx"))]
#[serde(default)]
pub guild_positions: Vec<String>,
pub inline_attachment_media: bool,
@ -69,6 +77,9 @@ pub struct UserSettings {
pub native_phone_integration_enabled: bool,
pub render_embeds: bool,
pub render_reactions: bool,
#[cfg(feature = "sqlx")]
pub restricted_guilds: sqlx::types::Json<Vec<String>>,
#[cfg(not(feature = "sqlx"))]
pub restricted_guilds: Vec<String>,
pub show_current_game: bool,
pub status: Shared<UserStatus>,
@ -80,14 +91,10 @@ pub struct UserSettings {
impl Default for UserSettings {
fn default() -> Self {
Self {
#[allow(clippy::useless_conversion)]
afk_timeout: Some(3600u16.into()),
afk_timeout: Some(3600),
allow_accessibility_detection: true,
animate_emoji: true,
#[cfg(not(feature = "sqlx"))]
animate_stickers: 0,
#[cfg(feature = "sqlx")]
animate_stickers: 0.into(),
contact_sync_enabled: false,
convert_emoticons: false,
custom_status: None,
@ -96,10 +103,7 @@ impl Default for UserSettings {
developer_mode: true,
disable_games_tab: true,
enable_tts_command: false,
#[cfg(not(feature = "sqlx"))]
explicit_content_filter: 0,
#[cfg(feature = "sqlx")]
explicit_content_filter: 0.into(),
friend_source_flags: Default::default(),
gateway_connected: Some(false),
gif_auto_play: false,
@ -123,8 +127,7 @@ impl Default for UserSettings {
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "sqlx", derive(sqlx::FromRow, sqlx::Type))]
#[cfg_attr(feature = "sqlx", sqlx(type_name = "interface_type"))]
#[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))]
pub struct CustomStatus {
pub emoji_id: Option<String>,
pub emoji_name: Option<String>,
@ -134,7 +137,6 @@ pub struct CustomStatus {
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Copy, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "sqlx", derive(sqlx::FromRow, sqlx::Type))]
pub struct FriendSourceFlags {
pub all: bool,
}
@ -146,10 +148,8 @@ impl Default for FriendSourceFlags {
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "sqlx", derive(sqlx::FromRow, sqlx::Type))]
#[cfg_attr(feature = "sqlx", sqlx(type_name = "interface_type"))]
pub struct GuildFolder {
pub color: Option<UInt32>,
pub color: Option<u32>,
pub guild_ids: Vec<String>,
// FIXME: What is this thing?
// It's not a snowflake, and it's sometimes a string and sometimes an integer.

View File

@ -71,8 +71,7 @@ impl PartialEq for Webhook {
#[derive(
Serialize, Deserialize, Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash,
)]
#[cfg_attr(not(feature = "sqlx"), repr(u8))]
#[cfg_attr(feature = "sqlx", repr(i16))]
#[repr(u8)]
#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
pub enum WebhookType {
#[default]

View File

@ -5,7 +5,7 @@
use bitflags::bitflags;
use serde::{Deserialize, Serialize};
use crate::types::{entities::PermissionOverwrite, ChannelType, DefaultReaction, Snowflake};
use crate::types::{ChannelType, DefaultReaction, entities::PermissionOverwrite, Snowflake};
#[derive(Debug, Deserialize, Serialize, Default, PartialEq, PartialOrd)]
#[serde(rename_all = "snake_case")]
@ -141,8 +141,7 @@ bitflags! {
#[derive(Debug, Deserialize, Serialize, Clone, Copy, Default, PartialOrd, Ord, PartialEq, Eq)]
#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
#[cfg_attr(not(feature = "sqlx"), repr(u8))]
#[cfg_attr(feature = "sqlx", repr(i16))]
#[repr(u8)]
pub enum InviteType {
#[default]
Guild = 0,
@ -153,8 +152,7 @@ pub enum InviteType {
#[derive(Debug, Deserialize, Serialize, Clone, Copy, Default, PartialOrd, Ord, PartialEq, Eq)]
#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
#[cfg_attr(not(feature = "sqlx"), repr(u8))]
#[cfg_attr(feature = "sqlx", repr(i16))]
#[repr(u8)]
pub enum InviteTargetType {
#[default]
Stream = 1,
@ -171,9 +169,7 @@ pub struct AddChannelRecipientSchema {
}
/// See <https://discord-userdoccers.vercel.app/resources/channel#add-channel-recipient>
#[derive(
Debug, Deserialize, Serialize, Clone, Default, PartialOrd, Ord, PartialEq, Eq, Copy, Hash,
)]
#[derive(Debug, Deserialize, Serialize, Clone, Default, PartialOrd, Ord, PartialEq, Eq, Copy, Hash)]
pub struct ModifyChannelPositionsSchema {
pub id: Snowflake,
pub position: Option<u32>,
@ -182,9 +178,7 @@ pub struct ModifyChannelPositionsSchema {
}
/// See <https://docs.discord.sex/resources/channel#follow-channel>
#[derive(
Debug, Deserialize, Serialize, Clone, Default, PartialOrd, Ord, PartialEq, Eq, Copy, Hash,
)]
#[derive(Debug, Deserialize, Serialize, Clone, Default, PartialOrd, Ord, PartialEq, Eq, Copy, Hash)]
pub struct AddFollowingChannelSchema {
pub webhook_channel_id: Snowflake,
}

View File

@ -78,11 +78,6 @@ pub struct UserModifySchema {
/// # Note
///
/// This is not yet implemented on Spacebar
///
/// [UserFlags]: crate::types::UserFlags
/// [UserFlags::PREMIUM_PROMO_DISMISSED]: crate::types::UserFlags::PREMIUM_PROMO_DISMISSED
/// [UserFlags::HAS_UNREAD_URGENT_MESSAGES]:
/// crate::types::UserFlags::HAS_UNREAD_URGENT_MESSAGES
pub flags: Option<u64>,
/// The user's date of birth, can only be set once
///

View File

@ -3,14 +3,11 @@
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
use crate::types::utils::Snowflake;
use jsonwebtoken::errors::Error;
use jsonwebtoken::{
decode, encode, Algorithm, DecodingKey, EncodingKey, Header, TokenData, Validation,
};
use jsonwebtoken::{encode, EncodingKey, Header};
use serde::{Deserialize, Serialize};
pub fn generate_token(id: &Snowflake, email: &str, jwt_key: &str) -> String {
let claims = Claims::new(email, id);
pub fn generate_token(id: &Snowflake, email: String, jwt_key: &str) -> String {
let claims = Claims::new(&email, id);
build_token(&claims, jwt_key).unwrap()
}
@ -45,13 +42,8 @@ pub fn build_token(claims: &Claims, jwt_key: &str) -> Result<String, jsonwebtoke
)
}
pub fn decode_token(token: &str, jwt_secret: &str) -> Result<TokenData<Claims>, Error> {
/*pub fn decode_token(token: &str) -> Result<TokenData<Claims>, Error> {
let mut validation = Validation::new(Algorithm::HS256);
//TODO: What is this?
//validation.sub = Some("quartzauth".to_string());
decode(
token,
&DecodingKey::from_secret(jwt_secret.as_bytes()),
&validation,
)
}
validation.sub = Some("quartzauth".to_string());
decode(token, &DecodingKey::from_secret(JWT_SECRET), &validation)
}*/

View File

@ -26,7 +26,7 @@ impl Snowflake {
const PROCESS_ID: u64 = 1;
static INCREMENT: AtomicUsize = AtomicUsize::new(0);
let time = (Utc::now().naive_utc().and_utc().timestamp_millis() - EPOCH) << 22;
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;
@ -53,15 +53,12 @@ impl Display for Snowflake {
}
}
impl From<u64> for Snowflake {
fn from(item: u64) -> Self {
Self(item)
}
}
impl From<Snowflake> for u64 {
fn from(item: Snowflake) -> Self {
item.0
impl<T> From<T> for Snowflake
where
T: Into<u64>,
{
fn from(item: T) -> Self {
Self(item.into())
}
}
@ -102,39 +99,29 @@ impl<'de> serde::Deserialize<'de> for Snowflake {
}
#[cfg(feature = "sqlx")]
impl sqlx::Type<sqlx::Postgres> for Snowflake {
fn type_info() -> <sqlx::Postgres as sqlx::Database>::TypeInfo {
<sqlx_pg_uint::PgU64 as sqlx::Type<sqlx::Postgres>>::type_info()
impl sqlx::Type<sqlx::Any> for Snowflake {
fn type_info() -> <sqlx::Any as sqlx::Database>::TypeInfo {
<String as sqlx::Type<sqlx::Any>>::type_info()
}
}
#[cfg(feature = "sqlx")]
impl sqlx::postgres::PgHasArrayType for Snowflake {
fn array_type_info() -> sqlx::postgres::PgTypeInfo {
<Vec<sqlx_pg_uint::PgU64> as sqlx::Type<sqlx::Postgres>>::type_info()
}
}
#[cfg(feature = "sqlx")]
impl<'q> sqlx::Encode<'q, sqlx::Postgres> for Snowflake {
impl<'q> sqlx::Encode<'q, sqlx::Any> for Snowflake {
fn encode_by_ref(
&self,
buf: &mut <sqlx::Postgres as sqlx::Database>::ArgumentBuffer<'q>,
buf: &mut <sqlx::Any as sqlx::Database>::ArgumentBuffer<'q>,
) -> Result<sqlx::encode::IsNull, sqlx::error::BoxDynError> {
<sqlx_pg_uint::PgU64 as sqlx::Encode<'q, sqlx::Postgres>>::encode_by_ref(
&sqlx_pg_uint::PgU64::from(self.0),
buf,
)
<String as sqlx::Encode<'q, sqlx::Any>>::encode_by_ref(&self.0.to_string(), buf)
}
}
#[cfg(feature = "sqlx")]
impl<'d> sqlx::Decode<'d, sqlx::Postgres> for Snowflake {
impl<'d> sqlx::Decode<'d, sqlx::Any> for Snowflake {
fn decode(
value: <sqlx::Postgres as sqlx::Database>::ValueRef<'d>,
value: <sqlx::Any as sqlx::Database>::ValueRef<'d>,
) -> Result<Self, sqlx::error::BoxDynError> {
<sqlx_pg_uint::PgU64 as sqlx::Decode<'d, sqlx::Postgres>>::decode(value)
.map(|s| s.to_uint().into())
<String as sqlx::Decode<'d, sqlx::Any>>::decode(value)
.map(|s| s.parse::<u64>().map(Snowflake).unwrap())
}
}

View File

@ -41,7 +41,7 @@ pub struct VoiceGateway {
impl VoiceGateway {
#[allow(clippy::new_ret_no_self)]
pub async fn spawn(websocket_url: &str) -> Result<VoiceGatewayHandle, VoiceGatewayError> {
pub async fn spawn(websocket_url: String) -> Result<VoiceGatewayHandle, VoiceGatewayError> {
// Append the needed things to the websocket url
let processed_url = format!("wss://{}/?v=7", websocket_url);
trace!("VGW: Connecting to {}", processed_url.clone());
@ -110,7 +110,7 @@ impl VoiceGateway {
});
Ok(VoiceGatewayHandle {
url: websocket_url.to_string(),
url: websocket_url.clone(),
events: shared_events,
websocket_send: shared_websocket_send.clone(),
kill_send: kill_send.clone(),

View File

@ -72,7 +72,7 @@ impl VoiceGatewayHandle {
/// Sends a speaking event to the gateway
pub async fn send_speaking(&self, to_send: Speaking) {
let to_send_value = serde_json::to_value(to_send).unwrap();
let to_send_value = serde_json::to_value(&to_send).unwrap();
trace!("VGW: Sending Speaking");

View File

@ -79,7 +79,11 @@ async fn test_login_with_token() {
let mut bundle = common::setup().await;
let token = &bundle.user.token;
let other_user = bundle.instance.login_with_token(token).await.unwrap();
let other_user = bundle
.instance
.login_with_token(token.clone())
.await
.unwrap();
assert_eq!(
bundle.user.object.read().unwrap().id,
other_user.object.read().unwrap().id
@ -94,8 +98,8 @@ async fn test_login_with_token() {
async fn test_login_with_invalid_token() {
let mut bundle = common::setup().await;
let token = "invalid token lalalalala";
let other_user = bundle.instance.login_with_token(token).await;
let token = "invalid token lalalalala".to_string();
let other_user = bundle.instance.login_with_token(token.clone()).await;
assert!(other_user.is_err());

View File

@ -50,7 +50,7 @@ impl TestBundle {
limits: self.user.limits.clone(),
settings: self.user.settings.clone(),
object: self.user.object.clone(),
gateway: Gateway::spawn(&self.instance.urls.wss, GatewayOptions::default())
gateway: Gateway::spawn(self.instance.urls.wss.clone(), GatewayOptions::default())
.await
.unwrap(),
}
@ -66,9 +66,7 @@ pub(crate) async fn setup() -> TestBundle {
)
.init();
let instance = Instance::new("http://localhost:3001/api", None)
.await
.unwrap();
let instance = Instance::new("http://localhost:3001/api").await.unwrap();
// Requires the existence of the below user.
let reg = RegisterSchema {
username: "integrationtestuser".into(),
@ -126,10 +124,10 @@ pub(crate) async fn setup() -> TestBundle {
.unwrap();
let urls = UrlBundle::new(
"http://localhost:3001/api",
"http://localhost:3001/api",
"ws://localhost:3001/",
"http://localhost:3001",
"http://localhost:3001/api".to_string(),
"http://localhost:3001/api".to_string(),
"ws://localhost:3001/".to_string(),
"http://localhost:3001".to_string(),
);
TestBundle {
urls,

View File

@ -31,7 +31,7 @@ use wasmtimer::tokio::sleep;
async fn test_gateway_establish() {
let bundle = common::setup().await;
let _: GatewayHandle = Gateway::spawn(&bundle.urls.wss, GatewayOptions::default())
let _: GatewayHandle = Gateway::spawn(bundle.urls.wss.clone(), GatewayOptions::default())
.await
.unwrap();
common::teardown(bundle).await
@ -55,7 +55,7 @@ impl Subscriber<GatewayReady> for GatewayReadyObserver {
async fn test_gateway_authenticate() {
let bundle = common::setup().await;
let gateway: GatewayHandle = Gateway::spawn(&bundle.urls.wss, GatewayOptions::default())
let gateway: GatewayHandle = Gateway::spawn(bundle.urls.wss.clone(), GatewayOptions::default())
.await
.unwrap();