123
This commit is contained in:
@@ -1 +1,3 @@
|
||||
__pycache__
|
||||
lecture.json
|
||||
result.txt
|
||||
Vendored
+3
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"languageTool.language": "ru"
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"version": "0.2",
|
||||
"ignorePaths": [],
|
||||
"dictionaryDefinitions": [],
|
||||
"dictionaries": [
|
||||
"en",
|
||||
"ru"
|
||||
],
|
||||
"words": [],
|
||||
"ignoreWords": [],
|
||||
"import": [],
|
||||
"language": "en,ru"
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
import json
|
||||
import requests
|
||||
import time
|
||||
import tqdm
|
||||
|
||||
class Non200StatusCodeError(Exception):
|
||||
def __init__(self, status_code, message="Сервер вернул не 200 код"):
|
||||
self.status_code = status_code
|
||||
self.message = message
|
||||
super().__init__(self.message)
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.message}. Код статуса: {self.status_code}"
|
||||
|
||||
def do_request(name, start, end):
|
||||
response = requests.post(
|
||||
"https://ulsu.ru/oto/api/production/external/createKtalkMeeting.php",
|
||||
data={
|
||||
"name": name,
|
||||
"desc": "Встреча создана средствами API (Copyright © 2023 Kravenrus), обработка расписания © Kozhilya",
|
||||
"startAt": start,
|
||||
"endAt": end,
|
||||
"stateAnonymous": True,
|
||||
"stateSessionHalls": False,
|
||||
"stateLobby": False,
|
||||
"stateSip": False,
|
||||
"stateAutoRecording": True,
|
||||
"stateAudioPolicy": False,
|
||||
"stateVideoPolicy": False,
|
||||
"stateScreenSharePolicy": False,
|
||||
"pinCode": False
|
||||
},
|
||||
headers={ \
|
||||
"Referer": "https://ulsu.ru/oto/ktalk/"
|
||||
}
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
time.sleep(1)
|
||||
data = response.json()
|
||||
return f"https://{data['meetingData']['global']['space']}.ktalk.ru/{data['createdMeeting']['roomName']}"
|
||||
|
||||
else:
|
||||
raise Non200StatusCodeError(response.status_code, "Ответ сервера: " + response.text)
|
||||
|
||||
if __name__ == "__main__":
|
||||
lines = []
|
||||
|
||||
with open("lecture.json", "r", encoding="utf-8") as f:
|
||||
lectures = json.load(f)
|
||||
|
||||
last_auditorium = ""
|
||||
last_day_of_week = ""
|
||||
|
||||
for lecture in tqdm.tqdm(lectures):
|
||||
url = do_request(lecture['name'], lecture['start'], lecture['end'])
|
||||
|
||||
if lecture['auditorium'] != last_auditorium:
|
||||
if last_auditorium != "":
|
||||
lines.append("\n========================\n")
|
||||
lines.append(lecture['auditorium'] + "\n")
|
||||
last_auditorium = lecture['auditorium']
|
||||
last_day_of_week = ""
|
||||
|
||||
if lecture['day_of_week'] != last_day_of_week:
|
||||
prefix = f" {lecture['day_of_week']} "
|
||||
last_day_of_week = lecture['day_of_week']
|
||||
else:
|
||||
prefix = " " * 6
|
||||
|
||||
lines.append(f"{prefix} #{lecture['pair_number']} - {lecture['name']}\n")
|
||||
lines.append(" " * 12 + url + "\n")
|
||||
|
||||
with open("result.txt", "w", encoding="utf-8") as f:
|
||||
f.writelines(lines)
|
||||
@@ -1,36 +1,15 @@
|
||||
from datetime import datetime
|
||||
from structure import Structure
|
||||
|
||||
struct = Structure()
|
||||
worksheet = struct.document.worksheet("Пушк. 701")
|
||||
|
||||
range_name = struct.find_range(worksheet, False)
|
||||
part = struct.get_lectures_from_range(worksheet, range_name)
|
||||
|
||||
|
||||
|
||||
week = struct.get_week_number()
|
||||
|
||||
lectures = []
|
||||
|
||||
for worksheet in struct.get_worksheets():
|
||||
print(worksheet.title + "...")
|
||||
range_name = struct.find_range(worksheet, week == 1)
|
||||
part = struct.get_lectures_from_range(worksheet, range_name)
|
||||
lectures.extend(part)
|
||||
break
|
||||
|
||||
print("===============================================================")
|
||||
|
||||
for lecture in lectures:
|
||||
|
||||
print(f"{lecture['auditorium']} / {lecture['day_of_week']} / #{lecture['pair_number']} / {lecture['lecture']} / {lecture['teacher']}")
|
||||
|
||||
t = struct.find_teachers(lecture['teacher'])
|
||||
l = struct.find_disciplines(lecture['lecture'])
|
||||
|
||||
print("[Преподаватель] ", lecture['teacher'], "->", t)
|
||||
print("[Предмет] ", lecture['lecture'], "->", l)
|
||||
|
||||
lecture["start"], lecture["end"] = struct.get_lecture_time(lecture['day_of_week'], lecture['pair_number'])
|
||||
|
||||
formatted_date = datetime.fromisoformat(lecture["start"]).strftime("%H:%M %-d.%-m.%Y")
|
||||
lecture["name"] = f"{l} [{t}] ({formatted_date})"
|
||||
|
||||
for lecture in part:
|
||||
print("\n==========================")
|
||||
print(lecture)
|
||||
print()
|
||||
|
||||
lecture = struct.process_lecture(lecture)
|
||||
+4
-1
@@ -7,5 +7,8 @@
|
||||
"ЧТ",
|
||||
"ПТ",
|
||||
"СБ"
|
||||
]
|
||||
],
|
||||
"aliases": {
|
||||
"Введение в НОК": "Введение в специальности Н.О.К."
|
||||
}
|
||||
}
|
||||
+241
-73
@@ -4,9 +4,224 @@ from datetime import datetime, timedelta
|
||||
import json
|
||||
import random
|
||||
import time
|
||||
from util import find_in_list
|
||||
import re
|
||||
from functools import lru_cache
|
||||
|
||||
class Structure:
|
||||
@lru_cache(None)
|
||||
def partialize(s):
|
||||
s = s.lower().replace("ё", "е")
|
||||
p = re.split("[. ]+", s)
|
||||
return list(filter(lambda a: a, p))
|
||||
|
||||
def test_short(short: str, full: str) -> bool:
|
||||
short_parts = partialize(short)
|
||||
full_parts = partialize(full)
|
||||
|
||||
for i in range(min(len(short_parts), len(full_parts))):
|
||||
if not full_parts[i].startswith(short_parts[i]):
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def find_in_list(short: str, full_list: list) -> str|list:
|
||||
result = []
|
||||
|
||||
for item in full_list:
|
||||
if test_short(short, item):
|
||||
result.append(item)
|
||||
|
||||
if len(result) == 1:
|
||||
return result[0]
|
||||
elif short in result:
|
||||
return short
|
||||
else:
|
||||
return result
|
||||
|
||||
def find_teacher_by_date(input_string: str, target_date: str) -> str:
|
||||
"""
|
||||
Ищет преподавателя по целевой дате в строке с датами и ФИО.
|
||||
|
||||
:param input_string: Строка с датами и ФИО преподавателей.
|
||||
:param target_date: Целевая дата в формате "%d.%m".
|
||||
:return: ФИО преподавателя, связанного с целевой датой, или None, если дата не найдена.
|
||||
"""
|
||||
# Преобразуем целевую дату в формат "%d.%m"
|
||||
target_date_str = target_date#.strftime("%d.%m")
|
||||
|
||||
# Регулярное выражение для поиска дат в формате "%d.%m"
|
||||
date_pattern = re.compile(r"\d{2}\.\d{2}")
|
||||
|
||||
# Разделяем строку на строки по переносам
|
||||
lines = re.split(" {5,}", input_string)
|
||||
|
||||
# Проходим по каждой строке
|
||||
for i in range(len(lines)):
|
||||
# Разделяем строку на даты и ФИО
|
||||
parts = lines[i].strip().split()
|
||||
|
||||
# Извлекаем даты и ФИО
|
||||
dates = []
|
||||
teacher = ""
|
||||
for part in parts:
|
||||
if date_pattern.fullmatch(part.replace(",", "")): # Убираем запятые, если они есть
|
||||
dates.append(part.replace(",", "")) # Убираем запятые, если они есть
|
||||
else:
|
||||
teacher += part + " " # Собираем ФИО
|
||||
|
||||
# Проверяем, есть ли целевая дата в списке дат
|
||||
if target_date_str in dates:
|
||||
return teacher.strip(), i # Возвращаем ФИО преподавателя
|
||||
|
||||
# Если дата не найдена
|
||||
return input_string, 1
|
||||
|
||||
def shorten_name(name: str) -> str:
|
||||
parts = name.split()
|
||||
result = ""
|
||||
|
||||
for i in range(len(parts)):
|
||||
if i == 0:
|
||||
result = parts[0] + " "
|
||||
else:
|
||||
result += parts[i][0] + "."
|
||||
|
||||
return result
|
||||
|
||||
|
||||
class Singleton(type):
|
||||
_instances = {}
|
||||
def __call__(cls, *args, **kwargs):
|
||||
if cls not in cls._instances:
|
||||
cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
|
||||
return cls._instances[cls]
|
||||
|
||||
class Lecture:
|
||||
def __init__(self, auditorium: str, day_of_week: str, pair_number: str, lecture: str, teacher: str):
|
||||
self.struct = Structure()
|
||||
self.auditorium = auditorium
|
||||
self.day_of_week = day_of_week
|
||||
self.day_of_week_i = self.struct.settings["week"].index(day_of_week)
|
||||
self.pair_number = pair_number
|
||||
self.input_teacher = teacher
|
||||
self.input_lecture = lecture
|
||||
|
||||
print("[Пара] ", self.auditorium, "/", self.day_of_week, "/", self.pair_number)
|
||||
|
||||
self.process_time()
|
||||
|
||||
self.teacher = self.process_teacher()
|
||||
if self.teacher == False:
|
||||
self.complete = False
|
||||
print()
|
||||
return
|
||||
|
||||
self.lecture = self.process_lecture()
|
||||
if self.lecture == False:
|
||||
self.complete = False
|
||||
print()
|
||||
return
|
||||
|
||||
print("[Препод.] ", self.input_teacher, "->", self.teacher)
|
||||
print("[Предмет] ", self.input_lecture, "->", self.lecture)
|
||||
|
||||
self.name = self.format_name()
|
||||
self.complete = True
|
||||
|
||||
print()
|
||||
|
||||
def process_time(self):
|
||||
target_date = self.struct.monday + timedelta(days=self.day_of_week_i)
|
||||
self.date = target_date.strftime("%d.%m")
|
||||
|
||||
for call in self.struct.calls:
|
||||
if call["lesson"] == self.pair_number:
|
||||
start_time = datetime.fromisoformat("2025-01-01T" + call["start_time"])
|
||||
end_time = datetime.fromisoformat("2025-01-01T" + call["end_time"])
|
||||
|
||||
# Объединяем дату и время
|
||||
self.start = target_date.replace(
|
||||
hour=start_time.hour,
|
||||
minute=start_time.minute,
|
||||
second=start_time.second,
|
||||
microsecond=0,
|
||||
tzinfo=start_time.tzinfo
|
||||
).isoformat(timespec="milliseconds")
|
||||
self.end = target_date.replace(
|
||||
hour=end_time.hour,
|
||||
minute=end_time.minute,
|
||||
second=end_time.second,
|
||||
microsecond=0,
|
||||
tzinfo=end_time.tzinfo
|
||||
).isoformat(timespec="milliseconds")
|
||||
def process_teacher(self) -> str:
|
||||
teacher, self.teacher_index = find_teacher_by_date(self.input_teacher, self.date)
|
||||
|
||||
if teacher in self.struct.settings["aliases"].keys():
|
||||
teacher = self.struct.settings["aliases"][teacher]
|
||||
|
||||
variants = find_in_list(teacher, self.struct.teachers)
|
||||
|
||||
return self.select_variant(teacher, variants, True)
|
||||
def process_lecture(self) -> str:
|
||||
parts = re.split(" {5,}", self.input_lecture)
|
||||
|
||||
if self.teacher_index < len(parts):
|
||||
lecture = parts[self.teacher_index]
|
||||
else:
|
||||
lecture = parts[0]
|
||||
|
||||
if lecture in self.struct.settings["aliases"].keys():
|
||||
lecture = self.struct.settings["aliases"][lecture]
|
||||
|
||||
variants = find_in_list(lecture, self.struct.disciplines)
|
||||
|
||||
return self.select_variant(lecture, variants, False)
|
||||
|
||||
def format_name(self) -> str:
|
||||
formatted_date = datetime.fromisoformat(self.start).strftime("%H:%M %d.%m.%Y")
|
||||
return f"{self.lecture} [{shorten_name(self.teacher)}] ({formatted_date})"
|
||||
|
||||
def select_variant(self, search: str, variants: str|list, is_teacher: bool) -> bool|str:
|
||||
if isinstance(variants, str):
|
||||
return variants
|
||||
|
||||
print("Невозможно выбрать",
|
||||
"преподавателя" if is_teacher else "лекцию",
|
||||
"для значения",
|
||||
f'"{search}".',
|
||||
"Выберите вариант:" if len(variants) > 0 else "")
|
||||
|
||||
if len(variants) == 0:
|
||||
return False
|
||||
|
||||
for i in range(len(variants)):
|
||||
print(str(i + 1).rjust(3) + ".", variants[i])
|
||||
print(" 0. [Пропустить]")
|
||||
|
||||
choice = int(input("Введите значение: ")) - 1
|
||||
|
||||
if choice < 0:
|
||||
return False
|
||||
|
||||
return variants[choice]
|
||||
|
||||
def json(self) -> dict|None:
|
||||
return {
|
||||
"auditorium": self.auditorium,
|
||||
"day_of_week": self.day_of_week,
|
||||
"pair_number": self.pair_number,
|
||||
"input_lecture": self.input_lecture,
|
||||
"input_teacher": self.input_teacher,
|
||||
|
||||
|
||||
"teacher": self.teacher,
|
||||
"lecture": self.lecture,
|
||||
"start": self.start,
|
||||
"end": self.end,
|
||||
"name": self.name
|
||||
}
|
||||
|
||||
class Structure(metaclass=Singleton):
|
||||
def __init__(self):
|
||||
print("Подключение Google Sheets...", end=" ")
|
||||
gc = gspread.oauth()
|
||||
@@ -30,22 +245,15 @@ class Structure:
|
||||
self.calls = self.get_json("calls", "schedule")
|
||||
print("Готово.")
|
||||
|
||||
print("Данные загружены!")
|
||||
self.monday = self.get_monday()
|
||||
self.week = self.get_week_number()
|
||||
|
||||
print("Данные загружены!")
|
||||
|
||||
def get_json(self, type, sub_type=None) -> dict:
|
||||
resp = requests.get(url="https://ulsu.ru/oto/api/production/internal/getEnterpriseData.php?type=" + type, params={})
|
||||
return resp.json()[type if sub_type is None else sub_type]
|
||||
|
||||
def get_week_number(self, target_date=None):
|
||||
if target_date is None:
|
||||
target_date = self.get_monday()
|
||||
|
||||
target_date = datetime.strptime(target_date, "%Y-%m-%d")
|
||||
first_week_start_date = datetime.strptime(self.settings["first_week"], "%Y-%m-%d")
|
||||
|
||||
return ((target_date - first_week_start_date).days // 7) % 2 + 1
|
||||
|
||||
def get_monday(self):
|
||||
today = datetime.today()
|
||||
days_until_next_monday = (7 - today.weekday()) % 7
|
||||
@@ -54,16 +262,17 @@ class Structure:
|
||||
# if days_until_next_monday == 0:
|
||||
# days_until_next_monday = 7
|
||||
|
||||
# Вычисляем дату следующего понедельника
|
||||
next_monday = today + timedelta(days=days_until_next_monday)
|
||||
return today + timedelta(days=days_until_next_monday)
|
||||
def get_week_number(self, target_date:datetime|None=None):
|
||||
if target_date is None:
|
||||
target_date = self.monday
|
||||
|
||||
# Возвращаем дату в формате "%Y-%m-%d"
|
||||
return next_monday.strftime("%Y-%m-%d")
|
||||
first_week_start_date = datetime.strptime(self.settings["first_week"], "%Y-%m-%d")
|
||||
|
||||
return ((target_date - first_week_start_date).days // 7) % 2 + 1
|
||||
|
||||
def get_worksheets(self):
|
||||
return self.document.worksheets()
|
||||
|
||||
def get_document_data(self, worksheet, range_name=None, max_retries=5, initial_backoff=1, maximum_backoff=32):
|
||||
retries = 0
|
||||
while retries < max_retries:
|
||||
@@ -79,8 +288,6 @@ class Structure:
|
||||
|
||||
backoff = min(initial_backoff * (2 ** (retries - 1)) + random.random(), maximum_backoff)
|
||||
time.sleep(backoff)
|
||||
|
||||
|
||||
def find_range(self, worksheet, first_week=True):
|
||||
def check(cell):
|
||||
if first_week:
|
||||
@@ -104,7 +311,7 @@ class Structure:
|
||||
if cell_value == "1":
|
||||
return "C%d:H%d" % (row+1, row+16)
|
||||
|
||||
def get_lectures_from_range(self, worksheet, range_name):
|
||||
def get_lectures_from_range(self, worksheet, range_name) -> list:
|
||||
# Получаем название аудитории из названия листа
|
||||
auditorium = worksheet.title
|
||||
|
||||
@@ -134,68 +341,29 @@ class Structure:
|
||||
for col_index, (lecture, teacher) in enumerate(zip(lecture_row, teacher_row)):
|
||||
if lecture.strip() and teacher.strip(): # Если есть и название лекции, и преподаватель
|
||||
day_of_week = days_of_week[col_index] # День недели
|
||||
lectures.append({
|
||||
"auditorium": auditorium,
|
||||
"day_of_week": day_of_week,
|
||||
"pair_number": pair_number,
|
||||
"lecture": lecture,
|
||||
"teacher": teacher
|
||||
})
|
||||
lectures.append(Lecture(auditorium, day_of_week, pair_number, lecture, teacher))
|
||||
|
||||
lectures = sorted(lectures, key=lambda x: (day_order[x["day_of_week"]], x["pair_number"]))
|
||||
lectures = sorted(lectures, key=lambda x: (x.day_of_week_i, x.pair_number))
|
||||
|
||||
return lectures
|
||||
|
||||
|
||||
|
||||
def find_teachers(self, short_name):
|
||||
return find_in_list(short_name, self.teachers)
|
||||
if __name__ == "__main__":
|
||||
struct = Structure()
|
||||
|
||||
def find_disciplines(self, short_name):
|
||||
return find_in_list(short_name, self.disciplines)
|
||||
week = struct.get_week_number()
|
||||
|
||||
lectures = []
|
||||
|
||||
def get_lecture_time(self, day_of_week, pair_number, monday=None):
|
||||
"""
|
||||
Возвращает время начала и окончания лекции для указанного дня недели и номера пары.
|
||||
for worksheet in struct.get_worksheets():
|
||||
print(worksheet.title + "...")
|
||||
range_name = struct.find_range(worksheet, week == 1)
|
||||
part = struct.get_lectures_from_range(worksheet, range_name)
|
||||
|
||||
:param day_of_week: День недели (0 - понедельник, 1 - вторник, ..., 6 - воскресенье).
|
||||
:param pair_number: Номер пары (начиная с 1).
|
||||
:return: Кортеж (start_time, end_time) в формате "2025-03-09T08:00:00.000+04:00".
|
||||
"""
|
||||
if monday is None:
|
||||
monday = datetime.strptime(self.get_monday(), "%Y-%m-%d")
|
||||
lectures.extend(part)
|
||||
|
||||
# Определяем дату для указанного дня недели
|
||||
target_date = monday + timedelta(days=self.settings["week"].index(day_of_week))
|
||||
lectures = list(filter(lambda l: l.complete, lectures))
|
||||
|
||||
# Находим время для указанной пары
|
||||
for call in self.calls:
|
||||
if call["lesson"] == pair_number:
|
||||
start_time = datetime.fromisoformat("2025-01-01T" + call["start_time"])
|
||||
end_time = datetime.fromisoformat("2025-01-01T" + call["end_time"])
|
||||
|
||||
# Объединяем дату и время
|
||||
start_datetime = target_date.replace(
|
||||
hour=start_time.hour,
|
||||
minute=start_time.minute,
|
||||
second=start_time.second,
|
||||
microsecond=0,
|
||||
tzinfo=start_time.tzinfo
|
||||
)
|
||||
end_datetime = target_date.replace(
|
||||
hour=end_time.hour,
|
||||
minute=end_time.minute,
|
||||
second=end_time.second,
|
||||
microsecond=0,
|
||||
tzinfo=end_time.tzinfo
|
||||
)
|
||||
|
||||
# Форматируем результат
|
||||
return (
|
||||
start_datetime.isoformat(timespec="milliseconds"),
|
||||
end_datetime.isoformat(timespec="milliseconds")
|
||||
)
|
||||
|
||||
# Если пара не найдена
|
||||
raise ValueError(f"Пара с номером {pair_number} не найдена.")
|
||||
with open("lecture.json", "w", encoding="utf-8") as f:
|
||||
json.dump(list(map(lambda l: l.json(), lectures)), f, ensure_ascii=False, indent=4)
|
||||
Reference in New Issue
Block a user