Представляю вашему внимаю свой велосипед, библиотеку yandex.station.php. Пока умеет отправлять сообщения на Яндекс.Станцию и команды. Пришлось написать, потому как модуль YaDevices (by SergeJey) на данный момент отказывается работать на Windows. Проблема с авторизацией.
(Позже я починил модуль YaDevices (by SergeJey), работает под Windows. Мое решение - viewtopic.php?f=5&t=6922&p=118198#p118198)
Установка:
1. Скопировать файл yandex.station.php в ./lib/yandex.station.php
2. Установить библиотеку WSSC https://github.com/arthurkushman/php-wss.
Для корректной работы со Станцией, инструкция ниже:
1. Яндекс Станция кооммуницирует с миром по WSS протолоку, поэтому MJDM нужно обеспечить поддержку WSS.
Вариант 1. Копируем каталог WSSC из архива yandex.station.zip в каталог ./lib/WSSC/.
Вариант 2. Ставим модуль YaDevices (by SergeJey) из Маркета дополнений. Этот модуль содержит библиотеку php-wss.
2. Прописываем:
$ya_login = "***********"; //логин Яндекс аккаунта
$ya_password = "**********"; //пароль Яндекс аккаунта
$ya_ip = "192.168.0.38"; //IP Яндекс Станции (Должен быть статическим выданным DHCP)
3. Переходим сюда:
https://passport.yandex.ru/auth?retpath ... igin=oauth
и даем приложению доступ. В ответ получаем ошибку, что приложение не установлено, это ок.
Это нужно для получения oauth_token через лазейку Яндекс Музыки.
Команды:
sayToYaStation("Привет!"); //произнести на YandexSatation
say2ya("Привет!"); //произнести на YandexSatation (сокращенный вариант функции)
cmdToYaStation("время"); //выполнить команду на YandexSatation
cmd2ya("время"); //выполнить команду на YandexSatation (сокращенный вариант функции)
cmd2ya("громкость 8"); //можно установить уровень громкости от 1 до 10.
cmd2ya("Погода"); //Алиса расскажет погоду.
cmd2ya("Последние новости"); //Алиса расскажет новости.
cmd2ya("Пробки в Минске"); //Алиса расскажет дорожную ситуацию в городе.
Внутренние команды библиотеки:
cmd2ya("stop"); //немедленно прерывает речь Алисы
cmd2ya("volume 0.5"); //задает уровень громкости
cmd2ya("csrf_token"); //вернет csrf_token
cmd2ya("devices"); //вернет массив устройств вашего Умного Дома привязанного к Яндекс Станции
cmd2ya("devices_online_stats"); //вернет массив ваших устройств экосистемы Яндекса
Другие команды можно посмотреть на https://voiceapp.ru/articles/yandex-alice-commands
еще больше команд описано в мобильном приложении https://play.google.com/store/apps/deta ... ands.alice
Известные проблемы:
1. Работает только с Яндекс.Станцией. Младшие колонки Irbis и другие не поддерживаются.
Для их поддержки голосуйте на https://yaboard.com/task/5e1d3c964930d2408498e979
2. Яндекс.Станция и сервер Majordomo должен находится в одной локальной сети.
3. Может работать только с одной Станцией в экосистеме.
Для поддержки нескольких Яндекс.Станций нужно доработать код, в моем случае это пока не требуется, Станция у меня одна.
Но если у вас есть такая необходимость и знание PHP сделать это не сложно.
4. Тестировалось только на Windows, но предполагаю, что будет работать и на Linux.
5. После последнего обновления станция начала ждать что скажет пользователь.
Приходится расчитывать примерное время произношения текста и "насильно затыкать Алису".
Оформление в виде модуля:
В теории можно оформить в виде модуля, но имеет ли смысл? Аналогичный функционал уже имеется в YaDevices (by SergeJey).
Код файла: ./lib/yandex.station.php
Код: Выделить всё
<?php
/*
Библиотека использования Яндекс.Станции в качестве терминала (синтез речи и управление).
@ author <s_nick@tut.by>.
@ Версия v.0.4 (на 15.04.2020 последняя рабочая версия).
Обновления публикую здесь: https://mjdm.ru/forum/viewtopic.php?f=5&t=6989
Установка:
1. Скопировать файл yandex.station.php в ./lib/yandex.station.php
2. Установить библиотеку WSSC https://github.com/arthurkushman/php-wss.
Для корректной работы со Станцией, инструкция ниже:
1. Яндекс Станция кооммуницирует с миром по WSS протолоку, поэтому MJDM нужно обеспечить поддержку WSS.
Вариант 1. Копируем каталог WSSC из архива yandex.station.zip в каталог ./lib/WSSC/.
Вариант 2. Ставим модуль YaDevices (by SergeJey) из Маркета дополнений. Этот модуль содержит библиотеку php-wss.
2. Прописываем:
*/
$ya_login = "***********"; //логин Яндекс аккаунта
$ya_password = "**********"; //пароль Яндекс аккаунта
$ya_ip = "192.168.0.38"; //IP Яндекс Станции (Должен быть статическим выданным DHCP)
/*
3. Переходим сюда:
https://passport.yandex.ru/auth?retpath=https%3A%2F%2Foauth.yandex.ru%2Fauthorize%3Fresponse_type%3Dtoken%26client_id%3D23cabbbdc6cd418abb4b39c32c41195d&origin=oauth
и даем приложению доступ. В ответ получаем ошибку, что приложение не установлено, это ок.
Это нужно для получения oauth_token через лазейку Яндекс Музыки.
Команды:
sayToYaStation("Привет!"); //произнести на YandexSatation
say2ya("Привет!"); //произнести на YandexSatation
cmdToYaStation("время"); //выполнить команду на YandexSatation
cmd2ya("время"); //выполнить команду на YandexSatation
cmd2ya("громкость 8"); //можно установить уровень громкости от 1 до 10.
cmd2ya("Погода"); //Алиса расскажет погоду.
cmd2ya("Последние новости"); //Алиса расскажет новости.
cmd2ya("Пробки в Минске"); //Алиса расскажет дорожную ситуацию в городе.
Внутренние команды библиотеки:
cmd2ya("stop"); //немедленно прерывает речь Алисы
cmd2ya("volume 0.5"); //задает уровень громкости
cmd2ya("csrf_token"); //вернет csrf_token
cmd2ya("devices"); //вернет массив устройств вашего Умного Дома привязанного к Яндекс Станции
cmd2ya("devices_online_stats"); //вернет массив ваших устройств экосистемы Яндекса
Другие команды можно посмотреть на https://voiceapp.ru/articles/yandex-alice-commands
еще больше команд описано в мобильном приложении https://play.google.com/store/apps/details?id=voiceapp.commands.alice
Известные проблемы:
1. Работает только с Яндекс.Станцией. Младшие колонки Irbis и другие не поддерживаются.
Для их поддержки голосуйте на https://yaboard.com/task/5e1d3c964930d2408498e979
2. Яндекс.Станция и сервер Majordomo должен находится в одной локальной сети.
3. Может работать только с одной Станцией в экосистеме.
Для поддержки нескольких Яндекс.Станций нужно доработать код, в моем случае это пока не требуется, Станция у меня одна.
Но если у вас есть такая необходимость и знание PHP сделать это не сложно.
4. Тестировалось только на Windows, но предполагаю, что будет работать и на Linux.
5. После последнего обновления станция начала ждать что скажет пользователь.
Приходится расчитывать примерное время произношения текста и "насильно затыкать Алису".
*/
//функция вывода для дебага
function print_arr($arr) {
echo "<pre>";
print_r($arr);
echo "</pre>";
}
//эмуляция запросов браузера GET и POST через CURL
function browser_get_contents($url, $cookie = "cookie_file", $referer = "https://google.by", $header = "") {
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_COOKIEFILE, $cookie . '.txt');
curl_setopt($ch, CURLOPT_COOKIEJAR, $cookie . '.txt');
curl_setopt($ch, CURLOPT_REFERER, $referer);
curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_HEADER, false);
if ($header) curl_setopt($ch, CURLOPT_HTTPHEADER, $header);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 30);
curl_setopt($ch, CURLOPT_TIMEOUT, 30);
//print_arr(curl_getinfo($ch))
$html = curl_exec($ch);
$info_arr = curl_getinfo($ch);
//print_arr($info_arr);
if ($info_arr['redirect_url']) {
$html = $info_arr['redirect_url'];
}
if (curl_errno($ch)) {
print_arr("Error Curl: " . curl_error($ch));
}
curl_close($ch);
return $html;
}
function browser_post_contents($url, $param, $cookie = "cookie_file", $referer = "https://google.by", $header = "") {
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $param);
curl_setopt($ch, CURLOPT_COOKIESESSION, true);
curl_setopt($ch, CURLOPT_COOKIEFILE, $cookie . ".txt");
curl_setopt($ch, CURLOPT_COOKIEJAR, $cookie . ".txt");
curl_setopt($ch, CURLOPT_REFERER, $referer);
curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.2 (KHTML, like Gecko) Chrome/22.0.1216.0 Safari/537.2');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_HEADER, true);
if ($header) curl_setopt($ch, CURLOPT_HTTPHEADER, $header);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 30);
curl_setopt($ch, CURLOPT_TIMEOUT, 30);
//print_arr(curl_getinfo($ch));
$html = curl_exec($ch);
$info_arr = curl_getinfo($ch);
//print_arr($info_arr);
if ($info_arr['redirect_url']) {
$html = $info_arr['redirect_url'];
}
if (curl_errno($ch)) {
print_arr("Error Curl: " . curl_error($ch));
}
curl_close($ch);
return $html;
}
//Парсер форм
function parse_form($page_cont) {
if ($page_cont) {
$page_cont = str_replace("\r" , "", $page_cont);
$page_cont = str_replace("\n" , "", $page_cont);
//print_arr($page_cont);
preg_match_all("/<FORM(.*?)<\/FORM>/i", $page_cont, $matchForm);
//print_arr($matchForm);
//$page_cont = $matchForm[0][0];
preg_match_all("/<INPUT(.*?)>/i", $page_cont, $matchInput);
//print_arr($matchInput[1]);
foreach($matchInput[1] as $key => $value) {
preg_match_all("/NAME=\"(.*?)\"/i", $value, $matchName);
//print_arr($matchName[1][0]);
preg_match_all("/VALUE=\"(.*?)\"/i", $value, $matchValue);
//print_arr($matchValue[1][0]);
$param_arr[$matchName[1][0]] = $matchValue[1][0];
}
//$param_arr = array_filter($param_arr);
unset($param_arr['']);
//print_arr($param_arr);
}
return $param_arr;
}
//---------------- Начало: WSSC библиотека ------------------------
//подключаем библиотеку WSSC
//https://github.com/arthurkushman/php-wss
function inc_WSSC() {
if (is_dir(ROOT . "lib/WSSC")) {
@include_once(ROOT . "lib/WSSC/Components/ClientConfig.php");
@include_once(ROOT . "lib/WSSC/Contracts/WscCommonsContract.php");
@include_once(ROOT . "lib/WSSC/Components/WSClientTrait.php");
@include_once(ROOT . "lib/WSSC/Components/WscMain.php");
@include_once(ROOT . "lib/WSSC/WebSocketClient.php");
@include_once(ROOT . "lib/WSSC/Contracts/CommonsContract.php");
} else {
if (is_dir(ROOT . "modules/yadevices/WSSC")) {
@include_once(ROOT . "modules/yadevices/WSSC/Components/ClientConfig.php");
@include_once(ROOT . "modules/yadevices/WSSC/Contracts/WscCommonsContract.php");
@include_once(ROOT . "modules/yadevices/WSSC/Components/WSClientTrait.php");
@include_once(ROOT . "modules/yadevices/WSSC/Components/WscMain.php");
@include_once(ROOT . "modules/yadevices/WSSC/WebSocketClient.php");
@include_once(ROOT . "modules/yadevices/WSSC/Contracts/CommonsContract.php");
} else {
$ret = "Ошибка! Не установлена бибилиотека WSSC https://github.com/arthurkushman/php-wss";
return $ret;
}
}
}
function sendDataToStation($command, $token, $ya_ip, $port = 1961) {
inc_WSSC();
$clientConfig = new WSSC\Components\ClientConfig();
$clientConfig->setHeaders([
'X-Origin' => 'http://yandex.ru/',
]);
$clientConfig->setContextOptions(['ssl' => ['verify_peer' => false, 'verify_peer_name' => false]]);
$msg = array(
'conversationToken' => $token,
//'id' => uniqid(''),
//'sentTime' => time() * 1000000000,
'payload' => array(
'command' => 'sendText',
'text' => $command
)
);
//print_arr($msg);
$client = new WSSC\WebSocketClient('wss://'.$ya_ip.':'.$port.'/', $clientConfig);
$client->send(json_encode($msg));
$client->close();
$result = $client->receive();
$result_data = json_decode($result,true);
if (is_array($result_data)) {
$result_data = array_merge($msg, $result_data);
return $result_data;
}
return false;
}
function stopListening($token, $ya_ip, $port = 1961) {
inc_WSSC();
$clientConfig = new WSSC\Components\ClientConfig();
$clientConfig->setHeaders([
'X-Origin' => 'http://yandex.ru/',
]);
$clientConfig->setContextOptions(['ssl' => ['verify_peer' => false, 'verify_peer_name' => false]]);
$msg = array(
'conversationToken' => $token,
'payload' => array(
'command' => 'serverAction',
'serverActionEventPayload' => array(
'type' => 'server_action',
'name' => 'on_suggest'
)
)
);
//print_arr($msg);
$client = new WSSC\WebSocketClient('wss://'.$ya_ip.':'.$port.'/', $clientConfig);
$client->send(json_encode($msg));
$client->close();
$result = $client->receive();
$result_data = json_decode($result,true);
if (is_array($result_data)) {
$result_data = array_merge($msg, $result_data);
return $result_data;
}
return false;
}
function setVolume($volume, $token, $ya_ip, $port = 1961) {
inc_WSSC();
$clientConfig = new WSSC\Components\ClientConfig();
$clientConfig->setHeaders([
'X-Origin' => 'http://yandex.ru/',
]);
$clientConfig->setContextOptions(['ssl' => ['verify_peer' => false, 'verify_peer_name' => false]]);
$msg = array(
'conversationToken' => $token,
'payload' => array(
'command' => 'setVolume',
'volume' => $volume
)
);
//print_arr($msg);
$client = new WSSC\WebSocketClient('wss://'.$ya_ip.':'.$port.'/', $clientConfig);
$client->send(json_encode($msg));
$client->close();
$result = $client->receive();
$result_data = json_decode($result,true);
if (is_array($result_data)) {
$result_data = array_merge($msg, $result_data);
return $result_data;
}
return false;
}
//---------------- Конец: WSSC библиотека ------------------------
//---------------- Начало: Яндекс авторизация ------------------------
function ya_auth($login, $password, $cookie, $referer) {
//проверяем старую авторизацию
$url = "https://frontend.vh.yandex.ru/csrf_token";
$page_cont = browser_get_contents($url, $cookie, $referer);
//print_arr("csrf_token: " . $page_cont);
if (strlen($page_cont) == 33) { //уже есть авторизация
//print_arr("csrf_token: " . $page_cont);
return 1;
}
@unlink('../' . $cookie . '.txt'); //удаляем куки
$url = "https://passport.yandex.ru/auth?";
$page_cont = browser_get_contents($url, $cookie, $referer);
//print_arr($page_cont);
$param_arr = parse_form($page_cont);
//print_arr($param_arr);
$param_arr['login'] = $login;
$param_arr['hidden-password'] = $password;
//print_arr($param_arr);
$url = "https://passport.yandex.ru/auth?retpath=https%3A%2F%2Fyandex.ru%2F?";
foreach($param_arr as $key => $value) {
$param .= "&" . $key . "=" . $value;
}
//print_arr($param);
$page_cont = browser_post_contents($url, $param, $cookie, $referer);
//print_arr($page_cont);
$param_arr = parse_form($page_cont);
//print_arr($param_arr);
$param_arr['login'] = $login;
$param_arr['passwd'] = $password;
//print_arr($param_arr);
$url = "https://passport.yandex.ru/auth?retpath=https%3A%2F%2Fyandex.ru%2F?";
foreach($param_arr as $key => $value) {
$param .= "&" . $key . "=" . $value;
}
$page_cont = browser_post_contents($url, $param, $cookie, $referer);
//print_arr($page_cont);
if (strstr($page_cont, "https://passport.yandex.ru/auth/finish")) {
return 1;
} else {
return 0;
}
}
//---------------- Конец: Яндекс авторизация ------------------------
function get_ya_glagol_token($cookie, $referer) {
//получаем список онлайн устройств Яндекса
$url = 'https://quasar.yandex.ru/devices_online_stats';
$devices_online_stats = browser_get_contents($url, $cookie, $referer);
$devices_online_stats = json_decode($devices_online_stats, true);
//print_arr($devices_online_stats);
if ($devices_online_stats['status'] == "ok") {
//ищем Яндекс Станцию
foreach ($devices_online_stats['items'] as $key => $value) {
if ($value['platform'] == 'yandexstation') {
if ($value['online'] == 1) {
$id = $value['id'];
//print_arr("id = " . $id);
} else {
$ret = "Что-то пошло не так! Яндекс Станция офлайн.";
print_arr($ret);
return 0;
}
}
}
if (!$id) {
$ret = "Что-то пошло не так! Не удалось найти Яндекс Станцию.";
print_arr($ret);
return 0;
}
} else {
$ret = "Что-то пошло не так! Не удалось получить список онлайн устройств экосистемы Яндекса.";
print_arr($ret);
return 0;
}
//получаем oauth_token
$ya_music_client_id = '23cabbbdc6cd418abb4b39c32c41195d'; //через лазейку Яндекс Музыки
$url = "https://oauth.yandex.ru/authorize?response_type=token&client_id=" . $ya_music_client_id;
$authorize = browser_get_contents($url, $cookie, $referer);
//print_arr($authorize);
preg_match_all("/access_token=(.*?)&/ui", $authorize, $match);
$oauth_token = $match[1][0];
//print_arr("oauth_token = " . $oauth_token);
if (!$oauth_token) {
$ret = "Что-то пошло не так! Не удалось получить oauth_token.";
print_arr($ret);
return 0;
}
//получаем glagol_token
$url = "https://quasar.yandex.net/glagol/token?device_id=" . $id . "&platform=yandexstation";
$header = array();
$header[] = 'Content-type: application/json';
$header[] = 'Authorization: Oauth ' . $oauth_token;
//print_arr($header);
$data = browser_get_contents($url, $cookie, $referer, $header);
$data = json_decode($data, true);
//print_arr($data);
$glagol_token = $data['token'];
//print_arr("glagol_token = " . $glagol_token);
if ($data['status'] == "ok") {
return $glagol_token;
} else {
return 0;
}
}
function cmdToYaStation($command = "") {
global $ya_login, $ya_password, $ya_ip;
$cookie = "cookie_yandex_ru";
$referer = "https://passport.yandex.ru/auth/";
//предпринимаем до 5 попыток авторизации, т.к. иногда серверы Яндеска тормозят или глючат
for ($i = 0; $i < 5; $i++) {
if (!$ya_auth) {
$ya_auth = ya_auth($ya_login, $ya_password, $cookie, $referer);
}
}
if (!$ya_auth) {
@unlink('../' . $cookie . '.txt'); //удаляем куки
$ret = "Яндекс Станция: Что-то пошло не так! Не удалось авторизироваться в Яндексе.";
print_arr($ret);
say($ret, 1);
return $ret;
}
//предпринимаем до 5 попыток получить glagol_token, т.к. иногда серверы Яндеска тормозят или глючат
for ($i = 0; $i < 5; $i++) {
if (!$glagol_token) {
$glagol_token = get_ya_glagol_token($cookie, $referer);
}
}
if (!$glagol_token) {
@unlink('../' . $cookie . '.txt'); //удаляем куки
$ret = "Яндекс Станция: Что-то пошло не так! Возможно устройство не поддерживается.";
print_arr($ret);
say($ret, 1);
return $ret;
}
$command_arr = explode(" ", $command);
if ($command == "stop") {
$ret = stopListening($glagol_token, $ya_ip); //глушим станцию
print_arr($ret);
return $ret;
}
if ($command == "csrf_token") {
$url = "https://frontend.vh.yandex.ru/csrf_token";
$ret = browser_get_contents($url, $cookie, $referer);
print_arr($ret);
return $ret;
}
if ($command == "devices") {
$url = "https://iot.quasar.yandex.ru/m/user/devices";
$data = browser_get_contents($url, $cookie, $referer);
$ret = json_decode($data, true);
print_arr($ret);
return $ret;
}
if ($command == "devices_online_stats") {
$url = "https://quasar.yandex.ru/devices_online_stats";
$data = browser_get_contents($url, $cookie, $referer);
$ret = json_decode($data, true);
print_arr($ret);
return $ret;
}
if ($command_arr[0] == "volume") {
if (is_numeric($command_arr[1])) {
//print_arr("Громкость: " . $command_arr[1]);
$ret = setVolume($command_arr[1], $glagol_token, $ya_ip); //устанавливаем громкость станции
print_arr($ret);
return $ret;
}
}
//шлем команду Яндекс Станции
//print_arr($command);
$ret = sendDataToStation($command, $glagol_token, $ya_ip);
//print_arr($ret);
//После последнего обновления станция начала ждать что скажет пользователь
if (strstr($command, "Повтори за мной")) {
$pause = ceil(mb_strlen(str_replace(array('Повтори за мной:', ' '), '', $command)) * 5 / 48);
//print_arr("Пауза: " . $pause);
sleep($pause);
stopListening($glagol_token, $ya_ip); //глушим станцию
}
return $ret;
}
function cmd2ya($command = "") {
$ret = cmdToYaStation($command);
return $ret;
}
function sayToYaStation($say = "") {
$command = "Повтори за мной: " . $say;
$ret = cmdToYaStation($command);
return $ret;
}
function say2ya($say = "") {
$ret = sayToYaStation($say);
return $ret;
}
?>
Версия v.0.2 (устарела, в связи с изменениями в алгоритме на стороне Яндекса, сломалась авторизация): (Если вы ранее использовали эту версию, обновитесь минимум до v.0.4)
Версия v.0.4 (на 15.04.2020 последняя рабочая версия):