feat: parity — reminders, today/tomorrow/yesterday, voice type, dice, stopwatch (67 commands)

Mirrors the rust feature/pc-tools work. yaml.safe_load + ast.parse pass.
Commands count: 56 → 67 (+11).

main.py:
+ do_set_reminder — strips trigger, parses "N (секунд|минут|часов)"
  with Russian-word numeral support (один/две/пять/десять/...).
  Spawns a daemon thread that time.sleep()s then runs tts.va_speak
  + PowerShell WScript.Shell.Popup. Defaults to 5 min when number/
  unit are missing.
+ do_date_query — accepts {offset: 0/1/-1}, computes target date with
  datetime.timedelta, formats with hardcoded Russian weekday/month
  names (no locale dependency), speaks "Сегодня/Завтра/Вчера + ...".
+ do_type_text — clipboard + pyautogui.hotkey('ctrl', 'v') so it
  works in any focused window.
+ do_dice — coin/dice/random via random.choice / random.randint.
+ do_stopwatch — stores start ts in
  %LOCALAPPDATA%\com.priler.jarvis\stopwatch.txt; start/check/stop
  ops with humanised elapsed string.

commands.yaml +11 entries:
+ set_reminder
+ today / tomorrow / yesterday (action: date_query, offset: 0/1/-1)
+ type_text
+ coin_flip / roll_dice / random_number (action: dice, kind: ...)
+ stopwatch_start / stopwatch_check / stopwatch_stop
  (action: stopwatch, op: start/check/stop)
This commit is contained in:
Bossiara13 2026-05-15 12:28:57 +03:00
parent 69359ed5c0
commit 5790ca18af
2 changed files with 254 additions and 0 deletions

View file

@ -469,6 +469,90 @@ open_sound_panel:
- управление звуком - управление звуком
action: {type: open_sound_panel} action: {type: open_sound_panel}
set_reminder:
phrases:
- напомни через
- напомни мне через
- поставь напоминание через
- установи таймер на
- таймер на
action: {type: set_reminder}
today:
phrases:
- какой сегодня день
- какое сегодня число
- сегодня какое число
- что сегодня
action: {type: date_query, offset: 0}
tomorrow:
phrases:
- какой день завтра
- что завтра
- завтра какое число
- какое число завтра
action: {type: date_query, offset: 1}
yesterday:
phrases:
- какой был вчера день
- что было вчера
- вчера какое число было
action: {type: date_query, offset: -1}
type_text:
phrases:
- напечатай
- набери текст
- введи текст
- впиши
action: {type: type_text}
coin_flip:
phrases:
- подбрось монетку
- брось монетку
- орёл или решка
- орел или решка
action: {type: dice, kind: coin}
roll_dice:
phrases:
- брось кубик
- подбрось кубик
- кинь кубик
action: {type: dice, kind: dice}
random_number:
phrases:
- случайное число
- выбери случайное число
- рандомное число
action: {type: dice, kind: random}
stopwatch_start:
phrases:
- засеки время
- запусти секундомер
- старт секундомер
- включи секундомер
action: {type: stopwatch, op: start}
stopwatch_check:
phrases:
- сколько прошло
- сколько времени прошло
- проверь секундомер
action: {type: stopwatch, op: check}
stopwatch_stop:
phrases:
- стоп секундомер
- останови секундомер
- выключи секундомер
action: {type: stopwatch, op: stop}
thanks: thanks:
phrases: phrases:
- спасибо - спасибо

170
main.py
View file

