"""Scheduler for sending reminder notifications.""" import asyncio from typing import Optional from aiogram import Bot from apscheduler.schedulers.asyncio import AsyncIOScheduler from apscheduler.triggers.interval import IntervalTrigger from bot.db.operations import get_due_reminders from bot.keyboards.reminders import get_reminder_notification_keyboard from bot.services.time_service import get_time_service from bot.logging_config import get_logger logger = get_logger(__name__) # Global scheduler instance _scheduler: Optional[AsyncIOScheduler] = None async def send_reminder_notification(bot: Bot, user_tg_id: int, reminder_id: int, text: str) -> None: """ Send reminder notification to user. Args: bot: Bot instance user_tg_id: Telegram user ID reminder_id: Reminder ID text: Reminder text """ try: await bot.send_message( chat_id=user_tg_id, text=f"⏰ Напоминание:\n\n{text}", reply_markup=get_reminder_notification_keyboard(reminder_id), parse_mode="HTML", ) logger.info(f"Sent reminder {reminder_id} to user {user_tg_id}") except Exception as e: logger.error(f"Failed to send reminder {reminder_id} to user {user_tg_id}: {e}") async def check_and_send_reminders(bot: Bot) -> None: """ Check for due reminders and send notifications. Args: bot: Bot instance """ from bot.db.base import async_session_maker if not async_session_maker: logger.error("Session maker not initialized") return try: time_service = get_time_service() current_time = time_service.get_now() async with async_session_maker() as session: due_reminders = await get_due_reminders(session, current_time) if not due_reminders: logger.debug("No due reminders found") return logger.info(f"Found {len(due_reminders)} due reminders") for reminder in due_reminders: await send_reminder_notification( bot=bot, user_tg_id=reminder.user.tg_user_id, reminder_id=reminder.id, text=reminder.text, ) next_run = time_service.calculate_next_occurrence( current_run=reminder.next_run_at, days_interval=reminder.days_interval, ) next_run = next_run.replace( hour=reminder.time_of_day.hour, minute=reminder.time_of_day.minute, second=0, microsecond=0, ) reminder.next_run_at = next_run reminder.updated_at = time_service.get_now() await asyncio.sleep(0.5) await session.commit() except Exception as e: logger.error(f"Error in check_and_send_reminders: {e}", exc_info=True) def create_scheduler(bot: Bot) -> AsyncIOScheduler: """ Create and configure scheduler. Args: bot: Bot instance Returns: Configured AsyncIOScheduler instance """ global _scheduler scheduler = AsyncIOScheduler(timezone="UTC") # Add job to check reminders every minute scheduler.add_job( check_and_send_reminders, trigger=IntervalTrigger(minutes=1), args=[bot], id="check_reminders", name="Check and send due reminders", replace_existing=True, ) _scheduler = scheduler logger.info("Scheduler created and configured") return scheduler def start_scheduler() -> None: """Start the scheduler.""" if _scheduler is None: raise RuntimeError("Scheduler not initialized. Call create_scheduler() first.") _scheduler.start() logger.info("Scheduler started") def stop_scheduler() -> None: """Stop the scheduler.""" if _scheduler is not None: _scheduler.shutdown() logger.info("Scheduler stopped") def get_scheduler() -> Optional[AsyncIOScheduler]: """ Get global scheduler instance. Returns: Scheduler instance or None """ return _scheduler