initial commit

This commit is contained in:
2025-12-25 15:25:46 +03:00
commit 58827ac124
20 changed files with 1854 additions and 0 deletions

252
handlers/favorites.py Normal file
View File

@@ -0,0 +1,252 @@
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)