""" Детект мультиаккаунтов — выявление нескольких аккаунтов от одного человека Только для администраторов """ 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 сброшен' )