reminder-bot/bot/core/scheduler.py

151 lines
4.2 KiB
Python

"""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"⏰ <b>Напоминание:</b>\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