Files

146 lines
5.5 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.
import asyncio
from random import randint
from datetime import datetime, timedelta
from aiogram import Bot, types
from config import Config
from logging import getLogger
from models.state import BotState
from services.schedule_service import ScheduleService
logger = getLogger(__name__)
class WatcherService:
def __init__(self, state: BotState, bot: Bot):
self.state = state
self.bot = bot
self.schedule_service = ScheduleService()
async def start(self):
"""Запуск слежки"""
if self.state.watcher_work:
return
self.state.watcher_work = True
self.state.watcher_task = asyncio.create_task(self._watcher_loop())
logger.info("Watcher запущен")
async def stop(self):
"""Остановка слежки"""
if not self.state.watcher_work:
return
self.state.watcher_work = False
if self.state.watcher_task:
self.state.watcher_task.cancel()
try:
await self.state.watcher_task
except asyncio.CancelledError:
pass
logger.info("Watcher остановлен")
@staticmethod
def _next_delay() -> int:
return Config.WATCHER_INTERVAL_SEC + randint(
Config.WATCHER_RANDOM_DELAY_MIN,
Config.WATCHER_RANDOM_DELAY_MAX,
)
@staticmethod
def _get_target_date_with_weekend_handling(days_ahead: int) -> datetime:
"""
Получить целевую дату с учетом выходных.
Если целевая дата - воскресенье, переносится на понедельник.
"""
target = (datetime.now() + timedelta(days=days_ahead)).replace(
hour=0, minute=0, second=0, microsecond=0
)
# weekday() returns 6 for Sunday
if target.weekday() == 6:
target += timedelta(days=1)
return target
async def _watcher_loop(self):
"""Основной цикл слежки за появлением PDF на Google Drive."""
while self.state.watcher_work:
try:
nothing_found = await self._check_all_groups()
if nothing_found:
delay = self._next_delay()
logger.info(f"PDF/расписание не найдено, следующая проверка через {delay} с")
await asyncio.sleep(delay)
else:
logger.info("Расписание найдено и отправлено, останавливаем watcher")
self.state.watcher_work = False
break
except asyncio.CancelledError:
break
except Exception as e:
logger.error(f"Ошибка в watcher_loop: {e}")
await asyncio.sleep(60)
async def _check_all_groups(self) -> bool:
"""
Возвращает True, если расписание ещё недоступно ни для одной группы.
Возвращает False, если хотя бы одной группе отправили расписание.
"""
days_ahead = self.state.watcher_days_ahead
target = self._get_target_date_with_weekend_handling(days_ahead)
logger.info(
f"Проверяем Google Drive на расписание за {target.strftime('%d.%m.%Y')} "
f"(дней вперед: {days_ahead})"
)
if not await self.schedule_service.is_published_for(days_ahead):
return True
found_any = False
for group, chat_id in Config.GROUP_CHATS.items():
logger.info(
f"Проверяем расписание для {group} на {target.strftime('%d.%m.%Y')}"
)
if await self._check_group_schedule(group, chat_id, days_ahead):
found_any = True
return not found_any
async def _check_group_schedule(self, group: str, chat_id: int, days_ahead: int) -> bool:
target = self._get_target_date_with_weekend_handling(days_ahead)
text, url, data_day, data_month = await self.schedule_service.get_schedule(
group, target.day
)
if not self.schedule_service.is_schedule_missing(text):
msg = await self.bot.send_message(
chat_id,
(
f"🔔 Авто-расписание для {group} "
f"на {data_day:02d}.{data_month:02d}\n\n{text}"
),
parse_mode="HTML",
)
try:
await self.bot.pin_chat_message(
chat_id, msg.message_id, disable_notification=False
)
except Exception as e:
logger.warning(f"Не удалось закрепить сообщение в {chat_id}: {e}")
return True
png, url, data_day, data_month = await self.schedule_service.get_pschedule(
group, 0
)
if png:
await self.bot.send_photo(
chat_id,
types.BufferedInputFile(png, filename=f"{group}.png"),
caption=(
f"🔔 АВАРИЙНЫЙ РЕЖИМ\n\n"
f"Авто-расписание для {group} "
f"на {data_day:02d}.{data_month:02d}"
),
)
return True
return False