Add support for editing messages
This commit is contained in:
+202
-88
@@ -2,13 +2,16 @@ use std::{error::Error, sync::Arc};
|
||||
|
||||
use async_trait::async_trait;
|
||||
use itertools::Itertools;
|
||||
use twilight_model::{gateway::payload::incoming::MessageCreate, id::Id};
|
||||
use twilight_model::{
|
||||
gateway::payload::incoming::{MessageCreate, MessageUpdate},
|
||||
id::Id,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
database::models::{Call, Channel, Message, User, UserIncident},
|
||||
discord::sanitizing::SanitizeDiscordMarkdown,
|
||||
framework::{
|
||||
events::traits::{EventResult, OnMessageHandler},
|
||||
events::traits::{EventResult, OnMessageEditHandler, OnMessageHandler},
|
||||
interactions::components::ActionRow,
|
||||
runtime::Runtime,
|
||||
},
|
||||
@@ -18,8 +21,62 @@ use crate::{
|
||||
moderation::{Action, Rule, escape_message},
|
||||
},
|
||||
state::State,
|
||||
utils::i64_to_u64,
|
||||
};
|
||||
|
||||
async fn penalize_user(
|
||||
user: User,
|
||||
message: Message,
|
||||
broken_rules: Vec<Arc<dyn Rule>>,
|
||||
rt: &Runtime<State>,
|
||||
) -> Result<(), Box<dyn Error>> {
|
||||
let reason = format!(
|
||||
"You broke the following rules during a call:\n{}",
|
||||
broken_rules
|
||||
.iter()
|
||||
.map(|rule| format!("- {}", rule.name()))
|
||||
.join("\n")
|
||||
);
|
||||
|
||||
let new_incident = user
|
||||
.incident(
|
||||
reason.clone(),
|
||||
message.data.clone(),
|
||||
Some(user.id),
|
||||
&rt.state.db,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let dm_channel = rt
|
||||
.http
|
||||
.create_private_channel(Id::new(user.id))
|
||||
.await?
|
||||
.model()
|
||||
.await?;
|
||||
|
||||
let embed = if new_incident.is_first_incident {
|
||||
embeds::first_warning_embed(&rt.app, &message, broken_rules)
|
||||
} else if new_incident.counting_incidents == 0 {
|
||||
embeds::repeat_warning_embed(&rt.app, &message, broken_rules)
|
||||
} else if new_incident.counting_incidents == 1 {
|
||||
embeds::first_ban_embed(&rt.app, &message, broken_rules)
|
||||
} else {
|
||||
embeds::final_ban_embed(&rt.app, &message, broken_rules)
|
||||
};
|
||||
|
||||
rt.http
|
||||
.create_message(dm_channel.id)
|
||||
.embeds(&[embed])
|
||||
.components(&[ActionRow::new(&[
|
||||
(None, Arc::new(CodeOfConductButton)),
|
||||
(None, Arc::new(NeedHelpButton)),
|
||||
])
|
||||
.build()])
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct CallMessageHandler;
|
||||
|
||||
@@ -114,18 +171,73 @@ impl OnMessageHandler<State> for CallMessageHandler {
|
||||
|
||||
let profile = get_display_profile(event);
|
||||
|
||||
let message = Message::build()
|
||||
.call_id(call.id)
|
||||
.message(event.0.clone())
|
||||
.profile(profile.clone())
|
||||
.build()
|
||||
.expect(
|
||||
"The message builder was missing either the call ID, the message, or the profile",
|
||||
);
|
||||
message.insert(&rt.state.db).await?;
|
||||
|
||||
let (content, rules) = escape_message(event);
|
||||
|
||||
let message: Message;
|
||||
|
||||
if rules
|
||||
.iter()
|
||||
.any(|rule| rule.action() == &Action::BlockAndPenalty)
|
||||
{
|
||||
rt.http
|
||||
.create_message(event.channel_id)
|
||||
.content(
|
||||
"❌ Your message has been blocked because it broke Payphone's code of conduct. Check your DMs for more information.",
|
||||
)
|
||||
.await?;
|
||||
|
||||
rt.http
|
||||
.create_message(Id::new(opposite_channel.id))
|
||||
.content(&format!(
|
||||
"❌ **{}** Sent a message that was blocked.\n-# If you see an inappropiate message, please report it using `Message > Apps > Report Message`.",
|
||||
profile.name.sanitize(false)
|
||||
))
|
||||
.await?;
|
||||
|
||||
message = Message::build()
|
||||
.call_id(call.id)
|
||||
.message(event.0.clone())
|
||||
.profile(profile.clone())
|
||||
.is_censored(!rules.is_empty())
|
||||
.build()
|
||||
.expect(
|
||||
"The message builder was missing either the call ID, the message, or the profile",
|
||||
);
|
||||
} else {
|
||||
let message_sent = rt
|
||||
.http
|
||||
.execute_webhook(Id::new(webhook_id), &webhook_token)
|
||||
.allowed_mentions(None)
|
||||
.username(&profile.name)
|
||||
.avatar_url(&profile.avatar_url)
|
||||
.content(&content)
|
||||
.wait()
|
||||
.await
|
||||
.inspect_err(|e| {
|
||||
log::error!(
|
||||
"Failed to send message to webhook for call {} in channel {}: {e:?}",
|
||||
call.id,
|
||||
opposite_channel.id
|
||||
);
|
||||
})?
|
||||
.model()
|
||||
.await?;
|
||||
|
||||
message = Message::build()
|
||||
.call_id(call.id)
|
||||
.message(event.0.clone())
|
||||
.profile(profile.clone())
|
||||
.is_censored(!rules.is_empty())
|
||||
.propagation_message_id(message_sent.id.get())
|
||||
.propagation_channel_id(opposite_channel.id)
|
||||
.build()
|
||||
.expect(
|
||||
"The message builder was missing either the call ID, the message, or the profile",
|
||||
);
|
||||
}
|
||||
|
||||
message.insert(&rt.state.db).await?;
|
||||
|
||||
if rules
|
||||
.iter()
|
||||
.any(|rule| rule.action() == &Action::BlockAndPenalty)
|
||||
@@ -142,92 +254,94 @@ impl OnMessageHandler<State> for CallMessageHandler {
|
||||
user
|
||||
};
|
||||
|
||||
rt.http
|
||||
.create_message(event.channel_id)
|
||||
.content(
|
||||
"❌ Your message has been blocked because it broke Payphone's code of conduct. Check your DMs for more information.",
|
||||
)
|
||||
.await?;
|
||||
|
||||
rt.http
|
||||
.create_message(Id::new(opposite_channel.id))
|
||||
.content(&format!(
|
||||
"❌ **{}** Sent a message that was blocked.\n-# If you see an inappropiate message, please report it using `Message > Apps > Report Message`.",
|
||||
profile.name.sanitize(false)
|
||||
))
|
||||
.await?;
|
||||
|
||||
penalize_user(user, message, rules, &rt).await?;
|
||||
} else {
|
||||
rt.http
|
||||
.execute_webhook(Id::new(webhook_id), &webhook_token)
|
||||
.allowed_mentions(None)
|
||||
.username(&profile.name)
|
||||
.avatar_url(&profile.avatar_url)
|
||||
.content(&content)
|
||||
.await
|
||||
.inspect_err(|e| {
|
||||
log::error!(
|
||||
"Failed to send message to webhook for call {} in channel {}: {e:?}",
|
||||
call.id,
|
||||
opposite_channel.id
|
||||
);
|
||||
})?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
async fn penalize_user(
|
||||
user: User,
|
||||
message: Message,
|
||||
broken_rules: Vec<Arc<dyn Rule>>,
|
||||
rt: &Runtime<State>,
|
||||
) -> Result<(), Box<dyn Error>> {
|
||||
let reason = format!(
|
||||
"You broke the following rules during a call:\n{}",
|
||||
broken_rules
|
||||
.iter()
|
||||
.map(|rule| format!("- {}", rule.name()))
|
||||
.join("\n")
|
||||
);
|
||||
#[derive(Debug)]
|
||||
pub struct CallMessageEditHandler;
|
||||
|
||||
let new_incident = user
|
||||
.incident(
|
||||
reason.clone(),
|
||||
message.data.clone(),
|
||||
Some(user.id),
|
||||
&rt.state.db,
|
||||
)
|
||||
.await?;
|
||||
#[async_trait]
|
||||
impl OnMessageEditHandler<State> for CallMessageEditHandler {
|
||||
async fn on_message_edit(&self, event: &MessageUpdate, rt: Arc<Runtime<State>>) -> EventResult {
|
||||
let Some(channel) = Channel::get_by_id(event.channel_id.get(), &rt.state.db).await? else {
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
let dm_channel = rt
|
||||
.http
|
||||
.create_private_channel(Id::new(user.id))
|
||||
.await?
|
||||
.model()
|
||||
.await?;
|
||||
let call = if let Some(call_id) = channel.call_id {
|
||||
if let Some(call) = Call::get_by_id(call_id, &rt.state.db).await? {
|
||||
if !call.is_active() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let embed = if new_incident.is_first_incident {
|
||||
embeds::first_warning_embed(&rt.app, &message, broken_rules)
|
||||
} else if new_incident.counting_incidents == 0 {
|
||||
embeds::repeat_warning_embed(&rt.app, &message, broken_rules)
|
||||
} else if new_incident.counting_incidents == 1 {
|
||||
embeds::first_ban_embed(&rt.app, &message, broken_rules)
|
||||
} else {
|
||||
embeds::final_ban_embed(&rt.app, &message, broken_rules)
|
||||
};
|
||||
call
|
||||
} else {
|
||||
return Ok(());
|
||||
}
|
||||
} else {
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
rt.http
|
||||
.create_message(dm_channel.id)
|
||||
.embeds(&[embed])
|
||||
.components(&[ActionRow::new(&[
|
||||
(None, Arc::new(CodeOfConductButton)),
|
||||
(None, Arc::new(NeedHelpButton)),
|
||||
])
|
||||
.build()])
|
||||
.await?;
|
||||
let Some(mut message) =
|
||||
Message::get_by_call_and_message_id(call.id, event.id.get(), &rt.state.db).await?
|
||||
else {
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
Ok(())
|
||||
let opposite_channel_id = call.other_party_channel_id(event.channel_id.get());
|
||||
let Some(opposite_channel) = Channel::get_by_id(opposite_channel_id, &rt.state.db).await?
|
||||
else {
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
if let Some(propagation_message_id) = message.data.propagation_message_id {
|
||||
let (content, rules) = escape_message(event);
|
||||
|
||||
if rules
|
||||
.iter()
|
||||
.any(|rule| rule.action() == &Action::BlockAndPenalty)
|
||||
{
|
||||
rt.http
|
||||
.create_message(event.channel_id)
|
||||
.content(
|
||||
"❌ Your message's edit has been blocked because it broke Payphone's code of conduct. Check your DMs for more information.",
|
||||
)
|
||||
.reply(event.id)
|
||||
.await?;
|
||||
|
||||
let user = User::get_by_id(event.author.id.get(), &rt.state.db).await?;
|
||||
|
||||
if let Some(user) = user {
|
||||
penalize_user(user, message, rules, &rt).await?;
|
||||
}
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if let Some((webhook_id, webhook_token)) = opposite_channel
|
||||
.webhook_id
|
||||
.zip(opposite_channel.webhook_token)
|
||||
{
|
||||
rt.http
|
||||
.update_webhook_message(
|
||||
Id::new(webhook_id),
|
||||
&webhook_token,
|
||||
Id::new(i64_to_u64(propagation_message_id)),
|
||||
)
|
||||
.content(Some(&content))
|
||||
.await?;
|
||||
|
||||
message.data.content = content;
|
||||
message.data.is_edited = true;
|
||||
message.data.is_censored = !rules.is_empty();
|
||||
|
||||
message.insert(&rt.state.db).await?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,7 +88,7 @@ use std::{collections::HashMap, sync::Arc};
|
||||
use linkify::LinkFinder;
|
||||
use once_cell::sync::Lazy;
|
||||
use regex::Regex;
|
||||
use twilight_model::gateway::payload::incoming::MessageCreate;
|
||||
use twilight_model::channel::Message;
|
||||
use unicode_segmentation::UnicodeSegmentation;
|
||||
use unidecode::unidecode;
|
||||
|
||||
@@ -103,7 +103,7 @@ use crate::discord::sanitizing::SanitizeDiscordMarkdown;
|
||||
/// Returns:
|
||||
/// [`(String, Vec<Arc<dyn Rule>>)`] - The escaped and censored message, and a vector of the rules
|
||||
/// broken.
|
||||
pub fn escape_message(event: &MessageCreate) -> (String, Vec<Arc<dyn Rule>>) {
|
||||
pub fn escape_message(event: &Message) -> (String, Vec<Arc<dyn Rule>>) {
|
||||
let mut message = event.content.to_string();
|
||||
|
||||
for mention in &event.mentions {
|
||||
@@ -274,10 +274,7 @@ type RuleAnalysis = ((Vec<String>, Vec<String>), Arc<dyn Rule>);
|
||||
/// Returns:
|
||||
/// [`Vec<RuleAnalysis>`] - The results of the analysis, including all detections (on the original
|
||||
/// message first, on the normalized message later) and their associated rules.
|
||||
pub fn analyze_message(
|
||||
original: &str,
|
||||
normalized: &str,
|
||||
) -> Vec<RuleAnalysis> {
|
||||
pub fn analyze_message(original: &str, normalized: &str) -> Vec<RuleAnalysis> {
|
||||
let mut results = Vec::new();
|
||||
|
||||
for rule in RULES.iter() {
|
||||
@@ -568,7 +565,10 @@ impl Rule for LinksRule {
|
||||
fn detect(&self, original: &str, _normalized: &str) -> (Vec<String>, Vec<String>) {
|
||||
let mut detections = Vec::new();
|
||||
|
||||
for link in LinkFinder::new().url_must_have_scheme(false).links(original) {
|
||||
for link in LinkFinder::new()
|
||||
.url_must_have_scheme(false)
|
||||
.links(original)
|
||||
{
|
||||
detections.push(link.as_str().to_string());
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user