node-red-contrib-alice 2.2.4 → 2.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.

Potentially problematic release.


This version of node-red-contrib-alice might be problematic. Click here for more details.

Files changed (43) hide show
  1. package/.claude/settings.local.json +11 -0
  2. package/CLAUDE.md +54 -0
  3. package/nodes/alice-color.js +208 -231
  4. package/nodes/alice-device.html +6 -1
  5. package/nodes/alice-device.js +252 -286
  6. package/nodes/alice-event.js +110 -114
  7. package/nodes/alice-get.html +91 -0
  8. package/nodes/alice-get.js +9 -0
  9. package/nodes/alice-mode.js +136 -145
  10. package/nodes/alice-onoff.js +126 -130
  11. package/nodes/alice-range.js +144 -150
  12. package/nodes/alice-sensor.html +0 -2
  13. package/nodes/alice-sensor.js +101 -106
  14. package/nodes/alice-togle.js +118 -125
  15. package/nodes/alice-video.js +88 -132
  16. package/nodes/alice.js +127 -122
  17. package/nodes/types.js +3 -0
  18. package/package.json +22 -8
  19. package/src/alice-color.html +255 -0
  20. package/src/alice-color.ts +227 -0
  21. package/src/alice-device.html +94 -0
  22. package/src/alice-device.ts +301 -0
  23. package/src/alice-event.html +148 -0
  24. package/src/alice-event.ts +112 -0
  25. package/src/alice-get.html +67 -6
  26. package/src/alice-get.ts +12 -15
  27. package/src/alice-mode.html +296 -0
  28. package/src/alice-mode.ts +139 -0
  29. package/src/alice-onoff.html +93 -0
  30. package/src/alice-onoff.ts +132 -0
  31. package/src/alice-range.html +293 -0
  32. package/src/alice-range.ts +144 -0
  33. package/src/alice-sensor.html +307 -0
  34. package/src/alice-sensor.ts +103 -0
  35. package/src/alice-togle.html +96 -0
  36. package/src/alice-togle.ts +122 -0
  37. package/src/alice-video.html +90 -0
  38. package/src/alice-video.ts +99 -0
  39. package/src/alice.html +242 -0
  40. package/src/alice.ts +146 -0
  41. package/src/types.ts +157 -0
  42. package/tsconfig.json +13 -106
  43. package/.eslintrc.json +0 -20
