80 lines
3.1 KiB
Python
80 lines
3.1 KiB
Python
from __future__ import annotations
|
||
|
||
import asyncio
|
||
import logging
|
||
from contextlib import suppress
|
||
from datetime import datetime, time
|
||
from zoneinfo import ZoneInfo, ZoneInfoNotFoundError
|
||
|
||
from aiogram import Bot
|
||
from aiogram.exceptions import TelegramForbiddenError
|
||
|
||
from app.database import Database
|
||
from app.keyboards import main_keyboard
|
||
from app.messages import daily_report_text
|
||
from app.periods import day_bounds, month_bounds
|
||
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
|
||
async def daily_report_loop(bot: Bot, database: Database) -> None:
|
||
"""Check report schedules periodically; the database makes delivery idempotent."""
|
||
while True:
|
||
try:
|
||
await _send_due_reports(bot, database)
|
||
except asyncio.CancelledError:
|
||
raise
|
||
except Exception:
|
||
logger.exception("Ошибка цикла ежедневных отчётов")
|
||
await asyncio.sleep(30)
|
||
|
||
|
||
async def _send_due_reports(bot: Bot, database: Database) -> None:
|
||
users = await database.users_for_reports()
|
||
for user in users:
|
||
try:
|
||
timezone = ZoneInfo(user["timezone"])
|
||
report_at = time.fromisoformat(user["report_time"])
|
||
except (ZoneInfoNotFoundError, ValueError):
|
||
logger.error("Некорректные настройки времени у пользователя %s", user["user_id"])
|
||
continue
|
||
|
||
now = datetime.now(timezone)
|
||
if now.time().replace(tzinfo=None) < report_at:
|
||
continue
|
||
local_date = now.date().isoformat()
|
||
day_start, day_end = day_bounds(now)
|
||
month_start, month_end = month_bounds(now)
|
||
day = await database.totals(user["user_id"], day_start, day_end)
|
||
month = await database.totals(user["user_id"], month_start, month_end)
|
||
debt = await database.family_debt(user["user_id"])
|
||
report_text = daily_report_text(day, month, debt)
|
||
if not await database.report_was_sent(user["user_id"], local_date):
|
||
try:
|
||
await bot.send_message(
|
||
chat_id=user["chat_id"],
|
||
text=report_text,
|
||
reply_markup=main_keyboard(),
|
||
)
|
||
except TelegramForbiddenError:
|
||
logger.info("Пользователь %s заблокировал бота", user["user_id"])
|
||
await database.mark_report_sent(user["user_id"], local_date, now)
|
||
|
||
family_target = await database.family_report_target(user["user_id"])
|
||
if family_target is None:
|
||
continue
|
||
household_id, family_chat_id = family_target
|
||
if await database.family_report_was_sent(household_id, local_date):
|
||
continue
|
||
try:
|
||
await bot.send_message(chat_id=family_chat_id, text=report_text)
|
||
except TelegramForbiddenError:
|
||
logger.info("Бот не может писать в семейный чат %s", family_chat_id)
|
||
await database.mark_family_report_sent(household_id, local_date, now)
|
||
|
||
|
||
async def stop_task(task: asyncio.Task) -> None:
|
||
task.cancel()
|
||
with suppress(asyncio.CancelledError):
|
||
await task
|