chore: sync local changes

This commit is contained in:
2026-03-01 12:55:17 +03:00
parent 27ee32be38
commit f1bc254c6b
8 changed files with 192 additions and 292 deletions

View File

@@ -1,8 +1,5 @@
"""Timer module."""
# Модуль таймера.
# Отвечает за установку таймеров (в оперативной памяти), их проверку и воспроизведение звука.
import subprocess
import re
import json
@@ -25,7 +22,7 @@ ALARM_SOUND = BASE_DIR / "assets" / "sounds" / "Apex-1.mp3"
TIMER_FILE = BASE_DIR / "data" / "timers.json"
ASK_TIMER_TIME_PROMPT = "На какое время мне поставить таймер?"
# --- Number words parsing helpers (ru) ---
# Числа словами
_NUMBER_UNITS = {
"ноль": 0,
"один": 1,
@@ -103,13 +100,19 @@ _UNIT_LEMMAS = {
"мин": "minutes",
"сек": "seconds",
}
_UNIT_LEMMAS = {
"час": "hours",
"минута": "minutes",
"секунда": "seconds",
"мин": "minutes",
"сек": "seconds",
}
_UNIT_FORMS = {
"hours": ("час", "часа", "часов"),
"minutes": ("минуту", "минуты", "минут"),
"seconds": ("секунду", "секунды", "секунд"),
}
# Optional ordinal formatting for list numbering.
try:
from num2words import num2words
except Exception:
@@ -251,12 +254,11 @@ def _format_ordinal_index(index: int) -> str:
class TimerManager:
def __init__(self):
# Список активных таймеров: {"end_time": datetime, "label": str}
self.timers = []
self.load_timers()
def load_timers(self):
"""Загрузка списка таймеров из JSON файла."""
"""Загрузка из файла."""
if TIMER_FILE.exists():
try:
with open(TIMER_FILE, "r", encoding="utf-8") as f:
@@ -277,7 +279,7 @@ class TimerManager:
self.timers = sorted(timers, key=lambda x: x["end_time"])
def save_timers(self):
"""Сохранение списка таймеров в JSON файл."""
"""Сохранение в файл."""
payload = [
{"end_time": t["end_time"].isoformat(), "label": t.get("label", "")}
for t in self.timers
@@ -289,7 +291,7 @@ class TimerManager:
print(f"❌ Ошибка сохранения таймеров: {e}")
def describe_timers(self) -> str:
"""Возвращает текстовое описание активных таймеров."""
"""Описание активных таймеров."""
if not self.timers:
return "Активных таймеров нет."
@@ -312,38 +314,29 @@ class TimerManager:
return "Активные таймеры: " + "; ".join(items) + "."
def add_timer(self, seconds: int, label: str):
"""Добавление нового таймера."""
"""Добавить таймер."""
end_time = datetime.now() + timedelta(seconds=seconds)
self.timers.append({"end_time": end_time, "label": label})
# Сортируем, чтобы ближайший был первым
self.timers.sort(key=lambda x: x["end_time"])
self.save_timers()
print(f"⏳ Таймер установлен на {label} (до {end_time.strftime('%H:%M:%S')})")
print(f"⏳ Таймер: {label} (до {end_time.strftime('%H:%M:%S')})")
def cancel_all_timers(self):
"""Отмена всех таймеров."""
"""Отменить все таймеры."""
count = len(self.timers)
self.timers = []
self.save_timers()
print(f"🔕 Все таймеры ({count}) отменены.")
print(f"🔕 Таймеры отменены: {count}")
def check_timers(self):
"""
Проверка: не истек ли какой-то таймер?
Вызывается в главном цикле.
Возвращает True, если таймер сработал (и был обработан).
"""
"""Проверка таймеров. Возвращает True если сработал."""
if not self.timers:
return False
now = datetime.now()
# Смотрим первый (самый ранний) таймер
# Используем индекс 0, так как список отсортирован
first_timer = self.timers[0]
if now >= first_timer["end_time"]:
# Таймер сработал!
# Удаляем его из списка
label = first_timer["label"]
self.timers.pop(0)
self.save_timers()
@@ -355,36 +348,30 @@ class TimerManager:
return False
def trigger_timer(self, label: str):
"""
Логика срабатывания таймера.
Запускает воспроизведение MP3 и слушает команду "Стоп".
"""
print(f"🔔 ТАЙМЕР НА {label} СРАБОТАЛ! (Скажите 'Стоп')")
"""Срабатывание таймера."""
print(f"🔔 ТАЙМЕР {label}!")
# Запуск плеера mpg123 в бесконечном цикле
cmd = ["mpg123", "-q", "--loop", "-1", str(ALARM_SOUND)]
try:
process = subprocess.Popen(cmd)
except FileNotFoundError:
print(
"❌ Ошибка: mpg123 не найден. Установите его: sudo apt install mpg123"
)
print("❌ mpg123 не найден. Установите: sudo apt install mpg123")
return
try:
# Цикл ожидания стоп-команды
while True:
text = listen(timeout_seconds=3.0, detection_timeout=3.0, fast_stop=True)
text = listen(
timeout_seconds=3.0, detection_timeout=3.0, fast_stop=True
)
if text:
if is_stop_command(text, mode="lenient"):
print(f"🛑 Таймер остановлен по команде: '{text}'")
print(f"🛑 Остановлен: '{text}'")
break
except Exception as e:
print(f"❌ Ошибка во время таймера: {e}")
print(f"❌ Ошибка: {e}")
finally:
# Обязательно убиваем процесс плеера
process.terminate()
try:
process.wait(timeout=1)
@@ -393,12 +380,9 @@ class TimerManager:
print("🔕 Таймер выключен.")
def parse_command(self, text: str) -> str | None:
"""
Парсинг команды установки таймера.
Примеры: "таймер на 5 минут", "засеки 10 секунд".
"""
"""Парсинг команды таймера."""
text = _normalize_timer_text(text.lower())
# Ключевые слова для таймера
if not any(word in text for word in ["таймер", "засеки", "поставь таймер"]):
return None
@@ -413,9 +397,6 @@ class TimerManager:
return "Хорошо, все таймеры отменены."
# Поиск времени
# Ищем комбинации: число + (час/мин/сек)
# Пример: "1 час 30 минут", "5 минут", "30 секунд"
total_seconds = 0
parts = []
hours = None
@@ -438,7 +419,7 @@ class TimerManager:
if match_seconds:
seconds = int(match_seconds.group(1))
# Дополняем числительные словами (например, "одну минуту")
# Числа словами
word_values = _extract_word_time_values(text)
if hours is None and word_values["hours"] is not None:
hours = word_values["hours"]
@@ -456,9 +437,7 @@ class TimerManager:
found_time = any(value is not None for value in [hours, minutes, seconds])
if found_time:
total_seconds = (
(hours or 0) * 3600 + (minutes or 0) * 60 + (seconds or 0)
)
total_seconds = (hours or 0) * 3600 + (minutes or 0) * 60 + (seconds or 0)
if has_fractional:
total_seconds = int(round(total_seconds))
h = total_seconds // 3600
@@ -480,16 +459,17 @@ class TimerManager:
label = " ".join(parts)
self.add_timer(total_seconds, label)
return f"Поставил таймер на {label}."
# Если попросили поставить таймер, но не назвали время — задаем уточняющий вопрос.
if re.search(r"(постав|установ|запусти|включи|засеки)", text) or text.strip() in {
# Если время не названо — спрашиваем
if re.search(
r"(постав|установ|запусти|включи|засеки)", text
) or text.strip() in {
"таймер",
"поставь таймер",
}:
return ASK_TIMER_TIME_PROMPT
# Если сказали "таймер", но не нашли время.
return "Я не понял, на сколько поставить таймер. Скажите, например, 'таймер на 5 минут'."
return "Я не понял, на сколько поставить таймер."
# Глобальный экземпляр