"""
Детект мультиаккаунтов — выявление нескольких аккаунтов от одного человека
Только для администраторов
"""
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.multicount_detector import MulticountDetector
from utils.decorators import admin_only
import config
import json
logger = logging.getLogger(__name__)
router = Router()
# =============================================================================
# ПРОВЕРКА НА МУЛЬТИАККАУНТЫ
# =============================================================================
@router.message(Command('multicheck'))
@router.message(Command('мультичек'))
@admin_only
async def cmd_multicheck(message: Message):
"""
Проверить всех пользователей на мультиаккаунты
Использование: /multicheck
"""
await message.answer('🔍 Анализирую пользователей на мультиаккаунты...')
async with AsyncSessionLocal() as session:
detector = MulticountDetector(session)
suspicious = await detector.analyze_all_users()
if not suspicious:
await message.answer('✅ Мультиаккаунты не обнаружены')
return
text = f'🚨 Обнаружены подозрительные пары ({len(suspicious)})\n\n'
for i, pair in enumerate(suspicious, 1):
text += f'{i}. Пара #{i}\n'
text += f' ├─ {pair["user1_name"]} (кв. {pair["user1_apartment"] or "?"})\n'
text += f' └─ {pair["user2_name"]} (кв. {pair["user2_apartment"] or "?"})\n'
text += f' └─ Score: {pair["score"]}/100\n'
text += f' └─ Причины: {", ".join(pair["reasons"])}\n\n'
text += '💡 Для проверки квартиры: /check_apt <номер>'
await message.answer(text, parse_mode='HTML')
@router.message(Command('check_apt'))
@router.message(Command('проверить_квартиру'))
@admin_only
async def cmd_check_apartment(message: Message):
"""
Проверить квартиру на мультиаккаунты
Использование: /check_apt <номер>
"""
args = message.text.split()
if len(args) < 2:
await message.answer(
'❌ Использование:\n'
'/check_apt <номер квартиры>\n\n'
'Пример: /check_apt 45',
parse_mode='HTML'
)
return
apartment = args[1]
async with AsyncSessionLocal() as session:
# Получаем всех пользователей в квартире
stmt = select(User).where(User.apartment == apartment)
result = await session.execute(stmt)
users = list(result.scalars().all())
if not users:
await message.answer(f'ℹ️ В квартире {apartment} нет зарегистрированных пользователей')
return
if len(users) == 1:
user = users[0]
text = (
f'🏠 Квартира {apartment}\n\n'
f'1 жилец:\n'
f'• {user.full_name} (@{user.username or "нет ника"})\n'
f' └─ ID: {user.user_id}\n'
f' └─ Верифицирован: {"✅" if user.verified else "❌"}\n'
f' └─ Сообщений: {user.message_count}\n'
)
await message.answer(text, parse_mode='HTML')
return
# Несколько жильцов
text = f'🏠 Квартира {apartment} — {len(users)} жильцов\n\n'
for i, user in enumerate(users, 1):
text += f'{i}. {user.full_name} (@{user.username or "нет ника"})\n'
text += f' └─ ID: {user.user_id}\n'
text += f' └─ Верифицирован: {"✅" if user.verified else "❌"}\n'
text += f' └─ Сообщений: {user.message_count}\n'
text += f' └─ Рейтинг: {user.rating}\n\n'
if len(users) > 2:
text += f'⚠️ Внимание! В квартире {len(users)} человек — это много!\n\n'
text += '💡 Для детальной проверки: /multicheck'
await message.answer(text, parse_mode='HTML')
@router.message(Command('multistats'))
@admin_only
async def cmd_multistats(message: Message):
"""Показать статистику по мультиаккаунтам"""
async with AsyncSessionLocal() as session:
detector = MulticountDetector(session)
# Получаем пользователей с multicount_score > 0
stmt = select(User).where(User.multicount_score > 0)
result = await session.execute(stmt)
flagged_users = list(result.scalars().all())
# Считаем квартиры с несколькими жильцами
stmt = select(User.apartment, func.count(User.user_id)).where(
User.apartment.isnot(None)
).group_by(User.apartment).having(func.count(User.user_id) > 1)
result = await session.execute(stmt)
multi_apt = list(result.all())
text = '📊 Статистика мультиаккаунтов\n\n'
if flagged_users:
text += f'🔴 Пользователей с подозрительным score: {len(flagged_users)}\n\n'
for user in flagged_users[:10]:
text += f'• {user.full_name} (кв. {user.apartment or "?"}) — score: {user.multicount_score}\n'
if len(flagged_users) > 10:
text += f'\n... и ещё {len(flagged_users) - 10}'
text += '\n\n'
if multi_apt:
text += f'🏠 Квартир с несколькими жильцами: {len(multi_apt)}\n\n'
for apt, count in sorted(multi_apt, key=lambda x: x[1], reverse=True)[:10]:
text += f'• Кв. {apt} — {count} чел.\n'
if len(multi_apt) > 10:
text += f'\n... и ещё {len(multi_apt) - 10}'
if not flagged_users and not multi_apt:
text += '✅ Подозрительных активностей не обнаружено'
await message.answer(text, parse_mode='HTML')
@router.message(Command('clear_multiscore'))
@admin_only
async def cmd_clear_multiscore(message: Message):
"""
Сбросить multicount_score пользователю
Использование: /clear_multiscore
"""
args = message.text.split()
if len(args) < 2:
await message.answer(
'❌ Использование:\n'
'/clear_multiscore <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.multicount_score = 0
user.suspected_alt_accounts = None
await session.commit()
await message.answer(f'✅ multicount_score пользователя {user_id} сброшен')
@router.message(Command('link_accounts'))
@admin_only
async def cmd_link_accounts(message: Message):
"""
Связать аккаунты (официально, например семья)
Использование: /link_accounts
"""
args = message.text.split()
if len(args) < 3:
await message.answer(
'❌ Использование:\n'
'/link_accounts <user_id1> <user_id2>\n\n'
'Пример: /link_accounts 123456789 987654321',
parse_mode='HTML'
)
return
try:
user_id1 = int(args[1])
user_id2 = int(args[2])
except ValueError:
await message.answer('❌ Неверные ID пользователей')
return
async with AsyncSessionLocal() as session:
user1 = await session.get(User, user_id1)
user2 = await session.get(User, user_id2)
if not user1 or not user2:
await message.answer('❌ Один из пользователей не найден')
return
# Сбрасываем score
user1.multicount_score = 0
user2.multicount_score = 0
# Помечаем что аккаунты связаны (это не шпионы)
# Можно добавить отдельную таблицу linked_accounts
await session.commit()
await message.answer(
f'✅ Аккаунты связаны:\n'
f'• {user1.full_name} (ID: {user_id1})\n'
f'• {user2.full_name} (ID: {user_id2})\n\n'
f'multicount_score сброшен'
)