from aiogram import Router, F from aiogram.filters import CommandStart from aiogram.types import Message, CallbackQuery from aiogram.fsm.context import FSMContext from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy import select from database.models import User from database.connection import async_session_maker from utils.states import RegistrationStates, LoginStates from keyboards.inline import get_auth_menu, get_main_menu, get_back_to_menu_keyboard import bcrypt import logging import re router = Router() logger = logging.getLogger(__name__) @router.message(CommandStart()) async def cmd_start(message: Message, state: FSMContext): """Обработчик команды /start""" await state.clear() telegram_id = message.from_user.id try: async with async_session_maker() as session: result = await session.execute( select(User).where(User.telegram_id == telegram_id) ) user = result.scalar_one_or_none() if user: # Пользователь авторизован await message.answer( f"👋 Добро пожаловать, {user.first_name or user.username}!\n\n" "📚 Выберите действие:", reply_markup=get_main_menu() ) else: # Пользователь не авторизован await message.answer( "👋 Добро пожаловать в бот 'Что почитать'!\n\n" "📚 Здесь вы можете просматривать книги с описаниями, обложками и рейтингами.\n\n" "Пожалуйста, войдите или зарегистрируйтесь:", reply_markup=get_auth_menu() ) except Exception as e: logger.error(f"Ошибка в cmd_start: {e}") await message.answer("❌ Произошла ошибка. Попробуйте позже.") @router.callback_query(F.data == "main_menu") async def show_main_menu(callback: CallbackQuery, state: FSMContext): """Показать главное меню""" await state.clear() telegram_id = callback.from_user.id try: async with async_session_maker() as session: result = await session.execute( select(User).where(User.telegram_id == telegram_id) ) user = result.scalar_one_or_none() if user: await callback.message.edit_text( f"👋 Добро пожаловать, {user.first_name or user.username}!\n\n" "📚 Выберите действие:", reply_markup=get_main_menu() ) else: await callback.message.edit_text( "👋 Добро пожаловать в бот 'Что почитать'!\n\n" "Пожалуйста, войдите или зарегистрируйтесь:", reply_markup=get_auth_menu() ) await callback.answer() except Exception as e: logger.error(f"Ошибка в show_main_menu: {e}") await callback.answer("❌ Ошибка", show_alert=True) # === РЕГИСТРАЦИЯ === @router.callback_query(F.data == "register") async def start_registration(callback: CallbackQuery, state: FSMContext): """Начало регистрации""" await state.set_state(RegistrationStates.waiting_email) await callback.message.edit_text( "📝 Регистрация\n\n" "Введите ваш email:" ) await callback.answer() @router.message(RegistrationStates.waiting_email) async def process_registration_email(message: Message, state: FSMContext): """Обработка email при регистрации""" email = message.text.strip().lower() # Валидация email email_pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$' if not re.match(email_pattern, email): await message.answer("❌ Неверный формат email. Попробуйте снова:") return try: async with async_session_maker() as session: # Проверка уникальности email result = await session.execute( select(User).where(User.email == email) ) existing_user = result.scalar_one_or_none() if existing_user: await message.answer( "❌ Этот email уже зарегистрирован.\n\n" "Используйте другой email или войдите в систему." ) await state.clear() return await state.update_data(email=email) await state.set_state(RegistrationStates.waiting_username) await message.answer("✅ Email принят.\n\nТеперь введите желаемое имя пользователя (username):") except Exception as e: logger.error(f"Ошибка при проверке email: {e}") await message.answer("❌ Произошла ошибка. Попробуйте снова.") await state.clear() @router.message(RegistrationStates.waiting_username) async def process_registration_username(message: Message, state: FSMContext): """Обработка username при регистрации""" username = message.text.strip() # Валидация username if len(username) < 3 or len(username) > 150: await message.answer("❌ Имя пользователя должно быть от 3 до 150 символов. Попробуйте снова:") return if not re.match(r'^[a-zA-Z0-9_]+$', username): await message.answer("❌ Имя пользователя может содержать только буквы, цифры и подчеркивание. Попробуйте снова:") return try: async with async_session_maker() as session: # Проверка уникальности username result = await session.execute( select(User).where(User.username == username) ) existing_user = result.scalar_one_or_none() if existing_user: await message.answer("❌ Это имя пользователя уже занято. Попробуйте другое:") return await state.update_data(username=username) await state.set_state(RegistrationStates.waiting_password) await message.answer( "✅ Имя пользователя принято.\n\n" "Теперь введите пароль (минимум 8 символов):" ) except Exception as e: logger.error(f"Ошибка при проверке username: {e}") await message.answer("❌ Произошла ошибка. Попробуйте снова.") await state.clear() @router.message(RegistrationStates.waiting_password) async def process_registration_password(message: Message, state: FSMContext): """Обработка пароля и завершение регистрации""" password = message.text.strip() # Валидация пароля if len(password) < 8: await message.answer("❌ Пароль должен содержать минимум 8 символов. Попробуйте снова:") return try: # Удаляем сообщение с паролем из чата await message.delete() except: pass try: data = await state.get_data() email = data['email'] username = data['username'] # Хешируем пароль hashed_password = bcrypt.hashpw(password.encode('utf-8'), bcrypt.gensalt()).decode('utf-8') async with async_session_maker() as session: # Создаем нового пользователя new_user = User( email=email, username=username, password=hashed_password, telegram_id=message.from_user.id, first_name=message.from_user.first_name or "", last_name=message.from_user.last_name or "", is_active=True ) session.add(new_user) await session.commit() await message.answer( f"✅ Регистрация успешно завершена!\n\n" f"👤 Ваш username: {username}\n" f"📧 Email: {email}\n\n" "Добро пожаловать в бот 'Что почитать'!", reply_markup=get_main_menu() ) await state.clear() except Exception as e: logger.error(f"Ошибка при создании пользователя: {e}") await message.answer("❌ Произошла ошибка при регистрации. Попробуйте снова.") await state.clear() # === ВХОД === @router.callback_query(F.data == "login") async def start_login(callback: CallbackQuery, state: FSMContext): """Начало входа""" await state.set_state(LoginStates.waiting_email) await callback.message.edit_text( "🔐 Вход в систему\n\n" "Введите ваш email:" ) await callback.answer() @router.message(LoginStates.waiting_email) async def process_login_email(message: Message, state: FSMContext): """Обработка email при входе""" email = message.text.strip().lower() await state.update_data(email=email) await state.set_state(LoginStates.waiting_password) await message.answer("Теперь введите ваш пароль:") @router.message(LoginStates.waiting_password) async def process_login_password(message: Message, state: FSMContext): """Обработка пароля и завершение входа""" password = message.text.strip() try: # Удаляем сообщение с паролем await message.delete() except: pass try: data = await state.get_data() email = data['email'] async with async_session_maker() as session: result = await session.execute( select(User).where(User.email == email) ) user = result.scalar_one_or_none() if not user: await message.answer("❌ Пользователь с таким email не найден.") await state.clear() return # Проверяем пароль if not bcrypt.checkpw(password.encode('utf-8'), user.password.encode('utf-8')): await message.answer("❌ Неверный пароль.") await state.clear() return # Проверяем, не привязан ли уже другой Telegram аккаунт if user.telegram_id and user.telegram_id != message.from_user.id: await message.answer("❌ Этот аккаунт уже привязан к другому Telegram профилю.") await state.clear() return # Обновляем telegram_id user.telegram_id = message.from_user.id await session.commit() await message.answer( f"✅ Вход выполнен успешно!\n\n" f"👋 Добро пожаловать, {user.first_name or user.username}!", reply_markup=get_main_menu() ) await state.clear() except Exception as e: logger.error(f"Ошибка при входе: {e}") await message.answer("❌ Произошла ошибка при входе. Попробуйте снова.") await state.clear() # === ПРОФИЛЬ === @router.callback_query(F.data == "profile") async def show_profile(callback: CallbackQuery): """Показать профиль пользователя""" telegram_id = callback.from_user.id try: async with async_session_maker() as session: result = await session.execute( select(User).where(User.telegram_id == telegram_id) ) user = result.scalar_one_or_none() if not user: await callback.answer("⛔ Пользователь не найден", show_alert=True) return profile_text = ( f"👤 Ваш профиль\n\n" f"🆔 Username: {user.username}\n" f"📧 Email: {user.email}\n" f"👤 Имя: {user.first_name or 'Не указано'}\n" f"📅 Дата регистрации: {user.date_joined.strftime('%d.%m.%Y')}" ) await callback.message.edit_text( profile_text, reply_markup=get_back_to_menu_keyboard() ) await callback.answer() except Exception as e: logger.error(f"Ошибка в show_profile: {e}") await callback.answer("❌ Ошибка", show_alert=True)