This commit is contained in:
q 2026-03-18 09:39:32 +03:00
parent 9295538f5f
commit a7c047f847
3 changed files with 94 additions and 23 deletions

View file

@ -4,7 +4,7 @@
- вписывает текст шрифтом Impact (белый с чёрной обводкой) в макеты (`/maket`); - вписывает текст шрифтом Impact (белый с чёрной обводкой) в макеты (`/maket`);
- делает мемы из присланных фото (`/gen_mem Up:... Down:...`); - делает мемы из присланных фото (`/gen_mem Up:... Down:...`);
- подсказывает, сколько минут до перекура по заданному расписанию (`/break`); - подсказывает, сколько минут до перекура по заданному расписанию (`/break`);
- показывает расписание, текущую и следующую пару (`/schedule`); - показывает расписание, текущую и следующую пару (`/schedule`, можно с днём недели);
- умеет базарить (`/talk`) с контекстом 100 сообщений; - умеет базарить (`/talk`) с контекстом 100 сообщений;
- авто-ответы в чате (10% шанс + триггер-слова); - авто-ответы в чате (10% шанс + триггер-слова);
- миниигра `/penis` + `/top_penis`; - миниигра `/penis` + `/top_penis`;
@ -44,7 +44,7 @@ docker compose down
- `/maket <id> <текст>` — вписать текст в макет. Доступные: `1 — Владимир Владимирович Путин самый лучший президент` (737x414, точка 104,240). - `/maket <id> <текст>` — вписать текст в макет. Доступные: `1 — Владимир Владимирович Путин самый лучший президент` (737x414, точка 104,240).
- `/gen_mem Up:верх Down:низ` — прислать с фото (подпись) или ответом на фото. Можно указывать только Up или только Down. - `/gen_mem Up:верх Down:низ` — прислать с фото (подпись) или ответом на фото. Можно указывать только Up или только Down.
- `/break` — показывает, сколько минут до ближайшего перекура/идёт ли перекур. - `/break` — показывает, сколько минут до ближайшего перекура/идёт ли перекур.
- `/schedule` — показывает расписание на сегодня, текущую и следующую пару. - `/schedule [пн|mon]` — показывает расписание на ближайший указанный день недели; без аргумента показывает сегодня, текущую и следующую пару.
- `/talk <сообщение>` — побазарить с ИИ (контекст 100 сообщений). - `/talk <сообщение>` — побазарить с ИИ (контекст 100 сообщений).
- `/penis` — миниигра, 1 раз в 24 часа. - `/penis` — миниигра, 1 раз в 24 часа.
- `/top_penis` — топ по длине. - `/top_penis` — топ по длине.

View file

@ -187,7 +187,7 @@ def get_hint_text(templates_str: str) -> str:
f"• /maket «id» «текст» — доступные: {templates_str}.\n" f"• /maket «id» «текст» — доступные: {templates_str}.\n"
"• /gen_mem Up:верх Down:низ — отправь с фото.\n" "• /gen_mem Up:верх Down:низ — отправь с фото.\n"
"• /break — узнать время до перекура.\n" "• /break — узнать время до перекура.\n"
"• /schedule — расписание, текущая и следующая пара.\n" "• /schedule [пн|mon] — расписание; без аргумента еще текущая и следующая пара.\n"
"• /penis — миниигра 1 раз в 24 часа.\n" "• /penis — миниигра 1 раз в 24 часа.\n"
"• /top_penis — топ по длине.\n" "• /top_penis — топ по длине.\n"
"• /talk [текст] — ИИ базарит на фене.\n" "• /talk [текст] — ИИ базарит на фене.\n"

89
main.py
View file

