Passed
Push — master ( 175e2e...724686 )
by Daniil
01:42
created

scheduler.Schedule.generate()   B

Complexity

Conditions 5

Size

Total Lines 31
Code Lines 26

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 26
dl 0
loc 31
rs 8.7893
c 0
b 0
f 0
cc 5
nop 1
1
import re
2
import time
3
from datetime import datetime
4
from datetime import timedelta
5
from typing import List
6
7
import requests
8
import schedule
9
from bs4 import BeautifulSoup
10
11
import logger
12
from bot import Bot
13
14
15
class Date:
16
    """
17
    Вспомогательный класс, содержащий строковые представления дат
18
    """
19
20
    @property
21
    def today(self) -> str:
22
        """
23
        Возвращает сегодняшнюю дату в формате ГГГГ-ММ-ДД
24
        """
25
        return datetime.today().strftime("%Y-%m-%d")
26
27
    @property
28
    def day_after_tomorrow(self) -> str:
29
        """
30
        Возвращает послезавтрашнюю дату в формате ГГГГ-ММ-ДД
31
        """
32
        return datetime.strftime(datetime.now() + timedelta(2), "%Y-%m-%d")
33
34
    @property
35
    def tomorrow(self) -> str:
36
        """
37
        Возвращает послезавтрашнюю дату в формате ГГГГ-ММ-ДД,
38
        если сегодня суббота, завтрашнюю в любой другой день
39
        """
40
        if datetime.today().weekday() == 5:
41
            return self.day_after_tomorrow
42
        return datetime.strftime(datetime.now() + timedelta(1), "%Y-%m-%d")
43
44
45
class Schedule:
46
47
    """Класс, переводящий расписание из сырой веб-страницы в читаемую строку
48
49
    Attributes:
50
        date (str): Дата в формате ГГГГ-ММ-ДД, используемая для получения расписания
51
        gid (str): Идентификатор группы, для которой нужно получать расписание
52
    """
53
54
    def __init__(self, date: str, gid: str = "324"):
55
        self.date = date
56
        self.gid = gid
57
        self.log = logger.init_logger()
58
        self.raw = None
59
60
    def get_raw(self):
61
        """Подключается к серверу и получает расписание как объект вебскрапера
62
        """
63
        request = requests.get(
64
            "http://rating.ivpek.ru/timetable/timetable/show",
65
            params={"date": self.date, "gid": self.gid},
66
        )
67
        soup = BeautifulSoup(request.text, "lxml")
68
        self.raw = soup
69
70
    def is_exist(self) -> bool:
71
        """
72
        Проверяет наличие расписания, основываясь на присутствии плашек
73
        "Расписание отстутствует" и "Расписание составлено, но не опубликовано" и
74
        содержимом таблицы с расписанием
75
76
        Returns:
77
            bool: Флаг, указывающий на существование расписания
78
        """
79
        soup = self.raw
80
        warn = soup.find_all("div", {"class": "msg warning"})
81
        err = soup.find_all("div", {"class": "msg error"})
82
        lessons = soup.find_all("div", {"id": "lesson"})
83
        if warn or err or not lessons:
84
            self.log.info("Расписание отсутствует.")
85
            return False
86
        return True
87
88
    def clean(self) -> List[list]:
89
        """Чистит объект вебскрапера от мусорных данных и оставляет только то,
90
        что относится к расписанию
91
92
        Returns:
93
            List[list]: Список со списками. Каждый вложенный список описывает пару (
94
            номер, название предмета, преподавателя, кабинет)
95
        """
96
        soup = self.raw
97
        for span in soup.find_all("span", {"class": "ldur"}):
98
            span.decompose()
99
        for br in soup.find_all("br"):
100
            soup.br.insert_before(" ")
101
            br.decompose()
102
        soup = soup.find_all("table", {"class": "tbl"})[1]
103
        sch = []
104
        rows = soup.find_all("tr")
105
        for row in rows:
106
            cols = row.find_all("td")
107
            cols = [el.text.strip() for el in cols]
108
            sch.append([el for el in cols if el])
109
        sch = [el for el in sch if len(el) > 1]
110
        return sch
111
112
    def generate(self) -> str:
113
        """Собирает расписание в читаемую строку
114
        """
115
        sch = self.clean()
116
        msg = ""
117
        replacements = {
118
            "Лекция": "(Л)",
119
            "Лабораторная работа": "(Л/Р)",
120
            "Зачет": "(З)",
121
            "Диф. зачет": "(ДифЗ)",
122
            "Диф.зачет": "(ДифЗ)",
123
            "Экзамен": "(Э)",
124
            "(английский язык)": "",
125
        }
126
        for i in range(len(sch)):
127
            for j in range(len(sch[i])):
128
                item = sch[i][j]
129
                for k, v in replacements.items():
130
                    item = re.sub(k, v, item)
131
                if re.findall("Иностранный язык", item):
132
                    lesson = re.compile(r"(?<=\()\D+(?=\))").findall(item)
133
                    item = (
134
                        f"Английский язык ({lesson[1]}) "
135
                        f"Кузнецова И.Н/Коротина М.А. 12/13а"
136
                    )
137
                    sch[i][j + 1] = ""
138
                msg += f"{item} "
139
            msg += "\n"
140
        date = datetime.strptime(self.date, "%Y-%m-%d").strftime("%d.%m.%Y")
141
        msg = f"Расписание на {date}:\n{msg}"
142
        return msg
143
144
    def send(self):
145
        """Отправляет расписание в активную беседу
146
        и в ЛС подписчикам рассылки "Расписание"
147
        """
148
        bot = Bot()
149
        sch = self.generate()
150
        if sch:
151
            bot.send_mailing(slug="schedule", text=sch)
152
            bot.send_message(msg=sch, pid=bot.cid)
153
154
155
def listen():
156
    """Слушает сервер на предмет наличия расписания.
157
    Если находит - отправляет, иначе ждет 15 минут
158
    """
159
    d = Date()
160
    sch = Schedule(d.tomorrow)
161
    sch.get_raw()
162
    if sch.is_exist():
163
        sch.log.info("Расписание опубликовано")
164
        sch.send()
165
    else:
166
        time.sleep(15 * 60)
167
168
169
if __name__ == "__main__":
170
    schedule.every().day.at("09:20").do(listen)
171
    while True:
172
        schedule.run_pending()
173