diff --git a/README.md b/README.md index e0b27e9..a1cfa49 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,7 @@ - [Быстрый старт](#быстрый-старт) - [Полная установка](#полная-установка) - [Токен](#токен) +- [Как получить токен](#как-получить-токен) - [CLI](#cli) - [Примеры wb-rules](#примеры-wb-rules) - [Прямые Python-команды](#прямые-python-команды) @@ -89,9 +90,15 @@ alice/ ## Быстрый старт ```bash -cd /путь/к/распакованному/репозиторию +apt update +apt install -y git python3 python3-venv python3-pip + +mkdir -p /opt/shd/plugins +cd /opt/shd/plugins +git clone https://git.xyz.su/shd/local_yandex_station.git alice +cd alice chmod +x *.sh -./install.sh +./install.sh --in-place printf %s 'YANDEX_OAUTH_TOKEN' > /opt/shd/plugins/alice/token.txt chmod 600 /opt/shd/plugins/alice/token.txt @@ -100,7 +107,10 @@ cd /opt/shd/plugins/alice ./cli.sh columns ./cli.sh loxone ./cli.sh wb-rules -./cli.sh web +systemctl enable --now shd-alice.service shd-alice-plugin.service + +curl -sS http://127.0.0.1:9124/health +curl -sS http://127.0.0.1:9140/api/status ``` После этого web UI обычно доступен по адресу: @@ -120,17 +130,20 @@ sudo apt install -y python3 python3-venv python3-pip Для более удобной синхронизации в `/opt` желателен `rsync`, но без него установка тоже работает. -### 2. Распаковать проект и запустить установку +### 2. Клонировать проект в рабочий путь и запустить установку ```bash -cd /путь/к/распакованному/репозиторию +mkdir -p /opt/shd/plugins +cd /opt/shd/plugins +git clone https://git.xyz.su/shd/local_yandex_station.git alice +cd alice chmod +x *.sh -./install.sh +./install.sh --in-place ``` Что делает `install.sh`: -- копирует проект в `/opt/shd/plugins/alice`; +- при запуске с `--in-place` работает прямо из `/opt/shd/plugins/alice`; - создаёт `.venv`, если его нет; - ставит зависимости из `requirements.txt`; - создаёт каталоги `data/`, `configs/loxone/`, `configs/wb-rules/`; @@ -146,11 +159,12 @@ printf %s 'YANDEX_OAUTH_TOKEN' > /opt/shd/plugins/alice/token.txt chmod 600 /opt/shd/plugins/alice/token.txt ``` -Или через CLI/web-команду: +Разовый запуск через переменную окружения (не для постоянного хранения): ```bash -cd /opt/shd/plugins/alice -./cli.sh web --token 'YANDEX_OAUTH_TOKEN' +export YANDEX_TOKEN='YANDEX_OAUTH_TOKEN' +./cli.sh columns +unset YANDEX_TOKEN ``` ### 4. Обновить список станций @@ -188,12 +202,32 @@ sudo systemctl enable --now shd-alice.service shd-alice-plugin.service /opt/shd/plugins/alice/token.txt ``` +Рекомендация по безопасности: + +- для постоянной работы храни токен только в `token.txt` с правами `600`; +- `YANDEX_TOKEN` используй только временно для разовых команд, затем делай `unset YANDEX_TOKEN`. + Если токен неверный, при обновлении списка станций будет ошибка: ```text YANDEX_OAUTH_TOKEN invalid: update token and retry ``` +## Как получить токен + +Создать собственное OAuth-приложение для этого сценария нельзя. Обычно используют токен, +полученный через официальные клиенты Яндекс.Музыки. + +Рабочие варианты: + +- Веб-сервис (может работать не для всех аккаунтов): [music-yandex-bot.ru](https://music-yandex-bot.ru/) +- Android-приложение (APK): [MarshalX/yandex-music-token releases](https://github.com/MarshalX/yandex-music-token/releases) +- Расширение для Google Chrome: [Yandex Music Token (Chrome Web Store)](https://chrome.google.com/webstore/detail/yandex-music-token/lcbjeookjibfhjjopieifgjnhlegmkib) +- Расширение для Mozilla Firefox: [Yandex Music Token (Firefox Add-ons)](https://addons.mozilla.org/en-US/firefox/addon/yandex-music-token/) + +Во всех вариантах выше итог один: получить и скопировать OAuth-токен. +Исходный код инструментов открыт: [github.com/MarshalX/yandex-music-token](https://github.com/MarshalX/yandex-music-token). + ## CLI Основной интерфейс из терминала — `cli.sh`. @@ -322,11 +356,11 @@ configs/wb-rules/wb-rules-examples.js Как привязать к конкретной колонке: -1. возьми `id` нужной станции из `data/stations.json`; +1. возьми IP нужной станции (или `id` из `data/stations.json`); 2. подставь его в строку: ```js -var STATION = "PUT_EXACT_STATION_ID_HERE"; +var STATION = "PUT_STATION_IP_HERE"; ``` 3. скопируй файл в `/etc/wb-rules/` и перезапусти `wb-rules`. diff --git a/alice_plugin.py b/alice_plugin.py index 7cec926..db07d20 100644 --- a/alice_plugin.py +++ b/alice_plugin.py @@ -479,6 +479,25 @@ function _enc(v) {{ return encodeURIComponent(String(v === undefined || v === null ? "" : v)); }} +function _clampVolume(v, defVal) {{ + var n = parseInt(v, 10); + if (isNaN(n)) n = defVal; + if (isNaN(n)) n = 35; + if (n < 0) n = 0; + if (n > 100) n = 100; + return n; +}} + +function _ttsRestoreDelayMs(text) {{ + var clean = String(text || "").trim(); + if (!clean) return 3500; + var chars = clean.length; + var words = clean.split(/\\s+/).filter(Boolean).length; + var speechSec = Math.max(words / 2.4, chars / 14.0); + var delaySec = Math.max(3.5, Math.min(20.0, speechSec + 1.5)); + return Math.round(delaySec * 1000); +}} + defineVirtualDevice(DEVICE_ID, {{ title: DEVICE_TITLE, cells: {{ @@ -507,7 +526,18 @@ defineRule(DEVICE_ID + "_tts_send", {{ if (!newValue) return; var txt = String(dev[DEVICE_ID + "/tts_text"] || "").trim(); if (!txt) return; - _apiGet("/api/exec?action=tts&station=" + _enc(STATION_SELECTOR) + "&text=" + _enc(txt) + "&volume=35", function(){{}}); + var targetVolume = _clampVolume(dev[DEVICE_ID + "/volume"], 35); + var prevVolumeRaw = dev[DEVICE_ID + "/current_volume"]; + var prevVolume = _clampVolume(prevVolumeRaw, targetVolume); + _apiGet( + "/api/exec?action=tts&station=" + _enc(STATION_SELECTOR) + "&text=" + _enc(txt) + "&volume=" + _enc(targetVolume), + function(){{}} + ); + if (prevVolume !== targetVolume) {{ + setTimeout(function() {{ + _apiGet("/api/exec?action=volume&station=" + _enc(STATION_SELECTOR) + "&level=" + _enc(prevVolume), function(){{}}); + }}, _ttsRestoreDelayMs(txt)); + }} }} }}); diff --git a/configs/wb-rules/wb-rules-examples.js b/configs/wb-rules/wb-rules-examples.js index 8968645..0126297 100644 --- a/configs/wb-rules/wb-rules-examples.js +++ b/configs/wb-rules/wb-rules-examples.js @@ -1,12 +1,12 @@ // Примеры wb-rules для ОДНОЙ выбранной станции. // // Как использовать: -// 1) подставь id станции из data/stations.json в переменную STATION; +// 1) подставь IP станции (или station_id) в переменную STATION; // 2) скопируй файл в /etc/wb-rules/; // 3) перезапусти сервис wb-rules. var API_BASE = "http://127.0.0.1:9124"; -var STATION = "PUT_EXACT_STATION_ID_HERE"; +var STATION = "PUT_STATION_IP_HERE"; function enc(v) { return encodeURIComponent(String(v === undefined || v === null ? "" : v));