""" Анти-спам лимиты — мут/размут пользователей Только для администраторов """ import logging from datetime import datetime from aiogram import Router, F from aiogram.types import Message from aiogram.filters import Command from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy import select from database.db import AsyncSessionLocal from database.models import User from services.rate_limiter import RateLimiter from utils.decorators import admin_only import config logger = logging.getLogger(__name__) router = Router() # ============================================================================= # МУТ/РАЗМУТ # ============================================================================= @router.message(Command('mute')) @admin_only async def cmd_mute(message: Message): """ Замутить пользователя Использование: /mute [минуты] """ args = message.text.split() if len(args) < 2: await message.answer( '❌ Использование:\n' '/mute <user_id> [минуты]\n\n' 'Пример: /mute 123456789 30', parse_mode='HTML' ) return try: user_id = int(args[1]) except ValueError: await message.answer('❌ Неверный ID пользователя') return duration = int(args[2]) if len(args) > 2 else None async with AsyncSessionLocal() as session: rate_limiter = RateLimiter(session) mute_until = await rate_limiter.mute_user(user_id, duration) if mute_until: user = await session.get(User, user_id) name = user.full_name if user else f'user{user_id}' await message.answer( f'🔇 Пользователь замучен\n\n' f'Пользователь: {name}\n' f'ID: {user_id}\n' f'До: {mute_until.strftime("%d.%m.%Y %H:%M")}\n\n' f'Пользователь не сможет писать в чат до указанного времени.', parse_mode='HTML' ) else: await message.answer(f'❌ Пользователь {user_id} не найден') @router.message(Command('unmute')) @admin_only async def cmd_unmute(message: Message): """ Размутить пользователя Использование: /unmute """ args = message.text.split() if len(args) < 2: await message.answer( '❌ Использование:\n' '/unmute <user_id>', parse_mode='HTML' ) return try: user_id = int(args[1]) except ValueError: await message.answer('❌ Неверный ID пользователя') return async with AsyncSessionLocal() as session: rate_limiter = RateLimiter(session) if await rate_limiter.unmute_user(user_id): user = await session.get(User, user_id) name = user.full_name if user else f'user{user_id}' await message.answer( f'🔊 Пользователь размучен\n\n' f'Пользователь: {name}\n' f'ID: {user_id}\n\n' f'Пользователь снова может писать в чат.', parse_mode='HTML' ) else: await message.answer(f'❌ Пользователь {user_id} не найден или не был замучен') @router.message(Command('check_mute')) @admin_only async def cmd_check_mute(message: Message): """ Проверить истёк ли мут у пользователя Использование: /check_mute """ args = message.text.split() if len(args) < 2: await message.answer( '❌ Использование:\n' '/check_mute <user_id>', parse_mode='HTML' ) return try: user_id = int(args[1]) except ValueError: await message.answer('❌ Неверный ID пользователя') return async with AsyncSessionLocal() as session: rate_limiter = RateLimiter(session) expired = await rate_limiter.check_mute_expired(user_id) user = await session.get(User, user_id) if not user: await message.answer(f'❌ Пользователь {user_id} не найден') return name = user.full_name if user else f'user{user_id}' if user.is_muted and user.mute_until: if expired: await message.answer( f'✅ Мут истёк\n\n' f'Пользователь: {name}\n' f'ID: {user_id}\n\n' f'Пользователь размучен автоматически.', parse_mode='HTML' ) else: remaining = user.mute_until - datetime.utcnow() minutes = int(remaining.total_seconds() / 60) await message.answer( f'⏳ Мут активен\n\n' f'Пользователь: {name}\n' f'ID: {user_id}\n' f'Осталось: {minutes} мин.\n' f'До: {user.mute_until.strftime("%d.%m.%Y %H:%M")}', parse_mode='HTML' ) else: await message.answer(f'ℹ️ Пользователь {name} не замучен') # ============================================================================= # СТАТИСТИКА # ============================================================================= @router.message(Command('muted')) @admin_only async def cmd_muted(message: Message): """Показать всех замученных пользователей""" async with AsyncSessionLocal() as session: stmt = ( select(User) .where(User.is_muted == True) .order_by(User.mute_until) ) result = await session.execute(stmt) users = list(result.scalars().all()) if not users: await message.answer('✅ Нет замученных пользователей') return text = '🔇 Замученные пользователи\n\n' for user in users: name = user.full_name or f'user{user.user_id}' apt = user.apartment or '?' remaining = user.mute_until - datetime.utcnow() if user.mute_until else None minutes = int(remaining.total_seconds() / 60) if remaining and remaining.total_seconds() > 0 else 0 if minutes > 0: text += f'• {name} (кв. {apt}) — ещё {minutes} мин.\n' else: text += f'• {name} (кв. {apt}) — мут истёк (нужно проверить)\n' await message.answer(text) @router.message(Command('rate_stats')) @admin_only async def cmd_rate_stats(message: Message): """Показать статистику лимитов""" async with AsyncSessionLocal() as session: rate_limiter = RateLimiter(session) stats = rate_limiter.get_stats() violators = await rate_limiter.get_all_violators() text = ( f'📊 Статистика лимитов сообщений\n\n' f'⚙️ Настройки:\n' f'├─ Максимум сообщений: {stats["max_messages"]}\n' f'├─ За период: {stats["period_seconds"]} сек\n' f'├─ Длительность мута: {stats["mute_duration_minutes"]} мин\n' f'└─ Предупреждений перед мутом: {stats["warnings_before_mute"]}\n\n' ) if violators: text += f'🔥 Нарушители ({len(violators)})\n\n' for v in violators[:10]: name = v.get('full_name') or v.get('username') or f'user{v["user_id"]}' status = '🔇' if v.get('is_muted') else '⚠️' text += f'{status} {name} — {v["messages_count"]} сообщ.\n' if len(violators) > 10: text += f'\n... и ещё {len(violators) - 10}' else: text += '✅ Нет активных нарушителей' await message.answer(text) @router.message(Command('user_rate')) @admin_only async def cmd_user_rate(message: Message): """ Проверить статистику пользователя Использование: /user_rate """ args = message.text.split() if len(args) < 2: await message.answer( '❌ Использование:\n' '/user_rate <user_id>', parse_mode='HTML' ) return try: user_id = int(args[1]) except ValueError: await message.answer('❌ Неверный ID пользователя') return async with AsyncSessionLocal() as session: rate_limiter = RateLimiter(session) stats = await rate_limiter.get_user_stats(user_id) user = await session.get(User, user_id) if not user: await message.answer(f'❌ Пользователь {user_id} не найден') return name = user.full_name or f'user{user_id}' apt = user.apartment or '?' text = ( f'📊 Статистика пользователя\n\n' f'Пользователь: {name}\n' f'ID: {user_id}\n' f'Квартира: {apt}\n\n' f'📈 Активность:\n' f'├─ Сообщений за период: {stats["messages_count"]} из {stats["max_messages"]}\n' f'├─ Предупреждений: {stats["rate_limit_warnings"]}\n' f'└─ Статус: {"🔇 Замучен" if stats["is_muted"] else "✅ Активен"}\n' ) if stats["is_muted"] and stats.get("mute_until"): text += f'└─ До: {stats["mute_until"]}' await message.answer(text, parse_mode='HTML') # ============================================================================= # СБРОС ПРЕДУПРЕЖДЕНИЙ # ============================================================================= @router.message(Command('clear_rate_warnings')) @admin_only async def cmd_clear_rate_warnings(message: Message): """ Сбросить предупреждения о лимитах Использование: /clear_rate_warnings """ args = message.text.split() if len(args) < 2: await message.answer( '❌ Использование:\n' '/clear_rate_warnings <user_id>', parse_mode='HTML' ) return try: user_id = int(args[1]) except ValueError: await message.answer('❌ Неверный ID пользователя') return async with AsyncSessionLocal() as session: user = await session.get(User, user_id) if user: user.rate_limit_warnings = 0 await session.commit() await message.answer(f'✅ Предупреждения пользователя {user_id} сброшены') else: await message.answer(f'❌ Пользователь {user_id} не найден')