Compare commits

..

No commits in common. "433711409ee1fa2950395ff2ff3da0c07c441e2f" and "44ac1c0c85f9446964b83190c03a902b9f3c4c4d" have entirely different histories.

232 changed files with 1708 additions and 5979 deletions

View File

@ -13,14 +13,13 @@ jobs:
linux:
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v4
- name: Clone spacebar server
run: |
git clone https://github.com/bitfl0wer/server.git
- uses: actions/setup-node@v4
- uses: actions/setup-node@v3
with:
node-version: 18
cache: 'npm'
@ -53,7 +52,7 @@ jobs:
# - name: Clone spacebar server
# run: |
# git clone https://github.com/bitfl0wer/server.git
# - uses: actions/setup-node@v4
# - uses: actions/setup-node@v3
# with:
# node-version: 18
# cache: 'npm'
@ -76,13 +75,12 @@ jobs:
# SAFARIDRIVER=$(which safaridriver) cargo test --target wasm32-unknown-unknown --no-default-features --features="client, rt" --no-fail-fast
wasm-gecko:
runs-on: macos-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v4
- name: Clone spacebar server
run: |
git clone https://github.com/bitfl0wer/server.git
- uses: actions/setup-node@v4
- uses: actions/setup-node@v3
with:
node-version: 18
cache: 'npm'
@ -102,16 +100,15 @@ jobs:
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.88" --force
GECKODRIVER=$(which geckodriver) cargo test --target wasm32-unknown-unknown --no-default-features --features="client, rt, voice_gateway"
GECKODRIVER=$(which geckodriver) cargo test --target wasm32-unknown-unknown --no-default-features --features="client, rt"
wasm-chrome:
runs-on: macos-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v4
- name: Clone spacebar server
run: |
git clone https://github.com/bitfl0wer/server.git
- uses: actions/setup-node@v4
- uses: actions/setup-node@v3
with:
node-version: 18
cache: 'npm'
@ -131,4 +128,4 @@ jobs:
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.88" --force
CHROMEDRIVER=$(which chromedriver) cargo test --target wasm32-unknown-unknown --no-default-features --features="client, rt, voice_gateway"
CHROMEDRIVER=$(which chromedriver) cargo test --target wasm32-unknown-unknown --no-default-features --features="client, rt"

View File

@ -29,9 +29,12 @@ jobs:
uses: actions/checkout@v4
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
components: clippy
override: true
- name: Install required cargo
run: cargo install clippy-sarif sarif-fmt
@ -44,7 +47,7 @@ jobs:
continue-on-error: true
- name: Upload analysis results to GitHub
uses: github/codeql-action/upload-sarif@v3
uses: github/codeql-action/upload-sarif@v2
with:
sarif_file: rust-clippy-results.sarif
wait-for-processing: true

View File

