It's version 0.8.3 bug fix ТЕПЕРЬ УЖ точно восстановлена система слежения расписания
This commit is contained in:
@@ -116,8 +116,8 @@ def register_handlers(dp: Dispatcher, state: BotState, bot: Bot) -> int:
|
|||||||
save_message(msg.chat.id, msg.message_id)
|
save_message(msg.chat.id, msg.message_id)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.exception(f"Ошибка при обработке uid={uid}: {e}")
|
logger.exception(f"Ошибка при обработке uid={uid}: {e}")
|
||||||
logger.info("Завершён цикл periodic_task, спим 6 часов")
|
logger.info("Завершён цикл periodic_task, спим 24 часов")
|
||||||
await asyncio.sleep(21600) # каждые 6 часов
|
await asyncio.sleep(86400) # каждые 24 часов
|
||||||
|
|
||||||
asyncio.create_task(periodic_task())
|
asyncio.create_task(periodic_task())
|
||||||
return 0
|
return 0
|
||||||
|
|||||||
+20
-3
@@ -113,16 +113,33 @@ def register_handlers(dp: Dispatcher, state: BotState, bot: Bot):
|
|||||||
async def power_control(message: types.Message):
|
async def power_control(message: types.Message):
|
||||||
args = message.text.split()
|
args = message.text.split()
|
||||||
if len(args) < 2:
|
if len(args) < 2:
|
||||||
|
days = state.watcher_days_ahead
|
||||||
status = "включена" if state.watcher_work else "выключена"
|
status = "включена" if state.watcher_work else "выключена"
|
||||||
await message.answer(f"⏱️ Слежка расписания: {status}")
|
await message.answer(f"⏱️ Слежка расписания: {status} (на {days} дн.)")
|
||||||
return
|
return
|
||||||
|
|
||||||
command = args[1].lower()
|
command = args[1].lower()
|
||||||
watcher_service = WatcherService(state, bot)
|
watcher_service = WatcherService(state, bot)
|
||||||
|
|
||||||
if command == "on" and not state.watcher_work:
|
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 watcher_service.start()
|
||||||
await message.answer("✅ Слежка расписания включена")
|
await message.answer(f"✅ Слежка расписания включена (на {days} дн.)")
|
||||||
|
else:
|
||||||
|
await message.answer(f"✅ Количество дней изменено на {days} дн.")
|
||||||
elif command == "off" and state.watcher_work:
|
elif command == "off" and state.watcher_work:
|
||||||
await watcher_service.stop()
|
await watcher_service.stop()
|
||||||
await message.answer("❌ Слежка расписания выключена")
|
await message.answer("❌ Слежка расписания выключена")
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ class BotState:
|
|||||||
last_pinned: Dict[str, int] = None
|
last_pinned: Dict[str, int] = None
|
||||||
watcher_work: bool = False
|
watcher_work: bool = False
|
||||||
watcher_task: Optional[Task] = None
|
watcher_task: Optional[Task] = None
|
||||||
|
watcher_days_ahead: int = 1
|
||||||
file_id_cache: Dict[str, str] = None
|
file_id_cache: Dict[str, str] = None
|
||||||
last_day: Dict[str, int] = None
|
last_day: Dict[str, int] = None
|
||||||
last_clip_hash: Dict[str, str] = None
|
last_clip_hash: Dict[str, str] = None
|
||||||
|
|||||||
@@ -36,13 +36,16 @@ class ScheduleService:
|
|||||||
target = target.replace(day=int(day_offset))
|
target = target.replace(day=int(day_offset))
|
||||||
return target.replace(hour=0, minute=0, second=0, microsecond=0)
|
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(
|
async def _load_pdf_for_date(
|
||||||
self, day_offset: int = 1
|
self, day_offset: int = 0
|
||||||
) -> Tuple[Optional[bytes], Optional[str], int, int]:
|
) -> 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
|
day, month = target.day, target.month
|
||||||
|
|
||||||
drive_file = await self.drive.find_for_date(target)
|
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"
|
url = f"https://drive.google.com/file/d/{drive_file.file_id}/view"
|
||||||
return self._pdf_cache[drive_file.file_id], url, day, month
|
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
|
@staticmethod
|
||||||
def exact_group_regex(group: str) -> re.Pattern:
|
def exact_group_regex(group: str) -> re.Pattern:
|
||||||
pattern = rf"(^|{BOUNDARY}){re.escape(group)}({BOUNDARY}|$)"
|
pattern = rf"(^|{BOUNDARY}){re.escape(group)}({BOUNDARY}|$)"
|
||||||
@@ -108,7 +130,7 @@ class ScheduleService:
|
|||||||
return f"📅 Расписание для {day} числа:\n<pre>{body}</pre>"
|
return f"📅 Расписание для {day} числа:\n<pre>{body}</pre>"
|
||||||
|
|
||||||
async def is_published_for(self, day_offset: int = 0) -> bool:
|
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
|
return await self.drive.find_for_date(target) is not None
|
||||||
|
|
||||||
async def get_schedule(
|
async def get_schedule(
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
from random import randint
|
from random import randint
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
from aiogram import Bot, types
|
from aiogram import Bot, types
|
||||||
|
|
||||||
from config import Config
|
from config import Config
|
||||||
from logging import getLogger
|
from logging import getLogger
|
||||||
from models.state import BotState
|
from models.state import BotState
|
||||||
@@ -47,6 +47,20 @@ class WatcherService:
|
|||||||
Config.WATCHER_RANDOM_DELAY_MAX,
|
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):
|
async def _watcher_loop(self):
|
||||||
"""Основной цикл слежки за появлением PDF на Google Drive."""
|
"""Основной цикл слежки за появлением PDF на Google Drive."""
|
||||||
while self.state.watcher_work:
|
while self.state.watcher_work:
|
||||||
@@ -71,12 +85,14 @@ class WatcherService:
|
|||||||
Возвращает True, если расписание ещё недоступно ни для одной группы.
|
Возвращает True, если расписание ещё недоступно ни для одной группы.
|
||||||
Возвращает False, если хотя бы одной группе отправили расписание.
|
Возвращает 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(
|
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
|
return True
|
||||||
|
|
||||||
found_any = False
|
found_any = False
|
||||||
@@ -84,14 +100,15 @@ class WatcherService:
|
|||||||
logger.info(
|
logger.info(
|
||||||
f"Проверяем расписание для {group} на {target.strftime('%d.%m.%Y')}"
|
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
|
found_any = True
|
||||||
|
|
||||||
return not found_any
|
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(
|
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):
|
if not self.schedule_service.is_schedule_missing(text):
|
||||||
msg = await self.bot.send_message(
|
msg = await self.bot.send_message(
|
||||||
|
|||||||
@@ -36,6 +36,4 @@ if __name__ == "__main__":
|
|||||||
|
|
||||||
|
|
||||||
def get_db():
|
def get_db():
|
||||||
if Config.DISABLE_STORAGE:
|
|
||||||
raise RuntimeError("Хранение отключено (DISABLE_STORAGE=1)")
|
|
||||||
return sqlite3.connect(DIR)
|
return sqlite3.connect(DIR)
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ from config import Config
|
|||||||
def save_message(chat_id: int, message_id: int):
|
def save_message(chat_id: int, message_id: int):
|
||||||
if Config.DISABLE_STORAGE:
|
if Config.DISABLE_STORAGE:
|
||||||
return
|
return
|
||||||
|
if True:
|
||||||
|
return
|
||||||
from .DB import get_db
|
from .DB import get_db
|
||||||
|
|
||||||
db = get_db()
|
db = get_db()
|
||||||
@@ -17,6 +19,8 @@ def save_message(chat_id: int, message_id: int):
|
|||||||
def load_messages():
|
def load_messages():
|
||||||
if Config.DISABLE_STORAGE:
|
if Config.DISABLE_STORAGE:
|
||||||
return []
|
return []
|
||||||
|
if True:
|
||||||
|
return []
|
||||||
from .DB import get_db
|
from .DB import get_db
|
||||||
|
|
||||||
db = get_db()
|
db = get_db()
|
||||||
@@ -31,6 +35,8 @@ def load_messages():
|
|||||||
def clear_messages():
|
def clear_messages():
|
||||||
if Config.DISABLE_STORAGE:
|
if Config.DISABLE_STORAGE:
|
||||||
return
|
return
|
||||||
|
if True:
|
||||||
|
return
|
||||||
from .DB import get_db
|
from .DB import get_db
|
||||||
|
|
||||||
db = get_db()
|
db = get_db()
|
||||||
|
|||||||
@@ -4,8 +4,6 @@ _DEFAULT_GROUP = "30тс"
|
|||||||
|
|
||||||
|
|
||||||
def save_user(user_id: int, group: str = _DEFAULT_GROUP):
|
def save_user(user_id: int, group: str = _DEFAULT_GROUP):
|
||||||
if Config.DISABLE_STORAGE:
|
|
||||||
return
|
|
||||||
from .DB import get_db
|
from .DB import get_db
|
||||||
|
|
||||||
db = 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):
|
def set_group(user_id: int, group: str = _DEFAULT_GROUP):
|
||||||
if Config.DISABLE_STORAGE:
|
|
||||||
return
|
|
||||||
from .DB import get_db
|
from .DB import get_db
|
||||||
|
|
||||||
db = 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:
|
def get_group(user_id: int, default: str = _DEFAULT_GROUP) -> str:
|
||||||
if Config.DISABLE_STORAGE:
|
|
||||||
return default
|
|
||||||
from .DB import get_db
|
from .DB import get_db
|
||||||
|
|
||||||
db = get_db()
|
db = get_db()
|
||||||
|
|||||||
Reference in New Issue
Block a user