node-red-contrib-yandex-station-management 0.2.5 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,71 @@
1
+ {
2
+ "label": {
3
+ "name": "Name",
4
+ "text": "Text",
5
+ "effect": "Effect",
6
+ "login": "Login",
7
+ "station": "Station",
8
+ "voice": "Voice",
9
+ "debug": "Debug",
10
+ "command": "Command",
11
+ "volume": "Volume",
12
+ "prevent_listening": "Prevent listening",
13
+ "pause_while_tts": "Pause while TTS",
14
+ "default_command": "Default command"
15
+ },
16
+ "placeholder": {
17
+ "name": "Name",
18
+ "play_music": "Play music"
19
+ },
20
+ "command": {
21
+ "player": "Player",
22
+ "tts": "Text-to-Speech",
23
+ "voice": "Voice",
24
+ "raw": "Raw",
25
+ "stop_listening": "Stop listening",
26
+ "homekit": "Homekit"
27
+ },
28
+ "voice": {
29
+ "default": "Default",
30
+ "alena": "Alena",
31
+ "alyss": "Alyss",
32
+ "anton_samokhvalov": "Anton Samokhvalov",
33
+ "dude": "Dude",
34
+ "ermil": "Ermil",
35
+ "ermilov": "Ermilov",
36
+ "ermil_with_tuning": "Ermil (tuning)",
37
+ "erkanyavas": "Erkanyavas",
38
+ "filipp": "Filipp",
39
+ "jane": "Jane",
40
+ "kolya": "Kolya",
41
+ "kostya": "Kostya",
42
+ "levitan": "Levitan",
43
+ "nastya": "Nastya",
44
+ "nick": "Nick",
45
+ "oksana": "Oksana",
46
+ "omazh": "Omazh",
47
+ "robot": "Robot",
48
+ "sasha": "Sasha",
49
+ "silaerkan": "Silaerkan",
50
+ "smoky": "Smoky",
51
+ "tanya": "Tanya",
52
+ "tatyana_abramova": "Tatyana Abramova",
53
+ "zahar": "Zahar",
54
+ "zhenya": "Zhenya",
55
+ "zombie": "Zombie",
56
+ "voicesearch": "Voicesearch"
57
+ },
58
+ "effect": {
59
+ "none": "None",
60
+ "behind_the_wall": "behind_the_wall",
61
+ "hamster": "hamster",
62
+ "megaphone": "megaphone",
63
+ "pitch_down": "pitch_down",
64
+ "psychodelic": "psychodelic",
65
+ "pulse": "pulse",
66
+ "train_announce": "train_announce"
67
+ }
68
+ }
69
+
70
+
71
+
@@ -0,0 +1,42 @@
1
+ <script type="text/html" data-help-name="alice-local-out">
2
+ <p>Команда для яндекс станции</p>
3
+
4
+ <h3>TTS</h3>
5
+ <p>Воспросизведение голосом отправленных фраз - Text to Speech. Не имеет ограничения по символам.</p>
6
+ <dl class="message-properties">
7
+ <dt>Текст</dt><dd>Откуда брать текст: msg.payload, flow, global или Json - выбрать сообщение случайным образом из массива вида <code>["один", "два", "три"]</code>.</dd>
8
+ <dt class="optional">Голос</dt><dd>Изменить голос.</dd>
9
+ <dt class="optional">Эффект</dt><dd>Наложить эффект на голос.</dd>
10
+ <dt class="optional">Громкость</dt><dd>Позволяет произносить фразу заданной громкостью. Если не выбрано, то фраза произносится с текущим уровнем громкости. После произнесения, уровень громкости вернется в изначальный.</dd>
11
+ <dt class="optional">Не ждать ответ</dt><dd>Если выбрано, то колонка, после воспроизведения, не "слушает", что ей ответят.</dd>
12
+ <dt class="optional">Плеер на паузу</dt><dd>Ставит воспроизведение плеера на паузу на время речи. Воспроизведение будет продолжено, только если что-то играло на момент поступления команды.</dd>
13
+ </dl>
14
+
15
+ <h4>Отмечайте ударения</h4>
16
+ <p>При необходимости ударные гласные в словах следует отмечать знаком «+», например:</p>
17
+ <ul>
18
+ <li><code>остр+ота</code></li>
19
+ <li><code>м+ука</code></li>
20
+ </ul>
21
+
22
+ <h4>Разделяйте слова</h4>
23
+ <p>Длинные слова можно разбить на слова покороче и проставить ударения для каждого из этих коротких слов, например:</p>
24
+ <ul>
25
+ <li><code>мн+ого пр+офильный</code></li>
26
+ <li><code>с+еми пал+атинск</code></li>
27
+ </ul>
28
+
29
+ <h4>Меняйте написание слов</h4>
30
+ <p>Некоторые слова можно попробовать писать так, как они слышатся:</p>
31
+ <ul>
32
+ <li><code>«ненастный» — нен+асный</code></li>
33
+ <li><code>«пожалуйста» — пож+алуста</code></li>
34
+ </ul>
35
+
36
+ <h4>Добавляйте паузы</h4>
37
+ <p>Чтобы задать паузу между словами, используйте синтаксис sil <[ количество_миллисекунд ]>. Например:</p>
38
+ <ul>
39
+ <li><code>смелость sil <[500]> город+а берёт</code></li>
40
+ </ul>
41
+ <p>Каждый отделенный пробелами пунктуационный знак обозначается паузой в 50-100 мс.</p>
42
+ </script>
@@ -0,0 +1,68 @@
1
+ {
2
+ "label": {
3
+ "name": "Название",
4
+ "text": "Текст",
5
+ "effect": "Эффект",
6
+ "login": "Логин",
7
+ "station": "Станция",
8
+ "voice": "Голос",
9
+ "debug": "Дебаг",
10
+ "command": "Команда",
11
+ "volume": "Громкость",
12
+ "prevent_listening": "Не ждать ответ",
13
+ "pause_while_tts": "Плеер на паузу",
14
+ "default_command": "Команда"
15
+ },
16
+ "placeholder": {
17
+ "name": "Название",
18
+ "play_music": "Включи музыку"
19
+ },
20
+ "command": {
21
+ "player": "Плеер",
22
+ "tts": "Синтез речи из текста",
23
+ "voice": "Голосовая команда",
24
+ "stop_listening": "Перестать слушать",
25
+ "homekit": "Homekit",
26
+ "raw": "Сырая команда"
27
+ },
28
+ "voice": {
29
+ "default": "Алиса (стандартный)",
30
+ "alena": "Алёна (alena)",
31
+ "alyss": "Элис (alyss)",
32
+ "anton_samokhvalov": "Антон Самохвалов (anton_samokhvalov)",
33
+ "dude": "Чувак (dude)",
34
+ "ermil": "Ермил (ermil)",
35
+ "ermilov": "Ermilov (ermilov)",
36
+ "ermil_with_tuning": "Ермил Т (ermil_with_tuning)",
37
+ "erkanyavas": "Ерканявас (erkanyavas)",
38
+ "filipp": "Филипп (filipp)",
39
+ "jane": "Джейн (jane)",
40
+ "kolya": "Коля (kolya)",
41
+ "kostya": "Костя (kostya)",
42
+ "levitan": "Левитан (levitan)",
43
+ "nastya": "Настя (nastya)",
44
+ "nick": "Ник (nick)",
45
+ "oksana": "Оксана (oksana)",
46
+ "omazh": "Омаж (omazh)",
47
+ "robot": "Робот (robot)",
48
+ "sasha": "Саша (sasha)",
49
+ "silaerkan": "Силаеркан (silaerkan)",
50
+ "smoky": "Смоки (smoky)",
51
+ "tanya": "Таня (tanya)",
52
+ "tatyana_abramova": "Татьяна Абрамова (tatyana_abramova)",
53
+ "zahar": "Захар (zahar)",
54
+ "zhenya": "Женя (zhenya)",
55
+ "zombie": "Зомби (zombie)",
56
+ "voicesearch": "voicesearch"
57
+ },
58
+ "effect": {
59
+ "none": "Без эффекта",
60
+ "behind_the_wall": "Из-за стены (behind_the_wall)",
61
+ "hamster": "Хомяк (hamster)",
62
+ "megaphone": "Мегафон (megaphone)",
63
+ "pitch_down": "Низкий (pitch_down)",
64
+ "psychodelic": "Психоделический (psychodelic)",
65
+ "pulse": "С прерыванием (pulse)",
66
+ "train_announce": "Громкоговоритель (train_announce)"
67
+ }
68
+ }
@@ -2,24 +2,92 @@
2
2
  RED.nodes.registerType('yandex-login',{
3
3
  category: 'config',
4
4
  credentials: {
5
- token: {type:"text"}
5
+ token: { type:"text", required: true }
6
6
  },
7
7
  defaults: {
8
- name: {value: null,
9
- required: true},
10
- debugFlag: {
11
- value: false
12
- }
8
+ name: { value: null },
9
+ debugFlag: { value: false }
13
10
  },
14
- label: function(){ return this.name || "YandexLogin" }
11
+ label: function() { return this.name || "YandexLogin" },
12
+ oneditprepare: function () {
13
+ let node = this;
14
+
15
+ $('#oauth-button').on('click', function () {
16
+ let username = $('#oauth-username').val();
17
+ let password = $('#oauth-password').val();
18
+ let captcha_key = $('#oauth-captcha_key').val();
19
+ let captcha_answer = $('#oauth-captcha_answer').val();
20
+
21
+ getoAuthToken(username, password, captcha_key, captcha_answer);
22
+ });
23
+ }
15
24
  });
25
+
26
+
27
+ function getoAuthToken(username, password, captcha_key, captcha_answer) {
28
+ const url_oauth = 'https://oauth.yandex.com';
29
+ const url = `${url_oauth}/token`;
30
+ const client_id = '23cabbbdc6cd418abb4b39c32c41195d';
31
+ const client_secret = '53bc75238f0c4d08a118e51fe9203300';
32
+
33
+ $('#oauth-status').show();
34
+ $('#oauth-status').html('Waiting ...').css("color", "black");
35
+
36
+ $('#oauth-captcha').hide();
37
+ $('#oauth-captcha_key, #oauth-captcha_answer').val('');
38
+
39
+ if (!username || !password) {
40
+ $('#oauth-status').html('Empty username or password').css("color", "red");
41
+ return;
42
+ }
43
+
44
+ let data = {
45
+ grant_type: 'password',
46
+ client_id: client_id,
47
+ client_secret: client_secret,
48
+ username: username,
49
+ password: password
50
+ };
51
+
52
+ if (captcha_key && captcha_answer) {
53
+ data = {...data, captcha_key, captcha_answer}
54
+ }
55
+
56
+ $.post( url, data )
57
+ .done(function( res ) {
58
+ if (res.access_token) {
59
+ let str = `Success`;
60
+ $('#oauth-status').html(str).css("color", "black");
61
+
62
+ $('#node-config-input-token').val(res.access_token);
63
+ $('#oauth-username, #oauth-password').val('');
64
+ }
65
+ })
66
+ .fail(function( res ) {
67
+ if (res.responseJSON.error_description.match(/not valid/gi)) {
68
+ let str = `${res.responseJSON.error_description} (more <a href='https://github.com/AlexxIT/YandexStation/issues/103' target='_blank'>issues/103</a>)`;
69
+ $('#oauth-status').html(str).css("color", "red");
70
+ }
71
+ else if (res.responseJSON.error_description.match(/CAPTCHA/gi)) {
72
+ let str = `<img src='${res.responseJSON.x_captcha_url}'>`;
73
+ $('#oauth-status').html(str);
74
+
75
+ $('#oauth-captcha').show();
76
+ $('#oauth-captcha_key').val(res.responseJSON.x_captcha_key);
77
+ }
78
+ else if (res.responseJSON.error_description) {
79
+ let str = res.responseJSON.error_description;
80
+ $('#oauth-status').html(str).css("color", "red");
81
+ }
82
+ });
83
+ }
16
84
  </script>
