diff --git a/commands.yaml b/commands.yaml index e7c91fc..73bba1b 100644 --- a/commands.yaml +++ b/commands.yaml @@ -277,6 +277,136 @@ read_screen: - сними текст с экрана action: {type: ocr_screen} +# Window management via pyautogui hotkeys +show_desktop: + phrases: + - покажи рабочий стол + - свернуть всё + - сверни все окна + - покажи десктоп + action: {type: keys, sequence: 'winleft+d'} + +maximize_window: + phrases: + - разверни окно + - развернуть окно + - на весь экран + - раскрой окно + action: {type: keys, sequence: 'winleft+up'} + +minimize_window: + phrases: + - сверни окно + - свернуть окно + - убери окно + action: {type: keys, sequence: 'winleft+down'} + +snap_left: + phrases: + - окно влево + - сдвинь влево + - прижми влево + action: {type: keys, sequence: 'winleft+left'} + +snap_right: + phrases: + - окно вправо + - сдвинь вправо + - прижми вправо + action: {type: keys, sequence: 'winleft+right'} + +close_window: + phrases: + - закрой окно + - закрой вкладку + - закрой текущее + action: {type: keys, sequence: 'alt+f4'} + +restore_all: + phrases: + - восстанови окна + - верни окна + - разверни окна + action: {type: keys, sequence: 'winleft+shift+m'} + +task_view: + phrases: + - открой все окна + - обзор окон + - таск вью + - покажи все окна + action: {type: keys, sequence: 'winleft+tab'} + +# Clipboard read aloud +read_clipboard: + phrases: + - прочитай буфер + - что в буфере + - прочитай из буфера + - озвучь буфер + action: {type: clipboard_read} + +# Notes +add_note: + phrases: + - запиши + - запиши заметку + - запиши идею + - запомни + - запомни идею + - сделай заметку + action: {type: notes_add} + +open_notes: + phrases: + - покажи заметки + - открой заметки + - что я записал + action: {type: notes_open} + +# Process killer +kill_process: + phrases: + - убей процесс + - закрой программу + - завершить процесс + - выруби процесс + action: {type: process_kill} + +# Web search shortcuts +search_google: + phrases: + - загугли + - поиск в гугл + - поищи в гугл + - найди в гугл + - ищи в гугл + action: {type: websearch, service: google} + +search_youtube: + phrases: + - найди на ютубе + - поиск на ютубе + - ищи на ютубе + - ютуб поиск + action: {type: websearch, service: youtube} + +search_wiki: + phrases: + - найди в википедии + - поиск в википедии + - википедия + - вики поиск + action: {type: websearch, service: wiki} + +search_yandex: + phrases: + - поищи в яндекс + - найди в яндекс + - яндекс поиск + - пробей в яндекс + action: {type: websearch, service: yandex} + thanks: phrases: - спасибо diff --git a/main.py b/main.py index 09dd4dc..865c359 100644 --- a/main.py +++ b/main.py @@ -395,6 +395,122 @@ def do_file_search(action, voice: str): _speak(f"Найдено {len(lines)}, открываю первое: {os.path.basename(first)}.") +_NOTES_TRIGGERS = ( + 'запиши заметку', 'запиши идею', 'сделай заметку', 'запомни идею', + 'запиши', 'запомни', +) + +_PROCESS_KILL_TRIGGERS = ( + 'завершить процесс', 'выруби процесс', 'убей процесс', 'kill процесс', + 'закрой программу', +) + +_WEBSEARCH_TRIGGERS = ( + 'загугли', 'поиск в гугл', 'поищи в гугл', 'найди в гугл', 'ищи в гугл', + 'найди на ютубе', 'поиск на ютубе', 'ищи на ютубе', 'ютуб поиск', + 'найди в википедии', 'поиск в википедии', 'википедия', 'вики поиск', + 'поищи в яндекс', 'найди в яндекс', 'яндекс поиск', 'пробей в яндекс', +) + +_PROCESS_ALIASES = { + 'хром': 'chrome', 'хрома': 'chrome', + 'спотифай': 'spotify', + 'едж': 'msedge', 'эдж': 'msedge', 'edge': 'msedge', + 'файрфокс': 'firefox', 'фаерфокс': 'firefox', + 'вскод': 'code', 'vs code': 'code', + 'дискорд': 'discord', + 'телега': 'telegram', 'телеграм': 'telegram', + 'стим': 'steam', + 'обс': 'obs64', + 'проводник': 'explorer', + 'блокнот': 'notepad', + 'калькулятор': 'calculatorapp', + 'диспетчер задач': 'taskmgr', +} + +_WEBSEARCH_BASES = { + 'google': 'https://www.google.com/search?q=', + 'youtube': 'https://www.youtube.com/results?search_query=', + 'wiki': 'https://ru.wikipedia.org/wiki/Special:Search?search=', + 'yandex': 'https://yandex.ru/search/?text=', +} + + +def do_clipboard_read(action, voice: str): + res = subprocess.run( + ['powershell', '-NoProfile', '-Command', 'Get-Clipboard'], + capture_output=True, text=True, encoding='utf-8', timeout=10, + ) + text = (res.stdout or '').strip() + if not text: + _speak("Буфер пуст.") + return + to_speak = text[:400] + if len(text) > 400: + to_speak += " ... и так далее" + _speak(to_speak.replace('\r', ' ').replace('\n', ' ')) + + +def do_notes_add(action, voice: str): + body = _strip_trigger(voice, _NOTES_TRIGGERS).lstrip(' ,.:').strip() + if not body: + _speak("Что записать, сэр?") + return + user_profile = os.environ.get('USERPROFILE', os.path.expanduser('~')) + notes_path = os.path.join(user_profile, 'Documents', 'jarvis-notes.txt') + stamp = datetime.datetime.now().strftime('%Y-%m-%d %H:%M') + line = f"[{stamp}] {body}\n" + try: + os.makedirs(os.path.dirname(notes_path), exist_ok=True) + with open(notes_path, 'a', encoding='utf-8') as f: + f.write(line) + except OSError as e: + print(f"notes append failed: {e}") + _speak("Не удалось записать.") + return + _speak(f"Записал: {body[:120]}") + + +def do_notes_open(action, voice: str): + user_profile = os.environ.get('USERPROFILE', os.path.expanduser('~')) + notes_path = os.path.join(user_profile, 'Documents', 'jarvis-notes.txt') + if not os.path.isfile(notes_path): + _speak("Заметок пока нет.") + return + subprocess.Popen(['cmd', '/c', 'start', '', notes_path], shell=False) + _speak("Открываю заметки.") + + +def do_process_kill(action, voice: str): + name = _strip_trigger(voice, _PROCESS_KILL_TRIGGERS).strip().lower() + target = _PROCESS_ALIASES.get(name, name) + if not target: + _speak("Какой процесс?") + return + exe = target if target.endswith('.exe') else target + '.exe' + res = subprocess.run( + ['taskkill', '/F', '/IM', exe], + capture_output=True, text=True, timeout=10, + ) + if res.returncode == 0: + _speak(f"Закрыл {exe}.") + else: + _speak(f"Не нашёл процесс {exe}.") + + +def do_websearch(action, voice: str): + import urllib.parse + svc = action.get('service', 'google') + base = _WEBSEARCH_BASES.get(svc, _WEBSEARCH_BASES['google']) + query = _strip_trigger(voice, _WEBSEARCH_TRIGGERS).strip() + if not query: + _speak(f"Что искать в {svc}?") + return + url = base + urllib.parse.quote(query) + webbrowser.open(url) + _speak(f"Открываю {svc}: {query[:100]}") + + def do_audio_devices_panel(action, voice: str): # Full programmatic switching needs IPolicyConfig + IMMDeviceEnumerator # via comtypes; out of scope for this pass. Opens Sound control panel so @@ -489,6 +605,16 @@ def run_action(action): do_file_search(action, _current_voice or '') elif t == 'audio_devices_panel': do_audio_devices_panel(action, _current_voice or '') + elif t == 'clipboard_read': + do_clipboard_read(action, _current_voice or '') + elif t == 'notes_add': + do_notes_add(action, _current_voice or '') + elif t == 'notes_open': + do_notes_open(action, _current_voice or '') + elif t == 'process_kill': + do_process_kill(action, _current_voice or '') + elif t == 'websearch': + do_websearch(action, _current_voice or '') _current_voice: str = ''