"""Scheduler for sending reminder notifications.""" import asyncio from datetime import datetime from typing import Optional from aiogram import Bot from apscheduler.schedulers.asyncio import AsyncIOScheduler from apscheduler.triggers.interval import IntervalTrigger from bot.db.base import get_session 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 from bot.db.operations import update_reminder if not async_session_maker: logger.error("Session maker not initialized") return try: time_service = get_time_service() current_time = time_service.get_now() # Get due reminders from database using proper async session 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") # Send notifications 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, ) # Update next_run_at to prevent sending again # (it will be properly updated when user clicks "Done" or by periodic update) temp_next_run = time_service.calculate_next_occurrence( current_run=reminder.next_run_at, days_interval=reminder.days_interval, ) await update_reminder(session, reminder.id, next_run_at=temp_next_run) # Commit all updates await session.commit() # Small delay to avoid rate limits await asyncio.sleep(0.5) 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