DispelEvilBot/bot/services/notifications.py

83 lines
3.1 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.

"""Планировщик рассылок.
Исполняется внутри процесса бота. Расписание пока задано в коде (данные, а не
магические числа); в Блоке 4 переедет в таблицу ``schedules`` и будет
редактироваться через админку.
"""
from __future__ import annotations
import asyncio
import logging
from dataclasses import dataclass, field
from datetime import datetime
from aiogram import Bot
from bot.services import users as users_service
from common.config import get_settings
from common.texts import format_usernames, render
from common.texts import notifications as notify_texts
logger = logging.getLogger(__name__)
#: Размер порции упоминаний в одном сообщении.
BATCH_SIZE = 10
@dataclass(frozen=True, slots=True)
class ScheduledMessage:
"""Одна запланированная рассылка.
:param weekdays: дни недели (0=Пн … 6=Вс). None — ежедневно.
"""
hour: int
minute: int
template: str
weekdays: tuple[int, ...] | None = field(default=None)
def matches(self, now: datetime) -> bool:
if self.hour != now.hour or self.minute != now.minute:
return False
return self.weekdays is None or now.weekday() in self.weekdays
SCHEDULES: tuple[ScheduledMessage, ...] = (
ScheduledMessage(16, 30, notify_texts.MSG_BLAGO_30),
ScheduledMessage(16, 45, notify_texts.MSG_BLAGO_45),
ScheduledMessage(16, 55, notify_texts.MSG_BLAGO_55),
ScheduledMessage(17, 0, notify_texts.MSG_BLAGO),
ScheduledMessage(15, 0, notify_texts.MSG_VKLAD, weekdays=(4,)), # пятница
ScheduledMessage(14, 0, notify_texts.MSG_VKLAD, weekdays=(6,)), # воскресенье
)
async def _send_broadcast(bot: Bot, template: str, subscribers: list[dict]) -> None:
chunks = [subscribers[i : i + BATCH_SIZE] for i in range(0, len(subscribers), BATCH_SIZE)] or [[]]
for chunk in chunks:
text = render(template, {"usernames": format_usernames(chunk)})
await bot.send_message(chat_id=get_settings().chat_id, text=text)
async def _tick(bot: Bot, now: datetime) -> None:
for schedule in SCHEDULES:
if schedule.matches(now):
subscribers = await users_service.list_subscribers()
await _send_broadcast(bot, schedule.template, subscribers)
async def run_notifications(bot: Bot) -> None:
"""Бесконечно проверять расписание раз в минуту."""
tz = get_settings().tzinfo
while True:
now = datetime.now(tz).replace(second=0, microsecond=0)
try:
await _tick(bot, now)
except asyncio.CancelledError:
raise
except Exception:
logger.exception("Ошибка планировщика рассылок")
# Спать до начала следующей минуты, чтобы обрабатывать каждую минуту один раз.
await asyncio.sleep(60 - datetime.now(tz).second)