ReminderBot/bot/scheduler/executor.py
leo 5b329d0afc fix: correct interval reminder scheduling and done action behavior
- Replace IntervalTrigger with OrTrigger of CronTriggers for interval
  reminders, ensuring triggers align to window start each day without drift
- Refactor executor to extract _send_reminder_message helper, removing
  the runtime window check (now handled at scheduling layer)
- Change "done" button to only acknowledge the notification without
  deactivating the reminder

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-05 17:54:33 +08:00

91 lines
2.7 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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