It's my tg bot for schedule. version 0.1
This commit is contained in:
@@ -0,0 +1,62 @@
|
||||
import hashlib
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Optional, Tuple
|
||||
from playwright.async_api import async_playwright, ViewportSize, FloatRect
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ScheduleService:
|
||||
def __init__(self):
|
||||
self.base_url = "https://college.by/accounts/raspis/{mouth:02d}/{day:02d}-PODNAM.htm"
|
||||
|
||||
def _make_url(self, day: int = 0) -> Tuple[str, int, int]:
|
||||
"""Генерация URL для расписания"""
|
||||
d = datetime.now()
|
||||
if day == 0:
|
||||
if d.hour >= 12:
|
||||
d += timedelta(days=1)
|
||||
if d.weekday() == 6:
|
||||
d += timedelta(days=1)
|
||||
return self.base_url.format(day=d.day, mouth=d.month), d.day, d.month
|
||||
else:
|
||||
return self.base_url.format(day=int(day), mouth=d.month), int(day), int(d.month)
|
||||
|
||||
async def get_schedule(self, group: str, day_offset: int = 0) -> Tuple[Optional[bytes], str, int, int]:
|
||||
"""Получение скриншота расписания"""
|
||||
url, day, month = self._make_url(day_offset)
|
||||
|
||||
async with async_playwright() as p:
|
||||
browser = await p.chromium.launch(headless=True)
|
||||
context = await browser.new_context(viewport=ViewportSize(width=400, height=3000))
|
||||
page = await context.new_page()
|
||||
|
||||
try:
|
||||
response = await page.goto(url, wait_until="networkidle", timeout=30000)
|
||||
|
||||
if not response or response.status != 200:
|
||||
logger.warning(f"Ошибка загрузки страницы: {url}")
|
||||
return None, url, day, month
|
||||
|
||||
locator = page.locator("p", has_text=group).first
|
||||
if await locator.count() > 0:
|
||||
await locator.scroll_into_view_if_needed()
|
||||
box = await locator.bounding_box()
|
||||
|
||||
if box:
|
||||
clip_rect = FloatRect(
|
||||
x=float(max(box["x"] - 0, 0)),
|
||||
y=float(max(box["y"] - 0, 0)),
|
||||
width=float(box["width"] + 150),
|
||||
height=float(box["height"] + 100)
|
||||
)
|
||||
return await page.screenshot(clip=clip_rect), url, day, month
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при получении расписания: {e}")
|
||||
finally:
|
||||
await context.close()
|
||||
await browser.close()
|
||||
|
||||
return None, url, day, month
|
||||
@@ -0,0 +1,97 @@
|
||||
import asyncio
|
||||
import hashlib
|
||||
from datetime import datetime, timedelta
|
||||
from random import randint
|
||||
from aiogram import Bot
|
||||
from aiogram.types import BufferedInputFile
|
||||
from models.state import BotState
|
||||
from config import Config
|
||||
from services.schedule_service import ScheduleService
|
||||
import logging
|
||||
|
||||
|
||||
logger = logging.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 остановлен")
|
||||
|
||||
async def _watcher_loop(self):
|
||||
"""Основной цикл слежки"""
|
||||
while self.state.watcher_work:
|
||||
try:
|
||||
await self._check_all_groups()
|
||||
delay = randint(600, 700)
|
||||
await asyncio.sleep(delay)
|
||||
except asyncio.CancelledError:
|
||||
break
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка в watcher_loop: {e}")
|
||||
await asyncio.sleep(60)
|
||||
|
||||
def _get_target_day(self) -> datetime:
|
||||
"""Получение целевого дня"""
|
||||
now = datetime.now()
|
||||
target = now + timedelta(days=1)
|
||||
if target.weekday() == 6:
|
||||
target += timedelta(days=1)
|
||||
return target
|
||||
|
||||
|
||||
async def _check_all_groups(self):
|
||||
"""Проверка всех групп на изменения"""
|
||||
day = self._get_target_day()
|
||||
|
||||
for group, chat_id in Config.GROUP_CHATS.items():
|
||||
await self._check_group_schedule(group, chat_id, day)
|
||||
|
||||
async def _check_group_schedule(self, group: str, chat_id: int, day: int):
|
||||
"""Проверка расписания для конкретной группы"""
|
||||
|
||||
logger.info(f"Проверяем расписание для {group} на {day.strftime('%d.%m.%Y')}")
|
||||
|
||||
clip_png, url, data_day, data_mouth = await self.schedule_service.get_schedule(group, day.day)
|
||||
if clip_png:
|
||||
msg = await self.bot.send_photo(
|
||||
chat_id,
|
||||
BufferedInputFile(clip_png, filename=f"{group}.png"),
|
||||
caption=f"Авто-расписание для {group} на {data_day:02d}.{data_mouth:02d}"
|
||||
)
|
||||
await self.bot.pin_chat_message(chat_id, msg.message_id, disable_notification=True)
|
||||
|
||||
else:
|
||||
logger.warning(f"Не удалось получить расписание для {group}, {data_day}, {data_mouth}, {url}")
|
||||
return
|
||||
|
||||
|
||||
|
||||
#clip_hash = hashlib.md5(clip_png).hexdigest()
|
||||
|
||||
# Логика проверки изменений и отправки сообщений
|
||||
# ... (ваша существующая логика)
|
||||
Reference in New Issue
Block a user