domovoy_bot/handlers/verification.py

443 lines
19 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, 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'Квартира: <code>{user.apartment}</code>\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'⏳ <b>Заявка на проверке</b>\n\n'
f'Квартира: <code>{request.apartment}</code>\n'
f'Статус: Ожидает подтверждения\n\n'
f'Администратор скоро подтвердит вашу заявку.',
parse_mode='HTML'
)
else:
await message.answer(
f'⏳ Вы не верифицированы\n\n'
f'📝 <b>Как пройти верификацию:</b>\n\n'
f'1. Отправьте номер квартиры текстом\n'
f'2. Дождитесь подтверждения администратора\n\n'
f'Примеры: <code>45</code>, <code>12А</code>, <code>100-1</code>',
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'✅ <b>Администратор верифицирован!</b>\n\n'
f'Квартира: <code>{apartment_upper}</code>\n'
f'Статус: <b>Владелец/Админ</b>\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'✅ <b>Квартира сохранена!</b>\n\n'
f'Квартира: <code>{apartment_upper}</code>\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'✅ <b>Заявка на верификацию создана!</b>\n\n'
f'Квартира: <code>{apartment_upper}</code>\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'🔔 <b>Новая заявка на верификацию</b>\n\n'
f'Жилец: {user_name} {username}\n'
f'Квартира: {apartment_upper}\n'
f'ID: <code>{user_id}</code>\n\n'
f'Для подтверждения используйте:\n'
f'<code>/verify_approve {user_id}</code>',
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'📋 <b>Заявки на верификацию</b> ({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: <code>{req.user_id}</code>\n\n'
text += 'Для подтверждения используйте /verify_approve <ID>'
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 <user_id>')
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'✅ <b>Верификация подтверждена!</b>\n\n'
f'Квартира: {user.apartment}\n'
f'Теперь у вас есть доступ ко всем функциям бота.',
parse_mode='HTML'
)
except Exception as e:
logger.error(f'Не удалось уведомить пользователя: {e}')
# Отвечаем админу (в тот же чат где отправил команду)
await message.answer(
f'✅ <b>Пользователь верифицирован!</b>\n\n'
f'Жилец: {user.full_name}\n'
f'Квартира: {user.apartment}\n'
f'ID: <code>{user_id}</code>',
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 <user_id> [причина]')
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'❌ <b>Верификация отклонена</b>\n\n'
f'Причина: {reason}\n\n'
f'Если вы считаете это ошибкой, обратитесь к администратору.'
)
except Exception as e:
logger.error(f'Не удалось уведомить пользователя: {e}')
await message.answer(f'❌ Заявка пользователя отклонена')