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": "лабораторна" } # 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"} 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 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="Назва"): output_settings = {"name": True, "date": True, "teacher": True, "link": True, "comment": True} output_settings.update(overrides) result = "" if output_settings['name']: result += f"{custom_name_prefix}: {escaped_string_html(lesson['name'])} ({escaped_string_html(get_name_of_lesson_type(lesson['type']))})\n" if output_settings['date']: human_readable_date = get_human_readable_date(start_datetime, end_datetime, current_day, current_week) result += f"Дата: {escaped_string_html(human_readable_date)}\n" if output_settings['teacher']: result += f"Викладач: {escaped_string_html(lesson['teacher'])}\n" if output_settings['link']: result += f"Посилання: {escaped_string_html(lesson['link'])}\n" if output_settings['comment'] and 'comment' in lesson: result += f"Примітка: {escaped_string_html(lesson['comment'])}\n" return result ''' def generate_lesson_description(lesson, start_datetime, end_datetime, current_day, current_week, overrides={}, custom_name_prefix="Назва", template="legacy-vibrant"): # temporarily not supported #output_settings = {"name": True, "date": True, "teacher": True, "link": True, "comment": True} #output_settings.update(overrides) if lesson.__class__ == dict: 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"): lesson_record = schedule[lesson_time] lesson_start_datetime = datetime.fromtimestamp(reference_time + lesson_time) lesson_end_datetime = datetime.fromtimestamp(reference_time + lesson_time + 5400) ''' if lesson_record.__class__ == dict: if force_date_at_top: user_defined_overrides = dict(overrides) internal_overrides = dict(overrides) internal_overrides['date'] = False description = generate_lesson_description(lesson_record, lesson_start_datetime, lesson_end_datetime, current_day, current_week, overrides=internal_overrides) if 'date' in user_defined_overrides and not user_defined_overrides['date']: return description else: human_readable_date = get_human_readable_date(lesson_start_datetime, lesson_end_datetime, current_day, current_week) return f"{human_readable_date.capitalize()}:\n" + description else: return generate_lesson_description(lesson_record, lesson_start_datetime, lesson_end_datetime, current_day, current_week, overrides=overrides) elif lesson_record.__class__ == list: user_defined_overrides = dict(overrides) internal_overrides = dict(overrides) internal_overrides['date'] = False descriptions = [generate_lesson_description(i, lesson_start_datetime, lesson_end_datetime, current_day, current_week, overrides=internal_overrides, custom_name_prefix=custom_name_prefix) for i in lesson_record] if 'date' in user_defined_overrides and not user_defined_overrides['date']: return "\n".join(descriptions) else: human_readable_date = get_human_readable_date(lesson_start_datetime, lesson_end_datetime, current_day, current_week) return f"{human_readable_date.capitalize()}:\n" + "\n".join(descriptions) ''' 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) 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 output_style_preference = get_preference_by_id(message.from_user.id, "output-style") if output_style_preference == None: output_style_preference = "legacy-vibrant" 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) 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] lesson_descriptions_list = [get_lesson_description(schedule, reference_time, lesson_time, current_day, current_week, overrides=preferences, custom_name_prefix="Назва", template=output_style_preference) 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": 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": prefs = get_all_preferences_by_id(message.from_user.id) if full_command[2] in prefs: if full_command[2] == "output-style": 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": requested_preference = get_preference_by_id(message.from_user.id, full_command[2]) return f"Налаштування {full_command[2]} має значення {requested_preference}", "HTML" else: return "Такої команди не існує", "HTML"