diff --git a/.gitignore b/.gitignore index e9e67c3..ece10f2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ -__pycache__ -lecture.json -result.txt \ No newline at end of file +__pycache__ +lecture.json +result.txt +custom.txt \ No newline at end of file diff --git a/custom.example.txt b/custom.example.txt new file mode 100644 index 0000000..d8cd645 --- /dev/null +++ b/custom.example.txt @@ -0,0 +1,9 @@ +День недели +Номер пары +Название занятия +Преподаватель + +ВТ +6 +Обеспечение антитеррористической защищенности объектов здравоохранения Российской Федерации +Окутина Н Н \ No newline at end of file diff --git a/custom.txt b/custom.txt deleted file mode 100644 index 68d0033..0000000 --- a/custom.txt +++ /dev/null @@ -1,24 +0,0 @@ -ВТ -2 -Методика сохранения и укрепления традиционных российских духовно-нравственных ценностей и профилактики деструктивной идеологии -Сергеева Юлия Валерьевна - -ВТ -3 -Методика сохранения и укрепления традиционных российских духовно-нравственных ценностей и профилактики деструктивной идеологии -Сергеева Юлия Валерьевна - -ПТ -2 -Методика сохранения и укрепления традиционных российских духовно-нравственных ценностей и профилактики деструктивной идеологии -Сергеева Юлия Валерьевна - -ПТ -3 -Методика сохранения и укрепления традиционных российских духовно-нравственных ценностей и профилактики деструктивной идеологии -Сергеева Юлия Валерьевна - -ПТ -4 -Методика сохранения и укрепления традиционных российских духовно-нравственных ценностей и профилактики деструктивной идеологии -Сергеева Юлия Валерьевна \ No newline at end of file diff --git a/links.py b/links.py index b78caee..6989f87 100644 --- a/links.py +++ b/links.py @@ -1,75 +1,99 @@ -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(5) - 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 +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}" + +class ResultFileWriter: + def __init__(self, filename): + self.filename = filename + self.file = None + self.last_auditorium = "" + self.last_day_of_week = "" + + def __enter__(self): + self.file = open(self.filename, "w", encoding="utf-8") + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + if self.file: + self.file.close() + + def write_lecture(self, lecture, url): + """Записывает информацию о лекции в файл""" + if lecture['auditorium'] != self.last_auditorium: + if self.last_auditorium != "": + self._write_line("\n========================\n") + self._write_line(lecture['auditorium'] + "\n") + self.last_auditorium = lecture['auditorium'] + self.last_day_of_week = "" + + if lecture['day_of_week'] != self.last_day_of_week: + prefix = f" {lecture['day_of_week']} " + self.last_day_of_week = lecture['day_of_week'] + else: + prefix = " " * 6 + + self._write_line(f"{prefix} #{lecture['pair_number']} - {lecture['name']}\n") + self._write_line(" " * 12 + url + "\n") + + def _write_line(self, line): + """Вспомогательный метод для записи строки в файл""" + self.file.write(line) + self.file.flush() # Принудительно сбрасываем буфер + +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(5) + 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__": + with open("lecture.json", "r", encoding="utf-8") as f: + lectures = json.load(f) + + # Используем контекстный менеджер для работы с файлом + with ResultFileWriter("result.txt") as writer: + for lecture in tqdm.tqdm(lectures): + try: + url = do_request(lecture['name'], lecture['start'], lecture['end']) + writer.write_lecture(lecture, url) + except Exception as e: + # Записываем информацию об ошибке в файл + error_line = f"\nОШИБКА при обработке лекции '{lecture['name']}': {str(e)}\n\n" + writer._write_line(error_line) + print(f"Произошла ошибка: {e}") + # Продолжаем обработку следующих лекций + continue \ No newline at end of file diff --git a/settings.json b/settings.json index e1305fa..839bc6d 100644 --- a/settings.json +++ b/settings.json @@ -1,15 +1,16 @@ -{ - "first_week": "2025-01-06", - "week": [ - "ПН", - "ВТ", - "СР", - "ЧТ", - "ПТ", - "СБ" - ], - "aliases": { - "Введение в НОК": "Введение в специальности Н.О.К.", - "Проф.электив. Введение в специальность стоматология": "Предпрофессиональный электив. Введение в специальность Стоматология" - } +{ + "first_week": "2025-09-01", + "week": [ + "ПН", + "ВТ", + "СР", + "ЧТ", + "ПТ", + "СБ" + ], + "aliases": { + "Введение в НОК": "Введение в специальности Н.О.К.", + "Проф.электив. Введение в специальность стоматология": "Предпрофессиональный электив. Введение в специальность Стоматология" , + "Абделбаги Мохаммат абба нана": "Абделбаги Махаммат абба нана" + } } \ No newline at end of file diff --git a/structure.py b/structure.py index 852ebcc..eb0982a 100644 --- a/structure.py +++ b/structure.py @@ -1,373 +1,407 @@ -import gspread -import requests -from datetime import datetime, timedelta -import json -import random -import time -import re -from functools import lru_cache - -@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, regular: bool = True): - 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() - - if regular: - 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) - else: - self.teacher = self.input_teacher - self.lecture = self.input_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() - self.document = gc.open_by_key("1srtrHkwbTCfvxyeDtb_ca4RMx8W1kxgUQ-MAIumamVA") - print("Готово.") - - print("Загрузка настроек...", end=" ") - with open("settings.json", 'r', encoding='utf-8') as file: - self.settings = json.load(file) - print("Готово.") - - print("Загрузка списка преподавателей...", end=" ") - self.teachers = list(map(lambda a: a["name"], self.get_json("teachers"))) - print("Готово.") - - print("Загрузка списка дисциплин...", end=" ") - self.disciplines = list(map(lambda a: a["name"], self.get_json("disciplines"))) - print("Готово.") - - print("Загрузка расписания...", end=" ") - 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_monday(self): - today = datetime.today() - days_until_next_monday = (7 - today.weekday()) % 7 - - # Если сегодня понедельник, 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 - - 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: - try: - if range_name is None: - return worksheet.get_all_values() - else: - return worksheet.get(range_name) - except gspread.exceptions.APIError as e: - retries += 1 - if retries == max_retries: - raise e - - 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: - return "1 неделя" in cell or "I неделя" in cell - else: - return "2 неделя" in cell or "II неделя" in cell - - all_values = self.get_document_data(worksheet) - - week_row = None - for i, row in enumerate(all_values): - for j, cell in enumerate(row): - if check(cell): - week_row = i + 1 - break - if week_row is not None: - break - - for row in range(week_row, worksheet.row_count + 1): - cell_value = all_values[row][0] - if cell_value == "1": - return "C%d:H%d" % (row+1, row+16) - - def get_lectures_from_range(self, worksheet, range_name) -> list: - # Получаем название аудитории из названия листа - auditorium = worksheet.title - - # Получаем данные из указанного диапазона - data = self.get_document_data(worksheet, range_name) - - - if len(data) < 2: - return [] - - # Дни недели - days_of_week = self.settings["week"] - day_order = {day: idx for idx, day in enumerate(days_of_week)} # Словарь для порядка дней - - # Список для хранения лекций - lectures = [] - - # Проходим по данным - for row_index in range(0, len(data), 2): # Шаг 2, так как каждая пара строк — это одна пара в расписании - lecture_row = data[row_index] # Строка с названием лекции - teacher_row = data[row_index + 1] # Строка с преподавателем - - # Номер пары - pair_number = (row_index // 2) + 1 - - # Проходим по дням недели (столбцам) - 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(Lecture(auditorium, day_of_week, pair_number, lecture, teacher)) - - lectures = sorted(lectures, key=lambda x: (x.day_of_week_i, x.pair_number)) - - return lectures - - - -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) - - lectures.extend(part) - - lectures = list(filter(lambda l: l.complete, lectures)) - - with open("lecture.json", "w", encoding="utf-8") as f: +import gspread +import requests +from datetime import datetime, timedelta +import json +import random +import time +import re +import os +from functools import lru_cache + +@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, regular: bool = True): + 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() + + if regular: + 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) + else: + self.teacher = self.input_teacher + self.lecture = self.input_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) + lecture, _ = find_teacher_by_date(self.input_lecture, self.date) + + # 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 = self.load_gspread() + # self.document = gc.open_by_key("1srtrHkwbTCfvxyeDtb_ca4RMx8W1kxgUQ-MAIumamVA") + self.document = self.load_gspread() + print("Готово.") + + print("Загрузка настроек...", end=" ") + with open("settings.json", 'r', encoding='utf-8') as file: + self.settings = json.load(file) + print("Готово.") + + print("Загрузка списка преподавателей...", end=" ") + self.teachers = list(map(lambda a: a["name"], self.get_json("teachers"))) + print("Готово.") + + print("Загрузка списка дисциплин...", end=" ") + self.disciplines = list(map(lambda a: a["name"], self.get_json("disciplines"))) + print("Готово.") + + print("Загрузка расписания...", end=" ") + self.calls = self.get_json("calls", "schedule") + print("Готово.") + + self.monday = self.get_monday() + self.week = self.get_week_number() + + print("Данные загружены!") + + def load_gspread(self) -> gspread.Spreadsheet: + try: + gc = gspread.oauth() + except: + self.remove_auth_data() + gc = gspread.oauth() + + try: + return gc.open_by_key("1srtrHkwbTCfvxyeDtb_ca4RMx8W1kxgUQ-MAIumamVA") + except: + self.remove_auth_data() + return gc.open_by_key("1srtrHkwbTCfvxyeDtb_ca4RMx8W1kxgUQ-MAIumamVA") + + def remove_auth_data(self): + appdata_path = os.environ.get('APPDATA') + file_path = os.path.join(appdata_path, "gspread", "authorized_user.json") + os.remove(file_path) + + + 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_monday(self): + today = datetime.today() + days_until_next_monday = (7 - today.weekday()) % 7 + + # Если сегодня понедельник, 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 + + 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: + try: + if range_name is None: + return worksheet.get_all_values() + else: + return worksheet.get(range_name) + except gspread.exceptions.APIError as e: + retries += 1 + if retries == max_retries: + raise e + + 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: + return "1 неделя" in cell or "I неделя" in cell + else: + return "2 неделя" in cell or "II неделя" in cell + + all_values = self.get_document_data(worksheet) + + week_row = None + for i, row in enumerate(all_values): + for j, cell in enumerate(row): + if check(cell): + week_row = i + 1 + break + if week_row is not None: + break + + for row in range(week_row, worksheet.row_count + 1): + cell_value = all_values[row][0] + if cell_value == "1": + return "C%d:H%d" % (row+1, row+16) + + def get_lectures_from_range(self, worksheet, range_name) -> list: + # Получаем название аудитории из названия листа + auditorium = worksheet.title + + # Получаем данные из указанного диапазона + data = self.get_document_data(worksheet, range_name) + + + if len(data) < 2: + return [] + + # Дни недели + days_of_week = self.settings["week"] + day_order = {day: idx for idx, day in enumerate(days_of_week)} # Словарь для порядка дней + + # Список для хранения лекций + lectures = [] + + # Проходим по данным + for row_index in range(0, len(data), 2): # Шаг 2, так как каждая пара строк — это одна пара в расписании + lecture_row = data[row_index] # Строка с названием лекции + + if row_index + 1 == len(data): + print("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!") + print(f"Ошибка с данными, row_index = {row_index + 1} не существует!") + print(data) + print("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!") + continue + + teacher_row = data[row_index + 1] # Строка с преподавателем + + # Номер пары + pair_number = (row_index // 2) + 1 + + # Проходим по дням недели (столбцам) + 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] # День недели + + # if col_index > 3: + # continue + + lectures.append(Lecture(auditorium, day_of_week, pair_number, lecture, teacher)) + + lectures = sorted(lectures, key=lambda x: (x.day_of_week_i, x.pair_number)) + + return lectures + + + +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) + + lectures.extend(part) + + 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 1511be6..c483734 100644 --- a/util.py +++ b/util.py @@ -1,75 +1,75 @@ -import re -from functools import lru_cache - -@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, full_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, target_date): - """ - Ищет преподавателя по целевой дате в строке с датами и ФИО. - - :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 line in lines: - # Разделяем строку на даты и ФИО - parts = line.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() # Возвращаем ФИО преподавателя - - # Если дата не найдена - return input_string - -if __name__ == "__main__": - # print(test_short("кузнецов в.н.", "Кузнецов Владимир Николаевич")) - s = " 10.02, 24.02, 10.03 Абдусалямова Л.И . 27.01, 07.04 Голубцова Т.С. 24.03 Шаршова С.М. " +import re +from functools import lru_cache + +@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, full_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, target_date): + """ + Ищет преподавателя по целевой дате в строке с датами и ФИО. + + :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 line in lines: + # Разделяем строку на даты и ФИО + parts = line.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() # Возвращаем ФИО преподавателя + + # Если дата не найдена + return input_string + +if __name__ == "__main__": + # print(test_short("кузнецов в.н.", "Кузнецов Владимир Николаевич")) + s = " 10.02, 24.02, 10.03 Абдусалямова Л.И . 27.01, 07.04 Голубцова Т.С. 24.03 Шаршова С.М. " print(find_teacher_by_date(s, "10.03")) \ No newline at end of file