Добавлено: загрузка расписания

This commit is contained in:
far-galaxy 2023-02-02 22:07:16 +03:00
parent c1a06a834b
commit 28cd1673ac
5 changed files with 314 additions and 44 deletions

40
bot.py
View File

@ -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()

View File

@ -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

View File

@ -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)

View File

@ -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
);

View File

@ -26,4 +26,6 @@ empty=К сожалению, такой группы нет ни в моей б
loading=Загружаю расписание...
loaded=Расписание загружено!
nogroup=Возможно, ты написал не ту группу, попробуй снова