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

190 lines
7.3 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.

"""
Хендлеры для работы с квартирами
Показывает кто живёт в квартире, статистику и т.д.
"""
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, func
from database.db import AsyncSessionLocal
from database.models import User
from utils.decorators import admin_only
from datetime import datetime
logger = logging.getLogger(__name__)
router = Router()
@router.message(Command('apt'))
@admin_only
async def cmd_apartment_info(message: Message):
"""
Показать всех жильцов квартиры
Использование: /apt 88
"""
args = message.text.split()
if len(args) < 2:
await message.answer(
'Использование: <code>/apt &lt;номер квартиры&gt;</code>\n\n'
'Пример: <code>/apt 88</code>',
parse_mode='HTML'
)
return
apartment = args[1].strip().upper()
async with AsyncSessionLocal() as session:
# Находим всех жильцов этой квартиры
stmt = (
select(User)
.where(User.apartment == apartment)
.order_by(User.verification_date.asc())
)
result = await session.execute(stmt)
users = list(result.scalars().all())
if not users:
await message.answer(f'🏠 <b>Квартира {apartment}</b>\n\n❌ Нет жильцов')
return
# Считаем статистику
verified_count = sum(1 for u in users if u.verified)
unverified_count = len(users) - verified_count
text = f'🏠 <b>Квартира {apartment}</b> — {len(users)} чел.\n\n'
text += f'✅ Верифицированы: {verified_count}\n'
if unverified_count > 0:
text += f'Не верифицированы: {unverified_count}\n'
text += '\n'
for i, user in enumerate(users, 1):
status = '' if user.verified else ''
name = user.full_name or f'user{user.user_id}'
username = f'@{user.username}' if user.username else ''
# Кто первый верифицировался — основной жилец
main_marker = '👤 <b>(основной)</b>' if i == 1 else ''
text += f'{i}. {status} <code>{user.user_id}</code> — {name} {username} {main_marker}\n'
if user.verification_date:
days_ago = (datetime.utcnow() - user.verification_date).days
text += f' └─ Верифицирован: {days_ago} дн. назад\n'
else:
text += f' └─ Не верифицирован\n'
if user.message_count > 0:
text += f' └─ Сообщений: {user.message_count}\n'
text += '\n'
# Если больше одного — предупреждение для админа
if len(users) > 1:
text += '⚠️ <b>Внимание: несколько человек в одной квартире!</b>\n\n'
text += 'Это может быть:\n'
text += '✅ Члены семьи (нормально)\n'
text += '❌ Шпион (нужно проверить)\n\n'
text += '💡 Спросите в чате: "Кто ещё живёт в кв. {apartment}?"'
await message.answer(text, parse_mode='HTML')
@router.message(Command('apt_stats'))
@admin_only
async def cmd_apartment_stats(message: Message):
"""
Статистика по квартирам с несколькими жильцами
"""
async with AsyncSessionLocal() as session:
# Группируем по квартирам и считаем количество жильцов
stmt = (
select(User.apartment, func.count(User.user_id).label('count'))
.where(User.apartment.isnot(None))
.group_by(User.apartment)
.having(func.count(User.user_id) > 1)
.order_by(func.count(User.user_id).desc())
)
result = await session.execute(stmt)
multi_apt = list(result.all())
# Общая статистика
stmt = select(func.count(User.user_id))
total_users = (await session.execute(stmt)).scalar() or 0
stmt = select(func.count(User.apartment)).where(User.apartment.isnot(None))
total_apartments = (await session.execute(stmt)).scalar() or 0
if not multi_apt:
await message.answer(
f'📊 <b>Статистика по квартирам</b>\n\n'
f'Всего жильцов: {total_users}\n'
f'Всего квартир: {total_apartments}\n\n'
f'✅ Нет квартир с несколькими жильцами'
)
return
text = f'📊 <b>Статистика по квартирам</b>\n\n'
text += f'Всего жильцов: {total_users}\n'
text += f'Всего квартир: {total_apartments}\n'
text += f'🏠 Квартир с несколькими жильцами: {len(multi_apt)}\n\n'
text += f'<b>Квартиры с несколькими жильцами:</b>\n\n'
for apt, count in multi_apt[:20]: # Показываем первые 20
text += f'🏠 <b>Кв. {apt}</b> — {count} чел.\n'
if len(multi_apt) > 20:
text += f'\n... и ещё {len(multi_apt) - 20} квартир'
text += '\n\n💡 Используйте <code>/apt &lt;номер&gt;</code> для деталей'
await message.answer(text, parse_mode='HTML')
@router.message(Command('find_apt'))
@admin_only
async def cmd_find_apartment(message: Message):
"""
Поиск квартиры по номеру (частичное совпадение)
Использование: /find 8
Найдёт: 8, 88, 8А, 108, 808 и т.д.
"""
args = message.text.split()
if len(args) < 2:
await message.answer(
'Использование: <code>/find &lt;часть номера&gt;</code>\n\n'
'Пример: <code>/find 8</code> найдёт 8, 88, 8А, 108...',
parse_mode='HTML'
)
return
search_term = args[1].strip().upper()
async with AsyncSessionLocal() as session:
# Ищем квартиры где есть такой номер
stmt = (
select(User.apartment, func.count(User.user_id).label('count'))
.where(User.apartment.like(f'%{search_term}%'))
.group_by(User.apartment)
.order_by(func.count(User.user_id).desc())
)
result = await session.execute(stmt)
apartments = list(result.all())
if not apartments:
await message.answer(f'🔍 Квартиры с "{search_term}" не найдены')
return
text = f'🔍 <b>Квартиры с "{search_term}"</b> ({len(apartments)}):\n\n'
for apt, count in apartments:
text += f'🏠 <b>Кв. {apt}</b> — {count} чел.\n'
text += '\n💡 Используйте <code>/apt &lt;номер&gt;</code> для деталей'
await message.answer(text, parse_mode='HTML')