forked from jmug/stoatchat
feat: add message reactions + interactions object
This commit is contained in:
4
Cargo.lock
generated
4
Cargo.lock
generated
@@ -2714,7 +2714,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "revolt-bonfire"
|
name = "revolt-bonfire"
|
||||||
version = "1.0.6-patch.2"
|
version = "0.5.5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-std",
|
"async-std",
|
||||||
"async-tungstenite",
|
"async-tungstenite",
|
||||||
@@ -2730,7 +2730,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "revolt-delta"
|
name = "revolt-delta"
|
||||||
version = "0.5.4"
|
version = "0.5.5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-channel",
|
"async-channel",
|
||||||
"async-std",
|
"async-std",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "revolt-bonfire"
|
name = "revolt-bonfire"
|
||||||
version = "1.0.6-patch.2"
|
version = "0.5.5"
|
||||||
license = "AGPL-3.0-or-later"
|
license = "AGPL-3.0-or-later"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "revolt-delta"
|
name = "revolt-delta"
|
||||||
version = "0.5.4"
|
version = "0.5.5"
|
||||||
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"
|
||||||
|
|||||||
34
crates/delta/src/routes/channels/message_clear_reactions.rs
Normal file
34
crates/delta/src/routes/channels/message_clear_reactions.rs
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
use revolt_quark::{
|
||||||
|
models::{message::PartialMessage, User},
|
||||||
|
perms, Db, EmptyResponse, Permission, Ref, Result,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// # Remove All Reactions from Message
|
||||||
|
///
|
||||||
|
/// Remove your own, someone else's or all of a given reaction.
|
||||||
|
///
|
||||||
|
/// Requires `ManageMessages` permission.
|
||||||
|
#[openapi(tag = "Interactions")]
|
||||||
|
#[delete("/<target>/messages/<msg>/reactions")]
|
||||||
|
pub async fn clear_reactions(db: &Db, user: User, target: Ref, msg: Ref) -> Result<EmptyResponse> {
|
||||||
|
let channel = target.as_channel(db).await?;
|
||||||
|
perms(&user)
|
||||||
|
.channel(&channel)
|
||||||
|
.throw_permission_and_view_channel(db, Permission::ManageMessages)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
// Fetch relevant message
|
||||||
|
let mut message = msg.as_message_in(db, channel.id()).await?;
|
||||||
|
|
||||||
|
// Clear reactions
|
||||||
|
message
|
||||||
|
.update(
|
||||||
|
db,
|
||||||
|
PartialMessage {
|
||||||
|
reactions: Some(Default::default()),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map(|_| EmptyResponse)
|
||||||
|
}
|
||||||
29
crates/delta/src/routes/channels/message_react.rs
Normal file
29
crates/delta/src/routes/channels/message_react.rs
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
use revolt_quark::{models::User, perms, Db, EmptyResponse, Permission, Ref, Result};
|
||||||
|
|
||||||
|
/// # Add Reaction to Message
|
||||||
|
///
|
||||||
|
/// React to a given message.
|
||||||
|
#[openapi(tag = "Interactions")]
|
||||||
|
#[put("/<target>/messages/<msg>/reactions/<emoji>")]
|
||||||
|
pub async fn react_message(
|
||||||
|
db: &Db,
|
||||||
|
user: User,
|
||||||
|
target: Ref,
|
||||||
|
msg: Ref,
|
||||||
|
emoji: Ref,
|
||||||
|
) -> Result<EmptyResponse> {
|
||||||
|
let channel = target.as_channel(db).await?;
|
||||||
|
perms(&user)
|
||||||
|
.channel(&channel)
|
||||||
|
.throw_permission_and_view_channel(db, Permission::React)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
// Fetch relevant message
|
||||||
|
let message = msg.as_message_in(db, channel.id()).await?;
|
||||||
|
|
||||||
|
// Add the reaction
|
||||||
|
message
|
||||||
|
.add_reaction(db, &user, &emoji.id)
|
||||||
|
.await
|
||||||
|
.map(|_| EmptyResponse)
|
||||||
|
}
|
||||||
@@ -2,7 +2,7 @@ use std::collections::HashSet;
|
|||||||
|
|
||||||
use revolt_quark::{
|
use revolt_quark::{
|
||||||
models::{
|
models::{
|
||||||
message::{Masquerade, Reply, SendableEmbed},
|
message::{Interactions, Masquerade, Reply, SendableEmbed},
|
||||||
Message, User,
|
Message, User,
|
||||||
},
|
},
|
||||||
perms,
|
perms,
|
||||||
@@ -40,6 +40,8 @@ pub struct DataMessageSend {
|
|||||||
/// Masquerade to apply to this message
|
/// Masquerade to apply to this message
|
||||||
#[validate]
|
#[validate]
|
||||||
masquerade: Option<Masquerade>,
|
masquerade: Option<Masquerade>,
|
||||||
|
/// Information about how this message should be interacted with
|
||||||
|
interactions: Option<Interactions>,
|
||||||
}
|
}
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
@@ -86,6 +88,7 @@ pub async fn message_send(
|
|||||||
channel: channel.id().to_string(),
|
channel: channel.id().to_string(),
|
||||||
author: user.id.clone(),
|
author: user.id.clone(),
|
||||||
masquerade: data.masquerade,
|
masquerade: data.masquerade,
|
||||||
|
interactions: data.interactions.unwrap_or_default(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -112,7 +115,10 @@ pub async fn message_send(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. Verify replies are valid.
|
// 3. Ensure interactions information is correct
|
||||||
|
message.interactions.validate(db).await?;
|
||||||
|
|
||||||
|
// 4. 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() > 5 {
|
if entries.len() > 5 {
|
||||||
@@ -145,7 +151,7 @@ pub async fn message_send(
|
|||||||
.replace(replies.into_iter().collect::<Vec<String>>());
|
.replace(replies.into_iter().collect::<Vec<String>>());
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. Process included embeds.
|
// 5. Process included embeds.
|
||||||
let mut embeds = vec![];
|
let mut embeds = vec![];
|
||||||
if let Some(sendable_embeds) = data.embeds {
|
if let Some(sendable_embeds) = data.embeds {
|
||||||
for sendable_embed in sendable_embeds {
|
for sendable_embed in sendable_embeds {
|
||||||
@@ -157,7 +163,7 @@ pub async fn message_send(
|
|||||||
message.embeds.replace(embeds);
|
message.embeds.replace(embeds);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 5. Add attachments to message.
|
// 6. Add attachments to message.
|
||||||
let mut attachments = vec![];
|
let mut attachments = vec![];
|
||||||
if let Some(ids) = &data.attachments {
|
if let Some(ids) = &data.attachments {
|
||||||
if !ids.is_empty() {
|
if !ids.is_empty() {
|
||||||
@@ -183,10 +189,10 @@ pub async fn message_send(
|
|||||||
message.attachments.replace(attachments);
|
message.attachments.replace(attachments);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 6. Set content
|
// 7. Set content
|
||||||
message.content = data.content;
|
message.content = data.content;
|
||||||
|
|
||||||
// 7. Pass-through nonce value for clients
|
// 8. Pass-through nonce value for clients
|
||||||
message.nonce = Some(idempotency.into_key());
|
message.nonce = Some(idempotency.into_key());
|
||||||
|
|
||||||
message.create(db, &channel, Some(&user)).await?;
|
message.create(db, &channel, Some(&user)).await?;
|
||||||
|
|||||||
58
crates/delta/src/routes/channels/message_unreact.rs
Normal file
58
crates/delta/src/routes/channels/message_unreact.rs
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
use revolt_quark::{models::User, perms, Db, EmptyResponse, Permission, Ref, Result};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
/// # Query Parameters
|
||||||
|
#[derive(Serialize, Deserialize, JsonSchema, FromForm)]
|
||||||
|
pub struct OptionsUnreact {
|
||||||
|
/// Remove a specific user's reaction
|
||||||
|
user_id: Option<String>,
|
||||||
|
/// Remove all reactions
|
||||||
|
remove_all: Option<bool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// # Remove Reaction(s) to Message
|
||||||
|
///
|
||||||
|
/// Remove your own, someone else's or all of a given reaction.
|
||||||
|
///
|
||||||
|
/// Requires `ManageMessages` if changing others' reactions.
|
||||||
|
#[openapi(tag = "Interactions")]
|
||||||
|
#[delete("/<target>/messages/<msg>/reactions/<emoji>?<options..>")]
|
||||||
|
pub async fn unreact_message(
|
||||||
|
db: &Db,
|
||||||
|
user: User,
|
||||||
|
target: Ref,
|
||||||
|
msg: Ref,
|
||||||
|
emoji: Ref,
|
||||||
|
options: OptionsUnreact,
|
||||||
|
) -> Result<EmptyResponse> {
|
||||||
|
let channel = target.as_channel(db).await?;
|
||||||
|
let mut permissions = perms(&user).channel(&channel);
|
||||||
|
permissions
|
||||||
|
.throw_permission_and_view_channel(db, Permission::React)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
// Check if we need to escalate permissions
|
||||||
|
let remove_all = options.remove_all.unwrap_or_default();
|
||||||
|
if options.user_id.is_some() || remove_all {
|
||||||
|
permissions
|
||||||
|
.throw_permission(db, Permission::ManageMessages)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch relevant message
|
||||||
|
let message = msg.as_message_in(db, channel.id()).await?;
|
||||||
|
|
||||||
|
// Check if we should wipe all of this reaction
|
||||||
|
if remove_all {
|
||||||
|
return message
|
||||||
|
.clear_reaction(db, &emoji.id)
|
||||||
|
.await
|
||||||
|
.map(|_| EmptyResponse);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove the reaction
|
||||||
|
message
|
||||||
|
.remove_reaction(db, options.user_id.as_ref().unwrap_or(&user.id), &emoji.id)
|
||||||
|
.await
|
||||||
|
.map(|_| EmptyResponse)
|
||||||
|
}
|
||||||
@@ -11,13 +11,16 @@ mod group_remove_member;
|
|||||||
mod invite_create;
|
mod invite_create;
|
||||||
mod members_fetch;
|
mod members_fetch;
|
||||||
mod message_bulk_delete;
|
mod message_bulk_delete;
|
||||||
|
mod message_clear_reactions;
|
||||||
mod message_delete;
|
mod message_delete;
|
||||||
mod message_edit;
|
mod message_edit;
|
||||||
mod message_fetch;
|
mod message_fetch;
|
||||||
mod message_query;
|
mod message_query;
|
||||||
mod message_query_stale;
|
mod message_query_stale;
|
||||||
|
mod message_react;
|
||||||
mod message_search;
|
mod message_search;
|
||||||
mod message_send;
|
mod message_send;
|
||||||
|
mod message_unreact;
|
||||||
mod permissions_set;
|
mod permissions_set;
|
||||||
mod permissions_set_default;
|
mod permissions_set_default;
|
||||||
mod voice_join;
|
mod voice_join;
|
||||||
@@ -44,5 +47,8 @@ pub fn routes() -> (Vec<Route>, OpenApi) {
|
|||||||
voice_join::req,
|
voice_join::req,
|
||||||
permissions_set::req,
|
permissions_set::req,
|
||||||
permissions_set_default::req,
|
permissions_set_default::req,
|
||||||
|
message_react::react_message,
|
||||||
|
message_unreact::unreact_message,
|
||||||
|
message_clear_reactions::clear_reactions
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -80,6 +80,7 @@ fn custom_openapi_spec() -> OpenApi {
|
|||||||
"Channel Invites",
|
"Channel Invites",
|
||||||
"Channel Permissions",
|
"Channel Permissions",
|
||||||
"Messaging",
|
"Messaging",
|
||||||
|
"Interactions",
|
||||||
"Groups",
|
"Groups",
|
||||||
"Voice"
|
"Voice"
|
||||||
]
|
]
|
||||||
|
|||||||
1850
crates/quark/assets/emojis.txt
Normal file
1850
crates/quark/assets/emojis.txt
Normal file
File diff suppressed because it is too large
Load Diff
@@ -78,6 +78,29 @@ pub enum EventV1 {
|
|||||||
/// Delete message
|
/// Delete message
|
||||||
MessageDelete { id: String, channel: String },
|
MessageDelete { id: String, channel: String },
|
||||||
|
|
||||||
|
/// New reaction to a message
|
||||||
|
MessageReact {
|
||||||
|
id: String,
|
||||||
|
channel_id: String,
|
||||||
|
user_id: String,
|
||||||
|
emoji_id: String,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// Remove user's reaction from message
|
||||||
|
MessageUnreact {
|
||||||
|
id: String,
|
||||||
|
channel_id: String,
|
||||||
|
user_id: String,
|
||||||
|
emoji_id: String,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// Remove a reaction from message
|
||||||
|
MessageRemoveReaction {
|
||||||
|
id: String,
|
||||||
|
channel_id: String,
|
||||||
|
emoji_id: String,
|
||||||
|
},
|
||||||
|
|
||||||
/// Bulk delete messages
|
/// Bulk delete messages
|
||||||
BulkMessageDelete { channel: String, ids: Vec<String> },
|
BulkMessageDelete { channel: String, ids: Vec<String> },
|
||||||
|
|
||||||
|
|||||||
@@ -64,4 +64,22 @@ impl AbstractMessage for DummyDb {
|
|||||||
) -> Result<Vec<Message>> {
|
) -> Result<Vec<Message>> {
|
||||||
Ok(vec![self.fetch_message(channel).await.unwrap()])
|
Ok(vec![self.fetch_message(channel).await.unwrap()])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Add a new reaction to a message
|
||||||
|
async fn add_reaction(&self, id: &str, emoji: &str, user: &str) -> Result<()> {
|
||||||
|
info!("Add to {id} with {emoji} and {user}");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Remove a reaction from a message
|
||||||
|
async fn remove_reaction(&self, id: &str, emoji: &str, user: &str) -> Result<()> {
|
||||||
|
info!("Remove {emoji} from {id} for {user}");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Remove reaction from a message
|
||||||
|
async fn clear_reaction(&self, id: &str, emoji: &str) -> Result<()> {
|
||||||
|
info!("Clear {emoji} on {id}");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,9 +8,10 @@ use crate::{
|
|||||||
events::client::EventV1,
|
events::client::EventV1,
|
||||||
models::{
|
models::{
|
||||||
message::{
|
message::{
|
||||||
AppendMessage, BulkMessageResponse, PartialMessage, SendableEmbed, SystemMessage,
|
AppendMessage, BulkMessageResponse, Interactions, PartialMessage, SendableEmbed,
|
||||||
|
SystemMessage,
|
||||||
},
|
},
|
||||||
Channel, Message, User,
|
Channel, Emoji, Message, User,
|
||||||
},
|
},
|
||||||
presence::presence_filter_online,
|
presence::presence_filter_online,
|
||||||
tasks::ack::AckEvent,
|
tasks::ack::AckEvent,
|
||||||
@@ -191,6 +192,79 @@ impl Message {
|
|||||||
Err(Error::PayloadTooLarge)
|
Err(Error::PayloadTooLarge)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Add a reaction to a message
|
||||||
|
pub async fn add_reaction(&self, db: &Database, user: &User, emoji: &str) -> Result<()> {
|
||||||
|
// Check if the emoji is whitelisted
|
||||||
|
if !self.interactions.can_use(emoji) {
|
||||||
|
return Err(Error::InvalidOperation);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the emoji is usable by us
|
||||||
|
if !Emoji::can_use(db, emoji).await? {
|
||||||
|
return Err(Error::InvalidOperation);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send reaction event
|
||||||
|
EventV1::MessageReact {
|
||||||
|
id: self.id.to_string(),
|
||||||
|
channel_id: self.channel.to_string(),
|
||||||
|
user_id: user.id.to_string(),
|
||||||
|
emoji_id: emoji.to_string(),
|
||||||
|
}
|
||||||
|
.p(self.channel.to_string())
|
||||||
|
.await;
|
||||||
|
|
||||||
|
// Add emoji
|
||||||
|
db.add_reaction(&self.id, emoji, &user.id).await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Remove a reaction from a message
|
||||||
|
pub async fn remove_reaction(&self, db: &Database, user: &str, emoji: &str) -> Result<()> {
|
||||||
|
// Check if it actually exists
|
||||||
|
let empty = if let Some(users) = self.reactions.get(emoji) {
|
||||||
|
if !users.contains(user) {
|
||||||
|
return Err(Error::NotFound);
|
||||||
|
}
|
||||||
|
|
||||||
|
users.len() == 1
|
||||||
|
} else {
|
||||||
|
return Err(Error::NotFound);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Send reaction event
|
||||||
|
EventV1::MessageUnreact {
|
||||||
|
id: self.id.to_string(),
|
||||||
|
channel_id: self.channel.to_string(),
|
||||||
|
user_id: user.to_string(),
|
||||||
|
emoji_id: emoji.to_string(),
|
||||||
|
}
|
||||||
|
.p(self.channel.to_string())
|
||||||
|
.await;
|
||||||
|
|
||||||
|
if empty {
|
||||||
|
// If empty, remove the reaction entirely
|
||||||
|
db.clear_reaction(&self.id, emoji).await
|
||||||
|
} else {
|
||||||
|
// Otherwise only remove that one reaction
|
||||||
|
db.remove_reaction(&self.id, emoji, user).await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Remove a reaction from a message
|
||||||
|
pub async fn clear_reaction(&self, db: &Database, emoji: &str) -> Result<()> {
|
||||||
|
// Send reaction event
|
||||||
|
EventV1::MessageRemoveReaction {
|
||||||
|
id: self.id.to_string(),
|
||||||
|
channel_id: self.channel.to_string(),
|
||||||
|
emoji_id: emoji.to_string(),
|
||||||
|
}
|
||||||
|
.p(self.channel.to_string())
|
||||||
|
.await;
|
||||||
|
|
||||||
|
// Write to database
|
||||||
|
db.clear_reaction(&self.id, emoji).await
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait IntoUsers {
|
pub trait IntoUsers {
|
||||||
@@ -324,3 +398,40 @@ impl BulkMessageResponse {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Interactions {
|
||||||
|
/// Validate interactions info is correct
|
||||||
|
pub async fn validate(&self, db: &Database) -> Result<()> {
|
||||||
|
if let Some(reactions) = &self.reactions {
|
||||||
|
if reactions.len() > 20 {
|
||||||
|
return Err(Error::InvalidOperation);
|
||||||
|
}
|
||||||
|
|
||||||
|
for reaction in reactions {
|
||||||
|
if !Emoji::can_use(db, reaction).await? {
|
||||||
|
return Err(Error::InvalidOperation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if we can use a given emoji to react
|
||||||
|
pub fn can_use(&self, emoji: &str) -> bool {
|
||||||
|
if self.restrict_reactions {
|
||||||
|
if let Some(reactions) = &self.reactions {
|
||||||
|
reactions.contains(emoji)
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if default initialisation of fields
|
||||||
|
pub fn is_default(&self) -> bool {
|
||||||
|
!self.restrict_reactions && self.reactions.is_none()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,9 +1,21 @@
|
|||||||
|
use std::{collections::HashSet, str::FromStr};
|
||||||
|
|
||||||
|
use ulid::Ulid;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
events::client::EventV1,
|
events::client::EventV1,
|
||||||
models::{emoji::EmojiParent, Emoji},
|
models::{emoji::EmojiParent, Emoji},
|
||||||
Database, Result,
|
Database, Result,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
/// Permissible emojis
|
||||||
|
static ref PERMISSIBLE_EMOJIS: HashSet<String> = include_str!(crate::asset!("emojis.txt"))
|
||||||
|
.split('\n')
|
||||||
|
.map(|x| x.into())
|
||||||
|
.collect();
|
||||||
|
}
|
||||||
|
|
||||||
impl Emoji {
|
impl Emoji {
|
||||||
/// Get parent id
|
/// Get parent id
|
||||||
fn parent(&self) -> &str {
|
fn parent(&self) -> &str {
|
||||||
@@ -33,4 +45,14 @@ impl Emoji {
|
|||||||
|
|
||||||
db.detach_emoji(&self).await
|
db.detach_emoji(&self).await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Check whether we can use a given emoji
|
||||||
|
pub async fn can_use(db: &Database, emoji: &str) -> Result<bool> {
|
||||||
|
if Ulid::from_str(emoji).is_ok() {
|
||||||
|
db.fetch_emoji(emoji).await?;
|
||||||
|
Ok(true)
|
||||||
|
} else {
|
||||||
|
Ok(PERMISSIBLE_EMOJIS.contains(emoji))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -279,4 +279,70 @@ impl AbstractMessage for MongoDb {
|
|||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Add a new reaction to a message
|
||||||
|
async fn add_reaction(&self, id: &str, emoji: &str, user: &str) -> Result<()> {
|
||||||
|
self.col::<Document>(COL)
|
||||||
|
.update_one(
|
||||||
|
doc! {
|
||||||
|
"_id": id
|
||||||
|
},
|
||||||
|
doc! {
|
||||||
|
"$addToSet": {
|
||||||
|
format!("reactions.{emoji}"): user
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map(|_| ())
|
||||||
|
.map_err(|_| Error::DatabaseError {
|
||||||
|
operation: "update_one",
|
||||||
|
with: "message",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Remove a reaction from a message
|
||||||
|
async fn remove_reaction(&self, id: &str, emoji: &str, user: &str) -> Result<()> {
|
||||||
|
self.col::<Document>(COL)
|
||||||
|
.update_one(
|
||||||
|
doc! {
|
||||||
|
"_id": id
|
||||||
|
},
|
||||||
|
doc! {
|
||||||
|
"$pull": {
|
||||||
|
format!("reactions.{emoji}"): user
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map(|_| ())
|
||||||
|
.map_err(|_| Error::DatabaseError {
|
||||||
|
operation: "update_one",
|
||||||
|
with: "message",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Remove reaction from a message
|
||||||
|
async fn clear_reaction(&self, id: &str, emoji: &str) -> Result<()> {
|
||||||
|
self.col::<Document>(COL)
|
||||||
|
.update_one(
|
||||||
|
doc! {
|
||||||
|
"_id": id
|
||||||
|
},
|
||||||
|
doc! {
|
||||||
|
"$unset": {
|
||||||
|
format!("reactions.{emoji}"): 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map(|_| ())
|
||||||
|
.map_err(|_| Error::DatabaseError {
|
||||||
|
operation: "update_one",
|
||||||
|
with: "message",
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
use crate::util::regex::RE_COLOUR;
|
use crate::util::regex::RE_COLOUR;
|
||||||
|
use std::collections::{HashMap, HashSet};
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use validator::Validate;
|
use validator::Validate;
|
||||||
@@ -13,6 +14,11 @@ use crate::{
|
|||||||
types::january::Embed,
|
types::january::Embed,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// Utility function to check if a boolean value is false
|
||||||
|
pub fn if_false(t: &bool) -> bool {
|
||||||
|
!t
|
||||||
|
}
|
||||||
|
|
||||||
/// # Reply
|
/// # Reply
|
||||||
///
|
///
|
||||||
/// Representation of a message reply before it is sent.
|
/// Representation of a message reply before it is sent.
|
||||||
@@ -88,6 +94,17 @@ pub struct Masquerade {
|
|||||||
pub colour: Option<String>,
|
pub colour: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Information to guide interactions on this message
|
||||||
|
#[derive(Serialize, Deserialize, JsonSchema, Debug, Clone, Validate, Default)]
|
||||||
|
pub struct Interactions {
|
||||||
|
/// Reactions which should always appear and be distinct
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none", default)]
|
||||||
|
pub reactions: Option<HashSet<String>>,
|
||||||
|
/// Whether reactions should be restricted to the given list
|
||||||
|
#[serde(skip_serializing_if = "if_false", default)]
|
||||||
|
pub restrict_reactions: bool,
|
||||||
|
}
|
||||||
|
|
||||||
/// Representation of a Message on Revolt
|
/// Representation of a Message on Revolt
|
||||||
#[derive(Serialize, Deserialize, JsonSchema, Debug, Clone, OptionalStruct, Default)]
|
#[derive(Serialize, Deserialize, JsonSchema, Debug, Clone, OptionalStruct, Default)]
|
||||||
#[optional_derive(Serialize, Deserialize, JsonSchema, Debug, Default, Clone)]
|
#[optional_derive(Serialize, Deserialize, JsonSchema, Debug, Default, Clone)]
|
||||||
@@ -127,6 +144,12 @@ pub struct Message {
|
|||||||
/// Array of message ids this message is replying to
|
/// Array of message ids this message is replying to
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub replies: Option<Vec<String>>,
|
pub replies: Option<Vec<String>>,
|
||||||
|
/// Hashmap of emoji IDs to array of user IDs
|
||||||
|
#[serde(skip_serializing_if = "HashMap::is_empty", default)]
|
||||||
|
pub reactions: HashMap<String, HashSet<String>>,
|
||||||
|
/// Information about how this message should be interacted with
|
||||||
|
#[serde(skip_serializing_if = "Interactions::is_default", default)]
|
||||||
|
pub interactions: Interactions,
|
||||||
/// Name and / or avatar overrides for this message
|
/// Name and / or avatar overrides for this message
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub masquerade: Option<Masquerade>,
|
pub masquerade: Option<Masquerade>,
|
||||||
|
|||||||
@@ -65,8 +65,8 @@ pub enum Permission {
|
|||||||
UploadFiles = 1 << 27,
|
UploadFiles = 1 << 27,
|
||||||
/// Masquerade messages using custom nickname and avatar
|
/// Masquerade messages using custom nickname and avatar
|
||||||
Masquerade = 1 << 28,
|
Masquerade = 1 << 28,
|
||||||
|
/// React to messages with emojis
|
||||||
// % 1 bits reserved
|
React = 1 << 29,
|
||||||
|
|
||||||
// * Voice permissions
|
// * Voice permissions
|
||||||
/// Connect to a voice channel
|
/// Connect to a voice channel
|
||||||
|
|||||||
@@ -42,4 +42,13 @@ pub trait AbstractMessage: Sync + Send {
|
|||||||
after: Option<String>,
|
after: Option<String>,
|
||||||
sort: MessageSort,
|
sort: MessageSort,
|
||||||
) -> Result<Vec<Message>>;
|
) -> Result<Vec<Message>>;
|
||||||
|
|
||||||
|
/// Add a new reaction to a message
|
||||||
|
async fn add_reaction(&self, id: &str, emoji: &str, user: &str) -> Result<()>;
|
||||||
|
|
||||||
|
/// Remove a reaction from a message
|
||||||
|
async fn remove_reaction(&self, id: &str, emoji: &str, user: &str) -> Result<()>;
|
||||||
|
|
||||||
|
/// Remove reaction from a message
|
||||||
|
async fn clear_reaction(&self, id: &str, emoji: &str) -> Result<()>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ use serde::{Deserialize, Serialize};
|
|||||||
|
|
||||||
use crate::models::{Bot, Channel, Emoji, Invite, Member, Message, Server, ServerBan, User};
|
use crate::models::{Bot, Channel, Emoji, Invite, Member, Message, Server, ServerBan, User};
|
||||||
use crate::presence::presence_is_online;
|
use crate::presence::presence_is_online;
|
||||||
use crate::{Database, Result};
|
use crate::{Database, Error, Result};
|
||||||
|
|
||||||
/// Reference to some object in the database
|
/// Reference to some object in the database
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
@@ -44,6 +44,16 @@ impl Ref {
|
|||||||
db.fetch_message(&self.id).await
|
db.fetch_message(&self.id).await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Fetch message in channel from Ref
|
||||||
|
pub async fn as_message_in(&self, db: &Database, channel: &str) -> Result<Message> {
|
||||||
|
let message = self.as_message(db).await?;
|
||||||
|
if message.channel != channel {
|
||||||
|
return Err(Error::NotFound);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(message)
|
||||||
|
}
|
||||||
|
|
||||||
/// Fetch bot from Ref
|
/// Fetch bot from Ref
|
||||||
pub async fn as_bot(&self, db: &Database) -> Result<Bot> {
|
pub async fn as_bot(&self, db: &Database) -> Result<Bot> {
|
||||||
db.fetch_bot(&self.id).await
|
db.fetch_bot(&self.id).await
|
||||||
|
|||||||
Reference in New Issue
Block a user