147 lines
4.3 KiB
Python
147 lines
4.3 KiB
Python
"""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"⏰ <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
|
|
"""
|
|
try:
|
|
time_service = get_time_service()
|
|
current_time = time_service.get_now()
|
|
|
|
# Get due reminders from database
|
|
async for session in get_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)
|
|
from bot.db.operations import update_reminder
|
|
from datetime import timedelta
|
|
|
|
# Temporarily set next_run_at to current + interval to avoid duplicate sends
|
|
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)
|
|
|
|
# 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
|