17
85
 
18
86
  <script type="text/html" data-template-name="yandex-login">
19
87
  <style>
20
88
  .label-long {min-width: 150px;width: 20%;}
21
89
  .online {display: inline-block; width: auto; vertical-align: middle;}
22
- </style>
90
+ </style>
23
91
  <div class="form-row">
24
92
  <label for="node-config-input-name"><i class="fa fa-bookmark"></i> Name </label>
25
93
  <input type="text" id="node-config-input-name">
@@ -30,6 +98,31 @@
30
98
  </div>
31
99
  <div class="form-row">
32
100
  <label for='node-config-input-debugFlag' style="min-width: 110px; width: 20%;"><i class='fa fa-share-square'>Debug</i></label>
33
- <input type="checkbox" id="node-config-input-debugFlag" style="display: inline-block; width: auto; vertical-align: top;">
101
+ <input type="checkbox" id="node-config-input-debugFlag" style="display: inline-block; width: auto; vertical-align: top;">
102
+ </div>
103
+ <div>
104
+ <span style="font-weight: bold; margin: 4px 0;"> Get oAuthToken (Experimental)</span>
105
+ <hr style="font-weight: bold; margin: 10px 0;" \>
106
+ </div>
107
+ <div class="form-row">
108
+ <label for="oauth-username"><i class="fa fa-bookmark"></i> Username </label>
109
+ <input type="text" id="oauth-username">
110
+ </div>
111
+ <div class="form-row">
112
+ <label for="oauth-password"><i class="fa fa-bookmark"></i> Password </label>
113
+ <input type="password" id="oauth-password">
114
+ </div>
115
+ <div style="text-align-last: center; margin: 4px 0;">
116
+ <span style="display: none;" id="oauth-status"></span>
117
+ </div>
118
+ <div class="form-row" style="display: none;" id="oauth-captcha">
119
+ <label for="oauth-captcha_answer"><i class="fa fa-bookmark"></i> Captcha </label>
120
+ <input type="text" id="oauth-captcha_answer">
121
+ <input type="hidden" id="oauth-captcha_key">
122
+ </div>
123
+ <div class="form-row">
124
+ <span class="button-group" style="float: right; margin-right: 30px;">
125
+ <button type="button" id="oauth-button" class="red-ui-button toggle status-button-group">Get oAuthToken</button>
126
+ </span>
34
127
  </div>
