253 lines
11 KiB
Python
253 lines
11 KiB
Python
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)
|
|
|