251 lines
8.8 KiB
Python
251 lines
8.8 KiB
Python
"""Handlers for inline button callbacks (done, snooze, pause, delete)."""
|
||
|
||
from aiogram import Router, F
|
||
from aiogram.types import CallbackQuery, Message
|
||
from sqlalchemy.ext.asyncio import AsyncSession
|
||
|
||
from bot.keyboards.reminders import (
|
||
ReminderActionCallback,
|
||
SnoozeDelayCallback,
|
||
ConfirmCallback,
|
||
get_snooze_delay_keyboard,
|
||
get_confirmation_keyboard,
|
||
)
|
||
from bot.keyboards.main_menu import get_main_menu_keyboard
|
||
from bot.services.reminders_service import RemindersService
|
||
from bot.services.time_service import get_time_service
|
||
from bot.logging_config import get_logger
|
||
|
||
logger = get_logger(__name__)
|
||
|
||
router = Router(name="callbacks")
|
||
reminders_service = RemindersService()
|
||
|
||
|
||
async def _verify_owner(
|
||
session: AsyncSession,
|
||
reminder_id: int,
|
||
tg_user_id: int,
|
||
) -> bool:
|
||
"""Fetch reminder and verify it belongs to tg_user_id."""
|
||
from sqlalchemy.orm import selectinload
|
||
from sqlalchemy import select
|
||
from bot.db.models import Reminder
|
||
|
||
result = await session.execute(
|
||
select(Reminder)
|
||
.options(selectinload(Reminder.user))
|
||
.where(Reminder.id == reminder_id)
|
||
)
|
||
reminder = result.scalar_one_or_none()
|
||
|
||
if not reminder:
|
||
return False
|
||
if reminder.user.tg_user_id != tg_user_id:
|
||
return False
|
||
return True
|
||
|
||
|
||
@router.callback_query(F.data == "noop")
|
||
async def handle_noop(callback: CallbackQuery) -> None:
|
||
"""Handle noop callback (page counter button)."""
|
||
await callback.answer()
|
||
|
||
|
||
@router.callback_query(ReminderActionCallback.filter(F.action == "done"))
|
||
async def handle_done(
|
||
callback: CallbackQuery,
|
||
callback_data: ReminderActionCallback,
|
||
session: AsyncSession,
|
||
) -> None:
|
||
"""Handle 'Done' button - mark reminder as completed."""
|
||
owner_check = await _verify_owner(session, callback_data.reminder_id, callback.from_user.id)
|
||
if not owner_check:
|
||
await callback.answer("Напоминание не найдено", show_alert=True)
|
||
return
|
||
|
||
reminder = await reminders_service.mark_as_done(session, callback_data.reminder_id)
|
||
if not reminder:
|
||
await callback.answer("Напоминание не найдено", show_alert=True)
|
||
return
|
||
|
||
next_run_str = get_time_service().format_next_run(reminder.next_run_at)
|
||
|
||
await callback.message.edit_text(
|
||
f"✅ Отлично! Отметил как выполненное.\n\n"
|
||
f"Следующее напоминание будет {next_run_str}."
|
||
)
|
||
await callback.answer("Выполнено!")
|
||
|
||
logger.info(f"Reminder {reminder.id} marked as done by user {callback.from_user.id}")
|
||
|
||
|
||
@router.callback_query(ReminderActionCallback.filter(F.action == "snooze"))
|
||
async def handle_snooze_select(
|
||
callback: CallbackQuery,
|
||
callback_data: ReminderActionCallback,
|
||
session: AsyncSession,
|
||
) -> None:
|
||
"""Handle 'Snooze' button - show delay options."""
|
||
owner_check = await _verify_owner(session, callback_data.reminder_id, callback.from_user.id)
|
||
if not owner_check:
|
||
await callback.answer("Напоминание не найдено", show_alert=True)
|
||
return
|
||
|
||
await callback.message.edit_text(
|
||
"На сколько отложить напоминание?",
|
||
reply_markup=get_snooze_delay_keyboard(callback_data.reminder_id),
|
||
)
|
||
await callback.answer()
|
||
|
||
|
||
@router.callback_query(SnoozeDelayCallback.filter())
|
||
async def handle_snooze_delay(
|
||
callback: CallbackQuery,
|
||
callback_data: SnoozeDelayCallback,
|
||
session: AsyncSession,
|
||
) -> None:
|
||
"""Handle snooze delay selection."""
|
||
owner_check = await _verify_owner(session, callback_data.reminder_id, callback.from_user.id)
|
||
if not owner_check:
|
||
await callback.answer("Напоминание не найдено", show_alert=True)
|
||
return
|
||
|
||
reminder = await reminders_service.snooze(
|
||
session, callback_data.reminder_id, callback_data.hours
|
||
)
|
||
if not reminder:
|
||
await callback.answer("Напоминание не найдено", show_alert=True)
|
||
return
|
||
|
||
next_run_str = get_time_service().format_next_run(reminder.next_run_at)
|
||
|
||
await callback.message.edit_text(
|
||
f"⏰ Напоминание отложено.\n\n"
|
||
f"Напомню {next_run_str}."
|
||
)
|
||
await callback.answer("Отложено!")
|
||
|
||
logger.info(
|
||
f"Reminder {reminder.id} snoozed for {callback_data.hours}h "
|
||
f"by user {callback.from_user.id}"
|
||
)
|
||
|
||
|
||
@router.callback_query(ReminderActionCallback.filter(F.action == "pause"))
|
||
async def handle_pause(
|
||
callback: CallbackQuery,
|
||
callback_data: ReminderActionCallback,
|
||
session: AsyncSession,
|
||
) -> None:
|
||
"""Handle 'Pause' button - deactivate reminder."""
|
||
owner_check = await _verify_owner(session, callback_data.reminder_id, callback.from_user.id)
|
||
if not owner_check:
|
||
await callback.answer("Напоминание не найдено", show_alert=True)
|
||
return
|
||
|
||
reminder = await reminders_service.pause_reminder(session, callback_data.reminder_id)
|
||
if not reminder:
|
||
await callback.answer("Напоминание не найдено", show_alert=True)
|
||
return
|
||
|
||
await callback.message.edit_text(
|
||
"⏸ Напоминание поставлено на паузу.\n\n"
|
||
"Можешь возобновить его в любое время через «📋 Мои напоминания»."
|
||
)
|
||
await callback.answer("Поставлено на паузу")
|
||
|
||
logger.info(f"Reminder {reminder.id} paused by user {callback.from_user.id}")
|
||
|
||
|
||
@router.callback_query(ReminderActionCallback.filter(F.action == "resume"))
|
||
async def handle_resume(
|
||
callback: CallbackQuery,
|
||
callback_data: ReminderActionCallback,
|
||
session: AsyncSession,
|
||
) -> None:
|
||
"""Handle 'Resume' button - reactivate reminder."""
|
||
owner_check = await _verify_owner(session, callback_data.reminder_id, callback.from_user.id)
|
||
if not owner_check:
|
||
await callback.answer("Напоминание не найдено", show_alert=True)
|
||
return
|
||
|
||
reminder = await reminders_service.resume_reminder(session, callback_data.reminder_id)
|
||
if not reminder:
|
||
await callback.answer("Напоминание не найдено", show_alert=True)
|
||
return
|
||
|
||
next_run_str = get_time_service().format_next_run(reminder.next_run_at)
|
||
|
||
await callback.message.edit_text(
|
||
f"▶️ Напоминание возобновлено!\n\n"
|
||
f"Следующее напоминание будет {next_run_str}."
|
||
)
|
||
await callback.answer("Возобновлено!")
|
||
|
||
logger.info(f"Reminder {reminder.id} resumed by user {callback.from_user.id}")
|
||
|
||
|
||
@router.callback_query(ReminderActionCallback.filter(F.action == "delete"))
|
||
async def handle_delete_confirm(
|
||
callback: CallbackQuery,
|
||
callback_data: ReminderActionCallback,
|
||
session: AsyncSession,
|
||
) -> None:
|
||
"""Handle 'Delete' button - ask for confirmation."""
|
||
owner_check = await _verify_owner(session, callback_data.reminder_id, callback.from_user.id)
|
||
if not owner_check:
|
||
await callback.answer("Напоминание не найдено", show_alert=True)
|
||
return
|
||
|
||
await callback.message.edit_text(
|
||
"Точно удалить напоминание?",
|
||
reply_markup=get_confirmation_keyboard(
|
||
entity="delete",
|
||
entity_id=callback_data.reminder_id
|
||
),
|
||
)
|
||
await callback.answer()
|
||
|
||
|
||
@router.callback_query(ConfirmCallback.filter(F.entity == "delete"))
|
||
async def handle_delete_execute(
|
||
callback: CallbackQuery,
|
||
callback_data: ConfirmCallback,
|
||
session: AsyncSession,
|
||
) -> None:
|
||
"""Execute reminder deletion after confirmation."""
|
||
if callback_data.action == "no":
|
||
await callback.message.edit_text("Удаление отменено.")
|
||
await callback.answer()
|
||
return
|
||
|
||
deleted = await reminders_service.delete_reminder_by_id(
|
||
session, callback_data.entity_id
|
||
)
|
||
|
||
if not deleted:
|
||
await callback.answer("Напоминание не найдено", show_alert=True)
|
||
return
|
||
|
||
await callback.message.edit_text("🗑 Напоминание удалено.")
|
||
await callback.answer("Удалено")
|
||
|
||
logger.info(
|
||
f"Reminder {callback_data.entity_id} deleted by user {callback.from_user.id}"
|
||
)
|
||
|
||
|
||
@router.message(F.text == "⚙️ Настройки")
|
||
async def handle_settings(message: Message) -> None:
|
||
"""Handle settings button (placeholder)."""
|
||
await message.answer(
|
||
"⚙️ Настройки\n\n"
|
||
"Функционал настроек будет добавлен в следующих версиях.\n\n"
|
||
"Пока доступны основные функции:\n"
|
||
"• Создание напоминаний\n"
|
||
"• Просмотр и управление\n"
|
||
"• Редактирование параметров",
|
||
reply_markup=get_main_menu_keyboard(),
|
||
)
|