feat: parity — games, mouse, random_choice + new packs (90 commands)
Mirrors the rust feature/pc-tools work. yaml.safe_load + ast.parse pass. 82 → 90 commands (+8). main.py: + do_launch_game / do_list_games: reads ~/Documents/jarvis-games.json (creates sample on first call with dota / cs2 / elden ring / witcher 3 / factorio entries). Fuzzy name+alias match, launches via steam://rungameid/<appid> (webbrowser.open), epic_uri, or cmd /c start "" path. + do_mouse: action param op = left|right|middle|double|scroll_up| scroll_down. Embedded PowerShell P/Invokes user32!mouse_event with the right flag pairs. + do_random_choice: strips trigger, splits on " или " / " or " / ", " / " либо " / " чи ", picks one with random.choice. commands.yaml +8 entries: + launch_game / list_games + mouse_left_click / mouse_right_click / mouse_double_click / mouse_scroll_up / mouse_scroll_down (all action:mouse, op:...) + random_choice
This commit is contained in:
parent
40a46567b8
commit
b3a5bcabe5
2 changed files with 186 additions and 0 deletions
|
|
@ -681,6 +681,65 @@ help:
|
|||
- что можешь
|
||||
action: {type: help_commands}
|
||||
|
||||
launch_game:
|
||||
phrases:
|
||||
- запусти игру
|
||||
- включи игру
|
||||
- поиграем
|
||||
- хочу поиграть
|
||||
- запусти доту
|
||||
- включи кс
|
||||
- включи фортнайт
|
||||
action: {type: launch_game}
|
||||
|
||||
list_games:
|
||||
phrases:
|
||||
- какие у меня игры
|
||||
- список игр
|
||||
- покажи игры
|
||||
action: {type: list_games}
|
||||
|
||||
mouse_left_click:
|
||||
phrases:
|
||||
- клик
|
||||
- кликни
|
||||
- нажми мышкой
|
||||
action: {type: mouse, op: left}
|
||||
|
||||
mouse_right_click:
|
||||
phrases:
|
||||
- правый клик
|
||||
- клик правой
|
||||
- правой кнопкой
|
||||
action: {type: mouse, op: right}
|
||||
|
||||
mouse_double_click:
|
||||
phrases:
|
||||
- двойной клик
|
||||
- дабл клик
|
||||
- двойное нажатие
|
||||
action: {type: mouse, op: double}
|
||||
|
||||
mouse_scroll_down:
|
||||
phrases:
|
||||
- промотай вниз
|
||||
- прокрути вниз
|
||||
action: {type: mouse, op: scroll_down}
|
||||
|
||||
mouse_scroll_up:
|
||||
phrases:
|
||||
- промотай вверх
|
||||
- прокрути вверх
|
||||
action: {type: mouse, op: scroll_up}
|
||||
|
||||
random_choice:
|
||||
phrases:
|
||||
- выбери из
|
||||
- выбери
|
||||
- решай за меня
|
||||
- выбрать
|
||||
action: {type: random_choice}
|
||||
|
||||
thanks:
|
||||
phrases:
|
||||
- спасибо
|
||||
|
|
|
|||
127
main.py
127
main.py
|
|
@ -1074,6 +1074,125 @@ def do_help_commands(action, voice: str):
|
|||
_speak(f"Загружено {len(names)} команд. Например: {preview}. Полный список в буфере.")
|
||||
|
||||
|
||||
_GAME_TRIGGERS = ('запусти игру', 'включи игру', 'хочу поиграть', 'поиграем',
|
||||
'запусти', 'включи', 'launch game', 'play game', 'launch',
|
||||
'запусти гру', 'увімкни гру')
|
||||
|
||||
|
||||
def _load_games_config():
|
||||
user_profile = os.environ.get('USERPROFILE', os.path.expanduser('~'))
|
||||
cfg_path = os.path.join(user_profile, 'Documents', 'jarvis-games.json')
|
||||
if not os.path.isfile(cfg_path):
|
||||
sample = [
|
||||
{"name": "dota", "aliases": ["доту", "доту 2", "dota 2"], "steam_appid": 570},
|
||||
{"name": "cs2", "aliases": ["кс", "контру", "контр страйк"], "steam_appid": 730},
|
||||
{"name": "elden ring", "aliases": ["элден", "элден ринг"], "steam_appid": 1245620},
|
||||
{"name": "witcher 3", "aliases": ["ведьмак", "ведьмака 3"], "steam_appid": 292030},
|
||||
{"name": "factorio", "aliases": ["факторио"], "steam_appid": 427520},
|
||||
]
|
||||
os.makedirs(os.path.dirname(cfg_path), exist_ok=True)
|
||||
with open(cfg_path, 'w', encoding='utf-8') as f:
|
||||
json.dump(sample, f, ensure_ascii=False, indent=2)
|
||||
return cfg_path, sample, True
|
||||
try:
|
||||
with open(cfg_path, 'r', encoding='utf-8') as f:
|
||||
return cfg_path, json.load(f), False
|
||||
except (OSError, json.JSONDecodeError) as e:
|
||||
print(f"games config read failed: {e}")
|
||||
return cfg_path, [], False
|
||||
|
||||
|
||||
def do_launch_game(action, voice: str):
|
||||
cfg_path, entries, created = _load_games_config()
|
||||
if created:
|
||||
_speak("Создал шаблон, добавь свои игры и повтори.")
|
||||
subprocess.Popen(['cmd', '/c', 'start', '', cfg_path])
|
||||
return
|
||||
query = _strip_trigger(voice, _GAME_TRIGGERS).strip().lower()
|
||||
if not query:
|
||||
_speak("Какую игру?")
|
||||
return
|
||||
|
||||
def name_match(e, q):
|
||||
if e['name'].lower() == q: return True
|
||||
if q in e['name'].lower(): return True
|
||||
for a in e.get('aliases', []):
|
||||
if a.lower() == q or a.lower() in q: return True
|
||||
return False
|
||||
|
||||
match = next((e for e in entries if name_match(e, query)), None)
|
||||
if not match:
|
||||
_speak(f"Не нашёл {query}.")
|
||||
return
|
||||
|
||||
appid = match.get('steam_appid')
|
||||
path = match.get('path')
|
||||
epic_uri = match.get('epic_uri')
|
||||
if appid:
|
||||
webbrowser.open(f"steam://rungameid/{appid}")
|
||||
elif epic_uri:
|
||||
webbrowser.open(epic_uri)
|
||||
elif path and os.path.isfile(path):
|
||||
subprocess.Popen(['cmd', '/c', 'start', '', path])
|
||||
else:
|
||||
_speak(f"Не настроен запуск для {match['name']}.")
|
||||
return
|
||||
_speak(f"Запускаю {match['name']}.")
|
||||
|
||||
|
||||
def do_list_games(action, voice: str):
|
||||
cfg_path, entries, created = _load_games_config()
|
||||
if created or not entries:
|
||||
_speak("Список пуст.")
|
||||
return
|
||||
names = [e['name'] for e in entries]
|
||||
_speak(f"В библиотеке {len(names)}: {', '.join(names)}.")
|
||||
|
||||
|
||||
_MOUSE_PS = r"""
|
||||
Add-Type -Name MouseM -Namespace Win32 -MemberDefinition @'
|
||||
[DllImport("user32.dll")]
|
||||
public static extern void mouse_event(uint flags, int dx, int dy, int data, System.UIntPtr extra);
|
||||
'@
|
||||
$LD=0x0002; $LU=0x0004; $RD=0x0008; $RU=0x0010; $MD=0x0020; $MU=0x0040; $WHEEL=0x0800
|
||||
function Click([uint32]$d,[uint32]$u){ [Win32.MouseM]::mouse_event($d,0,0,0,[UIntPtr]::Zero); Start-Sleep -Milliseconds 30; [Win32.MouseM]::mouse_event($u,0,0,0,[UIntPtr]::Zero) }
|
||||
switch ($args[0]) {
|
||||
'left' { Click $LD $LU }
|
||||
'right' { Click $RD $RU }
|
||||
'middle' { Click $MD $MU }
|
||||
'double' { Click $LD $LU; Start-Sleep -Milliseconds 50; Click $LD $LU }
|
||||
'scroll_up' { [Win32.MouseM]::mouse_event($WHEEL,0,0, 360,[UIntPtr]::Zero) }
|
||||
'scroll_down' { [Win32.MouseM]::mouse_event($WHEEL,0,0,-360,[UIntPtr]::Zero) }
|
||||
}
|
||||
""".strip()
|
||||
|
||||
|
||||
def do_mouse(action, voice: str):
|
||||
op = action.get('op', 'left')
|
||||
subprocess.run(
|
||||
['powershell', '-NoProfile', '-ExecutionPolicy', 'Bypass', '-Command', _MOUSE_PS, op],
|
||||
capture_output=True, timeout=5,
|
||||
)
|
||||
|
||||
|
||||
_CHOICE_TRIGGERS = ('выбери из', 'выбери', 'решай за меня', 'выбрать',
|
||||
'pick from', 'choose from', 'decide between',
|
||||
'обери з', 'вибери')
|
||||
|
||||
|
||||
def do_random_choice(action, voice: str):
|
||||
rest = _strip_trigger(voice, _CHOICE_TRIGGERS).strip().lower()
|
||||
options = None
|
||||
for sep in (' или ', ' or ', ', ', ' либо ', ' чи '):
|
||||
if sep in rest:
|
||||
options = [t.strip() for t in rest.split(sep) if t.strip()]
|
||||
break
|
||||
if not options or len(options) < 2:
|
||||
_speak("Скажи: выбери из X или Y или Z.")
|
||||
return
|
||||
_speak(f"Я выбираю {random.choice(options)}.")
|
||||
|
||||
|
||||
def do_stopwatch(action, voice: str):
|
||||
op = action.get('op', 'check')
|
||||
state = _stopwatch_state_path()
|
||||
|
|
@ -1238,6 +1357,14 @@ def run_action(action):
|
|||
do_power(action, _current_voice or '')
|
||||
elif t == 'help_commands':
|
||||
do_help_commands(action, _current_voice or '')
|
||||
elif t == 'launch_game':
|
||||
do_launch_game(action, _current_voice or '')
|
||||
elif t == 'list_games':
|
||||
do_list_games(action, _current_voice or '')
|
||||
elif t == 'mouse':
|
||||
do_mouse(action, _current_voice or '')
|
||||
elif t == 'random_choice':
|
||||
do_random_choice(action, _current_voice or '')
|
||||
|
||||
|
||||
_current_voice: str = ''
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue