filter update, hero optimization
This commit is contained in:
+60
-27
@@ -1,10 +1,9 @@
|
||||
use axum::{Json, Router, http::StatusCode, response::IntoResponse, routing::post};
|
||||
use axum::{http::StatusCode, response::IntoResponse, routing::post, Json, Router};
|
||||
use regex::Regex;
|
||||
use reqwest::Client;
|
||||
use rustrict::CensorStr;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::sync::LazyLock;
|
||||
use unicode_segmentation::UnicodeSegmentation;
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug)]
|
||||
struct CommentPayload {
|
||||
@@ -12,31 +11,59 @@ struct CommentPayload {
|
||||
message: String,
|
||||
}
|
||||
|
||||
// Расширенный список КОРНЕЙ. Регулярка будет искать эти корни внутри слов.
|
||||
// Используем \p{L}* (любые буквы) вокруг корня, чтобы ловить склонения.
|
||||
// 1. Расширенный список КОРНЕЙ.
|
||||
static BAD_WORDS_REGEX: LazyLock<Regex> = LazyLock::new(|| {
|
||||
Regex::new(r"(?i)(хуй|хуя|пизд|ебат|ебан|бляд|блят|шлюх|сука|сучк|порно|наркот|убить|насилие|heroin|meth|murder|gore|соли|закладк|мефедрон|гашиш)").unwrap()
|
||||
Regex::new(r"(?i)(хуй|хуя|хуе|пизд|ебат|ебан|ебл|бляд|блят|шлюх|сука|сучк|порно|наркот|убить|насилие|heroin|meth|murder|gore|соли|закладк|мефедрон|гашиш|спайс|cpam|scam|crypto|binance)").unwrap()
|
||||
});
|
||||
|
||||
// 2. БЕЛЫЙ СПИСОК (Исключения). Защищает нормальные слова, содержащие плохие корни.
|
||||
const SAFE_WORDS: &[&str] = &[
|
||||
"колебат",
|
||||
"оскорблят",
|
||||
"употреблят",
|
||||
"расслаблят",
|
||||
"потреблят",
|
||||
"страхуй",
|
||||
"рубл",
|
||||
"сабл",
|
||||
"хлебат",
|
||||
"греб",
|
||||
"истреб",
|
||||
"щебет",
|
||||
"мебел",
|
||||
];
|
||||
|
||||
// Заменяет легальные слова-исключения на плейсхолдер перед проверкой
|
||||
fn apply_allowlist(text: &str) -> String {
|
||||
let mut safe_text = text.to_string();
|
||||
for &safe_word in SAFE_WORDS {
|
||||
safe_text = safe_text.replace(safe_word, "[SAFE]");
|
||||
}
|
||||
safe_text
|
||||
}
|
||||
|
||||
// Функция жесткой нормализации текста перед проверкой
|
||||
fn normalize_for_filter(input: &str) -> String {
|
||||
let mut normalized = String::new();
|
||||
let mut prev_char: Option<char> = None;
|
||||
|
||||
// 1. Приводим к нижнему регистру
|
||||
let lower_input = input.to_lowercase();
|
||||
|
||||
// 2. Идем по символам (с учетом юникода)
|
||||
for g in lower_input.graphemes(true) {
|
||||
let mut ch = g.chars().next().unwrap_or(' ');
|
||||
for ch in lower_input.chars() {
|
||||
// СОХРАНЯЕМ ПРОБЕЛЫ, чтобы не склеивать соседние слова ("нас укачало" != "насукачало")
|
||||
if ch.is_whitespace() {
|
||||
normalized.push(' ');
|
||||
prev_char = Some(' ');
|
||||
continue;
|
||||
}
|
||||
|
||||
// Пропускаем символы, которые не являются буквами или цифрами (запятые, точки, пробелы)
|
||||
// Вырезаем пунктуацию (чтобы ловить х.у.й), но пропускаем дальше только буквы/цифры
|
||||
if !ch.is_alphanumeric() {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 3. Замена "leetspeak" и похожих английских букв на русские (базовая защита)
|
||||
ch = match ch {
|
||||
// Замена "leetspeak" (англо-русская подмена и цифры)
|
||||
let mapped = match ch {
|
||||
'a' | '@' => 'а',
|
||||
'b' => 'в',
|
||||
'c' => 'с',
|
||||
@@ -48,43 +75,45 @@ fn normalize_for_filter(input: &str) -> String {
|
||||
'm' => 'м',
|
||||
't' => 'т',
|
||||
'k' => 'к',
|
||||
'h' => 'н',
|
||||
'4' => 'ч',
|
||||
_ => ch,
|
||||
};
|
||||
|
||||
// 4. Сжатие повторяющихся символов (хххуууууууй -> хуй)
|
||||
// Сжатие повторяющихся символов (хххуууууууй -> хуй)
|
||||
// Не сжимаем пробелы, только буквы
|
||||
if let Some(p) = prev_char {
|
||||
if p == ch {
|
||||
continue; // Пропускаем дубликат
|
||||
if p == mapped && mapped != ' ' {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
normalized.push(ch);
|
||||
prev_char = Some(ch);
|
||||
normalized.push(mapped);
|
||||
prev_char = Some(mapped);
|
||||
}
|
||||
|
||||
normalized
|
||||
}
|
||||
|
||||
// Функция проверки на спам (КАПС или слишком длинные слова)
|
||||
// Функция проверки на спам (КАПС или слишком длинные бессмысленные слова)
|
||||
fn is_spam(input: &str) -> bool {
|
||||
let alpha_chars: String = input.chars().filter(|c| c.is_alphabetic()).collect();
|
||||
if alpha_chars.is_empty() {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Считаем процент заглавных букв
|
||||
// Проверка на КАПС (> 70% заглавных при длине > 10)
|
||||
let uppercase_count = alpha_chars.chars().filter(|c| c.is_uppercase()).count();
|
||||
let uppercase_ratio = uppercase_count as f64 / alpha_chars.len() as f64;
|
||||
|
||||
// Если больше 70% текста - КАПС (при длине текста больше 10 символов)
|
||||
if input.len() > 10 && uppercase_ratio > 0.7 {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Проверка на слишком длинные "слова" без пробелов (защита от aaaaaaaaaaaaaa)
|
||||
// Проверка на зажатую клавиатуру (aaaaaaaaaaaa)
|
||||
for word in input.split_whitespace() {
|
||||
if word.len() > 35 {
|
||||
// Нормальных слов длиннее 35 символов почти нет
|
||||
// Игнорируем длинные ссылки (http/https)
|
||||
if word.len() > 35 && !word.starts_with("http") {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -102,7 +131,7 @@ async fn main() {
|
||||
}
|
||||
|
||||
async fn create_comment(Json(payload): Json<CommentPayload>) -> impl IntoResponse {
|
||||
// --- 1. Проверка на спам (КАПС, длинные слова) ---
|
||||
// --- 1. Проверка на спам ---
|
||||
if is_spam(&payload.message) || is_spam(&payload.nickname) {
|
||||
println!("🗑️ Blocked by SPAM filter: {}", payload.nickname);
|
||||
return (
|
||||
@@ -120,14 +149,18 @@ async fn create_comment(Json(payload): Json<CommentPayload>) -> impl IntoRespons
|
||||
);
|
||||
}
|
||||
|
||||
// --- 3. Русский хардкорный фильтр ---
|
||||
// --- 3. Русский хардкорный фильтр с защитой от ложных срабатываний ---
|
||||
let normalized_msg = normalize_for_filter(&payload.message);
|
||||
let normalized_nick = normalize_for_filter(&payload.nickname);
|
||||
|
||||
if BAD_WORDS_REGEX.is_match(&normalized_msg) || BAD_WORDS_REGEX.is_match(&normalized_nick) {
|
||||
// Сначала защищаем нормальные слова, затем проверяем регуляркой
|
||||
let safe_msg = apply_allowlist(&normalized_msg);
|
||||
let safe_nick = apply_allowlist(&normalized_nick);
|
||||
|
||||
if BAD_WORDS_REGEX.is_match(&safe_msg) || BAD_WORDS_REGEX.is_match(&safe_nick) {
|
||||
println!(
|
||||
"🛡️ Blocked by Deep Regex: [Original: {}] -> [Normalized: {}]",
|
||||
payload.message, normalized_msg
|
||||
payload.message, safe_msg
|
||||
);
|
||||
return (
|
||||
StatusCode::NOT_ACCEPTABLE,
|
||||
|
||||
Reference in New Issue
Block a user