from __future__ import annotations import logging from datetime import datetime, timezone import pytz from telegram import Bot from bot.models.database import Session from bot.models.reminder import Reminder, ReminderLog from bot.utils.holiday import is_holiday from bot.utils.keyboards import reminder_action_keyboard logger = logging.getLogger(__name__) async def execute_reminder(reminder_id: int, bot: Bot) -> None: """Send a reminder message. Called by APScheduler.""" session = Session() try: reminder = session.get(Reminder, reminder_id) if reminder is None or not reminder.is_active: return now_utc = datetime.now(timezone.utc) tz = pytz.timezone("Asia/Shanghai") now_local = now_utc.astimezone(tz) # Skip on holidays if requested if reminder.skip_holidays and is_holiday(now_local.date()): logger.info("Skipping reminder %d on holiday", reminder_id) return # Send the reminder await _send_reminder_message(reminder, bot, session, now_utc, now_local) except Exception: logger.exception("Error executing reminder %d", reminder_id) session.rollback() finally: Session.remove() async def _send_reminder_message( reminder: Reminder, bot: Bot, session, now_utc: datetime, now_local: datetime ) -> None: """Send a single reminder message.""" # Build message text = _build_message(reminder) keyboard = reminder_action_keyboard(reminder.id) # Fetch the telegram_id from users table from bot.models.user import User # avoid circular import at module level user_obj = session.get(User, reminder.user_id) if user_obj is None: return await bot.send_message( chat_id=user_obj.telegram_id, text=text, parse_mode="Markdown", reply_markup=keyboard, ) # Update reminder timestamps reminder.last_sent_at = now_utc log = ReminderLog(reminder_id=reminder.id, sent_at=now_utc, status="sent") session.add(log) # Deactivate one-time reminders after firing if reminder.reminder_type == "once": reminder.is_active = False session.commit() logger.info("Sent reminder %d to user %d", reminder.id, user_obj.telegram_id) def _build_message(reminder: Reminder) -> str: lines = [f"⏰ *{_escape(reminder.title)}*"] if reminder.description: lines.append(f"\n{_escape(reminder.description)}") lines.append(f"\n_类型:{reminder.type_display()} | {reminder.schedule_summary()}_") return "\n".join(lines) def _escape(text: str) -> str: """Escape Markdown v1 special chars.""" for ch in ["_", "*", "`", "["]: text = text.replace(ch, f"\\{ch}") return text