✅ Регистрация и верификация жильцов ✅ Рейтинг активности (уровни) ✅ Детект шпионов (Score 0-100) ✅ Анти-мат система (3 предупреждения → бан) ✅ Админ-панель с рассылкой ✅ Учёт квартир (несколько жильцов) ✅ Телефоны экстренных служб и мастеров ✅ Еженедельный экспорт JSON ✅ Прокси (socks5) для обхода РКН Бекап: backups/versions/v1.0_mvp_2026-02-26/ Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
271 lines
10 KiB
Python
271 lines
10 KiB
Python
"""
|
||
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 <user_id></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 <user_id></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 <user_id> [причина]</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')
|