241 lines
7.5 KiB
Python
241 lines
7.5 KiB
Python
"""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}")
|