diff --git a/Schedule.json b/Schedule.json new file mode 100644 index 0000000..edb258c --- /dev/null +++ b/Schedule.json @@ -0,0 +1,43 @@ +{ + "0": [ + [9, 50, 10, 35, 5], + [10, 40, 11, 25, 30], + [11, 55, 12, 40, 5], + [12, 45, 13, 30, 15], + [13, 45, 14, 30, 5], + [14, 35, 15, 20, 30], + [15, 50, 16, 35, 5], + [16, 40, 17, 25, null] + ], + "1": [ + [9, 50, 10, 35, 5], + [10, 40, 11, 25, 30], + [11, 55, 12, 40, 5], + [12, 45, 13, 30, 15], + [13, 45, 14, 30, 5], + [14, 35, 15, 20, null] + ], + "2": [], + "3": [ + [8, 0, 8, 45, 5], + [8, 50, 9, 35, 15], + [9, 50, 10, 35, 5], + [10, 40, 11, 25, 30], + [11, 55, 12, 40, 5], + [12, 45, 13, 30, 15], + [13, 45, 14, 30, 5], + [14, 35, 15, 20, 30], + [15, 50, 16, 35, 5], + [16, 40, 17, 25, null] + ], + "4": [ + [9, 50, 10, 35, 5], + [10, 40, 11, 25, 30], + [11, 55, 12, 40, 5], + [12, 45, 13, 30, 15], + [13, 45, 14, 30, 5], + [14, 35, 15, 20, null] + ], + "5": [], + "6": [] +} diff --git a/main.py b/main.py index 3eb7df6..a5defae 100644 --- a/main.py +++ b/main.py @@ -1,11 +1,12 @@ import asyncio +import json import logging import os import re from io import BytesIO from pathlib import Path -from textwrap import wrap import random +from typing import Any from aiogram import Bot, Dispatcher, F, types from aiogram.enums import ParseMode @@ -40,54 +41,80 @@ def templates_list_text() -> str: # ---- Schedule & breaks ---- -def make_time(h: int, m: int) -> time: - return time(hour=h, minute=m) +def _ensure_int(value: Any, field_name: str, day: int, lesson_idx: int) -> int: + if isinstance(value, bool) or not isinstance(value, int): + raise ValueError( + f"Invalid {field_name} in day {day}, lesson #{lesson_idx + 1}: expected integer, got {value!r}" + ) + return value -# Schedule format: list of (lesson_start, lesson_end, break_minutes_after) -SCHEDULE: dict[int, list[tuple[time, time, int | None]]] = { - 0: [ # Monday - (make_time(9, 50), make_time(10, 35), 5), - (make_time(10, 40), make_time(11, 25), 30), - (make_time(11, 55), make_time(12, 40), 5), - (make_time(12, 45), make_time(13, 30), 15), - (make_time(13, 45), make_time(14, 30), 5), - (make_time(14, 35), make_time(15, 20), 30), - (make_time(15, 50), make_time(16, 35), 5), - (make_time(16, 40), make_time(17, 25), None), - ], - 1: [ # Tuesday - (make_time(9, 50), make_time(10, 35), 5), - (make_time(10, 40), make_time(11, 25), 30), - (make_time(11, 55), make_time(12, 40), 5), - (make_time(12, 45), make_time(13, 30), 15), - (make_time(13, 45), make_time(14, 30), 5), - (make_time(14, 35), make_time(15, 20), None), - ], - 2: [], # Wednesday — кайфуем дома - 3: [ # Thursday - (make_time(8, 0), make_time(8, 45), 5), - (make_time(8, 50), make_time(9, 35), 15), - (make_time(9, 50), make_time(10, 35), 5), - (make_time(10, 40), make_time(11, 25), 30), - (make_time(11, 55), make_time(12, 40), 5), - (make_time(12, 45), make_time(13, 30), 15), - (make_time(13, 45), make_time(14, 30), 5), - (make_time(14, 35), make_time(15, 20), 30), - (make_time(15, 50), make_time(16, 35), 5), - (make_time(16, 40), make_time(17, 25), None), - ], - 4: [ # Friday - (make_time(9, 50), make_time(10, 35), 5), - (make_time(10, 40), make_time(11, 25), 30), - (make_time(11, 55), make_time(12, 40), 5), - (make_time(12, 45), make_time(13, 30), 15), - (make_time(13, 45), make_time(14, 30), 5), - (make_time(14, 35), make_time(15, 20), None), - ], - 5: [], # Saturday — выходной - 6: [], # Sunday — выходной -} +def load_schedule_from_json(file_path: str | Path = BASE_DIR / "Schedule.json") -> dict[int, list[tuple[time, time, int | None]]]: + path = Path(file_path) + if not path.is_absolute(): + path = BASE_DIR / path + + with path.open("r", encoding="utf-8") as f: + try: + raw_data = json.load(f) + except json.JSONDecodeError as exc: + raise ValueError(f"Invalid JSON in schedule file {path}: {exc}") from exc + + if not isinstance(raw_data, dict): + raise ValueError(f"Schedule JSON root must be an object, got: {type(raw_data).__name__}") + + formatted_schedule: dict[int, list[tuple[time, time, int | None]]] = {day: [] for day in range(7)} + + for day_key, lessons in raw_data.items(): + try: + day = int(day_key) + except (TypeError, ValueError) as exc: + raise ValueError(f"Day key must be an integer from 0 to 6, got: {day_key!r}") from exc + + if day < 0 or day > 6: + raise ValueError(f"Day key must be in range 0..6, got: {day}") + + if not isinstance(lessons, list): + raise ValueError(f"Lessons for day {day} must be a list, got: {type(lessons).__name__}") + + parsed_lessons: list[tuple[time, time, int | None]] = [] + for idx, lesson in enumerate(lessons): + if not isinstance(lesson, list) or len(lesson) != 5: + raise ValueError( + f"Day {day}, lesson #{idx + 1} must be [start_h, start_m, end_h, end_m, break_minutes]" + ) + + start_h = _ensure_int(lesson[0], "start_h", day, idx) + start_m = _ensure_int(lesson[1], "start_m", day, idx) + end_h = _ensure_int(lesson[2], "end_h", day, idx) + end_m = _ensure_int(lesson[3], "end_m", day, idx) + + break_minutes_raw = lesson[4] + if break_minutes_raw is None: + break_minutes = None + else: + break_minutes = _ensure_int(break_minutes_raw, "break_minutes", day, idx) + if break_minutes < 0: + raise ValueError(f"Day {day}, lesson #{idx + 1}: break_minutes must be >= 0") + + try: + start_t = time(start_h, start_m) + end_t = time(end_h, end_m) + except ValueError as exc: + raise ValueError(f"Day {day}, lesson #{idx + 1}: invalid lesson time") from exc + + if (end_h, end_m) <= (start_h, start_m): + raise ValueError(f"Day {day}, lesson #{idx + 1}: end time must be after start time") + + parsed_lessons.append((start_t, end_t, break_minutes)) + + parsed_lessons.sort(key=lambda item: (item[0].hour, item[0].minute)) + formatted_schedule[day] = parsed_lessons + + return formatted_schedule + +# Теперь заменяем глобальный словарь SCHEDULE: +SCHEDULE = load_schedule_from_json() def minutes_until_break(now: datetime | None = None) -> str: @@ -364,7 +391,6 @@ async def handle_gen_mem(message: Message, bot: Bot): await message.reply_photo(BufferedInputFile(output.getvalue(), filename="meme.jpg")) -<<<<<<< HEAD async def handle_size(message: Message): size = round(random.uniform(0.1, 35.0), 1) @@ -385,8 +411,7 @@ async def handle_size(message: Message): f"📏 Твой хуек: {size} см\n" f"Ты: {status}" ) -======= -KEYWORDS = {'крип', 'радиохэд', 'creep', 'radiohead'} +KEYWORDS_CREEP = {'крип', 'радиохэд', 'creep', 'radiohead'} # I'M CREEP async def handle_keywords(message: types.Message): @@ -395,7 +420,7 @@ async def handle_keywords(message: types.Message): text = message.text.lower() - if any(re.search(rf'\b{word}\b', text) for word in KEYWORDS): + if any(re.search(rf'\b{word}\b', text) for word in KEYWORDS_CREEP): audio_path = BASE_DIR / "maket" / "Radiohead - Creep.mp3" if audio_path.exists(): @@ -405,7 +430,6 @@ async def handle_keywords(message: types.Message): ) else: logger.warning(f"Файл не найден по пути: {audio_path}") ->>>>>>> SUDOZOVCHIK-CREEP-FUNCTION def main():