forked from jmug/stoatchat
refactor(core): use core crates for sending messages
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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))
|
))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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(),
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user