Some checks failed
continuous-integration/drone/pr Build is failing
- Удалена кнопка 'Розыгрыши' из главной клавиатуры - Переименована кнопка 'Мои счета' -> 'Мои логины' - Показывается ник пользователя вместо TG_ID в чате - Добавлена кнопка 'Главная' на все клавиатуры - Проверка регистрации и сокрытие кнопки регистрации - Валидация номера телефона при регистрации (проверка на символ '-')
249 lines
10 KiB
Python
249 lines
10 KiB
Python
"""Обработчики для регистрации пользователей"""
|
||
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)
|