Добавлено: загрузка расписания
This commit is contained in:
parent
c1a06a834b
commit
28cd1673ac
40
bot.py
40
bot.py
|
@ -55,6 +55,19 @@ class Bot:
|
|||
except telegram.error.BadRequest:
|
||||
pass
|
||||
|
||||
def edit(self, query: telegram.CallbackQuery, text=None, markup=None):
|
||||
if isinstance(text, str):
|
||||
try:
|
||||
query.edit_message_text(text)
|
||||
except telegram.error.BadRequest:
|
||||
pass
|
||||
|
||||
if isinstance(markup, telegram.ReplyKeyboardMarkup):
|
||||
try:
|
||||
query.edit_message_reply_markup(text)
|
||||
except telegram.error.BadRequest:
|
||||
pass
|
||||
|
||||
def checkMessages(self):
|
||||
"""Проверка и обработка входящих сообщений"""
|
||||
|
||||
|
@ -69,13 +82,7 @@ class Bot:
|
|||
tgId = query.from_user.id
|
||||
|
||||
if 'conf' in tag:
|
||||
success = self.shedule.uploadShedule(query, tag[5:], loc)
|
||||
self.answer(query)
|
||||
if success:
|
||||
# Код с загрузкой расписания
|
||||
self.tg_db.changeTag(tgId, 'ready')
|
||||
else:
|
||||
self.tg_db.changeTag(tgId, 'add')
|
||||
self.confirmGroup(query, tag)
|
||||
|
||||
if update.message:
|
||||
query = update.message
|
||||
|
@ -86,10 +93,10 @@ class Bot:
|
|||
if tag == 'not_started':
|
||||
self.start(query)
|
||||
|
||||
if tag == 'add':
|
||||
elif tag == 'add':
|
||||
self.addGroup(l9Id, query)
|
||||
|
||||
if query.text == 'Отмена':
|
||||
elif query.text == 'Отмена':
|
||||
# TODO: прописать отмену при отсутствующих группах
|
||||
self.tg_db.changeTag(tgId, 'ready')
|
||||
self.tg.sendMessage(
|
||||
|
@ -170,6 +177,21 @@ class Bot:
|
|||
reply_markup=Keyboard.cancel(),
|
||||
)
|
||||
|
||||
def confirmGroup(self, query: telegram.CallbackQuery, tag: str):
|
||||
"""Процесс подтверждения группы и загрузка расписания"""
|
||||
tgId = query.from_user.id
|
||||
self.answer(query)
|
||||
if query.data == 'yes':
|
||||
self.edit(query, loc['group']['loading'])
|
||||
self.shedule.loadShedule(
|
||||
tag[5:], query.message.date, config['first_week']
|
||||
)
|
||||
self.edit(query, loc['group']['loaded'], Keyboard.menu())
|
||||
self.tg_db.changeTag(tgId, 'ready')
|
||||
else:
|
||||
self.edit(query, loc['group']['nogroup'], Keyboard.cancel())
|
||||
self.tg_db.changeTag(tgId, 'add')
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
initLogger()
|
||||
|
|
|
@ -3,32 +3,196 @@ from bs4 import BeautifulSoup
|
|||
from ast import literal_eval
|
||||
import time
|
||||
import logging
|
||||
import datetime
|
||||
from itertools import groupby
|
||||
|
||||
logger = logging.getLogger('bot')
|
||||
|
||||
def findInRasp(req) -> dict:
|
||||
"""Поиск группы (преподавателя) в расписании"""
|
||||
logger.debug(f'Find {req}')
|
||||
|
||||
rasp = requests.Session()
|
||||
rasp.headers['User-Agent'] = 'Mozilla/5.0'
|
||||
hed = rasp.get("https://ssau.ru/rasp/")
|
||||
if hed.status_code == 200:
|
||||
soup = BeautifulSoup(hed.text, 'lxml')
|
||||
csrf_token = soup.select_one('meta[name="csrf-token"]')['content']
|
||||
else:
|
||||
return 'Error'
|
||||
|
||||
time.sleep(1)
|
||||
|
||||
rasp.headers['Accept'] = 'application/json'
|
||||
rasp.headers['X-CSRF-TOKEN'] = csrf_token
|
||||
result = rasp.post("https://ssau.ru/rasp/search", data = {'text':req})
|
||||
if result.status_code == 200:
|
||||
num = literal_eval(result.text)
|
||||
else:
|
||||
return 'Error'
|
||||
|
||||
if len(num) == 0:
|
||||
return None
|
||||
else:
|
||||
return num[0]
|
||||
def findInRasp(req: str):
|
||||
"""Поиск группы (преподавателя) в расписании"""
|
||||
logger.debug(f'Find {req}')
|
||||
|
||||
rasp = requests.Session()
|
||||
rasp.headers['User-Agent'] = 'Mozilla/5.0'
|
||||
hed = rasp.get("https://ssau.ru/rasp/")
|
||||
if hed.status_code == 200:
|
||||
soup = BeautifulSoup(hed.text, 'lxml')
|
||||
csrf_token = soup.select_one('meta[name="csrf-token"]')['content']
|
||||
else:
|
||||
return 'Error'
|
||||
|
||||
time.sleep(1)
|
||||
|
||||
rasp.headers['Accept'] = 'application/json'
|
||||
rasp.headers['X-CSRF-TOKEN'] = csrf_token
|
||||
result = rasp.post("https://ssau.ru/rasp/search", data={'text': req})
|
||||
if result.status_code == 200:
|
||||
num = literal_eval(result.text)
|
||||
else:
|
||||
return 'Error'
|
||||
|
||||
if len(num) == 0:
|
||||
return None
|
||||
else:
|
||||
return num[0]
|
||||
|
||||
|
||||
def connect(groupId: str, week: int, reconnects=0) -> BeautifulSoup:
|
||||
"""Подключение к сайту с расписанием"""
|
||||
logger.debug(
|
||||
f'Connecting to sasau, groupId = {groupId}, week N {week}, attempt {reconnects}'
|
||||
)
|
||||
rasp = requests.Session()
|
||||
rasp.headers['User-Agent'] = 'Mozilla/5.0'
|
||||
site = rasp.get(
|
||||
f'https://ssau.ru/rasp?groupId={groupId}&selectedWeek={week}'
|
||||
)
|
||||
if site.status_code == 200:
|
||||
contents = site.text.replace("\n", " ")
|
||||
soup = BeautifulSoup(contents, 'html.parser')
|
||||
return soup
|
||||
elif reconnects < 5:
|
||||
time.sleep(2)
|
||||
return connect(groupId, week, reconnects + 1)
|
||||
else:
|
||||
raise 'Connection to sasau failed!'
|
||||
|
||||
|
||||
def getGroupInfo(groupId: str) -> dict:
|
||||
"""Получение информации о группе (ID, полный номер, название направления)"""
|
||||
logger.debug(f'Getting group {groupId} information')
|
||||
soup = connect(groupId, 1)
|
||||
|
||||
group_spec_soup = soup.find(
|
||||
"div", {"class": "body-text info-block__description"}
|
||||
)
|
||||
group_spec = group_spec_soup.find("div").contents[0].text[1:]
|
||||
|
||||
group_name_soup = soup.find("h2", {"class": "h2-text info-block__title"})
|
||||
group_name = group_name_soup.text[1:5]
|
||||
|
||||
info = {
|
||||
'groupId': groupId,
|
||||
'groupName': group_name,
|
||||
'specName': group_spec,
|
||||
}
|
||||
|
||||
return info
|
||||
|
||||
|
||||
lesson_types = ('lect', 'lab', 'pract', 'other')
|
||||
teacher_columns = ('surname', 'name', 'midname', 'teacherId')
|
||||
|
||||
|
||||
def parseWeek(groupId: str, week: int, teachers=[]):
|
||||
|
||||
soup = connect(groupId, week)
|
||||
|
||||
dates_soup = soup.find_all("div", {"class": "schedule__head-date"})
|
||||
dates = []
|
||||
for date in dates_soup:
|
||||
date = datetime.datetime.strptime(
|
||||
date.contents[0].text, ' %d.%m.%Y'
|
||||
).date()
|
||||
dates.append(date)
|
||||
|
||||
blocks = soup.find("div", {"class": "schedule__items"})
|
||||
|
||||
blocks = [
|
||||
item
|
||||
for item in blocks
|
||||
if "schedule__head" not in item.attrs["class"]
|
||||
]
|
||||
|
||||
numInDay = 0
|
||||
weekday = 0
|
||||
times = []
|
||||
shedule = []
|
||||
week = []
|
||||
for block in blocks:
|
||||
if block.attrs['class'] == ['schedule__time']:
|
||||
begin = datetime.datetime.strptime(
|
||||
block.contents[0].text, ' %H:%M '
|
||||
).time()
|
||||
end = datetime.datetime.strptime(
|
||||
block.contents[1].text, ' %H:%M '
|
||||
).time()
|
||||
times.append((begin, end))
|
||||
numInDay += 1
|
||||
weekday = 0
|
||||
|
||||
if numInDay != 1:
|
||||
week = []
|
||||
else:
|
||||
begin_dt = datetime.datetime.combine(dates[weekday], begin)
|
||||
end_dt = datetime.datetime.combine(dates[weekday], end)
|
||||
|
||||
sub_pairs = block.find_all("div", {"class": "schedule__lesson"})
|
||||
|
||||
pair = []
|
||||
for sub_pair in sub_pairs:
|
||||
if sub_pair != []:
|
||||
name = sub_pair.select_one('div.schedule__discipline')
|
||||
lesson_type = lesson_types[
|
||||
int(name['class'][-1][-1]) - 1
|
||||
]
|
||||
name = name.text
|
||||
|
||||
place = sub_pair.select_one('div.schedule__place').text
|
||||
place = place if "on" not in place.lower() else "ONLINE"
|
||||
place = place if place != "" else None
|
||||
|
||||
teacher = sub_pair.select_one('.schedule__teacher a')
|
||||
teacherId = (
|
||||
teacher['href'][14:] if teacher != None else None
|
||||
)
|
||||
if teacher != None:
|
||||
if teacherId not in [
|
||||
str(i['teacherId']) for i in teachers
|
||||
]:
|
||||
teacher_name = teacher.text[:-4]
|
||||
t_info = findInRasp(teacher_name)['text'].split()
|
||||
t_info.append(teacherId)
|
||||
teachers.append(
|
||||
dict(zip(teacher_columns, t_info))
|
||||
)
|
||||
|
||||
groups = sub_pair.select_one('div.schedule__groups').text
|
||||
groups = "\n" + groups if 'групп' in groups else ""
|
||||
|
||||
comment = sub_pair.select_one(
|
||||
'div.schedule__comment'
|
||||
).text
|
||||
comment = comment if comment != "" else None
|
||||
|
||||
full_name = f'{name}{groups}'
|
||||
|
||||
lesson = {
|
||||
'numInDay': numInDay,
|
||||
'numInShedule': numInDay,
|
||||
'type': lesson_type,
|
||||
'name': full_name,
|
||||
'groupId': groupId,
|
||||
'begin': begin_dt,
|
||||
'end': end_dt,
|
||||
'teacherId': teacherId,
|
||||
'place': place,
|
||||
'addInfo': comment,
|
||||
}
|
||||
|
||||
shedule.append(lesson)
|
||||
|
||||
weekday += 1
|
||||
|
||||
shedule = sorted(shedule, key=lambda d: d['begin'])
|
||||
new_shedule = []
|
||||
|
||||
# Correct numInDay
|
||||
for date, day in groupby(shedule, key=lambda d: d['begin'].date()):
|
||||
day = list(day)
|
||||
first_num = day[0]['numInDay'] - 1
|
||||
for l in day:
|
||||
l['numInDay'] -= first_num
|
||||
new_shedule.append(l)
|
||||
return new_shedule, teachers
|
||||
|
|
|
@ -2,6 +2,7 @@ from .l9 import L9_DB
|
|||
from .a_ssau_parser import *
|
||||
import telegram
|
||||
from configparser import ConfigParser
|
||||
import datetime
|
||||
|
||||
|
||||
class Shedule_DB:
|
||||
|
@ -61,14 +62,22 @@ class Shedule_DB:
|
|||
else:
|
||||
return 'Empty'
|
||||
|
||||
def uploadShedule(
|
||||
self, query: telegram.CallbackQuery, groupId: str, loc: ConfigParser
|
||||
):
|
||||
if query.data == 'yes':
|
||||
query.edit_message_text(loc['group']['loading'])
|
||||
def loadShedule(self, groupId, date, first_week):
|
||||
week = date.isocalendar()[1] - first_week
|
||||
|
||||
else:
|
||||
query.edit_message_text(loc['group']['nogroup'])
|
||||
query.edit_message_reply_markup(Keyboard.cancel())
|
||||
self.db.execute(
|
||||
f'DELETE FROM `lessons` WHERE WEEK(`begin`, 1) = {date.isocalendar()[1]} AND groupId = {groupId};'
|
||||
)
|
||||
|
||||
return query.data == 'yes'
|
||||
t_info = self.db.get('teachers', None, teacher_columns)
|
||||
t_info = [dict(zip(teacher_columns, i)) for i in t_info]
|
||||
lessons, teachers = parseWeek(groupId, week, t_info)
|
||||
|
||||
g = getGroupInfo(groupId)
|
||||
self.db.insert('groups', g)
|
||||
|
||||
for t in teachers:
|
||||
self.l9lk.db.insert('teachers', t)
|
||||
|
||||
for l in lessons:
|
||||
self.l9lk.db.insert('lessons', l)
|
||||
|
|
|
@ -42,4 +42,77 @@ CREATE TABLE IF NOT EXISTS `groups_users` (
|
|||
KEY `gid_idx` (`groupId`),
|
||||
CONSTRAINT `gr_gu` FOREIGN KEY (`groupId`) REFERENCES `groups` (`groupId`) ON DELETE CASCADE ON UPDATE CASCADE,
|
||||
CONSTRAINT `l9_gu` FOREIGN KEY (`l9Id`) REFERENCES `users` (`l9Id`) ON DELETE CASCADE ON UPDATE CASCADE
|
||||
);
|
||||
|
||||
-- Преподаватели
|
||||
CREATE TABLE IF NOT EXISTS `teachers` (
|
||||
`teacherId` bigint NOT NULL,
|
||||
-- Идентификатор преподавателя, соответствует ID на сайте
|
||||
|
||||
`surname` varchar(45) DEFAULT 'Brzęczyszczykiewicz',
|
||||
`name` varchar(45) DEFAULT 'Grzegorz',
|
||||
`midname` varchar(45) DEFAULT 'Chrząszczyżewoszywicz',
|
||||
-- ФИО преподавателя
|
||||
|
||||
PRIMARY KEY (`teacherId`)
|
||||
);
|
||||
|
||||
-- Занятия
|
||||
CREATE TABLE IF NOT EXISTS `lessons` (
|
||||
`lessonId` bigint NOT NULL AUTO_INCREMENT,
|
||||
-- (service) Идентификатор занятия, устанавливается автоматически
|
||||
|
||||
`addedBy` varchar(4) DEFAULT 'ssau',
|
||||
-- Источник информации о занятии:
|
||||
-- 'ssau' - сайт Университета
|
||||
-- 'lk' - Личный кабинет сайта Университета
|
||||
-- '`l9Id`' - добавлено пользователем
|
||||
|
||||
`cancelled` bigint DEFAULT '0',
|
||||
-- Отметка, является ли занятие отменённым
|
||||
-- '0' - занятие НЕ отменено
|
||||
-- '`l9Id`' - занятие отменено пользователем
|
||||
|
||||
`migrated` bigint DEFAULT '0',
|
||||
-- Отметка, является ли занятие перенесённым
|
||||
-- '0' - занятие НЕ перенесено
|
||||
-- '`lessonId`' - занятие перенесено на другое время
|
||||
|
||||
`numInDay` int DEFAULT '1',
|
||||
-- Порядковый номер занятия в текущем дне
|
||||
|
||||
`numInShedule` int DEFAULT '1',
|
||||
-- Порядковый номер занятия относительно расписания на неделю
|
||||
|
||||
`type` char(5) DEFAULT 'other',
|
||||
-- Тип занятия:
|
||||
-- 'lect' - лекция
|
||||
-- 'pract' - практика (семинар)
|
||||
-- 'lab' - лабораторная работа
|
||||
-- 'other' - прочие
|
||||
|
||||
`name` text,
|
||||
-- Название занятия
|
||||
|
||||
`groupId` bigint NOT NULL,
|
||||
-- ID учебной группы
|
||||
|
||||
`begin` datetime NOT NULL,
|
||||
`end` datetime NOT NULL,
|
||||
-- Начало и конец занятия
|
||||
|
||||
`teacherId` bigint DEFAULT NULL,
|
||||
-- (опционально) ID преподавателя
|
||||
|
||||
`place` text,
|
||||
-- (опционально) Учебная аудитория
|
||||
|
||||
`addInfo` text,
|
||||
-- (опционально) Дополнительная информация
|
||||
|
||||
PRIMARY KEY (`lessonId`),
|
||||
KEY `gr_l_idx` (`groupId`),
|
||||
KEY `t_l_idx` (`teacherId`),
|
||||
CONSTRAINT `group_l` FOREIGN KEY (`groupId`) REFERENCES `groups` (`groupId`) ON DELETE RESTRICT ON UPDATE RESTRICT,
|
||||
CONSTRAINT `teach_l` FOREIGN KEY (`teacherId`) REFERENCES `teachers` (`teacherId`) ON DELETE SET NULL ON UPDATE CASCADE
|
||||
);
|
|
@ -26,4 +26,6 @@ empty=К сожалению, такой группы нет ни в моей б
|
|||
|
||||
loading=Загружаю расписание...
|
||||
|
||||
loaded=Расписание загружено!
|
||||
|
||||
nogroup=Возможно, ты написал не ту группу, попробуй снова
|
Reference in New Issue