Астат ты не вознесешься
This commit is contained in:
@@ -124,6 +124,9 @@ def setup_middlewares(
|
||||
if enable_subscription_check:
|
||||
enabled_features.append("Subscription")
|
||||
|
||||
dp.channel_post.middleware(LoggingMiddleware())
|
||||
dp.edited_channel_post.middleware(LoggingMiddleware())
|
||||
|
||||
logger.info(
|
||||
text=(
|
||||
f"Middleware зарегистрированы: "
|
||||
@@ -135,3 +138,5 @@ def setup_middlewares(
|
||||
)
|
||||
|
||||
return instances
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
"""
|
||||
Умный middleware для защиты от спама с адаптивными лимитами
|
||||
ВЕРСИЯ 2.0: мгновенная блокировка при явном флуде
|
||||
"""
|
||||
from time import time
|
||||
from typing import Callable, Awaitable, Any, Dict, Optional
|
||||
@@ -25,6 +26,7 @@ class MessageContext:
|
||||
is_command: bool = False
|
||||
media_type: Optional[str] = None
|
||||
callback_data: Optional[str] = None
|
||||
timestamp: float = field(default_factory=time)
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -53,7 +55,7 @@ class UserSpamStats:
|
||||
|
||||
# Разблокировка
|
||||
self.blocked_until = None
|
||||
self.warnings = max(0, self.warnings - 1) # Снижаем предупреждения, но не сбрасываем полностью
|
||||
self.warnings = max(0, self.warnings - 1) # Снижаем предупреждения
|
||||
return False
|
||||
|
||||
def get_remaining_block_time(self, current_time: float) -> float:
|
||||
@@ -80,6 +82,7 @@ class UserSpamStats:
|
||||
|
||||
def add_request(self, current_time: float, context: MessageContext) -> None:
|
||||
"""Добавляет новый запрос с контекстом"""
|
||||
context.timestamp = current_time
|
||||
self.request_times.append(current_time)
|
||||
self.message_contexts.append(context)
|
||||
self.total_requests += 1
|
||||
@@ -103,9 +106,10 @@ class UserSpamStats:
|
||||
self.total_blocks += 1
|
||||
self.reputation = max(0.5, self.reputation - 0.3)
|
||||
|
||||
def detect_spam_patterns(self) -> Dict[str, Any]:
|
||||
def detect_spam_patterns(self, time_window: float = 10.0) -> Dict[str, Any]:
|
||||
"""
|
||||
Умная детекция спама на основе паттернов.
|
||||
УЛУЧШЕНО: учитывает скорость отправки сообщений.
|
||||
|
||||
Returns:
|
||||
Dict с результатами анализа
|
||||
@@ -113,46 +117,88 @@ class UserSpamStats:
|
||||
if len(self.message_contexts) < 3:
|
||||
return {'is_spam': False, 'reason': None, 'severity': 0.0}
|
||||
|
||||
recent_contexts = self.message_contexts[-10:] # Последние 10 сообщений
|
||||
recent_contexts = self.message_contexts[-15:] # Последние 15 сообщений
|
||||
current_time = time()
|
||||
|
||||
# 1. Проверка идентичных текстовых сообщений
|
||||
# 1. КРИТИЧНО: Экстремально быстрая отправка (флуд-бот)
|
||||
# Если 5+ сообщений за 2 секунды => мгновенный мут
|
||||
very_recent = [ctx for ctx in recent_contexts if (current_time - ctx.timestamp) < 2.0]
|
||||
if len(very_recent) >= 5:
|
||||
return {
|
||||
'is_spam': True,
|
||||
'reason': 'extreme_flood',
|
||||
'severity': 1.0,
|
||||
'details': f"⚡ Экстремальный флуд: {len(very_recent)} сообщений за 2 секунды",
|
||||
'instant_block': True,
|
||||
'block_duration': 600.0 # 10 минут сразу
|
||||
}
|
||||
|
||||
# 2. КРИТИЧНО: 8+ сообщений за 5 секунд => агрессивный флуд
|
||||
recent_5s = [ctx for ctx in recent_contexts if (current_time - ctx.timestamp) < 5.0]
|
||||
if len(recent_5s) >= 8:
|
||||
return {
|
||||
'is_spam': True,
|
||||
'reason': 'aggressive_flood',
|
||||
'severity': 0.95,
|
||||
'details': f"🔥 Агрессивный флуд: {len(recent_5s)} сообщений за 5 секунд",
|
||||
'instant_block': True,
|
||||
'block_duration': 300.0 # 5 минут
|
||||
}
|
||||
|
||||
# 3. Медиа-флуд (стикеры/фото/видео)
|
||||
media_contexts = [ctx for ctx in recent_contexts if ctx.media_type]
|
||||
if len(media_contexts) >= 7:
|
||||
# Проверяем скорость отправки медиа
|
||||
media_recent = [ctx for ctx in media_contexts if (current_time - ctx.timestamp) < 5.0]
|
||||
if len(media_recent) >= 6:
|
||||
return {
|
||||
'is_spam': True,
|
||||
'reason': 'media_flood_fast',
|
||||
'severity': 0.9,
|
||||
'details': f"📸 Медиа-флуд: {len(media_recent)} файлов за 5 секунд",
|
||||
'instant_block': True,
|
||||
'block_duration': 240.0 # 4 минуты
|
||||
}
|
||||
|
||||
return {
|
||||
'is_spam': True,
|
||||
'reason': 'media_flood',
|
||||
'severity': 0.7,
|
||||
'details': f"📸 Медиа-флуд: {len(media_contexts)} файлов подряд"
|
||||
}
|
||||
|
||||
# 4. Проверка идентичных текстовых сообщений
|
||||
texts = [ctx.text for ctx in recent_contexts if ctx.text and not ctx.is_command]
|
||||
if texts:
|
||||
text_counts = Counter(texts)
|
||||
most_common_text, count = text_counts.most_common(1)[0]
|
||||
|
||||
if count >= 5: # 5 одинаковых сообщений подряд
|
||||
if count >= 5: # 5 одинаковых сообщений
|
||||
return {
|
||||
'is_spam': True,
|
||||
'reason': 'identical_messages',
|
||||
'severity': 1.0,
|
||||
'details': f"Повторяющееся сообщение: '{most_common_text[:50]}...'"
|
||||
'severity': 0.85,
|
||||
'details': f"📋 Повтор: '{most_common_text[:40]}...' ({count}x)",
|
||||
'instant_block': True,
|
||||
'block_duration': 180.0 # 3 минуты
|
||||
}
|
||||
|
||||
# 2. Проверка спама callback кнопок
|
||||
# 5. Проверка спама callback кнопок
|
||||
callbacks = [ctx.callback_data for ctx in recent_contexts if ctx.callback_data]
|
||||
if callbacks:
|
||||
callback_counts = Counter(callbacks)
|
||||
most_common_callback, count = callback_counts.most_common(1)[0]
|
||||
|
||||
if count >= 8: # 8 нажатий одной кнопки
|
||||
if count >= 10: # 10 нажатий одной кнопки
|
||||
return {
|
||||
'is_spam': True,
|
||||
'reason': 'callback_spam',
|
||||
'severity': 0.8,
|
||||
'details': f"Спам кнопки: {most_common_callback}"
|
||||
'details': f"🔘 Спам кнопки: {count} нажатий",
|
||||
'instant_block': True,
|
||||
'block_duration': 120.0 # 2 минуты
|
||||
}
|
||||
|
||||
# 3. Проверка флуда медиа
|
||||
media_types = [ctx.media_type for ctx in recent_contexts if ctx.media_type]
|
||||
if len(media_types) >= 7: # 7+ медиафайлов подряд
|
||||
return {
|
||||
'is_spam': True,
|
||||
'reason': 'media_flood',
|
||||
'severity': 0.6,
|
||||
'details': f"Флуд медиа: {len(media_types)} файлов"
|
||||
}
|
||||
|
||||
return {'is_spam': False, 'reason': None, 'severity': 0.0}
|
||||
|
||||
|
||||
@@ -163,6 +209,7 @@ class SpamStatistics:
|
||||
self.users: Dict[int, UserSpamStats] = {}
|
||||
self.total_blocked_requests: int = 0
|
||||
self.total_warnings_issued: int = 0
|
||||
self.instant_blocks: int = 0
|
||||
|
||||
def get_user(self, user_id: int) -> UserSpamStats:
|
||||
"""Получает или создает статистику пользователя"""
|
||||
@@ -185,6 +232,7 @@ class SpamStatistics:
|
||||
'total_users': len(self.users),
|
||||
'total_blocked_requests': self.total_blocked_requests,
|
||||
'total_warnings': self.total_warnings_issued,
|
||||
'instant_blocks': self.instant_blocks,
|
||||
'active_blocks': sum(
|
||||
1 for stats in self.users.values()
|
||||
if stats.blocked_until and stats.blocked_until > time()
|
||||
@@ -214,30 +262,29 @@ spam_stats = SpamStatistics()
|
||||
|
||||
class AntiSpamMiddleware(BaseMiddleware):
|
||||
"""
|
||||
Умный антиспам с адаптивными лимитами.
|
||||
Умный антиспам с мгновенной блокировкой при флуде.
|
||||
|
||||
Особенности:
|
||||
- Различает типы активности (текст, форварды, команды, callback)
|
||||
- Адаптивные лимиты в зависимости от типа сообщения
|
||||
- Система репутации пользователей
|
||||
- Умная детекция спам-паттернов
|
||||
- Мягкое отношение к пересылкам и ответам
|
||||
Особенности v2:
|
||||
- Мгновенная блокировка при экстремальном флуде (5+ сообщений за 2с)
|
||||
- Детекция скорости отправки сообщений
|
||||
- Адаптивная длительность блокировки
|
||||
- Различает типы активности
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
# Базовые лимиты
|
||||
rate_limit_text: int = 8, # Текстовых сообщений за окно
|
||||
rate_limit_forward: int = 20, # Пересылок за окно
|
||||
rate_limit_callback: int = 10, # Нажатий кнопок за окно
|
||||
rate_limit_media: int = 10, # Медиа за окно
|
||||
# Базовые лимиты (мягкие, для накопления варнингов)
|
||||
rate_limit_text: int = 8,
|
||||
rate_limit_forward: int = 20,
|
||||
rate_limit_callback: int = 12,
|
||||
rate_limit_media: int = 10,
|
||||
|
||||
time_window: float = 10.0, # Временное окно (секунды)
|
||||
time_window: float = 10.0,
|
||||
|
||||
# Предупреждения и блокировки
|
||||
# Предупреждения (уже не так важны — флуд блокируется мгновенно)
|
||||
warning_limit: int = 3,
|
||||
block_duration: float = 120.0, # 2 минуты базовая блокировка
|
||||
max_block_duration: float = 3600.0, # 1 час максимум
|
||||
base_block_duration: float = 120.0, # 2 минуты за накопленные варнинги
|
||||
max_block_duration: float = 3600.0,
|
||||
|
||||
# Опции
|
||||
whitelist_admins: bool = True,
|
||||
@@ -253,7 +300,7 @@ class AntiSpamMiddleware(BaseMiddleware):
|
||||
self.rate_limit_media = rate_limit_media
|
||||
self.time_window = time_window
|
||||
self.warning_limit = warning_limit
|
||||
self.block_duration = block_duration
|
||||
self.base_block_duration = base_block_duration
|
||||
self.max_block_duration = max_block_duration
|
||||
self.whitelist_admins = whitelist_admins
|
||||
self.progressive_blocking = progressive_blocking
|
||||
@@ -292,7 +339,6 @@ class AntiSpamMiddleware(BaseMiddleware):
|
||||
|
||||
def _get_effective_rate_limit(self, user_stats: UserSpamStats, context: MessageContext) -> int:
|
||||
"""Вычисляет эффективный лимит с учётом типа и репутации"""
|
||||
# Базовый лимит по типу
|
||||
if context.is_command:
|
||||
return 999 # Команды не ограничиваем
|
||||
elif context.callback_data:
|
||||
@@ -308,15 +354,15 @@ class AntiSpamMiddleware(BaseMiddleware):
|
||||
if self.enable_reputation:
|
||||
base_limit = int(base_limit * user_stats.reputation)
|
||||
|
||||
return max(3, base_limit) # Минимум 3 сообщения
|
||||
return max(3, base_limit)
|
||||
|
||||
def _calculate_block_duration(self, warnings: int) -> float:
|
||||
"""Вычисляет длительность блокировки"""
|
||||
"""Вычисляет длительность блокировки за накопленные варнинги"""
|
||||
if not self.progressive_blocking:
|
||||
return self.block_duration
|
||||
return self.base_block_duration
|
||||
|
||||
multiplier = 2 ** (warnings // self.warning_limit)
|
||||
duration = self.block_duration * multiplier
|
||||
duration = self.base_block_duration * multiplier
|
||||
|
||||
return min(duration, self.max_block_duration)
|
||||
|
||||
@@ -357,7 +403,7 @@ class AntiSpamMiddleware(BaseMiddleware):
|
||||
current_time = time()
|
||||
user_stats = spam_stats.get_user(user_id)
|
||||
|
||||
# Проверка блокировки
|
||||
# Проверка существующей блокировки
|
||||
if user_stats.is_blocked(current_time):
|
||||
remaining = user_stats.get_remaining_block_time(current_time)
|
||||
spam_stats.total_blocked_requests += 1
|
||||
@@ -368,17 +414,10 @@ class AntiSpamMiddleware(BaseMiddleware):
|
||||
user=user_str
|
||||
)
|
||||
|
||||
block_message = (
|
||||
f"🚫 <b>Вы заблокированы за спам!</b>\n\n"
|
||||
f"⏳ Оставшееся время: <b>{self._format_duration(remaining)}</b>\n"
|
||||
f"⚠️ Предупреждений: <b>{user_stats.warnings}</b>"
|
||||
)
|
||||
|
||||
if isinstance(event, Message):
|
||||
await event.answer(block_message, parse_mode="HTML")
|
||||
elif isinstance(event, CallbackQuery):
|
||||
# НЕ отправляем сообщение каждый раз — только callback answer
|
||||
if isinstance(event, CallbackQuery):
|
||||
await event.answer(
|
||||
f"🚫 Заблокирован на {self._format_duration(remaining)}",
|
||||
f"🚫 Блокировка: {self._format_duration(remaining)}",
|
||||
show_alert=True
|
||||
)
|
||||
|
||||
@@ -387,51 +426,52 @@ class AntiSpamMiddleware(BaseMiddleware):
|
||||
# Извлекаем контекст сообщения
|
||||
context = self._extract_context(event)
|
||||
|
||||
# Добавляем запрос СНАЧАЛА (важно для детекции скорости)
|
||||
user_stats.add_request(current_time, context)
|
||||
|
||||
# Очищаем старые запросы
|
||||
user_stats.clean_old_requests(current_time, self.time_window)
|
||||
|
||||
# Умная детекция спам-паттернов
|
||||
# ========== КРИТИЧНО: МГНОВЕННАЯ ДЕТЕКЦИЯ ФЛУДА ==========
|
||||
if self.enable_smart_detection:
|
||||
spam_analysis = user_stats.detect_spam_patterns()
|
||||
spam_analysis = user_stats.detect_spam_patterns(self.time_window)
|
||||
|
||||
if spam_analysis['is_spam']:
|
||||
user_stats.add_warning()
|
||||
spam_stats.total_warnings_issued += 1
|
||||
if spam_analysis.get('is_spam') and spam_analysis.get('instant_block'):
|
||||
# МГНОВЕННАЯ БЛОКИРОВКА
|
||||
block_duration = spam_analysis.get('block_duration', 300.0)
|
||||
user_stats.block(current_time, block_duration)
|
||||
user_stats.warnings = self.warning_limit # Максимум варнингов
|
||||
spam_stats.instant_blocks += 1
|
||||
|
||||
logger.warning(
|
||||
f"Обнаружен спам-паттерн: {spam_analysis['reason']} - {spam_analysis['details']}",
|
||||
logger.error(
|
||||
f"🚨 МГНОВЕННАЯ БЛОКИРОВКА! Причина: {spam_analysis['reason']}\n"
|
||||
f" └─ {spam_analysis['details']}\n"
|
||||
f" └─ Длительность: {self._format_duration(block_duration)}",
|
||||
log_type='ANTI_SPAM',
|
||||
user=user_str
|
||||
)
|
||||
|
||||
# Немедленная блокировка при явном спаме
|
||||
if spam_analysis['severity'] >= 0.9:
|
||||
block_duration = self._calculate_block_duration(user_stats.warnings)
|
||||
user_stats.block(current_time, block_duration)
|
||||
block_message = (
|
||||
f"🚫 <b>БЛОКИРОВКА ЗА ФЛУД!</b>\n\n"
|
||||
f"⚠️ {spam_analysis['details']}\n\n"
|
||||
f"⏳ Длительность: <b>{self._format_duration(block_duration)}</b>\n"
|
||||
f"💡 Не отправляйте сообщения слишком быстро!"
|
||||
)
|
||||
|
||||
logger.error(
|
||||
f"Пользователь заблокирован за спам: {spam_analysis['reason']}",
|
||||
log_type='ANTI_SPAM',
|
||||
user=user_str
|
||||
)
|
||||
|
||||
block_message = (
|
||||
f"🚫 <b>Вы заблокированы за спам!</b>\n\n"
|
||||
f"⏳ Длительность: <b>{self._format_duration(block_duration)}</b>\n"
|
||||
f"⚠️ Причина: {spam_analysis['details']}"
|
||||
)
|
||||
|
||||
if isinstance(event, Message):
|
||||
if isinstance(event, Message):
|
||||
try:
|
||||
await event.answer(block_message, parse_mode="HTML")
|
||||
elif isinstance(event, CallbackQuery):
|
||||
await event.answer(
|
||||
f"🚫 Блокировка: {spam_analysis['reason']}",
|
||||
show_alert=True
|
||||
)
|
||||
except:
|
||||
pass
|
||||
elif isinstance(event, CallbackQuery):
|
||||
await event.answer(
|
||||
f"🚫 Блокировка: {self._format_duration(block_duration)}",
|
||||
show_alert=True
|
||||
)
|
||||
|
||||
return None
|
||||
return None
|
||||
|
||||
# Получаем эффективный лимит
|
||||
# ========== ОБЫЧНАЯ ПРОВЕРКА ЛИМИТОВ (для мягких превышений) ==========
|
||||
effective_limit = self._get_effective_rate_limit(user_stats, context)
|
||||
|
||||
# Подсчитываем релевантные запросы
|
||||
@@ -448,84 +488,71 @@ class AntiSpamMiddleware(BaseMiddleware):
|
||||
|
||||
if self.log_all:
|
||||
logger.debug(
|
||||
f"Rate limit: {relevant_requests}/{effective_limit} (тип: {context.media_type or 'text'}, репутация: {user_stats.reputation:.2f})",
|
||||
f"Rate: {relevant_requests}/{effective_limit} | rep: {user_stats.reputation:.2f}",
|
||||
log_type='ANTI_SPAM',
|
||||
user=user_str
|
||||
)
|
||||
|
||||
# Проверка лимита
|
||||
# Мягкое превышение лимита
|
||||
if relevant_requests >= effective_limit:
|
||||
user_stats.add_warning()
|
||||
spam_stats.total_warnings_issued += 1
|
||||
|
||||
logger.warning(
|
||||
f"Превышен rate limit ({relevant_requests}/{effective_limit}). "
|
||||
f"Превышен лимит ({relevant_requests}/{effective_limit}). "
|
||||
f"Предупреждение {user_stats.warnings}/{self.warning_limit}",
|
||||
log_type='ANTI_SPAM',
|
||||
user=user_str
|
||||
)
|
||||
|
||||
# Блокировка при достижении лимита предупреждений
|
||||
# Блокировка при достижении лимита варнингов
|
||||
if user_stats.warnings >= self.warning_limit:
|
||||
block_duration = self._calculate_block_duration(user_stats.warnings)
|
||||
user_stats.block(current_time, block_duration)
|
||||
|
||||
logger.error(
|
||||
f"Пользователь заблокирован на {self._format_duration(block_duration)}. "
|
||||
f"Пользователь заблокирован на {self._format_duration(block_duration)} (варнинги). "
|
||||
f"Всего блокировок: {user_stats.total_blocks}",
|
||||
log_type='ANTI_SPAM',
|
||||
user=user_str
|
||||
)
|
||||
|
||||
block_message = (
|
||||
f"🚫 <b>Вы заблокированы за спам!</b>\n\n"
|
||||
f"🚫 <b>Вы заблокированы!</b>\n\n"
|
||||
f"⏳ Длительность: <b>{self._format_duration(block_duration)}</b>\n"
|
||||
f"⚠️ Причина: Превышение лимита запросов\n"
|
||||
f"📊 Это блокировка #{user_stats.total_blocks}"
|
||||
f"⚠️ Причина: Превышение лимита запросов"
|
||||
)
|
||||
|
||||
if isinstance(event, Message):
|
||||
await event.answer(block_message, parse_mode="HTML")
|
||||
try:
|
||||
await event.answer(block_message, parse_mode="HTML")
|
||||
except:
|
||||
pass
|
||||
elif isinstance(event, CallbackQuery):
|
||||
await event.answer(
|
||||
f"🚫 Блокировка на {self._format_duration(block_duration)}",
|
||||
f"🚫 Блокировка: {self._format_duration(block_duration)}",
|
||||
show_alert=True
|
||||
)
|
||||
|
||||
return None
|
||||
|
||||
# Предупреждение
|
||||
warning_message = (
|
||||
f"⚠️ <b>Предупреждение #{user_stats.warnings}</b>\n\n"
|
||||
f"Вы отправляете запросы слишком часто!\n"
|
||||
f"Лимит: {effective_limit} запросов за {self._format_duration(self.time_window)}\n\n"
|
||||
f"При {self.warning_limit} предупреждениях последует блокировка."
|
||||
)
|
||||
|
||||
# Предупреждение (только для сообщений, не для callback)
|
||||
if isinstance(event, Message):
|
||||
await event.answer(warning_message, parse_mode="HTML")
|
||||
elif isinstance(event, CallbackQuery):
|
||||
await event.answer(
|
||||
f"⚠️ Предупреждение {user_stats.warnings}/{self.warning_limit}",
|
||||
show_alert=True
|
||||
warning_message = (
|
||||
f"⚠️ <b>Предупреждение {user_stats.warnings}/{self.warning_limit}</b>\n\n"
|
||||
f"Вы отправляете сообщения слишком часто!"
|
||||
)
|
||||
try:
|
||||
await event.answer(warning_message, parse_mode="HTML")
|
||||
except:
|
||||
pass
|
||||
|
||||
return None
|
||||
|
||||
# Добавляем текущий запрос
|
||||
user_stats.add_request(current_time, context)
|
||||
|
||||
# Улучшаем репутацию за нормальное поведение
|
||||
if self.enable_reputation and user_stats.total_requests % 10 == 0:
|
||||
user_stats.improve_reputation()
|
||||
|
||||
if self.log_all:
|
||||
logger.debug(
|
||||
f"Запрос разрешен. Всего: {user_stats.total_requests}, репутация: {user_stats.reputation:.2f}",
|
||||
log_type='ANTI_SPAM',
|
||||
user=user_str
|
||||
)
|
||||
|
||||
return await handler(event, data)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user