domovoy_bot/handlers/multicount.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

244 lines
9.2 KiB
Python
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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.

"""
Детект мультиаккаунтов — выявление нескольких аккаунтов от одного человека
Только для администраторов
"""
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'🚨 <b>Обнаружены подозрительные пары</b> ({len(suspicious)})\n\n'
for i, pair in enumerate(suspicious, 1):
text += f'{i}. <b>Пара #{i}</b>\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(
'❌ <b>Использование:</b>\n'
'<code>/check_apt &lt;номер квартиры&gt;</code>\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'🏠 <b>Квартира {apartment}</b>\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'🏠 <b>Квартира {apartment}</b> — {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'⚠️ <b>Внимание!</b> В квартире {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 = '📊 <b>Статистика мультиаккаунтов</b>\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 <user_id>
"""
args = message.text.split()
if len(args) < 2:
await message.answer(
'❌ <b>Использование:</b>\n'
'<code>/clear_multiscore &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.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 <user_id1> <user_id2>
"""
args = message.text.split()
if len(args) < 3:
await message.answer(
'❌ <b>Использование:</b>\n'
'<code>/link_accounts &lt;user_id1&gt; &lt;user_id2&gt;</code>\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 сброшен'
)