"""
Мониторинг чата
Считывает все сообщения для статистики и детекта шпионов
"""
import logging
from datetime import datetime
from aiogram import Router, F
from aiogram.types import Message
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select
from database.db import AsyncSessionLocal
from database.models import User, Message as MessageModel, SpamWarning
from services.spy_detector import SpyDetector
from services.antispam import AntiSpamService
import config
logger = logging.getLogger(__name__)
router = Router()
# Баллы за активность
RATING_PER_MESSAGE = 1 # +1 балл за каждое сообщение
@router.message()
async def monitor_all_messages(message: Message):
"""
Перехват всех сообщений в чате
Для статистики и анализа
"""
# Пропускаем сообщения от ботов
if message.from_user.is_bot:
return
# Пропускаем сервисные сообщения
if message.system:
return
user_id = message.from_user.id
chat_id = message.chat.id
text = message.text or ''
async with AsyncSessionLocal() as session:
# Получаем или создаём пользователя
stmt = select(User).where(User.user_id == user_id)
result = await session.execute(stmt)
user = result.scalar_one_or_none()
if not user:
# Создаём нового пользователя
user = User(
user_id=user_id,
username=message.from_user.username,
first_name=message.from_user.first_name,
last_name=message.from_user.last_name,
)
session.add(user)
else:
# Логируем изменения профиля
from database.models import ProfileHistory
changes = []
if user.username != message.from_user.username:
changes.append(('username', user.username, message.from_user.username))
user.username = message.from_user.username
if user.first_name != message.from_user.first_name:
changes.append(('first_name', user.first_name, message.from_user.first_name))
user.first_name = message.from_user.first_name
if user.last_name != message.from_user.last_name:
changes.append(('last_name', user.last_name, message.from_user.last_name))
user.last_name = message.from_user.last_name
# Сохраняем историю изменений
for field_name, old_value, new_value in changes:
history_record = ProfileHistory(
user_id=user_id,
field_name=field_name,
old_value=old_value,
new_value=new_value,
)
session.add(history_record)
if old_value:
logger.info(f'Profile change: user_id={user_id}, {field_name}: {old_value} → {new_value}')
# Обновляем данные
user.message_count += 1
user.last_seen = datetime.utcnow()
# Начисляем рейтинг за сообщение
user.rating += RATING_PER_MESSAGE
user.update_level() # Обновляем уровень
# ==========================================
# ПРОВЕРКА ДОСТИЖЕНИЙ
# ==========================================
if not config.is_admin(user_id):
from services.achievements import AchievementsService
achievements_service = AchievementsService(session)
# Проверяем и выдаём достижения
awarded = await achievements_service.check_and_award(
user_id,
message_count=user.message_count,
rating=user.rating,
message_hour=datetime.utcnow().hour
)
# Уведомляем о полученных достижениях
for ach_type in awarded:
ach_info = achievements_service.get_achievement_info(ach_type)
try:
await message.bot.send_message(
user_id,
f'🏆 Новое достижение!\n\n'
f'{ach_info.get("emoji", "🏆")} {ach_info.get("name", ach_type)}\n\n'
f'{ach_info.get("description", "")}\n\n'
f'Продолжайте в том же духе!',
parse_mode='HTML'
)
logger.info(f'Пользователь {user_id} получил достижение {ach_type}')
except Exception as e:
logger.error(f'Не удалось уведомить о достижении: {e}')
# ==========================================
# ПРОВЕРКА НА СПАМ (чёрный список ссылок)
# ==========================================
if text and not config.is_admin(user_id):
antispam = AntiSpamService(session)
is_spam, found_items, reason = antispam.check_message(text)
settings = antispam.get_settings()
if is_spam:
# Сохраняем предупреждение
spam_warning = SpamWarning(
user_id=user_id,
message_text=text[:1000] if text else None,
reason=reason,
)
session.add(spam_warning)
# Увеличиваем счётчик предупреждений
user.spam_warning_count += 1
warning_count = user.spam_warning_count
# Получаем настройки
delete_message = settings.get('delete_message', True)
warn_user = settings.get('warn_user', True)
notify_admin = settings.get('notify_admin', True)
ban_after = settings.get('ban_after_warnings', 3)
# Удаляем сообщение если нужно
if delete_message:
try:
await message.delete()
logger.info(f'Спам удалён от user_id={user_id}: {reason}')
except Exception as e:
logger.error(f'Не удалось удалить спам: {e}')
# Предупреждаем пользователя
if warn_user:
try:
await message.bot.send_message(
user_id,
f'⚠️ Предупреждение #{warning_count}\n\n'
f'В вашем сообщении обнаружена запрещённая ссылка или спам.\n\n'
f'Причина: {reason}\n\n'
f'📊 Ваши нарушения: {warning_count} из {ban_after}\n\n'
f'При следующем нарушении будет публичное предупреждение.\n'
f'При достижении {ban_after} предупреждений — бан.',
parse_mode='HTML'
)
except Exception as e:
logger.error(f'Не удалось предупредить пользователя: {e}')
# Уведомляем админа
if notify_admin:
try:
await message.bot.send_message(
config.ADMIN_USER_ID,
f'⚠️ СПАМ В ЧАТЕ!\n\n'
f'Пользователь: {user.full_name}\n'
f'ID: {user.user_id}\n'
f'Квартира: {user.apartment or "не указана"}\n\n'
f'Причина: {reason}\n\n'
f'Предупреждений: {warning_count} из {ban_after}',
parse_mode='HTML'
)
except Exception as e:
logger.error(f'Не удалось уведомить админа о спаме: {e}')
# Бан после достижения лимита
if warning_count >= ban_after:
user.is_banned = True
try:
await message.bot.send_message(
user_id,
f'🚫 ВЫ ЗАБАНЕНЫ\n\n'
f'Причина: Систематический спам ({warning_count} предупреждений).\n\n'
f'Вы больше не можете писать в чат.',
parse_mode='HTML'
)
# Пытаемся удалить из чата
await message.bot.ban_chat_member(chat_id, user_id)
except Exception as e:
logger.error(f'Не удалось забанить спамера: {e}')
logger.warning(f'Пользователь {user_id} забанен за спам')
await session.commit()
# Прерываем дальнейшую обработку если спам
return
# ==========================================
# ПРОВЕРКА ЛИМИТОВ СООБЩЕНИЙ (анти-спам)
# ==========================================
if not config.is_admin(user_id):
from services.rate_limiter import RateLimiter
rate_limiter = RateLimiter(session)
# Проверяем не замучен ли пользователь
if user.is_muted and user.mute_until:
# Проверяем истёк ли мут
if await rate_limiter.check_mute_expired(user_id):
# Мут истёк — пропускаем сообщение
pass
else:
# Ещё замучен — удаляем сообщение
try:
await message.delete()
logger.info(f'Сообщение от замученного user_id={user_id} удалено')
except Exception as e:
logger.error(f'Не удалось удалить сообщение замученного: {e}')
return
# Проверяем лимиты
is_limited, msg_count = await rate_limiter.check_rate_limit(user_id)
if is_limited:
# Добавляем сообщение в счётчик (для статистики)
await rate_limiter.add_message(user_id)
# Увеличиваем предупреждения
user.rate_limit_warnings += 1
warning_count = user.rate_limit_warnings
config_rl = rate_limiter.get_config()
mute_after = config_rl.get('warnings_before_mute', 2)
# Удаляем сообщение
try:
await message.delete()
logger.info(f'Флуд удалён от user_id={user_id}')
except Exception as e:
logger.error(f'Не удалось удалить флуд: {e}')
# Предупреждаем пользователя
try:
await message.bot.send_message(
user_id,
f'⚠️ Предупреждение #{warning_count}\n\n'
f'Вы превысили лимит сообщений!\n\n'
f'📊 Ваши нарушения: {warning_count} из {mute_after}\n\n'
f'Лимит: {config_rl["max_messages"]} сообщений за {config_rl["period_seconds"]} сек.\n'
f'Вы отправили: {msg_count} сообщ.\n\n'
f'При достижении {mute_after} предупреждений вы будете замучены на {config_rl["mute_duration_minutes"]} мин.',
parse_mode='HTML'
)
except Exception as e:
logger.error(f'Не удалось предупредить о флуде: {e}')
# Уведомляем админа
try:
await message.bot.send_message(
config.ADMIN_USER_ID,
f'⚠️ ФЛУД В ЧАТЕ!\n\n'
f'Пользователь: {user.full_name}\n'
f'ID: {user.user_id}\n'
f'Квартира: {user.apartment or "не указана"}\n\n'
f'Сообщений: {msg_count} (лимит: {config_rl["max_messages"]})\n'
f'Предупреждений: {warning_count} из {mute_after}',
parse_mode='HTML'
)
except Exception as e:
logger.error(f'Не удалось уведомить админа о флуде: {e}')
# Мут после достижения лимита предупреждений
if warning_count >= mute_after:
mute_until = await rate_limiter.mute_user(user_id)
try:
await message.bot.send_message(
user_id,
f'🔇 ВЫ ЗАМУЧЕНЫ\n\n'
f'Причина: Превышение лимита сообщений ({warning_count} предупреждений).\n\n'
f'Мут до: {mute_until.strftime("%d.%m.%Y %H:%M")}\n\n'
f'Вы не сможете писать в чат до указанного времени.',
parse_mode='HTML'
)
except Exception as e:
logger.error(f'Не удалось уведомить о муте: {e}')
logger.warning(f'Пользователь {user_id} замучен за флуд')
await session.commit()
return
# Добавляем сообщение в счётчик (если не превышен лимит)
await rate_limiter.add_message(user_id)
# Классифицируем тему сообщения
topic = None
if text:
from services.topics_analyzer import TopicsAnalyzer
topics_analyzer = TopicsAnalyzer(session)
topic = await topics_analyzer.analyze_message(text)
# Анализируем токсичность сообщения
toxicity_score = 0
if text and not config.is_admin(user_id):
from services.toxicity_detector import ToxicityDetector
toxicity_detector = ToxicityDetector(session)
toxicity_score, _ = await toxicity_detector.analyze_message(text)
# Обновляем статистику пользователя
if toxicity_score > 0:
user.toxic_messages_count += 1
# Обновляем средний score
total_messages = user.message_count + 1
user.toxicity_score = int(
(user.toxicity_score * (total_messages - 1) + toxicity_score) / total_messages
)
# Уведомляем админа если токсичность высокая
if toxicity_score > 50:
try:
await message.bot.send_message(
config.ADMIN_USER_ID,
f'⚠️ Токсичное сообщение!\n\n'
f'Пользователь: {user.full_name}\n'
f'ID: {user.user_id}\n'
f'Квартира: {user.apartment or "не указана"}\n'
f'Toxicity Score: {toxicity_score}/100\n\n'
f'Проверьте через /toxic_stats {user.user_id}',
parse_mode='HTML'
)
except Exception as e:
logger.error(f'Не удалось уведомить о токсичности: {e}')
# Сохраняем сообщение (если не спам)
msg_model = MessageModel(
message_id=message.message_id,
user_id=user_id,
chat_id=chat_id,
text=text[:4000] if text else None,
topic=topic, # Сохраняем тему
reply_to_message_id=message.reply_to_message.message_id if message.reply_to_message else None,
)
session.add(msg_model)
await session.commit()
# Проверяем на шпиона (только если не админ)
if not config.is_admin(user_id) and config.SPY_DETECTION_ENABLED:
spy_detector = SpyDetector(session)
changed = await spy_detector.update_spy_score(user)
# Если score стал критическим — уведомляем админа
if user.spy_score > 60 and changed:
try:
await message.bot.send_message(
config.get_admin_chat_id(),
f'🚨 ВНИМАНИЕ! ВОЗМОЖНЫЙ ШПИОН!\n\n'
f'Пользователь: {user.full_name}\n'
f'ID: {user.user_id}\n'
f'Квартира: {user.apartment or "не указана"}\n'
f'Spy Score: {user.spy_score}/100\n\n'
f'Флаги: {user.spy_flags}',
parse_mode='HTML'
)
except Exception as e:
logger.error(f'Не удалось уведомить о шпионе: {e}')
logger.debug(f"User {user_id}: msg_count={user.message_count}, rating={user.rating}, level={user.level}")