domovoy_bot/handlers/toxicity.py
Admin ba4676b0af v1.0 MVP - Инициализация проекта 🏠
 Регистрация и верификация жильцов
 Рейтинг активности (уровни)
 Детект шпионов (Score 0-100)
 Анти-мат система (3 предупреждения → бан)
 Админ-панель с рассылкой
 Учёт квартир (несколько жильцов)
 Телефоны экстренных служб и мастеров
 Еженедельный экспорт JSON
 Прокси (socks5) для обхода РКН

Бекап: backups/versions/v1.0_mvp_2026-02-26/

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-02-27 10:54:16 +00:00

271 lines
10 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
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'😠 <b>Топ токсичных пользователей</b> (за {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 <user_id>
"""
args = message.text.split()
if len(args) < 2:
await message.answer(
'❌ <b>Использование:</b>\n'
'<code>/toxic_stats &lt;user_id&gt;</code>\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} <b>Токсичность: {name}</b>\n\n'
f'Квартира: {apt}\n'
f'Уровень: {level}\n\n'
f'📊 <b>Статистика (7 дн.)</b>\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📁 <b>Категории</b>\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 <user_id>
"""
args = message.text.split()
if len(args) < 2:
await message.answer(
'❌ <b>Использование:</b>\n'
'<code>/clear_toxicity &lt;user_id&gt;</code>',
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 <user_id> [причина]
"""
args = message.text.split(maxsplit=2)
if len(args) < 2:
await message.answer(
'❌ <b>Использование:</b>\n'
'<code>/complaint &lt;user_id&gt; [причина]</code>\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'✅ <b>Жалоба принята</b>\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'⚠️ <b>Много жалоб!</b>\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'📊 <b>Отчёт по токсичности</b>\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 += '🔥 <b>Топ-5 токсичных (7 дн.)</b>\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')