refactor(core): use core crates for sending messages

This commit is contained in:
Paul Makles
2023-09-03 16:31:39 +01:00
parent 279d9ef1b5
commit 9929f7499a
7 changed files with 100 additions and 71 deletions

View File

@@ -52,6 +52,7 @@ message_length = 2000
message_embeds = 5 message_embeds = 5
message_replies = 5 message_replies = 5
message_attachments = 5 message_attachments = 5
message_reactions = 20
servers = 100 servers = 100
server_emoji = 100 server_emoji = 100
server_roles = 200 server_roles = 200

View File

@@ -101,6 +101,7 @@ pub struct FeaturesLimits {
pub message_replies: usize, pub message_replies: usize,
pub message_attachments: usize, pub message_attachments: usize,
pub message_embeds: usize, pub message_embeds: usize,
pub message_reactions: usize,
pub servers: usize, pub servers: usize,
pub server_emoji: usize, pub server_emoji: usize,
pub server_roles: usize, pub server_roles: usize,

View File

@@ -7,6 +7,7 @@ use revolt_models::v0::{
self, DataMessageSend, Embed, MessageAuthor, MessageSort, MessageWebhook, PushNotification, self, DataMessageSend, Embed, MessageAuthor, MessageSort, MessageWebhook, PushNotification,
ReplyIntent, SendableEmbed, RE_MENTION, ReplyIntent, SendableEmbed, RE_MENTION,
}; };
use revolt_permissions::{ChannelPermission, PermissionValue};
use revolt_result::Result; use revolt_result::Result;
use ulid::Ulid; use ulid::Ulid;
@@ -14,7 +15,7 @@ use crate::{
events::client::EventV1, events::client::EventV1,
tasks::{self, ack::AckEvent}, tasks::{self, ack::AckEvent},
util::idempotency::IdempotencyKey, util::idempotency::IdempotencyKey,
Channel, Database, File, Channel, Database, Emoji, File,
}; };
auto_derived_partial!( auto_derived_partial!(
@@ -353,7 +354,9 @@ impl Message {
// Pass-through nonce value for clients // Pass-through nonce value for clients
message.nonce = Some(idempotency.into_key()); message.nonce = Some(idempotency.into_key());
// Send the message
message.send(db, author, &channel, generate_embeds).await?; message.send(db, author, &channel, generate_embeds).await?;
Ok(message) Ok(message)
} }
@@ -528,6 +531,27 @@ impl SystemMessage {
} }
impl Interactions { impl Interactions {
/// Validate interactions info is correct
pub async fn validate(&self, db: &Database, permissions: &PermissionValue) -> Result<()> {
let config = config().await;
if let Some(reactions) = &self.reactions {
permissions.throw_if_lacking_channel_permission(ChannelPermission::React)?;
if reactions.len() > config.features.limits.default.message_reactions {
return Err(create_error!(InvalidOperation));
}
for reaction in reactions {
if !Emoji::can_use(db, reaction).await? {
return Err(create_error!(InvalidOperation));
}
}
}
Ok(())
}
/// Check if we can use a given emoji to react /// Check if we can use a given emoji to react
pub fn can_use(&self, emoji: &str) -> bool { pub fn can_use(&self, emoji: &str) -> bool {
if self.restrict_reactions { if self.restrict_reactions {

View File

@@ -626,18 +626,26 @@ impl From<crate::FieldsRole> for FieldsRole {
} }
impl crate::User { impl crate::User {
pub async fn into<P>(self, perspective: P) -> User pub async fn into<'a, P>(self, perspective: P) -> User
where where
P: Into<Option<crate::User>>, P: Into<Option<&'a crate::User>>,
{ {
let relationship = if let Some(perspective) = perspective.into() { let relationship = if let Some(perspective) = perspective.into() {
perspective if perspective.id == self.id {
.relations RelationshipStatus::User
.unwrap_or_default() } else {
.into_iter() perspective
.find(|relationship| relationship.id == self.id) .relations
.map(|relationship| relationship.status.into()) .as_ref()
.unwrap_or_default() .map(|relations| {
relations
.iter()
.find(|relationship| relationship.id == self.id)
.map(|relationship| relationship.status.clone().into())
.unwrap_or_default()
})
.unwrap_or_default()
}
} else { } else {
RelationshipStatus::None RelationshipStatus::None
}; };

View File

@@ -39,20 +39,17 @@ impl PermissionValue {
} }
/// Check whether certain a permission has been granted /// Check whether certain a permission has been granted
pub fn has(&mut self, v: u64) -> bool { pub fn has(&self, v: u64) -> bool {
(self.0 & v) == v (self.0 & v) == v
} }
/// Check whether certain a channel permission has been granted /// Check whether certain a channel permission has been granted
pub fn has_channel_permission(&mut self, permission: ChannelPermission) -> bool { pub fn has_channel_permission(&self, permission: ChannelPermission) -> bool {
self.has(permission as u64) self.has(permission as u64)
} }
/// Throw if missing channel permission /// Throw if missing channel permission
pub fn throw_if_lacking_channel_permission( pub fn throw_if_lacking_channel_permission(&self, permission: ChannelPermission) -> Result<()> {
&mut self,
permission: ChannelPermission,
) -> Result<()> {
if self.has_channel_permission(permission) { if self.has_channel_permission(permission) {
Ok(()) Ok(())
} else { } else {

View File

@@ -1,12 +1,13 @@
use revolt_database::util::idempotency::IdempotencyKey; use revolt_database::util::permissions::DatabasePermissionQuery;
use revolt_quark::{ use revolt_database::{
models::{message::DataMessageSend, Message, User}, util::idempotency::IdempotencyKey, util::reference::Reference, Database, User,
perms,
types::push::MessageAuthor,
Db, Error, Permission, Ref, Result,
}; };
use revolt_database::{Interactions, Message};
use revolt_models::v0;
use revolt_permissions::{calculate_channel_permissions, ChannelPermission};
use revolt_result::{create_error, Result};
use rocket::serde::json::Json; use rocket::serde::json::Json;
use rocket::State;
use validator::Validate; use validator::Validate;
/// # Send Message /// # Send Message
@@ -15,68 +16,63 @@ use validator::Validate;
#[openapi(tag = "Messaging")] #[openapi(tag = "Messaging")]
#[post("/<target>/messages", data = "<data>")] #[post("/<target>/messages", data = "<data>")]
pub async fn message_send( pub async fn message_send(
db: &Db, db: &State<Database>,
user: User, user: User,
target: Ref, target: Reference,
data: Json<DataMessageSend>, data: Json<v0::DataMessageSend>,
idempotency: IdempotencyKey, idempotency: IdempotencyKey,
) -> Result<Json<Message>> { ) -> Result<Json<v0::Message>> {
let data = data.into_inner(); let data = data.into_inner();
data.validate() data.validate().map_err(|error| {
.map_err(|error| Error::FailedValidation { error })?; create_error!(FailedValidation {
error: error.to_string()
})
})?;
// Ensure we have permissions to send a message // Ensure we have permissions to send a message
let channel = target.as_channel(db).await?; let channel = target.as_channel(db).await?;
let mut permissions = perms(&user).channel(&channel); let mut query = DatabasePermissionQuery::new(db, &user).channel(&channel);
permissions let permissions = calculate_channel_permissions(&mut query).await;
.throw_permission_and_view_channel(db, Permission::SendMessage) permissions.throw_if_lacking_channel_permission(ChannelPermission::SendMessage)?;
.await?;
// Verify permissions for masquerade // Verify permissions for masquerade
if let Some(masq) = &data.masquerade { if let Some(masq) = &data.masquerade {
permissions permissions.throw_if_lacking_channel_permission(ChannelPermission::Masquerade)?;
.throw_permission(db, Permission::Masquerade)
.await?;
if masq.colour.is_some() { if masq.colour.is_some() {
permissions permissions.throw_if_lacking_channel_permission(ChannelPermission::ManageRole)?;
.throw_permission(db, Permission::ManageRole)
.await?;
} }
} }
// Check permissions for embeds // Check permissions for embeds
if data.embeds.as_ref().is_some_and(|v| !v.is_empty()) { if data.embeds.as_ref().is_some_and(|v| !v.is_empty()) {
permissions permissions.throw_if_lacking_channel_permission(ChannelPermission::SendEmbeds)?;
.throw_permission(db, Permission::SendEmbeds)
.await?;
} }
// Check permissions for files // Check permissions for files
if data.attachments.as_ref().is_some_and(|v| !v.is_empty()) { if data.attachments.as_ref().is_some_and(|v| !v.is_empty()) {
permissions permissions.throw_if_lacking_channel_permission(ChannelPermission::UploadFiles)?;
.throw_permission(db, Permission::UploadFiles)
.await?;
} }
// Ensure interactions information is correct // Ensure interactions information is correct
if let Some(interactions) = &data.interactions { if let Some(interactions) = &data.interactions {
interactions.validate(db, &mut permissions).await?; let interactions: Interactions = interactions.clone().into();
interactions.validate(db, &permissions).await?;
} }
// Create the message // Create the message
let message = channel let author: v0::User = user.clone().into(Some(&user)).await;
.send_message( Ok(Json(
Message::create_from_api(
db, db,
channel,
data, data,
MessageAuthor::User(&user), v0::MessageAuthor::User(&author),
idempotency, idempotency,
permissions true,
.has_permission(db, Permission::SendEmbeds)
.await?,
) )
.await?; .await?
.into(),
Ok(Json(message)) ))
} }

View File

@@ -31,35 +31,37 @@ pub async fn webhook_execute(
let webhook = webhook_id.as_webhook(db).await?; let webhook = webhook_id.as_webhook(db).await?;
webhook.assert_token(&token)?; webhook.assert_token(&token)?;
let mut value: PermissionValue = webhook.permissions.into(); let permissions: PermissionValue = webhook.permissions.into();
value.throw_if_lacking_channel_permission(ChannelPermission::SendMessage)?; permissions.throw_if_lacking_channel_permission(ChannelPermission::SendMessage)?;
if data.attachments.as_ref().map_or(false, |v| !v.is_empty()) { if data.attachments.as_ref().map_or(false, |v| !v.is_empty()) {
value.throw_if_lacking_channel_permission(ChannelPermission::UploadFiles)?; permissions.throw_if_lacking_channel_permission(ChannelPermission::UploadFiles)?;
} }
if data.embeds.as_ref().map_or(false, |v| !v.is_empty()) { if data.embeds.as_ref().map_or(false, |v| !v.is_empty()) {
value.throw_if_lacking_channel_permission(ChannelPermission::SendEmbeds)?; permissions.throw_if_lacking_channel_permission(ChannelPermission::SendEmbeds)?;
} }
if data.masquerade.is_some() { if data.masquerade.is_some() {
value.throw_if_lacking_channel_permission(ChannelPermission::Masquerade)?; permissions.throw_if_lacking_channel_permission(ChannelPermission::Masquerade)?;
} }
if data.interactions.is_some() { if data.interactions.is_some() {
value.throw_if_lacking_channel_permission(ChannelPermission::React)?; permissions.throw_if_lacking_channel_permission(ChannelPermission::React)?;
} }
let channel = db.fetch_channel(&webhook.channel_id).await?; let channel = db.fetch_channel(&webhook.channel_id).await?;
let message = Message::create_from_api(
db,
channel,
data,
v0::MessageAuthor::Webhook(&webhook.into()),
idempotency,
true,
)
.await?;
Ok(Json(message.into())) Ok(Json(
Message::create_from_api(
db,
channel,
data,
v0::MessageAuthor::Webhook(&webhook.into()),
idempotency,
true,
)
.await?
.into(),
))
} }