Files
pyproject2/handlers/favorites.py
2025-12-25 15:25:46 +03:00

253 lines
11 KiB
Python
Raw Permalink Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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)