This commit is contained in:
2026-01-28 13:35:28 +03:00
parent c3e0da7029
commit 95cd4b5fb1
2 changed files with 87 additions and 9 deletions

View File

@ -1,12 +1,13 @@
"""Scheduler for daily birthday notifications.""" """Scheduler for daily birthday notifications."""
import telebot import telebot
from datetime import datetime 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.schedulers.blocking import BlockingScheduler
from apscheduler.triggers.cron import CronTrigger from apscheduler.triggers.cron import CronTrigger
import pytz import pytz
from database import get_db_session, User, Chat, UserChat 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 from config import Config
@ -27,7 +28,10 @@ def send_birthday_notifications(bot: telebot.TeleBot) -> None:
if not users_with_birthday: if not users_with_birthday:
return 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: for user in users_with_birthday:
# Get all chats where user is a member # Get all chats where user is a member
user_chats = db.query(Chat).join(UserChat).filter( 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 Chat.bot_is_admin == True
).all() ).all()
greeting = format_birthday_greeting(user.first_name, user.preference_theme)
for chat in user_chats: for chat in user_chats:
try: try:
# Check if user is still in chat # Check if user is still in chat
member = bot.get_chat_member(chat.chat_id, user.user_id) member = bot.get_chat_member(chat.chat_id, user.user_id)
if member.status not in ['left', 'kicked']: 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: except telebot.apihelper.ApiTelegramException as e:
# Handle errors: user blocked bot, bot removed from chat, etc. # Handle errors: user blocked bot, bot removed from chat, etc.
if e.error_code == 403: if e.error_code == 403:
@ -57,6 +61,31 @@ def send_birthday_notifications(bot: telebot.TeleBot) -> None:
except Exception: except Exception:
# Other errors - continue # Other errors - continue
pass 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: finally:
db.close() db.close()

View File

@ -38,6 +38,20 @@ def get_theme_emoji(theme: str) -> str:
"""Get emoji for the given theme.""" """Get emoji for the given theme."""
return THEME_EMOJIS.get(theme, "🎉") # Default emoji 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 for each theme
BIRTHDAY_MESSAGES = { BIRTHDAY_MESSAGES = {
"Автомобили": [ "Автомобили": [
@ -132,8 +146,43 @@ def get_birthday_message(theme: str) -> str:
return random.choice(messages) return random.choice(messages)
def format_birthday_greeting(first_name: str, theme: str) -> str: def format_birthday_greeting(first_name: str, theme: str, user_id: int) -> str:
"""Format a complete birthday greeting with emoji.""" """Format a complete birthday greeting with emoji and user link."""
emoji = get_theme_emoji(theme) emoji = get_theme_emoji(theme)
greeting_opening = get_birthday_greeting_opening()
message = get_birthday_message(theme) 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}, <a href=\"{user_link}\">{emoji} <b>{first_name}</b></a>, {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"<a href=\"{user_link}\">{emoji} <b>{first_name}</b></a> — {greeting_opening.lower()}, {message}"
greetings.append(greeting)
return header + "\n\n".join(greetings)