reminder-bot/bot/handlers/reminders_manage.py

439 lines
13 KiB
Python
Raw Permalink Normal View History

2025-12-19 11:19:54 +01:00
"""Handlers for managing reminders (view, edit, delete)."""
from aiogram import Router, F
from aiogram.types import Message, CallbackQuery
from aiogram.fsm.context import FSMContext
from sqlalchemy.ext.asyncio import AsyncSession
from bot.states.reminder_states import EditReminderStates
from bot.keyboards.pagination import get_reminders_list_keyboard, PaginationCallback
from bot.keyboards.reminders import (
get_reminder_details_keyboard,
get_edit_menu_keyboard,
get_interval_selection_keyboard,
get_confirmation_keyboard,
ReminderActionCallback,
ReminderEditCallback,
ReminderIntervalCallback,
ConfirmCallback,
)
from bot.keyboards.main_menu import get_main_menu_keyboard
from bot.services.user_service import UserService
from bot.services.reminders_service import RemindersService
from bot.utils.formatting import format_datetime, format_interval_days
from bot.utils.validators import validate_time_format, validate_days_interval
from bot.logging_config import get_logger
logger = get_logger(__name__)
router = Router(name="reminders_manage")
reminders_service = RemindersService()
@router.message(F.text == "📋 Мои напоминания")
async def show_reminders_list(
message: Message,
session: AsyncSession,
) -> None:
"""
Show user's reminders list.
Args:
message: Telegram message
session: Database session
"""
user = await UserService.ensure_user_exists(session, message.from_user)
reminders = await reminders_service.get_user_all_reminders(session, user.id)
if not reminders:
await message.answer(
"У тебя пока нет напоминаний.\n\n"
"Нажми «➕ Новое напоминание», чтобы создать первое!",
reply_markup=get_main_menu_keyboard(),
)
return
await message.answer(
f"📋 Твои напоминания ({len(reminders)}):",
reply_markup=get_reminders_list_keyboard(reminders, page=0),
)
@router.callback_query(PaginationCallback.filter(F.action.in_(["prev", "next"])))
async def paginate_reminders(
callback: CallbackQuery,
callback_data: PaginationCallback,
session: AsyncSession,
) -> None:
"""
Handle pagination for reminders list.
Args:
callback: Callback query
callback_data: Parsed callback data
session: Database session
"""
user = await UserService.ensure_user_exists(session, callback.from_user)
reminders = await reminders_service.get_user_all_reminders(session, user.id)
await callback.message.edit_reply_markup(
reply_markup=get_reminders_list_keyboard(reminders, page=callback_data.page)
)
await callback.answer()
@router.callback_query(PaginationCallback.filter(F.action == "select"))
async def show_reminder_details(
callback: CallbackQuery,
callback_data: PaginationCallback,
session: AsyncSession,
) -> None:
"""
Show reminder details.
Args:
callback: Callback query
callback_data: Parsed callback data
session: Database session
"""
reminder = await reminders_service.get_reminder(session, callback_data.reminder_id)
if not reminder:
await callback.answer("Напоминание не найдено", show_alert=True)
return
status = "Активно ✅" if reminder.is_active else "На паузе ⏸"
last_done = (
format_datetime(reminder.last_done_at)
if reminder.last_done_at
else "Ещё не выполнялось"
)
details_text = (
f"📝 <b>Напоминание #{reminder.id}</b>\n\n"
f"<b>Текст:</b> {reminder.text}\n\n"
f"<b>Периодичность:</b> {format_interval_days(reminder.days_interval)}\n"
f"<b>Время:</b> {reminder.time_of_day.strftime('%H:%M')}\n"
f"<b>Статус:</b> {status}\n\n"
f"<b>Следующее напоминание:</b> {format_datetime(reminder.next_run_at)}\n"
f"<b>Последнее выполнение:</b> {last_done}\n"
f"<b>Выполнено раз:</b> {reminder.total_done_count}"
)
await callback.message.edit_text(
details_text,
reply_markup=get_reminder_details_keyboard(reminder.id, reminder.is_active),
parse_mode="HTML",
)
await callback.answer()
# ==================== Edit Reminder Flow ====================
@router.callback_query(ReminderActionCallback.filter(F.action == "edit"))
async def start_edit_reminder(
callback: CallbackQuery,
callback_data: ReminderActionCallback,
state: FSMContext,
) -> None:
"""
Start editing reminder.
Args:
callback: Callback query
callback_data: Parsed callback data
state: FSM state context
"""
await state.update_data(reminder_id=callback_data.reminder_id)
await state.set_state(EditReminderStates.selecting_field)
await callback.message.edit_text(
"Что изменить?",
reply_markup=get_edit_menu_keyboard(callback_data.reminder_id),
)
await callback.answer()
@router.callback_query(
EditReminderStates.selecting_field,
ReminderEditCallback.filter(F.action == "back")
)
async def cancel_edit(
callback: CallbackQuery,
callback_data: ReminderEditCallback,
state: FSMContext,
session: AsyncSession,
) -> None:
"""
Cancel editing and return to reminder details.
Args:
callback: Callback query
callback_data: Parsed callback data
state: FSM state context
session: Database session
"""
await state.clear()
# Show reminder details again
reminder = await reminders_service.get_reminder(session, callback_data.reminder_id)
if not reminder:
await callback.answer("Напоминание не найдено", show_alert=True)
return
status = "Активно ✅" if reminder.is_active else "На паузе ⏸"
last_done = (
format_datetime(reminder.last_done_at)
if reminder.last_done_at
else "Ещё не выполнялось"
)
details_text = (
f"📝 <b>Напоминание #{reminder.id}</b>\n\n"
f"<b>Текст:</b> {reminder.text}\n\n"
f"<b>Периодичность:</b> {format_interval_days(reminder.days_interval)}\n"
f"<b>Время:</b> {reminder.time_of_day.strftime('%H:%M')}\n"
f"<b>Статус:</b> {status}\n\n"
f"<b>Следующее напоминание:</b> {format_datetime(reminder.next_run_at)}\n"
f"<b>Последнее выполнение:</b> {last_done}"
)
await callback.message.edit_text(
details_text,
reply_markup=get_reminder_details_keyboard(reminder.id, reminder.is_active),
parse_mode="HTML",
)
await callback.answer()
@router.callback_query(
EditReminderStates.selecting_field,
ReminderEditCallback.filter(F.action == "text")
)
async def edit_text_start(
callback: CallbackQuery,
callback_data: ReminderEditCallback,
state: FSMContext,
) -> None:
"""
Start editing reminder text.
Args:
callback: Callback query
callback_data: Parsed callback data
state: FSM state context
"""
await state.set_state(EditReminderStates.editing_text)
await callback.message.edit_text("Введи новый текст напоминания:")
await callback.answer()
@router.message(EditReminderStates.editing_text)
async def edit_text_process(
message: Message,
state: FSMContext,
session: AsyncSession,
) -> None:
"""
Process new reminder text.
Args:
message: Telegram message
state: FSM state context
session: Database session
"""
text = message.text.strip()
if not text or len(text) > 1000:
await message.answer("Текст должен быть от 1 до 1000 символов. Попробуй ещё раз:")
return
data = await state.get_data()
reminder_id = data["reminder_id"]
reminder = await reminders_service.update_reminder_text(session, reminder_id, text)
if not reminder:
await message.answer("Напоминание не найдено")
await state.clear()
return
await state.clear()
await message.answer(
f"✅ Текст обновлён!\n\nНовый текст: {text}",
reply_markup=get_main_menu_keyboard(),
)
@router.callback_query(
EditReminderStates.selecting_field,
ReminderEditCallback.filter(F.action == "interval")
)
async def edit_interval_start(
callback: CallbackQuery,
callback_data: ReminderEditCallback,
state: FSMContext,
) -> None:
"""
Start editing reminder interval.
Args:
callback: Callback query
callback_data: Parsed callback data
state: FSM state context
"""
await state.set_state(EditReminderStates.editing_interval)
await callback.message.edit_text(
"Выбери новый период или введи количество дней:",
reply_markup=get_interval_selection_keyboard(),
)
await callback.answer()
@router.callback_query(
EditReminderStates.editing_interval,
ReminderIntervalCallback.filter()
)
async def edit_interval_button(
callback: CallbackQuery,
callback_data: ReminderIntervalCallback,
state: FSMContext,
session: AsyncSession,
) -> None:
"""
Process interval selection via button.
Args:
callback: Callback query
callback_data: Parsed callback data
state: FSM state context
session: Database session
"""
if callback_data.days == 0:
await callback.message.edit_text("Введи количество дней (целое положительное число):")
await callback.answer()
return
data = await state.get_data()
reminder_id = data["reminder_id"]
reminder = await reminders_service.update_reminder_interval(
session, reminder_id, callback_data.days
)
if not reminder:
await callback.answer("Напоминание не найдено", show_alert=True)
await state.clear()
return
await state.clear()
await callback.message.edit_text(
f"✅ Периодичность обновлена!\n\nТеперь: {format_interval_days(callback_data.days)}"
)
await callback.message.answer(
"Выбери действие из меню:",
reply_markup=get_main_menu_keyboard(),
)
await callback.answer("Обновлено!")
@router.message(EditReminderStates.editing_interval)
async def edit_interval_text(
message: Message,
state: FSMContext,
session: AsyncSession,
) -> None:
"""
Process interval input as text.
Args:
message: Telegram message
state: FSM state context
session: Database session
"""
days = validate_days_interval(message.text)
if days is None:
await message.answer("Некорректное значение. Введи целое положительное число дней:")
return
data = await state.get_data()
reminder_id = data["reminder_id"]
reminder = await reminders_service.update_reminder_interval(session, reminder_id, days)
if not reminder:
await message.answer("Напоминание не найдено")
await state.clear()
return
await state.clear()
await message.answer(
f"✅ Периодичность обновлена!\n\nТеперь: {format_interval_days(days)}",
reply_markup=get_main_menu_keyboard(),
)
@router.callback_query(
EditReminderStates.selecting_field,
ReminderEditCallback.filter(F.action == "time")
)
async def edit_time_start(
callback: CallbackQuery,
callback_data: ReminderEditCallback,
state: FSMContext,
) -> None:
"""
Start editing reminder time.
Args:
callback: Callback query
callback_data: Parsed callback data
state: FSM state context
"""
await state.set_state(EditReminderStates.editing_time)
await callback.message.edit_text(
"Введи новое время в формате ЧЧ:ММ (например, 09:00):"
)
await callback.answer()
@router.message(EditReminderStates.editing_time)
async def edit_time_process(
message: Message,
state: FSMContext,
session: AsyncSession,
) -> None:
"""
Process new reminder time.
Args:
message: Telegram message
state: FSM state context
session: Database session
"""
time_of_day = validate_time_format(message.text)
if time_of_day is None:
await message.answer(
"Некорректный формат времени. Используй формат ЧЧ:ММ (например, 09:00):"
)
return
data = await state.get_data()
reminder_id = data["reminder_id"]
reminder = await reminders_service.update_reminder_time(session, reminder_id, time_of_day)
if not reminder:
await message.answer("Напоминание не найдено")
await state.clear()
return
await state.clear()
await message.answer(
f"✅ Время обновлено!\n\nНовое время: {time_of_day.strftime('%H:%M')}",
reply_markup=get_main_menu_keyboard(),
)