@@ -0,0 +1,90 @@
1
+ <script type="text/javascript">
2
+ RED.nodes.registerType('Video',{
3
+ category: 'alice',
4
+ defaults:{
5
+ device: {value:"", type:"alice-device"},
6
+ name: {value:""},
7
+ protocol: { value:"hls" },
8
+ stream_url:{ value:"", required: true }
9
+ },
10
+ inputs:0,
11
+ outputs:0,
12
+ icon: "alice.png",
13
+ color: "#D8BFD8",
14
+ label: function(){
15
+ return this.name + ":video";
16
+ },
17
+ // oneditprepare: function(){
18
+ // $("#node-input-protocol").typedInput({
19
+ // types: [
20
+ // {
21
+ // value: "protocol",
22
+ // options: [
23
+ // { value: "hls", label: "HLS"},
24
+ // { value: "progressive_mp4", label: "Progressive mp4"},
25
+ // ]
26
+ // }
27
+ // ]
28
+ // });
29
+ // },
30
+ oneditsave: function(){
31
+ deivcename = $('#node-input-device option:selected').text();
32
+ $('#node-input-name').val(deivcename);
33
+ }
34
+ })
35
+ </script>
36
+
37
+ <script type="text/x-red" data-template-name="Video">
38
+ <input type="hidden" id="node-input-name">
39
+ <div class="form-row">
40
+ <label for="node-input-device">Device</label>
41
+ <input id="node-input-device">
42
+ </div>
43
+ <div class="form-row">
44
+ <label for="node-input-protocol">Protocol</label>
45
+ <input type="text" id="node-input-protocol" disabled>
46
+ </div>
47
+ <div class="form-row">
48
+ <label for="node-input-stream_url">URL</label>
49
+ <input type="text" id="node-input-stream_url">
50
+ </div>
51
+ <div class="form-tips" id="node-tip">
52
+ <span>Currently only supports the HLS stream protocol.<br>
53
+ Supported video codecs: H264.<br>
54
+ Maximum video resolution: 1920 x 1080.<br>
55
+ Supported audio codecs: AAC.
56
+ </span>
57
+ </div>
58
+ </script>
59
+
60
+ <script type="text/x-red" data-help-name="Video">
61
+ <p>Getting a video stream from a camera.</p>
62
+
63
+ <h3>Property</h3>
64
+ <dl class="message-properties">
65
+ <dt>Device
66
+ <span class="property-type">Select</span>
67
+ </dt>
68
+ <dd> The device to which this feature is connected </dd>
69
+ <dt>Protocol
70
+ <span class="property-type">disabled</span>
71
+ </dt>
72
+ <dd>Currently only supports the HLS stream protocol.<br>
73
+ Supported video codecs: H264.<br>
74
+ Maximum video resolution: 1920 x 1080.<br>
75
+ Supported audio codecs: AAC.</dd>
76
+ <dt>URL
77
+ <span class="property-type">string</span>
78
+ </dt>
79
+ <dd>Stream URL with parameters.<br>
80
+ The URL must be accessible from the Internet<br>
81
+ In response to a request for a stream using this link, you need to pass headers:<br>
82
+ Content-type: application/vnd.apple.mpegurl<br>
83
+ Access-Control-Allow-Origin: https://yastatic.net</dd>
84
+ </dl>
85
+
86
+ <h3>References</h3>
87
+ <ul>
88
+ <li><a href="https://yandex.ru/dev/dialogs/smart-home/doc/concepts/video_stream.html"> - Yandex documentation</a></li>
89
+ </ul>
90
+ </script>
@@ -0,0 +1,99 @@
1
+ import { NodeAPI, Node } from "node-red";
2
+ import { AliceVideoConfig, AliceDeviceNode, CapabilityState } from "./types.js";
3
+
4
+ export = (RED: NodeAPI): void => {
5
+ function AliceVideo(this: Node, config: AliceVideoConfig): void {
6
+ RED.nodes.createNode(this, config);
7
+ const device = RED.nodes.getNode(config.device) as AliceDeviceNode;
8
+ device.setMaxListeners(device.getMaxListeners() + 1);
9
+
10
+ const id = this.id;
11
+ const ctype = 'devices.capabilities.video_stream';
12
+ const instance = 'get_stream';
13
+ const stream_url = config.stream_url;
14
+ const protocol = config.protocol;
15
+
16
+ const curentState: CapabilityState = {
17
+ type: ctype,
18
+ state: {
19
+ instance: instance,
20
+ value: {
21
+ stream_url: stream_url,
22
+ protocol: protocol
23
+ }
24
+ }
25
+ };
26
+
27
+ this.status({ fill: "red", shape: "dot", text: "offline" });
28
+
29
+ const init = (): void => {
30
+ this.debug("Starting capability initilization ...");
31
+ const capab = {
32
+ type: ctype,
33
+ retrievable: false,
34
+ reportable: false,
35
+ parameters: {
36
+ instance: instance,
37
+ protocols: [protocol]
38
+ }
39
+ };
40
+
41
+ device.setCapability(id, capab)
42
+ .then(() => {
43
+ this.debug("Capability initilization - success!");
44
+ this.status({ fill: "green", shape: "dot", text: "online" });
45
+ })
46
+ .catch(err => {
47
+ this.error("Error on create capability: " + err.message);
48
+ this.status({ fill: "red", shape: "dot", text: "Error" });
49
+ });
50
+
51
+ device.updateCapabState(id, curentState)
52
+ .then(() => {
53
+ this.status({ fill: "green", shape: "dot", text: "online" });
54
+ })
55
+ .catch(err => {
56
+ this.error("Error on update capability state: " + err.message);
57
+ this.status({ fill: "red", shape: "dot", text: "Error" });
58
+ });
59
+ };
60
+
61
+ if (device.initState) init();
62
+
63
+ device.on("online", () => {
64
+ init();
65
+ });
66
+
67
+ device.on("offline", () => {
68
+ this.status({ fill: "red", shape: "dot", text: "offline" });
69
+ });
70
+
71
+ device.on(id, () => {
72
+ device.updateCapabState(id, curentState)
73
+ .then(() => {
74
+ const str_url = stream_url.slice(0, 25) + "...";
75
+ this.status({ fill: "green", shape: "dot", text: str_url });
76
+ })
77
+ .catch(err => {
78
+ this.error("Error on update capability state: " + err.message);
79
+ this.status({ fill: "red", shape: "dot", text: "Error" });
80
+ });
81
+ });
82
+
83
+ this.on('close', (removed: boolean, done: () => void) => {
84
+ device.setMaxListeners(device.getMaxListeners() - 1);
85
+ if (removed) {
86
+ device.delCapability(id)
87
+ .then(() => { done(); })
88
+ .catch(err => {
89
+ this.error("Error on delete capability: " + err.message);
90
+ done();
91
+ });
92
+ } else {
93
+ done();
94
+ }
95
+ });
96
+ }
97
+
98
+ RED.nodes.registerType("Video", AliceVideo);
99
+ };
package/src/alice.html ADDED
@@ -0,0 +1,242 @@
1
+
2
+ <script src="https://widget.cloudpayments.ru/bundles/cloudpayments.js"></script>
3
+ <script type="text/javascript">
4
+ RED.nodes.registerType('alice-service',{
5
+ category: 'config',
6
+ defaults: {
7
+ name: {value:null}
8
+ },
9
+ credentials:{
10
+ email: {type: "text", required:true},
11
+ password: {type: "password", required:true},
12
+ token: {type: "password", required:true},
13
+ id:{type:"text",required:true}
14
+ },
15
+ label: function() {
16
+ return this.name || "Alice-Credentials";
17
+ },
18
+ oneditprepare:function(){
19
+ $('#subscribe-status').text("checking ...");
20
+ let em = $('#node-config-input-email').val();
21
+ let idt = $('#node-config-input-id').val();
22
+ getSubscribeStatus(idt,em,"subscribe-status");
23
+ },
24
+ oneditsave: function(){
25
+ nodename = $('#node-config-input-email').val();
26
+ $('#node-config-input-name').val(nodename);
27
+ }
28
+ });
29
+ function getSubscribeStatus(id, email, contId){
30
+ if (email.length < 3 || id.length < 3){
31
+ return;
32
+ };
33
+ $.ajax({
34
+ url: "https://nodered-home.ru/payment/getsubscribestatus",
35
+ type:"POST",
36
+ headers: {
37
+ 'Content-Type':'application/json'
38
+ },
39
+ crossDomain: true,
40
+ contentType:"application/json",
41
+ data: JSON.stringify({
42
+ id: id,
43
+ email: email
44
+ }),
45
+ dataType: "json",
46
+ format:"json"
47
+ })
48
+ .done(result=>{
49
+ // console.log(result);
50
+ $('#'+contId).text(result.data.text);
51
+ })
52
+ .fail(error=>{
53
+ console.log(error)
54
+ console.error(error.responseJSON);
55
+ RED.notify("Error : "+error.responseJSON.message, {type:"error"});
56
+ });
57
+ };
58
+ function GetToken(code){
59
+ console.log(this.id);
60
+ RED.notify("Request has been sent. Please, wait",{type:"compact"});
61
+ $('#verification_code').prop('disabled', true);
62
+ $('#submit_button').prop('disabled', true);
63
+ $.ajax({
64
+ url: "https://nodered-home.ru/api/v1/getyatoken",
65
+ type:"POST",
66
+ headers: {
67
+ 'Content-Type':'application/json'
68
+ },
69
+ crossDomain: true,
70
+ contentType:"application/json",
71
+ data: JSON.stringify({code:code}),
72
+ dataType: "json",
73
+ format:"json"
74
+ })
75
+ .done(data=>{
76
+ RED.notify("Authentication data, received successfully", {type:"success"});
77
+ $('#node-config-input-email').val(data.email).trigger("change");
78
+ $('#node-config-input-id').val(data.id).trigger("input").trigger("change");
79
+ $('#node-config-input-password').val(data.password).trigger("change");
80
+ $('#node-config-input-token').val(JSON.stringify(data.token)).trigger("change");
81
+ }).fail(error=>{
82
+ console.error(error.responseJSON);
83
+ RED.notify("Error : "+error.responseJSON.message, {type:"error"});
84
+ $('#verification_code').prop('disabled', false);
85
+ $('#submit_button').prop('disabled', false);
86
+ });
87
+ }
88
+ function ClearDevicesConfigOnGateway(email) {
89
+ RED.notify("Request has been sent. Please, wait",{type:"compact"});
90
+ const suburl = btoa(email);
91
+ $.ajax({
92
+ url: "/noderedhome/"+suburl+"/clearalldevice",
93
+ type:"GET"
94
+ })
95
+ .done(result=>{
96
+ RED.notify("All configs have been successfully cleared", {type:"success"});
97
+ })
98
+ .fail(error=>{
99
+ RED.notify("Error when deleting configs on the gateway", {type:"error"});
100
+ })
101
+ };
102
+ function pay(id, email) {
103
+ console.log("Start pay");
104
+ if (!id || !email){
105
+ RED.notify("Please authorize before purchasing a subscription", {type:"error"});
106
+ return;
107
+ };
108
+ $('#subscribe-button').prop('disabled', true);
109
+ RED.notify("Request has been sent. Please, wait",{type:"compact"});
110
+ let paymentWidget = new cp.CloudPayments();
111
+ console.log("Start get pay confi");
112
+ $.ajax({
113
+ url: "https://nodered-home.ru/payment/create",
114
+ type:"POST",
115
+ headers: {
116
+ 'Content-Type':'application/json'
117
+ },
118
+ crossDomain: true,
119
+ contentType:"application/json",
120
+ data: JSON.stringify({
121
+ id: id,
122
+ email: email
123
+ }),
124
+ dataType: "json",
125
+ format:"json"
126
+ })
127
+ .done(paydata=>{
128
+ console.log("pay config done");
129
+ console.log("Start widget");
130
+ paymentWidget.pay('auth', // или 'charge'
131
+ paydata,
132
+ {
133
+ onSuccess: function (result) { // success
134
+ //действие при успешной оплате
135
+ let em = $('#node-config-input-email').val();
136
+ let idt = $('#node-config-input-id').val();
137
+ $('#subscribe-status').text("checking ...");
138
+ setTimeout(() => {
139
+ getSubscribeStatus(idt,em,"subscribe-status");
140
+ }, 2000);
141
+
142
+ },
143
+ onFail: function (reason, options) { // fail
144
+ //действие при неуспешной оплате
145
+ },
146
+ onComplete: function (paymentResult, options) { //Вызывается как только виджет получает от api.cloudpayments ответ с результатом транзакции.
147
+ //например вызов вашей аналитики Facebook Pixel
148
+
149
+ }
150
+ }
151
+ );
152
+ $('#subscribe-button').prop('disabled', false);
153
+ })
154
+ .fail(error=>{
155
+ console.log(error)
156
+ console.error(error.responseJSON);
157
+ RED.notify("Error : "+error.responseJSON.message, {type:"error"});
158
+ $('#subscribe-button').prop('disabled', false);
159
+ })
160
+ };
161
+ </script>
162
+
163
+ <script type="text/x-red" data-template-name="alice-service">
164
+ <input type="hidden" id="node-config-input-name">
165
+ <div name="New Reg">
166
+ <div class="form-row"><b>Credentials</b></div>
167
+ <div class="form-row">
168
+ <p>1. Follow the link and confirm access</p>
169
+ <label>Authentication</label>
170
+ <button
171
+ onclick="window.open('https://oauth.yandex.ru/authorize?response_type=code&client_id=aa882e33283046fc83de54be20a2e5d8')"
172
+ class="ui-button">Yandex Authentication
173
+ </button>
174
+ </div>
175
+
176
+ <div class="form-row">
177
+ <p>2. Input the verification code and click submit</p>
178
+ <label>Code</label>
179
+ <input type="text" id="verification_code" style="width:auto">
180
+ <button id="submit_button" onclick="GetToken($('#verification_code').val())" class="ui-button">Submit</button>
181
+ </div>
182
+ <div class="form-tips" id="node-tip">
183
+ <span>Tip: Data request may take up to 15 seconds, please wait for success or error message.</span>
184
+ </div>
185
+ <hr>
186
+ <div class="form-row">
187
+ <p><b>Authentication result:</b></p>
188
+ </div>
189
+ <div class="form-row">
190
+ <label for="node-config-input-email">Email</label>
191
+ <input type="text" id="node-config-input-email" disabled>
192
+ </div>
193
+ <div class="form-row">
194
+ <label for="node-config-input-id">ID</label>
195
+ <input type="text" id="node-config-input-id" disabled>
196
+ </div>
197
+ <!-- <div class="form-row" style="visibility: hidden;">
198
+ <label for="node-config-input-password">Password</label> -->
199
+ <input type="hidden" id="node-config-input-password" disabled>
200
+ <!-- </div> -->
201
+ <div class="form-row" style="display: none;">
202
+ <label for="node-config-input-token">Token</label>
203
+ <input type="password" id="node-config-input-token" disabled>
204
+ </div>
205
+ </div>
206
+ <hr>
207
+ <div name="subcribtion">
208
+ <div class="form-row">
209
+ <p><b>Subscription:</b></p>
210
+ </div>
211
+ <div class="form-row">
212
+ <label for="node-config-input-token" style="width: 150px">Subscription status:</label> <span id="subscribe-status"></span>
213
+ <div class="form-tips" id="node-tip-subscribe-warn">
214
+ <span>Important:</span><br>
215
+ <span> - By purchasing a subscription, you agree to the terms of the <a href="https://nodered-home.ru/public_offer.pdf" target="_blank">public offer</a></span><br>
216
+ <span> - View and manage your subscriptions <a href="https://my.cloudpayments.ru/ru/unsubscribe" target="_blank">here</a></span>
217
+ <div style="text-align: end">
218
+ <button id="subscribe-button"
219
+ onclick="pay($('#node-config-input-id').val(), $('#node-config-input-email').val())"
220
+ class="ui-button">Buy a subscription
221
+ </button>
222
+ </div>
223
+ </div>
224
+ </div>
225
+ </div>
226
+ <hr>
227
+ <div name="Clearing">
228
+ <div class="form-row"><b>Сlear all data and configs on the gateway</b></div>
229
+ <div class="form-tips" id="node-tip">
230
+ <span>Tip: Сlear all data on the gateway may take up to 15 seconds, please wait for success or error message. When finished, make a full deployment to update the settings on the gateway.</span>
231
+ <div style="text-align: end">
232
+ <button
233
+ style="margin-left: 150px; margin-top: 10px;"
234
+ onclick="ClearDevicesConfigOnGateway($('#node-config-input-email').val())"
235
+ class="ui-button">
236
+ Сlear data on the gateway
237
+ </button>
238
+ </div>
239
+ </div>
240
+ </div>
241
+ </script>
242
+
package/src/alice.ts ADDED
@@ -0,0 +1,146 @@
1
+ import { NodeAPI } from "node-red";
2
+ import axios from "axios";
3
+ import mqtt from "mqtt";
4
+ import { AliceServiceConfig, AliceServiceNode } from "./types.js";
5
+
6
+ export = (RED: NodeAPI): void => {
7
+ function AliceService(this: AliceServiceNode, config: AliceServiceConfig): void {
8
+ RED.nodes.createNode(this, config);
9
+ this.debug("Starting Alice service... ID: " + this.id);
10
+
11
+ const email = this.credentials.email;
12
+ const login = this.credentials.id;
13
+ const password = this.credentials.password;
14
+ const token = this.credentials.token;
15
+
16
+ //вызов для удаления всех устройств
17
+ const suburl = Buffer.from(email).toString('base64');
18
+ RED.httpAdmin.get("/noderedhome/" + suburl + "/clearalldevice", (_req, res) => {
19
+ axios.request({
20
+ method: 'POST',
21
+ url: 'https://api.nodered-home.ru/gtw/device/clearallconfigs',
22
+ headers: {
23
+ 'content-type': 'application/json',
24
+ 'Authorization': "Bearer " + this.getToken()
25
+ },
26
+ data: {}
27
+ })
28
+ .then(() => {
29
+ this.trace("All devices configs deleted on gateway successfully");
30
+ res.sendStatus(200);
31
+ })
32
+ .catch(error => {
33
+ this.debug("Error when delete All devices configs deleted on gateway: " + error.message);
34
+ res.sendStatus(500);
35
+ });
36
+ });
37
+
38
+ RED.httpAdmin.get("/noderedhome/" + this.id + "/getfullconfig", (_req, res) => {
39
+ axios.request({
40
+ method: 'GET',
41
+ url: 'https://api.iot.yandex.net/v1.0/user/info',
42
+ headers: {
43
+ 'content-type': 'application/json',
44
+ 'Authorization': "Bearer " + this.getToken()
45
+ }
46
+ })
47
+ .then(result => {
48
+ this.trace("Full Alice SmartHome config successfully retrieved");
49
+ res.json(result.data);
50
+ })
51
+ .catch(error => {
52
+ this.debug("Error when retrieve Alice SmartHome config: " + error.message);
53
+ res.sendStatus(500);
54
+ });
55
+ });
56
+
57
+ this.isOnline = false;
58
+
59
+ if (!token) {
60
+ this.error("Authentication is required!!!");
61
+ return;
62
+ }
63
+
64
+ const mqttClient = mqtt.connect("mqtts://mqtt.cloud.yandex.net", {
65
+ port: 8883,
66
+ clientId: login,
67
+ rejectUnauthorized: false,
68
+ username: login,
69
+ password: password,
70
+ reconnectPeriod: 10000
71
+ });
72
+
73
+ mqttClient.on("message", (topic: string, payload: Buffer) => {
74
+ const arrTopic = topic.split('/');
75
+ const data = JSON.parse(payload.toString());
76
+ this.trace("Incoming:" + topic + " timestamp:" + new Date().getTime());
77
+ if (payload.length && typeof data === 'object') {
78
+ if (arrTopic[3] == 'message') {
79
+ this.warn(data.text);
80
+ } else {
81
+ this.emit(arrTopic[3], data);
82
+ }
83
+ }
84
+ });
85
+
86
+ mqttClient.on("connect", () => {
87
+ this.debug("Yandex IOT client connected. ");
88
+ this.emit('online');
89
+ mqttClient.subscribe("$me/device/commands/+", () => {
90
+ this.debug("Yandex IOT client subscribed to the command");
91
+ });
92
+ });
93
+
94
+ mqttClient.on("offline", () => {
95
+ this.debug("Yandex IOT client offline. ");
96
+ this.emit('offline');
97
+ });
98
+
99
+ mqttClient.on("disconnect", () => {
100
+ this.debug("Yandex IOT client disconnect.");
101
+ this.emit('offline');
102
+ });
103
+
104
+ mqttClient.on("reconnect", () => {
105
+ this.debug("Yandex IOT client reconnecting ...");
106
+ });
107
+
108
+ mqttClient.on("error", (err) => {
109
+ this.error("Yandex IOT client Error: " + err.message);
110
+ this.emit('offline');
111
+ });
112
+
113
+ this.on('offline', () => {
114
+ this.isOnline = false;
115
+ });
116
+
117
+ this.on('online', () => {
118
+ this.isOnline = true;
119
+ });
120
+
121
+ this.on('close', (done: () => void) => {
122
+ this.emit('offline');
123
+ setTimeout(() => {
124
+ mqttClient.end(false, {}, done);
125
+ }, 500);
126
+ });
127
+
128
+ this.send2gate = (path: string, data: string, retain: boolean) => {
129
+ this.trace("Outgoing: " + path);
130
+ mqttClient.publish(path, data, { qos: 0, retain: retain });
131
+ };
132
+
133
+ this.getToken = () => {
134
+ return JSON.parse(token).access_token;
135
+ };
136
+ }
137
+
138
+ RED.nodes.registerType("alice-service", AliceService, {
139
+ credentials: {
140
+ email: { type: "text" },
141
+ password: { type: "password" },
142
+ token: { type: "password" },
143
+ id: { type: "text" }
144
+ }
145
+ });
146
+ };