Passed
Push — master ( a2d271...aed06e )
by Daniil
09:39
created

scheduler.send()   A

Complexity

Conditions 1

Size

Total Lines 12
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 9
dl 0
loc 12
rs 9.95
c 0
b 0
f 0
cc 1
nop 0
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
    Example:
54
        .. codeblock:: python
55
            d = Date() # Новый объект конструктора дат
56
            sch = Schedule(d.today) # Новый объект конструктора расписания
57
            sch.get_raw() # Соединение с сервером, получение свежего
58
            расписания на сегодня
59
            text = sch.generate() # Генерация расписания в читаемом виде
60
    """
61
62
    def __init__(self, date: str, gid: str = "324"):
63
        self.date = date
64
        self.gid = gid
65
        self.log = logger.init_logger()
66
        self.raw = None
67
68
    def get_raw(self):
69
        """Подключается к серверу и получает расписание как объект вебскрапера
70
        """
71
        request = requests.get(
72
            "http://rating.ivpek.ru/timetable/timetable/show",
73
            params={"date": self.date, "gid": self.gid},
74
        )
75
        soup = BeautifulSoup(request.text, "lxml")
76
        self.raw = soup
77
78
    def is_exist(self) -> bool:
79
        """
80
        Проверяет наличие расписания, основываясь на присутствии плашек
81
        "Расписание отстутствует" и "Расписание составлено, но не опубликовано" и
82
        содержимом таблицы с расписанием
83
84
        Returns:
85
            bool: Флаг, указывающий на существование расписания
86
        """
87
        soup = self.raw
88
        warn = soup.find_all("div", {"class": "msg warning"})
89
        err = soup.find_all("div", {"class": "msg error"})
90
        lessons = soup.find_all("div", {"id": "lesson"})
91
        if warn or err or not lessons:
92
            self.log.info("Расписание отсутствует.")
93
            return False
94
        return True
95
96
    def clean(self) -> List[list]:
97
        """Чистит объект вебскрапера от мусорных данных и оставляет только то,
98
        что относится к расписанию
99
100
        Returns:
101
            List[list]: Список со списками. Каждый вложенный список описывает пару (
102
            номер, название предмета, преподавателя, кабинет)
103
        """
104
        soup = self.raw
105
        for span in soup.find_all("span", {"class": "ldur"}):
106
            span.decompose()
107
        for br in soup.find_all("br"):
108
            soup.br.insert_before(" ")
109
            br.decompose()
110
        soup = soup.find_all("table", {"class": "tbl"})[1]
111
        sch = []
112
        rows = soup.find_all("tr")
113
        for row in rows:
114
            cols = row.find_all("td")
115
            cols = [el.text.strip() for el in cols]
116
            sch.append([el for el in cols if el])
117
        sch = [el for el in sch if len(el) > 1]
118
        return sch
119
120
    def generate(self) -> str:
121
        """Собирает расписание в читаемую строку
122
        """
123
        sch = self.clean()
124
        msg = ""
125
        replacements = {
126
            "Лекция": "(Л)",
127
            "Лабораторная работа": "(Л/Р)",
128
            "Зачет": "(З)",
129
            "Диф. зачет": "(ДифЗ)",
130
            "Диф.зачет": "(ДифЗ)",
131
            "Экзамен": "(Э)",
132
            "(английский язык)": "",
133
        }
134
        for i in range(len(sch)):
135
            for j in range(len(sch[i])):
136
                item = sch[i][j]
137
                for k, v in replacements.items():
138
                    item = re.sub(k, v, item)
139
                if re.findall("Иностранный язык", item):
140
                    lesson = re.compile(r"(?<=\()\D+(?=\))").findall(item)
141
                    item = (
142
                        f"Английский язык ({lesson[1]}) "
143
                        f"Кузнецова И.Н/Коротина М.А. 12/13а"
144
                    )
145
                    sch[i][j + 1] = ""
146
                msg += f"{item} "
147
            msg += "\n"
148
        date = datetime.strptime(self.date, "%Y-%m-%d").strftime("%d.%m.%Y")
149
        msg = f"Расписание на {date}:\n{msg}"
150
        return msg
151
152
153
def send():
154
    """Отправляет расписание в активную беседу
155
    и в ЛС подписчикам рассылки "Расписание"
156
    """
157
    bot = Bot()
158
    bot.auth()
159
    d = Date()
160
    s = Schedule(d.tomorrow)
161
    s.get_raw()
162
    sch = s.generate()
163
    bot.send_mailing(slug="schedule", text=sch)
164
    bot.send_message(msg=sch, pid=bot.cid)
165
166
167
def listen():
168
    """Слушает сервер на предмет наличия расписания.
169
    Если находит - отправляет, иначе ждет 15 минут
170
    """
171
    d = Date()
172
    sch = Schedule(d.tomorrow)
173
    sch.get_raw()
174
    while not sch.is_exist():
175
        time.sleep(15 * 60)
176
        sch.get_raw()
177
    sch.log.info("Расписание опубликовано")
178
    send()
179
180
181
if __name__ == "__main__":
182
    schedule.every().day.at("09:55").do(listen)
183
    while True:
184
        schedule.run_pending()
185