blocked word lists

This commit is contained in:
2025-04-08 22:48:17 +03:00
parent cb6d7d23ae
commit 513e810c91
+82
View File
@@ -0,0 +1,82 @@
use std::collections::HashSet;
use std::path::Path;
use crate::error::Result;
// embedded at compile time - cannot be removed without recompiling
const BUILTIN_LIST: &str = include_str!("../../../data/banned_words.txt");
pub struct Blocklist {
words: HashSet<String>,
}
fn normalize_leet(s: &str) -> String {
s.chars().map(|c| match c {
'0' | 'O' => 'O',
'1' | 'l' | '!' | '|' => 'I',
'3' => 'E',
'4' | '@' => 'A',
'5' | '$' => 'S',
'7' => 'T',
'8' => 'B',
'9' => 'G',
_ => c.to_ascii_uppercase(),
}).collect()
}
fn parse_lines(content: &str) -> impl Iterator<Item = String> + '_ {
content.lines()
.map(|l| l.trim().to_uppercase())
.filter(|l| !l.is_empty() && !l.starts_with('#'))
}
impl Blocklist {
pub fn empty() -> Self {
// "empty" still includes the built-in list
let words = parse_lines(BUILTIN_LIST).collect();
Self { words }
}
pub fn load(path: &Path) -> Result<Self> {
// start with built-in, then add from file (additive only)
let mut words: HashSet<String> = parse_lines(BUILTIN_LIST).collect();
if let Ok(content) = std::fs::read_to_string(path) {
for w in parse_lines(&content) {
words.insert(w);
}
}
Ok(Self { words })
}
pub fn is_blocked(&self, word: &str) -> bool {
self.words.contains(word)
}
pub fn add_word(&mut self, word: &str) {
let w = word.trim().to_uppercase();
if !w.is_empty() {
self.words.insert(w);
}
}
pub fn extra_count(&self) -> usize {
let builtin: HashSet<String> = parse_lines(BUILTIN_LIST).collect();
self.words.len().saturating_sub(builtin.len())
}
pub fn contains_blocked(&self, text: &str) -> bool {
let upper = text.to_uppercase();
let normalized = normalize_leet(text);
let stripped: String = upper.chars().filter(|c| c.is_alphabetic()).collect();
let stripped_leet: String = normalized.chars().filter(|c| c.is_alphabetic()).collect();
for word in &self.words {
if word.len() < 3 { continue; }
if upper.contains(word.as_str()) { return true; }
if normalized.contains(word.as_str()) { return true; }
if stripped.contains(word.as_str()) { return true; }
if stripped_leet.contains(word.as_str()) { return true; }
}
false
}
}