Compare commits
8 Commits
f90fc51265
...
e24816f500
Author | SHA1 | Date |
---|---|---|
xystrive | e24816f500 | |
xystrive | 317dbe1ed1 | |
xystrive | ee19cb762f | |
xystrive | c3c506bc1b | |
kozabrada123 | 743f106ec6 | |
Quat3rnion | d59161619c | |
Quat3rnion | 39e7f89c78 | |
kozabrada123 | 89333d6353 |
|
@ -124,7 +124,7 @@ jobs:
|
|||
cargo binstall --no-confirm wasm-bindgen-cli --version "0.2.92" --force
|
||||
GECKODRIVER=$(which geckodriver) cargo test --target wasm32-unknown-unknown --no-default-features --features="client, rt, voice_gateway"
|
||||
wasm-chrome:
|
||||
runs-on: macos-latest
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
|
|
@ -231,6 +231,7 @@ dependencies = [
|
|||
"crypto_secretbox",
|
||||
"custom_error",
|
||||
"discortp",
|
||||
"flate2",
|
||||
"futures-util",
|
||||
"getrandom",
|
||||
"hostname",
|
||||
|
@ -250,6 +251,7 @@ dependencies = [
|
|||
"serde_json",
|
||||
"serde_repr",
|
||||
"serde_with",
|
||||
"simple_logger",
|
||||
"sqlx",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
|
@ -264,7 +266,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "chorus-macros"
|
||||
version = "0.4.0"
|
||||
version = "0.4.1"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"quote",
|
||||
|
@ -353,6 +355,15 @@ version = "2.4.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5"
|
||||
|
||||
[[package]]
|
||||
name = "crc32fast"
|
||||
version = "1.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-queue"
|
||||
version = "0.3.11"
|
||||
|
@ -553,6 +564,16 @@ version = "1.2.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8fcfdc7a0362c9f4444381a9e697c79d435fe65b52a37466fc2c1184cee9edc6"
|
||||
|
||||
[[package]]
|
||||
name = "flate2"
|
||||
version = "1.0.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5f54427cfd1c7829e2a139fcefea601bf088ebca651d2bf53ebc600eac295dae"
|
||||
dependencies = [
|
||||
"crc32fast",
|
||||
"miniz_oxide",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "flume"
|
||||
version = "0.11.0"
|
||||
|
@ -1251,6 +1272,24 @@ dependencies = [
|
|||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "multer"
|
||||
version = "3.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "83e87776546dc87511aa5ee218730c92b666d7264ab6ed41f9d215af9cd5224b"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"encoding_rs",
|
||||
"futures-util",
|
||||
"http 1.1.0",
|
||||
"httparse",
|
||||
"memchr",
|
||||
"mime",
|
||||
"spin 0.9.8",
|
||||
"tokio",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "native-tls"
|
||||
version = "0.2.11"
|
||||
|
@ -1588,6 +1627,7 @@ dependencies = [
|
|||
"hyper 1.3.1",
|
||||
"hyper-util",
|
||||
"mime",
|
||||
"multer",
|
||||
"nix 0.28.0",
|
||||
"parking_lot",
|
||||
"percent-encoding",
|
||||
|
@ -2124,6 +2164,16 @@ dependencies = [
|
|||
"time",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "simple_logger"
|
||||
version = "5.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e8c5dfa5e08767553704aa0ffd9d9794d527103c736aba9854773851fd7497eb"
|
||||
dependencies = [
|
||||
"log",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "slab"
|
||||
version = "0.4.9"
|
||||
|
|
|
@ -16,7 +16,7 @@ default = ["client", "rt-multi-thread"]
|
|||
backend = ["poem", "sqlx"]
|
||||
rt-multi-thread = ["tokio/rt-multi-thread"]
|
||||
rt = ["tokio/rt"]
|
||||
client = []
|
||||
client = ["flate2"]
|
||||
voice = ["voice_udp", "voice_gateway"]
|
||||
voice_udp = ["dep:discortp", "dep:crypto_secretbox"]
|
||||
voice_gateway = []
|
||||
|
@ -38,7 +38,7 @@ http = "0.2.11"
|
|||
base64 = "0.21.7"
|
||||
bitflags = { version = "2.4.1", features = ["serde"] }
|
||||
lazy_static = "1.4.0"
|
||||
poem = { version = "3.0.1", optional = true }
|
||||
poem = { version = "3.0.1", features = ["multipart"], optional = true }
|
||||
thiserror = "1.0.56"
|
||||
jsonwebtoken = "8.3.0"
|
||||
log = "0.4.20"
|
||||
|
@ -56,6 +56,7 @@ sqlx = { version = "0.7.3", features = [
|
|||
discortp = { version = "0.5.0", optional = true, features = ["rtp", "discord", "demux"] }
|
||||
crypto_secretbox = { version = "0.1.1", optional = true }
|
||||
rand = "0.8.5"
|
||||
flate2 = { version = "1.0.30", optional = true }
|
||||
|
||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||
rustls = "0.21.10"
|
||||
|
@ -78,3 +79,4 @@ wasmtimer = "0.2.0"
|
|||
lazy_static = "1.4.0"
|
||||
wasm-bindgen-test = "0.3.42"
|
||||
wasm-bindgen = "0.2.92"
|
||||
simple_logger = { version = "5.0.0", default-features=false }
|
||||
|
|
|
@ -15,7 +15,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "chorus-macros"
|
||||
version = "0.4.0"
|
||||
version = "0.4.1"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"quote",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "chorus-macros"
|
||||
version = "0.4.0"
|
||||
version = "0.4.1"
|
||||
edition = "2021"
|
||||
license = "MPL-2.0"
|
||||
description = "Macros for the chorus crate."
|
||||
|
|
|
@ -214,9 +214,11 @@ pub fn serde_bitflag_derive(input: TokenStream) -> TokenStream {
|
|||
// let s = String::deserialize(deserializer)?.parse::<u64>().map_err(serde::de::Error::custom)?;
|
||||
let s = crate::types::serde::string_or_u64(deserializer)?;
|
||||
|
||||
Ok(Self::from_bits(s).unwrap())
|
||||
// Note: while truncating may not be ideal, it's better than a panic if there are
|
||||
// extra flags
|
||||
Ok(Self::from_bits_truncate(s))
|
||||
}
|
||||
}
|
||||
}
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,8 @@
|
|||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
// This example showcase how to properly use gateway observers.
|
||||
// (This assumes you have a manually created gateway, if you created
|
||||
// a ChorusUser by e.g. logging in, you can access the gateway with user.gateway)
|
||||
//
|
||||
// To properly run it, you will need to change the token below.
|
||||
|
||||
|
@ -12,7 +14,7 @@ const TOKEN: &str = "";
|
|||
const GATEWAY_URL: &str = "wss://gateway.old.server.spacebar.chat/";
|
||||
|
||||
use async_trait::async_trait;
|
||||
use chorus::gateway::Gateway;
|
||||
use chorus::gateway::{Gateway, GatewayOptions};
|
||||
use chorus::{
|
||||
self,
|
||||
gateway::Observer,
|
||||
|
@ -47,8 +49,14 @@ impl Observer<GatewayReady> for ExampleObserver {
|
|||
async fn main() {
|
||||
let gateway_websocket_url = GATEWAY_URL.to_string();
|
||||
|
||||
// These options specify the encoding format, compression, etc
|
||||
//
|
||||
// For most cases the defaults should work, though some implementations
|
||||
// might only support some formats or not support compression
|
||||
let options = GatewayOptions::default();
|
||||
|
||||
// Initiate the gateway connection
|
||||
let gateway = Gateway::spawn(gateway_websocket_url).await.unwrap();
|
||||
let gateway = Gateway::spawn(gateway_websocket_url, options).await.unwrap();
|
||||
|
||||
// Create an instance of our observer
|
||||
let observer = ExampleObserver {};
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
// 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)
|
||||
// (e. g. not through ChorusUser or Instance)
|
||||
//
|
||||
// To properly run it, you will need to modify the token below.
|
||||
|
||||
|
@ -14,7 +14,7 @@ const GATEWAY_URL: &str = "wss://gateway.old.server.spacebar.chat/";
|
|||
|
||||
use std::time::Duration;
|
||||
|
||||
use chorus::gateway::Gateway;
|
||||
use chorus::gateway::{Gateway, GatewayOptions};
|
||||
use chorus::{self, types::GatewayIdentifyPayload};
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
|
@ -26,9 +26,15 @@ use wasmtimer::tokio::sleep;
|
|||
#[tokio::main(flavor = "current_thread")]
|
||||
async fn main() {
|
||||
let gateway_websocket_url = GATEWAY_URL.to_string();
|
||||
|
||||
// These options specify the encoding format, compression, etc
|
||||
//
|
||||
// For most cases the defaults should work, though some implementations
|
||||
// might only support some formats or not support compression
|
||||
let options = GatewayOptions::default();
|
||||
|
||||
// Initiate the gateway connection, starting a listener in one thread and a heartbeat handler in another
|
||||
let gateway = Gateway::spawn(gateway_websocket_url).await.unwrap();
|
||||
let gateway = Gateway::spawn(gateway_websocket_url, options).await.unwrap();
|
||||
|
||||
// At this point, we are connected to the server and are sending heartbeats, however we still haven't authenticated
|
||||
|
||||
|
|
|
@ -24,5 +24,5 @@ async fn main() {
|
|||
.await
|
||||
.expect("An error occurred during the login process");
|
||||
dbg!(user.belongs_to);
|
||||
dbg!(&user.object.read().unwrap().username);
|
||||
dbg!(&user.object.unwrap().as_ref().read().unwrap().username);
|
||||
}
|
||||
|
|
|
@ -44,6 +44,16 @@ impl ChorusUser {
|
|||
&mut self,
|
||||
query: Option<GetUserGuildSchema>,
|
||||
) -> ChorusResult<Vec<Guild>> {
|
||||
|
||||
let query_parameters = {
|
||||
if let Some(query_some) = query {
|
||||
query_some.to_query()
|
||||
}
|
||||
else {
|
||||
Vec::new()
|
||||
}
|
||||
};
|
||||
|
||||
let url = format!(
|
||||
"{}/users/@me/guilds",
|
||||
self.belongs_to.read().unwrap().urls.api,
|
||||
|
@ -53,7 +63,7 @@ impl ChorusUser {
|
|||
.get(url)
|
||||
.header("Authorization", self.token())
|
||||
.header("Content-Type", "application/json")
|
||||
.body(to_string(&query).unwrap()),
|
||||
.query(&query_parameters),
|
||||
|
||||
limit_type: LimitType::Global,
|
||||
};
|
||||
|
|
|
@ -32,7 +32,7 @@ impl ChorusUser {
|
|||
/// # Notes
|
||||
/// This function is a wrapper around [`User::get_settings`].
|
||||
pub async fn get_settings(&mut self) -> ChorusResult<UserSettings> {
|
||||
User::get_settings(self).await
|
||||
User::get_settings(self).await
|
||||
}
|
||||
|
||||
/// Modifies the current user's representation. (See [`User`])
|
||||
|
@ -40,12 +40,18 @@ impl ChorusUser {
|
|||
/// # Reference
|
||||
/// See <https://discord-userdoccers.vercel.app/resources/user#modify-current-user>
|
||||
pub async fn modify(&mut self, modify_schema: UserModifySchema) -> ChorusResult<User> {
|
||||
if modify_schema.new_password.is_some()
|
||||
|
||||
// See <https://docs.discord.sex/resources/user#json-params>, note 1
|
||||
let requires_current_password = modify_schema.username.is_some()
|
||||
|| modify_schema.discriminator.is_some()
|
||||
|| modify_schema.email.is_some()
|
||||
|| modify_schema.code.is_some()
|
||||
{
|
||||
|| modify_schema.date_of_birth.is_some()
|
||||
|| modify_schema.new_password.is_some();
|
||||
|
||||
if requires_current_password && modify_schema.current_password.is_none() {
|
||||
return Err(ChorusError::PasswordRequired);
|
||||
}
|
||||
|
||||
let request = Client::new()
|
||||
.patch(format!(
|
||||
"{}/users/@me",
|
||||
|
@ -132,4 +138,3 @@ impl User {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -48,7 +48,7 @@ custom_error! {
|
|||
/// Invalid, insufficient or too many arguments provided.
|
||||
InvalidArguments{error: String} = "Invalid arguments were provided. Error: {error}",
|
||||
/// The request requires MFA verification
|
||||
MfaRequired {error: MfaRequiredSchema} = "Mfa verification required to perform this action"
|
||||
MfaRequired {error: MfaRequiredSchema} = "Mfa verification is required to perform this action"
|
||||
}
|
||||
|
||||
impl From<reqwest::Error> for ChorusError {
|
||||
|
|
|
@ -12,7 +12,7 @@ use tokio_tungstenite::{
|
|||
connect_async_tls_with_config, tungstenite, Connector, MaybeTlsStream, WebSocketStream,
|
||||
};
|
||||
|
||||
use crate::gateway::GatewayMessage;
|
||||
use crate::gateway::{GatewayMessage, RawGatewayMessage};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TungsteniteBackend;
|
||||
|
@ -80,3 +80,22 @@ impl From<tungstenite::Message> for GatewayMessage {
|
|||
Self(value.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<RawGatewayMessage> for tungstenite::Message {
|
||||
fn from(message: RawGatewayMessage) -> Self {
|
||||
match message {
|
||||
RawGatewayMessage::Text(text) => tungstenite::Message::Text(text),
|
||||
RawGatewayMessage::Bytes(bytes) => tungstenite::Message::Binary(bytes),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<tungstenite::Message> for RawGatewayMessage {
|
||||
fn from(value: tungstenite::Message) -> Self {
|
||||
match value {
|
||||
tungstenite::Message::Binary(bytes) => RawGatewayMessage::Bytes(bytes),
|
||||
tungstenite::Message::Text(text) => RawGatewayMessage::Text(text),
|
||||
_ => RawGatewayMessage::Text(value.to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ use futures_util::{
|
|||
|
||||
use ws_stream_wasm::*;
|
||||
|
||||
use crate::gateway::GatewayMessage;
|
||||
use crate::gateway::{GatewayMessage, RawGatewayMessage};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct WasmBackend;
|
||||
|
@ -46,3 +46,21 @@ impl From<WsMessage> for GatewayMessage {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<RawGatewayMessage> for WsMessage {
|
||||
fn from(message: RawGatewayMessage) -> Self {
|
||||
match message {
|
||||
RawGatewayMessage::Text(text) => WsMessage::Text(text),
|
||||
RawGatewayMessage::Bytes(bytes) => WsMessage::Binary(bytes),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<WsMessage> for RawGatewayMessage {
|
||||
fn from(value: WsMessage) -> Self {
|
||||
match value {
|
||||
WsMessage::Binary(bytes) => RawGatewayMessage::Bytes(bytes),
|
||||
WsMessage::Text(text) => RawGatewayMessage::Text(text),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
use std::time::Duration;
|
||||
|
||||
use flate2::Decompress;
|
||||
use futures_util::{SinkExt, StreamExt};
|
||||
use log::*;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
|
@ -19,6 +20,9 @@ use crate::types::{
|
|||
WebSocketEvent,
|
||||
};
|
||||
|
||||
/// Tells us we have received enough of the buffer to decompress it
|
||||
const ZLIB_SUFFIX: [u8; 4] = [0, 0, 255, 255];
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Gateway {
|
||||
events: Arc<Mutex<Events>>,
|
||||
|
@ -28,21 +32,36 @@ pub struct Gateway {
|
|||
kill_send: tokio::sync::broadcast::Sender<()>,
|
||||
kill_receive: tokio::sync::broadcast::Receiver<()>,
|
||||
store: Arc<Mutex<HashMap<Snowflake, Arc<RwLock<ObservableObject>>>>>,
|
||||
/// Url which was used to initialize the gateway
|
||||
url: String,
|
||||
/// Options which were used to initialize the gateway
|
||||
options: GatewayOptions,
|
||||
zlib_inflate: Option<flate2::Decompress>,
|
||||
zlib_buffer: Option<Vec<u8>>,
|
||||
}
|
||||
|
||||
impl Gateway {
|
||||
#[allow(clippy::new_ret_no_self)]
|
||||
pub async fn spawn(websocket_url: String) -> Result<GatewayHandle, GatewayError> {
|
||||
let (websocket_send, mut websocket_receive) =
|
||||
match WebSocketBackend::connect(&websocket_url).await {
|
||||
Ok(streams) => streams,
|
||||
Err(e) => {
|
||||
return Err(GatewayError::CannotConnect {
|
||||
error: format!("{:?}", e),
|
||||
})
|
||||
}
|
||||
};
|
||||
/// Creates / opens a new gateway connection.
|
||||
///
|
||||
/// # Note
|
||||
/// The websocket url should begin with the prefix wss:// or ws:// (for unsecure connections)
|
||||
pub async fn spawn(
|
||||
websocket_url: String,
|
||||
options: GatewayOptions,
|
||||
) -> Result<GatewayHandle, GatewayError> {
|
||||
let url = options.add_to_url(websocket_url);
|
||||
|
||||
debug!("GW: Connecting to {}", url);
|
||||
|
||||
let (websocket_send, mut websocket_receive) = match WebSocketBackend::connect(&url).await {
|
||||
Ok(streams) => streams,
|
||||
Err(e) => {
|
||||
return Err(GatewayError::CannotConnect {
|
||||
error: format!("{:?}", e),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
let shared_websocket_send = Arc::new(Mutex::new(websocket_send));
|
||||
|
||||
|
@ -52,10 +71,34 @@ impl Gateway {
|
|||
// Wait for the first hello and then spawn both tasks so we avoid nested tasks
|
||||
// This automatically spawns the heartbeat task, but from the main thread
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
let msg: GatewayMessage = websocket_receive.next().await.unwrap().unwrap().into();
|
||||
let received: RawGatewayMessage = websocket_receive.next().await.unwrap().unwrap().into();
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
let msg: GatewayMessage = websocket_receive.next().await.unwrap().into();
|
||||
let gateway_payload: types::GatewayReceivePayload = serde_json::from_str(&msg.0).unwrap();
|
||||
let received: RawGatewayMessage = websocket_receive.next().await.unwrap().into();
|
||||
|
||||
let message: GatewayMessage;
|
||||
|
||||
let zlib_buffer;
|
||||
let zlib_inflate;
|
||||
|
||||
match options.transport_compression {
|
||||
GatewayTransportCompression::None => {
|
||||
zlib_buffer = None;
|
||||
zlib_inflate = None;
|
||||
message = GatewayMessage::from_raw_json_message(received).unwrap();
|
||||
}
|
||||
GatewayTransportCompression::ZLibStream => {
|
||||
zlib_buffer = Some(Vec::new());
|
||||
let mut inflate = Decompress::new(true);
|
||||
|
||||
message =
|
||||
GatewayMessage::from_zlib_stream_json_message(received, &mut inflate).unwrap();
|
||||
|
||||
zlib_inflate = Some(inflate);
|
||||
}
|
||||
}
|
||||
|
||||
let gateway_payload: types::GatewayReceivePayload =
|
||||
serde_json::from_str(&message.0).unwrap();
|
||||
|
||||
if gateway_payload.op_code != GATEWAY_HELLO {
|
||||
return Err(GatewayError::NonHelloOnInitiate {
|
||||
|
@ -85,7 +128,10 @@ impl Gateway {
|
|||
kill_send: kill_send.clone(),
|
||||
kill_receive: kill_send.subscribe(),
|
||||
store: store.clone(),
|
||||
url: websocket_url.clone(),
|
||||
url: url.clone(),
|
||||
options,
|
||||
zlib_inflate,
|
||||
zlib_buffer,
|
||||
};
|
||||
|
||||
// Now we can continuously check for messages in a different task, since we aren't going to receive another hello
|
||||
|
@ -99,7 +145,7 @@ impl Gateway {
|
|||
});
|
||||
|
||||
Ok(GatewayHandle {
|
||||
url: websocket_url.clone(),
|
||||
url: url.clone(),
|
||||
events: shared_events,
|
||||
websocket_send: shared_websocket_send.clone(),
|
||||
kill_send: kill_send.clone(),
|
||||
|
@ -108,7 +154,7 @@ impl Gateway {
|
|||
}
|
||||
|
||||
/// The main gateway listener task;
|
||||
pub async fn gateway_listen_task(&mut self) {
|
||||
async fn gateway_listen_task(&mut self) {
|
||||
loop {
|
||||
let msg;
|
||||
|
||||
|
@ -125,12 +171,12 @@ impl Gateway {
|
|||
// PRETTYFYME: Remove inline conditional compiling
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
if let Some(Ok(message)) = msg {
|
||||
self.handle_message(message.into()).await;
|
||||
self.handle_raw_message(message.into()).await;
|
||||
continue;
|
||||
}
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
if let Some(message) = msg {
|
||||
self.handle_message(message.into()).await;
|
||||
self.handle_raw_message(message.into()).await;
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -163,8 +209,42 @@ impl Gateway {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// Takes a [RawGatewayMessage], converts it to [GatewayMessage] based
|
||||
/// of connection options and calls handle_message
|
||||
async fn handle_raw_message(&mut self, raw_message: RawGatewayMessage) {
|
||||
let message;
|
||||
|
||||
match self.options.transport_compression {
|
||||
GatewayTransportCompression::None => {
|
||||
message = GatewayMessage::from_raw_json_message(raw_message).unwrap()
|
||||
}
|
||||
GatewayTransportCompression::ZLibStream => {
|
||||
let message_bytes = raw_message.into_bytes();
|
||||
|
||||
let can_decompress = message_bytes.len() > 4
|
||||
&& message_bytes[message_bytes.len() - 4..] == ZLIB_SUFFIX;
|
||||
|
||||
let zlib_buffer = self.zlib_buffer.as_mut().unwrap();
|
||||
zlib_buffer.extend(message_bytes.clone());
|
||||
|
||||
if !can_decompress {
|
||||
return;
|
||||
}
|
||||
|
||||
let zlib_buffer = self.zlib_buffer.as_ref().unwrap();
|
||||
let inflate = self.zlib_inflate.as_mut().unwrap();
|
||||
|
||||
message =
|
||||
GatewayMessage::from_zlib_stream_json_bytes(zlib_buffer, inflate).unwrap();
|
||||
self.zlib_buffer = Some(Vec::new());
|
||||
}
|
||||
};
|
||||
|
||||
self.handle_message(message).await;
|
||||
}
|
||||
|
||||
/// This handles a message as a websocket event and updates its events along with the events' observers
|
||||
pub async fn handle_message(&mut self, msg: GatewayMessage) {
|
||||
async fn handle_message(&mut self, msg: GatewayMessage) {
|
||||
if msg.0.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
@ -201,7 +281,10 @@ impl Gateway {
|
|||
let event = &mut self.events.lock().await.$($path).+;
|
||||
let json = gateway_payload.event_data.unwrap().get();
|
||||
match serde_json::from_str(json) {
|
||||
Err(err) => warn!("Failed to parse gateway event {event_name} ({err})"),
|
||||
Err(err) => {
|
||||
warn!("Failed to parse gateway event {event_name} ({err})");
|
||||
trace!("Event data: {json}");
|
||||
},
|
||||
Ok(message) => {
|
||||
$(
|
||||
let mut message: $message_type = message;
|
||||
|
@ -237,15 +320,12 @@ impl Gateway {
|
|||
},)*
|
||||
"RESUMED" => (),
|
||||
"SESSIONS_REPLACE" => {
|
||||
let result: Result<Vec<types::Session>, serde_json::Error> =
|
||||
serde_json::from_str(gateway_payload.event_data.unwrap().get());
|
||||
let json = gateway_payload.event_data.unwrap().get();
|
||||
let result: Result<Vec<types::Session>, serde_json::Error> = serde_json::from_str(json);
|
||||
match result {
|
||||
Err(err) => {
|
||||
warn!(
|
||||
"Failed to parse gateway event {} ({})",
|
||||
event_name,
|
||||
err
|
||||
);
|
||||
warn!("Failed to parse gateway event {event_name} ({err})");
|
||||
trace!("Event data: {json}");
|
||||
return;
|
||||
}
|
||||
Ok(sessions) => {
|
||||
|
@ -257,6 +337,7 @@ impl Gateway {
|
|||
},
|
||||
_ => {
|
||||
warn!("Received unrecognized gateway event ({event_name})! Please open an issue on the chorus github so we can implement it");
|
||||
trace!("Event data: {}", gateway_payload.event_data.unwrap().get());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -2,11 +2,41 @@
|
|||
// 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::string::FromUtf8Error;
|
||||
|
||||
use crate::types;
|
||||
|
||||
use super::*;
|
||||
|
||||
/// Represents a message received from the gateway. This will be either a [types::GatewayReceivePayload], containing events, or a [GatewayError].
|
||||
/// Defines a raw gateway message, being either string json or bytes
|
||||
///
|
||||
/// This is used as an intermediary type between types from different websocket implementations
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub(crate) enum RawGatewayMessage {
|
||||
Text(String),
|
||||
Bytes(Vec<u8>),
|
||||
}
|
||||
|
||||
impl RawGatewayMessage {
|
||||
/// Attempt to consume the message into a String, will try to convert binary to utf8
|
||||
pub fn into_text(self) -> Result<String, FromUtf8Error> {
|
||||
match self {
|
||||
RawGatewayMessage::Text(text) => Ok(text),
|
||||
RawGatewayMessage::Bytes(bytes) => String::from_utf8(bytes),
|
||||
}
|
||||
}
|
||||
|
||||
/// Consume the message into bytes, will convert text to binary
|
||||
pub fn into_bytes(self) -> Vec<u8> {
|
||||
match self {
|
||||
RawGatewayMessage::Text(text) => text.as_bytes().to_vec(),
|
||||
RawGatewayMessage::Bytes(bytes) => bytes,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents a json message 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);
|
||||
|
@ -44,4 +74,48 @@ impl GatewayMessage {
|
|||
pub fn payload(&self) -> Result<types::GatewayReceivePayload, serde_json::Error> {
|
||||
serde_json::from_str(&self.0)
|
||||
}
|
||||
|
||||
/// Create self from an uncompressed json [RawGatewayMessage]
|
||||
pub(crate) fn from_raw_json_message(
|
||||
message: RawGatewayMessage,
|
||||
) -> Result<GatewayMessage, FromUtf8Error> {
|
||||
let text = message.into_text()?;
|
||||
Ok(GatewayMessage(text))
|
||||
}
|
||||
|
||||
/// Attempt to create self by decompressing zlib-stream bytes
|
||||
// Thanks to <https://github.com/ByteAlex/zlib-stream-rs>, their
|
||||
// code helped a lot with the stream implementation
|
||||
pub(crate) fn from_zlib_stream_json_bytes(
|
||||
bytes: &[u8],
|
||||
inflate: &mut flate2::Decompress,
|
||||
) -> Result<GatewayMessage, std::io::Error> {
|
||||
|
||||
// Note: is there a better way to handle the size of this output buffer?
|
||||
//
|
||||
// This used to be 10, I measured it at 11.5, so a safe bet feels like 20
|
||||
//
|
||||
// ^ - This dude is naive. apparently not even 20x is okay. Measured at 47.9x!!!!
|
||||
// If it is >100x ever, I will literally explode
|
||||
//
|
||||
// About an hour later, you ^ will literally explode.
|
||||
// 133 vs 13994 -- 105.21805x ratio
|
||||
// Let's hope it doesn't go above 200??
|
||||
let mut output = Vec::with_capacity(bytes.len() * 200);
|
||||
let _status = inflate.decompress_vec(bytes, &mut output, flate2::FlushDecompress::Sync)?;
|
||||
|
||||
output.shrink_to_fit();
|
||||
|
||||
let string = String::from_utf8(output).unwrap();
|
||||
|
||||
Ok(GatewayMessage(string))
|
||||
}
|
||||
|
||||
/// Attempt to create self by decompressing a zlib-stream bytes raw message
|
||||
pub(crate) fn from_zlib_stream_json_message(
|
||||
message: RawGatewayMessage,
|
||||
inflate: &mut flate2::Decompress,
|
||||
) -> Result<GatewayMessage, std::io::Error> {
|
||||
Self::from_zlib_stream_json_bytes(&message.into_bytes(), inflate)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,12 +10,14 @@ pub mod gateway;
|
|||
pub mod handle;
|
||||
pub mod heartbeat;
|
||||
pub mod message;
|
||||
pub mod options;
|
||||
|
||||
pub use backends::*;
|
||||
pub use gateway::*;
|
||||
pub use handle::*;
|
||||
use heartbeat::*;
|
||||
pub use message::*;
|
||||
pub use options::*;
|
||||
|
||||
use crate::errors::GatewayError;
|
||||
use crate::types::{Snowflake, WebSocketEvent};
|
||||
|
|
|
@ -0,0 +1,118 @@
|
|||
// 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/.
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Ord, PartialOrd, Debug, Default)]
|
||||
/// Options passed when initializing the gateway connection.
|
||||
///
|
||||
/// E.g. compression
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// Discord allows specifying the api version (v10, v9, ...) as well, but chorus is built upon one
|
||||
/// main version (v9).
|
||||
///
|
||||
/// Similarly, discord also supports etf encoding, while chorus does not (yet).
|
||||
/// We are looking into supporting it as an option, since it is faster and more lightweight.
|
||||
///
|
||||
/// See <https://docs.discord.sex/topics/gateway#connections>
|
||||
pub struct GatewayOptions {
|
||||
pub encoding: GatewayEncoding,
|
||||
pub transport_compression: GatewayTransportCompression,
|
||||
}
|
||||
|
||||
impl GatewayOptions {
|
||||
/// Adds the options to an existing gateway url
|
||||
///
|
||||
/// Returns the new url
|
||||
pub(crate) fn add_to_url(&self, url: String) -> String {
|
||||
|
||||
let mut url = url;
|
||||
|
||||
let mut parameters = Vec::with_capacity(2);
|
||||
|
||||
let encoding = self.encoding.to_url_parameter();
|
||||
parameters.push(encoding);
|
||||
|
||||
let compression = self.transport_compression.to_url_parameter();
|
||||
if let Some(some_compression) = compression {
|
||||
parameters.push(some_compression);
|
||||
}
|
||||
|
||||
let mut has_parameters = url.contains('?') && url.contains('=');
|
||||
|
||||
if !has_parameters {
|
||||
// Insure it ends in a /, so we don't get a 400 error
|
||||
if !url.ends_with('/') {
|
||||
url.push('/');
|
||||
}
|
||||
|
||||
// Lets hope that if it already has parameters the person knew to add '/'
|
||||
}
|
||||
|
||||
for parameter in parameters {
|
||||
if !has_parameters {
|
||||
url = format!("{}?{}", url, parameter);
|
||||
has_parameters = true;
|
||||
}
|
||||
else {
|
||||
url = format!("{}&{}", url, parameter);
|
||||
}
|
||||
}
|
||||
|
||||
url
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Ord, PartialOrd, Debug, Default)]
|
||||
/// Possible transport compression options for the gateway.
|
||||
///
|
||||
/// See <https://docs.discord.sex/topics/gateway#transport-compression>
|
||||
pub enum GatewayTransportCompression {
|
||||
/// Do not transport compress packets
|
||||
None,
|
||||
/// Transport compress using zlib stream
|
||||
#[default]
|
||||
ZLibStream,
|
||||
}
|
||||
|
||||
impl GatewayTransportCompression {
|
||||
/// Returns the option as a url parameter.
|
||||
///
|
||||
/// If set to [GatewayTransportCompression::None] returns [None].
|
||||
///
|
||||
/// If set to anything else, returns a string like "compress=zlib-stream"
|
||||
pub(crate) fn to_url_parameter(self) -> Option<String> {
|
||||
match self {
|
||||
Self::None => None,
|
||||
Self::ZLibStream => Some(String::from("compress=zlib-stream"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Ord, PartialOrd, Debug, Default)]
|
||||
/// See <https://docs.discord.sex/topics/gateway#encoding-and-compression>
|
||||
pub enum GatewayEncoding {
|
||||
/// Javascript object notation, a standard for websocket connections,
|
||||
/// but contains a lot of overhead
|
||||
#[default]
|
||||
Json,
|
||||
/// A binary format originating from Erlang
|
||||
///
|
||||
/// Should be lighter and faster than json.
|
||||
///
|
||||
/// !! Chorus does not implement ETF yet !!
|
||||
ETF
|
||||
}
|
||||
|
||||
impl GatewayEncoding {
|
||||
/// Returns the option as a url parameter.
|
||||
///
|
||||
/// Returns a string like "encoding=json"
|
||||
pub(crate) fn to_url_parameter(self) -> String {
|
||||
match self {
|
||||
Self::Json => String::from("encoding=json"),
|
||||
Self::ETF => String::from("encoding=etf")
|
||||
}
|
||||
}
|
||||
}
|
|
@ -15,7 +15,7 @@ use serde::{Deserialize, Serialize};
|
|||
use chrono::Utc;
|
||||
|
||||
use crate::errors::ChorusResult;
|
||||
use crate::gateway::{Gateway, GatewayHandle};
|
||||
use crate::gateway::{Gateway, GatewayHandle, GatewayOptions};
|
||||
use crate::ratelimiter::ChorusRequest;
|
||||
use crate::types::types::subconfigs::limits::rates::RateLimits;
|
||||
use crate::types::{
|
||||
|
@ -33,6 +33,8 @@ pub struct Instance {
|
|||
pub limits_information: Option<LimitsInformation>,
|
||||
#[serde(skip)]
|
||||
pub client: Client,
|
||||
#[serde(skip)]
|
||||
pub gateway_options: GatewayOptions,
|
||||
}
|
||||
|
||||
impl PartialEq for Instance {
|
||||
|
@ -106,6 +108,7 @@ impl Instance {
|
|||
instance_info: GeneralConfiguration::default(),
|
||||
limits_information: limit_information,
|
||||
client: Client::new(),
|
||||
gateway_options: GatewayOptions::default(),
|
||||
};
|
||||
instance.instance_info = match instance.general_configuration_schema().await {
|
||||
Ok(schema) => schema,
|
||||
|
@ -141,6 +144,13 @@ impl Instance {
|
|||
Err(_) => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the [`GatewayOptions`] the instance will use when spawning new connections.
|
||||
///
|
||||
/// These options are used on the gateways created when logging in and registering.
|
||||
pub fn set_gateway_options(&mut self, options: GatewayOptions) {
|
||||
self.gateway_options = options;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
|
@ -218,7 +228,9 @@ impl ChorusUser {
|
|||
let settings = Arc::new(RwLock::new(UserSettings::default()));
|
||||
let wss_url = instance.read().unwrap().urls.wss.clone();
|
||||
// Dummy gateway object
|
||||
let gateway = Gateway::spawn(wss_url).await.unwrap();
|
||||
let gateway = Gateway::spawn(wss_url, GatewayOptions::default())
|
||||
.await
|
||||
.unwrap();
|
||||
ChorusUser {
|
||||
token,
|
||||
mfa_token: None,
|
||||
|
@ -244,10 +256,11 @@ impl ChorusUser {
|
|||
///
|
||||
/// The JWT token expires after 5 minutes.
|
||||
pub async fn complete_mfa_challenge(&mut self, mfa_verify_schema: MfaVerifySchema) -> ChorusResult<()> {
|
||||
let endpoint_url = "/mfa/finish";
|
||||
let endpoint_url = self.belongs_to.read().unwrap().urls.api.clone() + "/mfa/finish";
|
||||
let chorus_request = ChorusRequest {
|
||||
request: Client::new()
|
||||
.post(endpoint_url)
|
||||
.header("Authorization", self.token())
|
||||
.json(&mfa_verify_schema),
|
||||
limit_type: match self.object.is_some() {
|
||||
true => LimitType::Global,
|
||||
|
|
|
@ -87,7 +87,7 @@ impl ChorusRequest {
|
|||
let client = user.belongs_to.read().unwrap().client.clone();
|
||||
let result = match client.execute(self.request.build().unwrap()).await {
|
||||
Ok(result) => {
|
||||
debug!("Request successful: {:?}", result);
|
||||
log::trace!("Request successful: {:?}", result);
|
||||
result
|
||||
}
|
||||
Err(error) => {
|
||||
|
@ -500,7 +500,7 @@ impl ChorusRequest {
|
|||
user: &mut ChorusUser,
|
||||
) -> ChorusResult<T> {
|
||||
let response = self.send_request(user).await?;
|
||||
debug!("Got response: {:?}", response);
|
||||
log::trace!("Got response: {:?}", response);
|
||||
let response_text = match response.text().await {
|
||||
Ok(string) => string,
|
||||
Err(e) => {
|
||||
|
|
|
@ -3,19 +3,10 @@
|
|||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
use std::fmt::{Display, Formatter};
|
||||
#[cfg(feature = "sqlx")]
|
||||
use std::io::Write;
|
||||
use std::ops::{Deref, DerefMut};
|
||||
use std::str::FromStr;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
#[cfg(feature = "sqlx")]
|
||||
use sqlx::{
|
||||
database::{HasArguments, HasValueRef},
|
||||
encode::IsNull,
|
||||
error::BoxDynError,
|
||||
Decode, MySql,
|
||||
};
|
||||
|
||||
use crate::types::config::types::subconfigs::guild::{
|
||||
autojoin::AutoJoinConfiguration, discovery::DiscoverConfiguration,
|
||||
|
@ -172,8 +163,8 @@ impl Display for GuildFeaturesList {
|
|||
|
||||
#[cfg(feature = "sqlx")]
|
||||
impl<'r> sqlx::Decode<'r, sqlx::MySql> for GuildFeaturesList {
|
||||
fn decode(value: <MySql as HasValueRef<'r>>::ValueRef) -> Result<Self, BoxDynError> {
|
||||
let v = <&str as Decode<sqlx::MySql>>::decode(value)?;
|
||||
fn decode(value: <sqlx::MySql as sqlx::database::HasValueRef<'r>>::ValueRef) -> Result<Self, sqlx::error::BoxDynError> {
|
||||
let v = <String as sqlx::Decode<sqlx::MySql>>::decode(value)?;
|
||||
Ok(Self(
|
||||
v.split(',')
|
||||
.filter(|f| !f.is_empty())
|
||||
|
@ -185,9 +176,9 @@ impl<'r> sqlx::Decode<'r, sqlx::MySql> for GuildFeaturesList {
|
|||
|
||||
#[cfg(feature = "sqlx")]
|
||||
impl<'q> sqlx::Encode<'q, sqlx::MySql> for GuildFeaturesList {
|
||||
fn encode_by_ref(&self, buf: &mut <MySql as HasArguments<'q>>::ArgumentBuffer) -> IsNull {
|
||||
fn encode_by_ref(&self, buf: &mut <sqlx::MySql as sqlx::database::HasArguments<'q>>::ArgumentBuffer) -> sqlx::encode::IsNull {
|
||||
if self.is_empty() {
|
||||
return IsNull::Yes;
|
||||
return sqlx::encode::IsNull::Yes;
|
||||
}
|
||||
let features = self
|
||||
.iter()
|
||||
|
@ -195,30 +186,18 @@ impl<'q> sqlx::Encode<'q, sqlx::MySql> for GuildFeaturesList {
|
|||
.collect::<Vec<_>>()
|
||||
.join(",");
|
||||
|
||||
let _ = buf.write(features.as_bytes());
|
||||
IsNull::No
|
||||
<String as sqlx::Encode<sqlx::MySql>>::encode_by_ref(&features, buf)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "sqlx")]
|
||||
impl sqlx::Type<sqlx::MySql> for GuildFeaturesList {
|
||||
fn type_info() -> sqlx::mysql::MySqlTypeInfo {
|
||||
<&str as sqlx::Type<sqlx::MySql>>::type_info()
|
||||
<String as sqlx::Type<sqlx::MySql>>::type_info()
|
||||
}
|
||||
|
||||
fn compatible(ty: &sqlx::mysql::MySqlTypeInfo) -> bool {
|
||||
<&str as sqlx::Type<sqlx::MySql>>::compatible(ty)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "sqlx")]
|
||||
impl sqlx::TypeInfo for GuildFeaturesList {
|
||||
fn is_null(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn name(&self) -> &str {
|
||||
"TEXT"
|
||||
<String as sqlx::Type<sqlx::MySql>>::compatible(ty)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -3,21 +3,27 @@
|
|||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_repr::{Deserialize_repr, Serialize_repr};
|
||||
|
||||
use crate::types::Shared;
|
||||
use crate::types::{AutoModerationRuleTriggerType, IntegrationType, PermissionOverwriteType, Shared};
|
||||
use crate::types::utils::Snowflake;
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Default, Clone)]
|
||||
#[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))]
|
||||
/// See <https://discord.com/developers/docs/resources/audit-log#audit-log-entry-object>
|
||||
pub struct AuditLogEntry {
|
||||
pub target_id: Option<String>,
|
||||
#[cfg(feature = "sqlx")]
|
||||
pub changes: sqlx::types::Json<Option<Vec<Shared<AuditLogChange>>>>,
|
||||
#[cfg(not(feature = "sqlx"))]
|
||||
pub changes: Option<Vec<Shared<AuditLogChange>>>,
|
||||
pub user_id: Option<Snowflake>,
|
||||
pub id: Snowflake,
|
||||
// to:do implement an enum for these types
|
||||
pub action_type: u8,
|
||||
// to:do add better options type
|
||||
pub options: Option<serde_json::Value>,
|
||||
pub action_type: AuditLogActionType,
|
||||
#[cfg(feature = "sqlx")]
|
||||
pub options: Option<sqlx::types::Json<AuditEntryInfo>>,
|
||||
#[cfg(not(feature = "sqlx"))]
|
||||
pub options: Option<AuditEntryInfo>,
|
||||
pub reason: Option<String>,
|
||||
}
|
||||
|
||||
|
@ -28,3 +34,164 @@ pub struct AuditLogChange {
|
|||
pub old_value: Option<serde_json::Value>,
|
||||
pub key: String,
|
||||
}
|
||||
|
||||
|
||||
#[derive(Default, Serialize_repr, Deserialize_repr, Debug, Clone, Copy)]
|
||||
#[repr(u8)]
|
||||
#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
|
||||
/// # Reference:
|
||||
/// See <https://docs.discord.sex/resources/audit-log#audit-log-events>
|
||||
pub enum AuditLogActionType {
|
||||
#[default]
|
||||
/// Guild settings were updated
|
||||
GuildUpdate = 1,
|
||||
/// Channel was created
|
||||
ChannelCreate = 10,
|
||||
/// Channel settings were updated
|
||||
ChannelUpdate = 11,
|
||||
/// Channel was deleted
|
||||
ChannelDelete = 12,
|
||||
/// Permission overwrite was added to a channel
|
||||
ChannelOverwriteCreate = 13,
|
||||
/// Permission overwrite was updated for a channel
|
||||
ChannelOverwriteUpdate = 14,
|
||||
/// Permission overwrite was deleted from a channel
|
||||
ChannelOverwriteDelete = 15,
|
||||
/// Member was removed from guild
|
||||
MemberKick = 20,
|
||||
/// Members were pruned from guild
|
||||
MemberPrune = 21,
|
||||
/// Member was banned from guild
|
||||
MemberBanAdd = 22,
|
||||
/// Member was unbanned from guild
|
||||
MemberBanRemove = 23,
|
||||
/// Member was updated in guild
|
||||
MemberUpdate = 24,
|
||||
/// Member was added or removed from a role
|
||||
MemberRoleUpdate = 25,
|
||||
/// Member was moved to a different voice channel
|
||||
MemberMove = 26,
|
||||
/// Member was disconnected from a voice channel
|
||||
MemberDisconnect = 27,
|
||||
/// Bot user was added to guild
|
||||
BotAdd = 28,
|
||||
/// Role was created
|
||||
RoleCreate = 30,
|
||||
/// Role was edited
|
||||
RoleUpdate = 31,
|
||||
/// Role was deleted
|
||||
RoleDelete = 32,
|
||||
/// Guild invite was created
|
||||
InviteCreate = 40,
|
||||
/// Guild invite was updated
|
||||
InviteUpdate = 41,
|
||||
/// Guild invite was deleted
|
||||
InviteDelete = 42,
|
||||
/// Webhook was created
|
||||
WebhookCreate = 50,
|
||||
/// Webhook properties or channel were updated
|
||||
WebhookUpdate = 51,
|
||||
/// Webhook was deleted
|
||||
WebhookDelete = 52,
|
||||
/// Emoji was created
|
||||
EmojiCreate = 60,
|
||||
/// Emoji name was updated
|
||||
EmojiUpdate = 61,
|
||||
/// Emoji was deleted
|
||||
EmojiDelete = 62,
|
||||
/// Single message was deleted
|
||||
MessageDelete = 72,
|
||||
/// Multiple messages were deleted
|
||||
MessageBulkDelete = 73,
|
||||
/// Message was pinned to a channel
|
||||
MessagePin = 74,
|
||||
/// Message was unpinned from a channel
|
||||
MessageUnpin = 75,
|
||||
/// Interaction was added to guild
|
||||
IntegrationCreate = 80,
|
||||
/// Integration was updated (e.g. its scopes were updated)
|
||||
IntegrationUpdate = 81,
|
||||
/// Integration was removed from guild
|
||||
IntegrationDelete = 82,
|
||||
/// Stage instance was created (stage channel becomes live)
|
||||
StageInstanceCreate = 83,
|
||||
/// Stage instance details were updated
|
||||
StageInstanceUpdate = 84,
|
||||
/// Stage instance was deleted (stage channel no longer live)
|
||||
StageInstanceDelete = 85,
|
||||
/// Sticker was created
|
||||
StickerCreate = 90,
|
||||
/// Sticker details were updated
|
||||
StickerUpdate = 91,
|
||||
/// Sticker was deleted
|
||||
StickerDelete = 92,
|
||||
/// Event was created
|
||||
GuildScheduledEventCreate = 100,
|
||||
/// Event was updated
|
||||
GuildScheduledEventUpdate = 101,
|
||||
/// Event was cancelled
|
||||
GuildScheduledEventDelete = 102,
|
||||
/// Thread was created in a channel
|
||||
ThreadCreate = 110,
|
||||
/// Thread was updated
|
||||
ThreadUpdate = 111,
|
||||
/// Thread was deleted
|
||||
ThreadDelete = 112,
|
||||
/// Permissions were updated for a command
|
||||
ApplicationCommandPermissionUpdate = 121,
|
||||
/// AutoMod rule created
|
||||
AutoModerationRuleCreate = 140,
|
||||
/// AutoMod rule was updated
|
||||
AutoModerationRuleUpdate = 141,
|
||||
/// AutoMod rule was deleted
|
||||
AutoModerationRuleDelete = 142,
|
||||
/// Message was blocked by AutoMod
|
||||
AutoModerationBlockMessage = 143,
|
||||
/// Message was flagged by AutoMod
|
||||
AutoModerationFlagToChannel = 144,
|
||||
/// Member was timed out by AutoMod
|
||||
AutoModerationUserCommunicationDisabled = 145,
|
||||
/// Member was quarantined by AutoMod
|
||||
AutoModerationQuarantineUser = 146,
|
||||
/// Creator monetization request was created
|
||||
CreatorMonetizationRequestCreated = 150,
|
||||
/// Creator monetization terms were accepted
|
||||
CreatorMonetizationTermsAccepted = 151,
|
||||
/// Onboarding prompt was created
|
||||
OnboardingPromptCreate = 163,
|
||||
/// Onboarding prompt was updated
|
||||
OnboardingPromptUpdate = 164,
|
||||
/// Onboarding prompt was deleted
|
||||
OnboardingPromptDelete = 165,
|
||||
/// Onboarding was created
|
||||
OnboardingCreate = 166,
|
||||
/// Onboarding was updated
|
||||
OnboardingUpdate = 167,
|
||||
/// Voice channel status was updated
|
||||
VoiceChannelStatusUpdate = 192,
|
||||
/// Voice channel status was deleted
|
||||
VoiceChannelStatusDelete = 193
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
|
||||
pub struct AuditEntryInfo {
|
||||
pub application_id: Option<Snowflake>,
|
||||
pub auto_moderation_rule_name: Option<String>,
|
||||
pub auto_moderation_rule_trigger_type: Option<AutoModerationRuleTriggerType>,
|
||||
pub channel_id: Option<Snowflake>,
|
||||
// #[serde(option_string)]
|
||||
pub count: Option<u64>,
|
||||
// #[serde(option_string)]
|
||||
pub delete_member_days: Option<u64>,
|
||||
/// The ID of the overwritten entity
|
||||
pub id: Option<Snowflake>,
|
||||
pub integration_type: Option<IntegrationType>,
|
||||
// #[serde(option_string)]
|
||||
pub members_removed: Option<u64>,
|
||||
// #[serde(option_string)]
|
||||
pub message_id: Option<u64>,
|
||||
pub role_name: Option<String>,
|
||||
#[serde(rename = "type")]
|
||||
pub overwrite_type: Option<PermissionOverwriteType>,
|
||||
pub status: Option<String>
|
||||
}
|
|
@ -3,15 +3,16 @@
|
|||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_aux::prelude::deserialize_string_from_number;
|
||||
use serde::{Deserialize, Deserializer, Serialize};
|
||||
use serde_repr::{Deserialize_repr, Serialize_repr};
|
||||
use std::fmt::Debug;
|
||||
use std::fmt::{Debug, Formatter};
|
||||
use std::str::FromStr;
|
||||
|
||||
use crate::types::{
|
||||
PermissionFlags, Shared,
|
||||
entities::{GuildMember, User},
|
||||
utils::Snowflake,
|
||||
serde::string_or_u64
|
||||
};
|
||||
|
||||
#[cfg(feature = "client")]
|
||||
|
@ -25,6 +26,8 @@ use crate::gateway::Updateable;
|
|||
|
||||
#[cfg(feature = "client")]
|
||||
use chorus_macros::{observe_option_vec, Composite, Updateable};
|
||||
use serde::de::{Error, Visitor};
|
||||
|
||||
|
||||
#[derive(Default, Debug, Serialize, Deserialize, Clone)]
|
||||
#[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))]
|
||||
|
@ -156,14 +159,73 @@ pub struct PermissionOverwrite {
|
|||
}
|
||||
|
||||
|
||||
#[derive(Debug, Serialize_repr, Deserialize_repr, Clone, PartialEq, Eq, PartialOrd)]
|
||||
#[derive(Debug, Serialize_repr, Clone, PartialEq, Eq, PartialOrd)]
|
||||
#[repr(u8)]
|
||||
/// # Reference
|
||||
///
|
||||
/// See <https://docs.discord.sex/resources/channel#permission-overwrite-type>
|
||||
pub enum PermissionOverwriteType {
|
||||
Role = 0,
|
||||
Member = 1,
|
||||
}
|
||||
|
||||
impl From<u8> for PermissionOverwriteType {
|
||||
fn from(v: u8) -> Self {
|
||||
match v {
|
||||
0 => PermissionOverwriteType::Role,
|
||||
1 => PermissionOverwriteType::Member,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for PermissionOverwriteType {
|
||||
type Err = serde::de::value::Error;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s {
|
||||
"role" => Ok(PermissionOverwriteType::Role),
|
||||
"member" => Ok(PermissionOverwriteType::Member),
|
||||
_ => Err(Self::Err::custom("invalid permission overwrite type")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct PermissionOverwriteTypeVisitor;
|
||||
|
||||
impl<'de> Visitor<'de> for PermissionOverwriteTypeVisitor {
|
||||
type Value = PermissionOverwriteType;
|
||||
|
||||
fn expecting(&self, formatter: &mut Formatter) -> std::fmt::Result {
|
||||
formatter.write_str("a valid permission overwrite type")
|
||||
}
|
||||
|
||||
fn visit_u8<E>(self, v: u8) -> Result<Self::Value, E> where E: Error {
|
||||
Ok(PermissionOverwriteType::from(v))
|
||||
}
|
||||
|
||||
fn visit_u64<E>(self, v: u64) -> Result<Self::Value, E> where E: Error {
|
||||
self.visit_u8(v as u8)
|
||||
}
|
||||
|
||||
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E> where E: Error {
|
||||
PermissionOverwriteType::from_str(v)
|
||||
.map_err(E::custom)
|
||||
}
|
||||
|
||||
fn visit_string<E>(self, v: String) -> Result<Self::Value, E> where E: Error {
|
||||
self.visit_str(v.as_str())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for PermissionOverwriteType {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> where D: Deserializer<'de> {
|
||||
let val = deserializer.deserialize_any(PermissionOverwriteTypeVisitor)?;
|
||||
|
||||
Ok(val)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)]
|
||||
/// # Reference
|
||||
/// See <https://discord-userdoccers.vercel.app/resources/channel#thread-metadata-object>
|
||||
|
@ -274,4 +336,4 @@ pub enum ChannelType {
|
|||
pub struct FollowedChannel {
|
||||
pub channel_id: Snowflake,
|
||||
pub webhook_id: Snowflake
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,7 +23,7 @@ use super::PublicUser;
|
|||
use crate::gateway::Updateable;
|
||||
|
||||
#[cfg(feature = "client")]
|
||||
use chorus_macros::{observe_option_vec, observe_vec, Composite, Updateable};
|
||||
use chorus_macros::{observe_vec, Composite, Updateable};
|
||||
|
||||
#[cfg(feature = "client")]
|
||||
use crate::types::Composite;
|
||||
|
@ -46,10 +46,12 @@ pub struct Guild {
|
|||
pub approximate_presence_count: Option<i32>,
|
||||
pub banner: Option<String>,
|
||||
#[cfg_attr(feature = "sqlx", sqlx(skip))]
|
||||
pub bans: Option<Vec<GuildBan>>,
|
||||
#[serde(default)]
|
||||
pub bans: Vec<GuildBan>,
|
||||
#[cfg_attr(feature = "sqlx", sqlx(skip))]
|
||||
#[cfg_attr(feature = "client", observe_option_vec)]
|
||||
pub channels: Option<Vec<Shared<Channel>>>,
|
||||
#[cfg_attr(feature = "client", observe_vec)]
|
||||
#[serde(default)]
|
||||
pub channels: Vec<Shared<Channel>>,
|
||||
pub default_message_notifications: Option<MessageNotificationLevel>,
|
||||
pub description: Option<String>,
|
||||
pub discovery_splash: Option<String>,
|
||||
|
@ -66,7 +68,8 @@ pub struct Guild {
|
|||
pub icon_hash: Option<String>,
|
||||
pub id: Snowflake,
|
||||
#[cfg_attr(feature = "sqlx", sqlx(skip))]
|
||||
pub invites: Option<Vec<GuildInvite>>,
|
||||
#[serde(default)]
|
||||
pub invites: Vec<GuildInvite>,
|
||||
#[cfg_attr(feature = "sqlx", sqlx(skip))]
|
||||
pub joined_at: Option<DateTime<Utc>>,
|
||||
pub large: Option<bool>,
|
||||
|
@ -92,25 +95,29 @@ pub struct Guild {
|
|||
pub public_updates_channel_id: Option<Snowflake>,
|
||||
pub region: Option<String>,
|
||||
#[cfg_attr(feature = "sqlx", sqlx(skip))]
|
||||
#[cfg_attr(feature = "client", observe_option_vec)]
|
||||
pub roles: Option<Vec<Shared<RoleObject>>>,
|
||||
#[cfg_attr(feature = "client", observe_vec)]
|
||||
#[serde(default)]
|
||||
pub roles: Vec<Shared<RoleObject>>,
|
||||
#[cfg_attr(feature = "sqlx", sqlx(skip))]
|
||||
pub rules_channel: Option<String>,
|
||||
pub rules_channel_id: Option<Snowflake>,
|
||||
pub splash: Option<String>,
|
||||
#[cfg_attr(feature = "sqlx", sqlx(skip))]
|
||||
pub stickers: Option<Vec<Sticker>>,
|
||||
#[serde(default)]
|
||||
pub stickers: Vec<Sticker>,
|
||||
pub system_channel_flags: Option<SystemChannelFlags>,
|
||||
pub system_channel_id: Option<Snowflake>,
|
||||
#[cfg_attr(feature = "sqlx", sqlx(skip))]
|
||||
pub vanity_url_code: Option<String>,
|
||||
pub verification_level: Option<VerificationLevel>,
|
||||
#[serde(default)]
|
||||
#[cfg_attr(feature = "sqlx", sqlx(skip))]
|
||||
#[cfg_attr(feature = "client", observe_option_vec)]
|
||||
pub voice_states: Option<Vec<Shared<VoiceState>>>,
|
||||
#[cfg_attr(feature = "client", observe_vec)]
|
||||
pub voice_states: Vec<Shared<VoiceState>>,
|
||||
#[serde(default)]
|
||||
#[cfg_attr(feature = "sqlx", sqlx(skip))]
|
||||
#[cfg_attr(feature = "client", observe_option_vec)]
|
||||
pub webhooks: Option<Vec<Shared<Webhook>>>,
|
||||
#[cfg_attr(feature = "client", observe_vec)]
|
||||
pub webhooks: Vec<Shared<Webhook>>,
|
||||
#[cfg(feature = "sqlx")]
|
||||
pub welcome_screen: sqlx::types::Json<Option<WelcomeScreenObject>>,
|
||||
#[cfg(not(feature = "sqlx"))]
|
||||
|
@ -226,6 +233,7 @@ impl std::cmp::PartialEq for Guild {
|
|||
#[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq, Eq, Hash)]
|
||||
#[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))]
|
||||
pub struct GuildBan {
|
||||
#[cfg_attr(feature = "sqlx", sqlx(skip))]
|
||||
pub user: PublicUser,
|
||||
pub reason: Option<String>,
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
use chrono::{DateTime, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::types::{GuildMemberFlags, Shared};
|
||||
use crate::types::{GuildMemberFlags, PermissionFlags, Shared};
|
||||
use crate::types::{entities::PublicUser, Snowflake};
|
||||
|
||||
#[derive(Debug, Deserialize, Default, Serialize, Clone)]
|
||||
|
@ -27,6 +27,7 @@ pub struct GuildMember {
|
|||
pub mute: bool,
|
||||
pub flags: Option<GuildMemberFlags>,
|
||||
pub pending: Option<bool>,
|
||||
pub permissions: Option<String>,
|
||||
#[serde(default)]
|
||||
pub permissions: PermissionFlags,
|
||||
pub communication_disabled_until: Option<DateTime<Utc>>,
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ pub struct Integration {
|
|||
pub id: Snowflake,
|
||||
pub name: String,
|
||||
#[serde(rename = "type")]
|
||||
pub integration_type: String,
|
||||
pub integration_type: IntegrationType,
|
||||
pub enabled: bool,
|
||||
pub syncing: Option<bool>,
|
||||
pub role_id: Option<String>,
|
||||
|
@ -43,3 +43,15 @@ pub struct IntegrationAccount {
|
|||
pub id: String,
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
|
||||
#[cfg_attr(feature = "sqlx", sqlx(rename_all = "snake_case"))]
|
||||
pub enum IntegrationType {
|
||||
#[default]
|
||||
Twitch,
|
||||
Youtube,
|
||||
Discord,
|
||||
GuildSubscription,
|
||||
}
|
|
@ -13,7 +13,7 @@ use super::{Application, Channel, GuildMember, NSFWLevel, User};
|
|||
|
||||
/// Represents a code that when used, adds a user to a guild or group DM channel, or creates a relationship between two users.
|
||||
/// See <https://discord-userdoccers.vercel.app/resources/invite#invite-object>
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[derive(Default, Debug, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))]
|
||||
pub struct Invite {
|
||||
#[cfg_attr(feature = "sqlx", sqlx(skip))]
|
||||
|
|
|
@ -177,7 +177,7 @@ pub struct ChannelMention {
|
|||
pub struct Embed {
|
||||
title: Option<String>,
|
||||
#[serde(rename = "type")]
|
||||
embed_type: Option<String>,
|
||||
embed_type: Option<EmbedType>,
|
||||
description: Option<String>,
|
||||
url: Option<String>,
|
||||
timestamp: Option<String>,
|
||||
|
@ -191,6 +191,24 @@ pub struct Embed {
|
|||
fields: Option<Vec<EmbedField>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum EmbedType {
|
||||
#[deprecated]
|
||||
ApplicationNews,
|
||||
Article,
|
||||
AutoModerationMessage,
|
||||
AutoModerationNotification,
|
||||
Gift,
|
||||
#[serde(rename = "gifv")]
|
||||
GifVideo,
|
||||
Image,
|
||||
Link,
|
||||
PostPreview,
|
||||
Rich,
|
||||
Video
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct EmbedFooter {
|
||||
text: String,
|
||||
|
@ -291,7 +309,7 @@ pub enum MessageType {
|
|||
Default = 0,
|
||||
/// A message sent when a user is added to a group DM or thread
|
||||
RecipientAdd = 1,
|
||||
/// A message sent when a user is removed from a group DM or thread
|
||||
/// A message sent when a user is removed from a group DM or thread
|
||||
RecipientRemove = 2,
|
||||
/// A message sent when a user creates a call in a private channel
|
||||
Call = 3,
|
||||
|
@ -335,7 +353,7 @@ pub enum MessageType {
|
|||
ThreadStarterMessage = 21,
|
||||
/// A message sent to remind users to invite friends to a guild
|
||||
GuildInviteReminder = 22,
|
||||
/// A message sent when a user uses a context menu command
|
||||
/// A message sent when a user uses a context menu command
|
||||
ContextMenuCommand = 23,
|
||||
/// A message sent when auto moderation takes an action
|
||||
AutoModerationAction = 24,
|
||||
|
@ -429,4 +447,4 @@ pub struct PartialEmoji {
|
|||
pub enum ReactionType {
|
||||
Normal = 0,
|
||||
Burst = 1, // The dreaded super reactions
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
use bitflags::bitflags;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_aux::prelude::{deserialize_option_number_from_string, deserialize_string_from_number};
|
||||
use serde_aux::prelude::deserialize_option_number_from_string;
|
||||
use std::fmt::Debug;
|
||||
|
||||
use crate::types::utils::Snowflake;
|
||||
|
@ -34,8 +34,7 @@ pub struct RoleObject {
|
|||
pub unicode_emoji: Option<String>,
|
||||
pub position: u16,
|
||||
#[serde(default)]
|
||||
#[serde(deserialize_with = "deserialize_string_from_number")]
|
||||
pub permissions: String,
|
||||
pub permissions: PermissionFlags,
|
||||
pub managed: bool,
|
||||
pub mentionable: bool,
|
||||
#[cfg(feature = "sqlx")]
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_repr::{Deserialize_repr, Serialize_repr};
|
||||
|
||||
use crate::types::{entities::User, utils::Snowflake, Shared};
|
||||
|
||||
|
@ -18,15 +19,17 @@ pub struct Sticker {
|
|||
pub pack_id: Option<Snowflake>,
|
||||
pub name: String,
|
||||
pub description: Option<String>,
|
||||
pub tags: String,
|
||||
pub tags: Option<String>,
|
||||
#[cfg_attr(feature = "sqlx", sqlx(skip))]
|
||||
pub asset: Option<String>,
|
||||
#[serde(rename = "type")]
|
||||
pub sticker_type: u8,
|
||||
pub format_type: u8,
|
||||
pub sticker_type: StickerType,
|
||||
pub format_type: StickerFormatType,
|
||||
pub available: Option<bool>,
|
||||
pub guild_id: Option<Snowflake>,
|
||||
#[cfg_attr(feature = "sqlx", sqlx(skip))]
|
||||
pub user: Option<Shared<User>>,
|
||||
#[cfg_attr(feature = "sqlx", sqlx(skip))]
|
||||
pub sort_value: Option<u8>,
|
||||
}
|
||||
|
||||
|
@ -108,6 +111,18 @@ impl PartialOrd for Sticker {
|
|||
}
|
||||
}
|
||||
|
||||
impl Sticker {
|
||||
pub fn tags(&self) -> Vec<String> {
|
||||
self.tags
|
||||
.as_ref()
|
||||
.map_or(vec![], |s|
|
||||
s.split(',')
|
||||
.map(|tag| tag.trim().to_string())
|
||||
.collect()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
/// A partial sticker object.
|
||||
///
|
||||
|
@ -118,5 +133,61 @@ impl PartialOrd for Sticker {
|
|||
pub struct StickerItem {
|
||||
pub id: Snowflake,
|
||||
pub name: String,
|
||||
pub format_type: u8,
|
||||
pub format_type: StickerFormatType,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Hash, Serialize_repr, Deserialize_repr)]
|
||||
#[repr(u8)]
|
||||
#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
|
||||
#[serde(rename = "SCREAMING_SNAKE_CASE")]
|
||||
/// # Reference
|
||||
/// See <https://docs.discord.sex/resources/sticker#sticker-types>
|
||||
pub enum StickerType {
|
||||
/// An official sticker in a current or legacy purchasable pack
|
||||
Standard = 1,
|
||||
#[default]
|
||||
/// A sticker uploaded to a guild for the guild's members
|
||||
Guild = 2,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Hash, Serialize_repr, Deserialize_repr)]
|
||||
#[repr(u8)]
|
||||
#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
|
||||
/// # Reference
|
||||
/// See <https://docs.discord.sex/resources/sticker#sticker-format-types>
|
||||
pub enum StickerFormatType {
|
||||
#[default]
|
||||
/// A PNG image
|
||||
PNG = 1,
|
||||
/// An animated PNG image, using the APNG format - uses CDN
|
||||
APNG = 2,
|
||||
/// A lottie animation; requires the VERIFIED and/or PARTNERED guild feature - uses CDN
|
||||
LOTTIE = 3,
|
||||
/// An animated GIF image - does not use CDN
|
||||
GIF = 4,
|
||||
}
|
||||
|
||||
impl StickerFormatType {
|
||||
pub fn is_animated(&self) -> bool {
|
||||
matches!(self, StickerFormatType::APNG | StickerFormatType::LOTTIE | StickerFormatType::GIF)
|
||||
}
|
||||
|
||||
pub const fn to_mime(&self) -> &'static str {
|
||||
match self {
|
||||
StickerFormatType::PNG => "image/png",
|
||||
StickerFormatType::APNG => "image/apng",
|
||||
StickerFormatType::LOTTIE => "application/json",
|
||||
StickerFormatType::GIF => "image/gif",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_mime(mime: &str) -> Option<Self> {
|
||||
match mime {
|
||||
"image/png" => Some(StickerFormatType::PNG),
|
||||
"image/apng" => Some(StickerFormatType::APNG),
|
||||
"application/json" => Some(StickerFormatType::LOTTIE),
|
||||
"image/gif" => Some(StickerFormatType::GIF),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,11 +4,9 @@
|
|||
|
||||
use crate::types::utils::Snowflake;
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_aux::prelude::deserialize_option_number_from_string;
|
||||
use std::fmt::Debug;
|
||||
use std::num::ParseIntError;
|
||||
use std::str::FromStr;
|
||||
|
||||
#[cfg(feature = "client")]
|
||||
use crate::gateway::Updateable;
|
||||
|
@ -47,7 +45,7 @@ pub struct User {
|
|||
pub bot: Option<bool>,
|
||||
pub system: Option<bool>,
|
||||
pub mfa_enabled: Option<bool>,
|
||||
pub accent_color: Option<u8>,
|
||||
pub accent_color: Option<u32>,
|
||||
#[cfg_attr(feature = "sqlx", sqlx(default))]
|
||||
pub locale: Option<String>,
|
||||
pub verified: Option<bool>,
|
||||
|
@ -60,10 +58,10 @@ pub struct User {
|
|||
pub premium_since: Option<DateTime<Utc>>,
|
||||
pub premium_type: Option<u8>,
|
||||
pub pronouns: Option<String>,
|
||||
pub public_flags: Option<u32>,
|
||||
pub public_flags: Option<UserFlags>,
|
||||
pub banner: Option<String>,
|
||||
pub bio: Option<String>,
|
||||
pub theme_colors: Option<Vec<u8>>,
|
||||
pub theme_colors: Option<Vec<u32>>,
|
||||
pub phone: Option<String>,
|
||||
pub nsfw_allowed: Option<bool>,
|
||||
pub premium: Option<bool>,
|
||||
|
@ -78,15 +76,15 @@ pub struct PublicUser {
|
|||
pub username: Option<String>,
|
||||
pub discriminator: Option<String>,
|
||||
pub avatar: Option<String>,
|
||||
pub accent_color: Option<u8>,
|
||||
pub accent_color: Option<u32>,
|
||||
pub banner: Option<String>,
|
||||
pub theme_colors: Option<Vec<u8>>,
|
||||
pub theme_colors: Option<Vec<u32>>,
|
||||
pub pronouns: Option<String>,
|
||||
pub bot: Option<bool>,
|
||||
pub bio: Option<String>,
|
||||
pub premium_type: Option<u8>,
|
||||
pub premium_since: Option<DateTime<Utc>>,
|
||||
pub public_flags: Option<u32>,
|
||||
pub public_flags: Option<UserFlags>,
|
||||
}
|
||||
|
||||
impl From<User> for PublicUser {
|
||||
|
|
|
@ -6,6 +6,7 @@ use chrono::{serde::ts_milliseconds_option, Utc};
|
|||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::types::Shared;
|
||||
use serde_aux::field_attributes::deserialize_option_number_from_string;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
|
||||
#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
|
||||
|
@ -37,7 +38,7 @@ pub enum UserTheme {
|
|||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))]
|
||||
pub struct UserSettings {
|
||||
pub afk_timeout: u16,
|
||||
pub afk_timeout: Option<u16>,
|
||||
pub allow_accessibility_detection: bool,
|
||||
pub animate_emoji: bool,
|
||||
pub animate_stickers: u8,
|
||||
|
@ -90,7 +91,7 @@ pub struct UserSettings {
|
|||
impl Default for UserSettings {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
afk_timeout: 3600,
|
||||
afk_timeout: Some(3600),
|
||||
allow_accessibility_detection: true,
|
||||
animate_emoji: true,
|
||||
animate_stickers: 0,
|
||||
|
@ -148,10 +149,17 @@ impl Default for FriendSourceFlags {
|
|||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct GuildFolder {
|
||||
pub color: u32,
|
||||
pub color: Option<u32>,
|
||||
pub guild_ids: Vec<String>,
|
||||
pub id: u16,
|
||||
pub name: String,
|
||||
// FIXME: What is this thing?
|
||||
// It's not a snowflake, and it's sometimes a string and sometimes an integer.
|
||||
//
|
||||
// Ex: 1249181105
|
||||
//
|
||||
// It can also be negative somehow? Ex: -1176643795
|
||||
#[serde(deserialize_with = "deserialize_option_number_from_string")]
|
||||
pub id: Option<i64>,
|
||||
pub name: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
|
|
|
@ -34,9 +34,11 @@ use crate::types::{
|
|||
#[cfg_attr(feature = "client", derive(Composite))]
|
||||
pub struct VoiceState {
|
||||
pub guild_id: Option<Snowflake>,
|
||||
#[cfg_attr(feature = "sqlx", sqlx(skip))]
|
||||
pub guild: Option<Guild>,
|
||||
pub channel_id: Option<Snowflake>,
|
||||
pub user_id: Snowflake,
|
||||
#[cfg_attr(feature = "sqlx", sqlx(skip))]
|
||||
pub member: Option<Shared<GuildMember>>,
|
||||
/// Includes alphanumeric characters, not a snowflake
|
||||
pub session_id: String,
|
||||
|
|
|
@ -49,11 +49,7 @@ impl UpdateMessage<Guild> for ChannelCreate {
|
|||
fn update(&mut self, object_to_update: Shared<Guild>) {
|
||||
let mut write = object_to_update.write().unwrap();
|
||||
let update = self.channel.clone().into_shared();
|
||||
if write.channels.is_some() {
|
||||
write.channels.as_mut().unwrap().push(update);
|
||||
} else {
|
||||
write.channels = Some(Vec::from([update]));
|
||||
}
|
||||
write.channels.push(update);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -122,12 +118,12 @@ impl UpdateMessage<Guild> for ChannelDelete {
|
|||
return;
|
||||
}
|
||||
let mut write = object_to_update.write().unwrap();
|
||||
if write.channels.is_none() {
|
||||
if write.channels.is_empty() {
|
||||
return;
|
||||
}
|
||||
for (iteration, item) in (0_u32..).zip(write.channels.as_mut().unwrap().iter()) {
|
||||
for (iteration, item) in (0_u32..).zip(write.channels.iter()) {
|
||||
if item.read().unwrap().id == self.id().unwrap() {
|
||||
write.channels.as_mut().unwrap().remove(iteration as usize);
|
||||
write.channels.remove(iteration as usize);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -214,15 +214,9 @@ impl UpdateMessage<Guild> for GuildRoleCreate {
|
|||
|
||||
fn update(&mut self, object_to_update: Shared<Guild>) {
|
||||
let mut object_to_update = object_to_update.write().unwrap();
|
||||
if object_to_update.roles.is_some() {
|
||||
object_to_update
|
||||
.roles
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.push(self.role.clone().into_shared());
|
||||
} else {
|
||||
object_to_update.roles = Some(Vec::from([self.role.clone().into_shared()]));
|
||||
}
|
||||
object_to_update
|
||||
.roles
|
||||
.push(self.role.clone().into_shared());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -121,8 +121,8 @@ pub struct MessageReactionRemoveEmoji {
|
|||
///
|
||||
/// {"t":"MESSAGE_ACK","s":3,"op":0,"d":{"version":52,"message_id":"1107236673638633472","last_viewed":null,"flags":null,"channel_id":"967363950217936897"}}
|
||||
pub struct MessageACK {
|
||||
/// ?
|
||||
pub version: u16,
|
||||
// No ideas. See 206933
|
||||
pub version: u32,
|
||||
pub message_id: Snowflake,
|
||||
/// This is an integer???
|
||||
/// Not even unix, see '3070'???
|
||||
|
|
|
@ -12,9 +12,12 @@ use crate::types::{GuildMember, Snowflake, VoiceState};
|
|||
///
|
||||
/// Seems to be passively set to update the client on guild details (though, why not just send the update events?)
|
||||
pub struct PassiveUpdateV1 {
|
||||
#[serde(default)]
|
||||
pub voice_states: Vec<VoiceState>,
|
||||
pub members: Option<Vec<GuildMember>>,
|
||||
#[serde(default)]
|
||||
pub members: Vec<GuildMember>,
|
||||
pub guild_id: Snowflake,
|
||||
#[serde(default)]
|
||||
pub channels: Vec<ChannelUnreadUpdateObject>,
|
||||
}
|
||||
|
||||
|
|
|
@ -20,12 +20,15 @@ pub struct UpdatePresence {
|
|||
|
||||
#[derive(Debug, Deserialize, Serialize, Default, Clone, PartialEq, WebSocketEvent)]
|
||||
/// Received to tell the client that a user updated their presence / status
|
||||
///
|
||||
/// See <https://discord.com/developers/docs/topics/gateway-events#presence-update-presence-update-event-fields>
|
||||
/// (Same structure as <https://docs.discord.sex/resources/presence#presence-object>)
|
||||
pub struct PresenceUpdate {
|
||||
pub user: PublicUser,
|
||||
#[serde(default)]
|
||||
pub guild_id: Option<Snowflake>,
|
||||
pub status: UserStatus,
|
||||
#[serde(default)]
|
||||
pub activities: Vec<Activity>,
|
||||
pub client_status: ClientStatusObject,
|
||||
}
|
||||
|
|
|
@ -6,13 +6,14 @@ use serde::{Deserialize, Serialize};
|
|||
|
||||
use crate::types::entities::{Guild, User};
|
||||
use crate::types::events::{Session, WebSocketEvent};
|
||||
use crate::types::interfaces::ClientStatusObject;
|
||||
use crate::types::{Activity, GuildMember, PresenceUpdate, VoiceState};
|
||||
use crate::types::{Activity, Channel, ClientStatusObject, GuildMember, PresenceUpdate, Snowflake, VoiceState};
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, Default, Clone, WebSocketEvent)]
|
||||
/// 1/2 half documented;
|
||||
/// 1/2 officially documented;
|
||||
/// Received after identifying, provides initial user info;
|
||||
/// See <https://discord.com/developers/docs/topics/gateway-events#ready;>
|
||||
///
|
||||
/// See <https://docs.discord.sex/topics/gateway-events#ready> and <https://discord.com/developers/docs/topics/gateway-events#ready>
|
||||
// TODO: There are a LOT of fields missing here
|
||||
pub struct GatewayReady {
|
||||
pub analytics_token: Option<String>,
|
||||
pub auth_session_id_hash: Option<String>,
|
||||
|
@ -32,36 +33,47 @@ pub struct GatewayReady {
|
|||
|
||||
#[derive(Debug, Deserialize, Serialize, Default, Clone, WebSocketEvent)]
|
||||
/// Officially Undocumented;
|
||||
/// Sent after the READY event when a client is a user, seems to somehow add onto the ready event;
|
||||
/// Sent after the READY event when a client is a user,
|
||||
/// seems to somehow add onto the ready event;
|
||||
///
|
||||
/// See <https://docs.discord.sex/topics/gateway-events#ready-supplemental>
|
||||
pub struct GatewayReadySupplemental {
|
||||
/// The presences of the user's relationships and guild presences sent at startup
|
||||
pub merged_presences: MergedPresences,
|
||||
pub merged_members: Vec<Vec<GuildMember>>,
|
||||
// ?
|
||||
pub lazy_private_channels: Vec<serde_json::Value>,
|
||||
pub lazy_private_channels: Vec<Channel>,
|
||||
pub guilds: Vec<SupplementalGuild>,
|
||||
// ? pomelo
|
||||
// "Upcoming changes that the client should disclose to the user" (discord.sex)
|
||||
pub disclose: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, Default, Clone)]
|
||||
/// See <https://docs.discord.sex/topics/gateway-events#merged-presences-structure>
|
||||
pub struct MergedPresences {
|
||||
pub guilds: Vec<Vec<MergedPresenceGuild>>,
|
||||
/// "Presences of the user's guilds in the same order as the guilds array in ready"
|
||||
/// (discord.sex)
|
||||
pub guilds: Vec<Vec<MergedPresenceGuild>>,
|
||||
/// "Presences of the user's friends and implicit relationships" (discord.sex)
|
||||
pub friends: Vec<MergedPresenceFriend>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, Default, Clone)]
|
||||
/// Not documented even unofficially
|
||||
pub struct MergedPresenceFriend {
|
||||
pub user_id: String,
|
||||
pub user_id: Snowflake,
|
||||
pub status: String,
|
||||
/// Looks like ms??
|
||||
pub last_modified: u128,
|
||||
// Looks like ms??
|
||||
//
|
||||
// Not always sent
|
||||
pub last_modified: Option<u128>,
|
||||
pub client_status: ClientStatusObject,
|
||||
pub activities: Vec<Activity>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, Default, Clone)]
|
||||
/// Not documented even unofficially
|
||||
pub struct MergedPresenceGuild {
|
||||
pub user_id: String,
|
||||
pub user_id: Snowflake,
|
||||
pub status: String,
|
||||
// ?
|
||||
pub game: Option<serde_json::Value>,
|
||||
|
@ -70,8 +82,10 @@ pub struct MergedPresenceGuild {
|
|||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, Default, Clone)]
|
||||
/// See <https://docs.discord.sex/topics/gateway-events#supplemental-guild-structure>
|
||||
pub struct SupplementalGuild {
|
||||
pub id: Snowflake,
|
||||
pub voice_states: Option<Vec<VoiceState>>,
|
||||
pub id: String,
|
||||
/// Field not documented even unofficially
|
||||
pub embedded_activities: Vec<serde_json::Value>,
|
||||
}
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
use crate::types::{ApplicationCommand, AuditLogActionType, AuditLogEntry, AutoModerationRule, Channel, GuildScheduledEvent, Integration, Snowflake, User, Webhook};
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, Clone)]
|
||||
pub struct AuditLogObject {
|
||||
pub audit_log_entries: Vec<AuditLogEntry>,
|
||||
pub application_commands: Vec<ApplicationCommand>,
|
||||
pub auto_moderation_rules: Vec<AutoModerationRule>,
|
||||
pub guild_scheduled_events: Vec<GuildScheduledEvent>,
|
||||
pub integrations: Vec<Integration>,
|
||||
pub threads: Vec<Channel>,
|
||||
pub users: Vec<User>,
|
||||
pub webhooks: Vec<Webhook>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, Clone)]
|
||||
pub struct GetAuditLogsQuery {
|
||||
pub before: Option<Snowflake>,
|
||||
pub after: Option<Snowflake>,
|
||||
pub limit: Option<u8>,
|
||||
pub user_id: Option<Snowflake>,
|
||||
pub action_type: Option<AuditLogActionType>
|
||||
}
|
|
@ -3,10 +3,9 @@
|
|||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
use bitflags::bitflags;
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
use serde::de::Visitor;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::types::{ChannelType, DefaultReaction, Error, entities::PermissionOverwrite, Snowflake};
|
||||
use crate::types::{ChannelType, DefaultReaction, entities::PermissionOverwrite, Snowflake};
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, Default, PartialEq, PartialOrd)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
|
@ -188,4 +187,4 @@ pub struct AddFollowingChannelSchema {
|
|||
pub struct CreateWebhookSchema {
|
||||
pub name: String,
|
||||
pub avatar: Option<String>,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,16 +2,14 @@
|
|||
// 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 bitflags::bitflags;
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::types::entities::Channel;
|
||||
use crate::types::types::guild_configuration::GuildFeatures;
|
||||
use crate::types::{
|
||||
Emoji, ExplicitContentFilterLevel, MessageNotificationLevel, Snowflake, Sticker,
|
||||
SystemChannelFlags, VerificationLevel,
|
||||
};
|
||||
use crate::types::{Emoji, ExplicitContentFilterLevel, GenericSearchQueryWithLimit, MessageNotificationLevel, Snowflake, Sticker, StickerFormatType, SystemChannelFlags, VerificationLevel, WelcomeScreenChannel};
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
|
@ -32,10 +30,20 @@ pub struct GuildCreateSchema {
|
|||
/// Represents the schema which needs to be sent to create a Guild Ban.
|
||||
/// See: <https://discord-userdoccers.vercel.app/resources/guild#create-guild-ban>
|
||||
pub struct GuildBanCreateSchema {
|
||||
/// Deprecated
|
||||
pub delete_message_days: Option<u8>,
|
||||
pub delete_message_seconds: Option<u32>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, Default, Clone, Eq, PartialEq)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
/// Represents the schema which needs to be sent to create a Guild Ban.
|
||||
/// See: <https://discord-userdoccers.vercel.app/resources/guild#create-guild-ban>
|
||||
pub struct GuildBanBulkCreateSchema {
|
||||
pub user_ids: Vec<Snowflake>,
|
||||
pub delete_message_seconds: Option<u32>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, Default, Clone, Eq, PartialEq)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
/// Represents the schema used to modify a guild.
|
||||
|
@ -77,6 +85,31 @@ pub struct GetUserGuildSchema {
|
|||
pub with_counts: Option<bool>,
|
||||
}
|
||||
|
||||
impl GetUserGuildSchema {
|
||||
/// Converts self to query string parameters
|
||||
pub fn to_query(self) -> Vec<(&'static str, String)> {
|
||||
let mut query = Vec::with_capacity(4);
|
||||
|
||||
if let Some(before) = self.before {
|
||||
query.push(("before", before.to_string()));
|
||||
}
|
||||
|
||||
if let Some(after) = self.after {
|
||||
query.push(("after", after.to_string()));
|
||||
}
|
||||
|
||||
if let Some(limit) = self.limit {
|
||||
query.push(("limit", limit.to_string()));
|
||||
}
|
||||
|
||||
if let Some(with_counts) = self.with_counts {
|
||||
query.push(("with_counts", with_counts.to_string()));
|
||||
}
|
||||
|
||||
query
|
||||
}
|
||||
}
|
||||
|
||||
impl std::default::Default for GetUserGuildSchema {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
|
@ -119,6 +152,12 @@ impl Default for GuildMemberSearchSchema {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, PartialOrd, Eq, Ord)]
|
||||
pub struct GuildGetMembersQuery {
|
||||
pub limit: Option<u16>,
|
||||
pub after: Option<Snowflake>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, PartialOrd, Eq, Ord)]
|
||||
pub struct ModifyGuildMemberSchema {
|
||||
pub nick: Option<String>,
|
||||
|
@ -131,7 +170,7 @@ pub struct ModifyGuildMemberSchema {
|
|||
}
|
||||
|
||||
bitflags! {
|
||||
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, PartialOrd, Ord)]
|
||||
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, chorus_macros::SerdeBitFlags)]
|
||||
#[cfg_attr(feature = "sqlx", derive(chorus_macros::SqlxBitFlags))]
|
||||
/// Represents the flags of a Guild Member.
|
||||
///
|
||||
|
@ -174,3 +213,200 @@ pub struct GuildBansQuery {
|
|||
pub after: Option<Snowflake>,
|
||||
pub limit: Option<u16>,
|
||||
}
|
||||
|
||||
|
||||
/// Max query length is 32 characters.
|
||||
/// The limit argument is a number between 1 and 10, defaults to 10.
|
||||
pub type GuildBansSearchQuery = GenericSearchQueryWithLimit;
|
||||
|
||||
/// Query is partial or full, username or nickname.
|
||||
/// Limit argument is a number between 1 and 1000, defaults to 1.
|
||||
pub type GuildMembersSearchQuery = GenericSearchQueryWithLimit;
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq)]
|
||||
/// A guild's progress on meeting the requirements of joining discovery.
|
||||
///
|
||||
/// Certain guilds, such as those that are verified, are exempt from discovery requirements. These guilds will not have a fully populated discovery requirements object, and are guaranteed to receive only sufficient and sufficient_without_grace_period.
|
||||
///
|
||||
/// # Reference:
|
||||
/// See <https://docs.discord.sex/resources/discovery#discovery-requirements-object>
|
||||
pub struct GuildDiscoveryRequirements {
|
||||
pub guild_id: Option<Snowflake>,
|
||||
pub safe_environment: Option<bool>,
|
||||
pub healthy: Option<bool>,
|
||||
pub health_score_pending: Option<bool>,
|
||||
pub size: Option<bool>,
|
||||
pub nsfw_properties: Option<GuildDiscoveryNsfwProperties>,
|
||||
pub protected: Option<bool>,
|
||||
pub sufficient: Option<bool>,
|
||||
pub sufficient_without_grace_period: Option<bool>,
|
||||
pub valid_rules_channel: Option<bool>,
|
||||
pub retention_healthy: Option<bool>,
|
||||
pub engagement_healthy: Option<bool>,
|
||||
pub age: Option<bool>,
|
||||
pub minimum_age: Option<u16>,
|
||||
pub health_score: Option<GuildDiscoveryHealthScore>,
|
||||
pub minimum_size: Option<u64>,
|
||||
pub grace_period_end_date: Option<DateTime<Utc>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)]
|
||||
/// # Reference:
|
||||
/// See <https://docs.discord.sex/resources/discovery#discovery-nsfw-properties-structure>
|
||||
pub struct GuildDiscoveryNsfwProperties {
|
||||
pub channels: Vec<Snowflake>,
|
||||
pub channel_banned_keywords: HashMap<Snowflake, Vec<String>>,
|
||||
pub name: Option<String>,
|
||||
pub name_banned_keywords: Vec<String>,
|
||||
pub description: Option<String>,
|
||||
pub description_banned_keywords: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq)]
|
||||
/// Activity metrics are recalculated weekly, as an 8-week rolling average. If they are not yet eligible to be calculated, all fields will be null.
|
||||
///
|
||||
/// # Reference:
|
||||
/// See <https://docs.discord.sex/resources/discovery#discovery-health-score-structure>
|
||||
pub struct GuildDiscoveryHealthScore {
|
||||
pub avg_nonnew_communicators: u64,
|
||||
pub avg_nonnew_participators: u64,
|
||||
pub num_intentful_joiners: u64,
|
||||
pub perc_ret_w1_intentful: f64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq)]
|
||||
/// # Reference:
|
||||
/// See <https://docs.discord.sex/resources/emoji#create-guild-emoji>
|
||||
pub struct EmojiCreateSchema {
|
||||
pub name: Option<String>,
|
||||
/// # Reference:
|
||||
/// See <https://docs.discord.sex/reference#cdn-data>
|
||||
pub image: String,
|
||||
#[serde(default)]
|
||||
pub roles: Vec<Snowflake>
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq)]
|
||||
/// # Reference:
|
||||
/// See <https://docs.discord.sex/resources/emoji#modify-guild-emoji>
|
||||
pub struct EmojiModifySchema {
|
||||
pub name: Option<String>,
|
||||
pub roles: Option<Vec<Snowflake>>
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq)]
|
||||
/// # Reference:
|
||||
/// See <https://docs.discord.sex/resources/guild#get-guild-prune>
|
||||
pub struct GuildPruneQuerySchema {
|
||||
pub days: u8,
|
||||
/// Only used on POST
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub compute_prune_count: Option<bool>,
|
||||
#[serde(default)]
|
||||
pub include_roles: Vec<Snowflake>
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq)]
|
||||
/// # Reference:
|
||||
/// See <https://docs.discord.sex/resources/guild#get-guild-prune>
|
||||
pub struct GuildPruneResult {
|
||||
/// Null if compute_prune_count is false
|
||||
pub pruned: Option<usize>,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Deserialize, Serialize, Clone, PartialEq)]
|
||||
/// # Reference:
|
||||
/// See <https://docs.discord.sex/resources/sticker#create-guild-sticker>
|
||||
pub struct GuildCreateStickerSchema {
|
||||
pub name: String,
|
||||
#[serde(default)]
|
||||
pub description: Option<String>,
|
||||
#[serde(default)]
|
||||
pub tags: Option<String>,
|
||||
pub file_data: Vec<u8>,
|
||||
#[serde(skip)]
|
||||
pub sticker_format_type: StickerFormatType
|
||||
}
|
||||
|
||||
impl GuildCreateStickerSchema {
|
||||
#[cfg(feature = "poem")]
|
||||
pub async fn from_multipart(mut multipart: poem::web::Multipart) -> Result<Self, poem::Error> {
|
||||
let mut _self = GuildCreateStickerSchema::default();
|
||||
while let Some(field) = multipart.next_field().await? {
|
||||
let name = field.name().ok_or(poem::Error::from_string("All fields must be named", poem::http::StatusCode::BAD_REQUEST))?;
|
||||
match name {
|
||||
"name" => {
|
||||
_self.name = field.text().await?;
|
||||
}
|
||||
"description" => {
|
||||
_self.description = Some(field.text().await?);
|
||||
}
|
||||
"tags" => {
|
||||
_self.tags = Some(field.text().await?);
|
||||
}
|
||||
"file_data" => {
|
||||
if _self.name.is_empty() {
|
||||
_self.name = field.file_name().map(String::from).ok_or(poem::Error::from_string("File name must be set", poem::http::StatusCode::BAD_REQUEST))?;
|
||||
}
|
||||
_self.sticker_format_type = StickerFormatType::from_mime(field.content_type().ok_or(poem::Error::from_string("Content type must be set", poem::http::StatusCode::BAD_REQUEST))?).ok_or(poem::Error::from_string("Unknown sticker format", poem::http::StatusCode::BAD_REQUEST))?;
|
||||
_self.file_data = field.bytes().await?;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
}
|
||||
if _self.name.is_empty() || _self.file_data.is_empty() {
|
||||
return Err(poem::Error::from_string("At least the name and file_data are required", poem::http::StatusCode::BAD_REQUEST));
|
||||
}
|
||||
|
||||
Ok(_self)
|
||||
}
|
||||
|
||||
// #[cfg(feature = "client")]
|
||||
pub fn to_multipart(&self) -> reqwest::multipart::Form {
|
||||
let mut form = reqwest::multipart::Form::new()
|
||||
.text("name", self.name.clone())
|
||||
.part("file_data", reqwest::multipart::Part::bytes(self.file_data.clone()).mime_str(self.sticker_format_type.to_mime()).unwrap());
|
||||
|
||||
if let Some(description) = &self.description {
|
||||
form = form.text("description", description.to_owned());
|
||||
}
|
||||
|
||||
if let Some(tags) = &self.tags {
|
||||
form = form.text("tags", tags.to_owned())
|
||||
}
|
||||
form
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq)]
|
||||
/// # Reference:
|
||||
/// See <https://docs.discord.sex/resources/sticker#modify-guild-sticker>
|
||||
pub struct GuildModifyStickerSchema {
|
||||
#[serde(default)]
|
||||
pub name: Option<String>,
|
||||
#[serde(default)]
|
||||
pub description: Option<String>,
|
||||
#[serde(default)]
|
||||
pub tags: Option<String>
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq)]
|
||||
/// # Reference:
|
||||
/// See <https://docs.discord.sex/resources/guild#modify-guild-welcome-screen>
|
||||
pub struct GuildModifyWelcomeScreenSchema {
|
||||
pub enabled: Option<bool>,
|
||||
pub description: Option<String>,
|
||||
/// Max of 5
|
||||
pub welcome_channels: Option<Vec<WelcomeScreenChannel>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq)]
|
||||
/// # Reference:
|
||||
/// See <https://docs.discord.sex/resources/guild-template#create-guild-template>
|
||||
pub struct GuildTemplateCreateSchema {
|
||||
/// Name of the template (1-100 characters)
|
||||
pub name: String,
|
||||
/// Description of the template (max 120 characters)
|
||||
pub description: Option<String>
|
||||
}
|
||||
|
|
|
@ -7,4 +7,20 @@ use serde::{Deserialize, Serialize};
|
|||
/// Read: <https://docs.discord.sex/resources/invite#query-string-params>
|
||||
pub struct GetInvitesSchema {
|
||||
pub with_counts: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, PartialOrd, Eq, Ord)]
|
||||
/// # Reference:
|
||||
/// See <https://docs.discord.sex/resources/guild#get-guild-vanity-invite>
|
||||
pub struct GuildVanityInviteResponse {
|
||||
pub code: String,
|
||||
#[serde(default)]
|
||||
pub uses: Option<u32>
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, PartialOrd, Eq, Ord)]
|
||||
/// # Reference:
|
||||
/// See <https://docs.discord.sex/resources/guild#modify-guild-vanity-invite>
|
||||
pub struct GuildCreateVanitySchema {
|
||||
pub code: String,
|
||||
}
|
|
@ -7,7 +7,7 @@ use serde::{Deserialize, Serialize};
|
|||
use crate::types::entities::{
|
||||
AllowedMention, Component, Embed, MessageReference, PartialDiscordFileAttachment,
|
||||
};
|
||||
use crate::types::{Attachment, MessageFlags, MessageType, ReactionType, Snowflake};
|
||||
use crate::types::{Attachment, EmbedType, Message, MessageFlags, MessageType, ReactionType, Snowflake};
|
||||
|
||||
#[derive(Debug, Default, Deserialize, Serialize, Clone, PartialEq)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
|
@ -54,13 +54,13 @@ pub struct MessageSearchQuery {
|
|||
pub attachment_extension: Option<Vec<String>>,
|
||||
pub attachment_filename: Option<Vec<String>>,
|
||||
pub author_id: Option<Vec<Snowflake>>,
|
||||
pub author_type: Option<Vec<String>>,
|
||||
pub author_type: Option<Vec<AuthorType>>,
|
||||
pub channel_id: Option<Vec<Snowflake>>,
|
||||
pub command_id: Option<Vec<Snowflake>>,
|
||||
pub content: Option<String>,
|
||||
pub embed_provider: Option<Vec<String>>,
|
||||
pub embed_type: Option<Vec<String>>,
|
||||
pub has: Option<Vec<String>>,
|
||||
pub embed_type: Option<Vec<EmbedType>>,
|
||||
pub has: Option<Vec<HasType>>,
|
||||
pub include_nsfw: Option<bool>,
|
||||
pub limit: Option<i32>,
|
||||
pub link_hostname: Option<Vec<String>>,
|
||||
|
@ -70,8 +70,8 @@ pub struct MessageSearchQuery {
|
|||
pub min_id: Option<String>,
|
||||
pub offset: Option<i32>,
|
||||
pub pinned: Option<bool>,
|
||||
pub sort_by: Option<String>,
|
||||
pub sort_order: Option<String>,
|
||||
pub sort_by: Option<SortType>,
|
||||
pub sort_order: Option<SortOrder>,
|
||||
}
|
||||
|
||||
impl std::default::Default for MessageSearchQuery {
|
||||
|
@ -102,6 +102,75 @@ impl std::default::Default for MessageSearchQuery {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum AuthorType {
|
||||
User,
|
||||
#[serde(rename = "-user")]
|
||||
NotUser,
|
||||
Bot,
|
||||
#[serde(rename = "-bot")]
|
||||
NotBot,
|
||||
Webhook,
|
||||
#[serde(rename = "-webhook")]
|
||||
NotWebhook,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum HasType {
|
||||
Image,
|
||||
#[serde(rename = "-image")]
|
||||
NotImage,
|
||||
Sound,
|
||||
#[serde(rename = "-sound")]
|
||||
NotSound,
|
||||
Video,
|
||||
#[serde(rename = "-video")]
|
||||
NotVideo,
|
||||
File,
|
||||
#[serde(rename = "-file")]
|
||||
NotFile,
|
||||
Sticker,
|
||||
#[serde(rename = "-sticker")]
|
||||
NotSticker,
|
||||
Embed,
|
||||
#[serde(rename = "-embed")]
|
||||
NotEmbed,
|
||||
Link,
|
||||
#[serde(rename = "-link")]
|
||||
NotLink,
|
||||
Poll,
|
||||
#[serde(rename = "-poll")]
|
||||
NotPoll,
|
||||
Snapshot,
|
||||
#[serde(rename = "-snapshot")]
|
||||
NotSnapshot,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Deserialize, Serialize, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum SortType {
|
||||
#[default]
|
||||
Timestamp,
|
||||
Relevance
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Deserialize, Serialize, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub enum SortOrder {
|
||||
#[default]
|
||||
#[serde(rename = "desc")]
|
||||
Descending,
|
||||
#[serde(rename = "asc")]
|
||||
Ascending,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Deserialize, Serialize, Clone, PartialEq)]
|
||||
pub struct MessageSearchResponse {
|
||||
pub messages: Vec<Message>,
|
||||
pub total_results: u64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct CreateGreetMessage {
|
||||
pub sticker_ids: Vec<Snowflake>,
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
pub use apierror::*;
|
||||
pub use audit_log::*;
|
||||
pub use auth::*;
|
||||
pub use mfa::*;
|
||||
pub use channel::*;
|
||||
|
@ -12,8 +13,10 @@ pub use relationship::*;
|
|||
pub use role::*;
|
||||
pub use user::*;
|
||||
pub use invites::*;
|
||||
pub use voice_state::*;
|
||||
|
||||
mod apierror;
|
||||
mod audit_log;
|
||||
mod auth;
|
||||
mod mfa;
|
||||
mod channel;
|
||||
|
@ -23,3 +26,10 @@ mod relationship;
|
|||
mod role;
|
||||
mod user;
|
||||
mod invites;
|
||||
mod voice_state;
|
||||
|
||||
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone, PartialEq, PartialOrd, Eq, Ord)]
|
||||
pub struct GenericSearchQueryWithLimit {
|
||||
pub query: String,
|
||||
pub limit: Option<u16>,
|
||||
}
|
|
@ -3,6 +3,7 @@
|
|||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use crate::types::{PermissionFlags, Snowflake};
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, Clone)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
|
@ -10,8 +11,8 @@ use serde::{Deserialize, Serialize};
|
|||
/// See: [https://docs.spacebar.chat/routes/#cmp--schemas-rolemodifyschema](https://docs.spacebar.chat/routes/#cmp--schemas-rolemodifyschema)
|
||||
pub struct RoleCreateModifySchema {
|
||||
pub name: Option<String>,
|
||||
pub permissions: Option<String>,
|
||||
pub color: Option<u32>,
|
||||
pub permissions: Option<PermissionFlags>,
|
||||
pub color: Option<f64>,
|
||||
pub hoist: Option<bool>,
|
||||
pub icon: Option<Vec<u8>>,
|
||||
pub unicode_emoji: Option<String>,
|
||||
|
@ -24,6 +25,6 @@ pub struct RoleCreateModifySchema {
|
|||
/// Represents the schema which needs to be sent to update a roles' position.
|
||||
/// See: [https://docs.spacebar.chat/routes/#cmp--schemas-rolepositionupdateschema](https://docs.spacebar.chat/routes/#cmp--schemas-rolepositionupdateschema)
|
||||
pub struct RolePositionUpdateSchema {
|
||||
pub id: String,
|
||||
pub id: Snowflake,
|
||||
pub position: u16,
|
||||
}
|
||||
|
|
|
@ -4,24 +4,91 @@
|
|||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use chrono::NaiveDate;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::types::Snowflake;
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)]
|
||||
#[derive(Debug, Default, Deserialize, Serialize, Clone, PartialEq, Eq)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
/// A schema used to modify a user.
|
||||
///
|
||||
/// See <https://docs.discord.sex/resources/user#json-params>
|
||||
pub struct UserModifySchema {
|
||||
/// The user's new username (2-32 characters)
|
||||
///
|
||||
/// Requires that `current_password` is set.
|
||||
pub username: Option<String>,
|
||||
// TODO: Maybe add a special discriminator type?
|
||||
/// Requires that `current_password` is set.
|
||||
pub discriminator: Option<String>,
|
||||
/// The user's display name (1-32 characters)
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// This is not yet implemented on Spacebar
|
||||
pub global_name: Option<String>,
|
||||
// TODO: Add a CDN data type
|
||||
pub avatar: Option<String>,
|
||||
pub bio: Option<String>,
|
||||
pub accent_color: Option<u64>,
|
||||
pub banner: Option<String>,
|
||||
pub current_password: Option<String>,
|
||||
pub new_password: Option<String>,
|
||||
pub code: Option<String>,
|
||||
/// Note: This is not yet implemented on Spacebar
|
||||
pub avatar_decoration_id: Option<Snowflake>,
|
||||
/// Note: This is not yet implemented on Spacebar
|
||||
pub avatar_decoration_sku_id: Option<Snowflake>,
|
||||
/// The user's email address; if changing from a verified email, email_token must be provided
|
||||
///
|
||||
/// Requires that `current_password` is set.
|
||||
// TODO: Is ^ up to date? One would think this may not be the case, since email_token exists
|
||||
pub email: Option<String>,
|
||||
pub discriminator: Option<i16>,
|
||||
/// The user's email token from their previous email, required if a new email is set.
|
||||
///
|
||||
/// See <https://docs.discord.sex/resources/user#modify-user-email> and <https://docs.discord.sex/resources/user#verify-user-email-change>
|
||||
/// for changing the user's email.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// This is not yet implemented on Spacebar
|
||||
pub email_token: Option<String>,
|
||||
/// The user's pronouns (max 40 characters)
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// This is not yet implemented on Spacebar
|
||||
pub pronouns: Option<String>,
|
||||
/// The user's banner.
|
||||
///
|
||||
/// Can only be changed for premium users
|
||||
pub banner: Option<String>,
|
||||
/// The user's bio (max 190 characters)
|
||||
pub bio: Option<String>,
|
||||
/// The user's accent color, as a hex integer
|
||||
pub accent_color: Option<u64>,
|
||||
/// The user's [UserFlags].
|
||||
///
|
||||
/// Only [UserFlags::PREMIUM_PROMO_DISMISSED], [UserFlags::HAS_UNREAD_URGENT_MESSAGES]
|
||||
/// and DISABLE_PREMIUM can be set.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// This is not yet implemented on Spacebar
|
||||
pub flags: Option<u64>,
|
||||
/// The user's date of birth, can only be set once
|
||||
///
|
||||
/// Requires that `current_password` is set.
|
||||
pub date_of_birth: Option<NaiveDate>,
|
||||
/// The user's current password (if the account does not have a password, this sets it)
|
||||
///
|
||||
/// Required for updating `username`, `discriminator`, `email`, `date_of_birth` and
|
||||
/// `new_password`
|
||||
#[serde(rename = "password")]
|
||||
pub current_password: Option<String>,
|
||||
/// The user's new password (8-72 characters)
|
||||
///
|
||||
/// Requires that `current_password` is set.
|
||||
///
|
||||
/// Regenerates the user's token
|
||||
pub new_password: Option<String>,
|
||||
/// Spacebar only field, potentially same as `email_token`
|
||||
pub code: Option<String>,
|
||||
}
|
||||
|
||||
/// A schema used to create a private channel.
|
||||
|
@ -33,7 +100,7 @@ pub struct UserModifySchema {
|
|||
///
|
||||
/// # Reference:
|
||||
/// Read: <https://discord-userdoccers.vercel.app/resources/channel#json-params>
|
||||
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)]
|
||||
#[derive(Debug, Default, Deserialize, Serialize, Clone, PartialEq, Eq)]
|
||||
pub struct PrivateChannelCreateSchema {
|
||||
pub recipients: Option<Vec<Snowflake>>,
|
||||
pub access_tokens: Option<Vec<String>>,
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
use chrono::{DateTime, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use crate::types::Snowflake;
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, PartialOrd)]
|
||||
/// # Reference:
|
||||
/// See <https://docs.discord.sex/resources/voice#json-params>
|
||||
pub struct VoiceStateUpdateSchema {
|
||||
/// The ID of the channel the user is currently in
|
||||
pub channel_id: Option<Snowflake>,
|
||||
/// Whether to suppress the user
|
||||
pub suppress: Option<bool>,
|
||||
/// The time at which the user requested to speak
|
||||
pub request_to_speak_timestamp: Option<DateTime<Utc>>,
|
||||
}
|
|
@ -23,3 +23,4 @@ impl From<WsMessage> for VoiceGatewayMessage {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -44,7 +44,7 @@ impl VoiceGateway {
|
|||
pub async fn spawn(websocket_url: String) -> Result<VoiceGatewayHandle, VoiceGatewayError> {
|
||||
// Append the needed things to the websocket url
|
||||
let processed_url = format!("wss://{}/?v=7", websocket_url);
|
||||
trace!("Created voice socket url: {}", processed_url.clone());
|
||||
trace!("VGW: Connecting to {}", processed_url.clone());
|
||||
|
||||
let (websocket_send, mut websocket_receive) =
|
||||
match WebSocketBackend::connect(&processed_url).await {
|
||||
|
|
|
@ -85,8 +85,10 @@ async fn test_login_with_token() {
|
|||
.await
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
bundle.user.object.read().unwrap().id,
|
||||
other_user.object.read().unwrap().id
|
||||
bundle.user.object.as_ref().unwrap()
|
||||
.read().unwrap()
|
||||
.id,
|
||||
other_user.object.unwrap().read().unwrap().id
|
||||
);
|
||||
assert_eq!(bundle.user.token, other_user.token);
|
||||
|
||||
|
|
|
@ -2,7 +2,11 @@
|
|||
// 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::types::{self, Channel, GetChannelMessagesSchema, MessageSendSchema, PermissionFlags, PermissionOverwrite, PermissionOverwriteType, PrivateChannelCreateSchema, RelationshipType, Snowflake};
|
||||
use chorus::types::{
|
||||
self, Channel, GetChannelMessagesSchema, MessageSendSchema, PermissionFlags,
|
||||
PermissionOverwrite, PermissionOverwriteType, PrivateChannelCreateSchema, RelationshipType,
|
||||
Snowflake,
|
||||
};
|
||||
|
||||
mod common;
|
||||
|
||||
|
@ -67,7 +71,7 @@ async fn modify_channel() {
|
|||
assert_eq!(modified_channel.name, Some(CHANNEL_NAME.to_string()));
|
||||
|
||||
let permission_override = PermissionFlags::MANAGE_CHANNELS | PermissionFlags::MANAGE_MESSAGES;
|
||||
let user_id: types::Snowflake = bundle.user.object.read().unwrap().id;
|
||||
let user_id: types::Snowflake = bundle.user.object.as_ref().unwrap().read().unwrap().id;
|
||||
let permission_override = PermissionOverwrite {
|
||||
id: user_id,
|
||||
overwrite_type: PermissionOverwriteType::Member,
|
||||
|
@ -155,7 +159,13 @@ async fn create_dm() {
|
|||
let other_user = bundle.create_user("integrationtestuser2").await;
|
||||
let user = &mut bundle.user;
|
||||
let private_channel_create_schema = PrivateChannelCreateSchema {
|
||||
recipients: Some(Vec::from([other_user.object.read().unwrap().id])),
|
||||
recipients: Some(Vec::from([other_user
|
||||
.object
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.read()
|
||||
.unwrap()
|
||||
.id])),
|
||||
access_tokens: None,
|
||||
nicks: None,
|
||||
};
|
||||
|
@ -175,7 +185,7 @@ async fn create_dm() {
|
|||
.unwrap()
|
||||
.id
|
||||
.clone(),
|
||||
other_user.object.read().unwrap().id
|
||||
other_user.object.unwrap().read().unwrap().id
|
||||
);
|
||||
assert_eq!(
|
||||
dm_channel
|
||||
|
@ -188,7 +198,7 @@ async fn create_dm() {
|
|||
.unwrap()
|
||||
.id
|
||||
.clone(),
|
||||
user.object.read().unwrap().id.clone()
|
||||
user.object.as_ref().unwrap().read().unwrap().id.clone()
|
||||
);
|
||||
common::teardown(bundle).await;
|
||||
}
|
||||
|
@ -200,9 +210,9 @@ async fn remove_add_person_from_to_dm() {
|
|||
let mut bundle = common::setup().await;
|
||||
let mut other_user = bundle.create_user("integrationtestuser2").await;
|
||||
let mut third_user = bundle.create_user("integrationtestuser3").await;
|
||||
let third_user_id = third_user.object.read().unwrap().id;
|
||||
let other_user_id = other_user.object.read().unwrap().id;
|
||||
let user_id = bundle.user.object.read().unwrap().id;
|
||||
let third_user_id = third_user.object.as_ref().unwrap().read().unwrap().id;
|
||||
let other_user_id = other_user.object.as_ref().unwrap().read().unwrap().id;
|
||||
let user_id = bundle.user.object.as_ref().unwrap().read().unwrap().id;
|
||||
let user = &mut bundle.user;
|
||||
let private_channel_create_schema = PrivateChannelCreateSchema {
|
||||
recipients: Some(Vec::from([other_user_id, third_user_id])),
|
||||
|
|
|
@ -4,8 +4,8 @@
|
|||
|
||||
use std::str::FromStr;
|
||||
|
||||
use chorus::gateway::Gateway;
|
||||
use chorus::types::IntoShared;
|
||||
use chorus::gateway::{Gateway, GatewayOptions};
|
||||
use chorus::types::{IntoShared, PermissionFlags};
|
||||
use chorus::{
|
||||
instance::{ChorusUser, Instance},
|
||||
types::{
|
||||
|
@ -47,10 +47,11 @@ impl TestBundle {
|
|||
ChorusUser {
|
||||
belongs_to: self.user.belongs_to.clone(),
|
||||
token: self.user.token.clone(),
|
||||
mfa_token: None,
|
||||
limits: self.user.limits.clone(),
|
||||
settings: self.user.settings.clone(),
|
||||
object: self.user.object.clone(),
|
||||
gateway: Gateway::spawn(self.instance.urls.wss.clone())
|
||||
gateway: Gateway::spawn(self.instance.urls.wss.clone(), GatewayOptions::default())
|
||||
.await
|
||||
.unwrap(),
|
||||
}
|
||||
|
@ -59,6 +60,10 @@ impl TestBundle {
|
|||
|
||||
// Set up a test by creating an Instance and a User. Reduces Test boilerplate.
|
||||
pub(crate) async fn setup() -> TestBundle {
|
||||
|
||||
// So we can get logs when tests fail
|
||||
let _ = simple_logger::SimpleLogger::with_level(simple_logger::SimpleLogger::new(), log::LevelFilter::Debug).init();
|
||||
|
||||
let instance = Instance::new("http://localhost:3001/api").await.unwrap();
|
||||
// Requires the existence of the below user.
|
||||
let reg = RegisterSchema {
|
||||
|
@ -104,7 +109,7 @@ pub(crate) async fn setup() -> TestBundle {
|
|||
|
||||
let role_create_schema: chorus::types::RoleCreateModifySchema = RoleCreateModifySchema {
|
||||
name: Some("Bundle role".to_string()),
|
||||
permissions: Some("8".to_string()), // Administrator permissions
|
||||
permissions: PermissionFlags::from_bits(8), // Administrator permissions
|
||||
hoist: Some(true),
|
||||
icon: None,
|
||||
unicode_emoji: Some("".to_string()),
|
||||
|
@ -119,7 +124,7 @@ pub(crate) async fn setup() -> TestBundle {
|
|||
let urls = UrlBundle::new(
|
||||
"http://localhost:3001/api".to_string(),
|
||||
"http://localhost:3001/api".to_string(),
|
||||
"ws://localhost:3001".to_string(),
|
||||
"ws://localhost:3001/".to_string(),
|
||||
"http://localhost:3001".to_string(),
|
||||
);
|
||||
TestBundle {
|
||||
|
|
|
@ -30,7 +30,7 @@ use wasmtimer::tokio::sleep;
|
|||
async fn test_gateway_establish() {
|
||||
let bundle = common::setup().await;
|
||||
|
||||
let _: GatewayHandle = Gateway::spawn(bundle.urls.wss.clone()).await.unwrap();
|
||||
let _: GatewayHandle = Gateway::spawn(bundle.urls.wss.clone(), GatewayOptions::default()).await.unwrap();
|
||||
common::teardown(bundle).await
|
||||
}
|
||||
|
||||
|
@ -52,7 +52,7 @@ impl Observer<GatewayReady> for GatewayReadyObserver {
|
|||
async fn test_gateway_authenticate() {
|
||||
let bundle = common::setup().await;
|
||||
|
||||
let gateway: GatewayHandle = Gateway::spawn(bundle.urls.wss.clone()).await.unwrap();
|
||||
let gateway: GatewayHandle = Gateway::spawn(bundle.urls.wss.clone(), GatewayOptions::default()).await.unwrap();
|
||||
|
||||
let (ready_send, mut ready_receive) = tokio::sync::mpsc::channel(1);
|
||||
|
||||
|
@ -79,9 +79,9 @@ async fn test_gateway_authenticate() {
|
|||
println!("Timed out waiting for event, failing..");
|
||||
assert!(false);
|
||||
}
|
||||
// Sucess, we have received it
|
||||
// Success, we have received it
|
||||
Some(_) = ready_receive.recv() => {}
|
||||
};
|
||||
}
|
||||
|
||||
common::teardown(bundle).await
|
||||
}
|
||||
|
@ -125,7 +125,7 @@ async fn test_self_updating_structs() {
|
|||
.gateway
|
||||
.observe_and_into_inner(bundle.guild.clone())
|
||||
.await;
|
||||
assert!(guild.channels.is_none());
|
||||
assert!(guild.channels.is_empty());
|
||||
|
||||
Channel::create(
|
||||
&mut bundle.user,
|
||||
|
@ -145,8 +145,8 @@ async fn test_self_updating_structs() {
|
|||
.gateway
|
||||
.observe_and_into_inner(guild.into_shared())
|
||||
.await;
|
||||
assert!(guild.channels.is_some());
|
||||
assert!(guild.channels.as_ref().unwrap().len() == 1);
|
||||
assert!(!guild.channels.is_empty());
|
||||
assert_eq!(guild.channels.len(), 1);
|
||||
|
||||
common::teardown(bundle).await
|
||||
}
|
||||
|
@ -160,13 +160,12 @@ async fn test_recursive_self_updating_structs() {
|
|||
// Observe Guild, make sure it has no channels
|
||||
let guild = bundle.user.gateway.observe(guild.clone()).await;
|
||||
let inner_guild = guild.read().unwrap().clone();
|
||||
assert!(inner_guild.roles.is_none());
|
||||
assert!(inner_guild.roles.is_empty());
|
||||
// Create Role
|
||||
let permissions = types::PermissionFlags::CONNECT | types::PermissionFlags::MANAGE_EVENTS;
|
||||
let permissions = Some(permissions.to_string());
|
||||
let mut role_create_schema: types::RoleCreateModifySchema = RoleCreateModifySchema {
|
||||
name: Some("cool person".to_string()),
|
||||
permissions,
|
||||
permissions: Some(permissions),
|
||||
hoist: Some(true),
|
||||
icon: None,
|
||||
unicode_emoji: Some("".to_string()),
|
||||
|
@ -186,7 +185,7 @@ async fn test_recursive_self_updating_structs() {
|
|||
.await;
|
||||
// Update Guild and check for Guild
|
||||
let inner_guild = guild.read().unwrap().clone();
|
||||
assert!(inner_guild.roles.is_some());
|
||||
assert!(!inner_guild.roles.is_empty());
|
||||
// Update the Role
|
||||
role_create_schema.name = Some("yippieee".to_string());
|
||||
RoleObject::modify(&mut bundle.user, guild_id, role.id, role_create_schema)
|
||||
|
@ -202,8 +201,7 @@ async fn test_recursive_self_updating_structs() {
|
|||
let guild = bundle.user.gateway.observe(bundle.guild.clone()).await;
|
||||
let inner_guild = guild.read().unwrap().clone();
|
||||
let guild_roles = inner_guild.roles;
|
||||
let guild_role = guild_roles.unwrap();
|
||||
let guild_role_inner = guild_role.get(0).unwrap().read().unwrap().clone();
|
||||
let guild_role_inner = guild_roles.get(0).unwrap().read().unwrap().clone();
|
||||
assert_eq!(guild_role_inner.name, "yippieee".to_string());
|
||||
common::teardown(bundle).await;
|
||||
}
|
||||
|
|
|
@ -60,7 +60,8 @@ async fn guild_create_ban() {
|
|||
.await
|
||||
.unwrap();
|
||||
other_user.accept_invite(&invite.code, None).await.unwrap();
|
||||
let other_user_id = other_user.object.read().unwrap().id;
|
||||
let other_user_id = other_user.object.as_ref().unwrap()
|
||||
.read().unwrap().id;
|
||||
Guild::create_ban(
|
||||
guild.id,
|
||||
other_user_id,
|
||||
|
@ -112,7 +113,9 @@ async fn guild_remove_member() {
|
|||
.await
|
||||
.unwrap();
|
||||
other_user.accept_invite(&invite.code, None).await.unwrap();
|
||||
let other_user_id = other_user.object.read().unwrap().id;
|
||||
let other_user_id = other_user.object
|
||||
.as_ref().unwrap()
|
||||
.read().unwrap().id;
|
||||
Guild::remove_member(guild.id, other_user_id, None, &mut bundle.user)
|
||||
.await
|
||||
.unwrap();
|
||||
|
|
|
@ -16,7 +16,8 @@ async fn add_remove_role() -> ChorusResult<()> {
|
|||
let mut bundle = common::setup().await;
|
||||
let guild = bundle.guild.read().unwrap().id;
|
||||
let role = bundle.role.read().unwrap().id;
|
||||
let member_id = bundle.user.object.read().unwrap().id;
|
||||
let member_id = bundle.user.object.as_ref().unwrap()
|
||||
.read().unwrap().id;
|
||||
GuildMember::add_role(&mut bundle.user, guild, member_id, role).await?;
|
||||
let member = GuildMember::get(&mut bundle.user, guild, member_id)
|
||||
.await
|
||||
|
|
|
@ -106,7 +106,7 @@ async fn search_messages() {
|
|||
let _arg = Some(&vec_attach);
|
||||
let message = bundle.user.send_message(message, channel.id).await.unwrap();
|
||||
let query = MessageSearchQuery {
|
||||
author_id: Some(Vec::from([bundle.user.object.read().unwrap().id])),
|
||||
author_id: Some(Vec::from([bundle.user.object.as_ref().unwrap().read().unwrap().id])),
|
||||
..Default::default()
|
||||
};
|
||||
let guild_id = bundle.guild.read().unwrap().id;
|
||||
|
|
|
@ -16,9 +16,10 @@ async fn test_get_mutual_relationships() {
|
|||
let mut bundle = common::setup().await;
|
||||
let mut other_user = bundle.create_user("integrationtestuser2").await;
|
||||
let user = &mut bundle.user;
|
||||
let username = user.object.read().unwrap().username.clone();
|
||||
let discriminator = user.object.read().unwrap().discriminator.clone();
|
||||
let other_user_id: types::Snowflake = other_user.object.read().unwrap().id;
|
||||
|
||||
let username = user.object.as_ref().unwrap().read().unwrap().username.clone();
|
||||
let discriminator = user.object.as_ref().unwrap().read().unwrap().discriminator.clone();
|
||||
let other_user_id: types::Snowflake = other_user.object.as_ref().unwrap().read().unwrap().id;
|
||||
let friend_request_schema = types::FriendRequestSendSchema {
|
||||
username,
|
||||
discriminator: Some(discriminator),
|
||||
|
@ -38,8 +39,8 @@ async fn test_get_relationships() {
|
|||
let mut bundle = common::setup().await;
|
||||
let mut other_user = bundle.create_user("integrationtestuser2").await;
|
||||
let user = &mut bundle.user;
|
||||
let username = user.object.read().unwrap().username.clone();
|
||||
let discriminator = user.object.read().unwrap().discriminator.clone();
|
||||
let username = user.object.as_ref().unwrap().read().unwrap().username.clone();
|
||||
let discriminator = user.object.as_ref().unwrap().read().unwrap().discriminator.clone();
|
||||
let friend_request_schema = types::FriendRequestSendSchema {
|
||||
username,
|
||||
discriminator: Some(discriminator),
|
||||
|
@ -51,7 +52,7 @@ async fn test_get_relationships() {
|
|||
let relationships = user.get_relationships().await.unwrap();
|
||||
assert_eq!(
|
||||
relationships.get(0).unwrap().id,
|
||||
other_user.object.read().unwrap().id
|
||||
other_user.object.unwrap().read().unwrap().id
|
||||
);
|
||||
common::teardown(bundle).await
|
||||
}
|
||||
|
@ -62,8 +63,8 @@ async fn test_modify_relationship_friends() {
|
|||
let mut bundle = common::setup().await;
|
||||
let mut other_user = bundle.create_user("integrationtestuser2").await;
|
||||
let user = &mut bundle.user;
|
||||
let user_id: types::Snowflake = user.object.read().unwrap().id;
|
||||
let other_user_id: types::Snowflake = other_user.object.read().unwrap().id;
|
||||
let user_id: types::Snowflake = user.object.as_ref().unwrap().read().unwrap().id;
|
||||
let other_user_id: types::Snowflake = other_user.object.as_ref().unwrap().read().unwrap().id;
|
||||
|
||||
other_user
|
||||
.modify_user_relationship(user_id, types::RelationshipType::Friends)
|
||||
|
@ -72,7 +73,7 @@ async fn test_modify_relationship_friends() {
|
|||
let relationships = user.get_relationships().await.unwrap();
|
||||
assert_eq!(
|
||||
relationships.get(0).unwrap().id,
|
||||
other_user.object.read().unwrap().id
|
||||
other_user.object.as_ref().unwrap().read().unwrap().id
|
||||
);
|
||||
assert_eq!(
|
||||
relationships.get(0).unwrap().relationship_type,
|
||||
|
@ -81,7 +82,7 @@ async fn test_modify_relationship_friends() {
|
|||
let relationships = other_user.get_relationships().await.unwrap();
|
||||
assert_eq!(
|
||||
relationships.get(0).unwrap().id,
|
||||
user.object.read().unwrap().id
|
||||
user.object.as_ref().unwrap().read().unwrap().id
|
||||
);
|
||||
assert_eq!(
|
||||
relationships.get(0).unwrap().relationship_type,
|
||||
|
@ -114,7 +115,7 @@ async fn test_modify_relationship_block() {
|
|||
let mut bundle = common::setup().await;
|
||||
let mut other_user = bundle.create_user("integrationtestuser2").await;
|
||||
let user = &mut bundle.user;
|
||||
let user_id: types::Snowflake = user.object.read().unwrap().id;
|
||||
let user_id: types::Snowflake = user.object.as_ref().unwrap().read().unwrap().id;
|
||||
|
||||
other_user
|
||||
.modify_user_relationship(user_id, types::RelationshipType::Blocked)
|
||||
|
@ -125,7 +126,7 @@ async fn test_modify_relationship_block() {
|
|||
let relationships = other_user.get_relationships().await.unwrap();
|
||||
assert_eq!(
|
||||
relationships.get(0).unwrap().id,
|
||||
user.object.read().unwrap().id
|
||||
user.object.as_ref().unwrap().read().unwrap().id
|
||||
);
|
||||
assert_eq!(
|
||||
relationships.get(0).unwrap().relationship_type,
|
||||
|
|
|
@ -15,10 +15,9 @@ mod common;
|
|||
async fn create_and_get_roles() {
|
||||
let mut bundle = common::setup().await;
|
||||
let permissions = types::PermissionFlags::CONNECT | types::PermissionFlags::MANAGE_EVENTS;
|
||||
let permissions = Some(permissions.to_string());
|
||||
let role_create_schema: types::RoleCreateModifySchema = RoleCreateModifySchema {
|
||||
name: Some("cool person".to_string()),
|
||||
permissions,
|
||||
permissions: Some(permissions),
|
||||
hoist: Some(true),
|
||||
icon: None,
|
||||
unicode_emoji: Some("".to_string()),
|
||||
|
|
Loading…
Reference in New Issue