From f478e3d777d611fb3fce177d05aba9bcd84e3e3b Mon Sep 17 00:00:00 2001 From: kozhilya Date: Tue, 11 Mar 2025 17:18:38 +0400 Subject: [PATCH] 123 --- .gitignore | 4 +- .vscode/settings.json | 3 + cspell.json | 13 ++ links.py | 75 ++++++++++ main.py | 39 ++--- settings.json | 5 +- structure.py | 328 +++++++++++++++++++++++++++++++----------- util.py | 2 +- 8 files changed, 356 insertions(+), 113 deletions(-) create mode 100644 .vscode/settings.json create mode 100644 cspell.json create mode 100644 links.py diff --git a/.gitignore b/.gitignore index ed8ebf5..e9e67c3 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ -__pycache__ \ No newline at end of file +__pycache__ +lecture.json +result.txt \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..54a1430 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "languageTool.language": "ru" +} \ No newline at end of file diff --git a/cspell.json b/cspell.json new file mode 100644 index 0000000..06c6bbd --- /dev/null +++ b/cspell.json @@ -0,0 +1,13 @@ +{ + "version": "0.2", + "ignorePaths": [], + "dictionaryDefinitions": [], + "dictionaries": [ + "en", + "ru" + ], + "words": [], + "ignoreWords": [], + "import": [], + "language": "en,ru" +} diff --git a/links.py b/links.py new file mode 100644 index 0000000..d4c2e73 --- /dev/null +++ b/links.py @@ -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) \ No newline at end of file diff --git a/main.py b/main.py index 7561961..179bf13 100644 --- a/main.py +++ b/main.py @@ -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) \ No newline at end of file diff --git a/settings.json b/settings.json index 661eb5f..550570a 100644 --- a/settings.json +++ b/settings.json @@ -7,5 +7,8 @@ "ЧТ", "ПТ", "СБ" - ] + ], + "aliases": { + "Введение в НОК": "Введение в специальности Н.О.К." + } } \ No newline at end of file diff --git a/structure.py b/structure.py index 8345b57..70ae83f 100644 --- a/structure.py +++ b/structure.py @@ -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,21 +245,14 @@ class Structure: self.calls = self.get_json("calls", "schedule") 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() @@ -53,17 +261,18 @@ class Structure: # Если сегодня понедельник, days_until_next_monday будет 0, поэтому добавляем 7 дней # if days_until_next_monday == 0: # days_until_next_monday = 7 + + 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 - # Вычисляем дату следующего понедельника - next_monday = today + timedelta(days=days_until_next_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) - - def find_disciplines(self, short_name): - return find_in_list(short_name, self.disciplines) - - - def get_lecture_time(self, day_of_week, pair_number, monday=None): - """ - Возвращает время начала и окончания лекции для указанного дня недели и номера пары. +if __name__ == "__main__": + struct = Structure() + + 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) - :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") - - # Определяем дату для указанного дня недели - target_date = monday + timedelta(days=self.settings["week"].index(day_of_week)) + lectures.extend(part) - # Находим время для указанной пары - 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} не найдена.") \ No newline at end of file + lectures = list(filter(lambda l: l.complete, lectures)) + + 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) \ No newline at end of file diff --git a/util.py b/util.py index f764db3..1511be6 100644 --- a/util.py +++ b/util.py @@ -67,7 +67,7 @@ def find_teacher_by_date(input_string, target_date): return teacher.strip() # Возвращаем ФИО преподавателя # Если дата не найдена - return None + return input_string if __name__ == "__main__": # print(test_short("кузнецов в.н.", "Кузнецов Владимир Николаевич"))