35
128
  </script>
@@ -17,6 +17,7 @@ module.exports = function(RED) {
17
17
  node.deviceList = [];
18
18
  node.readyList = [];
19
19
  node.activeStationList = [];
20
+ //node.skipCloudDevices = false;
20
21
 
21
22
 
22
23
  node.on('stopListening', onStopListening);
@@ -44,7 +45,7 @@ module.exports = function(RED) {
44
45
  debugMessage(`Ready event fo ${device.id}`);
45
46
  node.emit(`deviceReady`, device);
46
47
  node.readyList.push({ 'name': device.name, 'id': device.id, 'platform': device.platform, 'address': device.address, 'port': device.port, 'host': device.host, 'parameters': device.parameters});
47
- node.emit('refreshHttp', node.activeStationList, node.readyList)
48
+ node.emit('refreshHttp', node.activeStationList, node.readyList);
48
49
  statusUpdate({"color": "yellow", "text": "connecting..."}, device);
49
50
  }
50
51
  }
@@ -63,8 +64,16 @@ module.exports = function(RED) {
63
64
  'Authorization': 'Oauth ' + token
64
65
  }
65
66
  };
66
-
67
-
67
+ // вариант для снижения частоты запросов на серверы ЯНдекса для обновления списка устройств. Требует тестирования.
68
+ /* if (node.skipCloudDevices) {
69
+ discoverDevices(node.deviceList)
70
+ .then(() => {
71
+ //debugMessage(`calling processing for ${node.deviceList.length} devices`);
72
+ deviceListProcessing(node.deviceList)
73
+
74
+ });
75
+ return node.deviceList;
76
+ } */
68
77
  await rp(options)
