iobroker.parcel 0.0.2
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.
- package/LICENSE +21 -0
- package/README.md +140 -0
- package/admin/index_m.html +157 -0
- package/admin/parcel.png +0 -0
- package/admin/style.css +32 -0
- package/admin/words.js +53 -0
- package/io-package.json +270 -0
- package/lib/adapter-config.d.ts +19 -0
- package/lib/json2iob.js +290 -0
- package/lib/tools.js +100 -0
- package/main.js +1210 -0
- package/package.json +77 -0
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// This file extends the AdapterConfig type from "@types/iobroker"
|
|
2
|
+
// using the actual properties present in io-package.json
|
|
3
|
+
// in order to provide typings for adapter.config properties
|
|
4
|
+
|
|
5
|
+
import { native } from "../io-package.json";
|
|
6
|
+
|
|
7
|
+
type _AdapterConfig = typeof native;
|
|
8
|
+
|
|
9
|
+
// Augment the globally declared type ioBroker.AdapterConfig
|
|
10
|
+
declare global {
|
|
11
|
+
namespace ioBroker {
|
|
12
|
+
interface AdapterConfig extends _AdapterConfig {
|
|
13
|
+
// Do not enter anything here!
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// this is required so the above AdapterConfig is found by TypeScript / type checking
|
|
19
|
+
export {};
|
package/lib/json2iob.js
ADDED
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
//v1.4
|
|
2
|
+
/*
|
|
3
|
+
options:
|
|
4
|
+
write //set common write variable to true
|
|
5
|
+
forceIndex //instead of trying to find names for array entries, use the index as the name
|
|
6
|
+
channelName //set name of the root channel
|
|
7
|
+
preferedArrayName //set key to use this as an array entry name
|
|
8
|
+
autoCast (true false) // make JSON.parse to parse numbers correctly
|
|
9
|
+
descriptions: Object of names for state keys
|
|
10
|
+
*/
|
|
11
|
+
const JSONbig = require("json-bigint")({ storeAsString: true });
|
|
12
|
+
module.exports = class Json2iob {
|
|
13
|
+
constructor(adapter) {
|
|
14
|
+
this.adapter = adapter;
|
|
15
|
+
this.alreadyCreatedObjects = {};
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async parse(path, element, options) {
|
|
19
|
+
try {
|
|
20
|
+
if (element === null || element === undefined) {
|
|
21
|
+
this.adapter.log.debug("Cannot extract empty: " + path);
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const objectKeys = Object.keys(element);
|
|
26
|
+
|
|
27
|
+
if (!options || !options.write) {
|
|
28
|
+
if (!options) {
|
|
29
|
+
options = { write: false };
|
|
30
|
+
} else {
|
|
31
|
+
options["write"] = false;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (typeof element === "string" || typeof element === "number") {
|
|
36
|
+
let name = element;
|
|
37
|
+
if (typeof element === "number") {
|
|
38
|
+
name = element.toString();
|
|
39
|
+
}
|
|
40
|
+
if (!this.alreadyCreatedObjects[path]) {
|
|
41
|
+
await this.adapter
|
|
42
|
+
.setObjectNotExistsAsync(path, {
|
|
43
|
+
type: "state",
|
|
44
|
+
common: {
|
|
45
|
+
name: name,
|
|
46
|
+
role: this.getRole(element, options.write),
|
|
47
|
+
type: element !== null ? typeof element : "mixed",
|
|
48
|
+
write: options.write,
|
|
49
|
+
read: true,
|
|
50
|
+
},
|
|
51
|
+
native: {},
|
|
52
|
+
})
|
|
53
|
+
.then(() => {
|
|
54
|
+
// this.alreadyCreatedObjects[path] = true;
|
|
55
|
+
})
|
|
56
|
+
.catch((error) => {
|
|
57
|
+
this.adapter.log.error(error);
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
this.adapter.setState(path, element, true);
|
|
62
|
+
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
if (!this.alreadyCreatedObjects[path]) {
|
|
66
|
+
await this.adapter
|
|
67
|
+
.setObjectNotExistsAsync(path, {
|
|
68
|
+
type: "channel",
|
|
69
|
+
common: {
|
|
70
|
+
name: options.channelName || "",
|
|
71
|
+
type: "mixed",
|
|
72
|
+
write: false,
|
|
73
|
+
read: true,
|
|
74
|
+
},
|
|
75
|
+
native: {},
|
|
76
|
+
})
|
|
77
|
+
.then(() => {
|
|
78
|
+
// this.alreadyCreatedObjects[path] = true;
|
|
79
|
+
options.channelName = undefined;
|
|
80
|
+
})
|
|
81
|
+
.catch((error) => {
|
|
82
|
+
this.adapter.log.error(error);
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
if (Array.isArray(element)) {
|
|
86
|
+
this.extractArray(element, "", path, options);
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
objectKeys.forEach(async (key) => {
|
|
90
|
+
if (this.isJsonString(element[key]) && options.autoCast) {
|
|
91
|
+
element[key] = JSONbig.parse(element[key]);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (Array.isArray(element[key])) {
|
|
95
|
+
this.extractArray(element, key, path, options);
|
|
96
|
+
} else if (element[key] !== null && typeof element[key] === "object") {
|
|
97
|
+
this.parse(path + "." + key, element[key], options);
|
|
98
|
+
} else {
|
|
99
|
+
if (!this.alreadyCreatedObjects[path + "." + key]) {
|
|
100
|
+
let objectName = key;
|
|
101
|
+
if (options.descriptions && options.descriptions[key]) {
|
|
102
|
+
objectName = options.descriptions[key];
|
|
103
|
+
}
|
|
104
|
+
const type = element[key] !== null ? typeof element[key] : "mixed";
|
|
105
|
+
const common = {
|
|
106
|
+
name: objectName,
|
|
107
|
+
role: this.getRole(element[key], options.write),
|
|
108
|
+
type: type,
|
|
109
|
+
write: options.write,
|
|
110
|
+
read: true,
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
await this.adapter
|
|
114
|
+
.setObjectNotExistsAsync(path + "." + key, {
|
|
115
|
+
type: "state",
|
|
116
|
+
common: common,
|
|
117
|
+
native: {},
|
|
118
|
+
})
|
|
119
|
+
.then(() => {
|
|
120
|
+
// this.alreadyCreatedObjects[path + "." + key] = true;
|
|
121
|
+
})
|
|
122
|
+
.catch((error) => {
|
|
123
|
+
this.adapter.log.error(error);
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
this.adapter.setState(path + "." + key, element[key], true);
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
} catch (error) {
|
|
130
|
+
this.adapter.log.error("Error extract keys: " + path + " " + JSON.stringify(element));
|
|
131
|
+
this.adapter.log.error(error);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
extractArray(element, key, path, options) {
|
|
135
|
+
try {
|
|
136
|
+
if (key) {
|
|
137
|
+
element = element[key];
|
|
138
|
+
}
|
|
139
|
+
element.forEach(async (arrayElement, index) => {
|
|
140
|
+
index = index + 1;
|
|
141
|
+
if (index < 10) {
|
|
142
|
+
index = "0" + index;
|
|
143
|
+
}
|
|
144
|
+
let arrayPath = key + index;
|
|
145
|
+
if (typeof arrayElement === "string") {
|
|
146
|
+
this.parse(path + "." + key + "." + arrayElement, arrayElement, options);
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
if (typeof arrayElement[Object.keys(arrayElement)[0]] === "string") {
|
|
150
|
+
arrayPath = arrayElement[Object.keys(arrayElement)[0]];
|
|
151
|
+
}
|
|
152
|
+
Object.keys(arrayElement).forEach((keyName) => {
|
|
153
|
+
if (keyName.endsWith("Id") && arrayElement[keyName] !== null) {
|
|
154
|
+
if (arrayElement[keyName] && arrayElement[keyName].replace) {
|
|
155
|
+
arrayPath = arrayElement[keyName].replace(/\./g, "");
|
|
156
|
+
} else {
|
|
157
|
+
arrayPath = arrayElement[keyName];
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
Object.keys(arrayElement).forEach((keyName) => {
|
|
162
|
+
if (keyName.endsWith("Name")) {
|
|
163
|
+
if (arrayElement[keyName] && arrayElement[keyName].replace) {
|
|
164
|
+
arrayPath = arrayElement[keyName].replace(/\./g, "");
|
|
165
|
+
} else {
|
|
166
|
+
arrayPath = arrayElement[keyName];
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
if (arrayElement.id) {
|
|
172
|
+
if (arrayElement.id.replace) {
|
|
173
|
+
arrayPath = arrayElement.id.replace(/\./g, "");
|
|
174
|
+
} else {
|
|
175
|
+
arrayPath = arrayElement.id;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
if (arrayElement.name) {
|
|
179
|
+
arrayPath = arrayElement.name.replace(/\./g, "");
|
|
180
|
+
}
|
|
181
|
+
if (arrayElement.label) {
|
|
182
|
+
arrayPath = arrayElement.label.replace(/\./g, "");
|
|
183
|
+
}
|
|
184
|
+
if (arrayElement.labelText) {
|
|
185
|
+
arrayPath = arrayElement.labelText.replace(/\./g, "");
|
|
186
|
+
}
|
|
187
|
+
if (arrayElement.start_date_time) {
|
|
188
|
+
arrayPath = arrayElement.start_date_time.replace(/\./g, "");
|
|
189
|
+
}
|
|
190
|
+
if (options.preferedArrayName && options.preferedArrayName.indexOf("+") !== -1) {
|
|
191
|
+
const preferedArrayNameArray = options.preferedArrayName.split("+");
|
|
192
|
+
if (arrayElement[preferedArrayNameArray[0]]) {
|
|
193
|
+
const element0 = arrayElement[preferedArrayNameArray[0]].replace(/\./g, "").replace(/\ /g, "");
|
|
194
|
+
let element1 = "";
|
|
195
|
+
if (preferedArrayNameArray[1].indexOf("/") !== -1) {
|
|
196
|
+
const subArray = preferedArrayNameArray[1].split("/");
|
|
197
|
+
const subElement = arrayElement[subArray[0]];
|
|
198
|
+
if (subElement && subElement[subArray[1]] !== undefined) {
|
|
199
|
+
element1 = subElement[subArray[1]];
|
|
200
|
+
} else if (arrayElement[subArray[1]] !== undefined) {
|
|
201
|
+
element1 = arrayElement[subArray[1]];
|
|
202
|
+
}
|
|
203
|
+
} else {
|
|
204
|
+
element1 = arrayElement[preferedArrayNameArray[1]].replace(/\./g, "").replace(/\ /g, "");
|
|
205
|
+
}
|
|
206
|
+
arrayPath = element0 + "-" + element1;
|
|
207
|
+
}
|
|
208
|
+
} else if (options.preferedArrayName && options.preferedArrayName.indexOf("/") !== -1) {
|
|
209
|
+
const preferedArrayNameArray = options.preferedArrayName.split("/");
|
|
210
|
+
const subElement = arrayElement[preferedArrayNameArray[0]];
|
|
211
|
+
if (subElement) {
|
|
212
|
+
arrayPath = subElement[preferedArrayNameArray[1]].replace(/\./g, "").replace(/\ /g, "");
|
|
213
|
+
}
|
|
214
|
+
} else if (options.preferedArrayName && arrayElement[options.preferedArrayName]) {
|
|
215
|
+
arrayPath = arrayElement[options.preferedArrayName].replace(/\./g, "");
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
if (options.forceIndex) {
|
|
219
|
+
arrayPath = key + index;
|
|
220
|
+
}
|
|
221
|
+
//special case array with 2 string objects
|
|
222
|
+
if (
|
|
223
|
+
!options.forceIndex &&
|
|
224
|
+
Object.keys(arrayElement).length === 2 &&
|
|
225
|
+
typeof Object.keys(arrayElement)[0] === "string" &&
|
|
226
|
+
typeof Object.keys(arrayElement)[1] === "string" &&
|
|
227
|
+
typeof arrayElement[Object.keys(arrayElement)[0]] !== "object" &&
|
|
228
|
+
typeof arrayElement[Object.keys(arrayElement)[1]] !== "object" &&
|
|
229
|
+
arrayElement[Object.keys(arrayElement)[0]] !== "null"
|
|
230
|
+
) {
|
|
231
|
+
let subKey = arrayElement[Object.keys(arrayElement)[0]];
|
|
232
|
+
const subValue = arrayElement[Object.keys(arrayElement)[1]];
|
|
233
|
+
const subName = Object.keys(arrayElement)[0] + " " + Object.keys(arrayElement)[1];
|
|
234
|
+
if (key) {
|
|
235
|
+
subKey = key + "." + subKey;
|
|
236
|
+
}
|
|
237
|
+
if (!this.alreadyCreatedObjects[path + "." + subKey]) {
|
|
238
|
+
await this.adapter
|
|
239
|
+
.setObjectNotExistsAsync(path + "." + subKey, {
|
|
240
|
+
type: "state",
|
|
241
|
+
common: {
|
|
242
|
+
name: subName,
|
|
243
|
+
role: this.getRole(subValue, options.write),
|
|
244
|
+
type: subValue !== null ? typeof subValue : "mixed",
|
|
245
|
+
write: options.write,
|
|
246
|
+
read: true,
|
|
247
|
+
},
|
|
248
|
+
native: {},
|
|
249
|
+
})
|
|
250
|
+
.then(() => {
|
|
251
|
+
// this.alreadyCreatedObjects[path + "." + subKey] = true;
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
this.adapter.setState(path + "." + subKey, subValue, true);
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
this.parse(path + "." + arrayPath, arrayElement, options);
|
|
258
|
+
});
|
|
259
|
+
} catch (error) {
|
|
260
|
+
this.adapter.log.error("Cannot extract array " + path);
|
|
261
|
+
this.adapter.log.error(error);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
isJsonString(str) {
|
|
265
|
+
try {
|
|
266
|
+
JSON.parse(str);
|
|
267
|
+
} catch (e) {
|
|
268
|
+
return false;
|
|
269
|
+
}
|
|
270
|
+
return true;
|
|
271
|
+
}
|
|
272
|
+
getRole(element, write) {
|
|
273
|
+
if (typeof element === "boolean" && !write) {
|
|
274
|
+
return "indicator";
|
|
275
|
+
}
|
|
276
|
+
if (typeof element === "boolean" && write) {
|
|
277
|
+
return "switch";
|
|
278
|
+
}
|
|
279
|
+
if (typeof element === "number" && !write) {
|
|
280
|
+
return "value";
|
|
281
|
+
}
|
|
282
|
+
if (typeof element === "number" && write) {
|
|
283
|
+
return "level";
|
|
284
|
+
}
|
|
285
|
+
if (typeof element === "string") {
|
|
286
|
+
return "text";
|
|
287
|
+
}
|
|
288
|
+
return "state";
|
|
289
|
+
}
|
|
290
|
+
};
|
package/lib/tools.js
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
const axios = require("axios").default;
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Tests whether the given variable is a real object and not an Array
|
|
5
|
+
* @param {any} it The variable to test
|
|
6
|
+
* @returns {it is Record<string, any>}
|
|
7
|
+
*/
|
|
8
|
+
function isObject(it) {
|
|
9
|
+
// This is necessary because:
|
|
10
|
+
// typeof null === 'object'
|
|
11
|
+
// typeof [] === 'object'
|
|
12
|
+
// [] instanceof Object === true
|
|
13
|
+
return Object.prototype.toString.call(it) === "[object Object]";
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Tests whether the given variable is really an Array
|
|
18
|
+
* @param {any} it The variable to test
|
|
19
|
+
* @returns {it is any[]}
|
|
20
|
+
*/
|
|
21
|
+
function isArray(it) {
|
|
22
|
+
if (typeof Array.isArray === "function") return Array.isArray(it);
|
|
23
|
+
return Object.prototype.toString.call(it) === "[object Array]";
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Translates text to the target language. Automatically chooses the right translation API.
|
|
28
|
+
* @param {string} text The text to translate
|
|
29
|
+
* @param {string} targetLang The target languate
|
|
30
|
+
* @param {string} [yandexApiKey] The yandex API key. You can create one for free at https://translate.yandex.com/developers
|
|
31
|
+
* @returns {Promise<string>}
|
|
32
|
+
*/
|
|
33
|
+
async function translateText(text, targetLang, yandexApiKey) {
|
|
34
|
+
if (targetLang === "en") {
|
|
35
|
+
return text;
|
|
36
|
+
} else if (!text) {
|
|
37
|
+
return "";
|
|
38
|
+
}
|
|
39
|
+
if (yandexApiKey) {
|
|
40
|
+
return translateYandex(text, targetLang, yandexApiKey);
|
|
41
|
+
} else {
|
|
42
|
+
return translateGoogle(text, targetLang);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Translates text with Yandex API
|
|
48
|
+
* @param {string} text The text to translate
|
|
49
|
+
* @param {string} targetLang The target languate
|
|
50
|
+
* @param {string} apiKey The yandex API key. You can create one for free at https://translate.yandex.com/developers
|
|
51
|
+
* @returns {Promise<string>}
|
|
52
|
+
*/
|
|
53
|
+
async function translateYandex(text, targetLang, apiKey) {
|
|
54
|
+
if (targetLang === "zh-cn") {
|
|
55
|
+
targetLang = "zh";
|
|
56
|
+
}
|
|
57
|
+
try {
|
|
58
|
+
const url = `https://translate.yandex.net/api/v1.5/tr.json/translate?key=${apiKey}&text=${encodeURIComponent(text)}&lang=en-${targetLang}`;
|
|
59
|
+
/** @type {any} */
|
|
60
|
+
const response = await axios({url, timeout: 15000});
|
|
61
|
+
if (response.data && response.data.text && isArray(response.data.text)) {
|
|
62
|
+
return response.data.text[0];
|
|
63
|
+
}
|
|
64
|
+
throw new Error("Invalid response for translate request");
|
|
65
|
+
} catch (e) {
|
|
66
|
+
throw new Error(`Could not translate to "${targetLang}": ${e}`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Translates text with Google API
|
|
72
|
+
* @param {string} text The text to translate
|
|
73
|
+
* @param {string} targetLang The target languate
|
|
74
|
+
* @returns {Promise<string>}
|
|
75
|
+
*/
|
|
76
|
+
async function translateGoogle(text, targetLang) {
|
|
77
|
+
try {
|
|
78
|
+
const url = `http://translate.googleapis.com/translate_a/single?client=gtx&sl=en&tl=${targetLang}&dt=t&q=${encodeURIComponent(text)}&ie=UTF-8&oe=UTF-8`;
|
|
79
|
+
const response = await axios({url, timeout: 15000});
|
|
80
|
+
if (isArray(response.data)) {
|
|
81
|
+
// we got a valid response
|
|
82
|
+
return response.data[0][0][0];
|
|
83
|
+
}
|
|
84
|
+
throw new Error("Invalid response for translate request");
|
|
85
|
+
} catch (e) {
|
|
86
|
+
if (e.response && e.response.status === 429) {
|
|
87
|
+
throw new Error(
|
|
88
|
+
`Could not translate to "${targetLang}": Rate-limited by Google Translate`
|
|
89
|
+
);
|
|
90
|
+
} else {
|
|
91
|
+
throw new Error(`Could not translate to "${targetLang}": ${e}`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
module.exports = {
|
|
97
|
+
isArray,
|
|
98
|
+
isObject,
|
|
99
|
+
translateText
|
|
100
|
+
};
|