diff --git a/Cargo.lock b/Cargo.lock index cbb3b94..4173922 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1431,7 +1431,7 @@ checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3" [[package]] name = "payphone" -version = "4.0.9" +version = "4.0.11" dependencies = [ "async-trait", "axum", diff --git a/Cargo.toml b/Cargo.toml index f70090e..2e51d4d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "payphone" -version = "4.0.9" +version = "4.0.11" edition = "2024" default-run = "payphone" diff --git a/playbooks/08-payphone.yml b/playbooks/08-payphone.yml index 1699e57..074fbd1 100644 --- a/playbooks/08-payphone.yml +++ b/playbooks/08-payphone.yml @@ -4,8 +4,7 @@ tasks: - name: Bump Version ansible.builtin.shell: > - cargo release commit --execute --no-confirm && \ - cargo release patch --no-verify --no-publish --execute --no-confirm + cargo set-version --bump patch changed_when: false - name: Get Cargo Project Metadata diff --git a/release.sh b/release.sh new file mode 100755 index 0000000..5f1623f --- /dev/null +++ b/release.sh @@ -0,0 +1 @@ +uv run ansible-playbook -i inventory.ini playbooks/08-payphone.yml --vault-password-file playbooks/00-vault-pwd.txt \ No newline at end of file diff --git a/src/groups/calls/lists/explicit-sexual-language.txt b/src/groups/calls/lists/explicit-sexual-language.txt index 1e897f9..8b3c500 100644 --- a/src/groups/calls/lists/explicit-sexual-language.txt +++ b/src/groups/calls/lists/explicit-sexual-language.txt @@ -59,7 +59,7 @@ huz slut dldo dildo -goon +goon / go on gooning i will fuh you i will fuck you diff --git a/src/groups/calls/moderation.rs b/src/groups/calls/moderation.rs index a559c36..111aada 100644 --- a/src/groups/calls/moderation.rs +++ b/src/groups/calls/moderation.rs @@ -247,7 +247,7 @@ fn regex_from_phrase(phrase: &str) -> String { /// /// Returns: /// [`Regex`] - The merged regex for the phrases. -fn regex_from_phrases_list(phrases: &[&str]) -> Regex { +fn regex_from_phrases_list(phrases: &[String]) -> Regex { let mut pattern = String::new(); for phrase in phrases { @@ -405,31 +405,43 @@ pub trait Rule: Send + Sync { /// * `file_content` - The content of the file to parse. /// /// Returns: -/// [`Vec<&str>`] - The list of phrases found in the file. -fn parse_phrases_file(file_content: &str) -> Vec<&str> { - file_content - .lines() - .filter_map(|line| { - let trimmed = line.trim(); - if !trimmed.is_empty() && !trimmed.starts_with("//") { - Some(trimmed) +/// [`(Vec, Vec)`] - The list of phrases found in the file. +fn parse_phrases_file(file_content: &str) -> (Vec, Vec) { + let mut banned = vec![]; + let mut exceptions = vec![]; + + for line in file_content.lines() { + let trimmed = line.trim().to_ascii_lowercase(); + + if !trimmed.is_empty() && !trimmed.starts_with("//") { + if let Some((phrase, phrase_exceptions)) = trimmed.split_once("/") { + banned.push(phrase.trim().to_string()); + exceptions.extend(phrase_exceptions.split(',').map(|s| s.trim().to_string())); } else { - None + banned.push(trimmed.to_string()); } - }) - .collect() + } + } + + (banned, exceptions) } -struct HateSpeechAndSlursRule(Regex); +struct HateSpeechAndSlursRule { + pattern: Regex, + exceptions: Vec, +} impl HateSpeechAndSlursRule { fn new() -> Self { - let phrases: Vec<&str> = + let (phrases, exceptions) = parse_phrases_file(include_str!("lists/hate-speech-and-slurs.txt")); let regex = regex_from_phrases_list(&phrases); - Self(regex) + Self { + pattern: regex, + exceptions, + } } } @@ -445,24 +457,43 @@ impl Rule for HateSpeechAndSlursRule { fn detect(&self, _original: &str, normalized: &str) -> (Vec, Vec) { ( vec![], - self.0 + self.pattern .find_iter(normalized) - .map(|m| normalized[m.start()..m.end()].to_string()) + .filter_map(|m| { + let string = normalized[m.start()..m.end()].to_string(); + + if self.exceptions.contains( + &string + .split_ascii_whitespace() + .collect::() + .to_ascii_lowercase(), + ) { + None + } else { + Some(string) + } + }) .collect(), ) } } -struct ThreatsAndSelfHarmRule(Regex); +struct ThreatsAndSelfHarmRule { + pattern: Regex, + exceptions: Vec, +} impl ThreatsAndSelfHarmRule { fn new() -> Self { - let phrases: Vec<&str> = + let (phrases, exceptions) = parse_phrases_file(include_str!("lists/threats-and-self-harm.txt")); let regex = regex_from_phrases_list(&phrases); - Self(regex) + Self { + pattern: regex, + exceptions, + } } } @@ -478,24 +509,43 @@ impl Rule for ThreatsAndSelfHarmRule { fn detect(&self, _original: &str, normalized: &str) -> (Vec, Vec) { ( vec![], - self.0 + self.pattern .find_iter(normalized) - .map(|m| normalized[m.start()..m.end()].to_string()) + .filter_map(|m| { + let string = normalized[m.start()..m.end()].to_string(); + + if self.exceptions.contains( + &string + .split_ascii_whitespace() + .collect::() + .to_ascii_lowercase(), + ) { + None + } else { + Some(string) + } + }) .collect(), ) } } -struct ExplicitSexualLanguageRule(Regex); +struct ExplicitSexualLanguageRule { + pattern: Regex, + exceptions: Vec, +} impl ExplicitSexualLanguageRule { fn new() -> Self { - let phrases: Vec<&str> = + let (phrases, exceptions) = parse_phrases_file(include_str!("lists/explicit-sexual-language.txt")); let regex = regex_from_phrases_list(&phrases); - Self(regex) + Self { + pattern: regex, + exceptions, + } } } @@ -511,23 +561,43 @@ impl Rule for ExplicitSexualLanguageRule { fn detect(&self, _original: &str, normalized: &str) -> (Vec, Vec) { ( vec![], - self.0 + self.pattern .find_iter(normalized) - .map(|m| normalized[m.start()..m.end()].to_string()) + .filter_map(|m| { + let string = normalized[m.start()..m.end()].to_string(); + + if self.exceptions.contains( + &string + .split_ascii_whitespace() + .collect::() + .to_ascii_lowercase(), + ) { + None + } else { + Some(string) + } + }) .collect(), ) } } -struct SuggestiveLanguageRule(Regex); +struct SuggestiveLanguageRule { + pattern: Regex, + exceptions: Vec, +} impl SuggestiveLanguageRule { fn new() -> Self { - let phrases: Vec<&str> = parse_phrases_file(include_str!("lists/suggestive-language.txt")); + let (phrases, exceptions) = + parse_phrases_file(include_str!("lists/suggestive-language.txt")); let regex = regex_from_phrases_list(&phrases); - Self(regex) + Self { + pattern: regex, + exceptions, + } } } @@ -543,9 +613,22 @@ impl Rule for SuggestiveLanguageRule { fn detect(&self, _original: &str, normalized: &str) -> (Vec, Vec) { ( vec![], - self.0 + self.pattern .find_iter(normalized) - .map(|m| normalized[m.start()..m.end()].to_string()) + .filter_map(|m| { + let string = normalized[m.start()..m.end()].to_string(); + + if self.exceptions.contains( + &string + .split_ascii_whitespace() + .collect::() + .to_ascii_lowercase(), + ) { + None + } else { + Some(string) + } + }) .collect(), ) } @@ -580,17 +663,23 @@ impl Rule for AdvertisingRule { } } -struct SensitiveTopicsAndTriggeringContentRule(Regex); +struct SensitiveTopicsAndTriggeringContentRule { + pattern: Regex, + exceptions: Vec, +} impl SensitiveTopicsAndTriggeringContentRule { fn new() -> Self { - let phrases: Vec<&str> = parse_phrases_file(include_str!( + let (phrases, exceptions) = parse_phrases_file(include_str!( "lists/sensitive-topics-and-triggering-content.txt" )); let regex = regex_from_phrases_list(&phrases); - Self(regex) + Self { + pattern: regex, + exceptions, + } } } @@ -606,23 +695,42 @@ impl Rule for SensitiveTopicsAndTriggeringContentRule { fn detect(&self, _original: &str, normalized: &str) -> (Vec, Vec) { ( vec![], - self.0 + self.pattern .find_iter(normalized) - .map(|m| normalized[m.start()..m.end()].to_string()) + .filter_map(|m| { + let string = normalized[m.start()..m.end()].to_string(); + + if self.exceptions.contains( + &string + .split_ascii_whitespace() + .collect::() + .to_ascii_lowercase(), + ) { + None + } else { + Some(string) + } + }) .collect(), ) } } -pub struct MildSwearWordsRule(Regex); +pub struct MildSwearWordsRule { + pattern: Regex, + exceptions: Vec, +} impl MildSwearWordsRule { fn new() -> Self { - let phrases: Vec<&str> = parse_phrases_file(include_str!("lists/mild-swear-words.txt")); + let (phrases, exceptions) = parse_phrases_file(include_str!("lists/mild-swear-words.txt")); let regex = regex_from_phrases_list(&phrases); - Self(regex) + Self { + pattern: regex, + exceptions, + } } } @@ -638,24 +746,43 @@ impl Rule for MildSwearWordsRule { fn detect(&self, _original: &str, normalized: &str) -> (Vec, Vec) { ( vec![], - self.0 + self.pattern .find_iter(normalized) - .map(|m| normalized[m.start()..m.end()].to_string()) + .filter_map(|m| { + let string = normalized[m.start()..m.end()].to_string(); + + if self.exceptions.contains( + &string + .split_ascii_whitespace() + .collect::() + .to_ascii_lowercase(), + ) { + None + } else { + Some(string) + } + }) .collect(), ) } } -pub struct CasualInsultsAndTeasing(Regex); +pub struct CasualInsultsAndTeasing { + pattern: Regex, + exceptions: Vec, +} impl CasualInsultsAndTeasing { fn new() -> Self { - let phrases: Vec<&str> = + let (phrases, exceptions) = parse_phrases_file(include_str!("lists/casual-insults-and-teasing.txt")); let regex = regex_from_phrases_list(&phrases); - Self(regex) + Self { + pattern: regex, + exceptions, + } } } @@ -671,9 +798,22 @@ impl Rule for CasualInsultsAndTeasing { fn detect(&self, _original: &str, normalized: &str) -> (Vec, Vec) { ( vec![], - self.0 + self.pattern .find_iter(normalized) - .map(|m| normalized[m.start()..m.end()].to_string()) + .filter_map(|m| { + let string = normalized[m.start()..m.end()].to_string(); + + if self.exceptions.contains( + &string + .split_ascii_whitespace() + .collect::() + .to_ascii_lowercase(), + ) { + None + } else { + Some(string) + } + }) .collect(), ) }