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