@ -41,6 +41,47 @@ _weekday_names = {
6: "Воскресенье", 6: "Воскресенье",
} }
_weekday_aliases = {
"mon": 0,
"monday": 0,
"пн": 0,
"пон": 0,
"понедельник": 0,
"tue": 1,
"tues": 1,
"tuesday": 1,
"вт": 1,
"втр": 1,
"вторник": 1,
"wed": 2,
"wednesday": 2,
"ср": 2,
"срд": 2,
"среда": 2,
"thu": 3,
"thur": 3,
"thurs": 3,
"thursday": 3,
"чт": 3,
"чет": 3,
"четверг": 3,
"fri": 4,
"friday": 4,
"пт": 4,
"пят": 4,
"пятница": 4,
"sat": 5,
"saturday": 5,
"сб": 5,
"суб": 5,
"суббота": 5,
"sun": 6,
"sunday": 6,
"вс": 6,
"вск": 6,
"воскресенье": 6,
}
@dataclass(frozen=True) @dataclass(frozen=True)
class LessonSlot: class LessonSlot:
@ -107,6 +148,20 @@ def _current_week_key(now: datetime) -> str:
return "upper" if is_upper else "lower" return "upper" if is_upper else "lower"
def _resolve_schedule_date(now: datetime, requested_weekday: int | None = None) -> datetime:
if requested_weekday is None:
return now
delta_days = (requested_weekday - now.weekday()) % 7
return now + timedelta(days=delta_days)
def _parse_weekday_arg(value: str) -> int | None:
normalized = re.sub(r"[^a-zа-яё]", "", value.lower())
if not normalized:
return None
return _weekday_aliases.get(normalized)
def _build_lesson_slot(raw_entry: dict) -> LessonSlot: def _build_lesson_slot(raw_entry: dict) -> LessonSlot:
pair_number = raw_entry.get("pair") pair_number = raw_entry.get("pair")
start_t, end_t = config.PAIR_TIMES.get(pair_number, (None, None)) start_t, end_t = config.PAIR_TIMES.get(pair_number, (None, None))
@ -120,7 +175,7 @@ def _build_lesson_slot(raw_entry: dict) -> LessonSlot:
) )
def get_today_lessons(now: datetime | None = None) -> tuple[str, list[LessonSlot]]: def get_lessons_for_day(now: datetime | None = None) -> tuple[str, list[LessonSlot]]:
now = _normalize_now(now) now = _normalize_now(now)
week_key = _current_week_key(now) week_key = _current_week_key(now)
raw_day = config.LESSON_SCHEDULE.get(week_key, {}).get(now.weekday(), []) raw_day = config.LESSON_SCHEDULE.get(week_key, {}).get(now.weekday(), [])
@ -169,20 +224,24 @@ def _format_lesson_line(lesson: LessonSlot) -> str:
def next_lesson_text(now: datetime | None = None) -> str: def next_lesson_text(now: datetime | None = None) -> str:
now = _normalize_now(now) now = _normalize_now(now)
_, lessons = get_today_lessons(now) _, lessons = get_lessons_for_day(now)
next_lesson = _find_next_lesson(now, lessons) next_lesson = _find_next_lesson(now, lessons)
if not next_lesson: if not next_lesson:
return "Следующей пары сегодня нет." return "Следующей пары сегодня нет."
return f"Следующая пара: {_format_lesson_brief(next_lesson)}" return f"Следующая пара: {_format_lesson_brief(next_lesson)}"
def build_schedule_text(now: datetime | None = None) -> str: def build_schedule_text(now: datetime | None = None, requested_weekday: int | None = None) -> str:
now = _normalize_now(now) now = _normalize_now(now)
week_key, lessons = get_today_lessons(now) target_now = _resolve_schedule_date(now, requested_weekday)
current_lesson = _find_current_lesson(now, lessons) week_key, lessons = get_lessons_for_day(target_now)
next_lesson = _find_next_lesson(now, lessons) show_live_status = target_now.date() == now.date()
current_lesson = _find_current_lesson(now, lessons) if show_live_status else None
next_lesson = _find_next_lesson(now, lessons) if show_live_status else None
week_label = "Верхняя" if week_key == "upper" else "Нижняя" week_label = "Верхняя" if week_key == "upper" else "Нижняя"
day_label = _weekday_names.get(now.weekday(), "Сегодня") day_label = _weekday_names.get(target_now.weekday(), "Сегодня")
if not show_live_status:
day_label = f"{day_label}, {target_now:%d.%m.%Y}"
lines = [f"{week_label} неделя", day_label] lines = [f"{week_label} неделя", day_label]
if lessons: if lessons:
@ -190,8 +249,9 @@ def build_schedule_text(now: datetime | None = None) -> str:
lines.extend(_format_lesson_line(lesson) for lesson in lessons) lines.extend(_format_lesson_line(lesson) for lesson in lessons)
else: else:
lines.append("") lines.append("")
lines.append("На сегодня расписание пустое.") lines.append("На этот день расписание пустое.")
if show_live_status:
lines.append("") lines.append("")
if current_lesson: if current_lesson:
if current_lesson.is_free: if current_lesson.is_free:
@ -877,7 +937,18 @@ async def handle_start(message: Message):
async def handle_schedule_cmd(message: Message): async def handle_schedule_cmd(message: Message):
now = datetime.now(ZoneInfo(config.DEFAULT_TZ)) now = datetime.now(ZoneInfo(config.DEFAULT_TZ))
await message.answer(build_schedule_text(now)) raw_text = message.text or ""
parts = raw_text.split(maxsplit=1)
requested_weekday = None
if len(parts) > 1:
requested_weekday = _parse_weekday_arg(parts[1].split(maxsplit=1)[0])
if requested_weekday is None:
await message.answer(
"Не понял день недели. Примеры: /schedule mon, /schedule fri, /schedule пн, /schedule чт."
)
return
await message.answer(build_schedule_text(now, requested_weekday))
async def handle_break_cmd(message: Message): async def handle_break_cmd(message: Message):
now = datetime.now(ZoneInfo(config.DEFAULT_TZ)) now = datetime.now(ZoneInfo(config.DEFAULT_TZ))