"""
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')