Улучшенный будильник, таймер, перевод
This commit is contained in:
@@ -7,9 +7,9 @@ import json
|
||||
import subprocess
|
||||
import re
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from ..core.config import BASE_DIR
|
||||
from ..audio.stt import listen
|
||||
from ..core.commands import is_stop_command
|
||||
|
||||
# Файл базы данных будильников
|
||||
ALARM_FILE = BASE_DIR / "data" / "alarms.json"
|
||||
@@ -22,6 +22,73 @@ class AlarmClock:
|
||||
self.alarms = []
|
||||
self.load_alarms()
|
||||
|
||||
def _normalize_days(self, days):
|
||||
if not days:
|
||||
return None
|
||||
unique = sorted({int(day) for day in days})
|
||||
return unique
|
||||
|
||||
def _days_key(self, days):
|
||||
normalized = self._normalize_days(days)
|
||||
return tuple(normalized) if normalized else None
|
||||
|
||||
def _format_days_phrase(self, days):
|
||||
normalized = self._normalize_days(days)
|
||||
if not normalized:
|
||||
return ""
|
||||
|
||||
day_set = set(normalized)
|
||||
if day_set == {0, 1, 2, 3, 4}:
|
||||
return "по будням"
|
||||
if day_set == {5, 6}:
|
||||
return "по выходным"
|
||||
if len(day_set) == 7:
|
||||
return "каждый день"
|
||||
|
||||
names = {
|
||||
0: "понедельникам",
|
||||
1: "вторникам",
|
||||
2: "средам",
|
||||
3: "четвергам",
|
||||
4: "пятницам",
|
||||
5: "субботам",
|
||||
6: "воскресеньям",
|
||||
}
|
||||
ordered = [names[d] for d in normalized if d in names]
|
||||
if not ordered:
|
||||
return ""
|
||||
if len(ordered) == 1:
|
||||
return f"по {ordered[0]}"
|
||||
return "по " + ", ".join(ordered[:-1]) + " и " + ordered[-1]
|
||||
|
||||
def _extract_alarm_days(self, text: str):
|
||||
text = text.lower().replace("ё", "е")
|
||||
days = set()
|
||||
|
||||
if re.search(r"\b(каждый день|ежедневно)\b", text):
|
||||
return [0, 1, 2, 3, 4, 5, 6]
|
||||
|
||||
if re.search(r"\b(по будн|в будн|будние)\b", text):
|
||||
days.update([0, 1, 2, 3, 4])
|
||||
|
||||
if re.search(r"\b(по выходн|в выходн|выходные)\b", text):
|
||||
days.update([5, 6])
|
||||
|
||||
day_patterns = {
|
||||
0: r"\bпонедельн\w*\b",
|
||||
1: r"\bвторник\w*\b",
|
||||
2: r"\bсред\w*\b",
|
||||
3: r"\bчетверг\w*\b",
|
||||
4: r"\bпятниц\w*\b",
|
||||
5: r"\bсуббот\w*\b",
|
||||
6: r"\bвоскресен\w*\b",
|
||||
}
|
||||
for day_idx, pattern in day_patterns.items():
|
||||
if re.search(pattern, text):
|
||||
days.add(day_idx)
|
||||
|
||||
return self._normalize_days(days)
|
||||
|
||||
def load_alarms(self):
|
||||
"""Загрузка списка будильников из JSON файла."""
|
||||
if ALARM_FILE.exists():
|
||||
@@ -42,15 +109,30 @@ class AlarmClock:
|
||||
|
||||
def add_alarm(self, hour: int, minute: int):
|
||||
"""Добавление нового будильника (или обновление существующего)."""
|
||||
return self.add_alarm_with_days(hour, minute, days=None)
|
||||
|
||||
def add_alarm_with_days(self, hour: int, minute: int, days=None):
|
||||
"""Добавление нового будильника (или обновление существующего) с днями недели."""
|
||||
days_key = self._days_key(days)
|
||||
for alarm in self.alarms:
|
||||
if alarm["hour"] == hour and alarm["minute"] == minute:
|
||||
if (
|
||||
alarm.get("hour") == hour
|
||||
and alarm.get("minute") == minute
|
||||
and self._days_key(alarm.get("days")) == days_key
|
||||
):
|
||||
alarm["active"] = True
|
||||
alarm["days"] = days_key
|
||||
alarm["last_triggered"] = None
|
||||
self.save_alarms()
|
||||
return
|
||||
|
||||
self.alarms.append({"hour": hour, "minute": minute, "active": True})
|
||||
self.alarms.append(
|
||||
{"hour": hour, "minute": minute, "active": True, "days": days_key}
|
||||
)
|
||||
self.save_alarms()
|
||||
print(f"⏰ Будильник установлен на {hour:02d}:{minute:02d}")
|
||||
days_phrase = self._format_days_phrase(days_key)
|
||||
suffix = f" {days_phrase}" if days_phrase else ""
|
||||
print(f"⏰ Будильник установлен на {hour:02d}:{minute:02d}{suffix}")
|
||||
|
||||
def cancel_all_alarms(self):
|
||||
"""Выключение (деактивация) всех будильников."""
|
||||
@@ -59,6 +141,27 @@ class AlarmClock:
|
||||
self.save_alarms()
|
||||
print("🔕 Все будильники отменены.")
|
||||
|
||||
def describe_alarms(self) -> str:
|
||||
"""Возвращает текстовое описание активных будильников."""
|
||||
active = [
|
||||
alarm
|
||||
for alarm in self.alarms
|
||||
if alarm.get("active") and "hour" in alarm and "minute" in alarm
|
||||
]
|
||||
if not active:
|
||||
return "Активных будильников нет."
|
||||
|
||||
active.sort(key=lambda a: (a["hour"], a["minute"]))
|
||||
items = []
|
||||
for alarm in active:
|
||||
time_str = f"{alarm['hour']:02d}:{alarm['minute']:02d}"
|
||||
days_phrase = self._format_days_phrase(alarm.get("days"))
|
||||
if days_phrase:
|
||||
items.append(f"{days_phrase} в {time_str}")
|
||||
else:
|
||||
items.append(time_str)
|
||||
return "Активные будильники: " + ", ".join(items) + "."
|
||||
|
||||
def check_alarms(self):
|
||||
"""
|
||||
Проверка: не пора ли звенеть?
|
||||
@@ -71,12 +174,30 @@ class AlarmClock:
|
||||
for alarm in self.alarms:
|
||||
if alarm["active"]:
|
||||
if alarm["hour"] == now.hour and alarm["minute"] == now.minute:
|
||||
days = self._normalize_days(alarm.get("days"))
|
||||
if days and now.weekday() not in days:
|
||||
continue
|
||||
|
||||
last_triggered = alarm.get("last_triggered")
|
||||
if last_triggered:
|
||||
try:
|
||||
last_dt = datetime.fromisoformat(last_triggered)
|
||||
if (
|
||||
last_dt.date() == now.date()
|
||||
and last_dt.hour == now.hour
|
||||
and last_dt.minute == now.minute
|
||||
):
|
||||
continue
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
print(
|
||||
f"⏰ ВРЕМЯ БУДИЛЬНИКА: {alarm['hour']:02d}:{alarm['minute']:02d}"
|
||||
)
|
||||
alarm["active"] = (
|
||||
False # Одноразовый будильник, выключаем после срабатывания
|
||||
)
|
||||
if not days:
|
||||
# Одноразовый будильник, выключаем после срабатывания
|
||||
alarm["active"] = False
|
||||
alarm["last_triggered"] = now.isoformat()
|
||||
triggered = True
|
||||
self.trigger_alarm() # Запуск звука и ожидание стоп-слова
|
||||
break # Звоним только один за раз
|
||||
@@ -106,21 +227,11 @@ class AlarmClock:
|
||||
return
|
||||
|
||||
try:
|
||||
stop_words = [
|
||||
"стоп",
|
||||
"хватит",
|
||||
"тихо",
|
||||
"замолчи",
|
||||
"отмена",
|
||||
"александр стоп",
|
||||
]
|
||||
|
||||
# Цикл ожидания стоп-команды
|
||||
while True:
|
||||
text = listen(timeout_seconds=3.0, detection_timeout=3.0)
|
||||
if text:
|
||||
text_lower = text.lower()
|
||||
if any(word in text_lower for word in stop_words):
|
||||
if is_stop_command(text, mode="lenient"):
|
||||
print(f"🛑 Будильник остановлен по команде: '{text}'")
|
||||
break
|
||||
|
||||
@@ -144,17 +255,26 @@ class AlarmClock:
|
||||
if "будильник" not in text and "разбуди" not in text:
|
||||
return None
|
||||
|
||||
if "будильник" in text and re.search(
|
||||
r"(какие|какой|список|активн|покажи|сколько|есть ли)", text
|
||||
):
|
||||
return self.describe_alarms()
|
||||
|
||||
if "отмени" in text:
|
||||
self.cancel_all_alarms()
|
||||
return "Хорошо, я отменил все будильники."
|
||||
|
||||
days = self._extract_alarm_days(text)
|
||||
|
||||
# Поиск формата "7:30", "7.30"
|
||||
match = re.search(r"\b(\d{1,2})[:.-](\d{2})\b", text)
|
||||
if match:
|
||||
h, m = int(match.group(1)), int(match.group(2))
|
||||
if 0 <= h <= 23 and 0 <= m <= 59:
|
||||
self.add_alarm(h, m)
|
||||
return f"Я установил будильник на {h} часов {m} минут."
|
||||
self.add_alarm_with_days(h, m, days=days)
|
||||
days_phrase = self._format_days_phrase(days)
|
||||
suffix = f" {days_phrase}" if days_phrase else ""
|
||||
return f"Я установил будильник на {h} часов {m} минут{suffix}."
|
||||
|
||||
# Поиск формата словами "на 7 часов 15 минут"
|
||||
match_time = re.search(
|
||||
@@ -174,8 +294,10 @@ class AlarmClock:
|
||||
h = 0
|
||||
|
||||
if 0 <= h <= 23 and 0 <= m <= 59:
|
||||
self.add_alarm(h, m)
|
||||
return f"Хорошо, разбужу вас в {h}:{m:02d}."
|
||||
self.add_alarm_with_days(h, m, days=days)
|
||||
days_phrase = self._format_days_phrase(days)
|
||||
suffix = f" {days_phrase}" if days_phrase else ""
|
||||
return f"Хорошо, разбужу вас в {h}:{m:02d}{suffix}."
|
||||
|
||||
return "Я не понял время для будильника. Пожалуйста, скажите точное время, например 'семь тридцать'."
|
||||
|
||||
|
||||
Reference in New Issue
Block a user