""" Мониторинг чата Считывает все сообщения для статистики и детекта шпионов """ 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}")