reminder-bot/bot/core/scheduler.py

145 lines
3.9 KiB
Python
Raw Normal View History

2025-12-19 11:19:54 +01:00
"""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
"""
2025-12-19 12:14:51 +01:00
from bot.db.base import async_session_maker
if not async_session_maker:
logger.error("Session maker not initialized")
return
2025-12-19 11:19:54 +01:00
try:
time_service = get_time_service()
current_time = time_service.get_now()
2025-12-19 12:14:51 +01:00
async with async_session_maker() as session:
2025-12-19 11:19:54 +01:00
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,
)
2026-02-17 10:52:46 +01:00
next_run = time_service.calculate_next_occurrence(
2025-12-19 11:19:54 +01:00
current_run=reminder.next_run_at,
days_interval=reminder.days_interval,
)
2026-02-17 10:52:46 +01:00
reminder.next_run_at = next_run
reminder.updated_at = time_service.get_now()
2025-12-19 11:19:54 +01:00
2026-02-17 10:52:46 +01:00
await asyncio.sleep(0.5)
2025-12-19 12:14:51 +01:00
2026-02-17 10:52:46 +01:00
await session.commit()
2025-12-19 11:19:54 +01:00
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