feat: add message reactions + interactions object

This commit is contained in:
Paul Makles
2022-07-15 21:24:49 +01:00
parent ab2af9b5e5
commit 7bdd2d69d6
19 changed files with 2281 additions and 15 deletions

View File

@@ -64,4 +64,22 @@ impl AbstractMessage for DummyDb {
) -> Result<Vec<Message>> {
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(())
}
}

View File

@@ -8,9 +8,10 @@ use crate::{
events::client::EventV1,
models::{
message::{
AppendMessage, BulkMessageResponse, PartialMessage, SendableEmbed, SystemMessage,
AppendMessage, BulkMessageResponse, Interactions, PartialMessage, SendableEmbed,
SystemMessage,
},
Channel, Message, User,
Channel, Emoji, Message, User,
},
presence::presence_filter_online,
tasks::ack::AckEvent,
@@ -191,6 +192,79 @@ impl Message {
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 {
@@ -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()
}
}

View File

@@ -1,9 +1,21 @@
use std::{collections::HashSet, str::FromStr};
use ulid::Ulid;
use crate::{
events::client::EventV1,
models::{emoji::EmojiParent, Emoji},
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 {
/// Get parent id
fn parent(&self) -> &str {
@@ -33,4 +45,14 @@ impl Emoji {
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))
}
}
}

View File

@@ -279,4 +279,70 @@ impl AbstractMessage for MongoDb {
)
.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",
})
}
}