diff --git a/main.py b/main.py index 9c93ac3..957f558 100644 --- a/main.py +++ b/main.py @@ -83,6 +83,84 @@ def minutes_until_break(now: datetime | None = None) -> str: return "Пары закончились, перекур только завтра." + +def next_break_window(now: datetime | None = None) -> tuple[datetime | None, datetime | None]: + """Возвращает (начало, конец) ближайшего перекура или (None, None).""" + if now is None: + now = datetime.now(ZoneInfo(config.DEFAULT_TZ)) + elif now.tzinfo is None: + now = now.replace(tzinfo=ZoneInfo(config.DEFAULT_TZ)) + + day = now.weekday() + today_schedule = config.SCHEDULE.get(day, []) + if not today_schedule: + return None, None + + def dt_for(t: time) -> datetime: + return now.replace(hour=t.hour, minute=t.minute, second=0, microsecond=0) + + for start_t, end_t, br_minutes in today_schedule: + if not br_minutes: + continue + end_dt = dt_for(end_t) + br_start = end_dt + br_end = end_dt + timedelta(minutes=br_minutes) + # Ближайший будущий или текущий перекур + if now < br_end: + return br_start, br_end + + return None, None + + +# --- Авто-напоминание о перекуре --- +_break_tasks: dict[int, tuple[datetime, asyncio.Task]] = {} +_break_notified: dict[int, datetime] = {} + + +async def _send_break_photo(chat_id: int, bot: Bot, start_dt: datetime) -> None: + last = _break_notified.get(chat_id) + if last and last == start_dt: + return # Уже кидали уведомление для этого слота + + photo_path = config.BASE_DIR / "vape.jpg" + if not photo_path.exists(): + logger.warning("vape.jpg not found, skip break photo") + return + + try: + await bot.send_photo(chat_id=chat_id, photo=FSInputFile(photo_path), caption="идем курить") + _break_notified[chat_id] = start_dt + except Exception: + logger.exception("Failed to send break photo") + + +async def _break_notification(chat_id: int, bot: Bot, start_dt: datetime) -> None: + try: + now = datetime.now(start_dt.tzinfo) + delay = (start_dt - now).total_seconds() + if delay > 0: + await asyncio.sleep(delay) + await _send_break_photo(chat_id, bot, start_dt) + except asyncio.CancelledError: + pass + finally: + stored = _break_tasks.get(chat_id) + if stored and stored[0] == start_dt: + _break_tasks.pop(chat_id, None) + + +def schedule_break_notification(chat_id: int, bot: Bot, start_dt: datetime) -> None: + existing = _break_tasks.get(chat_id) + if existing: + scheduled_start, task = existing + if scheduled_start == start_dt and not task.done(): + return # Уже ждём этот же слот + if not task.done(): + task.cancel() + + task = asyncio.create_task(_break_notification(chat_id, bot, start_dt)) + _break_tasks[chat_id] = (start_dt, task) + # --- Работа с изображениями (Макеты и Мемы) --- def load_font(size: int) -> ImageFont.FreeTypeFont: @@ -414,7 +492,13 @@ async def handle_start(message: Message): await message.answer("Соси") async def handle_break_cmd(message: Message): - await message.answer(minutes_until_break()) + now = datetime.now(ZoneInfo(config.DEFAULT_TZ)) + await message.answer(minutes_until_break(now)) + + # Ставим будильник на ближайший перекур для этого чата + start_dt, _ = next_break_window(now) + if start_dt: + schedule_break_notification(message.chat.id, message.bot, start_dt) async def handle_penis_cmd(message: Message): if not message.from_user: diff --git a/vape.jpg b/vape.jpg new file mode 100644 index 0000000..2ccf97c Binary files /dev/null and b/vape.jpg differ