Астат ты не вознесешься
This commit is contained in:
@@ -9,6 +9,8 @@ Pipeline проверки:
|
||||
5. Проверяем постоянные банворды (substring, lemma, part)
|
||||
6. Проверяем временные банворды
|
||||
7. Если найдено - удаляем, логируем, уведомляем админов
|
||||
|
||||
НОВОЕ: Все проверки работают с нормализацией повторяющихся букв (3+ → 1).
|
||||
"""
|
||||
from typing import Callable, Dict, Any, Awaitable, Optional
|
||||
import re
|
||||
@@ -103,9 +105,36 @@ class BanWordsMiddleware(BaseMiddleware):
|
||||
"hello@world.com" -> "helloworldcom"
|
||||
"test_123-456" -> "test123456"
|
||||
"""
|
||||
# Оставляем только буквы и цифры
|
||||
return re.sub(r'[^a-zA-Zа-яА-ЯёЁ0-9]', '', text.lower())
|
||||
|
||||
@staticmethod
|
||||
def _normalize_repeated_chars(text: str, max_repeats: int = 1) -> str:
|
||||
"""
|
||||
Убирает повторяющиеся буквы (обход "лееейн" -> "лейн", "телееелооог" -> "телелог").
|
||||
|
||||
Args:
|
||||
text: Исходное слово
|
||||
max_repeats: Максимальное количество повторов одной буквы (1 = убрать все повторы)
|
||||
|
||||
Returns:
|
||||
str: Нормализованное слово
|
||||
|
||||
Examples:
|
||||
("лееейн", 1) -> "лейн"
|
||||
("телееелооог", 1) -> "телелог"
|
||||
("хеееелооооу", 1) -> "хелоу"
|
||||
("аааааа", 1) -> "а"
|
||||
("привеееет", 2) -> "приввеет" (если max_repeats=2)
|
||||
"""
|
||||
if max_repeats == 1:
|
||||
# Заменяем 2+ одинаковых букв подряд на 1 такую же букву
|
||||
return re.sub(r'([а-яёa-z])\1+', r'\1', text, flags=re.IGNORECASE)
|
||||
else:
|
||||
# Заменяем (max_repeats+1)+ одинаковых букв на max_repeats таких букв
|
||||
pattern = f'([а-яёa-z])\\1{{{max_repeats},}}'
|
||||
replacement = '\\1' * max_repeats
|
||||
return re.sub(pattern, replacement, text, flags=re.IGNORECASE)
|
||||
|
||||
async def _check_message(self, text: str) -> Optional[Dict[str, str]]:
|
||||
"""
|
||||
Проверяет сообщение на наличие банвордов.
|
||||
@@ -120,10 +149,20 @@ class BanWordsMiddleware(BaseMiddleware):
|
||||
text_lower = text.lower()
|
||||
text_processed = process_text(text_lower)
|
||||
|
||||
# Дополнительно нормализуем повторяющиеся буквы для всех проверок
|
||||
text_normalized = self._normalize_repeated_chars(text_processed, max_repeats=1)
|
||||
|
||||
logger.debug(
|
||||
f"Проверка текста: исходный='{text[:50]}', обработанный='{text_processed[:50]}', "
|
||||
f"нормализованный='{text_normalized[:50]}'",
|
||||
log_type="BANWORDS"
|
||||
)
|
||||
|
||||
# === 1. WHITELIST (исключения) ===
|
||||
if self.manager.is_whitelisted(text_processed):
|
||||
# Проверяем оба варианта: с повторами и без
|
||||
if self.manager.is_whitelisted(text_processed) or self.manager.is_whitelisted(text_normalized):
|
||||
logger.debug(
|
||||
f"Сообщение содержит whitelist слово: '{text_processed[:50]}'",
|
||||
f"Сообщение содержит whitelist слово",
|
||||
log_type="BANWORDS"
|
||||
)
|
||||
return None
|
||||
@@ -137,12 +176,13 @@ class BanWordsMiddleware(BaseMiddleware):
|
||||
|
||||
# === 3. CONFLICT MODE (конфликтные слова) ===
|
||||
if await self.manager.is_conflict_active():
|
||||
# Проверяем конфликтные подстроки
|
||||
# Проверяем конфликтные подстроки (с нормализацией)
|
||||
conflict_substring = self.manager.get_banwords_cached(
|
||||
BanWordType.CONFLICT_SUBSTRING
|
||||
)
|
||||
for word in conflict_substring:
|
||||
if word in text_processed:
|
||||
word_normalized = self._normalize_repeated_chars(word, max_repeats=1)
|
||||
if word_normalized in text_normalized:
|
||||
return {"word": word, "type": "conflict_substring"}
|
||||
|
||||
# Проверяем конфликтные леммы
|
||||
@@ -151,35 +191,40 @@ class BanWordsMiddleware(BaseMiddleware):
|
||||
)
|
||||
words_in_text = extract_words(text_processed)
|
||||
for word_text in words_in_text:
|
||||
lemma = get_lemma(word_text)
|
||||
word_normalized = self._normalize_repeated_chars(word_text, max_repeats=1)
|
||||
lemma = get_lemma(word_normalized)
|
||||
|
||||
if lemma in conflict_lemma:
|
||||
return {"word": lemma, "type": "conflict_lemma"}
|
||||
|
||||
# === 4. SUBSTRING (подстроки) ===
|
||||
# === 4. SUBSTRING (подстроки) с нормализацией ===
|
||||
substring_words = self.manager.get_banwords_cached(BanWordType.SUBSTRING)
|
||||
for word in substring_words:
|
||||
if word in text_processed:
|
||||
# Нормализуем и банворд, и текст
|
||||
word_normalized = self._normalize_repeated_chars(word, max_repeats=1)
|
||||
|
||||
if word_normalized in text_normalized:
|
||||
logger.info(
|
||||
f"Найдена подстрока: '{word}' (норм: '{word_normalized}') в '{text_normalized[:100]}'",
|
||||
log_type="BANWORDS"
|
||||
)
|
||||
return {"word": word, "type": "substring"}
|
||||
|
||||
# === 5. PART (части слов без пробелов и спецсимволов) ===
|
||||
part_words = self.manager.get_banwords_cached(BanWordType.PART)
|
||||
if part_words:
|
||||
# Специальная нормализация для PART: удаляем ВСЁ кроме букв и цифр
|
||||
text_normalized = self._normalize_for_part_check(text)
|
||||
|
||||
logger.debug(
|
||||
f"Проверка PART: исходный='{text[:50]}', нормализованный='{text_normalized[:50]}'",
|
||||
log_type="BANWORDS"
|
||||
)
|
||||
text_part_normalized = self._normalize_for_part_check(text)
|
||||
text_part_normalized = self._normalize_repeated_chars(text_part_normalized, max_repeats=1)
|
||||
|
||||
for part in part_words:
|
||||
# Нормализуем само запрещенное слово тоже
|
||||
part_normalized = self._normalize_for_part_check(part)
|
||||
part_normalized = self._normalize_repeated_chars(part_normalized, max_repeats=1)
|
||||
|
||||
if part_normalized in text_normalized:
|
||||
if part_normalized in text_part_normalized:
|
||||
logger.info(
|
||||
f"Найдена запрещенная часть: '{part}' (нормализовано: '{part_normalized}') "
|
||||
f"в тексте '{text_normalized[:100]}'",
|
||||
f"Найдена запрещенная часть: '{part}' (норм: '{part_normalized}') "
|
||||
f"в '{text_part_normalized[:100]}'",
|
||||
log_type="BANWORDS"
|
||||
)
|
||||
return {"word": part, "type": "part"}
|
||||
@@ -189,8 +234,15 @@ class BanWordsMiddleware(BaseMiddleware):
|
||||
if lemma_words:
|
||||
words_in_text = extract_words(text_processed)
|
||||
for word_text in words_in_text:
|
||||
lemma = get_lemma(word_text)
|
||||
# Убираем повторяющиеся буквы ПЕРЕД лемматизацией
|
||||
word_normalized = self._normalize_repeated_chars(word_text, max_repeats=1)
|
||||
lemma = get_lemma(word_normalized)
|
||||
|
||||
if lemma in lemma_words:
|
||||
logger.info(
|
||||
f"Найдена лемма: '{lemma}' из слова '{word_text}' (норм: '{word_normalized}')",
|
||||
log_type="BANWORDS"
|
||||
)
|
||||
return {"word": lemma, "type": "lemma"}
|
||||
|
||||
# Банворды не найдены
|
||||
@@ -222,13 +274,13 @@ class BanWordsMiddleware(BaseMiddleware):
|
||||
f"Удалено сообщение от @{user.username or user.id} "
|
||||
f"(слово: '{matched_word}', тип: {match_type})",
|
||||
log_type="BANWORDS",
|
||||
message=message
|
||||
user=f"@{user.username}" if user.username else f"id{user.id}"
|
||||
)
|
||||
except TelegramBadRequest as e:
|
||||
logger.error(
|
||||
f"Не удалось удалить сообщение: {e}",
|
||||
log_type="ERROR",
|
||||
message=message
|
||||
log_type="BANWORDS",
|
||||
user=f"@{user.username}" if user.username else f"id{user.id}"
|
||||
)
|
||||
return
|
||||
|
||||
@@ -311,7 +363,7 @@ class BanWordsMiddleware(BaseMiddleware):
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
f"Ошибка отправки уведомления админам: {e}",
|
||||
log_type="ERROR"
|
||||
log_type="BANWORDS"
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
|
||||
Reference in New Issue
Block a user