"""Scheduler for daily birthday notifications.""" import telebot from datetime import datetime from typing import Optional, Dict, List, Tuple from collections import defaultdict from apscheduler.schedulers.blocking import BlockingScheduler from apscheduler.triggers.cron import CronTrigger import pytz from database import get_db_session, User, Chat, UserChat from messages import format_birthday_greeting, format_multiple_birthdays_greetings from config import Config def send_birthday_notifications(bot: telebot.TeleBot) -> None: """Send birthday notifications to all chats for users with birthday today.""" db = get_db_session() try: today = datetime.now().date() day = today.day month = today.month # Get all users with birthday today users_with_birthday = db.query(User).filter( User.birthday_day == day, User.birthday_month == month ).all() if not users_with_birthday: return # Group users by chat: chat_id -> list of (first_name, theme, user_id) chat_users: Dict[int, List[Tuple[str, str, int]]] = defaultdict(list) # For each user, find all their chats for user in users_with_birthday: # Get all chats where user is a member user_chats = db.query(Chat).join(UserChat).filter( UserChat.user_id == user.user_id, Chat.bot_is_admin == True ).all() for chat in user_chats: try: # Check if user is still in chat member = bot.get_chat_member(chat.chat_id, user.user_id) if member.status not in ['left', 'kicked']: chat_users[chat.chat_id].append( (user.first_name, user.preference_theme, user.user_id) ) except telebot.apihelper.ApiTelegramException as e: # Handle errors: user blocked bot, bot removed from chat, etc. if e.error_code == 403: # Bot was blocked or removed from chat # Update chat status chat.bot_is_admin = False db.commit() elif e.error_code == 400: # Invalid chat or user pass # Silently continue for other errors except Exception: # Other errors - continue pass # Send greetings grouped by chat for chat_id, users_data in chat_users.items(): try: if len(users_data) == 1: # Single user - use simple format first_name, theme, user_id = users_data[0] greeting = format_birthday_greeting(first_name, theme, user_id) bot.send_message(chat_id, greeting, parse_mode='HTML') else: # Multiple users - use special format greeting = format_multiple_birthdays_greetings(users_data) bot.send_message(chat_id, greeting, parse_mode='HTML') except telebot.apihelper.ApiTelegramException as e: # Handle errors: bot removed from chat, etc. if e.error_code == 403: # Bot was blocked or removed from chat chat = db.query(Chat).filter(Chat.chat_id == chat_id).first() if chat: chat.bot_is_admin = False db.commit() # Silently continue for other errors except Exception: # Other errors - continue pass finally: db.close() def setup_scheduler(bot: telebot.TeleBot) -> BlockingScheduler: """Setup and start the scheduler for daily birthday notifications.""" # Parse notification time time_str: str = Config.NOTIFICATION_TIME try: hour, minute = map(int, time_str.split(':')) except (ValueError, AttributeError): hour, minute = 9, 0 # Default to 9:00 # Get timezone timezone_str: str = Config.TIMEZONE try: tz = pytz.timezone(timezone_str) except (pytz.exceptions.UnknownTimeZoneError, AttributeError): tz = pytz.UTC # Create scheduler scheduler = BlockingScheduler(timezone=tz) # Add daily job scheduler.add_job( send_birthday_notifications, trigger=CronTrigger(hour=hour, minute=minute), args=[bot], id='daily_birthday_notifications', name='Send daily birthday notifications', replace_existing=True ) return scheduler