"""
Хендлер верификации жильцов
"""
import logging
from aiogram import Router, F
from aiogram.types import Message, CallbackQuery
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, VerificationRequest
from utils.validators import validate_apartment
from utils.decorators import admin_only, admin_callback_only
import config
from datetime import datetime
logger = logging.getLogger(__name__)
router = Router()
@router.message(Command('верификация'))
@router.message(Command('verification'))
async def cmd_verification(message: Message):
"""Команда /верификация - статус верификации"""
logger.info(f'Команда /верификация от user_id={message.from_user.id}')
async with AsyncSessionLocal() as session:
stmt = select(User).where(User.user_id == message.from_user.id)
result = await session.execute(stmt)
user = result.scalar_one_or_none()
if not user:
await message.answer(
'❌ Вы не зарегистрированы.\n\n'
'Нажмите /start или отправьте номер квартиры.'
)
return
if user.verified:
await message.answer(
f'✅ Вы верифицированы\n\n'
f'Квартира: {user.apartment}\n'
f'Сообщений: {user.message_count}',
parse_mode='HTML'
)
else:
# Проверяем, есть ли заявка
stmt = select(VerificationRequest).where(
VerificationRequest.user_id == message.from_user.id,
VerificationRequest.status == 'pending'
)
result = await session.execute(stmt)
request = result.scalar_one_or_none()
if request:
await message.answer(
f'⏳ Заявка на проверке\n\n'
f'Квартира: {request.apartment}\n'
f'Статус: Ожидает подтверждения\n\n'
f'Администратор скоро подтвердит вашу заявку.',
parse_mode='HTML'
)
else:
await message.answer(
f'⏳ Вы не верифицированы\n\n'
f'📝 Как пройти верификацию:\n\n'
f'1. Отправьте номер квартиры текстом\n'
f'2. Дождитесь подтверждения администратора\n\n'
f'Примеры: 45, 12А, 100-1',
parse_mode='HTML'
)
@router.message(F.text.regexp(r'^\s*\d+[А-ЯA-Z]?[\s/-]?\d*[А-ЯA-Z]?\s*$'))
async def handle_apartment_number(message: Message):
"""
Обработка номера квартиры
Срабатывает и в личке, и в группе
"""
text = message.text.strip()
user_id = message.from_user.id
# Если админ отправил номер квартиры — автоматически верифицируем
if config.is_admin(user_id):
logger.info(f'Админ отправил номер квартиры: {text}, автоматически верифицируем')
await auto_verify_admin(message, text)
return
logger.info(f'Получен номер квартиры: {text} от user_id={user_id}')
# Проверяем валидность
if not validate_apartment(text):
logger.info(f'Неверный формат квартиры: {text}')
return # Просто игнорируем, это не номер квартиры
await process_verification(message, text)
async def auto_verify_admin(message: Message, apartment: str):
"""Автоматическая верификация админа"""
user_id = message.from_user.id
apartment_upper = apartment.strip().upper()
async with AsyncSessionLocal() as session:
try:
# Проверяем пользователя в БД
stmt = select(User).where(User.user_id == user_id)
result = await session.execute(stmt)
user = result.scalar_one_or_none()
if not user:
# Создаём нового пользователя (админа)
user = User(
user_id=user_id,
username=message.from_user.username,
first_name=message.from_user.first_name,
last_name=message.from_user.last_name,
apartment=apartment_upper,
verified=True,
is_admin=True,
verification_date=datetime.utcnow(),
)
session.add(user)
logger.info(f'Создан новый админ с квартирой {apartment_upper}')
else:
# Обновляем данные админа
user.apartment = apartment_upper
user.verified = True
user.is_admin = True
if not user.verification_date:
user.verification_date = datetime.utcnow()
logger.info(f'Админ верифицирован с квартирой {apartment_upper}')
await session.commit()
await message.answer(
f'✅ Администратор верифицирован!\n\n'
f'Квартира: {apartment_upper}\n'
f'Статус: Владелец/Админ\n\n'
f'Теперь вы можете использовать все админские функции.',
parse_mode='HTML'
)
except Exception as e:
logger.error(f'Ошибка при авто-верификации админа: {e}', exc_info=True)
await message.answer('❌ Произошла ошибка при верификации админа.')
@router.message(F.text, F.chat.type == 'private')
async def handle_any_text_in_private(message: Message):
"""
Обработка любого текста в личке
Пытаемся распознать номер квартиры
"""
# Пропускаем если это админ
if config.is_admin(message.from_user.id):
return
text = message.text.strip()
logger.info(f'Текст в личке: {text} от user_id={message.from_user.id}')
# Проверяем, похоже ли на номер квартиры
if validate_apartment(text):
logger.info(f'Распознан номер квартиры: {text}')
await process_verification(message, text)
else:
# Если не похоже на квартиру — подсказываем
logger.info(f'Не похоже на номер квартиры: {text}')
# Не отвечаем на каждое сообщение, чтобы не спамить
async def process_verification(message: Message, apartment: str):
"""Обработка верификации"""
user_id = message.from_user.id
apartment_upper = apartment.strip().upper()
logger.info(f'Начало верификации: user_id={user_id}, apartment={apartment_upper}')
async with AsyncSessionLocal() as session:
try:
# Проверяем пользователя в БД
stmt = select(User).where(User.user_id == user_id)
result = await session.execute(stmt)
user = result.scalar_one_or_none()
if not user:
logger.info(f'Создаём нового пользователя: user_id={user_id}')
# Создаём нового пользователя
user = User(
user_id=user_id,
username=message.from_user.username,
first_name=message.from_user.first_name,
last_name=message.from_user.last_name,
apartment=apartment_upper,
)
session.add(user)
else:
logger.info(f'Пользователь найден: verified={user.verified}, apartment={user.apartment}')
# Если уже верифицирован
if user.verified:
# Но квартиры нет — сохраняем!
if not user.apartment:
user.apartment = apartment_upper
await session.commit()
logger.info(f'Сохранена квартира для верифицированного: {apartment_upper}')
await message.answer(
f'✅ Квартира сохранена!\n\n'
f'Квартира: {apartment_upper}\n\n'
f'Вы уже были верифицированы.\n'
f'Теперь ваша квартира привязана к аккаунту!',
parse_mode='HTML'
)
return
else:
# Уже есть квартира
await message.answer(
f'✅ Вы уже верифицированы (кв. {user.apartment})\n\n'
'Если нужно изменить квартиру, обратитесь к администратору.',
parse_mode='HTML'
)
return
# Обновляем квартиру для неверифицированных
user.apartment = apartment_upper
# Создаём заявку на верификацию (если ещё нет активной)
stmt = select(VerificationRequest).where(
VerificationRequest.user_id == user_id,
VerificationRequest.status == 'pending'
)
result = await session.execute(stmt)
existing_request = result.scalar_one_or_none()
if not existing_request:
logger.info(f'Создаём заявку на верификацию')
verification = VerificationRequest(
user_id=user_id,
apartment=apartment_upper,
)
session.add(verification)
await session.commit()
logger.info(f'Верификация сохранена в БД')
# Уведомляем пользователя
await message.answer(
f'✅ Заявка на верификацию создана!\n\n'
f'Квартира: {apartment_upper}\n'
f'Статус: На проверке\n\n'
f'Администратор проверит данные в ближайшее время.\n'
f'После подтверждения вы получите доступ ко всем функциям.',
parse_mode='HTML'
)
# Уведомляем админа в личку
try:
user_name = message.from_user.first_name
username = f'@{message.from_user.username}' if message.from_user.username else 'нет'
# Используем ADMIN_USER_ID для лички
await message.bot.send_message(
config.ADMIN_USER_ID,
f'🔔 Новая заявка на верификацию\n\n'
f'Жилец: {user_name} {username}\n'
f'Квартира: {apartment_upper}\n'
f'ID: {user_id}\n\n'
f'Для подтверждения используйте:\n'
f'/verify_approve {user_id}',
parse_mode='HTML'
)
logger.info(f'Админ уведомлён в личку (ID: {config.ADMIN_USER_ID})')
except Exception as e:
logger.error(f'Не удалось уведомить админа в личку: {e}')
except Exception as e:
logger.error(f'Ошибка при верификации: {e}', exc_info=True)
await message.answer('❌ Произошла ошибка при верификации. Попробуйте позже.')
@router.message(Command('verify'))
@admin_only
async def cmd_verify_list(message: Message):
"""Список заявок на верификацию (админ)"""
logger.info(f'Админ запросил список заявок')
async with AsyncSessionLocal() as session:
stmt = (
select(VerificationRequest)
.where(VerificationRequest.status == 'pending')
.order_by(VerificationRequest.created_at)
)
result = await session.execute(stmt)
requests = list(result.scalars().all())
if not requests:
await message.answer('✅ Все заявки обработаны')
return
text = f'📋 Заявки на верификацию ({len(requests)}):\n\n'
for req in requests:
# Получаем имя пользователя
stmt = select(User).where(User.user_id == req.user_id)
result = await session.execute(stmt)
user = result.scalar_one_or_none()
name = user.full_name if user else f'user{req.user_id}'
text += f'• {name} — кв. {req.apartment}\n'
text += f' ID: {req.user_id}\n\n'
text += 'Для подтверждения используйте /verify_approve '
await message.answer(text, parse_mode='HTML')
@router.message(Command('verify_approve'))
@admin_only
async def cmd_verify_approve(message: Message):
"""Подтверждение верификации (работает и в личке, и в группе)"""
logger.info(f'Админ подтверждает верификацию в чате типа: {message.chat.type}')
# Парсим ID пользователя из команды
args = message.text.split()
if len(args) < 2:
await message.answer('Использование: /verify_approve ')
return
try:
user_id = int(args[1])
except ValueError:
await message.answer('❌ Неверный ID пользователя')
return
async with AsyncSessionLocal() as session:
# Находим пользователя
stmt = select(User).where(User.user_id == user_id)
result = await session.execute(stmt)
user = result.scalar_one_or_none()
if not user:
await message.answer('❌ Пользователь не найден')
return
# Верифицируем
user.verified = True
user.verification_date = datetime.utcnow()
# Обновляем заявку
stmt = select(VerificationRequest).where(
VerificationRequest.user_id == user_id,
VerificationRequest.status == 'pending'
)
result = await session.execute(stmt)
request = result.scalar_one_or_none()
if request:
request.status = 'approved'
request.reviewed_at = datetime.utcnow()
request.reviewed_by = message.from_user.id
await session.commit()
logger.info(f'Пользователь {user_id} верифицирован')
# Уведомляем пользователя (в личку)
try:
await message.bot.send_message(
user_id,
f'✅ Верификация подтверждена!\n\n'
f'Квартира: {user.apartment}\n'
f'Теперь у вас есть доступ ко всем функциям бота.',
parse_mode='HTML'
)
except Exception as e:
logger.error(f'Не удалось уведомить пользователя: {e}')
# Отвечаем админу (в тот же чат где отправил команду)
await message.answer(
f'✅ Пользователь верифицирован!\n\n'
f'Жилец: {user.full_name}\n'
f'Квартира: {user.apartment}\n'
f'ID: {user_id}',
parse_mode='HTML'
)
@router.message(Command('verify_reject'))
@admin_only
async def cmd_verify_reject(message: Message):
"""Отклонение верификации"""
args = message.text.split(maxsplit=2)
if len(args) < 2:
await message.answer('Использование: /verify_reject [причина]')
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:
# Находим пользователя
stmt = select(User).where(User.user_id == user_id)
result = await session.execute(stmt)
user = result.scalar_one_or_none()
if not user:
await message.answer('❌ Пользователь не найден')
return
# Обновляем заявку
stmt = select(VerificationRequest).where(
VerificationRequest.user_id == user_id,
VerificationRequest.status == 'pending'
)
result = await session.execute(stmt)
request = result.scalar_one_or_none()
if request:
request.status = 'rejected'
request.reviewed_at = datetime.utcnow()
request.reviewed_by = message.from_user.id
request.reject_reason = reason
await session.commit()
# Уведомляем пользователя
try:
await message.bot.send_message(
user_id,
f'❌ Верификация отклонена\n\n'
f'Причина: {reason}\n\n'
f'Если вы считаете это ошибкой, обратитесь к администратору.'
)
except Exception as e:
logger.error(f'Не удалось уведомить пользователя: {e}')
await message.answer(f'❌ Заявка пользователя отклонена')