Merge branch 'main' into fix/doc-improvements
This commit is contained in:
commit
577c8a2d71
|
@ -189,6 +189,7 @@ dependencies = [
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"base64 0.21.2",
|
"base64 0.21.2",
|
||||||
"bitflags 2.3.3",
|
"bitflags 2.3.3",
|
||||||
|
"chorus-macros",
|
||||||
"chrono",
|
"chrono",
|
||||||
"custom_error",
|
"custom_error",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
|
@ -215,6 +216,14 @@ dependencies = [
|
||||||
"url",
|
"url",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "chorus-macros"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.26",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "chrono"
|
name = "chrono"
|
||||||
version = "0.4.26"
|
version = "0.4.26"
|
||||||
|
|
|
@ -36,6 +36,7 @@ thiserror = "1.0.43"
|
||||||
jsonwebtoken = "8.3.0"
|
jsonwebtoken = "8.3.0"
|
||||||
log = "0.4.19"
|
log = "0.4.19"
|
||||||
async-trait = "0.1.71"
|
async-trait = "0.1.71"
|
||||||
|
chorus-macros = {path = "chorus-macros"}
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
tokio = {version = "1.29.1", features = ["full"]}
|
tokio = {version = "1.29.1", features = ["full"]}
|
||||||
|
|
|
@ -0,0 +1,46 @@
|
||||||
|
# This file is automatically @generated by Cargo.
|
||||||
|
# It is not intended for manual editing.
|
||||||
|
version = 3
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "chorus-macros"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "proc-macro2"
|
||||||
|
version = "1.0.66"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9"
|
||||||
|
dependencies = [
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "quote"
|
||||||
|
version = "1.0.31"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5fe8a65d69dd0808184ebb5f836ab526bb259db23c657efa38711b1072ee47f0"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "syn"
|
||||||
|
version = "2.0.27"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b60f673f44a8255b9c8c657daf66a596d435f2da81a555b06dc644d080ba45e0"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-ident"
|
||||||
|
version = "1.0.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c"
|
|
@ -0,0 +1,11 @@
|
||||||
|
[package]
|
||||||
|
name = "chorus-macros"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
proc-macro = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
quote = "1"
|
||||||
|
syn = "2"
|
|
@ -0,0 +1,18 @@
|
||||||
|
use proc_macro::TokenStream;
|
||||||
|
use quote::quote;
|
||||||
|
|
||||||
|
#[proc_macro_derive(Updateable)]
|
||||||
|
pub fn updateable_macro_derive(input: TokenStream) -> TokenStream {
|
||||||
|
let ast: syn::DeriveInput = syn::parse(input).unwrap();
|
||||||
|
|
||||||
|
let name = &ast.ident;
|
||||||
|
// No need for macro hygiene, we're only using this in chorus
|
||||||
|
quote! {
|
||||||
|
impl Updateable for #name {
|
||||||
|
fn id(&self) -> Snowflake {
|
||||||
|
self.id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.into()
|
||||||
|
}
|
|
@ -6,9 +6,10 @@ use serde_json::to_string;
|
||||||
|
|
||||||
use crate::api::LimitType;
|
use crate::api::LimitType;
|
||||||
use crate::errors::ChorusResult;
|
use crate::errors::ChorusResult;
|
||||||
|
use crate::gateway::Gateway;
|
||||||
use crate::instance::{Instance, UserMeta};
|
use crate::instance::{Instance, UserMeta};
|
||||||
use crate::ratelimiter::ChorusRequest;
|
use crate::ratelimiter::ChorusRequest;
|
||||||
use crate::types::{LoginResult, LoginSchema};
|
use crate::types::{GatewayIdentifyPayload, LoginResult, LoginSchema};
|
||||||
|
|
||||||
impl Instance {
|
impl Instance {
|
||||||
/// Logs into an existing account on the spacebar server.
|
/// Logs into an existing account on the spacebar server.
|
||||||
|
@ -23,7 +24,8 @@ impl Instance {
|
||||||
// We do not have a user yet, and the UserRateLimits will not be affected by a login
|
// We do not have a user yet, and the UserRateLimits will not be affected by a login
|
||||||
// request (since login is an instance wide limit), which is why we are just cloning the
|
// request (since login is an instance wide limit), which is why we are just cloning the
|
||||||
// instances' limits to pass them on as user_rate_limits later.
|
// instances' limits to pass them on as user_rate_limits later.
|
||||||
let mut shell = UserMeta::shell(Rc::new(RefCell::new(self.clone())), "None".to_string());
|
let mut shell =
|
||||||
|
UserMeta::shell(Rc::new(RefCell::new(self.clone())), "None".to_string()).await;
|
||||||
let login_result = chorus_request
|
let login_result = chorus_request
|
||||||
.deserialize_response::<LoginResult>(&mut shell)
|
.deserialize_response::<LoginResult>(&mut shell)
|
||||||
.await?;
|
.await?;
|
||||||
|
@ -31,12 +33,17 @@ impl Instance {
|
||||||
if self.limits_information.is_some() {
|
if self.limits_information.is_some() {
|
||||||
self.limits_information.as_mut().unwrap().ratelimits = shell.limits.clone().unwrap();
|
self.limits_information.as_mut().unwrap().ratelimits = shell.limits.clone().unwrap();
|
||||||
}
|
}
|
||||||
|
let mut identify = GatewayIdentifyPayload::common();
|
||||||
|
let gateway = Gateway::new(self.urls.wss.clone()).await.unwrap();
|
||||||
|
identify.token = login_result.token.clone();
|
||||||
|
gateway.send_identify(identify).await;
|
||||||
let user = UserMeta::new(
|
let user = UserMeta::new(
|
||||||
Rc::new(RefCell::new(self.clone())),
|
Rc::new(RefCell::new(self.clone())),
|
||||||
login_result.token,
|
login_result.token,
|
||||||
self.clone_limits_if_some(),
|
self.clone_limits_if_some(),
|
||||||
login_result.settings,
|
login_result.settings,
|
||||||
object,
|
object,
|
||||||
|
gateway,
|
||||||
);
|
);
|
||||||
Ok(user)
|
Ok(user)
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,8 @@ use std::{cell::RefCell, rc::Rc};
|
||||||
use reqwest::Client;
|
use reqwest::Client;
|
||||||
use serde_json::to_string;
|
use serde_json::to_string;
|
||||||
|
|
||||||
|
use crate::gateway::Gateway;
|
||||||
|
use crate::types::GatewayIdentifyPayload;
|
||||||
use crate::{
|
use crate::{
|
||||||
api::policies::instance::LimitType,
|
api::policies::instance::LimitType,
|
||||||
errors::ChorusResult,
|
errors::ChorusResult,
|
||||||
|
@ -27,7 +29,8 @@ impl Instance {
|
||||||
// We do not have a user yet, and the UserRateLimits will not be affected by a login
|
// We do not have a user yet, and the UserRateLimits will not be affected by a login
|
||||||
// request (since register is an instance wide limit), which is why we are just cloning
|
// request (since register is an instance wide limit), which is why we are just cloning
|
||||||
// the instances' limits to pass them on as user_rate_limits later.
|
// the instances' limits to pass them on as user_rate_limits later.
|
||||||
let mut shell = UserMeta::shell(Rc::new(RefCell::new(self.clone())), "None".to_string());
|
let mut shell =
|
||||||
|
UserMeta::shell(Rc::new(RefCell::new(self.clone())), "None".to_string()).await;
|
||||||
let token = chorus_request
|
let token = chorus_request
|
||||||
.deserialize_response::<Token>(&mut shell)
|
.deserialize_response::<Token>(&mut shell)
|
||||||
.await?
|
.await?
|
||||||
|
@ -37,12 +40,17 @@ impl Instance {
|
||||||
}
|
}
|
||||||
let user_object = self.get_user(token.clone(), None).await.unwrap();
|
let user_object = self.get_user(token.clone(), None).await.unwrap();
|
||||||
let settings = UserMeta::get_settings(&token, &self.urls.api.clone(), self).await?;
|
let settings = UserMeta::get_settings(&token, &self.urls.api.clone(), self).await?;
|
||||||
|
let mut identify = GatewayIdentifyPayload::common();
|
||||||
|
let gateway = Gateway::new(self.urls.wss.clone()).await.unwrap();
|
||||||
|
identify.token = token.clone();
|
||||||
|
gateway.send_identify(identify).await;
|
||||||
let user = UserMeta::new(
|
let user = UserMeta::new(
|
||||||
Rc::new(RefCell::new(self.clone())),
|
Rc::new(RefCell::new(self.clone())),
|
||||||
token.clone(),
|
token.clone(),
|
||||||
self.clone_limits_if_some(),
|
self.clone_limits_if_some(),
|
||||||
settings,
|
settings,
|
||||||
user_object,
|
user_object,
|
||||||
|
gateway,
|
||||||
);
|
);
|
||||||
Ok(user)
|
Ok(user)
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,11 +41,11 @@ impl Channel {
|
||||||
/// Modifies a channel with the provided data.
|
/// Modifies a channel with the provided data.
|
||||||
/// Replaces self with the new channel object.
|
/// Replaces self with the new channel object.
|
||||||
pub async fn modify(
|
pub async fn modify(
|
||||||
&mut self,
|
&self,
|
||||||
modify_data: ChannelModifySchema,
|
modify_data: ChannelModifySchema,
|
||||||
channel_id: Snowflake,
|
channel_id: Snowflake,
|
||||||
user: &mut UserMeta,
|
user: &mut UserMeta,
|
||||||
) -> ChorusResult<()> {
|
) -> ChorusResult<Channel> {
|
||||||
let chorus_request = ChorusRequest {
|
let chorus_request = ChorusRequest {
|
||||||
request: Client::new()
|
request: Client::new()
|
||||||
.patch(format!(
|
.patch(format!(
|
||||||
|
@ -57,9 +57,7 @@ impl Channel {
|
||||||
.body(to_string(&modify_data).unwrap()),
|
.body(to_string(&modify_data).unwrap()),
|
||||||
limit_type: LimitType::Channel(channel_id),
|
limit_type: LimitType::Channel(channel_id),
|
||||||
};
|
};
|
||||||
let new_channel = chorus_request.deserialize_response::<Channel>(user).await?;
|
chorus_request.deserialize_response::<Channel>(user).await
|
||||||
let _ = std::mem::replace(self, new_channel);
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Fetches recent messages from a channel.
|
/// Fetches recent messages from a channel.
|
||||||
|
|
|
@ -121,7 +121,8 @@ impl User {
|
||||||
let request: reqwest::RequestBuilder = Client::new()
|
let request: reqwest::RequestBuilder = Client::new()
|
||||||
.get(format!("{}/users/@me/settings/", url_api))
|
.get(format!("{}/users/@me/settings/", url_api))
|
||||||
.bearer_auth(token);
|
.bearer_auth(token);
|
||||||
let mut user = UserMeta::shell(Rc::new(RefCell::new(instance.clone())), token.clone());
|
let mut user =
|
||||||
|
UserMeta::shell(Rc::new(RefCell::new(instance.clone())), token.clone()).await;
|
||||||
let chorus_request = ChorusRequest {
|
let chorus_request = ChorusRequest {
|
||||||
request,
|
request,
|
||||||
limit_type: LimitType::Global,
|
limit_type: LimitType::Global,
|
||||||
|
@ -148,7 +149,7 @@ impl Instance {
|
||||||
// # Notes
|
// # Notes
|
||||||
// This function is a wrapper around [`User::get`].
|
// This function is a wrapper around [`User::get`].
|
||||||
pub async fn get_user(&mut self, token: String, id: Option<&String>) -> ChorusResult<User> {
|
pub async fn get_user(&mut self, token: String, id: Option<&String>) -> ChorusResult<User> {
|
||||||
let mut user = UserMeta::shell(Rc::new(RefCell::new(self.clone())), token);
|
let mut user = UserMeta::shell(Rc::new(RefCell::new(self.clone())), token).await;
|
||||||
let result = User::get(&mut user, id).await;
|
let result = User::get(&mut user, id).await;
|
||||||
if self.limits_information.is_some() {
|
if self.limits_information.is_some() {
|
||||||
self.limits_information.as_mut().unwrap().ratelimits =
|
self.limits_information.as_mut().unwrap().ratelimits =
|
||||||
|
|
|
@ -1,10 +1,14 @@
|
||||||
use crate::errors::GatewayError;
|
use crate::errors::GatewayError;
|
||||||
use crate::gateway::events::Events;
|
use crate::gateway::events::Events;
|
||||||
use crate::types;
|
use crate::types::{self, Channel, ChannelUpdate, Snowflake};
|
||||||
use crate::types::WebSocketEvent;
|
use crate::types::{UpdateMessage, WebSocketEvent};
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
|
use std::any::Any;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::fmt::Debug;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
use tokio::sync::watch;
|
||||||
use tokio::time::sleep_until;
|
use tokio::time::sleep_until;
|
||||||
|
|
||||||
use futures_util::stream::SplitSink;
|
use futures_util::stream::SplitSink;
|
||||||
|
@ -163,6 +167,12 @@ pub struct GatewayHandle {
|
||||||
pub handle: JoinHandle<()>,
|
pub handle: JoinHandle<()>,
|
||||||
/// Tells gateway tasks to close
|
/// Tells gateway tasks to close
|
||||||
kill_send: tokio::sync::broadcast::Sender<()>,
|
kill_send: tokio::sync::broadcast::Sender<()>,
|
||||||
|
store: Arc<Mutex<HashMap<Snowflake, Box<dyn Send + Any>>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An entity type which is supposed to be updateable via the Gateway. This is implemented for all such types chorus supports, implementing it for your own types is likely a mistake.
|
||||||
|
pub trait Updateable: 'static + Send + Sync {
|
||||||
|
fn id(&self) -> Snowflake;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GatewayHandle {
|
impl GatewayHandle {
|
||||||
|
@ -186,6 +196,27 @@ impl GatewayHandle {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn observe<T: Updateable>(&self, object: T) -> watch::Receiver<T> {
|
||||||
|
let mut store = self.store.lock().await;
|
||||||
|
if let Some(channel) = store.get(&object.id()) {
|
||||||
|
let (_, rx) = channel
|
||||||
|
.downcast_ref::<(watch::Sender<T>, watch::Receiver<T>)>()
|
||||||
|
.unwrap_or_else(|| {
|
||||||
|
panic!(
|
||||||
|
"Snowflake {} already exists in the store, but it is not of type T.",
|
||||||
|
object.id()
|
||||||
|
)
|
||||||
|
});
|
||||||
|
rx.clone()
|
||||||
|
} else {
|
||||||
|
let id = object.id();
|
||||||
|
let channel = watch::channel(object);
|
||||||
|
let receiver = channel.1.clone();
|
||||||
|
store.insert(id, Box::new(channel));
|
||||||
|
receiver
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Sends an identify event to the gateway
|
/// Sends an identify event to the gateway
|
||||||
pub async fn send_identify(&self, to_send: types::GatewayIdentifyPayload) {
|
pub async fn send_identify(&self, to_send: types::GatewayIdentifyPayload) {
|
||||||
let to_send_value = serde_json::to_value(&to_send).unwrap();
|
let to_send_value = serde_json::to_value(&to_send).unwrap();
|
||||||
|
@ -263,9 +294,9 @@ impl GatewayHandle {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Gateway {
|
pub struct Gateway {
|
||||||
pub events: Arc<Mutex<Events>>,
|
events: Arc<Mutex<Events>>,
|
||||||
heartbeat_handler: HeartbeatHandler,
|
heartbeat_handler: HeartbeatHandler,
|
||||||
pub websocket_send: Arc<
|
websocket_send: Arc<
|
||||||
Mutex<
|
Mutex<
|
||||||
SplitSink<
|
SplitSink<
|
||||||
WebSocketStream<MaybeTlsStream<TcpStream>>,
|
WebSocketStream<MaybeTlsStream<TcpStream>>,
|
||||||
|
@ -273,8 +304,9 @@ pub struct Gateway {
|
||||||
>,
|
>,
|
||||||
>,
|
>,
|
||||||
>,
|
>,
|
||||||
pub websocket_receive: SplitStream<WebSocketStream<MaybeTlsStream<TcpStream>>>,
|
websocket_receive: SplitStream<WebSocketStream<MaybeTlsStream<TcpStream>>>,
|
||||||
kill_send: tokio::sync::broadcast::Sender<()>,
|
kill_send: tokio::sync::broadcast::Sender<()>,
|
||||||
|
store: Arc<Mutex<HashMap<Snowflake, Box<dyn Send + Any>>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Gateway {
|
impl Gateway {
|
||||||
|
@ -325,6 +357,8 @@ impl Gateway {
|
||||||
let events = Events::default();
|
let events = Events::default();
|
||||||
let shared_events = Arc::new(Mutex::new(events));
|
let shared_events = Arc::new(Mutex::new(events));
|
||||||
|
|
||||||
|
let store = Arc::new(Mutex::new(HashMap::new()));
|
||||||
|
|
||||||
let mut gateway = Gateway {
|
let mut gateway = Gateway {
|
||||||
events: shared_events.clone(),
|
events: shared_events.clone(),
|
||||||
heartbeat_handler: HeartbeatHandler::new(
|
heartbeat_handler: HeartbeatHandler::new(
|
||||||
|
@ -335,6 +369,7 @@ impl Gateway {
|
||||||
websocket_send: shared_websocket_send.clone(),
|
websocket_send: shared_websocket_send.clone(),
|
||||||
websocket_receive,
|
websocket_receive,
|
||||||
kill_send: kill_send.clone(),
|
kill_send: kill_send.clone(),
|
||||||
|
store: store.clone(),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Now we can continuously check for messages in a different task, since we aren't going to receive another hello
|
// Now we can continuously check for messages in a different task, since we aren't going to receive another hello
|
||||||
|
@ -348,6 +383,7 @@ impl Gateway {
|
||||||
websocket_send: shared_websocket_send.clone(),
|
websocket_send: shared_websocket_send.clone(),
|
||||||
handle,
|
handle,
|
||||||
kill_send: kill_send.clone(),
|
kill_send: kill_send.clone(),
|
||||||
|
store,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -379,6 +415,7 @@ impl Gateway {
|
||||||
|
|
||||||
/// Deserializes and updates a dispatched event, when we already know its type;
|
/// Deserializes and updates a dispatched event, when we already know its type;
|
||||||
/// (Called for every event in handle_message)
|
/// (Called for every event in handle_message)
|
||||||
|
#[allow(dead_code)] // TODO: Remove this allow annotation
|
||||||
async fn handle_event<'a, T: WebSocketEvent + serde::Deserialize<'a>>(
|
async fn handle_event<'a, T: WebSocketEvent + serde::Deserialize<'a>>(
|
||||||
data: &'a str,
|
data: &'a str,
|
||||||
event: &mut GatewayEvent<T>,
|
event: &mut GatewayEvent<T>,
|
||||||
|
@ -431,17 +468,25 @@ impl Gateway {
|
||||||
trace!("Gateway: Received {event_name}");
|
trace!("Gateway: Received {event_name}");
|
||||||
|
|
||||||
macro_rules! handle {
|
macro_rules! handle {
|
||||||
($($name:literal => $($path:ident).+),*) => {
|
($($name:literal => $($path:ident).+ $( $message_type:ty: $update_type:ty)?),*) => {
|
||||||
match event_name.as_str() {
|
match event_name.as_str() {
|
||||||
$($name => {
|
$($name => {
|
||||||
let event = &mut self.events.lock().await.$($path).+;
|
let event = &mut self.events.lock().await.$($path).+;
|
||||||
|
match serde_json::from_str(gateway_payload.event_data.unwrap().get()) {
|
||||||
let result =
|
Err(err) => warn!("Failed to parse gateway event {event_name} ({err})"),
|
||||||
Gateway::handle_event(gateway_payload.event_data.unwrap().get(), event)
|
Ok(message) => {
|
||||||
.await;
|
$(
|
||||||
|
let message: $message_type = message;
|
||||||
if let Err(err) = result {
|
if let Some(to_update) = self.store.lock().await.get(&message.id()) {
|
||||||
warn!("Failed to parse gateway event {event_name} ({err})");
|
if let Some((tx, _)) = to_update.downcast_ref::<(watch::Sender<$update_type>, watch::Receiver<$update_type>)>() {
|
||||||
|
tx.send_modify(|object| message.update(object));
|
||||||
|
} else {
|
||||||
|
warn!("Received {} for {}, but it has been observed to be a different type!", $name, message.id())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)?
|
||||||
|
event.notify(message).await;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},)*
|
},)*
|
||||||
"RESUMED" => (),
|
"RESUMED" => (),
|
||||||
|
@ -482,7 +527,7 @@ impl Gateway {
|
||||||
"AUTO_MODERATION_RULE_DELETE" => auto_moderation.rule_delete,
|
"AUTO_MODERATION_RULE_DELETE" => auto_moderation.rule_delete,
|
||||||
"AUTO_MODERATION_ACTION_EXECUTION" => auto_moderation.action_execution,
|
"AUTO_MODERATION_ACTION_EXECUTION" => auto_moderation.action_execution,
|
||||||
"CHANNEL_CREATE" => channel.create,
|
"CHANNEL_CREATE" => channel.create,
|
||||||
"CHANNEL_UPDATE" => channel.update,
|
"CHANNEL_UPDATE" => channel.update ChannelUpdate: Channel,
|
||||||
"CHANNEL_UNREAD_UPDATE" => channel.unread_update,
|
"CHANNEL_UNREAD_UPDATE" => channel.unread_update,
|
||||||
"CHANNEL_DELETE" => channel.delete,
|
"CHANNEL_DELETE" => channel.delete,
|
||||||
"CHANNEL_PINS_UPDATE" => channel.pins_update,
|
"CHANNEL_PINS_UPDATE" => channel.pins_update,
|
||||||
|
|
|
@ -8,6 +8,7 @@ use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::api::{Limit, LimitType};
|
use crate::api::{Limit, LimitType};
|
||||||
use crate::errors::ChorusResult;
|
use crate::errors::ChorusResult;
|
||||||
|
use crate::gateway::{Gateway, GatewayHandle};
|
||||||
use crate::ratelimiter::ChorusRequest;
|
use crate::ratelimiter::ChorusRequest;
|
||||||
use crate::types::types::subconfigs::limits::rates::RateLimits;
|
use crate::types::types::subconfigs::limits::rates::RateLimits;
|
||||||
use crate::types::{GeneralConfiguration, User, UserSettings};
|
use crate::types::{GeneralConfiguration, User, UserSettings};
|
||||||
|
@ -81,13 +82,14 @@ impl fmt::Display for Token {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug)]
|
||||||
pub struct UserMeta {
|
pub struct UserMeta {
|
||||||
pub belongs_to: Rc<RefCell<Instance>>,
|
pub belongs_to: Rc<RefCell<Instance>>,
|
||||||
pub token: String,
|
pub token: String,
|
||||||
pub limits: Option<HashMap<LimitType, Limit>>,
|
pub limits: Option<HashMap<LimitType, Limit>>,
|
||||||
pub settings: UserSettings,
|
pub settings: UserSettings,
|
||||||
pub object: User,
|
pub object: User,
|
||||||
|
pub gateway: GatewayHandle,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl UserMeta {
|
impl UserMeta {
|
||||||
|
@ -105,6 +107,7 @@ impl UserMeta {
|
||||||
limits: Option<HashMap<LimitType, Limit>>,
|
limits: Option<HashMap<LimitType, Limit>>,
|
||||||
settings: UserSettings,
|
settings: UserSettings,
|
||||||
object: User,
|
object: User,
|
||||||
|
gateway: GatewayHandle,
|
||||||
) -> UserMeta {
|
) -> UserMeta {
|
||||||
UserMeta {
|
UserMeta {
|
||||||
belongs_to,
|
belongs_to,
|
||||||
|
@ -112,19 +115,24 @@ impl UserMeta {
|
||||||
limits,
|
limits,
|
||||||
settings,
|
settings,
|
||||||
object,
|
object,
|
||||||
|
gateway,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a new 'shell' of a user. The user does not exist as an object, and exists so that you have
|
/// Creates a new 'shell' of a user. The user does not exist as an object, and exists so that you have
|
||||||
/// a UserMeta object to make Rate Limited requests with. This is useful in scenarios like
|
/// a UserMeta object to make Rate Limited requests with. This is useful in scenarios like
|
||||||
/// registering or logging in to the Instance, where you do not yet have a User object, but still
|
/// registering or logging in to the Instance, where you do not yet have a User object, but still
|
||||||
/// need to make a RateLimited request.
|
/// need to make a RateLimited request. To use the [`GatewayHandle`], you will have to identify
|
||||||
pub(crate) fn shell(instance: Rc<RefCell<Instance>>, token: String) -> UserMeta {
|
/// first.
|
||||||
|
pub(crate) async fn shell(instance: Rc<RefCell<Instance>>, token: String) -> UserMeta {
|
||||||
let settings = UserSettings::default();
|
let settings = UserSettings::default();
|
||||||
let object = User::default();
|
let object = User::default();
|
||||||
|
let wss_url = instance.borrow().urls.wss.clone();
|
||||||
|
// Dummy gateway object
|
||||||
|
let gateway = Gateway::new(wss_url).await.unwrap();
|
||||||
UserMeta {
|
UserMeta {
|
||||||
belongs_to: instance.clone(),
|
|
||||||
token,
|
token,
|
||||||
|
belongs_to: instance.clone(),
|
||||||
limits: instance
|
limits: instance
|
||||||
.borrow()
|
.borrow()
|
||||||
.limits_information
|
.limits_information
|
||||||
|
@ -132,6 +140,7 @@ impl UserMeta {
|
||||||
.map(|info| info.ratelimits.clone()),
|
.map(|info| info.ratelimits.clone()),
|
||||||
settings,
|
settings,
|
||||||
object,
|
object,
|
||||||
|
gateway,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,16 @@
|
||||||
|
use chorus_macros::Updateable;
|
||||||
use chrono::Utc;
|
use chrono::Utc;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_aux::prelude::deserialize_string_from_number;
|
use serde_aux::prelude::deserialize_string_from_number;
|
||||||
use serde_repr::{Deserialize_repr, Serialize_repr};
|
use serde_repr::{Deserialize_repr, Serialize_repr};
|
||||||
|
|
||||||
|
use crate::gateway::Updateable;
|
||||||
use crate::types::{
|
use crate::types::{
|
||||||
entities::{GuildMember, User},
|
entities::{GuildMember, User},
|
||||||
utils::Snowflake,
|
utils::Snowflake,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Default, Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
|
#[derive(Default, Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Updateable)]
|
||||||
#[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))]
|
#[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))]
|
||||||
pub struct Channel {
|
pub struct Channel {
|
||||||
pub application_id: Option<Snowflake>,
|
pub application_id: Option<Snowflake>,
|
||||||
|
|
|
@ -3,6 +3,8 @@ use crate::types::{entities::Channel, Snowflake};
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use super::UpdateMessage;
|
||||||
|
|
||||||
#[derive(Debug, Default, Deserialize, Serialize)]
|
#[derive(Debug, Default, Deserialize, Serialize)]
|
||||||
/// See <https://discord.com/developers/docs/topics/gateway-events#channel-pins-update>
|
/// See <https://discord.com/developers/docs/topics/gateway-events#channel-pins-update>
|
||||||
pub struct ChannelPinsUpdate {
|
pub struct ChannelPinsUpdate {
|
||||||
|
@ -31,6 +33,15 @@ pub struct ChannelUpdate {
|
||||||
|
|
||||||
impl WebSocketEvent for ChannelUpdate {}
|
impl WebSocketEvent for ChannelUpdate {}
|
||||||
|
|
||||||
|
impl UpdateMessage<Channel> for ChannelUpdate {
|
||||||
|
fn update(&self, object_to_update: &mut Channel) {
|
||||||
|
*object_to_update = self.channel.clone();
|
||||||
|
}
|
||||||
|
fn id(&self) -> Snowflake {
|
||||||
|
self.channel.id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default, Deserialize, Serialize, Clone)]
|
#[derive(Debug, Default, Deserialize, Serialize, Clone)]
|
||||||
/// Officially undocumented.
|
/// Officially undocumented.
|
||||||
/// Sends updates to client about a new message with its id
|
/// Sends updates to client about a new message with its id
|
||||||
|
|
|
@ -26,6 +26,10 @@ pub use user::*;
|
||||||
pub use voice::*;
|
pub use voice::*;
|
||||||
pub use webhooks::*;
|
pub use webhooks::*;
|
||||||
|
|
||||||
|
use crate::gateway::Updateable;
|
||||||
|
|
||||||
|
use super::Snowflake;
|
||||||
|
|
||||||
mod application;
|
mod application;
|
||||||
mod auto_moderation;
|
mod auto_moderation;
|
||||||
mod call;
|
mod call;
|
||||||
|
@ -92,3 +96,23 @@ pub struct GatewayReceivePayload<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> WebSocketEvent for GatewayReceivePayload<'a> {}
|
impl<'a> WebSocketEvent for GatewayReceivePayload<'a> {}
|
||||||
|
|
||||||
|
/// An [`UpdateMessage<T>`] represents a received Gateway Message which contains updated
|
||||||
|
/// information for an [`Updateable`] of Type T.
|
||||||
|
/// # Example:
|
||||||
|
/// ```rs
|
||||||
|
/// impl UpdateMessage<Channel> for ChannelUpdate {
|
||||||
|
/// fn update(...) {...}
|
||||||
|
/// fn id(...) {...}
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
/// This would imply, that the [`WebSocketEvent`] "[`ChannelUpdate`]" contains new/updated information
|
||||||
|
/// about a [`Channel`]. The update method describes how this new information will be turned into
|
||||||
|
/// a [`Channel`] object.
|
||||||
|
pub(crate) trait UpdateMessage<T>: Clone
|
||||||
|
where
|
||||||
|
T: Updateable,
|
||||||
|
{
|
||||||
|
fn update(&self, object_to_update: &mut T);
|
||||||
|
fn id(&self) -> Snowflake;
|
||||||
|
}
|
||||||
|
|
|
@ -28,10 +28,11 @@ async fn delete_channel() {
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn modify_channel() {
|
async fn modify_channel() {
|
||||||
|
const CHANNEL_NAME: &str = "beepboop";
|
||||||
let mut bundle = common::setup().await;
|
let mut bundle = common::setup().await;
|
||||||
let channel = &mut bundle.channel;
|
let channel = &mut bundle.channel;
|
||||||
let modify_data: types::ChannelModifySchema = types::ChannelModifySchema {
|
let modify_data: types::ChannelModifySchema = types::ChannelModifySchema {
|
||||||
name: Some("beepboop".to_string()),
|
name: Some(CHANNEL_NAME.to_string()),
|
||||||
channel_type: None,
|
channel_type: None,
|
||||||
topic: None,
|
topic: None,
|
||||||
icon: None,
|
icon: None,
|
||||||
|
@ -49,10 +50,10 @@ async fn modify_channel() {
|
||||||
default_thread_rate_limit_per_user: None,
|
default_thread_rate_limit_per_user: None,
|
||||||
video_quality_mode: None,
|
video_quality_mode: None,
|
||||||
};
|
};
|
||||||
Channel::modify(channel, modify_data, channel.id, &mut bundle.user)
|
let modified_channel = Channel::modify(channel, modify_data, channel.id, &mut bundle.user)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(channel.name, Some("beepboop".to_string()));
|
assert_eq!(modified_channel.name, Some(CHANNEL_NAME.to_string()));
|
||||||
|
|
||||||
let permission_override = PermissionFlags::from_vec(Vec::from([
|
let permission_override = PermissionFlags::from_vec(Vec::from([
|
||||||
PermissionFlags::MANAGE_CHANNELS,
|
PermissionFlags::MANAGE_CHANNELS,
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
use chorus::gateway::Gateway;
|
||||||
use chorus::{
|
use chorus::{
|
||||||
instance::{Instance, UserMeta},
|
instance::{Instance, UserMeta},
|
||||||
types::{
|
types::{
|
||||||
|
@ -18,8 +19,8 @@ pub(crate) struct TestBundle {
|
||||||
pub channel: Channel,
|
pub channel: Channel,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(unused)]
|
||||||
impl TestBundle {
|
impl TestBundle {
|
||||||
#[allow(unused)]
|
|
||||||
pub(crate) async fn create_user(&mut self, username: &str) -> UserMeta {
|
pub(crate) async fn create_user(&mut self, username: &str) -> UserMeta {
|
||||||
let register_schema = RegisterSchema {
|
let register_schema = RegisterSchema {
|
||||||
username: username.to_string(),
|
username: username.to_string(),
|
||||||
|
@ -32,6 +33,16 @@ impl TestBundle {
|
||||||
.await
|
.await
|
||||||
.unwrap()
|
.unwrap()
|
||||||
}
|
}
|
||||||
|
pub(crate) async fn clone_user_without_gateway(&self) -> UserMeta {
|
||||||
|
UserMeta {
|
||||||
|
belongs_to: self.user.belongs_to.clone(),
|
||||||
|
token: self.user.token.clone(),
|
||||||
|
limits: self.user.limits.clone(),
|
||||||
|
settings: self.user.settings.clone(),
|
||||||
|
object: self.user.object.clone(),
|
||||||
|
gateway: Gateway::new(self.instance.urls.wss.clone()).await.unwrap(),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set up a test by creating an Instance and a User. Reduces Test boilerplate.
|
// Set up a test by creating an Instance and a User. Reduces Test boilerplate.
|
||||||
|
|
|
@ -1,13 +1,15 @@
|
||||||
mod common;
|
mod common;
|
||||||
|
|
||||||
use chorus::gateway::*;
|
use chorus::gateway::*;
|
||||||
use chorus::types;
|
use chorus::types::{self, Channel};
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
/// Tests establishing a connection (hello and heartbeats) on the local gateway;
|
/// Tests establishing a connection (hello and heartbeats) on the local gateway;
|
||||||
async fn test_gateway_establish() {
|
async fn test_gateway_establish() {
|
||||||
let bundle = common::setup().await;
|
let bundle = common::setup().await;
|
||||||
|
|
||||||
Gateway::new(bundle.urls.wss).await.unwrap();
|
Gateway::new(bundle.urls.wss.clone()).await.unwrap();
|
||||||
|
common::teardown(bundle).await
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
|
@ -15,10 +17,30 @@ async fn test_gateway_establish() {
|
||||||
async fn test_gateway_authenticate() {
|
async fn test_gateway_authenticate() {
|
||||||
let bundle = common::setup().await;
|
let bundle = common::setup().await;
|
||||||
|
|
||||||
let gateway = Gateway::new(bundle.urls.wss).await.unwrap();
|
let gateway = Gateway::new(bundle.urls.wss.clone()).await.unwrap();
|
||||||
|
|
||||||
let mut identify = types::GatewayIdentifyPayload::common();
|
let mut identify = types::GatewayIdentifyPayload::common();
|
||||||
identify.token = bundle.user.token;
|
identify.token = bundle.user.token.clone();
|
||||||
|
|
||||||
gateway.send_identify(identify).await;
|
gateway.send_identify(identify).await;
|
||||||
|
common::teardown(bundle).await
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_self_updating_structs() {
|
||||||
|
let mut bundle = common::setup().await;
|
||||||
|
let channel_updater = bundle.user.gateway.observe(bundle.channel.clone()).await;
|
||||||
|
let received_channel = channel_updater.borrow().clone();
|
||||||
|
assert_eq!(received_channel, bundle.channel);
|
||||||
|
let channel = &mut bundle.channel;
|
||||||
|
let modify_data = types::ChannelModifySchema {
|
||||||
|
name: Some("beepboop".to_string()),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
Channel::modify(channel, modify_data, channel.id, &mut bundle.user)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
let received_channel = channel_updater.borrow();
|
||||||
|
assert_eq!(received_channel.name.as_ref().unwrap(), "beepboop");
|
||||||
|
common::teardown(bundle).await
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,12 @@
|
||||||
use chorus::types::CreateChannelInviteSchema;
|
|
||||||
|
|
||||||
mod common;
|
mod common;
|
||||||
|
use chorus::types::CreateChannelInviteSchema;
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn create_accept_invite() {
|
async fn create_accept_invite() {
|
||||||
let mut bundle = common::setup().await;
|
let mut bundle = common::setup().await;
|
||||||
let channel = bundle.channel.clone();
|
let channel = bundle.channel.clone();
|
||||||
let mut user = bundle.user.clone();
|
|
||||||
let create_channel_invite_schema = CreateChannelInviteSchema::default();
|
|
||||||
let mut other_user = bundle.create_user("testuser1312").await;
|
let mut other_user = bundle.create_user("testuser1312").await;
|
||||||
|
let user = &mut bundle.user;
|
||||||
|
let create_channel_invite_schema = CreateChannelInviteSchema::default();
|
||||||
assert!(chorus::types::Guild::get(bundle.guild.id, &mut other_user)
|
assert!(chorus::types::Guild::get(bundle.guild.id, &mut other_user)
|
||||||
.await
|
.await
|
||||||
.is_err());
|
.is_err());
|
||||||
|
|
Loading…
Reference in New Issue