from aiogram import Router, F from aiogram.types import CallbackQuery, InputMediaPhoto from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy import select, text, delete from database.models import Book, Favorite, User from database.connection import async_session_maker from keyboards.inline import get_book_keyboard, get_main_menu from handlers.books import format_book_message, is_book_favorite import logging router = Router() logger = logging.getLogger(__name__) @router.callback_query(F.data.startswith("add_fav:")) async def add_to_favorites(callback: CallbackQuery): """Добавить книгу в избранное""" book_id = int(callback.data.split(":")[1]) 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 # Проверяем, существует ли книга result = await session.execute( select(Book).where(Book.id == book_id, Book.deleted_at.is_(None)) ) book = result.scalar_one_or_none() if not book: await callback.answer("❌ Книга не найдена", show_alert=True) return # Проверяем, не добавлена ли уже result = await session.execute( select(Favorite).where( Favorite.user_id == user.id, Favorite.book_id == book_id ) ) existing_favorite = result.scalar_one_or_none() if existing_favorite: await callback.answer("ℹ️ Книга уже в избранном", show_alert=True) return # Добавляем в избранное new_favorite = Favorite( user_id=user.id, book_id=book_id ) session.add(new_favorite) await session.commit() # Обновляем клавиатуру try: current_markup = callback.message.reply_markup # Извлекаем номер страницы из существующей клавиатуры page = 0 total_pages = 1 if current_markup and current_markup.inline_keyboard: for row in current_markup.inline_keyboard: for button in row: if button.callback_data and "books_page:" in button.callback_data: parts = button.callback_data.split(":") if len(parts) > 1: page = int(parts[1]) elif button.text and "/" in button.text: try: pages_info = button.text.split("/") page = int(pages_info[0]) - 1 total_pages = int(pages_info[1]) except: pass new_keyboard = get_book_keyboard(book_id, True, page, total_pages) await callback.message.edit_reply_markup(reply_markup=new_keyboard) except Exception as e: logger.warning(f"Не удалось обновить клавиатуру: {e}") await callback.answer("✅ Книга добавлена в избранное!") except Exception as e: logger.error(f"Ошибка при добавлении в избранное: {e}") await callback.answer("❌ Произошла ошибка", show_alert=True) @router.callback_query(F.data.startswith("remove_fav:")) async def remove_from_favorites(callback: CallbackQuery): """Удалить книгу из избранного""" book_id = int(callback.data.split(":")[1]) 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 # Удаляем из избранного await session.execute( delete(Favorite).where( Favorite.user_id == user.id, Favorite.book_id == book_id ) ) await session.commit() # Обновляем клавиатуру try: current_markup = callback.message.reply_markup page = 0 total_pages = 1 if current_markup and current_markup.inline_keyboard: for row in current_markup.inline_keyboard: for button in row: if button.callback_data and "books_page:" in button.callback_data: parts = button.callback_data.split(":") if len(parts) > 1: page = int(parts[1]) elif button.text and "/" in button.text: try: pages_info = button.text.split("/") page = int(pages_info[0]) - 1 total_pages = int(pages_info[1]) except: pass new_keyboard = get_book_keyboard(book_id, False, page, total_pages) await callback.message.edit_reply_markup(reply_markup=new_keyboard) except Exception as e: logger.warning(f"Не удалось обновить клавиатуру: {e}") await callback.answer("❌ Книга удалена из избранного") except Exception as e: logger.error(f"Ошибка при удалении из избранного: {e}") await callback.answer("❌ Произошла ошибка", show_alert=True) @router.callback_query(F.data == "favorites") async def show_favorites(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 # Получаем избранные книги с полной информацией query = text(""" SELECT b.id, b.title, b.description, b.cover_url, b.average_rating, b.rating_count, STRING_AGG(DISTINCT a.name, ', ') as authors, STRING_AGG(DISTINCT g.name, ', ') as genres FROM books_favorite f JOIN books_book b ON f.book_id = b.id LEFT JOIN books_book_authors ba ON b.id = ba.book_id LEFT JOIN books_author a ON ba.author_id = a.id LEFT JOIN books_book_genres bg ON b.id = bg.book_id LEFT JOIN books_genre g ON bg.genre_id = g.id WHERE f.user_id = :user_id AND b.deleted_at IS NULL GROUP BY b.id, f.created_at ORDER BY f.created_at DESC LIMIT 1 """) result = await session.execute(query, {"user_id": user.id}) favorites = result.mappings().all() if not favorites: await callback.message.edit_text( "⭐ У вас пока нет избранных книг.\n\n" "Добавьте книги в избранное, чтобы быстро находить их здесь!", reply_markup=get_main_menu() ) await callback.answer() return # Показываем первую избранную книгу book = favorites[0] message_text = format_book_message(book, is_favorite=True) # Создаем клавиатуру (всегда с кнопкой удаления из избранного) keyboard = get_book_keyboard(book['id'], True, 0, 1) # Отправляем с картинкой если есть if book['cover_url']: try: if callback.message.photo: await callback.message.edit_media( media=InputMediaPhoto( media=book['cover_url'], caption=message_text, parse_mode="HTML" ), reply_markup=keyboard ) else: await callback.message.delete() await callback.message.answer_photo( photo=book['cover_url'], caption=message_text, parse_mode="HTML", reply_markup=keyboard ) except Exception as e: logger.warning(f"Не удалось загрузить обложку: {e}") await callback.message.edit_text( "🖼 [Обложка недоступна]\n\n" + message_text, parse_mode="HTML", reply_markup=keyboard ) else: await callback.message.edit_text( message_text, parse_mode="HTML", reply_markup=keyboard ) await callback.answer() except Exception as e: logger.error(f"Ошибка в show_favorites: {e}") await callback.answer("❌ Произошла ошибка", show_alert=True)