@ -728,6 +728,166 @@ def do_open_sound_panel(action, voice: str):
_speak("Открываю панель звука.") _speak("Открываю панель звука.")
import re as _re_reminders
import threading as _threading
_REMINDER_TRIGGERS = (
'поставь напоминание через', 'установи таймер на',
'напомни мне через', 'напомни через', 'напомни',
'remind me in', 'set reminder in', 'set timer for', 'timer for',
)
_RUS_NUMERALS = {
'один': 1, 'одну': 1, 'одна': 1, 'полтора': 1, 'полторы': 1,
'два': 2, 'две': 2, 'три': 3, 'четыре': 4, 'пять': 5,
'шесть': 6, 'семь': 7, 'восемь': 8, 'девять': 9, 'десять': 10,
'пятнадцать': 15, 'двадцать': 20, 'тридцать': 30, 'сорок': 40,
'пятьдесят': 50,
}
_UNIT_SECS = {
'секунд': 1, 'секунду': 1, 'секунды': 1, 'second': 1, 'seconds': 1, 'sec': 1,
'минут': 60, 'минуту': 60, 'минуты': 60, 'мин': 60,
'minute': 60, 'minutes': 60, 'min': 60,
'час': 3600, 'часа': 3600, 'часов': 3600, 'часик': 3600,
'hour': 3600, 'hours': 3600, 'hr': 3600,
}
def _fmt_seconds(sec: int) -> str:
sec = int(sec)
if sec < 60: return f"{sec} сек"
if sec < 3600: return f"{sec // 60} мин"
return f"{sec // 3600} ч {(sec % 3600) // 60} мин"
def do_set_reminder(action, voice: str):
rest = _strip_trigger(voice, _REMINDER_TRIGGERS).strip().lower()
number, unit, body = None, None, None
m = _re_reminders.match(r'^(\d+)\s+(.+)$', rest)
if m:
number = int(m.group(1))
rest = m.group(2)
else:
head, _, tail = rest.partition(' ')
if head in _RUS_NUMERALS:
number = _RUS_NUMERALS[head]
rest = tail
head, _, tail = rest.partition(' ')
if head:
for w, secs in _UNIT_SECS.items():
if head.startswith(w):
unit = secs
body = tail
break
if number is None:
number, unit, body = 5, unit or 60, rest
elif unit is None:
unit, body = 60, rest
total = max(1, min(86400, number * unit))
body = (body or '').strip() or "Напоминание!"
def fire():
time.sleep(total)
try:
recorder.stop()
tts.va_speak(f"Сэр, напоминаю: {body}")
except Exception:
pass
finally:
time.sleep(0.2)
try: recorder.start()
except Exception: pass
try:
subprocess.Popen(
['powershell', '-NoProfile', '-Command',
"$wsh = New-Object -ComObject WScript.Shell; "
f"$wsh.Popup('{body.replace(chr(39), chr(39)+chr(39))}', 30, "
f"'Напоминание J.A.R.V.I.S.', 64) | Out-Null"],
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL,
)
except Exception:
pass
_threading.Thread(target=fire, daemon=False).start()
_speak(f"Через {_fmt_seconds(total)} напомню: {body}.")
def do_date_query(action, voice: str):
offset = action.get('offset', 0)
target = datetime.datetime.now() + datetime.timedelta(days=offset)
weekdays = ['понедельник', 'вторник', 'среда', 'четверг', 'пятница', 'суббота', 'воскресенье']
months = ['января', 'февраля', 'марта', 'апреля', 'мая', 'июня',
'июля', 'августа', 'сентября', 'октября', 'ноября', 'декабря']
text = f"{weekdays[target.weekday()]}, {target.day} {months[target.month - 1]}"
labels = {0: 'Сегодня', 1: 'Завтра', -1: 'Вчера'}
_speak(f"{labels.get(offset, '')} {text}.")
def do_type_text(action, voice: str):
triggers = ('набери текст', 'введи текст', 'напечатай', 'впиши',
'type text', 'write text', 'type', 'надрукуй')
text = _strip_trigger(voice, triggers)
if not text:
_speak("Что напечатать?")
return
_set_clipboard(text)
time.sleep(0.15)
try:
import pyautogui
pyautogui.hotkey('ctrl', 'v')
except Exception as e:
print(f"voice type failed: {e}")
_speak("Не получилось напечатать.")
def do_dice(action, voice: str):
kind = action.get('kind', 'random')
if kind == 'coin':
_speak(random.choice(['Орёл.', 'Решка.']))
elif kind == 'dice':
_speak(f"Выпало {random.randint(1, 6)}.")
else:
_speak(f"Число {random.randint(1, 100)}.")
def _stopwatch_state_path() -> str:
base = os.environ.get('LOCALAPPDATA', os.path.expanduser('~'))
p = os.path.join(base, 'com.priler.jarvis', 'stopwatch.txt')
os.makedirs(os.path.dirname(p), exist_ok=True)
return p
def do_stopwatch(action, voice: str):
op = action.get('op', 'check')
state = _stopwatch_state_path()
def fmt(sec):
sec = int(sec)
if sec < 60: return f"{sec} секунд"
if sec < 3600: return f"{sec // 60} мин {sec % 60} сек"
return f"{sec // 3600} ч {(sec % 3600) // 60} мин {sec % 60} сек"
if op == 'start':
with open(state, 'w', encoding='utf-8') as f:
f.write(str(time.time()))
_speak("Секундомер запущен.")
return
if not os.path.isfile(state):
_speak("Секундомер не запущен.")
return
try:
started = float(open(state, 'r', encoding='utf-8').read().strip())
except (OSError, ValueError):
_speak("Секундомер не запущен.")
return
elapsed = time.time() - started
if op == 'stop':
try: os.unlink(state)
except OSError: pass
_speak(f"Прошло {fmt(elapsed)}.")
else:
_speak(f"Прошло {fmt(elapsed)}.")
def do_sysinfo(action, voice: str): def do_sysinfo(action, voice: str):
topic = action.get('topic', 'all') topic = action.get('topic', 'all')
@ -836,6 +996,16 @@ def run_action(action):
do_list_projects(action, _current_voice or '') do_list_projects(action, _current_voice or '')
elif t == 'open_sound_panel': elif t == 'open_sound_panel':
do_open_sound_panel(action, _current_voice or '') do_open_sound_panel(action, _current_voice or '')
elif t == 'set_reminder':
do_set_reminder(action, _current_voice or '')
elif t == 'date_query':
do_date_query(action, _current_voice or '')
elif t == 'type_text':
do_type_text(action, _current_voice or '')
elif t == 'dice':
do_dice(action, _current_voice or '')
elif t == 'stopwatch':
do_stopwatch(action, _current_voice or '')
_current_voice: str = '' _current_voice: str = ''