reminder-bot/bot/handlers/callbacks.py

251 lines
8.8 KiB
Python
Raw Normal View History

2025-12-19 11:19:54 +01:00
"""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()
2026-02-17 10:52:46 +01:00
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()
2025-12-19 11:19:54 +01:00
@router.callback_query(ReminderActionCallback.filter(F.action == "done"))
async def handle_done(
callback: CallbackQuery,
callback_data: ReminderActionCallback,
session: AsyncSession,
) -> None:
2026-02-17 10:52:46 +01:00
"""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
2025-12-19 11:19:54 +01:00
2026-02-17 10:52:46 +01:00
reminder = await reminders_service.mark_as_done(session, callback_data.reminder_id)
2025-12-19 11:19:54 +01:00
if not reminder:
await callback.answer("Напоминание не найдено", show_alert=True)
return
2026-02-17 10:52:46 +01:00
next_run_str = get_time_service().format_next_run(reminder.next_run_at)
2025-12-19 11:19:54 +01:00
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,
2026-02-17 10:52:46 +01:00
session: AsyncSession,
2025-12-19 11:19:54 +01:00
) -> None:
2026-02-17 10:52:46 +01:00
"""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
2025-12-19 11:19:54 +01:00
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:
2026-02-17 10:52:46 +01:00
"""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
2025-12-19 11:19:54 +01:00
reminder = await reminders_service.snooze(
session, callback_data.reminder_id, callback_data.hours
)
if not reminder:
await callback.answer("Напоминание не найдено", show_alert=True)
return
2026-02-17 10:52:46 +01:00
next_run_str = get_time_service().format_next_run(reminder.next_run_at)
2025-12-19 11:19:54 +01:00
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:
2026-02-17 10:52:46 +01:00
"""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
2025-12-19 11:19:54 +01:00
2026-02-17 10:52:46 +01:00
reminder = await reminders_service.pause_reminder(session, callback_data.reminder_id)
2025-12-19 11:19:54 +01:00
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:
2026-02-17 10:52:46 +01:00
"""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
2025-12-19 11:19:54 +01:00
2026-02-17 10:52:46 +01:00
reminder = await reminders_service.resume_reminder(session, callback_data.reminder_id)
2025-12-19 11:19:54 +01:00
if not reminder:
await callback.answer("Напоминание не найдено", show_alert=True)
return
2026-02-17 10:52:46 +01:00
next_run_str = get_time_service().format_next_run(reminder.next_run_at)
2025-12-19 11:19:54 +01:00
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,
2026-02-17 10:52:46 +01:00
session: AsyncSession,
2025-12-19 11:19:54 +01:00
) -> None:
2026-02-17 10:52:46 +01:00
"""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
2025-12-19 11:19:54 +01:00
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:
2026-02-17 10:52:46 +01:00
"""Execute reminder deletion after confirmation."""
2025-12-19 11:19:54 +01:00
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:
2026-02-17 10:52:46 +01:00
"""Handle settings button (placeholder)."""
2025-12-19 11:19:54 +01:00
await message.answer(
"⚙️ Настройки\n\n"
"Функционал настроек будет добавлен в следующих версиях.\n\n"
"Пока доступны основные функции:\n"
"• Создание напоминаний\n"
"• Просмотр и управление\n"
"• Редактирование параметров",
reply_markup=get_main_menu_keyboard(),
)