@ -1,10 +0,0 @@
# Contributing
**Please refer to the [contribution guidelines](https://github.com/polyphony-chat/.github/blob/main/CONTRIBUTION_GUIDELINES.md) and [our Code of Conduct](https://github.com/polyphony-chat/.github/blob/main/CODE_OF_CONDUCT.md) before making a contribution.**
Chorus is currently missing voice support and a lot of API endpoints, many of which should be trivial to implement,
ever since [we streamlined the process of doing so](https://github.com/polyphony-chat/chorus/discussions/401).
If you'd like to contribute new functionality, check out [The 'Meta'-issues.](https://github.com/polyphony-chat/chorus/issues?q=is%3Aissue+label%3A%22Type%3A+Meta%22+) They contain a comprehensive list of all features which are yet missing for full Discord.com compatibility.
Please feel free to open an Issue with the idea you have, or a Pull Request.

447
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,14 +1,13 @@
[package]
name = "chorus"
description = "A library for interacting with multiple Spacebar-compatible Instances at once."
version = "0.15.0"
license = "MPL-2.0"
version = "0.13.0"
license = "AGPL-3.0"
edition = "2021"
repository = "https://github.com/polyphony-chat/chorus"
readme = "README.md"
keywords = ["spacebar", "discord", "polyphony"]
website = ["https://discord.com/invite/m3FpcapGDD"]
rust-version = "1.67.1"
[features]
@ -17,34 +16,34 @@ backend = ["dep:poem", "dep:sqlx"]
rt-multi-thread = ["tokio/rt-multi-thread"]
rt = ["tokio/rt"]
client = []
voice = ["voice_udp", "voice_gateway"]
voice_udp = ["dep:discortp", "dep:crypto_secretbox"]
voice_gateway = []
[dependencies]
tokio = { version = "1.35.1", features = ["macros", "sync"] }
serde = { version = "1.0.195", features = ["derive", "rc"] }
serde_json = { version = "1.0.111", features = ["raw_value"] }
serde-aux = "4.3.1"
serde_with = "3.4.0"
serde_repr = "0.1.18"
reqwest = { features = ["multipart", "json"], version = "0.11.23" }
url = "2.5.0"
chrono = { version = "0.4.31", features = ["serde"] }
regex = "1.10.2"
tokio = { version = "1.34.0", features = ["macros", "sync"] }
serde = { version = "1.0.188", features = ["derive", "rc"] }
serde_json = { version = "1.0.105", features = ["raw_value"] }
serde-aux = "4.2.0"
serde_with = "3.3.0"
serde_repr = "0.1.16"
reqwest = { git = "https://github.com/bitfl0wer/reqwest.git", branch = "wasm-headers", features = [
"multipart",
"json",
], version = "0.11.22" } # reqwest versions > 0.11.22 will have adequate support for WASM. Until there is such a version, we will use a fork of reqwest v.0.11.22
url = "2.4.0"
chrono = { version = "0.4.26", features = ["serde"] }
regex = "1.9.4"
custom_error = "1.9.2"
futures-util = "0.3.30"
http = "0.2.11"
base64 = "0.21.7"
bitflags = { version = "2.4.1", features = ["serde"] }
futures-util = "0.3.28"
http = "0.2.9"
base64 = "0.21.3"
bitflags = { version = "2.4.0", features = ["serde"] }
lazy_static = "1.4.0"
poem = { version = "1.3.59", optional = true }
thiserror = "1.0.56"
poem = { version = "1.3.57", optional = true }
thiserror = "1.0.47"
jsonwebtoken = "8.3.0"
log = "0.4.20"
async-trait = "0.1.77"
chorus-macros = "0.3.0"
sqlx = { version = "0.7.3", features = [
async-trait = "0.1.73"
chorus-macros = "0.2.0"
sqlx = { version = "0.7.1", features = [
"mysql",
"sqlite",
"json",
@ -53,12 +52,11 @@ sqlx = { version = "0.7.3", features = [
"runtime-tokio-native-tls",
"any",
], optional = true }
discortp = { version = "0.5.0", optional = true, features = ["rtp", "discord", "demux"] }
crypto_secretbox = { version = "0.1.1", optional = true }
safina-timer = "0.1.11"
rand = "0.8.5"
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
rustls = "0.21.10"
rustls = "0.21.8"
rustls-native-certs = "0.6.3"
tokio-tungstenite = { version = "0.20.1", features = [
"rustls-tls-native-roots",
@ -66,15 +64,13 @@ tokio-tungstenite = { version = "0.20.1", features = [
] }
native-tls = "0.2.11"
hostname = "0.3.1"
getrandom = { version = "0.2.12" }
[target.'cfg(target_arch = "wasm32")'.dependencies]
getrandom = { version = "0.2.12", features = ["js"] }
getrandom = { version = "0.2.11", features = ["js"] }
ws_stream_wasm = "0.7.4"
wasm-bindgen-futures = "0.4.39"
wasmtimer = "0.2.0"
wasm-bindgen-futures = "0.4.38"
[dev-dependencies]
lazy_static = "1.4.0"
wasm-bindgen-test = "0.3.39"
wasm-bindgen = "0.2.89"
wasm-bindgen-test = "0.3.38"
wasm-bindgen = "0.2.88"

1034
LICENSE

File diff suppressed because it is too large Load Diff

View File

@ -44,7 +44,7 @@ To get started with Chorus, import it into your project by adding the following
```toml
[dependencies]
chorus = "0.15.0"
chorus = "0.13.0"
```
### Establishing a Connection
@ -53,10 +53,16 @@ To connect to a Spacebar compatible server, you need to create an [`Instance`](h
```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.
@ -81,7 +87,7 @@ let login_schema = LoginSchema {
password: "Correct-Horse-Battery-Staple".to_string(),
..Default::default()
};
// Each user connects to the Gateway. The Gateway connection lives on a separate thread. Depending on
// Each user connects to the Gateway. The Gateway connection lives on a seperate 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)
@ -119,7 +125,7 @@ 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
@ -128,7 +134,11 @@ This crate uses Semantic Versioning 2.0.0 as its versioning scheme. You can read
## Contributing
See [CONTRIBUTING.md](./CONTRIBUTING.md).
Chorus is currently missing voice support and a lot of API endpoints, many of which should be trivial to implement,
ever since [we streamlined the process of doing so](https://github.com/polyphony-chat/chorus/discussions/401).
If you'd like to contribute new functionality, check out [The 'Meta'-issues.](https://github.com/polyphony-chat/chorus/issues?q=is%3Aissue+label%3A%22Type%3A+Meta%22+) They contain a comprehensive list of all features which are yet missing for full Discord.com compatibility.
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>

View File

@ -15,7 +15,7 @@ dependencies = [
[[package]]
name = "chorus-macros"
version = "0.2.1"
version = "0.1.0"
dependencies = [
"async-trait",
"quote",

View File

@ -1,8 +1,8 @@
[package]
name = "chorus-macros"
version = "0.3.0"
version = "0.2.0"
edition = "2021"
license = "MPL-2.0"
license = "AGPL-3.0"
description = "Macros for the chorus crate."
[lib]

View File

@ -1,23 +1,7 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, Data, DeriveInput, Field, Fields, FieldsNamed};
#[proc_macro_derive(WebSocketEvent)]
pub fn websocket_event_macro_derive(input: TokenStream) -> TokenStream {
let ast: syn::DeriveInput = syn::parse(input).unwrap();
let name = &ast.ident;
quote! {
impl WebSocketEvent for #name {}
}
.into()
}
#[proc_macro_derive(Updateable)]
pub fn updateable_macro_derive(input: TokenStream) -> TokenStream {
let ast: syn::DeriveInput = syn::parse(input).unwrap();

View File

@ -1,16 +1,3 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
// This example showcase how to properly use gateway observers.
//
// To properly run it, you will need to change the token below.
const TOKEN: &str = "";
/// Find the gateway websocket url of the server we want to connect to
const GATEWAY_URL: &str = "wss://gateway.old.server.spacebar.chat/";
use async_trait::async_trait;
use chorus::gateway::Gateway;
use chorus::{
@ -21,11 +8,6 @@ use chorus::{
use std::{sync::Arc, time::Duration};
use tokio::{self};
#[cfg(not(target_arch = "wasm32"))]
use tokio::time::sleep;
#[cfg(target_arch = "wasm32")]
use wasmtimer::tokio::sleep;
// This example creates a simple gateway connection and a basic observer struct
// Due to certain limitations all observers must impl debug
@ -45,10 +27,11 @@ impl Observer<GatewayReady> for ExampleObserver {
#[tokio::main(flavor = "current_thread")]
async fn main() {
let gateway_websocket_url = GATEWAY_URL.to_string();
// Find the gateway websocket url of the server we want to connect to
let websocket_url_spacebar = "wss://gateway.old.server.spacebar.chat/".to_string();
// Initiate the gateway connection
let gateway = Gateway::spawn(gateway_websocket_url).await.unwrap();
let gateway = Gateway::spawn(websocket_url_spacebar).await.unwrap();
// Create an instance of our observer
let observer = ExampleObserver {};
@ -67,13 +50,14 @@ async fn main() {
.subscribe(shared_observer);
// Authenticate so we will receive any events
let token = TOKEN.to_string();
let token = "SecretToken".to_string();
let mut identify = GatewayIdentifyPayload::common();
identify.token = token;
gateway.send_identify(identify).await;
safina_timer::start_timer_thread();
// Do something on the main thread so we don't quit
loop {
sleep(Duration::from_secs(3600)).await;
safina_timer::sleep_for(Duration::MAX).await
}
}

View File

@ -1,39 +1,21 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
// This example showcases how to initiate a gateway connection manually
// (e. g. not through ChorusUser)
//
// To properly run it, you will need to modify the token below.
const TOKEN: &str = "";
/// Find the gateway websocket url of the server we want to connect to
const GATEWAY_URL: &str = "wss://gateway.old.server.spacebar.chat/";
use std::time::Duration;
use chorus::gateway::Gateway;
use chorus::{self, types::GatewayIdentifyPayload};
#[cfg(not(target_arch = "wasm32"))]
use tokio::time::sleep;
#[cfg(target_arch = "wasm32")]
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.to_string();
// Find the gateway websocket url of the server we want to connect to
let websocket_url_spacebar = "wss://gateway.old.server.spacebar.chat/".to_string();
// Initiate the gateway connection, starting a listener in one thread and a heartbeat handler in another
let gateway = Gateway::spawn(gateway_websocket_url).await.unwrap();
let _ = Gateway::spawn(websocket_url_spacebar).await.unwrap();
// At this point, we are connected to the server and are sending heartbeats, however we still haven't authenticated
// Get a token for an account on the server
let token = TOKEN.to_string();
let token = "SecretToken".to_string();
// Create an identify event
// An Identify event is how the server authenticates us and gets info about our os and browser, along with our intents / capabilities
@ -44,10 +26,10 @@ async fn main() {
identify.token = token;
// Send off the event
gateway.send_identify(identify).await;
safina_timer::start_timer_thread();
// Do something on the main thread so we don't quit
loop {
sleep(Duration::from_secs(3600)).await;
safina_timer::sleep_for(Duration::MAX).await
}
}

View File

@ -1,12 +1,14 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
use chorus::instance::Instance;
use chorus::UrlBundle;
#[tokio::main(flavor = "current_thread")]
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");
dbg!(instance.instance_info);

View File

@ -1,13 +1,15 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
use chorus::instance::Instance;
use chorus::types::LoginSchema;
use chorus::UrlBundle;
#[tokio::main(flavor = "current_thread")]
async fn main() {
let mut 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 mut instance = Instance::new(bundle)
.await
.expect("Failed to connect to the Spacebar server");
// Assume, you already have an account created on this instance. Registering an account works
@ -17,7 +19,7 @@ async fn main() {
password: "Correct-Horse-Battery-Staple".to_string(),
..Default::default()
};
// Each user connects to the Gateway. The Gateway connection lives on a separate thread. Depending on
// Each user connects to the Gateway. The Gateway connection lives on a seperate 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)

View File

@ -1,13 +0,0 @@
[package]
name = "voice_simple"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
async-trait = "*"
chorus = { path = "../../", features = ["rt", "client", "voice"] }
tokio = { version = "*", features = ["full"] }
simplelog = "*"
log = "*"

View File

@ -1,311 +0,0 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
// This example showcases how to use the voice udp channel.
//
// To use this to properly communicate with voice, you will need to bring your own opus bindings
// along with potentially sending some other events, like Speaking
//
// To properly run this example, you will need to change some values below,
// like the token, guild and channel ids.
const TOKEN: &str = "";
const VOICE_GUILD_ID: Option<Snowflake> = None;
const VOICE_CHANNEL_ID: Option<Snowflake> = Some(Snowflake(0_u64));
const GATEWAY_URL: &str = "wss://gateway.discord.gg";
use async_trait::async_trait;
use simplelog::{TermLogger, Config, WriteLogger};
use std::{net::SocketAddrV4, sync::Arc, fs::File, time::Duration};
use chorus::{
gateway::{Observer, Gateway},
types::{
GatewayReady, SelectProtocol, SelectProtocolData, SessionDescription, Snowflake, Speaking,
SpeakingBitflags, SsrcDefinition, VoiceEncryptionMode, VoiceIdentify, VoiceProtocol,
VoiceReady, VoiceServerUpdate, GatewayIdentifyPayload, UpdateVoiceState,
},
voice::{
gateway::{VoiceGateway, VoiceGatewayHandle},
udp::{UdpHandle, UdpHandler},
voice_data::VoiceData,
},
};
use log::{info, LevelFilter};
use tokio::sync::{Mutex, RwLock};
extern crate chorus;
extern crate tokio;
/// Handles in between connections between the gateway and UDP modules
#[derive(Debug, Clone)]
pub struct VoiceHandler {
pub voice_gateway_connection: Arc<Mutex<Option<VoiceGatewayHandle>>>,
pub voice_udp_connection: Arc<Mutex<Option<UdpHandle>>>,
pub data: Arc<RwLock<VoiceData>>,
}
impl VoiceHandler {
/// Creates a new [VoiceHandler], only initializing the data
pub fn new() -> VoiceHandler {
Self {
data: Arc::new(RwLock::new(VoiceData::default())),
voice_gateway_connection: Arc::new(Mutex::new(None)),
voice_udp_connection: Arc::new(Mutex::new(None)),
}
}
}
impl Default for VoiceHandler {
fn default() -> Self {
Self::new()
}
}
#[async_trait]
// On [VoiceServerUpdate] we get our starting data and URL for the voice gateway server.
impl Observer<VoiceServerUpdate> for VoiceHandler {
async fn update(&self, data: &VoiceServerUpdate) {
let mut data_lock = self.data.write().await;
data_lock.server_data = Some(data.clone());
let user_id = data_lock.user_id;
let session_id = data_lock.session_id.clone();
drop(data_lock);
// Create and connect to the voice gateway
let voice_gateway_handle = VoiceGateway::spawn(data.endpoint.clone().unwrap())
.await
.unwrap();
let server_id: Snowflake;
if data.guild_id.is_some() {
server_id = data.guild_id.unwrap();
} else {
server_id = data.channel_id.unwrap();
}
let voice_identify = VoiceIdentify {
server_id,
user_id,
session_id,
token: data.token.clone(),
video: Some(false),
};
voice_gateway_handle.send_identify(voice_identify).await;
let cloned_gateway_handle = voice_gateway_handle.clone();
let mut voice_events = cloned_gateway_handle.events.lock().await;
let self_reference = Arc::new(self.clone());
// Subscribe to voice gateway events
voice_events.voice_ready.subscribe(self_reference.clone());
voice_events
.session_description
.subscribe(self_reference.clone());
voice_events.speaking.subscribe(self_reference.clone());
voice_events
.ssrc_definition
.subscribe(self_reference.clone());
*self.voice_gateway_connection.lock().await = Some(voice_gateway_handle);
}
}
#[async_trait]
// On [VoiceReady] we get info for establishing a UDP connection, and we immediately need said UDP
// connection for ip discovery.
impl Observer<VoiceReady> for VoiceHandler {
async fn update(&self, data: &VoiceReady) {
let mut data_lock = self.data.write().await;
data_lock.ready_data = Some(data.clone());
drop(data_lock);
// Create a udp connection and perform ip discovery
let udp_handle = UdpHandler::spawn(
self.data.clone(),
std::net::SocketAddr::V4(SocketAddrV4::new(data.ip, data.port)),
data.ssrc,
)
.await
.unwrap();
// Subscribe ourself to receiving rtp data
udp_handle
.events
.lock()
.await
.rtp
.subscribe(Arc::new(self.clone()));
let ip_discovery = self.data.read().await.ip_discovery.clone().unwrap();
*self.voice_udp_connection.lock().await = Some(udp_handle.clone());
let string_ip_address =
String::from_utf8(ip_discovery.address).expect("Ip discovery gave non string ip");
// Send a select protocol, which tells the server where we'll be receiving data and what
// mode to encrypt data in
self.voice_gateway_connection
.lock()
.await
.clone()
.unwrap()
.send_select_protocol(SelectProtocol {
protocol: VoiceProtocol::Udp,
data: SelectProtocolData {
address: string_ip_address,
port: ip_discovery.port,
// There are several other voice encryption modes available, though not all are
// implemented in chorus
mode: VoiceEncryptionMode::Xsalsa20Poly1305,
},
..Default::default()
})
.await;
}
}
#[async_trait]
// Session descryption gives us final info regarding codecs and our encryption key
impl Observer<SessionDescription> for VoiceHandler {
async fn update(&self, data: &SessionDescription) {
let mut data_write = self.data.write().await;
data_write.session_description = Some(data.clone());
drop(data_write);
}
}
#[async_trait]
// Ready is used just to obtain some info, like the user id and session id
impl Observer<GatewayReady> for VoiceHandler {
async fn update(&self, data: &GatewayReady) {
let mut lock = self.data.write().await;
lock.user_id = data.user.id;
lock.session_id = data.session_id.clone();
drop(lock);
}
}
#[async_trait]
// This is the received voice data
impl Observer<chorus::voice::discortp::rtp::Rtp> for VoiceHandler {
async fn update(&self, data: &chorus::voice::discortp::rtp::Rtp) {
info!(
"Received decrypted voice data! {:?} (SSRC: {})",
data.payload.clone(),
data.ssrc,
);
}
}
#[async_trait]
// This event gives extra info about who is speaking
impl Observer<Speaking> for VoiceHandler {
async fn update(&self, data: &Speaking) {
println!(
"Received Speaking! (SRRC: {}, flags: {:?})",
data.ssrc,
SpeakingBitflags::from_bits(data.speaking).unwrap()
);
}
}
#[async_trait]
// This event gives some info about which user has which ssrc
impl Observer<SsrcDefinition> for VoiceHandler {
async fn update(&self, data: &SsrcDefinition) {
println!(
"Received SSRC Definition! (User {} has audio ssrc {})",
data.user_id.unwrap(),
data.audio_ssrc
);
}
}
#[tokio::main]
async fn main() {
simplelog::CombinedLogger::init(vec![
TermLogger::new(
LevelFilter::Debug,
Config::default(),
simplelog::TerminalMode::Mixed,
simplelog::ColorChoice::Auto,
),
WriteLogger::new(
LevelFilter::Trace,
Config::default(),
File::create("latest.log").unwrap(),
),
])
.unwrap();
let gateway = Gateway::spawn(GATEWAY_URL.to_string())
.await
.unwrap();
let mut identify = GatewayIdentifyPayload::common();
identify.token = TOKEN.to_string();
gateway.send_identify(identify).await;
let voice_handler = Arc::new(VoiceHandler::new());
// Voice handler needs voice server update
gateway
.events
.lock()
.await
.voice
.server_update
.subscribe(voice_handler.clone());
// It also needs a bit of the data in ready
gateway
.events
.lock()
.await
.session
.ready
.subscribe(voice_handler.clone());
// Data which channel to update the local user to be joined into.
//
// guild_id and channel_id can be some to join guild voice channels
//
// guild_id can be none and channel id some to join dm calls
//
// both can be none to leave all voice channels
let voice_state_update = UpdateVoiceState {
guild_id: VOICE_GUILD_ID,
channel_id: VOICE_CHANNEL_ID,
self_mute: false,
self_deaf: false,
..Default::default()
};
gateway.send_update_voice_state(voice_state_update).await;
loop {
tokio::time::sleep(Duration::from_millis(1000)).await;
// Potentially send some data here
/*let voice_udp_option = voice_handler.voice_udp_connection.lock().await.clone();
if voice_udp_option.is_some() {
voice_udp_option.unwrap().send_opus_data(0, vec![1, 2, 3, 4, 5]).await.unwrap();
}*/
}
}

View File

@ -1,7 +1,3 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
use std::sync::{Arc, RwLock};
use reqwest::Client;

View File

@ -1,13 +1,6 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
use std::sync::{Arc, RwLock};
#[allow(unused_imports)]
pub use login::*;
#[allow(unused_imports)]
pub use register::*;
use crate::gateway::Gateway;

View File

@ -1,7 +1,3 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
use std::sync::{Arc, RwLock};
use reqwest::Client;

View File

@ -1,7 +1,3 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
use reqwest::Client;
use serde_json::to_string;

View File

@ -1,7 +1,3 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
use http::header::CONTENT_DISPOSITION;
use http::HeaderMap;
use reqwest::{multipart, Client};
@ -40,7 +36,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 i16);
attachment.get_mut(index).unwrap().set_id(index as i16);
}
let mut form = reqwest::multipart::Form::new();
let payload_json = to_string(&message).unwrap();
@ -49,8 +45,8 @@ impl Message {
form = form.part("payload_json", payload_field);
for (index, attachment) in message.attachments.unwrap().into_iter().enumerate() {
let attachment_content = attachment.content;
let attachment_filename = attachment.filename;
let (attachment_content, current_attachment) = attachment.move_content();
let (attachment_filename, _) = current_attachment.move_filename();
let part_name = format!("files[{}]", index);
let content_disposition = format!(
"form-data; name=\"{}\"'; filename=\"{}\"",

View File

@ -1,8 +1,3 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
#![allow(unused_imports)]
pub use channels::*;
pub use messages::*;
pub use permissions::*;

View File

@ -1,7 +1,3 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
use reqwest::Client;
use serde_json::to_string;

View File

@ -1,7 +1,3 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
use crate::{
errors::ChorusResult,
instance::ChorusUser,

View File

@ -1,7 +1,3 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
use reqwest::Client;
use serde_json::from_str;
use serde_json::to_string;
@ -18,25 +14,6 @@ use crate::types::{
use crate::types::{GuildBan, Snowflake};
impl Guild {
/// Fetches a guild by its id.
///
/// # Reference
/// See <https://discord-userdoccers.vercel.app/resources/guild#get-guild>
pub async fn get(guild_id: Snowflake, user: &mut ChorusUser) -> ChorusResult<Guild> {
let chorus_request = ChorusRequest {
request: Client::new()
.get(format!(
"{}/guilds/{}",
user.belongs_to.read().unwrap().urls.api,
guild_id
))
.header("Authorization", user.token()),
limit_type: LimitType::Guild(guild_id),
};
let response = chorus_request.deserialize_response::<Guild>(user).await?;
Ok(response)
}
/// Creates a new guild.
///
/// # Reference
@ -57,35 +34,6 @@ impl Guild {
chorus_request.deserialize_response::<Guild>(user).await
}
/// Modify a guild's settings.
///
/// Requires the [MANAGE_GUILD](crate::types::PermissionFlags::MANAGE_GUILD) permission.
///
/// Returns the updated guild.
///
/// # Reference
/// <https://discord-userdoccers.vercel.app/resources/guild#modify-guild>
pub async fn modify(
guild_id: Snowflake,
schema: GuildModifySchema,
user: &mut ChorusUser,
) -> ChorusResult<Guild> {
let chorus_request = ChorusRequest {
request: Client::new()
.patch(format!(
"{}/guilds/{}",
user.belongs_to.read().unwrap().urls.api,
guild_id,
))
.header("Authorization", user.token())
.header("Content-Type", "application/json")
.body(to_string(&schema).unwrap()),
limit_type: LimitType::Guild(guild_id),
};
let response = chorus_request.deserialize_response::<Guild>(user).await?;
Ok(response)
}
/// Deletes a guild by its id.
///
/// User must be the owner.
@ -175,9 +123,77 @@ impl Guild {
};
}
/// Fetches a guild preview object for the given guild ID. If the user is not in the guild, the guild must be discoverable.
/// Fetches a guild by its id.
///
/// # Reference
/// See <https://discord-userdoccers.vercel.app/resources/guild#get-guild>
pub async fn get(guild_id: Snowflake, user: &mut ChorusUser) -> ChorusResult<Guild> {
let chorus_request = ChorusRequest {
request: Client::new()
.get(format!(
"{}/guilds/{}",
user.belongs_to.read().unwrap().urls.api,
guild_id
))
.header("Authorization", user.token()),
limit_type: LimitType::Guild(guild_id),
};
let response = chorus_request.deserialize_response::<Guild>(user).await?;
Ok(response)
}
pub async fn create_ban(
guild_id: Snowflake,
user_id: Snowflake,
audit_log_reason: Option<String>,
schema: GuildBanCreateSchema,
user: &mut ChorusUser,
) -> ChorusResult<()> {
// FIXME: Return GuildBan instead of (). Requires <https://github.com/spacebarchat/server/issues/1096> to be resolved.
let request = ChorusRequest::new(
http::Method::PUT,
format!(
"{}/guilds/{}/bans/{}",
user.belongs_to.read().unwrap().urls.api,
guild_id,
user_id
)
.as_str(),
Some(to_string(&schema).unwrap()),
audit_log_reason.as_deref(),
None,
Some(user),
LimitType::Guild(guild_id),
);
request.handle_request_as_result(user).await
}
/// # Reference
/// <https://discord-userdoccers.vercel.app/resources/guild#modify-guild>
pub async fn modify(
guild_id: Snowflake,
schema: GuildModifySchema,
user: &mut ChorusUser,
) -> ChorusResult<Guild> {
let chorus_request = ChorusRequest {
request: Client::new()
.patch(format!(
"{}/guilds/{}",
user.belongs_to.read().unwrap().urls.api,
guild_id,
))
.header("Authorization", user.token())
.header("Content-Type", "application/json")
.body(to_string(&schema).unwrap()),
limit_type: LimitType::Guild(guild_id),
};
let response = chorus_request.deserialize_response::<Guild>(user).await?;
Ok(response)
}
/// Fetches a guild preview object for the given guild ID. If the user is not in the guild, the guild must be discoverable.
/// # Reference:
///
/// See <https://discord-userdoccers.vercel.app/resources/guild#get-guild-preview>
// RAGC: These aren't just getters, they fetch something on the server.
// I am going to excuse these namings, since .preview() indicates we are just returning
@ -257,9 +273,7 @@ impl Guild {
request.deserialize_response::<Vec<GuildMember>>(user).await
}
/// Removes a member from a guild.
///
/// Requires the [KICK_MEMBERS](crate::types::PermissionFlags::KICK_MEMBERS) permission.
/// Removes a member from a guild. Requires the KICK_MEMBERS permission. Returns a 204 empty response on success.
///
/// # Reference
/// See <https://discord-userdoccers.vercel.app/resources/guild#remove-guild-member>
@ -372,9 +386,7 @@ impl Guild {
.await
}
/// Returns a list of ban objects for the guild.
///
/// Requires the [BAN_MEMBERS](crate::types::PermissionFlags::BAN_MEMBERS) permission.
/// Fetches a list of ban objects for the guild. Requires the `BAN_MEMBERS` permission.
///
/// # Reference:
/// See <https://discord-userdoccers.vercel.app/resources/guild#get-guild-bans>
@ -404,9 +416,7 @@ impl Guild {
request.deserialize_response::<Vec<GuildBan>>(user).await
}
/// Returns a ban object for the given user.
///
/// Requires the [BAN_MEMBERS](crate::types::PermissionFlags::BAN_MEMBERS) permission.
/// Fetches a ban object for the given user. Requires the `BAN_MEMBERS` permission.
///
/// # Reference:
/// See <https://discord-userdoccers.vercel.app/resources/guild#get-guild-ban>
@ -434,39 +444,7 @@ impl Guild {
request.deserialize_response::<GuildBan>(user).await
}
/// Creates a ban from the guild.
///
/// Requires the [BAN_MEMBERS](crate::types::PermissionFlags::BAN_MEMBERS) permission.
///
pub async fn create_ban(
guild_id: Snowflake,
user_id: Snowflake,
audit_log_reason: Option<String>,
schema: GuildBanCreateSchema,
user: &mut ChorusUser,
) -> ChorusResult<()> {
// FIXME: Return GuildBan instead of (). Requires <https://github.com/spacebarchat/server/issues/1096> to be resolved.
let request = ChorusRequest::new(
http::Method::PUT,
format!(
"{}/guilds/{}/bans/{}",
user.belongs_to.read().unwrap().urls.api,
guild_id,
user_id
)
.as_str(),
Some(to_string(&schema).unwrap()),
audit_log_reason.as_deref(),
None,
Some(user),
LimitType::Guild(guild_id),
);
request.handle_request_as_result(user).await
}
/// Removes the ban for a user.
///
/// Requires the [BAN_MEMBERS](crate::types::PermissionFlags::BAN_MEMBERS) permission.
/// Removes the ban for a user. Requires the BAN_MEMBERS permissions. Returns a 204 empty response on success.
///
/// # Reference:
/// See <https://discord-userdoccers.vercel.app/resources/guild#delete-guild-ban>

View File

@ -1,7 +1,3 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
use reqwest::Client;
use crate::{

View File

@ -1,7 +1,3 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
use crate::errors::ChorusResult;
use crate::instance::ChorusUser;
use crate::types::{Guild, Message, MessageSearchQuery, Snowflake};
@ -13,7 +9,7 @@ impl Guild {
/// permission to be present on the current user.
///
/// If the guild/channel you are searching is not yet indexed, the endpoint will return a 202 accepted response.
/// In this case, the method will return a [`ChorusError::InvalidResponse`](crate::errors::ChorusError::InvalidResponse) error.
/// In this case, the method will return a [`ChorusError::InvalidResponse`] error.
///
/// # Reference:
/// See <https://discord-userdoccers.vercel.app/resources/message#search-messages>

View File

@ -1,8 +1,3 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
#![allow(unused_imports)]
pub use guilds::*;
pub use messages::*;
pub use roles::*;

View File

@ -1,7 +1,3 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
use reqwest::Client;
use serde_json::to_string;

View File

@ -1,7 +1,3 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
use reqwest::Client;
use serde_json::to_string;

View File

@ -1,10 +1,4 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
//! All of the API's endpoints.
#![allow(unused_imports)]
pub use channels::messages::*;
pub use guilds::*;
pub use invites::*;

View File

@ -1,7 +1,3 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
use serde_json::from_str;
use crate::errors::{ChorusError, ChorusResult};

View File

@ -1,8 +1,3 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
#![allow(unused_imports)]
pub use instance::*;
pub mod instance;

View File

@ -1,5 +1 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
pub mod instance;

View File

@ -1,7 +1,3 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
use reqwest::Client;
use serde_json::to_string;
@ -13,26 +9,6 @@ use crate::{
};
impl ChorusUser {
/// Fetches a list of private channels the user is in.
///
/// # Reference:
/// See <https://docs.discord.sex/resources/channel#get-private-channels>
pub async fn get_private_channels(&mut self) -> ChorusResult<Vec<Channel>> {
let url = format!(
"{}/users/@me/channels",
self.belongs_to.read().unwrap().urls.api
);
ChorusRequest {
request: Client::new()
.get(url)
.header("Authorization", self.token())
.header("Content-Type", "application/json"),
limit_type: LimitType::Global,
}
.deserialize_response::<Vec<Channel>>(self)
.await
}
/// Creates a DM channel or group DM channel.
///
/// One recipient creates or returns an existing DM channel,

View File

@ -1,7 +1,3 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
use reqwest::Client;
use serde_json::to_string;

View File

@ -1,8 +1,3 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
#![allow(unused_imports)]
pub use channels::*;
pub use guilds::*;
pub use relationships::*;

View File

@ -1,7 +1,3 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
use reqwest::Client;
use serde_json::to_string;
@ -19,7 +15,7 @@ impl ChorusUser {
/// Retrieves a list of mutual friends between the authenticated user and a given user.
///
/// # Reference
/// See <https://luna.gitlab.io/discord-unofficial-docs/docs/relationships.html#get-userspeer_idrelationships>
/// See <https://luna.gitlab.io/discord-unofficial-docs/relationships.html#get-users-peer-id-relationships>
pub async fn get_mutual_relationships(
&mut self,
user_id: Snowflake,
@ -41,7 +37,7 @@ impl ChorusUser {
/// Retrieves the user's relationships.
///
/// # Reference
/// See <https://luna.gitlab.io/discord-unofficial-docs/docs/relationships.html#get-usersmerelationships>
/// See <https://luna.gitlab.io/discord-unofficial-docs/relationships.html#get-users-me-relationships>
pub async fn get_relationships(&mut self) -> ChorusResult<Vec<types::Relationship>> {
let url = format!(
"{}/users/@me/relationships",
@ -59,7 +55,7 @@ impl ChorusUser {
/// Sends a friend request to a user.
///
/// # Reference
/// See <https://luna.gitlab.io/discord-unofficial-docs/docs/relationships.html#post-usersmerelationships>
/// See <https://luna.gitlab.io/discord-unofficial-docs/relationships.html#post-users-me-relationships>
pub async fn send_friend_request(
&mut self,
schema: FriendRequestSendSchema,
@ -136,7 +132,7 @@ impl ChorusUser {
/// Removes the relationship between the authenticated user and a given user.
///
/// # Reference
/// See <https://luna.gitlab.io/discord-unofficial-docs/docs/relationships.html#delete-usersmerelationshipspeer_id>
/// See <https://luna.gitlab.io/discord-unofficial-docs/relationships.html#delete-users-me-relationships-peer-id>
pub async fn remove_relationship(&mut self, user_id: Snowflake) -> ChorusResult<()> {
let url = format!(
"{}/users/@me/relationships/{}",

View File

@ -1,7 +1,3 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
use std::sync::{Arc, RwLock};
use reqwest::Client;
@ -117,7 +113,7 @@ impl User {
/// Fetches the user's settings.
///
/// # Reference
/// See <https://luna.gitlab.io/discord-unofficial-docs/docs/user_settings.html#get-usersmesettings>
/// See <https://luna.gitlab.io/discord-unofficial-docs/user_settings.html#get-users-me-settings>
pub async fn get_settings(
token: &String,
url_api: &String,

View File

@ -1,17 +1,12 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
//! Contains all the errors that can be returned by the library.
use custom_error::custom_error;
use crate::types::WebSocketEvent;
use chorus_macros::WebSocketEvent;
custom_error! {
#[derive(PartialEq, Eq, Clone, Hash)]
pub RegistrationError
Consent = "consent must be 'true' to register",
Consent = "Consent must be 'true' to register.",
}
pub type ChorusResult<T> = std::result::Result<T, ChorusError>;
@ -20,34 +15,33 @@ custom_error! {
#[derive(Clone, Hash, PartialEq, Eq)]
pub ChorusError
/// Server did not respond.
NoResponse = "server did not respond",
NoResponse = "Did not receive a response from the Server.",
/// Reqwest returned an Error instead of a Response object.
RequestFailed{url:String, error: String} = "an error occurred while trying to GET from {url}: {error}",
RequestFailed{url:String, error: String} = "An error occured while trying to GET from {url}: {error}",
/// Response received, however, it was not of the successful responses type. Used when no other, special case applies.
ReceivedErrorCode{error_code: u16, error: String} = "received error code while requesting from the route: {error_code}",
ReceivedErrorCode{error_code: u16, error: String} = "Received the following error code while requesting from the route: {error_code}",
/// Used when there is likely something wrong with the instance, the request was directed to.
CantGetInformation{error:String} = "cannot get information about the instance: {error}, something is likely wrong with the instance",
CantGetInformation{error:String} = "Something seems to be wrong with the instance. Cannot get information about the instance: {error}",
/// The requests form body was malformed/invalid.
InvalidFormBody{error_type: String, error:String} = "the server responded with: {error_type}: {error}",
InvalidFormBody{error_type: String, error:String} = "The server responded with: {error_type}: {error}",
/// The request has not been processed by the server due to a relevant rate limit bucket being exhausted.
RateLimited{bucket:String} = "ratelimited on bucket {bucket}",
RateLimited{bucket:String} = "Ratelimited on Bucket {bucket}",
/// The multipart form could not be created.
MultipartCreation{error: String} = "got an error whilst creating the form: {error}",
MultipartCreation{error: String} = "Got an error whilst creating the form: {error}",
/// The regular form could not be created.
FormCreation{error: String} = "got an error whilst creating the form: {error}",
FormCreation{error: String} = "Got an error whilst creating the form: {error}",
/// The token is invalid.
TokenExpired = "token expired, invalid or not found",
TokenExpired = "Token expired, invalid or not found.",
/// No permission
NoPermission = "you lack the permissions needed to perform this action",
NoPermission = "You do not have the permissions needed to perform this action.",
/// Resource not found
NotFound{error: String} = "the provided resource wasn't found: {error}",
NotFound{error: String} = "The provided resource hasn't been found: {error}",
/// Used when you, for example, try to change your spacebar account password without providing your old password for verification.
// RAGC: could this be worded a bit better to be more concise?
PasswordRequired = "you need to provide your current password to authenticate for this action",
PasswordRequired = "You need to provide your current password to authenticate for this action.",
/// Malformed or unexpected response.
InvalidResponse{error: String} = "the response is malformed and cannot be processed: {error}",
InvalidResponse{error: String} = "The response is malformed and cannot be processed. Error: {error}",
/// Invalid, insufficient or too many arguments provided.
InvalidArguments{error: String} = "invalid arguments were provided: {error}"
InvalidArguments{error: String} = "Invalid arguments were provided. Error: {error}"
}
impl From<reqwest::Error> for ChorusError {
@ -63,119 +57,42 @@ impl From<reqwest::Error> for ChorusError {
}
custom_error! {
/// For errors we receive from the gateway, see <https://discord-userdoccers.vercel.app/topics/opcodes-and-status-codes#gateway-close-event-codes>;
#[derive(PartialEq, Eq)]
pub ObserverError
AlreadySubscribed = "Each event can only be subscribed to once."
}
custom_error! {
/// For errors we receive from the gateway, see https://discord-userdoccers.vercel.app/topics/opcodes-and-status-codes#gateway-close-event-codes;
///
/// Supposed to be sent as numbers, though they are sent as string most of the time?
///
/// Also includes errors when initiating a connection and unexpected opcodes
#[derive(PartialEq, Eq, Default, Clone, WebSocketEvent)]
#[derive(PartialEq, Eq, Default, Clone)]
pub GatewayError
// Errors we have received from the gateway
#[default]
/// We're not sure what went wrong. Try reconnecting?
Unknown = "unknown error occurred, try reconnecting",
/// You sent an invalid opcode or an invalid payload for an opcode
UnknownOpcode = "client sent invalid opcode or invalid payload for opcode",
/// Gateway server couldn't decode payload
Decode = "gateway server failed to decode payload",
/// You sent a payload prior to identifying
NotAuthenticated = "client sent payload before identifying",
/// The account token sent with your identify payload is invalid
AuthenticationFailed = "account token in identify is invalid",
/// You've already identified, no need to reauthenticate
AlreadyAuthenticated = "client sent more than one identify payload",
/// The sequence number sent when resuming the session was invalid. Reconnect and start a new session
InvalidSequenceNumber = "sequence number when resuming session was invalid.",
/// You're being rate limited
RateLimited = "you are being rate limited",
/// Your session timed out. Reconnect and start a new one
SessionTimedOut = "session timed out",
/// You sent an invalid shard when identifying
InvalidShard = "invalid shard in identify",
/// The session would have handled too many guilds - you are required to shard your connection in order to connect
ShardingRequired = "sharding is required to connect",
/// You sent an invalid Gateway version
InvalidAPIVersion = "client sent invalid gateway version",
/// You sent an invalid intent
InvalidIntents = "invalid intent",
/// You sent a disallowed intent.
///
/// You may have tried to specify an intent that you have not enabled or are not approved for
DisallowedIntents = "disallowed (not enabled / approved) intent",
Unknown = "We're not sure what went wrong. Try reconnecting?",
UnknownOpcode = "You sent an invalid Gateway opcode or an invalid payload for an opcode",
Decode = "Gateway server couldn't decode payload",
NotAuthenticated = "You sent a payload prior to identifying",
AuthenticationFailed = "The account token sent with your identify payload is invalid",
AlreadyAuthenticated = "You've already identified, no need to reauthenticate",
InvalidSequenceNumber = "The sequence number sent when resuming the session was invalid. Reconnect and start a new session",
RateLimited = "You are being rate limited!",
SessionTimedOut = "Your session timed out. Reconnect and start a new one",
InvalidShard = "You sent us an invalid shard when identifying",
ShardingRequired = "The session would have handled too many guilds - you are required to shard your connection in order to connect",
InvalidAPIVersion = "You sent an invalid Gateway version",
InvalidIntents = "You sent an invalid intent",
DisallowedIntents = "You sent a disallowed intent. You may have tried to specify an intent that you have not enabled or are not approved for",
// Errors when initiating a gateway connection
CannotConnect{error: String} = "encountered a tungstenite error: {error}",
NonHelloOnInitiate{opcode: u8} = "received non hello on initializing connection: {opcode}",
CannotConnect{error: String} = "Cannot connect due to a tungstenite error: {error}",
NonHelloOnInitiate{opcode: u8} = "Received non hello on initial gateway connection ({opcode}), something is definitely wrong",
// Other misc errors
UnexpectedOpcodeReceived{opcode: u8} = "unexpected opcode received: {opcode}",
}
custom_error! {
/// Voice Gateway errors
///
/// Similar to [GatewayError].
///
/// See <https://discord.com/developers/docs/topics/opcodes-and-status-codes#voice-voice-close-event-codes>;
#[derive(Clone, Default, PartialEq, Eq, WebSocketEvent)]
pub VoiceGatewayError
// Errors we receive
#[default]
/// You sent an invalid opcode
UnknownOpcode = "client sent invalid opcode",
/// You sent an invalid payload in your identifying to the (Voice) Gateway
FailedToDecodePayload = "server failed to decode payload while identifying",
/// You sent a payload before identifying with the (Voice) Gateway
NotAuthenticated = "client sent payload before identifying",
/// The token you sent in your identify payload is incorrect
AuthenticationFailed = "account token in identify is invalid",
/// You sent more than one identify payload
AlreadyAuthenticated = "client sent more than one identify payload",
/// Your session is no longer valid
SessionNoLongerValid = "session no longer valid",
/// Your session has timed out
SessionTimeout = "session timed out",
/// Can't find the desired server to connect to
ServerNotFound = "desired server not found",
/// The server didn't recognize the protocol you sent
UnknownProtocol = "unrecognized or unknown protocol",
/// Channel was deleted, you were kicked, voice server changed, or the main gateway session
/// closed.
///
/// Should not attempt to reconnect.
Disconnected = "disconnected from voice",
/// The server crashed, try resuming
VoiceServerCrashed = "the voice server crashed",
/// Server failed to decrypt data
UnknownEncryptionMode = "server failed to decrypt / unknown encryption mode",
// Errors when initiating a gateway connection
CannotConnect{error: String} = "encountered a tungstenite error: {error}",
NonHelloOnInitiate{opcode: u8} = "received non hello on initializing connection: {opcode}",
// Other misc errors
UnexpectedOpcodeReceived{opcode: u8} = "unexpected opcode received: {opcode}",
}
custom_error! {
/// Voice UDP errors.
#[derive(Clone, PartialEq, Eq, WebSocketEvent)]
pub VoiceUdpError
// General errors
BrokenSocket{error: String} = "Could not write / read from UDP socket: {error}",
/// We have not yet received the necessary data to perform this operation.
NoData = "required data not yet received",
// Encryption errors
EncryptionModeNotImplemented{encryption_mode: String} = "voice encryption mode {encryption_mode} is not yet implemented",
NoKey = "could not encrypt / decrypt data: no key received yet",
FailedEncryption = "failed to encrypt data (most likely this is an issue in chorus' nonce generation, please open an issue)",
FailedDecryption = "failed to decrypt data (most likely this is an issue in chorus' nonce generation, please open an issue)",
FailedNonceGeneration{error: String} = "failed to generate nonce: {error}.",
// Errors when initiating a socket connection
CannotBind{error: String} = "failed to bind UDP socket: {error}",
CannotConnect{error: String} = "failed to open UDP connection: {error}",
UnexpectedOpcodeReceived{opcode: u8} = "Received an opcode we weren't expecting to receive: {opcode}",
}
impl WebSocketEvent for GatewayError {}

View File

@ -1,7 +1,3 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
#[cfg(all(not(target_arch = "wasm32"), feature = "client"))]
pub mod tungstenite;
#[cfg(all(not(target_arch = "wasm32"), feature = "client"))]

View File

@ -1,7 +1,3 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
use futures_util::{
stream::{SplitSink, SplitStream},
StreamExt,
@ -27,16 +23,8 @@ impl TungsteniteBackend {
websocket_url: &str,
) -> Result<(TungsteniteSink, TungsteniteStream), crate::errors::GatewayError> {
let mut roots = rustls::RootCertStore::empty();
let certs = rustls_native_certs::load_native_certs();
if let Err(e) = certs {
log::error!("Failed to load platform native certs! {:?}", e);
return Err(GatewayError::CannotConnect {
error: format!("{:?}", e),
});
}
for cert in certs.unwrap() {
for cert in rustls_native_certs::load_native_certs().expect("could not load platform certs")
{
roots.add(&rustls::Certificate(cert.0)).unwrap();
}
let (websocket_stream, _) = match connect_async_tls_with_config(

View File

@ -1,7 +1,3 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
use futures_util::{
stream::{SplitSink, SplitStream},
StreamExt,

View File

@ -1,7 +1,3 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
use super::*;
use crate::types;
@ -46,8 +42,6 @@ pub struct Session {
pub ready: GatewayEvent<types::GatewayReady>,
pub ready_supplemental: GatewayEvent<types::GatewayReadySupplemental>,
pub replace: GatewayEvent<types::SessionsReplace>,
pub reconnect: GatewayEvent<types::GatewayReconnect>,
pub invalid: GatewayEvent<types::GatewayInvalidSession>,
}
#[derive(Default, Debug)]

View File

@ -1,7 +1,3 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
use std::time::Duration;
use futures_util::{SinkExt, StreamExt};
@ -9,14 +5,13 @@ use log::*;
#[cfg(not(target_arch = "wasm32"))]
use tokio::task;
use super::events::Events;
use self::event::Events;
use super::*;
use super::{Sink, Stream};
use crate::types::{
self, AutoModerationRule, AutoModerationRuleUpdate, Channel, ChannelCreate, ChannelDelete,
ChannelUpdate, GatewayInvalidSession, GatewayReconnect, Guild, GuildRoleCreate,
GuildRoleUpdate, JsonField, RoleObject, SourceUrlField, ThreadUpdate, UpdateMessage,
WebSocketEvent,
ChannelUpdate, Guild, GuildRoleCreate, GuildRoleUpdate, JsonField, RoleObject, SourceUrlField,
ThreadUpdate, UpdateMessage, WebSocketEvent,
};
#[derive(Debug)]
@ -26,7 +21,6 @@ pub struct Gateway {
websocket_send: Arc<Mutex<Sink>>,
websocket_receive: Stream,
kill_send: tokio::sync::broadcast::Sender<()>,
kill_receive: tokio::sync::broadcast::Receiver<()>,
store: Arc<Mutex<HashMap<Snowflake, Arc<RwLock<ObservableObject>>>>>,
url: String,
}
@ -76,7 +70,6 @@ impl Gateway {
websocket_send: shared_websocket_send.clone(),
websocket_receive,
kill_send: kill_send.clone(),
kill_receive: kill_send.subscribe(),
store: store.clone(),
url: websocket_url.clone(),
};
@ -101,21 +94,14 @@ impl Gateway {
}
/// The main gateway listener task;
///
/// Can only be stopped by closing the websocket, cannot be made to listen for kill
pub async fn gateway_listen_task(&mut self) {
loop {
let msg;
tokio::select! {
Ok(_) = self.kill_receive.recv() => {
log::trace!("GW: Closing listener task");
break;
}
message = self.websocket_receive.next() => {
msg = message;
}
}
let msg = self.websocket_receive.next().await;
// PRETTYFYME: Remove inline conditional compiling
// This if chain can be much better but if let is unstable on stable rust
#[cfg(not(target_arch = "wasm32"))]
if let Some(Ok(message)) = msg {
self.handle_message(message.into()).await;
@ -349,42 +335,10 @@ impl Gateway {
.unwrap();
}
GATEWAY_RECONNECT => {
trace!("GW: Received Reconnect");
let reconnect = GatewayReconnect {};
self.events
.lock()
.await
.session
.reconnect
.notify(reconnect)
.await;
todo!()
}
GATEWAY_INVALID_SESSION => {
trace!("GW: Received Invalid Session");
let mut resumable: bool = false;
if let Some(raw_value) = gateway_payload.event_data {
if let Ok(deserialized) = serde_json::from_str(raw_value.get()) {
resumable = deserialized;
} else {
warn!("Failed to parse part of INVALID_SESSION ('{}' as bool), assuming non-resumable", raw_value.get());
}
} else {
warn!("Failed to parse part of INVALID_SESSION ('d' missing), assuming non-resumable");
}
let invalid_session = GatewayInvalidSession { resumable };
self.events
.lock()
.await
.session
.invalid
.notify(invalid_session)
.await;
todo!()
}
// Starts our heartbeat
// We should have already handled this in gateway init
@ -440,3 +394,165 @@ impl Gateway {
}
}
}
pub mod event {
use super::*;
#[derive(Default, Debug)]
pub struct Events {
pub application: Application,
pub auto_moderation: AutoModeration,
pub session: Session,
pub message: Message,
pub user: User,
pub relationship: Relationship,
pub channel: Channel,
pub thread: Thread,
pub guild: Guild,
pub invite: Invite,
pub integration: Integration,
pub interaction: Interaction,
pub stage_instance: StageInstance,
pub call: Call,
pub voice: Voice,
pub webhooks: Webhooks,
pub gateway_identify_payload: GatewayEvent<types::GatewayIdentifyPayload>,
pub gateway_resume: GatewayEvent<types::GatewayResume>,
pub error: GatewayEvent<GatewayError>,
}
#[derive(Default, Debug)]
pub struct Application {
pub command_permissions_update: GatewayEvent<types::ApplicationCommandPermissionsUpdate>,
}
#[derive(Default, Debug)]
pub struct AutoModeration {
pub rule_create: GatewayEvent<types::AutoModerationRuleCreate>,
pub rule_update: GatewayEvent<types::AutoModerationRuleUpdate>,
pub rule_delete: GatewayEvent<types::AutoModerationRuleDelete>,
pub action_execution: GatewayEvent<types::AutoModerationActionExecution>,
}
#[derive(Default, Debug)]
pub struct Session {
pub ready: GatewayEvent<types::GatewayReady>,
pub ready_supplemental: GatewayEvent<types::GatewayReadySupplemental>,
pub replace: GatewayEvent<types::SessionsReplace>,
}
#[derive(Default, Debug)]
pub struct StageInstance {
pub create: GatewayEvent<types::StageInstanceCreate>,
pub update: GatewayEvent<types::StageInstanceUpdate>,
pub delete: GatewayEvent<types::StageInstanceDelete>,
}
#[derive(Default, Debug)]
pub struct Message {
pub create: GatewayEvent<types::MessageCreate>,
pub update: GatewayEvent<types::MessageUpdate>,
pub delete: GatewayEvent<types::MessageDelete>,
pub delete_bulk: GatewayEvent<types::MessageDeleteBulk>,
pub reaction_add: GatewayEvent<types::MessageReactionAdd>,
pub reaction_remove: GatewayEvent<types::MessageReactionRemove>,
pub reaction_remove_all: GatewayEvent<types::MessageReactionRemoveAll>,
pub reaction_remove_emoji: GatewayEvent<types::MessageReactionRemoveEmoji>,
pub ack: GatewayEvent<types::MessageACK>,
}
#[derive(Default, Debug)]
pub struct User {
pub update: GatewayEvent<types::UserUpdate>,
pub guild_settings_update: GatewayEvent<types::UserGuildSettingsUpdate>,
pub presence_update: GatewayEvent<types::PresenceUpdate>,
pub typing_start: GatewayEvent<types::TypingStartEvent>,
}
#[derive(Default, Debug)]
pub struct Relationship {
pub add: GatewayEvent<types::RelationshipAdd>,
pub remove: GatewayEvent<types::RelationshipRemove>,
}
#[derive(Default, Debug)]
pub struct Channel {
pub create: GatewayEvent<types::ChannelCreate>,
pub update: GatewayEvent<types::ChannelUpdate>,
pub unread_update: GatewayEvent<types::ChannelUnreadUpdate>,
pub delete: GatewayEvent<types::ChannelDelete>,
pub pins_update: GatewayEvent<types::ChannelPinsUpdate>,
}
#[derive(Default, Debug)]
pub struct Thread {
pub create: GatewayEvent<types::ThreadCreate>,
pub update: GatewayEvent<types::ThreadUpdate>,
pub delete: GatewayEvent<types::ThreadDelete>,
pub list_sync: GatewayEvent<types::ThreadListSync>,
pub member_update: GatewayEvent<types::ThreadMemberUpdate>,
pub members_update: GatewayEvent<types::ThreadMembersUpdate>,
}
#[derive(Default, Debug)]
pub struct Guild {
pub create: GatewayEvent<types::GuildCreate>,
pub update: GatewayEvent<types::GuildUpdate>,
pub delete: GatewayEvent<types::GuildDelete>,
pub audit_log_entry_create: GatewayEvent<types::GuildAuditLogEntryCreate>,
pub ban_add: GatewayEvent<types::GuildBanAdd>,
pub ban_remove: GatewayEvent<types::GuildBanRemove>,
pub emojis_update: GatewayEvent<types::GuildEmojisUpdate>,
pub stickers_update: GatewayEvent<types::GuildStickersUpdate>,
pub integrations_update: GatewayEvent<types::GuildIntegrationsUpdate>,
pub member_add: GatewayEvent<types::GuildMemberAdd>,
pub member_remove: GatewayEvent<types::GuildMemberRemove>,
pub member_update: GatewayEvent<types::GuildMemberUpdate>,
pub members_chunk: GatewayEvent<types::GuildMembersChunk>,
pub role_create: GatewayEvent<types::GuildRoleCreate>,
pub role_update: GatewayEvent<types::GuildRoleUpdate>,
pub role_delete: GatewayEvent<types::GuildRoleDelete>,
pub role_scheduled_event_create: GatewayEvent<types::GuildScheduledEventCreate>,
pub role_scheduled_event_update: GatewayEvent<types::GuildScheduledEventUpdate>,
pub role_scheduled_event_delete: GatewayEvent<types::GuildScheduledEventDelete>,
pub role_scheduled_event_user_add: GatewayEvent<types::GuildScheduledEventUserAdd>,
pub role_scheduled_event_user_remove: GatewayEvent<types::GuildScheduledEventUserRemove>,
pub passive_update_v1: GatewayEvent<types::PassiveUpdateV1>,
}
#[derive(Default, Debug)]
pub struct Invite {
pub create: GatewayEvent<types::InviteCreate>,
pub delete: GatewayEvent<types::InviteDelete>,
}
#[derive(Default, Debug)]
pub struct Integration {
pub create: GatewayEvent<types::IntegrationCreate>,
pub update: GatewayEvent<types::IntegrationUpdate>,
pub delete: GatewayEvent<types::IntegrationDelete>,
}
#[derive(Default, Debug)]
pub struct Interaction {
pub create: GatewayEvent<types::InteractionCreate>,
}
#[derive(Default, Debug)]
pub struct Call {
pub create: GatewayEvent<types::CallCreate>,
pub update: GatewayEvent<types::CallUpdate>,
pub delete: GatewayEvent<types::CallDelete>,
}
#[derive(Default, Debug)]
pub struct Voice {
pub state_update: GatewayEvent<types::VoiceStateUpdate>,
pub server_update: GatewayEvent<types::VoiceServerUpdate>,
}
#[derive(Default, Debug)]
pub struct Webhooks {
pub update: GatewayEvent<types::WebhooksUpdate>,
}
}

View File

@ -1,13 +1,9 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
use futures_util::SinkExt;
use log::*;
use std::fmt::Debug;
use super::{events::Events, *};
use super::{event::Events, *};
use crate::types::{self, Composite};
/// Represents a handle to a Gateway connection. A Gateway connection will create observable
@ -44,19 +40,10 @@ impl GatewayHandle {
.unwrap();
}
/// Recursively observes a [`Shared`] object, by making sure all [`Composite `] fields within
/// that object and its children are being watched.
///
/// Observing means, that if new information arrives about the observed object or its children,
/// the object automatically gets updated, without you needing to request new information about
/// the object in question from the API, which is expensive and can lead to rate limiting.
///
/// The [`Shared`] object returned by this method points to a different object than the one
/// being supplied as a &self function argument.
pub async fn observe<T: Updateable + Clone + Debug + Composite<T>>(
&self,
object: Shared<T>,
) -> Shared<T> {
object: Arc<RwLock<T>>,
) -> Arc<RwLock<T>> {
let mut store = self.store.lock().await;
let id = object.read().unwrap().id();
if let Some(channel) = store.get(&id) {
@ -97,7 +84,7 @@ impl GatewayHandle {
/// with all of its observable fields being observed.
pub async fn observe_and_into_inner<T: Updateable + Clone + Debug + Composite<T>>(
&self,
object: Shared<T>,
object: Arc<RwLock<T>>,
) -> T {
let channel = self.observe(object.clone()).await;
let object = channel.read().unwrap().clone();
@ -173,7 +160,7 @@ impl GatewayHandle {
/// Closes the websocket connection and stops all gateway tasks;
///
/// Essentially pulls the plug on the gateway, leaving it possible to resume;
/// Esentially pulls the plug on the gateway, leaving it possible to resume;
pub async fn close(&self) {
self.kill_send.send(()).unwrap();
self.websocket_send.lock().await.close().await.unwrap();

View File

@ -1,24 +1,9 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
use futures_util::SinkExt;
use log::*;
#[cfg(not(target_arch = "wasm32"))]
use tokio::time::Instant;
#[cfg(target_arch = "wasm32")]
use wasmtimer::std::Instant;
#[cfg(not(target_arch = "wasm32"))]
use tokio::time::sleep_until;
#[cfg(target_arch = "wasm32")]
use wasmtimer::tokio::sleep_until;
use std::time::Duration;
use std::time::{self, Duration, Instant};
use tokio::sync::mpsc::{Receiver, Sender};
use safina_timer::sleep_until;
#[cfg(not(target_arch = "wasm32"))]
use tokio::task;
@ -26,7 +11,7 @@ use super::*;
use crate::types;
/// The amount of time we wait for a heartbeat ack before resending our heartbeat in ms
pub const HEARTBEAT_ACK_TIMEOUT: u64 = 2000;
const HEARTBEAT_ACK_TIMEOUT: u64 = 2000;
/// Handles sending heartbeats to the gateway in another thread
#[allow(dead_code)] // FIXME: Remove this, once HeartbeatHandler is used
@ -72,11 +57,18 @@ impl HeartbeatHandler {
mut receive: Receiver<HeartbeatThreadCommunication>,
mut kill_receive: tokio::sync::broadcast::Receiver<()>,
) {
let mut last_heartbeat_timestamp: Instant = Instant::now();
let mut last_heartbeat_timestamp: Instant = time::Instant::now();
let mut last_heartbeat_acknowledged = true;
let mut last_seq_number: Option<u64> = None;
safina_timer::start_timer_thread();
loop {
if kill_receive.try_recv().is_ok() {
trace!("GW: Closing heartbeat task");
break;
}
let timeout = if last_heartbeat_acknowledged {
heartbeat_interval
} else {
@ -110,10 +102,6 @@ impl HeartbeatHandler {
}
}
}
Ok(_) = kill_receive.recv() => {
log::trace!("GW: Closing heartbeat task");
break;
}
}
if should_send {
@ -131,11 +119,11 @@ impl HeartbeatHandler {
let send_result = websocket_tx.lock().await.send(msg.into()).await;
if send_result.is_err() {
// We couldn't send, the websocket is broken
warn!("GW: Couldn't send heartbeat, websocket seems broken");
warn!("GW: Couldnt send heartbeat, websocket seems broken");
break;
}
last_heartbeat_timestamp = Instant::now();
last_heartbeat_timestamp = time::Instant::now();
last_heartbeat_acknowledged = false;
}
}

View File

@ -1,19 +1,15 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
use crate::types;
use super::*;
/// Represents a message received from the gateway. This will be either a [types::GatewayReceivePayload], containing events, or a [GatewayError].
/// Represents a messsage received from the gateway. This will be either a [types::GatewayReceivePayload], containing events, or a [GatewayError].
/// This struct is used internally when handling messages.
#[derive(Clone, Debug)]
pub struct GatewayMessage(pub String);
impl GatewayMessage {
/// Parses the message as an error;
/// Returns the error if successfully parsed, None if the message isn't an error
/// Returns the error if succesfully parsed, None if the message isn't an error
pub fn error(&self) -> Option<GatewayError> {
// Some error strings have dots on the end, which we don't care about
let processed_content = self.0.to_lowercase().replace('.', "");

View File

@ -1,7 +1,3 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
use async_trait::async_trait;
pub mod backends;
@ -98,12 +94,6 @@ pub struct GatewayEvent<T: WebSocketEvent> {
}
impl<T: WebSocketEvent> GatewayEvent<T> {
pub fn new() -> Self {
Self {
observers: Vec::new(),
}
}
/// Returns true if the GatewayEvent is observed by at least one Observer.
pub fn is_observed(&self) -> bool {
!self.observers.is_empty()
@ -126,17 +116,9 @@ impl<T: WebSocketEvent> GatewayEvent<T> {
}
/// Notifies the observers of the GatewayEvent.
pub(crate) async fn notify(&self, new_event_data: T) {
async fn notify(&self, new_event_data: T) {
for observer in &self.observers {
observer.update(&new_event_data).await;
}
}
}
/// A type alias for [`Arc<RwLock<T>>`], used to make the public facing API concerned with
/// Composite structs more ergonomic.
/// ## Note
///
/// While `T` does not have to implement `Composite` to be used with `Shared`,
/// the primary use of `Shared` is with types that implement `Composite`.
pub type Shared<T> = Arc<RwLock<T>>;

View File

@ -1,7 +1,3 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
//! Instance and ChorusUser objects.
use std::collections::HashMap;
@ -13,7 +9,7 @@ use reqwest::Client;
use serde::{Deserialize, Serialize};
use crate::errors::ChorusResult;
use crate::gateway::{Gateway, GatewayHandle, Shared};
use crate::gateway::{Gateway, GatewayHandle};
use crate::ratelimiter::ChorusRequest;
use crate::types::types::subconfigs::limits::rates::RateLimits;
use crate::types::{
@ -23,7 +19,6 @@ use crate::UrlBundle;
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
/// The [`Instance`]; what you will be using to perform all sorts of actions on the Spacebar server.
///
/// If `limits_information` is `None`, then the instance will not be rate limited.
pub struct Instance {
pub urls: UrlBundle,
@ -41,6 +36,8 @@ impl PartialEq for Instance {
}
}
impl Eq for Instance {}
impl std::hash::Hash for Instance {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.urls.hash(state);
@ -95,17 +92,8 @@ impl PartialEq for LimitsInformation {
}
impl Instance {
pub(crate) fn clone_limits_if_some(&self) -> Option<HashMap<LimitType, Limit>> {
if self.limits_information.is_some() {
return Some(self.limits_information.as_ref().unwrap().ratelimits.clone());
}
None
}
/// Creates a new [`Instance`] from the [relevant instance urls](UrlBundle).
///
/// To create an Instance from one singular url, use [`Instance::new()`].
pub async fn from_url_bundle(urls: UrlBundle) -> ChorusResult<Instance> {
/// Creates a new [`Instance`] from the [relevant instance urls](UrlBundle). To create an Instance from one singular url, use [`Instance::from_root_url()`].
pub async fn new(urls: UrlBundle) -> ChorusResult<Instance> {
let is_limited: Option<LimitsConfiguration> = Instance::is_limited(&urls.api).await?;
let limit_information;
@ -135,12 +123,21 @@ impl Instance {
Ok(instance)
}
pub(crate) fn clone_limits_if_some(&self) -> Option<HashMap<LimitType, Limit>> {
if self.limits_information.is_some() {
return Some(self.limits_information.as_ref().unwrap().ratelimits.clone());
}
None
}
/// Creates a new [`Instance`] by trying to get the [relevant instance urls](UrlBundle) from a root url.
///
/// Shorthand for `Instance::from_url_bundle(UrlBundle::from_root_domain(root_domain).await?)`.
pub async fn new(root_url: &str) -> ChorusResult<Instance> {
/// Shorthand for `Instance::new(UrlBundle::from_root_domain(root_domain).await?)`.
// RAGC: Can we really call this a conversion?
// Would with_root_url be better? Not really I think, because with is for more details
// Where are this is with.. less? (or rather with other ones)
pub async fn from_root_url(root_url: &str) -> ChorusResult<Instance> {
let urls = UrlBundle::from_root_url(root_url).await?;
Instance::from_url_bundle(urls).await
Instance::new(urls).await
}
pub async fn is_limited(api_url: &str) -> ChorusResult<Option<LimitsConfiguration>> {
@ -177,11 +174,11 @@ impl fmt::Display for Token {
/// It is used for most authenticated actions on a Spacebar server.
/// It also has its own [Gateway] connection.
pub struct ChorusUser {
pub belongs_to: Shared<Instance>,
pub belongs_to: Arc<RwLock<Instance>>,
pub token: String,
pub limits: Option<HashMap<LimitType, Limit>>,
pub settings: Shared<UserSettings>,
pub object: Shared<User>,
pub settings: Arc<RwLock<UserSettings>>,
pub object: Arc<RwLock<User>>,
pub gateway: GatewayHandle,
}
@ -236,14 +233,14 @@ impl ChorusUser {
/// Creates a new [ChorusUser] from existing data.
///
/// # Notes
/// This isn't the preferred way to create a ChorusUser.
/// This isn't the prefered way to create a ChorusUser.
/// See [Instance::login_account] and [Instance::register_account] instead.
pub fn new(
belongs_to: Shared<Instance>,
belongs_to: Arc<RwLock<Instance>>,
token: String,
limits: Option<HashMap<LimitType, Limit>>,
settings: Shared<UserSettings>,
object: Shared<User>,
settings: Arc<RwLock<UserSettings>>,
object: Arc<RwLock<User>>,
gateway: GatewayHandle,
) -> ChorusUser {
ChorusUser {
@ -261,7 +258,7 @@ 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: String) -> ChorusUser {
pub(crate) async fn shell(instance: Arc<RwLock<Instance>>, token: String) -> ChorusUser {
let settings = Arc::new(RwLock::new(UserSettings::default()));
let object = Arc::new(RwLock::new(User::default()));
let wss_url = instance.read().unwrap().urls.wss.clone();

View File

@ -1,7 +1,3 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
/*!
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
@ -48,7 +44,7 @@ let login_schema = LoginSchema {
password: "Correct-Horse-Battery-Staple".to_string(),
..Default::default()
};
// Each user connects to the Gateway. The Gateway connection lives on a separate thread. Depending on
// Each user connects to the Gateway. The Gateway connection lives on a seperate 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)
@ -132,10 +128,7 @@ pub mod instance;
#[cfg(feature = "client")]
pub mod ratelimiter;
pub mod types;
#[cfg(all(
feature = "client",
any(feature = "voice_udp", feature = "voice_gateway")
))]
#[cfg(feature = "client")]
pub mod voice;
#[derive(Clone, Default, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
@ -144,12 +137,6 @@ pub mod voice;
/// # Notes
/// All the urls can be found on the /api/policies/instance/domains endpoint of a spacebar server
pub struct UrlBundle {
/// The root url of an Instance. Usually, this would be the url where `.well-known/spacebar` can
/// be located under. If the instance you are connecting to for some reason does not have a
/// `.well-known` set up (for example, if it is a local/testing instance), you can use the api
/// url as a substitute.
/// Ex: `https://spacebar.chat`
pub root: String,
/// The api's url.
/// Ex: `https://old.server.spacebar.chat/api`
pub api: String,
@ -164,9 +151,8 @@ pub struct UrlBundle {
impl UrlBundle {
/// Creates a new UrlBundle from the relevant urls.
pub fn new(root: String, api: String, wss: String, cdn: String) -> Self {
pub fn new(api: String, wss: String, cdn: String) -> Self {
Self {
root: UrlBundle::parse_url(root),
api: UrlBundle::parse_url(api),
wss: UrlBundle::parse_url(wss),
cdn: UrlBundle::parse_url(cdn),
@ -251,12 +237,7 @@ impl UrlBundle {
.json::<types::types::domains_configuration::Domains>()
.await
{
Ok(UrlBundle::new(
url.to_string(),
body.api_endpoint,
body.gateway,
body.cdn,
))
Ok(UrlBundle::new(body.api_endpoint, body.gateway, body.cdn))
} else {
Err(ChorusError::RequestFailed {
url: url.to_string(),

View File

@ -1,7 +1,3 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
//! Ratelimiter and request handling functionality.
use std::collections::HashMap;

View File

@ -1,7 +1,3 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
use serde::{Deserialize, Serialize};
use serde_json::{Map, Value};

View File

@ -1,7 +1,3 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]

View File

@ -1,7 +1,3 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]

View File

@ -1,7 +1,3 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
use serde::{Deserialize, Serialize};
use crate::types::config::types::subconfigs::defaults::{guild::GuildDefaults, user::UserDefaults};

View File

@ -1,7 +1,3 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
use serde::{Deserialize, Serialize};
#[derive(Deserialize, Serialize, Eq, PartialEq, Hash, Clone, Debug)]

View File

@ -1,7 +1,3 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
use serde::{Deserialize, Serialize};
use crate::types::config::types::subconfigs::email::{

View File

@ -1,7 +1,3 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]

View File

@ -1,7 +1,3 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]

View File

@ -1,7 +1,3 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
use serde::{Deserialize, Serialize};
use crate::types::utils::Snowflake;

View File

@ -1,7 +1,3 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]

View File

@ -1,7 +1,3 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
use std::fmt::{Display, Formatter};
#[cfg(feature = "sqlx")]
use std::io::Write;

View File

@ -1,7 +1,3 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
use serde::{Deserialize, Serialize};
use crate::types::config::types::subconfigs::kafka::KafkaBroker;

View File

@ -1,7 +1,3 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
use serde::{Deserialize, Serialize};
use crate::types::config::types::subconfigs::limits::{

View File

@ -1,7 +1,3 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]

View File

@ -1,7 +1,3 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]

View File

@ -1,7 +1,3 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
pub mod api_configuration;
pub mod cdn_configuration;
pub mod defaults_configuration;

View File

@ -1,7 +1,3 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]

View File

@ -1,7 +1,3 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]

View File

@ -1,7 +1,3 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
use serde::{Deserialize, Serialize};
use crate::types::config::types::subconfigs::region::Region;

View File

@ -1,7 +1,3 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
use serde::{Deserialize, Serialize};
use crate::types::config::types::subconfigs::register::{

View File

@ -1,7 +1,3 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
use base64::Engine;
use rand::Fill;
use serde::{Deserialize, Serialize};

View File

@ -1,7 +1,3 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
use std::ffi::OsString;
use serde::{Deserialize, Serialize};

View File

@ -1,7 +1,3 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
use serde::{Deserialize, Serialize};
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]

View File

@ -1,7 +1,3 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
use serde::{Deserialize, Serialize};
use crate::types::{ExplicitContentFilterLevel, MessageNotificationLevel};

View File

@ -1,6 +1,2 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
pub mod guild;
pub mod user;

View File

@ -1,7 +1,3 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]

View File

@ -1,7 +1,3 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]

View File

@ -1,7 +1,3 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]

View File

@ -1,7 +1,3 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
pub mod mailgun;
pub mod mailjet;
pub mod sendgrid;

View File

@ -1,7 +1,3 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]

View File

@ -1,7 +1,3 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]

View File

@ -1,7 +1,3 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
use serde::{Deserialize, Serialize};
use crate::types::utils::Snowflake;

View File

@ -1,7 +1,3 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]

View File

@ -1,6 +1,2 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
pub mod autojoin;
pub mod discovery;

View File

@ -1,7 +1,3 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]

View File

@ -1,7 +1,3 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]

View File

@ -1,7 +1,3 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]

View File

@ -1,7 +1,3 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]

View File

@ -1,7 +1,3 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]

View File

@ -1,7 +1,3 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
pub mod channel;
pub mod global;
pub mod guild;

View File

@ -1,7 +1,3 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
use serde::{Deserialize, Serialize};
use crate::types::config::types::subconfigs::limits::ratelimits::RateLimitOptions;

View File

@ -1,7 +1,3 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
use serde::{Deserialize, Serialize};
pub mod auth;

View File

@ -1,7 +1,3 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
use serde::{Deserialize, Serialize};
use crate::types::config::types::subconfigs::limits::ratelimits::{

View File

@ -1,7 +1,3 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
use std::collections::HashMap;
use serde::{Deserialize, Serialize};

View File

@ -1,7 +1,3 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]

View File

@ -1,7 +1,3 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
pub mod client;
pub mod defaults;
pub mod email;

View File

@ -1,7 +1,3 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]

View File

@ -1,7 +1,3 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]

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