v1.2.0
This commit is contained in:
@@ -68,7 +68,6 @@ class UserSpamStats:
|
||||
"""Удаляет старые запросы за пределами временного окна"""
|
||||
cutoff_time = current_time - time_window
|
||||
|
||||
# Удаляем старые запросы
|
||||
new_times = []
|
||||
new_contexts = []
|
||||
|
||||
@@ -121,7 +120,6 @@ class UserSpamStats:
|
||||
current_time = time()
|
||||
|
||||
# 1. КРИТИЧНО: Экстремально быстрая отправка (флуд-бот)
|
||||
# Если 5+ сообщений за 2 секунды => мгновенный мут
|
||||
very_recent = [ctx for ctx in recent_contexts if (current_time - ctx.timestamp) < 2.0]
|
||||
if len(very_recent) >= 5:
|
||||
return {
|
||||
@@ -130,7 +128,7 @@ class UserSpamStats:
|
||||
'severity': 1.0,
|
||||
'details': f"⚡ Экстремальный флуд: {len(very_recent)} сообщений за 2 секунды",
|
||||
'instant_block': True,
|
||||
'block_duration': 600.0 # 10 минут сразу
|
||||
'block_duration': 600.0
|
||||
}
|
||||
|
||||
# 2. КРИТИЧНО: 8+ сообщений за 5 секунд => агрессивный флуд
|
||||
@@ -142,13 +140,12 @@ class UserSpamStats:
|
||||
'severity': 0.95,
|
||||
'details': f"🔥 Агрессивный флуд: {len(recent_5s)} сообщений за 5 секунд",
|
||||
'instant_block': True,
|
||||
'block_duration': 300.0 # 5 минут
|
||||
'block_duration': 300.0
|
||||
}
|
||||
|
||||
# 3. Медиа-флуд (стикеры/фото/видео)
|
||||
# 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 {
|
||||
@@ -157,7 +154,7 @@ class UserSpamStats:
|
||||
'severity': 0.9,
|
||||
'details': f"📸 Медиа-флуд: {len(media_recent)} файлов за 5 секунд",
|
||||
'instant_block': True,
|
||||
'block_duration': 240.0 # 4 минуты
|
||||
'block_duration': 240.0
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -173,14 +170,14 @@ class UserSpamStats:
|
||||
text_counts = Counter(texts)
|
||||
most_common_text, count = text_counts.most_common(1)[0]
|
||||
|
||||
if count >= 5: # 5 одинаковых сообщений
|
||||
if count >= 5:
|
||||
return {
|
||||
'is_spam': True,
|
||||
'reason': 'identical_messages',
|
||||
'severity': 0.85,
|
||||
'details': f"📋 Повтор: '{most_common_text[:40]}...' ({count}x)",
|
||||
'instant_block': True,
|
||||
'block_duration': 180.0 # 3 минуты
|
||||
'block_duration': 180.0
|
||||
}
|
||||
|
||||
# 5. Проверка спама callback кнопок
|
||||
@@ -189,14 +186,14 @@ class UserSpamStats:
|
||||
callback_counts = Counter(callbacks)
|
||||
most_common_callback, count = callback_counts.most_common(1)[0]
|
||||
|
||||
if count >= 10: # 10 нажатий одной кнопки
|
||||
if count >= 10:
|
||||
return {
|
||||
'is_spam': True,
|
||||
'reason': 'callback_spam',
|
||||
'severity': 0.8,
|
||||
'details': f"🔘 Спам кнопки: {count} нажатий",
|
||||
'instant_block': True,
|
||||
'block_duration': 120.0 # 2 минуты
|
||||
'block_duration': 120.0
|
||||
}
|
||||
|
||||
return {'is_spam': False, 'reason': None, 'severity': 0.0}
|
||||
@@ -269,11 +266,11 @@ class AntiSpamMiddleware(BaseMiddleware):
|
||||
- Детекция скорости отправки сообщений
|
||||
- Адаптивная длительность блокировки
|
||||
- Различает типы активности
|
||||
- Бот никогда не банит сам себя
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
# Базовые лимиты (мягкие, для накопления варнингов)
|
||||
rate_limit_text: int = 8,
|
||||
rate_limit_forward: int = 20,
|
||||
rate_limit_callback: int = 12,
|
||||
@@ -281,12 +278,10 @@ class AntiSpamMiddleware(BaseMiddleware):
|
||||
|
||||
time_window: float = 10.0,
|
||||
|
||||
# Предупреждения (уже не так важны — флуд блокируется мгновенно)
|
||||
warning_limit: int = 3,
|
||||
base_block_duration: float = 120.0, # 2 минуты за накопленные варнинги
|
||||
base_block_duration: float = 120.0,
|
||||
max_block_duration: float = 3600.0,
|
||||
|
||||
# Опции
|
||||
whitelist_admins: bool = True,
|
||||
progressive_blocking: bool = True,
|
||||
enable_smart_detection: bool = True,
|
||||
@@ -318,7 +313,6 @@ class AntiSpamMiddleware(BaseMiddleware):
|
||||
context.is_reply = event.reply_to_message is not None
|
||||
context.is_command = bool(context.text and context.text.startswith('/'))
|
||||
|
||||
# Определяем тип медиа
|
||||
if event.photo:
|
||||
context.media_type = 'photo'
|
||||
elif event.video:
|
||||
@@ -350,7 +344,6 @@ class AntiSpamMiddleware(BaseMiddleware):
|
||||
else:
|
||||
base_limit = self.rate_limit_text
|
||||
|
||||
# Применяем репутацию
|
||||
if self.enable_reputation:
|
||||
base_limit = int(base_limit * user_stats.reputation)
|
||||
|
||||
@@ -392,6 +385,11 @@ class AntiSpamMiddleware(BaseMiddleware):
|
||||
if user_id is None:
|
||||
return await handler(event, data)
|
||||
|
||||
# ✅ ИСПРАВЛЕНИЕ: пропускаем самого бота (предотвращает самобан)
|
||||
bot = data.get("bot")
|
||||
if bot and user_id == bot.id:
|
||||
return await handler(event, data)
|
||||
|
||||
user_str = f"@{event.from_user.username}" if event.from_user.username else f"id{user_id}"
|
||||
|
||||
# Whitelist для администраторов
|
||||
@@ -414,7 +412,7 @@ class AntiSpamMiddleware(BaseMiddleware):
|
||||
user=user_str
|
||||
)
|
||||
|
||||
# НЕ отправляем сообщение каждый раз — только callback answer
|
||||
# Только для callback — отвечаем алертом, для сообщений молчим
|
||||
if isinstance(event, CallbackQuery):
|
||||
await event.answer(
|
||||
f"🚫 Блокировка: {self._format_duration(remaining)}",
|
||||
@@ -426,10 +424,10 @@ 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)
|
||||
|
||||
# ========== КРИТИЧНО: МГНОВЕННАЯ ДЕТЕКЦИЯ ФЛУДА ==========
|
||||
@@ -437,10 +435,9 @@ class AntiSpamMiddleware(BaseMiddleware):
|
||||
spam_analysis = user_stats.detect_spam_patterns(self.time_window)
|
||||
|
||||
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 # Максимум варнингов
|
||||
user_stats.warnings = self.warning_limit
|
||||
spam_stats.instant_blocks += 1
|
||||
|
||||
logger.error(
|
||||
@@ -461,7 +458,7 @@ class AntiSpamMiddleware(BaseMiddleware):
|
||||
if isinstance(event, Message):
|
||||
try:
|
||||
await event.answer(block_message, parse_mode="HTML")
|
||||
except:
|
||||
except Exception:
|
||||
pass
|
||||
elif isinstance(event, CallbackQuery):
|
||||
await event.answer(
|
||||
@@ -471,10 +468,9 @@ class AntiSpamMiddleware(BaseMiddleware):
|
||||
|
||||
return None
|
||||
|
||||
# ========== ОБЫЧНАЯ ПРОВЕРКА ЛИМИТОВ (для мягких превышений) ==========
|
||||
# ========== ОБЫЧНАЯ ПРОВЕРКА ЛИМИТОВ ==========
|
||||
effective_limit = self._get_effective_rate_limit(user_stats, context)
|
||||
|
||||
# Подсчитываем релевантные запросы
|
||||
relevant_requests = 0
|
||||
for req_context in user_stats.message_contexts:
|
||||
if context.is_forward and req_context.is_forward:
|
||||
@@ -493,7 +489,6 @@ class AntiSpamMiddleware(BaseMiddleware):
|
||||
user=user_str
|
||||
)
|
||||
|
||||
# Мягкое превышение лимита
|
||||
if relevant_requests >= effective_limit:
|
||||
user_stats.add_warning()
|
||||
spam_stats.total_warnings_issued += 1
|
||||
@@ -505,7 +500,6 @@ class AntiSpamMiddleware(BaseMiddleware):
|
||||
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)
|
||||
@@ -526,7 +520,7 @@ class AntiSpamMiddleware(BaseMiddleware):
|
||||
if isinstance(event, Message):
|
||||
try:
|
||||
await event.answer(block_message, parse_mode="HTML")
|
||||
except:
|
||||
except Exception:
|
||||
pass
|
||||
elif isinstance(event, CallbackQuery):
|
||||
await event.answer(
|
||||
@@ -536,7 +530,6 @@ class AntiSpamMiddleware(BaseMiddleware):
|
||||
|
||||
return None
|
||||
|
||||
# Предупреждение (только для сообщений, не для callback)
|
||||
if isinstance(event, Message):
|
||||
warning_message = (
|
||||
f"⚠️ <b>Предупреждение {user_stats.warnings}/{self.warning_limit}</b>\n\n"
|
||||
@@ -544,7 +537,7 @@ class AntiSpamMiddleware(BaseMiddleware):
|
||||
)
|
||||
try:
|
||||
await event.answer(warning_message, parse_mode="HTML")
|
||||
except:
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return None
|
||||
|
||||
Reference in New Issue
Block a user