Files
2026-07-06 13:30:48 +03:00

103 lines
2.9 KiB
Python

import asyncio
import logging
import random
from datetime import datetime, timedelta
from zoneinfo import ZoneInfo
from aiogram import Bot
QUESTIONS = (
"Как дела?",
"Что нового?",
"Что сейчас происходит?",
"Чем занимаешься?",
"Есть что записать?",
"Как проходит день?",
)
class ReminderState:
def __init__(self) -> None:
self.last_manual_entry_at: datetime | None = None
self.changed = asyncio.Event()
def mark_manual_entry(self, timezone: ZoneInfo) -> None:
self.last_manual_entry_at = datetime.now(timezone)
self.changed.set()
def _is_allowed_time(now: datetime, start_hour: int, end_hour: int) -> bool:
return start_hour <= now.hour < end_hour
def _next_window_start(now: datetime, start_hour: int) -> datetime:
if now.hour < start_hour:
return now.replace(hour=start_hour, minute=0, second=0, microsecond=0)
tomorrow = now + timedelta(days=1)
return tomorrow.replace(hour=start_hour, minute=0, second=0, microsecond=0)
def _move_to_allowed_time(now: datetime, start_hour: int, end_hour: int) -> datetime:
if _is_allowed_time(now, start_hour, end_hour):
return now
return _next_window_start(now, start_hour)
def _next_reminder_at(
now: datetime,
state: ReminderState,
min_seconds: int,
max_seconds: int,
start_hour: int,
end_hour: int,
) -> datetime:
if not _is_allowed_time(now, start_hour, end_hour):
candidate = _move_to_allowed_time(now, start_hour, end_hour)
else:
candidate = now + timedelta(seconds=random.randint(min_seconds, max_seconds))
if state.last_manual_entry_at:
candidate = max(candidate, state.last_manual_entry_at + timedelta(hours=1))
return _move_to_allowed_time(candidate, start_hour, end_hour)
async def send_reminders(
bot: Bot,
user_ids: set[int],
min_seconds: int,
max_seconds: int,
start_hour: int,
end_hour: int,
timezone: ZoneInfo,
state: ReminderState,
) -> None:
while True:
now = datetime.now(timezone)
next_reminder_at = _next_reminder_at(
now=now,
state=state,
min_seconds=min_seconds,
max_seconds=max_seconds,
start_hour=start_hour,
end_hour=end_hour,
)
sleep_seconds = max(0, (next_reminder_at - now).total_seconds())
try:
await asyncio.wait_for(state.changed.wait(), timeout=sleep_seconds)
except TimeoutError:
pass
else:
state.changed.clear()
continue
question = random.choice(QUESTIONS)
for user_id in user_ids:
try:
await bot.send_message(user_id, question)
except Exception:
logging.exception("Failed to send reminder to user_id=%s", user_id)