From ef8da9cba80382b0fc6588dc4926f4c2c50f3415 Mon Sep 17 00:00:00 2001 From: Bossiara13 <236771060+DmitryBykov-ISPO@users.noreply.github.com> Date: Fri, 15 May 2026 13:11:37 +0300 Subject: [PATCH] =?UTF-8?q?feat:=20parity=20=E2=80=94=20brightness,=20wind?= =?UTF-8?q?ow=20switch,=20spelling,=20network,=20summarize=20(100=20comman?= =?UTF-8?q?ds)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Mirrors the rust tier-3 batch. 90 → 100 commands (round number — feels right for an "имба" milestone). main.py: + do_brightness: WMI WmiMonitorBrightness read + WmiSetBrightness write, ±20% step or 100/10 absolute, "no_wmi" sentinel for desktops. + do_window_switch: same alias map as process_kill (xrom→chrome/...), PS finds process by name OR window-title substring, picks newest StartTime, AppActivate via WScript.Shell. + do_spell_out: split chars, join with ". ", _speak. + do_network_settings: action.section = wifi|bluetooth|network → matching ms-settings:* URI via cmd /c start "". + do_my_ip: Get-NetIPAddress filtered to non-loopback non-APIPA IPv4, WAN via Invoke-RestMethod ipify, speaks "Локальный X. Внешний Y". + do_summarize_selection: pyautogui.hotkey(ctrl, c) → 250ms wait → Get-Clipboard → Groq @ temp 0.3 ("3-5 sentence retell") → speak + clipboard. commands.yaml +10 entries: brightness_up/down/max/min, switch_to_window, spell_out, open_wifi_settings, open_bluetooth_settings, my_ip, summarize_selection. Backlog: closed all "Tier-3 still TODO" pure-Lua items. Remaining TODOs (pomodoro, currency-math, stocks, translate-clipboard, daily briefing, etc.) are even smaller LOC and can roll in the next batch. --- commands.yaml | 77 ++++++++++++++++++++++ main.py | 172 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 249 insertions(+) diff --git a/commands.yaml b/commands.yaml index 5144ede..e6aed09 100644 --- a/commands.yaml +++ b/commands.yaml @@ -740,6 +740,83 @@ random_choice: - выбрать action: {type: random_choice} +brightness_up: + phrases: + - ярче + - сделай ярче + - увеличь яркость + - поярче + action: {type: brightness, op: up} + +brightness_down: + phrases: + - темнее + - сделай темнее + - уменьши яркость + - потемнее + action: {type: brightness, op: down} + +brightness_max: + phrases: + - максимальная яркость + - яркость на максимум + action: {type: brightness, op: max} + +brightness_min: + phrases: + - минимальная яркость + - яркость на минимум + action: {type: brightness, op: min} + +switch_to_window: + phrases: + - переключись на + - переключи на + - активируй окно + - покажи окно + - перейди в + action: {type: window_switch} + +spell_out: + phrases: + - произнеси по буквам + - продиктуй по буквам + - произнеси буквы + - по буквам + action: {type: spell_out} + +open_wifi_settings: + phrases: + - настройки wifi + - настройки вайфай + - wifi настройки + - сеть + action: {type: network_settings, section: wifi} + +open_bluetooth_settings: + phrases: + - настройки блютуз + - блютуз + action: {type: network_settings, section: bluetooth} + +my_ip: + phrases: + - мой ай пи + - мой ip + - какой у меня айпи + - айпишник + action: {type: my_ip} + +summarize_selection: + phrases: + - суммируй + - перескажи выделенное + - кратко перескажи + - о чём это + - что выделил + - перескажи это + action: {type: summarize_selection} + thanks: phrases: - спасибо diff --git a/main.py b/main.py index b0eb515..b96fb7a 100644 --- a/main.py +++ b/main.py @@ -1193,6 +1193,166 @@ def do_random_choice(action, voice: str): _speak(f"Я выбираю {random.choice(options)}.") +_BRIGHTNESS_PS = ( + "$ErrorActionPreference='SilentlyContinue'; " + "try { $cur = (Get-WmiObject -Namespace root\\WMI -Class WmiMonitorBrightness -EA Stop).CurrentBrightness } " + "catch { Write-Output 'no_wmi'; exit 0 } " + "$target = switch ('__OP__') { 'up'{[Math]::Min(100,$cur+20)} 'down'{[Math]::Max(0,$cur-20)} 'max'{100} 'min'{10} default{$cur} } " + "(Get-WmiObject -Namespace root\\WMI -Class WmiMonitorBrightnessMethods).WmiSetBrightness(1,$target) | Out-Null; " + "Write-Output $target" +) + + +def do_brightness(action, voice: str): + op = action.get('op', 'up') + ps = _BRIGHTNESS_PS.replace('__OP__', op) + res = subprocess.run( + ['powershell', '-NoProfile', '-Command', ps], + capture_output=True, text=True, encoding='utf-8', timeout=10, + ) + out = (res.stdout or '').strip() + if out == 'no_wmi' or not out: + _speak("Не поддерживается на этом дисплее.") + return + _speak(f"Яркость {out} процентов.") + + +_WINDOW_ALIASES = { + 'хром': 'chrome', 'хрома': 'chrome', 'едж': 'msedge', 'эдж': 'msedge', + 'файрфокс': 'firefox', 'фаерфокс': 'firefox', + 'вскод': 'code', 'vs code': 'code', + 'дискорд': 'discord', 'телега': 'telegram', 'телеграм': 'telegram', + 'стим': 'steam', 'обс': 'obs64', + 'проводник': 'explorer', 'блокнот': 'notepad', + 'терминал': 'windowsterminal', 'спотифай': 'spotify', + 'яндекс': 'browser', 'браузер': 'browser', +} +_WINDOW_TRIGGERS = ('переключись на', 'переключи на', 'активируй окно', + 'покажи окно', 'перейди в', + 'switch to', 'activate window', 'focus window', + 'перемкни на') + + +def do_window_switch(action, voice: str): + query = _strip_trigger(voice, _WINDOW_TRIGGERS).strip().lower() + if not query: + _speak("Какое окно?") + return + target = _WINDOW_ALIASES.get(query, query).replace("'", "") + ps = ( + f"$procs = Get-Process | Where-Object {{ $_.MainWindowTitle -and " + f"($_.ProcessName -like '*{target}*' -or $_.MainWindowTitle -like '*{target}*') }} " + f"| Sort-Object -Property StartTime -Descending; " + f"if ($procs.Count -eq 0) {{ Write-Output 'NOT_FOUND'; exit 0 }}; " + f"$p = $procs[0]; " + f"$wsh = New-Object -ComObject WScript.Shell; " + f"$null = $wsh.AppActivate($p.Id); " + f"Write-Output $p.MainWindowTitle" + ) + res = subprocess.run(['powershell', '-NoProfile', '-Command', ps], + capture_output=True, text=True, encoding='utf-8', timeout=10) + out = (res.stdout or '').strip() + if out == 'NOT_FOUND' or not out: + _speak(f"Не нашёл окно {query}.") + return + _speak(f"Переключаюсь на {out[:60]}.") + + +_SPELLING_TRIGGERS = ('продиктуй по буквам', 'произнеси по буквам', + 'произнеси буквы', 'по буквам', + 'spell out', 'spell it', 'вимов по буквах') + + +def do_spell_out(action, voice: str): + word = _strip_trigger(voice, _SPELLING_TRIGGERS).strip() + if not word: + _speak("Что произнести?") + return + letters = [ch for ch in word if not ch.isspace()] + if not letters: + return + _speak('. '.join(letters) + '.') + + +_NETWORK_URIS = { + 'wifi': 'ms-settings:network-wifi', + 'bluetooth': 'ms-settings:bluetooth', + 'network': 'ms-settings:network', +} + + +def do_network_settings(action, voice: str): + section = action.get('section', 'network') + uri = _NETWORK_URIS.get(section, _NETWORK_URIS['network']) + subprocess.Popen(['cmd', '/c', 'start', '', uri], shell=False) + label = {'wifi': 'Wi-Fi', 'bluetooth': 'Bluetooth', 'network': 'сети'}.get(section, 'сети') + _speak(f"Открываю настройки {label}.") + + +def do_my_ip(action, voice: str): + ps = ( + "$lan = (Get-NetIPAddress -AddressFamily IPv4 -EA SilentlyContinue | " + "Where-Object { $_.PrefixOrigin -in 'Dhcp','Manual' } | " + "Where-Object { $_.IPAddress -notmatch '^127\\.' -and $_.IPAddress -notmatch '^169\\.254\\.' } | " + "Select-Object -First 1).IPAddress; " + "if (-not $lan) { $lan = '(none)' }; " + "$wan = try { (Invoke-RestMethod -Uri 'https://api.ipify.org?format=json' -TimeoutSec 5).ip } catch { 'unavailable' }; " + "Write-Output ('LAN ' + $lan + ' WAN ' + $wan)" + ) + res = subprocess.run(['powershell', '-NoProfile', '-Command', ps], + capture_output=True, text=True, encoding='utf-8', timeout=15) + out = (res.stdout or '').strip() + if not out: + _speak("Не получил IP.") + return + say = out.replace('LAN', 'Локальный').replace('WAN', '. Внешний') + _speak(say) + + +_SUMMARIZE_TRIGGERS = ('суммируй', 'перескажи выделенное', 'кратко перескажи', + 'о чём это', 'что выделил', 'перескажи это', + 'summarise this', 'summarize selection', 'tldr', + 'перекажи це', 'стисло перекажи') + + +def do_summarize_selection(action, voice: str): + if not config.GROQ_TOKEN: + _speak("Groq токен не задан.") + return + try: + import pyautogui + pyautogui.hotkey('ctrl', 'c') + except Exception as e: + print(f"summarize copy failed: {e}") + _speak("Не удалось скопировать выделение.") + return + time.sleep(0.25) + res = subprocess.run(['powershell', '-NoProfile', '-Command', 'Get-Clipboard'], + capture_output=True, text=True, encoding='utf-8', timeout=10) + text = (res.stdout or '').strip() + if len(text) < 20: + _speak("Сначала выдели текст и повтори.") + return + if len(text) > 8000: + text = text[:8000] + " ..." + sys_prompt = ("Перескажи следующий текст одним абзацем (3-5 предложений). " + "Сохрани ключевые факты и имена, без 'вот суммарно'.") + try: + resp = client.chat.completions.create( + model=config.GROQ_MODEL, + messages=[{'role': 'system', 'content': sys_prompt}, + {'role': 'user', 'content': text}], + max_tokens=400, temperature=0.3, + ) + summary = resp.choices[0].message.content.strip() + except Exception as e: + print(f"summarize LLM failed: {e}") + _speak("LLM не ответила.") + return + _set_clipboard(summary) + _speak(summary) + + def do_stopwatch(action, voice: str): op = action.get('op', 'check') state = _stopwatch_state_path() @@ -1365,6 +1525,18 @@ def run_action(action): do_mouse(action, _current_voice or '') elif t == 'random_choice': do_random_choice(action, _current_voice or '') + elif t == 'brightness': + do_brightness(action, _current_voice or '') + elif t == 'window_switch': + do_window_switch(action, _current_voice or '') + elif t == 'spell_out': + do_spell_out(action, _current_voice or '') + elif t == 'network_settings': + do_network_settings(action, _current_voice or '') + elif t == 'my_ip': + do_my_ip(action, _current_voice or '') + elif t == 'summarize_selection': + do_summarize_selection(action, _current_voice or '') _current_voice: str = ''