Add support for banned word exceptions

This commit is contained in:
2025-08-18 22:04:46 -03:00
parent 23969006c2
commit a994c86f98
6 changed files with 192 additions and 52 deletions
Generated
+1 -1
View File
@@ -1431,7 +1431,7 @@ checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3"
[[package]] [[package]]
name = "payphone" name = "payphone"
version = "4.0.9" version = "4.0.11"
dependencies = [ dependencies = [
"async-trait", "async-trait",
"axum", "axum",
+1 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "payphone" name = "payphone"
version = "4.0.9" version = "4.0.11"
edition = "2024" edition = "2024"
default-run = "payphone" default-run = "payphone"
+1 -2
View File
@@ -4,8 +4,7 @@
tasks: tasks:
- name: Bump Version - name: Bump Version
ansible.builtin.shell: > ansible.builtin.shell: >
cargo release commit --execute --no-confirm && \ cargo set-version --bump patch
cargo release patch --no-verify --no-publish --execute --no-confirm
changed_when: false changed_when: false
- name: Get Cargo Project Metadata - name: Get Cargo Project Metadata
Executable
+1
View File
@@ -0,0 +1 @@
uv run ansible-playbook -i inventory.ini playbooks/08-payphone.yml --vault-password-file playbooks/00-vault-pwd.txt
@@ -59,7 +59,7 @@ huz
slut slut
dldo dldo
dildo dildo
goon goon / go on
gooning gooning
i will fuh you i will fuh you
i will fuck you i will fuck you
+186 -46
View File
@@ -247,7 +247,7 @@ fn regex_from_phrase(phrase: &str) -> String {
/// ///
/// Returns: /// Returns:
/// [`Regex`] - The merged regex for the phrases. /// [`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(); let mut pattern = String::new();
for phrase in phrases { for phrase in phrases {
@@ -405,31 +405,43 @@ pub trait Rule: Send + Sync {
/// * `file_content` - The content of the file to parse. /// * `file_content` - The content of the file to parse.
/// ///
/// Returns: /// Returns:
/// [`Vec<&str>`] - The list of phrases found in the file. /// [`(Vec<String>, Vec<String>)`] - The list of phrases found in the file.
fn parse_phrases_file(file_content: &str) -> Vec<&str> { fn parse_phrases_file(file_content: &str) -> (Vec<String>, Vec<String>) {
file_content let mut banned = vec![];
.lines() let mut exceptions = vec![];
.filter_map(|line| {
let trimmed = line.trim(); for line in file_content.lines() {
let trimmed = line.trim().to_ascii_lowercase();
if !trimmed.is_empty() && !trimmed.starts_with("//") { if !trimmed.is_empty() && !trimmed.starts_with("//") {
Some(trimmed) 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 { } else {
None banned.push(trimmed.to_string());
}
} }
})
.collect()
} }
struct HateSpeechAndSlursRule(Regex); (banned, exceptions)
}
struct HateSpeechAndSlursRule {
pattern: Regex,
exceptions: Vec<String>,
}
impl HateSpeechAndSlursRule { impl HateSpeechAndSlursRule {
fn new() -> Self { fn new() -> Self {
let phrases: Vec<&str> = let (phrases, exceptions) =
parse_phrases_file(include_str!("lists/hate-speech-and-slurs.txt")); parse_phrases_file(include_str!("lists/hate-speech-and-slurs.txt"));
let regex = regex_from_phrases_list(&phrases); 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<String>, Vec<String>) { fn detect(&self, _original: &str, normalized: &str) -> (Vec<String>, Vec<String>) {
( (
vec![], vec![],
self.0 self.pattern
.find_iter(normalized) .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::<String>()
.to_ascii_lowercase(),
) {
None
} else {
Some(string)
}
})
.collect(), .collect(),
) )
} }
} }
struct ThreatsAndSelfHarmRule(Regex); struct ThreatsAndSelfHarmRule {
pattern: Regex,
exceptions: Vec<String>,
}
impl ThreatsAndSelfHarmRule { impl ThreatsAndSelfHarmRule {
fn new() -> Self { fn new() -> Self {
let phrases: Vec<&str> = let (phrases, exceptions) =
parse_phrases_file(include_str!("lists/threats-and-self-harm.txt")); parse_phrases_file(include_str!("lists/threats-and-self-harm.txt"));
let regex = regex_from_phrases_list(&phrases); 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<String>, Vec<String>) { fn detect(&self, _original: &str, normalized: &str) -> (Vec<String>, Vec<String>) {
( (
vec![], vec![],
self.0 self.pattern
.find_iter(normalized) .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::<String>()
.to_ascii_lowercase(),
) {
None
} else {
Some(string)
}
})
.collect(), .collect(),
) )
} }
} }
struct ExplicitSexualLanguageRule(Regex); struct ExplicitSexualLanguageRule {
pattern: Regex,
exceptions: Vec<String>,
}
impl ExplicitSexualLanguageRule { impl ExplicitSexualLanguageRule {
fn new() -> Self { fn new() -> Self {
let phrases: Vec<&str> = let (phrases, exceptions) =
parse_phrases_file(include_str!("lists/explicit-sexual-language.txt")); parse_phrases_file(include_str!("lists/explicit-sexual-language.txt"));
let regex = regex_from_phrases_list(&phrases); 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<String>, Vec<String>) { fn detect(&self, _original: &str, normalized: &str) -> (Vec<String>, Vec<String>) {
( (
vec![], vec![],
self.0 self.pattern
.find_iter(normalized) .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::<String>()
.to_ascii_lowercase(),
) {
None
} else {
Some(string)
}
})
.collect(), .collect(),
) )
} }
} }
struct SuggestiveLanguageRule(Regex); struct SuggestiveLanguageRule {
pattern: Regex,
exceptions: Vec<String>,
}
impl SuggestiveLanguageRule { impl SuggestiveLanguageRule {
fn new() -> Self { 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); 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<String>, Vec<String>) { fn detect(&self, _original: &str, normalized: &str) -> (Vec<String>, Vec<String>) {
( (
vec![], vec![],
self.0 self.pattern
.find_iter(normalized) .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::<String>()
.to_ascii_lowercase(),
) {
None
} else {
Some(string)
}
})
.collect(), .collect(),
) )
} }
@@ -580,17 +663,23 @@ impl Rule for AdvertisingRule {
} }
} }
struct SensitiveTopicsAndTriggeringContentRule(Regex); struct SensitiveTopicsAndTriggeringContentRule {
pattern: Regex,
exceptions: Vec<String>,
}
impl SensitiveTopicsAndTriggeringContentRule { impl SensitiveTopicsAndTriggeringContentRule {
fn new() -> Self { 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" "lists/sensitive-topics-and-triggering-content.txt"
)); ));
let regex = regex_from_phrases_list(&phrases); 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<String>, Vec<String>) { fn detect(&self, _original: &str, normalized: &str) -> (Vec<String>, Vec<String>) {
( (
vec![], vec![],
self.0 self.pattern
.find_iter(normalized) .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::<String>()
.to_ascii_lowercase(),
) {
None
} else {
Some(string)
}
})
.collect(), .collect(),
) )
} }
} }
pub struct MildSwearWordsRule(Regex); pub struct MildSwearWordsRule {
pattern: Regex,
exceptions: Vec<String>,
}
impl MildSwearWordsRule { impl MildSwearWordsRule {
fn new() -> Self { 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); 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<String>, Vec<String>) { fn detect(&self, _original: &str, normalized: &str) -> (Vec<String>, Vec<String>) {
( (
vec![], vec![],
self.0 self.pattern
.find_iter(normalized) .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::<String>()
.to_ascii_lowercase(),
) {
None
} else {
Some(string)
}
})
.collect(), .collect(),
) )
} }
} }
pub struct CasualInsultsAndTeasing(Regex); pub struct CasualInsultsAndTeasing {
pattern: Regex,
exceptions: Vec<String>,
}
impl CasualInsultsAndTeasing { impl CasualInsultsAndTeasing {
fn new() -> Self { fn new() -> Self {
let phrases: Vec<&str> = let (phrases, exceptions) =
parse_phrases_file(include_str!("lists/casual-insults-and-teasing.txt")); parse_phrases_file(include_str!("lists/casual-insults-and-teasing.txt"));
let regex = regex_from_phrases_list(&phrases); 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<String>, Vec<String>) { fn detect(&self, _original: &str, normalized: &str) -> (Vec<String>, Vec<String>) {
( (
vec![], vec![],
self.0 self.pattern
.find_iter(normalized) .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::<String>()
.to_ascii_lowercase(),
) {
None
} else {
Some(string)
}
})
.collect(), .collect(),
) )
} }