Files
new_lottery_bot/src/handlers/registration_handlers.py
Andrew K. Choi b45fe005b9
Some checks failed
continuous-integration/drone/pr Build is failing
Обновление UI: убрать розыгрыши, переименовать счета, добавить кнопку главная
- Удалена кнопка 'Розыгрыши' из главной клавиатуры
- Переименована кнопка 'Мои счета' -> 'Мои логины'
- Показывается ник пользователя вместо TG_ID в чате
- Добавлена кнопка 'Главная' на все клавиатуры
- Проверка регистрации и сокрытие кнопки регистрации
- Валидация номера телефона при регистрации (проверка на символ '-')
2026-03-07 08:11:10 +09:00

249 lines
10 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.

"""Обработчики для регистрации пользователей"""
from aiogram import Router, F
from aiogram.types import Message, CallbackQuery, InlineKeyboardButton, InlineKeyboardMarkup
from aiogram.filters import Command, StateFilter
from src.filters.case_insensitive import CaseInsensitiveCommand
from aiogram.fsm.context import FSMContext
from aiogram.fsm.state import State, StatesGroup
import logging
from src.core.database import async_session_maker
from src.core.registration_services import RegistrationService, AccountService
from src.core.services import UserService
logger = logging.getLogger(__name__)
router = Router()
# Служебные слова, которые нельзя использовать как никнейм
FORBIDDEN_NICKNAMES = [
'привет', 'здравствуйте', 'добрый', 'день', 'вечер', 'утро',
'спасибо', 'пожалуйста', 'извините', 'до свидания', 'пока',
'admin', 'administrator', 'moderator', 'bot', 'system',
'hello', 'hi', 'thanks', 'please', 'sorry', 'goodbye', 'bye'
]
def validate_nickname(nickname: str) -> tuple[bool, str]:
"""
Валидация никнейма
Returns:
(valid, error_message)
"""
nickname = nickname.strip()
# Проверка длины
if len(nickname) < 2:
return False, "❌ Никнейм слишком короткий (минимум 2 символа)"
if len(nickname) > 20:
return False, "❌ Никнейм слишком длинный (максимум 20 символов)"
# Проверка на служебные слова
nickname_lower = nickname.lower()
for forbidden in FORBIDDEN_NICKNAMES:
if forbidden in nickname_lower:
import random
suggestion = f"{nickname[:3]}{random.randint(10, 99)}"
return False, f"❌ Это похоже на приветствие или служебное слово.\n\nПридумайте уникальный никнейм (например: {suggestion})"
# Проверка на команды
if nickname.startswith('/'):
return False, "❌ Никнейм не может начинаться с '/'"
return True, ""
class RegistrationStates(StatesGroup):
"""Состояния для процесса регистрации"""
waiting_for_nickname = State()
waiting_for_club_card = State()
waiting_for_phone = State()
@router.callback_query(F.data == "start_registration")
async def start_registration(callback: CallbackQuery, state: FSMContext):
"""Начать процесс регистрации"""
logger.info(f"Получен запрос на регистрацию от пользователя {callback.from_user.id}")
text = (
"📝 Регистрация в системе\n\n"
"Для участия в розыгрышах необходимо зарегистрироваться.\n\n"
"Шаг 1 из 3: Придумайте никнейм\n\n"
"🎭 Введите ваш никнейм для чата:\n"
"• От 2 до 20 символов\n"
"• Может содержать буквы, цифры, пробелы\n"
"• Это имя будут видеть другие участники"
)
await callback.message.edit_text(
text,
reply_markup=InlineKeyboardMarkup(inline_keyboard=[
[InlineKeyboardButton(text="❌ Отмена", callback_data="back_to_main")]
])
)
await state.set_state(RegistrationStates.waiting_for_nickname)
@router.message(StateFilter(RegistrationStates.waiting_for_nickname))
async def process_nickname(message: Message, state: FSMContext):
"""Обработка никнейма"""
nickname = message.text.strip()
# Валидация никнейма
valid, error_msg = validate_nickname(nickname)
if not valid:
await message.answer(
f"{error_msg}\n\n"
"Попробуйте другой вариант:"
)
return
# Сохраняем никнейм
await state.update_data(nickname=nickname)
await message.answer(
f"✅ Отлично! Ваш никнейм: {nickname}\n\n"
"Шаг 2 из 3: Клубная карта\n\n"
"📝 Введите номер вашей клубной карты:"
)
await state.set_state(RegistrationStates.waiting_for_club_card)
@router.message(StateFilter(RegistrationStates.waiting_for_club_card))
async def process_club_card(message: Message, state: FSMContext):
"""Обработка номера клубной карты"""
club_card_number = message.text.strip()
# Проверяем, не занята ли карта
async with async_session_maker() as session:
existing_user = await RegistrationService.get_user_by_club_card(session, club_card_number)
if existing_user:
await message.answer(
f"❌ Клубная карта {club_card_number} уже зарегистрирована.\n\n"
"Если это ваша карта, обратитесь к администратору."
)
await state.clear()
return
await state.update_data(club_card_number=club_card_number)
await message.answer(
"Шаг 3 из 3: Телефон\n\n"
"📱 Введите ваш номер телефона\n"
"(или отправьте '-' чтобы пропустить):"
)
await state.set_state(RegistrationStates.waiting_for_phone)
@router.message(StateFilter(RegistrationStates.waiting_for_phone))
async def process_phone(message: Message, state: FSMContext):
"""Обработка номера телефона"""
phone_input = message.text.strip()
# Проверяем, не отправил ли пользователь просто "-"
if phone_input == "-":
phone = None
else:
# Валидируем телефон: не должно быть пустых или некорректных значений
if not phone_input:
await message.answer(
"❌ Неверный номер телефона.\n\n"
"Пожалуйста, введите корректный номер или отправьте '-' чтобы пропустить."
)
return
phone = phone_input
data = await state.get_data()
club_card_number = data['club_card_number']
nickname = data.get('nickname')
try:
async with async_session_maker() as session:
user = await RegistrationService.register_user(
session,
telegram_id=message.from_user.id,
club_card_number=club_card_number,
phone=phone
)
# Обновляем никнейм пользователя
if nickname:
user.nickname = nickname
await session.commit()
await session.refresh(user)
text = (
"✅ Регистрация завершена!\n\n"
f"🎭 Никнейм: {user.nickname}\n"
f"🎫 Клубная карта: {user.club_card_number}\n"
f"🔑 Ваш код верификации: **{user.verification_code}**\n\n"
"⚠️ Сохраните этот код! Он понадобится для подтверждения выигрыша.\n\n"
"Теперь вы можете участвовать в розыгрышах!"
)
await message.answer(text, parse_mode="Markdown")
await state.clear()
except ValueError as e:
await message.answer(f"❌ Ошибка регистрации: {str(e)}")
await state.clear()
except Exception as e:
await message.answer(f"❌ Произошла ошибка: {str(e)}")
await state.clear()
@router.message(Command("my_code"))
async def show_verification_code(message: Message):
"""Показать код верификации пользователя"""
async with async_session_maker() as session:
user = await UserService.get_user_by_telegram_id(session, message.from_user.id)
if not user or not user.is_registered:
await message.answer(
"❌ Вы не зарегистрированы в системе.\n\n"
"Для регистрации отправьте /start и выберите 'Регистрация'"
)
return
text = (
"🔑 Ваш код верификации:\n\n"
f"**{user.verification_code}**\n\n"
"Этот код используется для подтверждения выигрыша.\n"
"Сообщите его администратору при получении приза."
)
await message.answer(text, parse_mode="Markdown")
@router.message(Command("my_accounts"))
async def show_user_accounts(message: Message):
"""Показать счета пользователя"""
async with async_session_maker() as session:
user = await UserService.get_user_by_telegram_id(session, message.from_user.id)
if not user or not user.is_registered:
await message.answer("❌ Вы не зарегистрированы в системе")
return
accounts = await AccountService.get_user_accounts(session, user.id)
if not accounts:
await message.answer(
"У вас пока нет привязанных счетов.\n\n"
"Счета добавляются администратором."
)
return
text = f"💳 Ваши счета (Клубная карта: {user.club_card_number}):\n\n"
for i, account in enumerate(accounts, 1):
status = "" if account.is_active else ""
text += f"{i}. {status} {account.account_number}\n"
await message.answer(text)