69
78
  .then(function(response)
70
79
  {
@@ -113,20 +122,28 @@ module.exports = function(RED) {
113
122
  await mDnsSd.discover({
114
123
  name: '_yandexio._tcp.local'
115
124
  }).then((result) => {
116
- //debugMessage(`Found ${result.length} devices`);
125
+ debugMessage(`MDNS. Found ${result.length} devices`);
126
+ node.emit('refreshHttpDNS', result);
117
127
  if (result.length != 0){
118
128
  for (const device of deviceList) {
119
129
  result.forEach(element => {
120
130
  let checkConnType = device.parameters.network || {}
121
131
  //если режим автоопределения адреса или набор параметров пустой, то записывать значния из результатов mdns поиска
122
132
  if (checkConnType.mode == "auto" || JSON.stringify(checkConnType) == "{}") {
123
- let srvEls = element.packet.answers.find(el => el.type == "SRV");
124
- let txtEls = element.packet.answers.find(el => el.type == "TXT");
133
+ //need remove to REGEXP!
134
+ //let mdnsId = element.fqdn.split("-")[1].split(".")[0];
135
+ let srvEls = element.packet.answers.find(el => el.type == "SRV") || element.packet.additionals.find(el => el.type == "SRV");
136
+ let txtEls = element.packet.answers.find(el => el.type == "TXT") || element.packet.additionals.find(el => el.type == "TXT");
125
137
  if (typeof(txtEls) !== 'undefined' ) {
126
138
  if (txtEls.rdata.deviceId == device.id) {
127
139
  device.address = element.address;
128
140
  device.port = element.service.port;
129
- device.host = srvEls.rdata.target;
141
+ try {
142
+ device.host = srvEls.rdata.target;
143
+ } catch(e) {
144
+ debugMessage(`Error searching hostname in mDNS answer`)
145
+ }
146
+
130
147
  }
131
148
  }
132
149
  }
@@ -191,8 +208,8 @@ module.exports = function(RED) {
191
208
  function connect(device) {
192
209
  //connect only if !device.ws
193
210
  //debugMessage(`device.ws = ${JSON.stringify(device.ws)}`);
194
- if (device.connection == true || typeof(device.connection) == "undefined") {
195
- debugMessage(`Connecting to device ${device.id}. ws is ${JSON.stringify(device.ws)}`)
211
+ if ( (device.connection == true || typeof(device.connection) == "undefined") && node.listenerCount(`statusUpdate_${device.id}`) > 0 ) {
212
+ debugMessage(`Connecting to device ${device.id}. ws is ${JSON.stringify(device.ws)}. Listeners: ` + node.listenerCount(`statusUpdate_${device.id}`));
196
213
  if (!device.ws) {
197
214
  debugMessage('recieving conversation token...');
198
215
  getLocalToken(device)
@@ -237,7 +254,7 @@ module.exports = function(RED) {
237
254
  //}
238
255
  }
239
256
  } else {
240
- debugMessage(`${device.id} connection is disabled by settings in manager node ${device.manager}`)
257
+ debugMessage(`${device.id} connection is disabled by settings in manager node ${device.manager} or you have not use any node for this station`)
241
258
  statusUpdate({"color": "red", "text": "disconnected"}, device);
242
259
  device.timer = setTimeout(connect, 60000, device);
243
260
 
@@ -266,11 +283,13 @@ module.exports = function(RED) {
266
283
  device.watchDog = setTimeout(() => device.ws.close(), 10000);
267
284
  device.pingInterval = setInterval(onPing,300,device);
268
285
  clearTimeout(device.timer);
286
+ debugMessage(`readyState: ${device.ws.readyState}`)
269
287
  });
270
288
  device.ws.on('message', function incoming(data) {
271
289
  //debugMessage(`${device.id}: ${JSON.stringify(data)}`);
272
290
  let dataRecieved = JSON.parse(data);
273
291
  device.lastState = dataRecieved.state;
292
+ device.fullMessage = JSON.stringify(dataRecieved);
274
293
  //debugMessage(checkSheduler(device, JSON.parse(data).sentTime));
275
294
  node.emit(`message_${device.id}`, device.lastState);
276
295
  if (device.lastState.aliceState == 'LISTENING' && device.waitForListening) {node.emit(`stopListening`, device)}
@@ -304,6 +323,7 @@ module.exports = function(RED) {
304
323
  //device.ws.on('ping', function);
305
324
  device.ws.on('close', function close(code, reason){
306
325
  statusUpdate({"color": "red", "text": "disconnected"}, device);
326
+ debugMessage(`readyState: ${device.ws.readyState}`)
307
327
  device.lastState = {};
308
328
  clearTimeout(device.watchDog);
309
329
  switch(code) {
@@ -490,8 +510,8 @@ module.exports = function(RED) {
490
510
  return result;
491
511
  break;
492
512
  case 'homekit':
493
- debugMessage('HAP: ' + JSON.stringify(message.hap.context) + ' PL: ' + JSON.stringify(message.payload) );
494
- if (message.hap.context != undefined) {
513
+ debugMessage('HAP: ' + JSON.stringify(message) + ' PL: ' + JSON.stringify(message.payload) );
514
+ if ("session" in message.hap) {
495
515
  switch(JSON.stringify(message.payload)){
496
516
  case '{"TargetMediaState":1}':
497
517
  case '{"Active":0}':
@@ -749,6 +769,12 @@ module.exports = function(RED) {
749
769
  res.json({"devices": activeList});
750
770
  });
751
771
  });
772
+
773
+ node.on('refreshHttpDNS', function(dnsList) {
774
+ RED.httpAdmin.get("/mdns/"+node.id, RED.auth.needsPermission('yandex-login.read'), function(req,res) {
775
+ res.json({"SearchResult": dnsList});
776
+ });
777
+ });
752
778
  node.on('close', onClose)
753
779
 
754
780
 
@@ -763,7 +789,7 @@ module.exports = function(RED) {
763
789
  let device = searchDeviceByID(id);
764
790
  if (device) {
765
791
 
766
- res.json({"id": device.id,"name": device.name, "platform": device.platform, "address": device.address, "port": device.port, "manager": device.manager, "ws": device.ws, "parameters": device.parameters});
792
+ res.json({"id": device.id,"name": device.name, "platform": device.platform, "address": device.address, "port": device.port, "manager": device.manager, "ws": device.ws, "parameters": device.parameters, "fullMessage": device.fullMessage });
767
793
  } else {
768
794
  res.json({"error": 'no device found'});
769
795
  }
@@ -771,8 +797,9 @@ module.exports = function(RED) {
771
797
 
772
798
  // main init
773
799
  if (typeof(node.token) !== 'undefined') {
800
+ debugMessage(`Starting server with id ${node.id}`)
774
801
  getDevices(node.token);
775
- node.interval = setInterval(getDevices, 300000, node.token);
802
+ node.interval = setInterval(getDevices, 60000, node.token);
776
803
  }
777
804
 
778
805
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-red-contrib-yandex-station-management",
3
- "version": "0.2.5",
3
+ "version": "0.3.1",
4
4
  "description": "Local management of YandexStation using API on websockets",
5
5
  "main": "index.js",
6
6
  "scripts": {