from datetime import datetime import json import os def readfile(filename): with open(module_path + filename) as f: return f.read() def writefile(filename, data): with open(module_path + filename, 'w') as f: f.write(data) # global constants # Accusative - znahidnyj WEEKDAYS_ACCUSATIVE = ["понеділок", "вівторок", "середу", "четвер", "п'ятницю", "суботу", "неділю"] # Genitive - rodovyj WEEKDAYS_GENITIVE_NEXT = ["наступного понеділка", "наступного вівторка", "наступної середи", "наступного четверга", "наступної п'ятниці", "наступної суботи", "наступної неділі"] WEEKDAYS_GENITIVE_THIS = ["цього понеділка", "цього вівторка", "цієї середи", "цього четверга", "цієї п'ятниці", "цієї суботи", "цієї неділі"] lesson_types_to_strings = { "lec": "лекція", "prac": "практика", "lab": "лабораторна", "con": "консультація" } # global variables module_path = "" def get_preference_by_id(user_id, name): if not os.path.exists(module_path + f"preference-db/{user_id}.json"): return None raw_prefs = readfile(f"preference-db/{user_id}.json") try: preferences = json.loads(raw_prefs) except Exception as e: return None if not name in preferences: return None return preferences[name] def get_all_preferences_by_id(user_id): user_preferences = { "output-style": "legacy-vibrant", "output-style-lesson": "None", "output-style-lookup": "None" } # label defaults as defaults and let custom settings override these labels for i in user_preferences: user_preferences[i] += " (default)" for i in user_preferences: override = get_preference_by_id(user_id, i) if override != None: user_preferences[i] = override return user_preferences def set_preference_by_id(user_id, name, value): if not os.path.exists(module_path + "preference-db/"): os.mkdir(module_path + "preference-db/") preferences = {} if os.path.exists(module_path + f"preference-db/{user_id}.json"): try: raw_prefs = readfile(f"preference-db/{user_id}.json") preferences = json.loads(raw_prefs) except Exception as e: preferences = {} else: preferences = {} preferences[name] = value final_data = json.dumps(preferences) writefile(f"preference-db/{user_id}.json", final_data) def clear_preference_by_id(user_id, name): if not os.path.exists(module_path + "preference-db/"): os.mkdir(module_path + "preference-db/") preferences = {} if os.path.exists(module_path + f"preference-db/{user_id}.json"): try: raw_prefs = readfile(f"preference-db/{user_id}.json") preferences = json.loads(raw_prefs) except Exception as e: preferences = {} else: preferences = {} if name in preferences: del preferences[name] final_data = json.dumps(preferences) writefile(f"preference-db/{user_id}.json", final_data) def load_template(template, part): return readfile(f"templates/{template}/{part}.msg") def escaped_string_markdownV2(input_string): result_string = input_string symbols_to_escape = ['_', '*', '[', ']', '(', ')', '~', '`', '>', '#', '+', '-', '=', '|', '{', '}', '.', '!'] for symbol in symbols_to_escape: result_string = result_string.replace(symbol, f"\\{symbol}") return result_string def escaped_string_html(input_string): result_string = input_string symbols_to_escape = ['<', '>'] for symbol in symbols_to_escape: result_string = result_string.replace(symbol, f"\\{symbol}") return result_string def get_human_readable_date(start_datetime, end_datetime, current_day, current_week): human_readable_date = "" if ((current_day + 2) == int(start_datetime.strftime("%u"))) or \ ((current_day == 6) and (start_datetime.strftime("%u") == "1")): human_readable_date += "завтра " elif current_week != int(start_datetime.strftime("%W")) % 2: human_readable_date += f"{WEEKDAYS_GENITIVE_NEXT[int(start_datetime.strftime('%u')) - 1]} " elif current_day != (int(start_datetime.strftime("%u")) - 1): human_readable_date += f"{WEEKDAYS_GENITIVE_THIS[int(start_datetime.strftime('%u')) - 1]} " else: human_readable_date += "сьогодні " human_readable_date += "з " human_readable_date += start_datetime.strftime("%H:%M") human_readable_date += " до " human_readable_date += end_datetime.strftime("%H:%M") return human_readable_date def get_name_of_lesson_type(lesson_type): if lesson_type in lesson_types_to_strings: return lesson_types_to_strings[lesson_type] def generate_lesson_description(lesson, start_datetime, end_datetime, current_day, current_week, overrides={}, custom_name_prefix="Назва", template="legacy-vibrant", force_date_at_top=False): # temporarily not supported #output_settings = {"name": True, "date": True, "teacher": True, "link": True, "comment": True} #output_settings.update(overrides) if lesson.__class__ == dict: if force_date_at_top: total_result = load_template(template, "date") human_readable_date = get_human_readable_date(start_datetime, end_datetime, current_day, current_week) total_result = total_result.replace("%DATE%", human_readable_date) total_result += load_template(template, "multiple") for i in ['name', 'teacher', 'link']: total_result = total_result.replace(f"%{i.upper()}%", lesson[i]) total_result = total_result.replace("%DATE%", human_readable_date) total_result = total_result.replace("%TYPE%", get_name_of_lesson_type(lesson['type'])) total_result = total_result.replace("%NAME_PREFIX%", custom_name_prefix) return total_result + "\n" else: active_template = load_template(template, "single") for i in ['name', 'teacher', 'link']: active_template = active_template.replace(f"%{i.upper()}%", lesson[i]) human_readable_date = get_human_readable_date(start_datetime, end_datetime, current_day, current_week) active_template = active_template.replace("%DATE%", human_readable_date) active_template = active_template.replace("%TYPE%", get_name_of_lesson_type(lesson['type'])) active_template = active_template.replace("%NAME_PREFIX%", custom_name_prefix) return active_template elif lesson.__class__ == list: total_result = load_template(template, "date") human_readable_date = get_human_readable_date(start_datetime, end_datetime, current_day, current_week) total_result = total_result.replace("%DATE%", human_readable_date) for l in lesson: active_template = load_template(template, "multiple") for i in ['name', 'teacher', 'link']: active_template = active_template.replace(f"%{i.upper()}%", l[i]) active_template = active_template.replace("%DATE%", human_readable_date) active_template = active_template.replace("%TYPE%", get_name_of_lesson_type(l['type'])) active_template = active_template.replace("%NAME_PREFIX%", custom_name_prefix) total_result += active_template + "\n" return total_result def get_schedule_data_from(filename): raw_schedule = json.loads(readfile(filename)) baked_schedule = {} for day_number, lesson_times in enumerate(raw_schedule): for lesson_time in lesson_times: timestamp = day_number * 86400 + int(lesson_time.split(":")[0]) * 3600 \ + int(lesson_time.split(":")[1]) * 60 new_record = raw_schedule[day_number][lesson_time] item_source = filename.split(".json")[0] if new_record.__class__ == list: for item in new_record: item["source"] = item_source else: new_record["source"] = item_source baked_schedule[timestamp] = new_record return baked_schedule def process_arguments(args, base_day): selected_day = int(base_day) preferences = {} for arg in args: if arg[0] == "-": if arg[1:].isdigit(): selected_day -= int(arg[1:]) else: preferences[arg[1:]] = False elif arg[0] == "+": if arg[1:].isdigit(): selected_day += int(arg[1:]) else: preferences[arg[1:]] = True selected_day = selected_day % 14 return preferences, selected_day def get_lesson_description(schedule, reference_time, lesson_time, current_day, current_week, overrides={}, custom_name_prefix="Назва", template="legacy-vibrant", force_date_at_top=False): lesson_record = schedule[lesson_time] lesson_start_datetime = datetime.fromtimestamp(reference_time + lesson_time) lesson_end_datetime = datetime.fromtimestamp(reference_time + lesson_time + 5400) return generate_lesson_description(lesson_record, lesson_start_datetime, lesson_end_datetime, current_day, current_week, overrides=overrides, custom_name_prefix=custom_name_prefix, template=template, force_date_at_top=force_date_at_top) def process(message, path): message_text = message["text"] full_command = message_text.split() # there is no need to check if the full_command list if empty as it # never will be - Telegram requires all messages to have at least # one printable symbol, so this is already protected base_command = full_command[0].lower() if base_command not in ["!пара", "!пари", "!schedule-ctl"]: return None, None global module_path module_path = path schedule = get_schedule_data_from("schedule-v2.json") schedule.update(get_schedule_data_from("additions-v2.json")) current_time = datetime.now() current_week = current_time.isocalendar()[1] % 2 current_day = current_time.weekday() current_seconds = current_week * 604800 + current_day * 86400 + current_time.hour * 3600 + current_time.minute \ * 60 + current_time.second reference_time = int(current_time.strftime("%s")) - current_seconds if base_command == "!пара": # easter egg study_begin_ts = int(datetime(year=2023, month=9, day=4).strftime("%s")) current_ts = int(datetime.now().strftime("%s")) if -3600*4 < study_begin_ts - current_ts < 0: return "Навчання от-от розпочнеться!", None elif 0 <= study_begin_ts - current_ts < 1209600: return f"До навчання залишилося {study_begin_ts - current_ts} секунд...", None elif study_begin_ts - current_ts >= 1209600: return "Ви маєте законне право відпочити, пари почнуться не скоро", None # actual lesson finding code upcoming_lessons = [timestamp for timestamp in schedule if timestamp > current_seconds - 5400] if len(upcoming_lessons) > 0: closest_lesson_time = min(upcoming_lessons) else: closest_lesson_time = min(schedule) # shifting lesson pointer if requested to do so if len(full_command) >= 2: possible_times = list(schedule.keys()) current_pointer_position = possible_times.index(closest_lesson_time) total_list_length = len(possible_times) if len(full_command[1]) > 1 and full_command[1][1:].isdigit(): if full_command[1][0] == "+": current_pointer_position = (current_pointer_position + int(full_command[1][1:])) % total_list_length else: current_pointer_position = (current_pointer_position - int(full_command[1][1:])) % total_list_length closest_lesson_time = possible_times[current_pointer_position] # getting corrent style output_style_preference = "legacy-vibrant" general_output_style_preference = get_preference_by_id(message.from_user.id, "output-style") if general_output_style_preference != None: output_style_preference = general_output_style_preference specific_output_style_preference = get_preference_by_id(message.from_user.id, "output-style-lesson") if specific_output_style_preference != None: output_style_preference = specific_output_style_preference # returning generated pair description return get_lesson_description(schedule, reference_time, closest_lesson_time, current_day, current_week, custom_name_prefix="Актуальна пара", template=output_style_preference), "HTML" elif base_command == "!пари": base_day = current_week * 7 + current_day if len(full_command) >= 2: args = [i for i in full_command[1:] if len(i) > 1] preferences, selected_day = process_arguments(args, base_day) else: preferences = {} selected_day = base_day lesson_list = [i for i in schedule if selected_day * 86400 <= i < (selected_day + 1) * 86400] output_style_preference = "legacy-vibrant" general_output_style_preference = get_preference_by_id(message.from_user.id, "output-style") if general_output_style_preference != None: output_style_preference = general_output_style_preference specific_output_style_preference = get_preference_by_id(message.from_user.id, "output-style-lookup") if specific_output_style_preference != None: output_style_preference = specific_output_style_preference lesson_descriptions_list = [get_lesson_description(schedule, reference_time, lesson_time, current_day, current_week, overrides=preferences, custom_name_prefix="Назва", template=output_style_preference, force_date_at_top=True) for lesson_time in lesson_list] return f"Пари у {WEEKDAYS_ACCUSATIVE[selected_day % 7]}:\n\n\n" + "\n".join(lesson_descriptions_list), "HTML" elif base_command == "!schedule-ctl" and len(full_command) >= 2: if full_command[1] == "list": prefs = get_all_preferences_by_id(message.from_user.id) return "Ваші персональні налаштування:\n" + '\n'.join([f"- {k} = {v}" for k, v in prefs.items()]), "HTML" elif full_command[1] == "set" and len(full_command) == 4: prefs = get_all_preferences_by_id(message.from_user.id) if full_command[2] in prefs: if full_command[2] in ["output-style", "output-style-lesson", "output-style-lookup"]: if full_command[3] not in os.listdir(module_path + "templates/"): return f"Стилю {full_command[3]} не існує; доступні варіанти: " \ + ', '.join(os.listdir(module_path + "templates/")), "HTML" previous_value = prefs[full_command[2]] prefs[full_command[2]] = full_command[3] set_preference_by_id(message.from_user.id, full_command[2], full_command[3]) return f"Змінено значення {full_command[2]}: {previous_value} -> {full_command[3]}", "HTML" else: return f"Такого налаштування не існує; переглянути наявні налаштування можна за допомогою команди !schedule-ctl list", "HTML" elif full_command[1] == "get" and len(full_command) == 3: requested_preference = get_preference_by_id(message.from_user.id, full_command[2]) return f"Налаштування {full_command[2]} має значення {requested_preference}", "HTML" elif full_command[1] == "clear" and len(full_command) == 3: clear_preference_by_id(message.from_user.id, full_command[2]) return f"Очищено значення для налаштування {full_command[2]}, надалі для нього використовуватиметься стандартне значення", "HTML" else: return "Такої команди не існує (або був використаний помилковий синтаксис)", "HTML"