reminder-bot/bot/handlers/reminders_create.py

241 lines
7.5 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 creating reminders with FSM."""
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 CreateReminderStates
from bot.keyboards.reminders import (
get_interval_selection_keyboard,
get_confirmation_keyboard,
ReminderIntervalCallback,
ConfirmCallback,
)
from bot.keyboards.main_menu import get_main_menu_keyboard
from bot.utils.validators import validate_time_format, validate_days_interval
from bot.utils.formatting import format_interval_days
from bot.services.user_service import UserService
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="reminders_create")
reminders_service = RemindersService()
time_service = get_time_service()
@router.message(F.text == " Новое напоминание")
async def start_create_reminder(message: Message, state: FSMContext) -> None:
"""
Start reminder creation flow.
Args:
message: Telegram message
state: FSM state context
"""
await state.set_state(CreateReminderStates.waiting_for_text)
await message.answer(
"Что нужно напоминать?\n\nВведи текст напоминания:"
)
@router.message(CreateReminderStates.waiting_for_text)
async def process_reminder_text(message: Message, state: FSMContext) -> None:
"""
Process reminder text input.
Args:
message: Telegram message
state: FSM state context
"""
text = message.text.strip()
if not text or len(text) > 1000:
await message.answer(
"Текст напоминания должен быть от 1 до 1000 символов. Попробуй ещё раз:"
)
return
# Save text to state
await state.update_data(text=text)
await state.set_state(CreateReminderStates.waiting_for_interval)
await message.answer(
"Как часто напоминать? Выбери или введи количество дней:",
reply_markup=get_interval_selection_keyboard(),
)
@router.callback_query(
CreateReminderStates.waiting_for_interval,
ReminderIntervalCallback.filter()
)
async def process_interval_button(
callback: CallbackQuery,
callback_data: ReminderIntervalCallback,
state: FSMContext,
) -> None:
"""
Process interval selection via button.
Args:
callback: Callback query
callback_data: Parsed callback data
state: FSM state context
"""
if callback_data.days == 0:
# User chose "Other"
await callback.message.edit_text(
"Введи количество дней (целое положительное число):"
)
await callback.answer()
return
# Save interval and proceed
await state.update_data(days_interval=callback_data.days)
await state.set_state(CreateReminderStates.waiting_for_time)
await callback.message.edit_text(
f"Выбрано: {format_interval_days(callback_data.days)}\n\n"
"В какое время напоминать?\n"
"Введи время в формате ЧЧ:ММ (например, 09:00 или 18:30):"
)
await callback.answer()
@router.message(CreateReminderStates.waiting_for_interval)
async def process_interval_text(message: Message, state: FSMContext) -> None:
"""
Process interval input as text.
Args:
message: Telegram message
state: FSM state context
"""
days = validate_days_interval(message.text)
if days is None:
await message.answer(
"Некорректное значение. Введи целое положительное число дней:"
)
return
# Save interval and proceed
await state.update_data(days_interval=days)
await state.set_state(CreateReminderStates.waiting_for_time)
await message.answer(
f"Отлично, {format_interval_days(days)}!\n\n"
"В какое время напоминать?\n"
"Введи время в формате ЧЧ:ММ (например, 09:00 или 18:30):"
)
@router.message(CreateReminderStates.waiting_for_time)
async def process_reminder_time(message: Message, state: FSMContext) -> None:
"""
Process reminder time input.
Args:
message: Telegram message
state: FSM state context
"""
time_of_day = validate_time_format(message.text)
if time_of_day is None:
await message.answer(
"Некорректный формат времени. Используй формат ЧЧ:ММ (например, 09:00):"
)
return
# Save time and show confirmation
await state.update_data(time_of_day=time_of_day)
await state.set_state(CreateReminderStates.waiting_for_confirmation)
# Get all data for confirmation
data = await state.get_data()
text = data["text"]
days_interval = data["days_interval"]
confirmation_text = (
"Создать напоминание?\n\n"
f"📝 <b>Текст:</b> {text}\n"
f"🔄 <b>Периодичность:</b> {format_interval_days(days_interval)}\n"
f"🕐 <b>Время:</b> {time_of_day.strftime('%H:%M')}"
)
await message.answer(
confirmation_text,
reply_markup=get_confirmation_keyboard(entity="create"),
parse_mode="HTML",
)
@router.callback_query(
CreateReminderStates.waiting_for_confirmation,
ConfirmCallback.filter(F.entity == "create")
)
async def process_create_confirmation(
callback: CallbackQuery,
callback_data: ConfirmCallback,
state: FSMContext,
session: AsyncSession,
) -> None:
"""
Process create reminder confirmation.
Args:
callback: Callback query
callback_data: Parsed callback data
state: FSM state context
session: Database session
"""
if callback_data.action == "no":
await state.clear()
await callback.message.edit_text("Создание напоминания отменено.")
await callback.message.answer(
"Выбери действие из меню:",
reply_markup=get_main_menu_keyboard(),
)
await callback.answer()
return
# Get user
user = await UserService.ensure_user_exists(session, callback.from_user)
# Get data from state
data = await state.get_data()
text = data["text"]
days_interval = data["days_interval"]
time_of_day = data["time_of_day"]
# Create reminder
reminder = await reminders_service.create_new_reminder(
session=session,
user_id=user.id,
text=text,
days_interval=days_interval,
time_of_day=time_of_day,
)
# Clear state
await state.clear()
# Format next run time
next_run_str = time_service.format_next_run(reminder.next_run_at)
await callback.message.edit_text(
f"✅ Напоминание создано!\n\n"
f"Первое напоминание будет {next_run_str}."
)
await callback.message.answer(
"Выбери действие из меню:",
reply_markup=get_main_menu_keyboard(),
)
await callback.answer("Напоминание создано!")
logger.info(f"User {user.tg_user_id} created reminder {reminder.id}")