""" Toxicity Rating — рейтинг токсичности пользователей Только для администраторов """ import logging 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.toxicity_detector import ToxicityDetector from utils.decorators import admin_only import config logger = logging.getLogger(__name__) router = Router() # ============================================================================= # ТОП ТОКСИЧНЫХ # ============================================================================= @router.message(Command('toxic_top')) @router.message(Command('токсичные')) @admin_only async def cmd_toxic_top(message: Message): """ Показать топ токсичных пользователей Использование: /toxic_top [дни] [лимит] """ args = message.text.split() days = int(args[1]) if len(args) > 1 and args[1].isdigit() else 7 limit = int(args[2]) if len(args) > 2 and args[2].isdigit() else 10 async with AsyncSessionLocal() as session: detector = ToxicityDetector(session) toxic_users = await detector.get_toxic_users(days, limit) if not toxic_users: await message.answer('✅ Токсичных пользователей не обнаружено') return text = f'😠 Топ токсичных пользователей (за {days} дн.)\n\n' for i, user_data in enumerate(toxic_users, 1): stats = user_data['stats'] name = user_data['full_name'] or user_data['username'] or f'user{user_data["user_id"]}' apt = user_data['apartment'] or '?' level, emoji = detector.get_toxicity_level(stats['avg_toxicity']) text += f'{i}. {name} (кв. {apt}) {emoji}\n' text += f' └─ Сообщений: {stats["total_messages"]}\n' text += f' └─ Токсичных: {stats["toxic_messages"]} ({stats["toxicity_percent"]}%)\n' text += f' └─ Средняя токсичность: {stats["avg_toxicity"]}\n' text += f' └─ Максимальная: {stats["max_toxicity"]}\n\n' await message.answer(text, parse_mode='HTML') # ============================================================================= # СТАТИСТИКА ПОЛЬЗОВАТЕЛЯ # ============================================================================= @router.message(Command('toxic_stats')) @router.message(Command('токсичность')) @admin_only async def cmd_toxic_stats(message: Message): """ Показать токсичность пользователя Использование: /toxic_stats """ args = message.text.split() if len(args) < 2: await message.answer( '❌ Использование:\n' '/toxic_stats <user_id>\n\n' 'Пример: /toxic_stats 123456789', parse_mode='HTML' ) return try: user_id = int(args[1]) except ValueError: await message.answer('❌ Неверный ID пользователя') return async with AsyncSessionLocal() as session: detector = ToxicityDetector(session) # Получаем пользователя user = await session.get(User, user_id) if not user: await message.answer(f'❌ Пользователь {user_id} не найден') return # Получаем статистику stats = await detector.calculate_user_toxicity(user_id, 7) level, emoji = detector.get_toxicity_level(stats['avg_toxicity']) name = user.full_name or user.username or f'user{user_id}' apt = user.apartment or '?' text = ( f'{emoji} Токсичность: {name}\n\n' f'Квартира: {apt}\n' f'Уровень: {level}\n\n' f'📊 Статистика (7 дн.)\n' f'├─ Всего сообщений: {stats["total_messages"]}\n' f'├─ Токсичных: {stats["toxic_messages"]} ({stats["toxicity_percent"]}%)\n' f'├─ Средняя токсичность: {stats["avg_toxicity"]}\n' f'└─ Максимальная: {stats["max_toxicity"]}\n' ) if stats['categories']: text += '\n📁 Категории\n' for category, count in sorted(stats['categories'].items(), key=lambda x: x[1], reverse=True): text += f' └─ {category}: {count}\n' await message.answer(text, parse_mode='HTML') # ============================================================================= # СБРОС ТОКСИЧНОСТИ # ============================================================================= @router.message(Command('clear_toxicity')) @admin_only async def cmd_clear_toxicity(message: Message): """ Сбросить токсичность пользователя Использование: /clear_toxicity """ args = message.text.split() if len(args) < 2: await message.answer( '❌ Использование:\n' '/clear_toxicity <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 not user: await message.answer(f'❌ Пользователь {user_id} не найден') return user.toxicity_score = 0 user.toxic_messages_count = 0 user.complaints_count = 0 await session.commit() await message.answer(f'✅ Токсичность пользователя {user_id} сброшена') # ============================================================================= # ЖАЛОБЫ # ============================================================================= @router.message(Command('complaint')) @router.message(Command('жалоба')) @admin_only async def cmd_complaint(message: Message): """ Подать жалобу на пользователя Использование: /complaint [причина] """ args = message.text.split(maxsplit=2) if len(args) < 2: await message.answer( '❌ Использование:\n' '/complaint <user_id> [причина]\n\n' 'Пример: /complaint 123456789 Оскорбления', parse_mode='HTML' ) return try: user_id = int(args[1]) except ValueError: await message.answer('❌ Неверный ID пользователя') return reason = args[2] if len(args) > 2 else 'Без указания причины' async with AsyncSessionLocal() as session: user = await session.get(User, user_id) if not user: await message.answer(f'❌ Пользователь {user_id} не найден') return # Увеличиваем счётчик жалоб user.complaints_count += 1 await session.commit() name = user.full_name or user.username or f'user{user_id}' await message.answer( f'✅ Жалоба принята\n\n' f'Пользователь: {name}\n' f'Причина: {reason}\n' f'Всего жалоб: {user.complaints_count}' ) # Уведомляем админа если жалоб много if user.complaints_count >= 3: try: await message.bot.send_message( config.ADMIN_USER_ID, f'⚠️ Много жалоб!\n\n' f'Пользователь: {name}\n' f'Жалоб: {user.complaints_count}\n\n' f'Рекомендуется проверить через /toxic_stats {user_id}', parse_mode='HTML' ) except Exception as e: logger.error(f'Не удалось уведомить о жалобах: {e}') # ============================================================================= # ОБЩАЯ СТАТИСТИКА # ============================================================================= @router.message(Command('toxicity_report')) @admin_only async def cmd_toxicity_report(message: Message): """Общий отчёт по токсичности в чате""" async with AsyncSessionLocal() as session: detector = ToxicityDetector(session) # Получаем всех пользователей stmt = select(User).where(User.is_banned == False) result = await session.execute(stmt) users = list(result.scalars().all()) total_users = len(users) toxic_users_count = sum(1 for u in users if u.toxicity_score > 0) total_complaints = sum(u.complaints_count for u in users) # Топ токсичных top_toxic = await detector.get_toxic_users(7, 5) text = ( f'📊 Отчёт по токсичности\n\n' f'👥 Пользователей: {total_users}\n' f'😠 Токсичных: {toxic_users_count} ({toxic_users_count/total_users*100:.1f}%)\n' f'📝 Всего жалоб: {total_complaints}\n\n' ) if top_toxic: text += '🔥 Топ-5 токсичных (7 дн.)\n\n' for i, user_data in enumerate(top_toxic, 1): name = user_data['full_name'] or user_data['username'] or f'user{user_data["user_id"]}' stats = user_data['stats'] level, emoji = detector.get_toxicity_level(stats['avg_toxicity']) text += f'{i}. {name} {emoji} — {stats["avg_toxicity"]}\n' await message.answer(text, parse_mode='HTML')