diff --git a/src/groups/calls/embeds.rs b/src/groups/calls/embeds.rs index a33e35e..5e66f84 100644 --- a/src/groups/calls/embeds.rs +++ b/src/groups/calls/embeds.rs @@ -34,7 +34,7 @@ pub fn first_warning_embed( ) ) .title("Warning") - .description("You broke [Payphone's code of conduct](https://payphone.lat/code-of-conduct) during a call. Since it's your first offense, we assumed you didn't read the rules and made this a warning instead. Read the code of conduct to prevent getting banned.") + .description("You broke [Payphone's code of conduct](https://payphone.lat/code-of-conduct) during a call. Read the code of conduct to prevent getting banned.") .field(EmbedFieldBuilder::new("What message broke the rules?", ellipse(message.content.clone(), 500))) .field(EmbedFieldBuilder::new( "What rules did you break?", diff --git a/src/groups/calls/moderation.rs b/src/groups/calls/moderation.rs index 41aae89..a3f8810 100644 --- a/src/groups/calls/moderation.rs +++ b/src/groups/calls/moderation.rs @@ -85,6 +85,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; @@ -260,6 +261,10 @@ fn regex_from_phrases_list(phrases: &[&str]) -> Regex { Regex::new(&pattern).expect("Failed to compile regex") } +/// The results of the analysis of a rule, including all detections (on the original message first, +/// on the normalized message later) and the associated rule. +type RuleAnalysis = ((Vec, Vec), Arc); + /// Scans all rules on the message and returns the results for each rule which gave detections. /// /// Arguments: @@ -267,15 +272,18 @@ fn regex_from_phrases_list(phrases: &[&str]) -> Regex { /// * `normalized` - The normalized version of the message. /// /// Returns: -/// [`Vec<(Vec, Arc)>`] - The results of the analysis, including all detections -/// and their associated rules. -pub fn analyze_message(original: &str, normalized: &str) -> Vec<(Vec, Arc)> { +/// [`Vec`] - 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 { let mut results = Vec::new(); for rule in RULES.iter() { - let detections = rule.detect(original, normalized); - if !detections.is_empty() { - results.push((detections, rule.clone())); + let (original_detections, normalized_detections) = rule.detect(original, normalized); + if !normalized_detections.is_empty() || !original_detections.is_empty() { + results.push(((original_detections, normalized_detections), rule.clone())); } } @@ -295,15 +303,15 @@ pub fn analyze_message(original: &str, normalized: &str) -> Vec<(Vec, Ar pub fn censor_message( original: &str, normalized: NormalizedMessage, - analysis: &[(Vec, Arc)], + analysis: &[RuleAnalysis], ) -> String { let mut censored = original.to_string(); // Collect all ranges to censor let mut ranges = Vec::new(); - for (findings, _) in analysis { - for finding in findings { + for ((original_findings, normalized_findings), _) in analysis { + for finding in normalized_findings { for (index, _) in normalized.message.match_indices(finding) { let start_index = normalized.mappings.get(index).cloned().unwrap_or(0); let end_index = normalized @@ -315,6 +323,15 @@ pub fn censor_message( ranges.push((start_index, end_index)); } } + + for finding in original_findings { + for (index, _) in original.match_indices(finding) { + let start_index = index; + let end_index = index + finding.len(); + + ranges.push((start_index, end_index)); + } + } } // Sort ranges in reverse order to avoid shifting indices @@ -380,10 +397,9 @@ pub trait Rule: Send + Sync { /// * `normalized` - The normalized version of the message. /// /// Returns: - /// [`Vec`] - The list of detected rule violations in the normalized message. - /// - /// TODO: Support detecting violations in the original message. - fn detect(&self, original: &str, normalized: &str) -> Vec; + /// [`(Vec, Vec)`] - The list of detected rule violations in the original + /// (first vector) and normalized (second vector) message. + fn detect(&self, original: &str, normalized: &str) -> (Vec, Vec); } /// Parses a file containing phrases to be detected by rules. @@ -429,11 +445,14 @@ impl Rule for HateSpeechAndSlursRule { &Action::BlockAndPenalty } - fn detect(&self, _original: &str, normalized: &str) -> Vec { - self.0 - .find_iter(normalized) - .map(|m| normalized[m.start()..m.end()].to_string()) - .collect() + fn detect(&self, _original: &str, normalized: &str) -> (Vec, Vec) { + ( + vec![], + self.0 + .find_iter(normalized) + .map(|m| normalized[m.start()..m.end()].to_string()) + .collect(), + ) } } @@ -459,11 +478,14 @@ impl Rule for ThreatsAndSelfHarmRule { &Action::BlockAndPenalty } - fn detect(&self, _original: &str, normalized: &str) -> Vec { - self.0 - .find_iter(normalized) - .map(|m| normalized[m.start()..m.end()].to_string()) - .collect() + fn detect(&self, _original: &str, normalized: &str) -> (Vec, Vec) { + ( + vec![], + self.0 + .find_iter(normalized) + .map(|m| normalized[m.start()..m.end()].to_string()) + .collect(), + ) } } @@ -489,11 +511,14 @@ impl Rule for ExplicitSexualLanguageRule { "Explicit Sexual Language" } - fn detect(&self, _original: &str, normalized: &str) -> Vec { - self.0 - .find_iter(normalized) - .map(|m| normalized[m.start()..m.end()].to_string()) - .collect() + fn detect(&self, _original: &str, normalized: &str) -> (Vec, Vec) { + ( + vec![], + self.0 + .find_iter(normalized) + .map(|m| normalized[m.start()..m.end()].to_string()) + .collect(), + ) } } @@ -518,11 +543,36 @@ impl Rule for SuggestiveLanguageRule { "Suggestive Language" } - fn detect(&self, _original: &str, normalized: &str) -> Vec { - self.0 - .find_iter(normalized) - .map(|m| normalized[m.start()..m.end()].to_string()) - .collect() + fn detect(&self, _original: &str, normalized: &str) -> (Vec, Vec) { + ( + vec![], + self.0 + .find_iter(normalized) + .map(|m| normalized[m.start()..m.end()].to_string()) + .collect(), + ) + } +} + +struct LinksRule; + +impl Rule for LinksRule { + fn name(&self) -> &'static str { + "Links" + } + + fn action(&self) -> &'static Action { + &Action::SoftCensorAndWarn + } + + fn detect(&self, original: &str, _normalized: &str) -> (Vec, Vec) { + let mut detections = Vec::new(); + + for link in LinkFinder::new().url_must_have_scheme(false).links(original) { + detections.push(link.as_str().to_string()); + } + + (detections, vec![]) } } @@ -533,5 +583,6 @@ pub static RULES: Lazy>> = Lazy::new(|| { Arc::new(ThreatsAndSelfHarmRule::new()), Arc::new(SuggestiveLanguageRule::new()), Arc::new(ExplicitSexualLanguageRule::new()), + Arc::new(LinksRule), ] });