diff --git a/addons/x_days_to/handlers.py b/addons/x_days_to/handlers.py index 9d42482..299eb40 100644 --- a/addons/x_days_to/handlers.py +++ b/addons/x_days_to/handlers.py @@ -116,8 +116,8 @@ def register_handlers(dp: Dispatcher, state: BotState, bot: Bot) -> int: save_message(msg.chat.id, msg.message_id) except Exception as e: logger.exception(f"Ошибка при обработке uid={uid}: {e}") - logger.info("Завершён цикл periodic_task, спим 6 часов") - await asyncio.sleep(21600) # каждые 6 часов + logger.info("Завершён цикл periodic_task, спим 24 часов") + await asyncio.sleep(86400) # каждые 24 часов asyncio.create_task(periodic_task()) return 0 diff --git a/handlers/admin.py b/handlers/admin.py index ae537ad..55c7984 100644 --- a/handlers/admin.py +++ b/handlers/admin.py @@ -113,16 +113,33 @@ def register_handlers(dp: Dispatcher, state: BotState, bot: Bot): async def power_control(message: types.Message): args = message.text.split() if len(args) < 2: + days = state.watcher_days_ahead status = "включена" if state.watcher_work else "выключена" - await message.answer(f"⏱️ Слежка расписания: {status}") + await message.answer(f"⏱️ Слежка расписания: {status} (на {days} дн.)") return command = args[1].lower() watcher_service = WatcherService(state, bot) - if command == "on" and not state.watcher_work: - await watcher_service.start() - await message.answer("✅ Слежка расписания включена") + if command == "on": + # Проверяем, есть ли параметр количества дней + days = 1 + if len(args) > 2: + try: + days = int(args[2]) + if days < 1: + await message.answer("❌ Количество дней должно быть >= 1") + return + except ValueError: + await message.answer("❌ Неверный формат дней. Используйте: /power on 3") + return + + state.watcher_days_ahead = days + if not state.watcher_work: + await watcher_service.start() + await message.answer(f"✅ Слежка расписания включена (на {days} дн.)") + else: + await message.answer(f"✅ Количество дней изменено на {days} дн.") elif command == "off" and state.watcher_work: await watcher_service.stop() await message.answer("❌ Слежка расписания выключена") diff --git a/models/state.py b/models/state.py index 0e8b321..ebb8390 100644 --- a/models/state.py +++ b/models/state.py @@ -11,6 +11,7 @@ class BotState: last_pinned: Dict[str, int] = None watcher_work: bool = False watcher_task: Optional[Task] = None + watcher_days_ahead: int = 1 file_id_cache: Dict[str, str] = None last_day: Dict[str, int] = None last_clip_hash: Dict[str, str] = None diff --git a/services/schedule_service.py b/services/schedule_service.py index 97259f4..419f360 100644 --- a/services/schedule_service.py +++ b/services/schedule_service.py @@ -35,14 +35,17 @@ class ScheduleService: else: target = target.replace(day=int(day_offset)) return target.replace(hour=0, minute=0, second=0, microsecond=0) - - def _get_next_day(self, day_offeset: int = 0) -> datetime: - return datetime.now() + timedelta(days=day_offeset) + + + def _next_target_date(self, day_offset: int = 0) -> datetime: + return (datetime.now() + timedelta(days=day_offset)).replace(hour=0, minute=0, second=0, microsecond=0) + + async def _load_pdf_for_date( - self, day_offset: int = 1 + self, day_offset: int = 0 ) -> Tuple[Optional[bytes], Optional[str], int, int]: - target = self._get_next_day(day_offset) + target = self._resolve_target_date(day_offset) day, month = target.day, target.month drive_file = await self.drive.find_for_date(target) @@ -57,6 +60,25 @@ class ScheduleService: url = f"https://drive.google.com/file/d/{drive_file.file_id}/view" return self._pdf_cache[drive_file.file_id], url, day, month + async def _load_pdf_for_watcher( + self, day_offset: int = 1 + ) -> Tuple[Optional[bytes], Optional[str], int, int]: + target = self._next_target_date(day_offset) + day, month = target.day, target.month + + drive_file = await self.drive.find_for_date(target) + if not drive_file: + return None, None, day, month + + if drive_file.file_id not in self._pdf_cache: + self._pdf_cache[drive_file.file_id] = await self.drive.download_pdf( + drive_file.file_id + ) + + url = f"https://drive.google.com/file/d/{drive_file.file_id}/view" + return self._pdf_cache[drive_file.file_id], url, day, month + + @staticmethod def exact_group_regex(group: str) -> re.Pattern: pattern = rf"(^|{BOUNDARY}){re.escape(group)}({BOUNDARY}|$)" @@ -108,7 +130,7 @@ class ScheduleService: return f"📅 Расписание для {day} числа:\n
{body}
" async def is_published_for(self, day_offset: int = 0) -> bool: - target = self._resolve_target_date(day_offset) + target = self._next_target_date(day_offset) return await self.drive.find_for_date(target) is not None async def get_schedule( diff --git a/services/watcher_service.py b/services/watcher_service.py index c2df54c..694a7d6 100644 --- a/services/watcher_service.py +++ b/services/watcher_service.py @@ -1,8 +1,8 @@ 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 @@ -47,6 +47,20 @@ class WatcherService: 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: @@ -71,12 +85,14 @@ class WatcherService: Возвращает True, если расписание ещё недоступно ни для одной группы. Возвращает False, если хотя бы одной группе отправили расписание. """ - target = self.schedule_service._resolve_target_date(0) + 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"Проверяем Google Drive на расписание за {target.strftime('%d.%m.%Y')} " + f"(дней вперед: {days_ahead})" ) - if not await self.schedule_service.is_published_for(0): + if not await self.schedule_service.is_published_for(days_ahead): return True found_any = False @@ -84,14 +100,15 @@ class WatcherService: logger.info( f"Проверяем расписание для {group} на {target.strftime('%d.%m.%Y')}" ) - if await self._check_group_schedule(group, chat_id): + 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) -> bool: + 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, 0 + group, target.day ) if not self.schedule_service.is_schedule_missing(text): msg = await self.bot.send_message( diff --git a/storage/DB.py b/storage/DB.py index f7c13d8..5557784 100644 --- a/storage/DB.py +++ b/storage/DB.py @@ -36,6 +36,4 @@ if __name__ == "__main__": def get_db(): - if Config.DISABLE_STORAGE: - raise RuntimeError("Хранение отключено (DISABLE_STORAGE=1)") return sqlite3.connect(DIR) diff --git a/storage/message_storage.py b/storage/message_storage.py index 87fc054..d0d71ea 100644 --- a/storage/message_storage.py +++ b/storage/message_storage.py @@ -4,6 +4,8 @@ from config import Config def save_message(chat_id: int, message_id: int): if Config.DISABLE_STORAGE: return + if True: + return from .DB import get_db db = get_db() @@ -17,6 +19,8 @@ def save_message(chat_id: int, message_id: int): def load_messages(): if Config.DISABLE_STORAGE: return [] + if True: + return [] from .DB import get_db db = get_db() @@ -31,6 +35,8 @@ def load_messages(): def clear_messages(): if Config.DISABLE_STORAGE: return + if True: + return from .DB import get_db db = get_db() diff --git a/storage/users_storage.py b/storage/users_storage.py index 7eb158e..e13cc84 100644 --- a/storage/users_storage.py +++ b/storage/users_storage.py @@ -4,8 +4,6 @@ _DEFAULT_GROUP = "30тс" def save_user(user_id: int, group: str = _DEFAULT_GROUP): - if Config.DISABLE_STORAGE: - return from .DB import get_db db = get_db() @@ -17,8 +15,6 @@ def save_user(user_id: int, group: str = _DEFAULT_GROUP): def set_group(user_id: int, group: str = _DEFAULT_GROUP): - if Config.DISABLE_STORAGE: - return from .DB import get_db db = get_db() @@ -37,8 +33,6 @@ def set_group(user_id: int, group: str = _DEFAULT_GROUP): def get_group(user_id: int, default: str = _DEFAULT_GROUP) -> str: - if Config.DISABLE_STORAGE: - return default from .DB import get_db db = get_db()