Compare commits

..

2 Commits

Author SHA1 Message Date
8ec622c427 feat: handlers 2026-01-28 14:56:43 +03:00
a16a979c63 feat: 01 schedule 2026-01-28 13:41:59 +03:00
3 changed files with 109 additions and 7 deletions

View File

@ -98,7 +98,7 @@ def register_command_handlers(bot: telebot.TeleBot) -> None:
@bot.message_handler(commands=['month'], chat_types=['group', 'supergroup']) @bot.message_handler(commands=['month'], chat_types=['group', 'supergroup'])
def handle_month(message: telebot.types.Message) -> None: def handle_month(message: telebot.types.Message) -> None:
"""Handle /month command - show birthdays for next 30 days.""" """Handle /month command - show birthdays for next 31 days."""
chat_id = message.chat.id chat_id = message.chat.id
db = get_db_session() db = get_db_session()
@ -109,16 +109,16 @@ def register_command_handlers(bot: telebot.TeleBot) -> None:
bot.reply_to(message, "Мне нужны права администратора для выполнения этой команды.") bot.reply_to(message, "Мне нужны права администратора для выполнения этой команды.")
return return
# Get birthdays for next 30 days # Get birthdays for next 31 days
today = datetime.now().date() today = datetime.now().date()
birthdays = get_birthdays_in_range(db, chat_id, today, days=30) birthdays = get_birthdays_in_range(db, chat_id, today, days=31)
if not birthdays: if not birthdays:
bot.reply_to(message, "На ближайшие 30 дней дней рождений не запланировано.") bot.reply_to(message, "На ближайшие 31 день дней рождений не запланировано.")
return return
# Format message # Format message
message_text = "🎂 Дни рождения на ближайшие 30 дней:\n\n" message_text = "🎂 Дни рождения на ближайшие 31 день:\n\n"
for date_str, names in sorted(birthdays.items()): for date_str, names in sorted(birthdays.items()):
names_list = ", ".join(names) names_list = ", ".join(names)
message_text += f"{date_str}: {names_list}\n" message_text += f"{date_str}: {names_list}\n"
@ -134,7 +134,7 @@ def register_command_handlers(bot: telebot.TeleBot) -> None:
"📚 Команды бота:\n\n" "📚 Команды бота:\n\n"
"/stats - Показать статистику: сколько человек поделились днем рождения\n" "/stats - Показать статистику: сколько человек поделились днем рождения\n"
"/week - Показать дни рождения на ближайшие 7 дней\n" "/week - Показать дни рождения на ближайшие 7 дней\n"
"/month - Показать дни рождения на ближайшие 30 дней\n" "/month - Показать дни рождения на ближайшие 31 день\n"
"/help - Показать это сообщение\n\n" "/help - Показать это сообщение\n\n"
"Чтобы поделиться своим днем рождения, напиши боту в личку /start\n\n" "Чтобы поделиться своим днем рождения, напиши боту в личку /start\n\n"
"from olly & cursor with <3" "from olly & cursor with <3"

View File

@ -75,6 +75,22 @@ def register_private_handlers(bot: telebot.TeleBot) -> None:
finally: finally:
db.close() db.close()
@bot.message_handler(commands=['help'], chat_types=['private'])
def handle_help(message: telebot.types.Message) -> None:
"""Handle /help command in private chat."""
help_text = (
"📚 Команды бота:\n\n"
"/start - Поделиться днем рождения и выбрать тему предпочтений\n"
"/update - Обновить свой день рождения или тему предпочтений\n"
"/help - Показать это сообщение\n\n"
"В группах доступны команды:\n"
"/stats - Статистика по чату\n"
"/week - Дни рождения на 7 дней\n"
"/month - Дни рождения на 31 день\n\n"
"from olly & cursor with <3"
)
bot.send_message(message.chat.id, help_text)
@bot.message_handler(func=lambda m: m.chat.type == 'private' and m.from_user and m.from_user.id in user_states and m.text) @bot.message_handler(func=lambda m: m.chat.type == 'private' and m.from_user and m.from_user.id in user_states and m.text)
def handle_birthday_input(message: telebot.types.Message) -> None: def handle_birthday_input(message: telebot.types.Message) -> None:
"""Handle birthday input from user.""" """Handle birthday input from user."""

View File

@ -1,11 +1,12 @@
"""Scheduler for daily birthday notifications.""" """Scheduler for daily birthday notifications."""
import telebot import telebot
from datetime import datetime from datetime import datetime, timedelta, date
from typing import Optional, Dict, List, Tuple from typing import Optional, Dict, List, Tuple
from collections import defaultdict 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 sqlalchemy.orm import Session
from database import get_db_session, User, Chat, UserChat from database import get_db_session, User, Chat, UserChat
from messages import format_birthday_greeting, format_multiple_birthdays_greetings from messages import format_birthday_greeting, format_multiple_birthdays_greetings
from config import Config from config import Config
@ -119,4 +120,89 @@ def setup_scheduler(bot: telebot.TeleBot) -> BlockingScheduler:
replace_existing=True replace_existing=True
) )
# Add monthly job (1st day of each month)
scheduler.add_job(
send_monthly_birthday_overview,
trigger=CronTrigger(day=1, hour=hour, minute=minute),
args=[bot],
id='monthly_birthday_overview',
name='Send monthly birthday overview',
replace_existing=True
)
return scheduler return scheduler
def get_birthdays_in_range_for_chat(db: Session, chat_id: int, start_date: date, days: int) -> Dict[str, List[str]]:
"""Get birthdays in the specified date range for users in the chat."""
birthdays = defaultdict(list)
# Get all users in this chat
users = db.query(User).join(UserChat).filter(
UserChat.chat_id == chat_id
).distinct().all()
end_date = start_date + timedelta(days=days)
for user in users:
# Create birthday date for current year
try:
birthday_this_year = datetime(start_date.year, user.birthday_month, user.birthday_day).date()
except ValueError:
# Invalid date (e.g., Feb 29 in non-leap year)
continue
# Check if birthday falls in range
if start_date <= birthday_this_year < end_date:
date_str = f"{user.birthday_day:02d}.{user.birthday_month:02d}"
birthdays[date_str].append(user.first_name)
else:
# Check next year if we're near year end
try:
birthday_next_year = datetime(start_date.year + 1, user.birthday_month, user.birthday_day).date()
if start_date <= birthday_next_year < end_date:
date_str = f"{user.birthday_day:02d}.{user.birthday_month:02d}"
birthdays[date_str].append(user.first_name)
except ValueError:
pass
return birthdays
def send_monthly_birthday_overview(bot: telebot.TeleBot) -> None:
"""Send monthly birthday overview (like /month command) to all chats on 1st of each month."""
db = get_db_session()
try:
today = datetime.now().date()
# Get all chats where bot is admin
admin_chats = db.query(Chat).filter(Chat.bot_is_admin == True).all()
for chat in admin_chats:
try:
# Get birthdays for next 31 days
birthdays = get_birthdays_in_range_for_chat(db, chat.chat_id, today, days=31)
if not birthdays:
# Don't send message if no birthdays
continue
# Format message (same as /month command)
message_text = "🎂 Дни рождения на ближайшие 31 день:\n\n"
for date_str, names in sorted(birthdays.items()):
names_list = ", ".join(names)
message_text += f"{date_str}: {names_list}\n"
bot.send_message(chat.chat_id, message_text)
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.bot_is_admin = False
db.commit()
# Silently continue for other errors
except Exception:
# Other errors - continue
pass
finally:
db.close()