Compare commits

...
12 Commits
25 changed files with 276 additions and 99 deletions

No files matched your search

Generated
+8 -8
View File
@@ -3452,7 +3452,7 @@ dependencies = [
[[package]] [[package]]
name = "revolt-bonfire" name = "revolt-bonfire"
version = "0.7.6" version = "0.7.10"
dependencies = [ dependencies = [
"async-channel 2.3.1", "async-channel 2.3.1",
"async-std", "async-std",
@@ -3482,7 +3482,7 @@ dependencies = [
[[package]] [[package]]
name = "revolt-config" name = "revolt-config"
version = "0.7.6" version = "0.7.10"
dependencies = [ dependencies = [
"async-std", "async-std",
"cached", "cached",
@@ -3498,7 +3498,7 @@ dependencies = [
[[package]] [[package]]
name = "revolt-database" name = "revolt-database"
version = "0.7.6" version = "0.7.10"
dependencies = [ dependencies = [
"async-lock", "async-lock",
"async-recursion", "async-recursion",
@@ -3544,7 +3544,7 @@ dependencies = [
[[package]] [[package]]
name = "revolt-delta" name = "revolt-delta"
version = "0.7.6" version = "0.7.10"
dependencies = [ dependencies = [
"async-channel 1.6.1", "async-channel 1.6.1",
"async-std", "async-std",
@@ -3590,7 +3590,7 @@ dependencies = [
[[package]] [[package]]
name = "revolt-models" name = "revolt-models"
version = "0.7.6" version = "0.7.10"
dependencies = [ dependencies = [
"indexmap", "indexmap",
"iso8601-timestamp 0.2.11", "iso8601-timestamp 0.2.11",
@@ -3607,7 +3607,7 @@ dependencies = [
[[package]] [[package]]
name = "revolt-permissions" name = "revolt-permissions"
version = "0.7.6" version = "0.7.10"
dependencies = [ dependencies = [
"async-std", "async-std",
"async-trait", "async-trait",
@@ -3622,7 +3622,7 @@ dependencies = [
[[package]] [[package]]
name = "revolt-presence" name = "revolt-presence"
version = "0.7.6" version = "0.7.10"
dependencies = [ dependencies = [
"async-std", "async-std",
"log", "log",
@@ -3633,7 +3633,7 @@ dependencies = [
[[package]] [[package]]
name = "revolt-result" name = "revolt-result"
version = "0.7.6" version = "0.7.10"
dependencies = [ dependencies = [
"revolt_okapi", "revolt_okapi",
"revolt_rocket_okapi", "revolt_rocket_okapi",
+2 -2
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "revolt-bonfire" name = "revolt-bonfire"
version = "0.7.6" version = "0.7.10"
license = "AGPL-3.0-or-later" license = "AGPL-3.0-or-later"
edition = "2021" edition = "2021"
@@ -41,7 +41,7 @@ revolt-result = { path = "../core/result" }
revolt-models = { path = "../core/models" } revolt-models = { path = "../core/models" }
revolt-config = { path = "../core/config" } revolt-config = { path = "../core/config" }
revolt-database = { path = "../core/database" } revolt-database = { path = "../core/database" }
revolt-permissions = { version = "0.7.6", path = "../core/permissions" } revolt-permissions = { version = "0.7.10", path = "../core/permissions" }
revolt-presence = { path = "../core/presence", features = ["redis-is-patched"] } revolt-presence = { path = "../core/presence", features = ["redis-is-patched"] }
# redis # redis
+134 -31
View File
@@ -3,12 +3,13 @@ use std::{collections::HashSet, net::SocketAddr, sync::Arc};
use async_tungstenite::WebSocketStream; use async_tungstenite::WebSocketStream;
use authifier::AuthifierEvent; use authifier::AuthifierEvent;
use fred::{ use fred::{
error::{RedisError, RedisErrorKind},
interfaces::{ClientLike, EventInterface, PubsubInterface}, interfaces::{ClientLike, EventInterface, PubsubInterface},
types::RedisConfig, types::RedisConfig,
}; };
use futures::{ use futures::{
channel::oneshot, channel::oneshot,
pin_mut, select, join, pin_mut, select,
stream::{SplitSink, SplitStream}, stream::{SplitSink, SplitStream},
FutureExt, SinkExt, StreamExt, TryStreamExt, FutureExt, SinkExt, StreamExt, TryStreamExt,
}; };
@@ -22,6 +23,7 @@ use revolt_presence::{create_session, delete_session};
use async_std::{ use async_std::{
net::TcpStream, net::TcpStream,
sync::{Mutex, RwLock}, sync::{Mutex, RwLock},
task::spawn,
}; };
use revolt_result::create_error; use revolt_result::create_error;
use sentry::Level; use sentry::Level;
@@ -127,34 +129,44 @@ pub async fn client(db: &'static Database, stream: TcpStream, addr: SocketAddr)
} }
{ {
// Setup channels and mutexes
let write = Mutex::new(write); let write = Mutex::new(write);
let subscribed = state.subscribed.clone(); let subscribed = state.subscribed.clone();
let active_servers = state.active_servers.clone(); let active_servers = state.active_servers.clone();
let (signal_s, signal_r) = async_channel::unbounded(); let (topic_signal_s, topic_signal_r) = async_channel::unbounded();
// TODO: this needs to be rewritten
// Create channels through which the tasks can signal to each other they need to clean up
let (kill_signal_1_s, kill_signal_1_r) = async_channel::bounded(1);
let (kill_signal_2_s, kill_signal_2_r) = async_channel::bounded(1);
// Create a PubSub connection to poll on. // Create a PubSub connection to poll on.
let listener = listener(db, &mut state, addr, &config, signal_r, &write).fuse(); let listener = listener_with_kill_signal(
db,
&mut state,
addr,
&config,
topic_signal_r,
kill_signal_1_r,
&write,
kill_signal_2_s,
);
// Read from WebSocket stream. // Read from WebSocket stream.
let worker = worker( let worker = worker_with_kill_signal(
addr, addr,
subscribed, subscribed,
active_servers, active_servers,
user_id.clone(), user_id.clone(),
&config, &config,
signal_s, topic_signal_s,
kill_signal_2_r,
read, read,
&write, &write,
) kill_signal_1_s,
.fuse();
// Pin both tasks.
pin_mut!(listener, worker);
// Wait for either disconnect or for listener to die.
select!(
() = listener => {},
() = worker => {}
); );
join!(listener, worker);
} }
// Clean up presence session. // Clean up presence session.
let last_session = delete_session(&user_id, session_id).await; let last_session = delete_session(&user_id, session_id).await;
@@ -165,16 +177,40 @@ pub async fn client(db: &'static Database, stream: TcpStream, addr: SocketAddr)
} }
} }
#[allow(clippy::too_many_arguments)]
async fn listener_with_kill_signal(
db: &'static Database,
state: &mut State,
addr: SocketAddr,
config: &ProtocolConfiguration,
topic_signal_r: async_channel::Receiver<()>,
kill_signal_r: async_channel::Receiver<()>,
write: &Mutex<WsWriter>,
kill_signal_s: async_channel::Sender<()>,
) {
listener(
db,
state,
addr,
config,
topic_signal_r,
kill_signal_r,
write,
)
.await;
kill_signal_s.send(()).await.ok();
}
async fn listener( async fn listener(
db: &'static Database, db: &'static Database,
state: &mut State, state: &mut State,
addr: SocketAddr, addr: SocketAddr,
config: &ProtocolConfiguration, config: &ProtocolConfiguration,
signal_r: async_channel::Receiver<()>, topic_signal_r: async_channel::Receiver<()>,
kill_signal_r: async_channel::Receiver<()>,
write: &Mutex<WsWriter>, write: &Mutex<WsWriter>,
) { ) {
let redis_config = RedisConfig::from_url(&REDIS_URI).unwrap(); let redis_config = RedisConfig::from_url(&REDIS_URI).unwrap();
let subscriber = match fred::types::Builder::from_config(redis_config).build_subscriber_client() let subscriber = match fred::types::Builder::from_config(redis_config).build_subscriber_client()
{ {
Ok(subscriber) => subscriber, Ok(subscriber) => subscriber,
@@ -191,15 +227,29 @@ async fn listener(
return; return;
}; };
// Handle Redis connection dropping
let (clean_up_s, clean_up_r) = async_channel::bounded(1);
let clean_up_s = Arc::new(Mutex::new(clean_up_s));
subscriber.on_error(move |err| {
if let RedisErrorKind::Canceled = err.kind() {
let clean_up_s = clean_up_s.clone();
spawn(async move {
clean_up_s.lock().await.send(()).await.ok();
});
}
Ok(())
});
let mut message_rx = subscriber.message_rx(); let mut message_rx = subscriber.message_rx();
loop { 'out: loop {
// Check for state changes for subscriptions. // Check for state changes for subscriptions.
match state.apply_state().await { match state.apply_state().await {
SubscriptionStateChange::Reset => { SubscriptionStateChange::Reset => {
if let Err(err) = subscriber.unsubscribe_all().await { if let Err(err) = subscriber.unsubscribe_all().await {
error!("Unsubscribe all failed: {err:?}"); error!("Unsubscribe all failed: {err:?}");
sentry::capture_error(&err); sentry::capture_error(&err);
return; break 'out;
} }
let subscribed = state.subscribed.read().await; let subscribed = state.subscribed.read().await;
@@ -207,7 +257,7 @@ async fn listener(
if let Err(err) = subscriber.subscribe(id).await { if let Err(err) = subscriber.subscribe(id).await {
error!("Subscribe failed: {err:?}"); error!("Subscribe failed: {err:?}");
sentry::capture_error(&err); sentry::capture_error(&err);
return; break 'out;
} }
} }
@@ -222,7 +272,7 @@ async fn listener(
if let Err(err) = subscriber.unsubscribe(id).await { if let Err(err) = subscriber.unsubscribe(id).await {
error!("Unsubscribe failed: {err:?}"); error!("Unsubscribe failed: {err:?}");
sentry::capture_error(&err); sentry::capture_error(&err);
return; break 'out;
} }
} }
@@ -233,7 +283,7 @@ async fn listener(
if let Err(err) = subscriber.subscribe(id).await { if let Err(err) = subscriber.subscribe(id).await {
error!("Subscribe failed: {err:?}"); error!("Subscribe failed: {err:?}");
sentry::capture_error(&err); sentry::capture_error(&err);
return; break 'out;
} }
} }
} }
@@ -241,11 +291,19 @@ async fn listener(
} }
let t1 = message_rx.recv().fuse(); let t1 = message_rx.recv().fuse();
let t2 = signal_r.recv().fuse(); let t2 = topic_signal_r.recv().fuse();
let t3 = kill_signal_r.recv().fuse();
let t4 = clean_up_r.recv().fuse();
pin_mut!(t1, t2); pin_mut!(t1, t2, t3, t4);
select! { select! {
_ = t4 => {
break 'out;
},
_ = t3 => {
break 'out;
},
_ = t2 => {}, _ = t2 => {},
message = t1 => { message = t1 => {
// Handle incoming events. // Handle incoming events.
@@ -254,7 +312,7 @@ async fn listener(
Err(e) => { Err(e) => {
error!("Error while consuming pub/sub messages: {e:?}"); error!("Error while consuming pub/sub messages: {e:?}");
sentry::capture_error(&e); sentry::capture_error(&e);
return; break 'out;
} }
}; };
@@ -285,7 +343,7 @@ async fn listener(
error!("{}", err); error!("{}", err);
sentry::capture_message(&err, Level::Error); sentry::capture_message(&err, Level::Error);
return; break 'out;
}; };
if let EventV1::Auth(auth) = &event { if let EventV1::Auth(auth) = &event {
@@ -321,16 +379,49 @@ async fn listener(
sentry::capture_message(&err, Level::Warning); sentry::capture_message(&err, Level::Warning);
} }
return; break 'out;
} }
if let EventV1::Logout = event { if let EventV1::Logout = event {
info!("User {addr:?} received log out event!"); info!("User {addr:?} received log out event!");
return; break 'out;
} }
} }
} }
} }
if let Err(err) = subscriber.quit().await {
error!("{}", err);
sentry::capture_error(&err);
}
}
#[allow(clippy::too_many_arguments)]
async fn worker_with_kill_signal(
addr: SocketAddr,
subscribed: Arc<RwLock<HashSet<String>>>,
active_servers: Arc<Mutex<lru_time_cache::LruCache<String, ()>>>,
user_id: String,
config: &ProtocolConfiguration,
topic_signal_s: async_channel::Sender<()>,
kill_signal_r: async_channel::Receiver<()>,
read: WsReader,
write: &Mutex<WsWriter>,
kill_signal_s: async_channel::Sender<()>,
) {
worker(
addr,
subscribed,
active_servers,
user_id,
config,
topic_signal_s,
kill_signal_r,
read,
write,
)
.await;
kill_signal_s.send(()).await.ok();
} }
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
@@ -340,12 +431,22 @@ async fn worker(
active_servers: Arc<Mutex<lru_time_cache::LruCache<String, ()>>>, active_servers: Arc<Mutex<lru_time_cache::LruCache<String, ()>>>,
user_id: String, user_id: String,
config: &ProtocolConfiguration, config: &ProtocolConfiguration,
signal_s: async_channel::Sender<()>, topic_signal_s: async_channel::Sender<()>,
kill_signal_r: async_channel::Receiver<()>,
mut read: WsReader, mut read: WsReader,
write: &Mutex<WsWriter>, write: &Mutex<WsWriter>,
) { ) {
loop { loop {
let result = read.try_next().await; let t1 = read.try_next().fuse();
let t2 = kill_signal_r.recv().fuse();
pin_mut!(t1, t2);
select! {
_ = t2 => {
return;
},
result = t1 => {
let msg = match result { let msg = match result {
Ok(Some(msg)) => msg, Ok(Some(msg)) => msg,
Ok(None) => { Ok(None) => {
@@ -401,7 +502,7 @@ async fn worker(
if !has_item { if !has_item {
// Poke the listener to adjust subscriptions // Poke the listener to adjust subscriptions
signal_s.send(()).await.ok(); topic_signal_s.send(()).await.ok();
} }
} }
ClientMessage::Ping { data, responded } => { ClientMessage::Ping { data, responded } => {
@@ -417,4 +518,6 @@ async fn worker(
_ => {} _ => {}
} }
} }
}
}
} }
+1 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "revolt-config" name = "revolt-config"
version = "0.7.6" version = "0.7.10"
edition = "2021" edition = "2021"
license = "MIT" license = "MIT"
authors = ["Paul Makles <me@insrt.uk>"] authors = ["Paul Makles <me@insrt.uk>"]
+26 -5
View File
@@ -46,19 +46,40 @@ webhooks_enabled = false
[features.limits] [features.limits]
[features.limits.default] [features.limits.global]
group_size = 100 group_size = 100
bots = 5
message_length = 2000
message_embeds = 5 message_embeds = 5
message_replies = 5 message_replies = 5
message_attachments = 5
message_reactions = 20 message_reactions = 20
servers = 100
server_emoji = 100 server_emoji = 100
server_roles = 200 server_roles = 200
server_channels = 200 server_channels = 200
new_user_days = 3
[features.limits.new_user]
outgoing_friend_requests = 5
bots = 2
message_length = 2000
message_attachments = 5
servers = 100
attachment_size = 20000000
avatar_size = 4000000
background_size = 6000000
icon_size = 2500000
banner_size = 6000000
emoji_size = 500000
[features.limits.default]
outgoing_friend_requests = 10
bots = 5
message_length = 2000
message_attachments = 5
servers = 100
attachment_size = 20000000 attachment_size = 20000000
avatar_size = 4000000 avatar_size = 4000000
background_size = 6000000 background_size = 6000000
+17 -6
View File
@@ -105,19 +105,27 @@ pub struct Api {
} }
#[derive(Deserialize, Debug, Clone)] #[derive(Deserialize, Debug, Clone)]
pub struct FeaturesLimits { pub struct GlobalLimits {
pub group_size: usize, pub group_size: usize,
pub bots: usize,
pub message_length: usize,
pub message_replies: usize,
pub message_attachments: usize,
pub message_embeds: usize, pub message_embeds: usize,
pub message_replies: usize,
pub message_reactions: usize, pub message_reactions: usize,
pub servers: usize,
pub server_emoji: usize, pub server_emoji: usize,
pub server_roles: usize, pub server_roles: usize,
pub server_channels: usize, pub server_channels: usize,
pub new_user_days: usize,
}
#[derive(Deserialize, Debug, Clone)]
pub struct FeaturesLimits {
pub outgoing_friend_requests: usize,
pub bots: usize,
pub message_length: usize,
pub message_attachments: usize,
pub servers: usize,
pub attachment_size: usize, pub attachment_size: usize,
pub avatar_size: usize, pub avatar_size: usize,
pub background_size: usize, pub background_size: usize,
@@ -128,6 +136,9 @@ pub struct FeaturesLimits {
#[derive(Deserialize, Debug, Clone)] #[derive(Deserialize, Debug, Clone)]
pub struct FeaturesLimitsCollection { pub struct FeaturesLimitsCollection {
pub global: GlobalLimits,
pub new_user: FeaturesLimits,
pub default: FeaturesLimits, pub default: FeaturesLimits,
#[serde(flatten)] #[serde(flatten)]
+6 -6
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "revolt-database" name = "revolt-database"
version = "0.7.6" version = "0.7.10"
edition = "2021" edition = "2021"
license = "AGPL-3.0-or-later" license = "AGPL-3.0-or-later"
authors = ["Paul Makles <me@insrt.uk>"] authors = ["Paul Makles <me@insrt.uk>"]
@@ -23,13 +23,13 @@ default = ["mongodb", "async-std-runtime", "tasks"]
[dependencies] [dependencies]
# Core # Core
revolt-config = { version = "0.7.6", path = "../config" } revolt-config = { version = "0.7.10", path = "../config" }
revolt-result = { version = "0.7.6", path = "../result" } revolt-result = { version = "0.7.10", path = "../result" }
revolt-models = { version = "0.7.6", path = "../models", features = [ revolt-models = { version = "0.7.10", path = "../models", features = [
"validator", "validator",
] } ] }
revolt-presence = { version = "0.7.6", path = "../presence" } revolt-presence = { version = "0.7.10", path = "../presence" }
revolt-permissions = { version = "0.7.6", path = "../permissions", features = [ revolt-permissions = { version = "0.7.10", path = "../permissions", features = [
"serde", "serde",
"bson", "bson",
] } ] }
@@ -80,8 +80,7 @@ impl Bot {
return Err(create_error!(IsBot)); return Err(create_error!(IsBot));
} }
let config = config().await; if db.get_number_of_bots_by_user(&owner.id).await? >= owner.limits().await.bots {
if db.get_number_of_bots_by_user(&owner.id).await? >= config.features.limits.default.bots {
return Err(create_error!(ReachedMaximumBots)); return Err(create_error!(ReachedMaximumBots));
} }
@@ -201,9 +201,9 @@ impl Channel {
update_server: bool, update_server: bool,
) -> Result<Channel> { ) -> Result<Channel> {
let config = config().await; let config = config().await;
if server.channels.len() > config.features.limits.default.server_channels { if server.channels.len() > config.features.limits.global.server_channels {
return Err(create_error!(TooManyChannels { return Err(create_error!(TooManyChannels {
max: config.features.limits.default.server_channels, max: config.features.limits.global.server_channels,
})); }));
}; };
@@ -263,9 +263,9 @@ impl Channel {
data.users.insert(owner_id.to_string()); data.users.insert(owner_id.to_string());
let config = config().await; let config = config().await;
if data.users.len() > config.features.limits.default.group_size { if data.users.len() > config.features.limits.global.group_size {
return Err(create_error!(GroupTooLarge { return Err(create_error!(GroupTooLarge {
max: config.features.limits.default.group_size, max: config.features.limits.global.group_size,
})); }));
} }
@@ -346,9 +346,9 @@ impl Channel {
} }
let config = config().await; let config = config().await;
if recipients.len() >= config.features.limits.default.group_size { if recipients.len() >= config.features.limits.global.group_size {
return Err(create_error!(GroupTooLarge { return Err(create_error!(GroupTooLarge {
max: config.features.limits.default.group_size max: config.features.limits.global.group_size
})); }));
} }
@@ -2,7 +2,7 @@ use std::collections::HashSet;
use indexmap::{IndexMap, IndexSet}; use indexmap::{IndexMap, IndexSet};
use iso8601_timestamp::Timestamp; use iso8601_timestamp::Timestamp;
use revolt_config::config; use revolt_config::{config, FeaturesLimits};
use revolt_models::v0::{ use revolt_models::v0::{
self, BulkMessageResponse, DataMessageSend, Embed, MessageAuthor, MessageSort, MessageWebhook, self, BulkMessageResponse, DataMessageSend, Embed, MessageAuthor, MessageSort, MessageWebhook,
PushNotification, ReplyIntent, SendableEmbed, Text, RE_MENTION, PushNotification, ReplyIntent, SendableEmbed, Text, RE_MENTION,
@@ -207,11 +207,13 @@ impl Default for Message {
#[allow(clippy::disallowed_methods)] #[allow(clippy::disallowed_methods)]
impl Message { impl Message {
/// Create message from API data /// Create message from API data
#[allow(clippy::too_many_arguments)]
pub async fn create_from_api( pub async fn create_from_api(
db: &Database, db: &Database,
channel: Channel, channel: Channel,
data: DataMessageSend, data: DataMessageSend,
author: MessageAuthor<'_>, author: MessageAuthor<'_>,
limits: FeaturesLimits,
mut idempotency: IdempotencyKey, mut idempotency: IdempotencyKey,
generate_embeds: bool, generate_embeds: bool,
allow_mentions: bool, allow_mentions: bool,
@@ -221,7 +223,7 @@ impl Message {
Message::validate_sum( Message::validate_sum(
&data.content, &data.content,
data.embeds.as_deref().unwrap_or_default(), data.embeds.as_deref().unwrap_or_default(),
config.features.limits.default.message_length, limits.message_length,
)?; )?;
idempotency idempotency
@@ -288,9 +290,9 @@ impl Message {
// Verify replies are valid. // Verify replies are valid.
let mut replies = HashSet::new(); let mut replies = HashSet::new();
if let Some(entries) = data.replies { if let Some(entries) = data.replies {
if entries.len() > config.features.limits.default.message_replies { if entries.len() > config.features.limits.global.message_replies {
return Err(create_error!(TooManyReplies { return Err(create_error!(TooManyReplies {
max: config.features.limits.default.message_replies, max: config.features.limits.global.message_replies,
})); }));
} }
@@ -320,20 +322,20 @@ impl Message {
if data if data
.attachments .attachments
.as_ref() .as_ref()
.is_some_and(|v| v.len() > config.features.limits.default.message_attachments) .is_some_and(|v| v.len() > limits.message_attachments)
{ {
return Err(create_error!(TooManyAttachments { return Err(create_error!(TooManyAttachments {
max: config.features.limits.default.message_attachments, max: limits.message_attachments,
})); }));
} }
if data if data
.embeds .embeds
.as_ref() .as_ref()
.is_some_and(|v| v.len() > config.features.limits.default.message_embeds) .is_some_and(|v| v.len() > config.features.limits.global.message_embeds)
{ {
return Err(create_error!(TooManyEmbeds { return Err(create_error!(TooManyEmbeds {
max: config.features.limits.default.message_embeds, max: config.features.limits.global.message_embeds,
})); }));
} }
@@ -616,7 +618,7 @@ impl Message {
pub async fn add_reaction(&self, db: &Database, user: &User, emoji: &str) -> Result<()> { pub async fn add_reaction(&self, db: &Database, user: &User, emoji: &str) -> Result<()> {
// Check how many reactions are already on the message // Check how many reactions are already on the message
let config = config().await; let config = config().await;
if self.reactions.len() >= config.features.limits.default.message_reactions if self.reactions.len() >= config.features.limits.global.message_reactions
&& !self.reactions.contains_key(emoji) && !self.reactions.contains_key(emoji)
{ {
return Err(create_error!(InvalidOperation)); return Err(create_error!(InvalidOperation));
@@ -781,7 +783,7 @@ impl Interactions {
if let Some(reactions) = &self.reactions { if let Some(reactions) = &self.reactions {
permissions.throw_if_lacking_channel_permission(ChannelPermission::React)?; permissions.throw_if_lacking_channel_permission(ChannelPermission::React)?;
if reactions.len() > config.features.limits.default.message_reactions { if reactions.len() > config.features.limits.global.message_reactions {
return Err(create_error!(InvalidOperation)); return Err(create_error!(InvalidOperation));
} }
+41 -5
View File
@@ -1,10 +1,10 @@
use std::{collections::HashSet, time::Duration}; use std::{collections::HashSet, str::FromStr, time::Duration};
use crate::{events::client::EventV1, Database, File, RatelimitEvent}; use crate::{events::client::EventV1, Database, File, RatelimitEvent};
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use rand::seq::SliceRandom; use rand::seq::SliceRandom;
use revolt_config::config; use revolt_config::{config, FeaturesLimits};
use revolt_models::v0; use revolt_models::v0;
use revolt_presence::filter_online; use revolt_presence::filter_online;
use revolt_result::{create_error, Result}; use revolt_result::{create_error, Result};
@@ -197,6 +197,22 @@ impl User {
Ok(user) Ok(user)
} }
/// Get limits for this user
pub async fn limits(&self) -> FeaturesLimits {
let config = config().await;
if ulid::Ulid::from_str(&self.id)
.expect("`ulid`")
.datetime()
.elapsed()
.expect("time went backwards")
<= Duration::from_secs(86400u64 * config.features.limits.global.new_user_days as u64)
{
config.features.limits.new_user
} else {
config.features.limits.default
}
}
/// Get the relationship with another user /// Get the relationship with another user
pub fn relationship_with(&self, user_b: &str) -> RelationshipStatus { pub fn relationship_with(&self, user_b: &str) -> RelationshipStatus {
if self.id == user_b { if self.id == user_b {
@@ -235,12 +251,11 @@ impl User {
/// Check if this user can acquire another server /// Check if this user can acquire another server
pub async fn can_acquire_server(&self, db: &Database) -> Result<()> { pub async fn can_acquire_server(&self, db: &Database) -> Result<()> {
let config = config().await; if db.fetch_server_count(&self.id).await? <= self.limits().await.servers {
if db.fetch_server_count(&self.id).await? <= config.features.limits.default.servers {
Ok(()) Ok(())
} else { } else {
Err(create_error!(TooManyServers { Err(create_error!(TooManyServers {
max: config.features.limits.default.servers max: self.limits().await.servers
})) }))
} }
} }
@@ -477,6 +492,7 @@ impl User {
RelationshipStatus::Blocked => Err(create_error!(Blocked)), RelationshipStatus::Blocked => Err(create_error!(Blocked)),
RelationshipStatus::BlockedOther => Err(create_error!(BlockedByOther)), RelationshipStatus::BlockedOther => Err(create_error!(BlockedByOther)),
RelationshipStatus::Incoming => { RelationshipStatus::Incoming => {
// Accept incoming friend request
self.apply_relationship( self.apply_relationship(
db, db,
target, target,
@@ -486,6 +502,26 @@ impl User {
.await .await
} }
RelationshipStatus::None => { RelationshipStatus::None => {
// Get this user's current count of outgoing friend requests
let count = self
.relations
.as_ref()
.map(|relations| {
relations
.iter()
.filter(|r| matches!(r.status, RelationshipStatus::Outgoing))
.count()
})
.unwrap_or_default();
// If we're over the limit, don't allow creating more requests
if count >= self.limits().await.outgoing_friend_requests {
return Err(create_error!(TooManyPendingFriendRequests {
max: self.limits().await.outgoing_friend_requests
}));
}
// Send the friend request
self.apply_relationship( self.apply_relationship(
db, db,
target, target,
@@ -56,7 +56,7 @@ pub async fn worker(db: Database) {
let embeds = generate( let embeds = generate(
task.content, task.content,
&config.hosts.january, &config.hosts.january,
config.features.limits.default.message_embeds, config.features.limits.global.message_embeds,
semaphore, semaphore,
) )
.await; .await;
+3 -3
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "revolt-models" name = "revolt-models"
version = "0.7.6" version = "0.7.10"
edition = "2021" edition = "2021"
license = "MIT" license = "MIT"
authors = ["Paul Makles <me@insrt.uk>"] authors = ["Paul Makles <me@insrt.uk>"]
@@ -19,8 +19,8 @@ default = ["serde", "partials", "rocket"]
[dependencies] [dependencies]
# Core # Core
revolt-config = { version = "0.7.6", path = "../config" } revolt-config = { version = "0.7.10", path = "../config" }
revolt-permissions = { version = "0.7.6", path = "../permissions" } revolt-permissions = { version = "0.7.10", path = "../permissions" }
# Utility # Utility
regex = "1" regex = "1"
+2 -2
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "revolt-permissions" name = "revolt-permissions"
version = "0.7.6" version = "0.7.10"
edition = "2021" edition = "2021"
license = "MIT" license = "MIT"
authors = ["Paul Makles <me@insrt.uk>"] authors = ["Paul Makles <me@insrt.uk>"]
@@ -21,7 +21,7 @@ async-std = { version = "1.8.0", features = ["attributes"] }
[dependencies] [dependencies]
# Core # Core
revolt-result = { version = "0.7.6", path = "../result" } revolt-result = { version = "0.7.10", path = "../result" }
# Utility # Utility
auto_ops = "0.3.0" auto_ops = "0.3.0"
+1 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "revolt-presence" name = "revolt-presence"
version = "0.7.6" version = "0.7.10"
edition = "2021" edition = "2021"
license = "AGPL-3.0-or-later" license = "AGPL-3.0-or-later"
authors = ["Paul Makles <me@insrt.uk>"] authors = ["Paul Makles <me@insrt.uk>"]
+1 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "revolt-result" name = "revolt-result"
version = "0.7.6" version = "0.7.10"
edition = "2021" edition = "2021"
license = "MIT" license = "MIT"
authors = ["Paul Makles <me@insrt.uk>"] authors = ["Paul Makles <me@insrt.uk>"]
+3
View File
@@ -60,6 +60,9 @@ pub enum ErrorType {
Blocked, Blocked,
BlockedByOther, BlockedByOther,
NotFriends, NotFriends,
TooManyPendingFriendRequests {
max: usize,
},
// ? Channel related errors // ? Channel related errors
UnknownChannel, UnknownChannel,
+1
View File
@@ -25,6 +25,7 @@ impl<'r> Responder<'r, 'static> for Error {
ErrorType::Blocked => Status::Conflict, ErrorType::Blocked => Status::Conflict,
ErrorType::BlockedByOther => Status::Forbidden, ErrorType::BlockedByOther => Status::Forbidden,
ErrorType::NotFriends => Status::Forbidden, ErrorType::NotFriends => Status::Forbidden,
ErrorType::TooManyPendingFriendRequests { .. } => Status::BadRequest,
ErrorType::UnknownChannel => Status::NotFound, ErrorType::UnknownChannel => Status::NotFound,
ErrorType::UnknownMessage => Status::NotFound, ErrorType::UnknownMessage => Status::NotFound,
+1 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "revolt-delta" name = "revolt-delta"
version = "0.7.6" version = "0.7.10"
license = "AGPL-3.0-or-later" license = "AGPL-3.0-or-later"
authors = ["Paul Makles <paulmakles@gmail.com>"] authors = ["Paul Makles <paulmakles@gmail.com>"]
edition = "2018" edition = "2018"
+1 -2
View File
@@ -47,8 +47,7 @@ pub async fn web() -> Rocket<Build> {
authifier::database::MongoDb(client.database("revolt")), authifier::database::MongoDb(client.database("revolt")),
), ),
}, },
config: Default::default(), config: authifier_config().await,
// config: authifier_config().await,
event_channel: Some(sender), event_channel: Some(sender),
}; };
@@ -30,11 +30,10 @@ pub async fn edit(
}) })
})?; })?;
let config = config().await;
Message::validate_sum( Message::validate_sum(
&edit.content, &edit.content,
edit.embeds.as_deref().unwrap_or_default(), edit.embeds.as_deref().unwrap_or_default(),
config.features.limits.default.message_length, user.limits().await.message_length,
)?; )?;
// Ensure we have permissions to send a message // Ensure we have permissions to send a message
@@ -81,6 +81,7 @@ pub async fn message_send(
channel, channel,
data, data,
v0::MessageAuthor::User(&author), v0::MessageAuthor::User(&author),
user.limits().await,
idempotency, idempotency,
permissions.has_channel_permission(ChannelPermission::SendEmbeds), permissions.has_channel_permission(ChannelPermission::SendEmbeds),
allow_mentions, allow_mentions,
@@ -45,9 +45,9 @@ pub async fn create_emoji(
// Check that we haven't hit the emoji limit // Check that we haven't hit the emoji limit
let emojis = db.fetch_emoji_by_parent_id(&server.id).await?; let emojis = db.fetch_emoji_by_parent_id(&server.id).await?;
if emojis.len() >= config.features.limits.default.server_emoji { if emojis.len() >= config.features.limits.global.server_emoji {
return Err(create_error!(TooManyEmoji { return Err(create_error!(TooManyEmoji {
max: config.features.limits.default.server_emoji, max: config.features.limits.global.server_emoji,
})); }));
} }
} }
@@ -34,9 +34,9 @@ pub async fn create(
.throw_if_lacking_channel_permission(ChannelPermission::ManageRole)?; .throw_if_lacking_channel_permission(ChannelPermission::ManageRole)?;
let config = config().await; let config = config().await;
if server.roles.len() >= config.features.limits.default.server_roles { if server.roles.len() >= config.features.limits.global.server_roles {
return Err(create_error!(TooManyRoles { return Err(create_error!(TooManyRoles {
max: config.features.limits.default.server_roles, max: config.features.limits.global.server_roles,
})); }));
}; };
@@ -1,3 +1,4 @@
use revolt_config::config;
use revolt_database::{ use revolt_database::{
util::{idempotency::IdempotencyKey, reference::Reference}, util::{idempotency::IdempotencyKey, reference::Reference},
Database, Message, Database, Message,
@@ -58,6 +59,7 @@ pub async fn webhook_execute(
channel, channel,
data, data,
v0::MessageAuthor::Webhook(&webhook.into()), v0::MessageAuthor::Webhook(&webhook.into()),
config().await.features.limits.default,
idempotency, idempotency,
true, true,
true, true,