add reminder schedule

This commit is contained in:
Dmitry
2026-07-06 13:30:48 +03:00
parent 793716f699
commit c4b72b22e6
8 changed files with 253 additions and 3 deletions
+71 -1
View File
@@ -1,6 +1,8 @@
import asyncio
import logging
import random
from datetime import datetime, timedelta
from zoneinfo import ZoneInfo
from aiogram import Bot
@@ -15,14 +17,82 @@ 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:
await asyncio.sleep(random.randint(min_seconds, max_seconds))
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: