feat(browser): Vivaldi app-mode with uBlock single window #2

Closed
opened 2026-08-09 19:17:41 +03:00 by slaid098 · 0 comments
Owner

Контекст

Голосовой ассистент для незрячих пользователей (мама, Паркинсон, слепота). Основное использование — YouTube. Текущая реализация браузера (services/browser.py:32) использует webbrowser.open(url) — открывает новую вкладку при каждом запросе «ютуб X» и никогда их не закрывает. BrowserState (browser.py:18-22) хранит только URL-строку, не handle окна → нечем закрыть. За десятки команд накапливается сотня вкладок → браузер перегружается → ПК зависает. Для незрячего пользователя панель вкладок невидима → копится незаметно.

Реклама: README.md:90 даёт неверный совет — утверждает что «Vivaldi блокирует рекламу по умолчанию, без расширений». На самом деле встроенный блокировщик Vivaldi выключен по умолчанию и неэффективен против YouTube-рекламы. Нужен uBlock Origin (расширение) в активном профиле. Код не указывает профиль → зависит от того, какой активен. «Иногда не блокирует» = чистый/сбросившийся профиль.

Задача

Заменить webbrowser.open на запуск Vivaldi в app-режиме (--app=<url>) с указанным профилем (где установлен uBlock Origin). App-режим открывает URL в отдельном окне без панели вкладок — вкладки физически не копятся. Каждый новый запуск = закрываем старое app-окно и открываем новое. PID не сохраняем (требование пользователя) — закрываем по маркеру профиля через taskkill.

Изменения в коде

config.py — новые поля:

  • browser_path: str — путь к vivaldi.exe, default пустой (авто-detect). Авто-detect: проверять C:\Users\{user}\AppData\Local\Vivaldi\Application\vivaldi.exe, C:\Program Files\Vivaldi\Application\vivaldi.exe, C:\Program Files (x86)\Vivaldi\Application\vivaldi.exe.
  • browser_profile: str — путь к профилю Vivaldi с предустановленным uBlock Origin. Default пустой. Если пустой — fallback на --app= без --user-data-dir (использует default профиль).
  • Env vars: BROWSER_PATH, BROWSER_PROFILE.

services/browser.py — полная переработка:

  • Убрать webbrowser импорт.
  • BrowserState: хранить _profile_marker (строка-маркер из browser_profile для taskkill фильтра по заголовку окна), НЕ хранить PID.
  • open_url(url):
    1. Если _profile_marker задан — taskkill /IM vivaldi.exe /FI "WINDOWTITLE eq *{marker}*" (закрыть старое app-окно, без сохранения PID).
    2. subprocess.Popen([browser_path, f"--app={url}", f"--user-data-dir={browser_profile}"]) (если profile задан) или subprocess.Popen([browser_path, f"--app={url}"]) (без profile).
    3. Логировать открытие.
  • Авто-detect browser_path если не задан в env.
  • На не-Windows — fallback на webbrowser.open (Linux для dev/CI).

README.md:

  • Убрать строку «Vivaldi блокирует рекламу по умолчанию, без расширений» (неверно).
  • Добавить инструкцию: установить Vivaldi, установить расширение uBlock Origin из Chrome Web Store, указать путь к профилю в BROWSER_PROFILE.

.env.template — новые переменные:

# Путь к Vivaldi (если пусто — авто-detect на Windows)
BROWSER_PATH=
# Путь к профилю Vivaldi с uBlock Origin (если пусто — default профиль)
BROWSER_PROFILE=

Тесты tests/test_browser.py — переписать:

  • Мок subprocess.Popen — проверка что вызывается с --app=<url> и --user-data-dir=<profile>.
  • Проверка что перед новым открытием вызывается taskkill (мок subprocess) с фильтром по маркеру профиля.
  • Проверка авто-detect путей Vivaldi на Windows.
  • Проверка fallback на webbrowser.open на не-Windows.
  • Убрать старые тесты webbrowser.open.

Контракты

  • open_browser_url(url: str) -> None — публичная функция (browser.py:56-62), вызывается из youtube_flow.py:102. Сигнатура не меняется.
  • youtube_flow._play_video (youtube_flow.py:99-103) — не меняется, вызывает open_browser_url(url).
  • ОС: Windows (taskkill). На Linux (dev/CI) — fallback на webbrowser.open.

Инварианты

  • Один URL = одно окно (app-режим). Вкладки не копятся физически.
  • PID не сохраняем (требование пользователя) — закрываем по маркеру профиля через taskkill /FI "WINDOWTITLE eq ...".
  • uBlock Origin устанавливается пользователем один раз в профиль Vivaldi. Код не устанавливает расширения.
  • --app= режим: окно без панели вкладок, без адресной строки — идеально для accessibility.
  • Команды «закрыть браузер» голосом — НЕ добавляем (требование пользователя: не лезть куда не просят).

Граничные случаи

  • Vivaldi не установлен → авто-detect не находит → логировать error, озвучить speak("Браузер не найден"), не падать.
  • BROWSER_PROFILE пустой → запуск без --user-data-dir (default профиль Vivaldi). Реклама может не блокироваться — логировать warning.
  • taskkill не находит окно по маркеру (первый запуск, ничего не открыто) — silently ignore (exit code 128 = no match, не ошибка).
  • Vivaldi уже запущен в обычном режиме (не app) — --app= откроет новое окно в том же процессе. taskkill по маркеру закроет только app-окно (фильтр по заголовку), не тронет обычные окна.
  • Несколько быстрых запросов подряд — taskkill перед каждым --app= гарантирует одно окно.
  • Unicode в URL (кириллические поисковые запросы) — subprocess.Popen с encoding="utf-8" на Windows.

Влияние на связанные компоненты

  • services/youtube_flow.py:102 — вызывает open_browser_url, не меняется.
  • services/commands.py — не затрагивается (нет команд закрытия).
  • nlu/handlers.py — не затрагивается.
  • README.md:88-90 — переписать секцию «YouTube без рекламы».
  • Release ZIP — добавить инструкцию по настройке Vivaldi + uBlock в README.md.

Вне scope

  • Авто-установка Vivaldi (скачивание + тихая установка при отсутствии) — отдельный future-issue
  • Голосовая команда «закрыть браузер/видео» — НЕ добавляем (требование пользователя)
  • Лимит вкладок / throttle — не нужно (app-режим = одно окно физически)
  • Сохранение PID — НЕ сохраняем (требование пользователя)
  • Установка uBlock Origin автоматически — пользователь ставит один раз

Критерии приемки

  • webbrowser.open заменён на subprocess.Popen([browser_path, "--app=<url>", ...])
  • Перед каждым открытием — taskkill по маркеру профиля (PID не сохраняется)
  • BROWSER_PATH, BROWSER_PROFILE в config.py и .env.template
  • Авто-detect пути Vivaldi на Windows (3 стандартных пути)
  • Fallback на webbrowser.open на не-Windows
  • README.md — убрано неверное про «блокирует по умолчанию», добавлена инструкция uBlock
  • Тесты test_browser.py переписаны: мок subprocess, проверка taskkill + --app=
  • ruff check src/ tests/ — чисто
  • mypy src/ — чисто
  • pytest — зелёный, coverage >= 75%
