Some checks failed
continuous-integration/drone/pr Build is failing
- Replace Markdown double asterisks with HTML tags - Change parse_mode from Markdown to HTML for registration confirmation - Use <b> tags for bold text in registration_completed message - Use <code> tags for verification code display - Fixes 'Can't find end of the entity' error in Telegram API - Remove unused JSON export files
306 lines
14 KiB
Python
306 lines
14 KiB
Python
"""Обработчики для регистрации пользователей"""
|
||
from aiogram import Router, F
|
||
from aiogram.types import Message, CallbackQuery, InlineKeyboardButton, InlineKeyboardMarkup
|
||
from aiogram.filters import Command, StateFilter
|
||
from sqlalchemy import select
|
||
from sqlalchemy.orm import selectinload
|
||
|
||
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
|
||
from src.core.models import Participation, Winner, Lottery
|
||
|
||
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"🔑 Ваш код верификации: <b>{user.verification_code}</b>\n\n"
|
||
"⚠️ Сохраните этот код! Он понадобится для подтверждения выигрыша.\n\n"
|
||
"Теперь вы можете участвовать в розыгрышах!"
|
||
)
|
||
|
||
await message.answer(text, parse_mode="HTML")
|
||
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"<code>{user.verification_code}</code>\n\n"
|
||
"Этот код используется для подтверждения выигрыша.\n"
|
||
"Сообщите его администратору при получении приза."
|
||
)
|
||
|
||
await message.answer(text, parse_mode="HTML")
|
||
|
||
|
||
@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"📱 <b>Ваши логины</b> (Клубная карта: {user.club_card_number})\n\n"
|
||
|
||
for i, account in enumerate(accounts, 1):
|
||
# Получаем participations для этого account с загруженными данными о lottery
|
||
participations = await session.execute(
|
||
select(Participation)
|
||
.where(Participation.account_id == account.id)
|
||
.options(selectinload(Participation.lottery))
|
||
)
|
||
participations = participations.scalars().all()
|
||
|
||
# Определяем статус логина
|
||
active_participations = [p for p in participations if not p.lottery.is_completed]
|
||
closed_participations = [p for p in participations if p.lottery.is_completed]
|
||
|
||
# Основная информация о логине
|
||
status_icon = "✅" if account.is_active and active_participations else "⏸️"
|
||
text += f"{i}. {status_icon} <b>{account.account_number}</b>\n"
|
||
|
||
if active_participations:
|
||
text += " 🎲 <b>Активные розыгрыши:</b>\n"
|
||
for p in active_participations[:5]: # Показываем не более 5
|
||
status = "🟢"
|
||
text += f" {status} {p.lottery.title}\n"
|
||
if len(active_participations) > 5:
|
||
text += f" ... и еще {len(active_participations) - 5}\n"
|
||
|
||
if closed_participations:
|
||
text += " 🏁 <b>Завершенные розыгрыши:</b>\n"
|
||
for p in closed_participations[:3]: # Показываем не более 3
|
||
# Проверяем, выиграл ли в этом розыгрыше
|
||
winner_result = await session.execute(
|
||
select(Winner)
|
||
.where(
|
||
(Winner.lottery_id == p.lottery_id) &
|
||
(Winner.account_number == account.account_number)
|
||
)
|
||
)
|
||
winner = winner_result.scalar_one_or_none()
|
||
|
||
if winner:
|
||
text += f" 🏆 {p.lottery.title} - <b>ВЫИГРАЛ!</b> ({winner.place} место)\n"
|
||
else:
|
||
text += f" ✗ {p.lottery.title}\n"
|
||
|
||
if len(closed_participations) > 3:
|
||
text += f" ... и еще {len(closed_participations) - 3} закрытых\n"
|
||
|
||
if not participations:
|
||
text += " ℹ️ В розыгрышах не участвовал\n"
|
||
|
||
text += "\n"
|
||
|
||
# Добавляем примечание о неактивных логинах
|
||
if any(not acc.is_active for acc in accounts):
|
||
text += "⏸️ - Логин не участвует в новых розыгрышах\n"
|
||
text += "✅ - Логин активен и может участвовать\n\n"
|
||
|
||
text += "💡 <b>Заметка:</b> Логины, участвовавшие в закрытых розыгрышах, не добавляются в новые розыгрыши."
|
||
|
||
await message.answer(text, parse_mode="HTML")
|