blocked word lists
This commit is contained in:
@@ -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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user