## Контекст Голосовой ассистент для незрячих пользователей (мама, Паркинсон, слепота). Основное использование — YouTube. Текущая реализация браузера (`services/browser.py:32`) использует `webbrowser.open(url)` — открывает новую вкладку при каждом запросе «ютуб X» и **никогда их не закрывает**. `BrowserState` (`browser.py:18-22`) хранит только URL-строку, не handle окна → нечем закрыть. За десятки команд накапливается сотня вкладок → браузер перегружается → ПК зависает. Для незрячего пользователя панель вкладок невидима → копится незаметно. Реклама: `README.md:90` даёт **неверный** совет — утверждает что «Vivaldi блокирует рекламу по умолчанию, без расширений». На самом деле встроенный блокировщик Vivaldi выключен по умолчанию и неэффективен против YouTube-рекламы. Нужен **uBlock Origin** (расширение) в активном профиле. Код не указывает профиль → зависит от того, какой активен. «Иногда не блокирует» = чистый/сбросившийся профиль. ## Задача Заменить `webbrowser.open` на запуск Vivaldi в **app-режиме** (`--app=<url>`) с указанным профилем (где установлен uBlock Origin). App-режим открывает URL в отдельном окне **без панели вкладок** — вкладки физически не копятся. Каждый новый запуск = закрываем старое app-окно и открываем новое. PID не сохраняем (требование пользователя) — закрываем по маркеру профиля через `taskkill`. ### Изменения в коде **`config.py`** — новые поля: - `browser_path: str` — путь к `vivaldi.exe`, default пустой (авто-detect). Авто-detect: проверять `C:\Users\{user}\AppData\Local\Vivaldi\Application\vivaldi.exe`, `C:\Program Files\Vivaldi\Application\vivaldi.exe`, `C:\Program Files (x86)\Vivaldi\Application\vivaldi.exe`. - `browser_profile: str` — путь к профилю Vivaldi с предустановленным uBlock Origin. Default пустой. Если пустой — fallback на `--app=` без `--user-data-dir` (использует default профиль). - Env vars: `BROWSER_PATH`, `BROWSER_PROFILE`. **`services/browser.py`** — полная переработка: - Убрать `webbrowser` импорт. - `BrowserState`: хранить `_profile_marker` (строка-маркер из `browser_profile` для `taskkill` фильтра по заголовку окна), НЕ хранить PID. - `open_url(url)`: 1. Если `_profile_marker` задан — `taskkill /IM vivaldi.exe /FI "WINDOWTITLE eq *{marker}*"` (закрыть старое app-окно, без сохранения PID). 2. `subprocess.Popen([browser_path, f"--app={url}", f"--user-data-dir={browser_profile}"])` (если profile задан) или `subprocess.Popen([browser_path, f"--app={url}"])` (без profile). 3. Логировать открытие. - Авто-detect `browser_path` если не задан в env. - На не-Windows — fallback на `webbrowser.open` (Linux для dev/CI). **`README.md`**: - Убрать строку «Vivaldi блокирует рекламу по умолчанию, без расширений» (неверно). - Добавить инструкцию: установить Vivaldi, установить расширение **uBlock Origin** из Chrome Web Store, указать путь к профилю в `BROWSER_PROFILE`. **`.env.template`** — новые переменные: ``` # Путь к Vivaldi (если пусто — авто-detect на Windows) BROWSER_PATH= # Путь к профилю Vivaldi с uBlock Origin (если пусто — default профиль) BROWSER_PROFILE= ``` **Тесты `tests/test_browser.py`** — переписать: - Мок `subprocess.Popen` — проверка что вызывается с `--app=<url>` и `--user-data-dir=<profile>`. - Проверка что перед новым открытием вызывается `taskkill` (мок subprocess) с фильтром по маркеру профиля. - Проверка авто-detect путей Vivaldi на Windows. - Проверка fallback на `webbrowser.open` на не-Windows. - Убрать старые тесты `webbrowser.open`. ## Контракты - `open_browser_url(url: str) -> None` — публичная функция (`browser.py:56-62`), вызывается из `youtube_flow.py:102`. Сигнатура не меняется. - `youtube_flow._play_video` (`youtube_flow.py:99-103`) — не меняется, вызывает `open_browser_url(url)`. - ОС: Windows (taskkill). На Linux (dev/CI) — fallback на `webbrowser.open`. ## Инварианты - Один URL = одно окно (app-режим). Вкладки не копятся физически. - PID не сохраняем (требование пользователя) — закрываем по маркеру профиля через `taskkill /FI "WINDOWTITLE eq ..."`. - uBlock Origin устанавливается пользователем один раз в профиль Vivaldi. Код не устанавливает расширения. - `--app=` режим: окно без панели вкладок, без адресной строки — идеально для accessibility. - Команды «закрыть браузер» голосом — НЕ добавляем (требование пользователя: не лезть куда не просят). ## Граничные случаи - Vivaldi не установлен → авто-detect не находит → логировать error, озвучить `speak("Браузер не найден")`, не падать. - `BROWSER_PROFILE` пустой → запуск без `--user-data-dir` (default профиль Vivaldi). Реклама может не блокироваться — логировать warning. - `taskkill` не находит окно по маркеру (первый запуск, ничего не открыто) — silently ignore (exit code 128 = no match, не ошибка). - Vivaldi уже запущен в обычном режиме (не app) — `--app=` откроет новое окно в том же процессе. `taskkill` по маркеру закроет только app-окно (фильтр по заголовку), не тронет обычные окна. - Несколько быстрых запросов подряд — `taskkill` перед каждым `--app=` гарантирует одно окно. - Unicode в URL (кириллические поисковые запросы) — `subprocess.Popen` с `encoding="utf-8"` на Windows. ## Влияние на связанные компоненты - `services/youtube_flow.py:102` — вызывает `open_browser_url`, не меняется. - `services/commands.py` — не затрагивается (нет команд закрытия). - `nlu/handlers.py` — не затрагивается. - `README.md:88-90` — переписать секцию «YouTube без рекламы». - Release ZIP — добавить инструкцию по настройке Vivaldi + uBlock в `README.md`. ## Вне scope - Авто-установка Vivaldi (скачивание + тихая установка при отсутствии) — отдельный future-issue - Голосовая команда «закрыть браузер/видео» — НЕ добавляем (требование пользователя) - Лимит вкладок / throttle — не нужно (app-режим = одно окно физически) - Сохранение PID — НЕ сохраняем (требование пользователя) - Установка uBlock Origin автоматически — пользователь ставит один раз ## Критерии приемки - [ ] `webbrowser.open` заменён на `subprocess.Popen([browser_path, "--app=<url>", ...])` - [ ] Перед каждым открытием — `taskkill` по маркеру профиля (PID не сохраняется) - [ ] `BROWSER_PATH`, `BROWSER_PROFILE` в `config.py` и `.env.template` - [ ] Авто-detect пути Vivaldi на Windows (3 стандартных пути) - [ ] Fallback на `webbrowser.open` на не-Windows - [ ] `README.md` — убрано неверное про «блокирует по умолчанию», добавлена инструкция uBlock - [ ] Тесты `test_browser.py` переписаны: мок subprocess, проверка `taskkill` + `--app=` - [ ] `ruff check src/ tests/` — чисто - [ ] `mypy src/` — чисто - [ ] `pytest` — зелёный, coverage >= 75%
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
slaid098/voice_assistant#2
No description provided.