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