reminder-bot/bot/handlers/reminders_manage.py

439 lines
13 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

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.

"""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(),
)