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]]
name = "payphone"
version = "4.0.9"
version = "4.0.11"
dependencies = [
"async-trait",
"axum",
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "payphone"
version = "4.0.9"
version = "4.0.11"
edition = "2024"
default-run = "payphone"
+1 -2
View File
@@ -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
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
dldo
dildo
goon
goon / go on
gooning
i will fuh you
i will fuck you
+187 -47
View File
@@ -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<String>, Vec<String>)`] - The list of phrases found in the file.
fn parse_phrases_file(file_content: &str) -> (Vec<String>, Vec<String>) {
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<String>,
}
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<String>, Vec<String>) {
(
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::<String>()
.to_ascii_lowercase(),
) {
None
} else {
Some(string)
}
})
.collect(),
)
}
}
struct ThreatsAndSelfHarmRule(Regex);
struct ThreatsAndSelfHarmRule {
pattern: Regex,
exceptions: Vec<String>,
}
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<String>, Vec<String>) {
(
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::<String>()
.to_ascii_lowercase(),
) {
None
} else {
Some(string)
}
})
.collect(),
)
}
}
struct ExplicitSexualLanguageRule(Regex);
struct ExplicitSexualLanguageRule {
pattern: Regex,
exceptions: Vec<String>,
}
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<String>, Vec<String>) {
(
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::<String>()
.to_ascii_lowercase(),
) {
None
} else {
Some(string)
}
})
.collect(),
)
}
}
struct SuggestiveLanguageRule(Regex);
struct SuggestiveLanguageRule {
pattern: Regex,
exceptions: Vec<String>,
}
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<String>, Vec<String>) {
(
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::<String>()
.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<String>,
}
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<String>, Vec<String>) {
(
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::<String>()
.to_ascii_lowercase(),
) {
None
} else {
Some(string)
}
})
.collect(),
)
}
}
pub struct MildSwearWordsRule(Regex);
pub struct MildSwearWordsRule {
pattern: Regex,
exceptions: Vec<String>,
}
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<String>, Vec<String>) {
(
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::<String>()
.to_ascii_lowercase(),
) {
None
} else {
Some(string)
}
})
.collect(),
)
}
}
pub struct CasualInsultsAndTeasing(Regex);
pub struct CasualInsultsAndTeasing {
pattern: Regex,
exceptions: Vec<String>,
}
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<String>, Vec<String>) {
(
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::<String>()
.to_ascii_lowercase(),
) {
None
} else {
Some(string)
}
})
.collect(),
)
}