"""Stopwatch module.""" import json import re from datetime import datetime from ..core.config import BASE_DIR STOPWATCH_FILE = BASE_DIR / "data" / "stopwatches.json" # Optional ordinal formatting for list numbering. try: from num2words import num2words except Exception: num2words = None def _format_ordinal_index(index: int) -> str: if num2words is None: return f"{index}-й" try: return num2words(index, lang="ru", to="ordinal", case="nominative", gender="m") except Exception: return f"{index}-й" def _format_duration(seconds: float) -> str: total = int(round(max(0, seconds))) hours = total // 3600 minutes = (total % 3600) // 60 sec = total % 60 parts = [] if hours: parts.append(f"{hours} ч") if minutes: parts.append(f"{minutes} мин") parts.append(f"{sec} сек") return " ".join(parts) class StopwatchManager: def __init__(self): self.stopwatches = [] self.load_stopwatches() def load_stopwatches(self): if not STOPWATCH_FILE.exists(): return try: with open(STOPWATCH_FILE, "r", encoding="utf-8") as f: raw = json.load(f) except Exception as e: print(f"❌ Ошибка загрузки секундомеров: {e}") return items = [] for item in raw: try: stopwatch_id = int(item["id"]) except Exception: continue items.append( { "id": stopwatch_id, "name": str(item.get("name", "")).strip(), "elapsed": float(item.get("elapsed", 0)), "running": bool(item.get("running", False)), "started_at": item.get("started_at"), } ) self.stopwatches = sorted(items, key=lambda x: x["id"]) def save_stopwatches(self): payload = [ { "id": sw["id"], "name": sw.get("name", ""), "elapsed": sw.get("elapsed", 0), "running": sw.get("running", False), "started_at": sw.get("started_at"), } for sw in self.stopwatches ] try: with open(STOPWATCH_FILE, "w", encoding="utf-8") as f: json.dump(payload, f, indent=4) except Exception as e: print(f"❌ Ошибка сохранения секундомеров: {e}") def _next_id(self) -> int: if not self.stopwatches: return 1 return max(sw["id"] for sw in self.stopwatches) + 1 def _now_iso(self) -> str: return datetime.now().isoformat() def _elapsed_now(self, stopwatch: dict) -> float: elapsed = float(stopwatch.get("elapsed", 0)) if not stopwatch.get("running"): return elapsed started_at = stopwatch.get("started_at") if not started_at: return elapsed try: started_dt = datetime.fromisoformat(started_at) except Exception: return elapsed delta = (datetime.now() - started_dt).total_seconds() return elapsed + max(0, delta) def _running(self): return [sw for sw in self.stopwatches if sw.get("running")] def _paused(self): return [sw for sw in self.stopwatches if not sw.get("running")] def has_running_stopwatches(self) -> bool: return bool(self._running()) def describe_active_stopwatches(self) -> str: running = self._running() if not running: return "Активных секундомеров нет." running.sort(key=lambda sw: sw["id"]) items = [] for idx, sw in enumerate(running, start=1): ordinal = _format_ordinal_index(idx) duration = _format_duration(self._elapsed_now(sw)) name = sw.get("name", "") if name: items.append(f"{ordinal}) {name} — {duration}") else: items.append(f"{ordinal}) {duration}") return "Активные секундомеры: " + "; ".join(items) + "." def start_stopwatch(self, name: str = "") -> str: stopwatch = { "id": self._next_id(), "name": name.strip(), "elapsed": 0.0, "running": True, "started_at": self._now_iso(), } self.stopwatches.append(stopwatch) self.save_stopwatches() if stopwatch["name"]: return f"Запустил секундомер «{stopwatch['name']}»." return "Запустил секундомер." def pause_stopwatches(self) -> str: running = self._running() if not running: return "Сейчас нет активных секундомеров." elapsed_items = [] for sw in running: elapsed_now = self._elapsed_now(sw) elapsed_items.append( { "id": sw["id"], "name": sw.get("name", ""), "elapsed": elapsed_now, } ) sw["elapsed"] = elapsed_now sw["running"] = False sw["started_at"] = None self.save_stopwatches() count = len(running) if count == 1: elapsed_text = _format_duration(elapsed_items[0]["elapsed"]) return f"Остановил секундомер. Он работал {elapsed_text}." details = [] for idx, item in enumerate(sorted(elapsed_items, key=lambda x: x["id"]), start=1): ordinal = _format_ordinal_index(idx) elapsed_text = _format_duration(item["elapsed"]) name = item.get("name", "") if name: details.append(f"{ordinal} «{name}» — {elapsed_text}") else: details.append(f"{ordinal} — {elapsed_text}") return f"Остановил секундомеры: {count} шт. Время: " + "; ".join(details) + "." def resume_stopwatches(self) -> str: paused = self._paused() if not paused: return "Пауза не активна: секундомеры уже запущены или отсутствуют." for sw in paused: sw["running"] = True sw["started_at"] = self._now_iso() self.save_stopwatches() count = len(paused) if count == 1: return "Продолжил секундомер." return f"Продолжил секундомеры: {count} шт." def reset_stopwatches(self) -> str: if not self.stopwatches: return "Секундомеров для сброса нет." count = len(self.stopwatches) self.stopwatches = [] self.save_stopwatches() if count == 1: return "Секундомер сброшен." return f"Сбросил секундомеры: {count} шт." def parse_command(self, text: str) -> str | None: text = text.lower().strip() has_stopwatch_word = any( word in text for word in [ "секундомер", "секундомеры", "секундомером", "секундомера", "секундомеру", ] ) if not has_stopwatch_word: return None if re.search(r"(какие|какой|список|активн|покажи|сколько|есть ли)", text): return self.describe_active_stopwatches() if any(word in text for word in ["сброс", "удали", "отмени", "очист"]): return self.reset_stopwatches() if any(word in text for word in ["продолж", "возобнов"]): return self.resume_stopwatches() if any(word in text for word in ["стоп", "останов", "пауза"]): return self.pause_stopwatches() if "постав" in text or "установ" in text: return self.start_stopwatch() if any(word in text for word in ["запусти", "включи", "старт", "начни"]): return self.start_stopwatch() # Если пользователь просто сказал "секундомер", трактуем как запуск. if text in {"секундомер", "запусти секундомер", "включи секундомер"}: return self.start_stopwatch() return "Я понял команду про секундомер, но не распознал действие. Скажите: запусти, стоп, продолжи, сбрось или покажи активные секундомеры." _stopwatch_manager = None def get_stopwatch_manager(): global _stopwatch_manager if _stopwatch_manager is None: _stopwatch_manager = StopwatchManager() return _stopwatch_manager