diff --git a/handlers/scheduler.py b/handlers/scheduler.py
index d66d90a..cdf568b 100644
--- a/handlers/scheduler.py
+++ b/handlers/scheduler.py
@@ -1,12 +1,13 @@
"""Scheduler for daily birthday notifications."""
import telebot
from datetime import datetime
-from typing import Optional
+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
+from messages import format_birthday_greeting, format_multiple_birthdays_greetings
from config import Config
@@ -27,7 +28,10 @@ def send_birthday_notifications(bot: telebot.TeleBot) -> None:
if not users_with_birthday:
return
- # For each user, send greetings to all their chats
+ # 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(
@@ -35,14 +39,14 @@ def send_birthday_notifications(bot: telebot.TeleBot) -> None:
Chat.bot_is_admin == True
).all()
- greeting = format_birthday_greeting(user.first_name, user.preference_theme)
-
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']:
- bot.send_message(chat.chat_id, greeting)
+ 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:
@@ -57,6 +61,31 @@ def send_birthday_notifications(bot: telebot.TeleBot) -> None:
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()
diff --git a/messages.py b/messages.py
index 2ff3a5a..4c15a01 100644
--- a/messages.py
+++ b/messages.py
@@ -38,6 +38,20 @@ def get_theme_emoji(theme: str) -> str:
"""Get emoji for the given theme."""
return THEME_EMOJIS.get(theme, "🎉") # Default emoji
+# Birthday greeting opening phrases (random variations)
+BIRTHDAY_GREETINGS = [
+ "С днем рождения",
+ "Поздравляю с днем рождения",
+ "С твоим днем рождения",
+ "Поздравляю тебя с днем рождения",
+ "Поздравляю с днем рождения",
+]
+
+
+def get_birthday_greeting_opening() -> str:
+ """Get a random birthday greeting opening phrase."""
+ return random.choice(BIRTHDAY_GREETINGS)
+
# Birthday messages for each theme
BIRTHDAY_MESSAGES = {
"Автомобили": [
@@ -132,8 +146,43 @@ def get_birthday_message(theme: str) -> str:
return random.choice(messages)
-def format_birthday_greeting(first_name: str, theme: str) -> str:
- """Format a complete birthday greeting with emoji."""
+def format_birthday_greeting(first_name: str, theme: str, user_id: int) -> str:
+ """Format a complete birthday greeting with emoji and user link."""
emoji = get_theme_emoji(theme)
+ greeting_opening = get_birthday_greeting_opening()
message = get_birthday_message(theme)
- return f"{emoji} С днем рождения {first_name}, {message}"
+ # Format: party popper + theme emoji + greeting + bold name with link
+ user_link = f"tg://user?id={user_id}"
+ return f"🎉 {greeting_opening}, {emoji} {first_name}, {message}"
+
+
+def format_multiple_birthdays_greetings(users_data: list[tuple[str, str, int]]) -> str:
+ """Format greetings for multiple users celebrating birthday today.
+
+ Args:
+ users_data: List of tuples (first_name, theme, user_id)
+
+ Returns:
+ Formatted message with all greetings
+ """
+ count = len(users_data)
+ # Determine correct form of "человек"
+ if count == 1:
+ person_word = "человек"
+ elif count in [2, 3, 4]:
+ person_word = "человека"
+ else:
+ person_word = "человек"
+
+ header = f"🎉 Сегодня день рождения отмечают {count} {person_word}:\n\n"
+
+ greetings = []
+ for first_name, theme, user_id in users_data:
+ emoji = get_theme_emoji(theme)
+ greeting_opening = get_birthday_greeting_opening()
+ message = get_birthday_message(theme)
+ user_link = f"tg://user?id={user_id}"
+ greeting = f"{emoji} {first_name} — {greeting_opening.lower()}, {message}"
+ greetings.append(greeting)
+
+ return header + "\n\n".join(greetings)