iobroker.weather-warnings
Version:
845 lines (844 loc) • 27.7 kB
JavaScript
"use strict";
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var library_exports = {};
__export(library_exports, {
BaseClass: () => BaseClass,
Library: () => Library,
sleep: () => sleep
});
module.exports = __toCommonJS(library_exports);
var import_jsonata = __toESM(require("jsonata"));
var import_fs = __toESM(require("fs"));
var import_definition = require("./def/definition");
class BaseClass {
unload = false;
log;
adapter;
library;
name = ``;
/**
* Create a new instance of the BaseClass.
*
* @param adapter The adapter instance.
* @param name The name of the instance, used for logging.
*/
constructor(adapter, name = "") {
this.name = name;
this.log = new CustomLog(adapter, this.name);
this.adapter = adapter;
this.library = adapter.library;
}
// hier lassen wir das async nicht das eine übergeordnete Version was mit await aufrufen will.
//eslint-disable-next-line
async delete() {
this.unload = true;
}
}
class CustomLog {
adapter;
prefix;
/**
* Create a new instance of the CustomLog class.
*
* @param adapter The adapter instance.
* @param text The prefix for the log messages.
*/
constructor(adapter, text = "") {
this.adapter = adapter;
this.prefix = text;
}
/**
* Return the prefix string used for log messages.
*
* @returns The prefix string.
*/
getName() {
return this.prefix;
}
/**
* Writes a debug message to the log.
*
* @param log The debug message.
* @param log2 An additional debug message.
*/
debug(log, log2 = "") {
this.adapter.log.debug(log2 ? `[${log}] ${log2}` : `[${this.prefix}] ${log}`);
}
info(message, additionalMessage = "") {
const formattedMessage = additionalMessage ? `[${message}] ${additionalMessage}` : `[${this.prefix}] ${message}`;
this.adapter.log.info(formattedMessage);
}
/**
* Writes a warning message to the log.
*
* @param message The warning message.
* @param additionalMessage An additional warning message.
*/
warn(message, additionalMessage = "") {
const formattedMessage = additionalMessage ? `[${message}] ${additionalMessage}` : `[${this.prefix}] ${message}`;
this.adapter.log.warn(formattedMessage);
}
/**
* Writes an error message to the log.
*
* @param errorMessage The error message.
* @param additionalErrorMessage An additional error message.
*/
error(errorMessage, additionalErrorMessage = "") {
const formattedErrorMessage = additionalErrorMessage ? `[${errorMessage}] ${additionalErrorMessage}` : `[${this.prefix}] ${errorMessage}`;
this.adapter.log.error(formattedErrorMessage);
}
/**
* Sets the prefix for log messages.
*
* @param prefix - The prefix to be used for log messages. Leading and trailing whitespace are trimmed.
*/
setLogPrefix(prefix) {
this.prefix = prefix.trim();
}
}
class Library extends BaseClass {
stateDataBase = {};
language = "en";
forbiddenDirs = [];
translation = {};
/**
* Creates a new instance of the Library class.
*
* @param adapter The adapter instance.
* @param _options The configuration options for the adapter. Not used.
*/
constructor(adapter, _options = null) {
super(adapter, "library");
this.stateDataBase = {};
}
/**
* Initializes the Library by setting the language based on the system configuration.
* If the system configuration's language is not available, defaults to English.
*
* @returns A promise that resolves when initialization is complete.
*/
async init() {
const obj = await this.adapter.getForeignObjectAsync("system.config");
if (obj) {
await this.setLanguage(obj.common.language, true);
} else {
await this.setLanguage("en", true);
}
}
/**
* Write/create from a Json with defined keys, the associated states and channels
*
* @param prefix iobroker datapoint prefix where to write
* @param objNode Entry point into the definition json.
* @param def the definition json
* @param data The Json to read
* @param expandTree expand arrays up to 99
* @returns void
*/
async writeFromJson(prefix, objNode, def, data, expandTree = false) {
if (!def || typeof def !== "object") {
return;
}
if (data === void 0 || ["string", "number", "boolean", "object"].indexOf(typeof data) == -1) {
return;
}
const objectDefinition = objNode ? this.getObjectDefFromJson(`${objNode}`, def) : null;
if (objectDefinition) {
objectDefinition.native = {
...objectDefinition.native || {},
objectDefinitionReference: objNode
};
}
if (typeof data === "object" && data !== null) {
if (Array.isArray(data)) {
if (!objectDefinition) {
return;
}
if (this.adapter.config.expandArray || objectDefinition.type !== "state" || expandTree) {
let a = 0;
for (const k of data) {
const defChannel = this.getChannelObject(objectDefinition);
const dp = `${prefix}${`00${a++}`.slice(-2)}`;
await this.writedp(dp, null, defChannel);
await this.writeFromJson(dp, `${objNode}`, def, k, expandTree);
}
} else {
await this.writeFromJson(prefix, objNode, def, JSON.stringify(data) || "[]", expandTree);
}
} else {
if (objectDefinition) {
const defChannel = this.getChannelObject(objectDefinition);
await this.writedp(prefix, null, defChannel);
}
if (data === null) {
return;
}
for (const k in data) {
await this.writeFromJson(`${prefix}.${k}`, `${objNode}.${k}`, def, data[k], expandTree);
}
}
} else {
if (!objectDefinition) {
return;
}
await this.writedp(prefix, data, objectDefinition);
}
}
/**
* Get the ioBroker.Object out of stateDefinition
*
* @param key is the deep linking key to the definition
* @param data is the definition dataset
* @returns ioBroker.ChannelObject | ioBroker.DeviceObject | ioBroker.StateObject
*/
getObjectDefFromJson(key, data) {
let result = this.deepJsonValue(key, data);
if (result === null || result === void 0) {
const k = key.split(".");
if (k && k[k.length - 1].startsWith("_")) {
result = import_definition.genericStateObjects.customString;
} else {
this.log.warn(`No definition for ${key}!`);
result = import_definition.genericStateObjects.state;
}
}
return this.cloneObject(result);
}
/**
* Retrieve a value from a nested object.
*
* @param key string of dot-separated keys to traverse the object.
* @param data The object to traverse.
* @returns The value at the key. If the key does not exist, throws an error.
*/
deepJsonValue(key, data) {
if (!key || !data || typeof data !== "object" || typeof key !== "string") {
throw new Error(`Error(222) data or key are missing/wrong type!`);
}
const k = key.split(`.`);
let c = 0, s = data;
while (c < k.length) {
s = s[k[c++]];
}
return s;
}
/**
* Create a ioBroker.ChannelObject or ioBroker.DeviceObject out of a
* ioBroker.Object definition.
*
* @param definition the ioBroker.Object definition
* @returns ioBroker.ChannelObject | ioBroker.DeviceObject
*/
getChannelObject(definition = null) {
const def = definition && definition._channel || null;
const result = {
_id: def ? def._id : "",
type: def && def.type != "device" ? "channel" : "device",
common: {
name: def && def.common && def.common.name || "no definition"
},
native: def && def.native || {}
};
return result;
}
/**
* Write/Create the specified data point with value, will only be written if val != oldval and obj.type == state or the data point value in the DB is not undefined. Channel and Devices have an undefined value.
*
* @param dp Data point to be written. Library.clean() is called with it.
* @param val Value for this data point. Channel vals (old and new) are undefined so they never will be written.
* @param obj The object definition for this data point (ioBroker.ChannelObject | ioBroker.DeviceObject | ioBroker.StateObject)
* @returns void
*/
async writedp(dp, val, obj = null) {
dp = this.cleandp(dp);
let node = this.readdp(dp);
const del = !this.isDirAllowed(dp);
if (node === void 0) {
if (!obj) {
throw new Error("writedp try to create a state without object informations.");
}
obj._id = `${this.adapter.name}.${this.adapter.instance}.${dp}`;
if (typeof obj.common.name == "string") {
obj.common.name = await this.getTranslationObj(obj.common.name);
}
if (!del) {
await this.adapter.extendObjectAsync(dp, obj);
}
const stateType = obj && obj.common && obj.common.type;
node = this.setdb(dp, obj.type, void 0, stateType, true);
} else if (node.init && obj) {
if (typeof obj.common.name == "string") {
obj.common.name = await this.getTranslationObj(obj.common.name);
}
if (!del) {
await this.adapter.extendObjectAsync(dp, obj);
}
}
if (obj && obj.type !== "state") {
return;
}
if (node) {
this.setdb(dp, node.type, val, node.stateTyp, true);
}
if (node && (node.val != val || !node.ack)) {
const typ = obj && obj.common && obj.common.type || node.stateTyp;
if (typ && typ != typeof val && val !== void 0) {
val = this.convertToType(val, typ);
}
if (!del) {
await this.adapter.setStateAsync(dp, {
val,
ts: Date.now(),
ack: true
});
}
}
}
/**
* Concatenates the given array of forbidden directory patterns with the current array of patterns.
* The given array elements are strings which are used to create a regular expression, so special characters should be escaped.
*
* @param dirs Array of strings which are used to create a regular expression to check if a directory is allowed.
*/
setForbiddenDirs(dirs) {
this.forbiddenDirs = this.forbiddenDirs.concat(dirs);
}
/**
* Checks if the given directory path is allowed to be used.
* The rules are as follows:
* - The path must not contain any of the strings from the `forbiddenDirs` array.
* - The path must not consist of more than 2 parts (i.e. it must not contain any dots).
*
* @param dp The directory path to check.
* @returns true if the directory path is allowed, false otherwise.
*/
isDirAllowed(dp) {
if (dp && dp.split(".").length <= 2) {
return true;
}
for (const a of this.forbiddenDirs) {
if (dp.search(new RegExp(a, "g")) != -1) {
return false;
}
}
return true;
}
/**
* Get all states that match the given string.
* The string is used as a regular expression to filter the state paths.
* The result is an object where the keys are the state paths and the values are the state values.
*
* @param str The string to filter the states with.
* @returns An object with the state paths as keys and the state values as values.
*/
getStates(str) {
const result = {};
for (const dp in this.stateDataBase) {
if (dp.search(new RegExp(str, "g")) != -1) {
result[dp] = this.stateDataBase[dp];
}
}
return result;
}
/**
* Clean up the state data base by deleting all states that do not have a hold object in the given array
* and do not match any of the given filter strings.
* Additionally, the given deep parameter is used to slice the state path and delete the resulting object
* from the adapter object list.
*
* @param hold An array of strings which are used to determine if a state should be kept or deleted.
* @param filter An array of strings which are used to filter the states to be deleted. If a state's path
* starts with any of the strings in this array, it will not be deleted.
* @param deep The number of path parts to slice from the state path to delete from the adapter object list.
* @returns A promise that resolves when all states have been deleted.
*/
async cleanUpTree(hold, filter, deep) {
let del = [];
for (const dp in this.stateDataBase) {
if (filter && filter.filter((a) => dp.startsWith(a) || a.startsWith(dp)).length == 0) {
continue;
}
if (hold.filter((a) => dp.startsWith(a) || a.startsWith(dp)).length > 0) {
continue;
}
delete this.stateDataBase[dp];
del.push(dp.split(".").slice(0, deep).join("."));
}
del = del.filter((item, pos, arr) => {
return arr.indexOf(item) == pos;
});
for (const a of del) {
await this.adapter.delObjectAsync(a, { recursive: true });
this.log.debug(`Clean up tree delete: ${a}`);
}
}
/**
* Cleans a given string by replacing forbidden characters with underscores.
*
* @param string - The input string to be cleaned.
* @param lowerCase - If true, converts the string to lowercase after cleaning.
* @param removePoints - If true, removes all non-alphanumeric characters except underscores and hyphens.
* If false, retains periods as valid characters.
* @returns The cleaned string with forbidden characters replaced and optionally converted to lowercase.
*/
cleandp(string, lowerCase = false, removePoints = false) {
if (!string && typeof string != "string") {
return string;
}
string = string.replace(this.adapter.FORBIDDEN_CHARS, "_");
if (removePoints) {
string = string.replace(/[^0-9A-Za-z_-]/gu, "_");
} else {
string = string.replace(/[^0-9A-Za-z._-]/gu, "_");
}
return lowerCase ? string.toLowerCase() : string;
}
/**
* Convert a value to the given type
*
* @param value then value to convert
* @param type the target type
*/
convertToType(value, type) {
if (value === null) {
return null;
}
if (type === void 0) {
throw new Error("convertToType type undefined not allowed!");
}
if (value === void 0) {
value = "";
}
const old_type = typeof value;
let newValue = typeof value == "object" ? JSON.stringify(value) : value;
if (type !== old_type) {
switch (type) {
case "string":
newValue = value.toString() || "";
break;
case "number":
newValue = value ? Number(value) : 0;
break;
case "boolean":
newValue = !!value;
break;
case "array":
case "json":
break;
}
}
return newValue;
}
/**
* reads a datapoint from the state database
*
* @param dp the datapointname
* @returns the data of the datapoint
*/
readdp(dp) {
return this.stateDataBase[this.cleandp(dp)];
}
/**
* Stores a datapoint in the state database
*
* @param dp datapoint name
* @param type type of the datapoint
* @param val the value
* @param stateType the state type
* @param [ack] ack flag
* @param [ts] timestamp
* @param [init] init flag
* @returns the new state
*/
setdb(dp, type, val, stateType, ack = true, ts = Date.now(), init = false) {
this.stateDataBase[dp] = {
type,
stateTyp: stateType !== void 0 ? stateType : this.stateDataBase[dp] !== void 0 && this.stateDataBase[dp].stateTyp !== void 0 ? this.stateDataBase[dp].stateTyp : void 0,
val,
ack,
ts: ts ? ts : Date.now(),
init
};
return this.stateDataBase[dp];
}
/**
* Delete all objects in data array
*
* @param data array of ioBroker objects
* @returns Promise that resolves when all objects are deleted
*/
async memberDeleteAsync(data) {
for (const d of data) {
await d.delete();
}
}
/**
* Clones a ioBroker object.
*
* @param obj the object to clone
* @returns the cloned object
* @throws {Error} if target is not an object
*/
cloneObject(obj) {
if (typeof obj !== "object") {
this.log.error(`Error clone object target is type: ${typeof obj}`);
return obj;
}
return JSON.parse(JSON.stringify(obj));
}
/**
* Deep clones a generic object.
*
* @param obj the object to clone
* @returns the cloned object
*/
cloneGenericObject(obj) {
if (typeof obj !== "object") {
this.log.error(`Error clone object target is type: ${typeof obj}`);
return obj;
}
return JSON.parse(JSON.stringify(obj));
}
/**
* Checks if a file exists in the './admin/' directory.
*
* @param file - The name of the file to check.
* @returns True if the file exists, otherwise false.
*/
fileExistAsync(file) {
if (import_fs.default.existsSync(`./admin/${file}`)) {
return true;
}
return false;
}
/**
* Evaluate a jsonata command on given data.
*
* @param data The data which is used to evaluate the jsonata command.
* @param cmd The jsonata command or an object with multiple commands.
* @returns The result of the jsonata command. If the command is invalid or
* the jsonata command returns undefined, an empty string is returned. If
* an object with multiple commands is given, the result is an object with
* the same keys as the input object, but with the results of the jsonata
* commands as values. If an error occurs during evaluation, an error is
* logged and an empty string is returned for the corresponding key.
*/
/**
* Evaluates a jsonata command on given data.
*
* @param data The data which is used to evaluate the jsonata command.
* @param cmd The jsonata command or an object with multiple commands.
* @returns The result of the jsonata command. If the command is invalid or
* the jsonata command returns undefined, an empty string is returned. If
* an object with multiple commands is given, the result is an object with
* the same keys as the input object, but with the results of the jsonata
* commands as values. If an error occurs during evaluation, an error is
* logged and an empty string is returned for the corresponding key.
*/
async readWithJsonata(data, cmd) {
let result;
if (typeof cmd === "string") {
if (cmd == "") {
return "";
}
try {
result = await (0, import_jsonata.default)(cmd).evaluate(data);
if (result == void 0) {
return "";
}
} catch (error) {
this.log.error(error.message);
this.log.error(`The cmd: ${cmd} is invaild Message: ${error.message}.`);
}
} else {
result = {};
for (const k in cmd) {
if (cmd[k]) {
try {
result[k] = await (0, import_jsonata.default)(cmd[k]).evaluate(data);
} catch (error) {
this.log.error(error);
this.log.error(`The cmd: ${cmd[k]} for key ${k} is invaild.`);
}
}
}
}
return result;
}
/**
* Initialise the database with the states to prevent unnecessary creation and writing.
*
* @param states States that are to be read into the database during initialisation.
* @returns void
*/
async initStates(states) {
if (!states) {
return;
}
const removedChannels = [];
for (const state in states) {
const dp = state.replace(`${this.adapter.name}.${this.adapter.instance}.`, "");
const del = !this.isDirAllowed(dp);
if (!del) {
const obj = await this.adapter.getObjectAsync(dp);
if (!this.adapter.config.useJsonHistory && dp.endsWith(".warning.jsonHistory")) {
this.log.debug(`delete state: ${dp}`);
await this.adapter.delObjectAsync(dp);
continue;
}
this.setdb(
dp,
"state",
states[state] && states[state].val ? states[state].val : void 0,
obj && obj.common && obj.common.type ? obj.common.type : void 0,
states[state] && states[state].ack,
states[state] && states[state].ts ? states[state].ts : Date.now(),
true
);
} else {
if (!removedChannels.every((a) => !dp.startsWith(a))) {
continue;
}
const channel = dp.split(".").slice(0, 4).join(".");
removedChannels.push(channel);
await this.adapter.delObjectAsync(channel, { recursive: true });
this.log.debug(`Delete channel with dp:${channel}`);
}
}
}
/**
* Resets states that have not been updated in the database in offset time.
*
* @param prefix String with which states begin that are reset.
* @param offset Time in ms since last update.
* @returns void
*/
async garbageColleting(prefix, offset = 2e3) {
if (!prefix) {
return;
}
if (this.stateDataBase) {
for (const id in this.stateDataBase) {
if (id.startsWith(prefix)) {
const state = this.stateDataBase[id];
if (!state || state.val == void 0) {
continue;
}
if (state.ts < Date.now() - offset) {
let newVal;
switch (state.stateTyp) {
case "string":
if (typeof state.val == "string") {
if (state.val.startsWith("{") && state.val.endsWith("}")) {
newVal = "{}";
} else if (state.val.startsWith("[") && state.val.endsWith("]")) {
newVal = "[]";
} else {
newVal = "";
}
} else {
newVal = "";
}
break;
case "bigint":
case "number":
newVal = -1;
break;
case "boolean":
newVal = false;
break;
case "symbol":
case "object":
case "function":
newVal = null;
break;
case "undefined":
newVal = void 0;
break;
}
await this.writedp(id, newVal);
}
}
}
}
}
/**
* Get the local language as a string
* The language is determined from the admin settings and is 'en-En' if no language is set
*
* @returns The local language as a string
*/
getLocalLanguage() {
if (this.language) {
return this.language;
}
return "en-En";
}
/**
* Return the translation of the given key
* If no translation is found the key itself is returned
*
* @param key The key to translate
* @returns The translated string
*/
getTranslation(key) {
if (this.translation[key] !== void 0) {
return this.translation[key];
}
return key;
}
/**
* Checks if a translation exists for the given key.
*
* @param key The key to check for translation.
* @returns True if the translation exists, otherwise false.
*/
existTranslation(key) {
return this.translation[key] !== void 0;
}
/**
* Return the translation of the given key for all languages
* If no translation is found the key itself is returned
*
* @param key The key to translate
* @returns The translated string or a object with the translations for all languages
*/
async getTranslationObj(key) {
const language = [
"en",
"de",
"ru",
"pt",
"nl",
"fr",
"it",
"es",
"pl",
"uk",
"zh-cn"
];
const result = {};
for (const l of language) {
try {
const i = await Promise.resolve().then(() => __toESM(require(`../../admin/i18n/${l}/translations.json`)));
if (i[key] !== void 0) {
result[l] = i[key];
}
} catch {
return key;
}
}
if (result.en == void 0) {
return key;
}
return result;
}
/**
* Sets the language for all getTranslation and getTranslationObj calls.
* If the language does not exist, it will not be changed and an error message will be logged.
* If force is true, the language will be changed even if it is already set.
*
* @param language The language to set.
* @param force Set to true to force the language to be changed.
* @returns True if the language was changed, otherwise false.
*/
async setLanguage(language, force = false) {
if (!language) {
language = "en";
}
if (force || this.language != language) {
try {
this.translation = await Promise.resolve().then(() => __toESM(require(`../../admin/i18n/${language}/translations.json`)));
this.language = language;
return true;
} catch {
this.log.error(`Language ${language} not exist!`);
}
}
return false;
}
/**
* Sorts an array of strings in an alphabetical order, ignoring case.
*
* @param text The array of strings to sort.
* @returns The sorted array.
*/
sortText(text) {
text.sort((a, b) => {
const nameA = a.toUpperCase();
const nameB = b.toUpperCase();
if (nameA < nameB) {
return -1;
}
if (nameA > nameB) {
return 1;
}
return 0;
});
return text;
}
/**
*
* @param text string to replace a Date
* @param noti appendix to translation key
* @param day true = Mo, 12.05 - false = 12.05
* @returns Monday first March
*/
convertSpeakDate(text, noti = "", day = false) {
if (!text || typeof text !== `string`) {
return ``;
}
const b = text.split(`.`);
if (day) {
b[0] = b[0].split(" ")[2];
}
return ` ${`${(/* @__PURE__ */ new Date(`${b[1]}/${b[0]}/${(/* @__PURE__ */ new Date()).getFullYear()}`)).toLocaleString(this.language, {
weekday: day ? "long" : void 0,
day: "numeric",
month: `long`
})} `.replace(/([0-9]+\.)/gu, (x) => {
const result = this.getTranslation(x + noti);
if (result != x + noti) {
return result;
}
return this.getTranslation(x);
})}`;
}
}
async function sleep(time) {
return new Promise((resolve) => setTimeout(resolve, time));
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
BaseClass,
Library,
sleep
});
//# sourceMappingURL=library.js.map