/**
* Name:            scriptRecovery
* Zweck:           Wiederherstellen/Auflisten von Skripten aus verschiedenen Quellen
* Datum:           24.01.2023
* Autor:           @fastfoot
* Forum:           https://forum.iobroker.net/post/930558
* 
* Changelog:       24.01.2023 - verbessertes Error-Handling
*                  21.01.2023 - Windows/Mac kompatibel
*/
/*                                                        Einstellungen                                               */
// zusätzliche Meldungen, dafür unbedingt auch in der JS-Instanz den Debug-Modus aktivieren!!!
const dbg = false;
// hier wird die JSON Tabelle gespeichert, der DP wird erstellt wenn generateTable true ist
const idBase = "0_userdata.0.scriptRecovery";
const idJson = "jsonTable";
// erzeugt eine JSON Tabelle der gefundenen Skripte für eine evtl. VIS
const generateTable = true;
// erzeugt ein Listing der gefundenen Skripte(__Listingxxx.json)
const generateListing = true;
// Dateien ins Filesystem schreiben, false = brauchbar wenn man nur ein Listing will
const restoreToFilesystem = false;
// Extension für importierte Skripte, zur Unterscheidung von existierenden Skripten.
const scriptSuffix = "_rcvr";
// Skripte werden sofort ins System geladen(Endung: wie in scriptSuffix) ACHTUNG: AUF EIGENE GEFAHR!!!!
// existierende Skripte werden nicht überschrieben
const restoreToSystemDB = false;
// Array Skriptnamen-Filter, wenn vorhanden werden nur diese  Skripte behandelt z.B. ['script 1','script 2']
// Nur Teil-Namens sind erlaubt und Groß- Kleinschreibung ist egal
// const scriptFilter = ['^[abc]', '[xyz0-9]$', 'script'];// Skripte beginnen mit a,b oder c oder enden mit x,y,z oder einer Zahl oder haben <<script>> im Namen
const scriptFilter = [''];
// Array Inhaltsfilter z.B. ['0_userdata.0.example_state', 'Wetter'] zum Suchen nach bestimmten Begriffen
// const contentFilter = ['0_user', 'sql', 'sendto'];
const contentFilter = [''];
// Array - Skripte in diesen Ordnern werden nicht berücksichtigt(für rootFolder 'root' oder '/')
// const excludeFolders = ['/','global', 'löschen'];
const excludeFolders = [''];
// Array - Nur Skripte in diesen Ordnern und deren Unterordnern werden berücksichtigt(für NUR rootFolder 'root' oder '/')
// const includeFolders = ['/','tools', 'forum'];
const includeFolders = [''];
// Array Typ Filter um nur bestimmte Typen zu berücksichtigen (Blockly, Javascript, Rules, Typescript)
// const typeFilter = ['Blockly','javas'];// findet Blockly- oder Javascript Skripte
const typeFilter = [''];
// hier liegt die in inputFile definierte Datei, entweder Archiv oder bereits extrahierte Datei
let inputPath = "../../scriptInput";
// hier landen die extrahierten Skripte und die Listing-Datei, wird bei Skriptstart geleert und angelegt wenn nicht vorhanden
const outputPath = "../../scriptOutput";
// Datei mit den Skripten(autoObjects = letzte objects.json[l], autoBackup = letzte backupdatei, autoScripts = letztes Skript Backup)
let inputFile = "";
//                                                       Beispiele für mögliche Dateien
// letzte Dateien JS-Controller und BackitUp-Adapter
inputFile = "autoObjects";
// inputFile = "autoBackup";
// inputFile = "autoScripts";
// Backup von JS-Controller
// inputFile = '2023-01-23_13-30_objects.jsonl.gz';
// inputFile = "objects.jsonl";
// inputFile = "2022-12-19_12-18_objects.json.gz";
// inputFile = "objects.json";
// BackitUp-Adapter
// inputFile = "iobroker_2023_01_23-13_34_49_backupiobroker.tar.gz";
// inputFile = "backup.json";
// inputFile = "javascripts_2023_01_23-13_35_04_backupiobroker.tar.gz";
// inputFile = "script.json";
// Konsole: 'iobroker backup'
// inputFile = "2023_01_23-13_36_02_backupiobroker.tar.gz";
// JS-Adapter Export
// inputFile = "2023-01-24-scripts.zip";
// inputFile = "FullBackup-scripts_2023-01-11.zip";
// Einzelskript
// inputFile = 'sqlBlockly.js';
/* **************************************************************************************
* *******************                                                *******************
* *******************       Ab hier keine Änderungen vornehmen       *******************
* *******************                                                *******************
* **************************************************************************************
*/
// @ts-ignore
const fse = require("fs-extra");
const Path = require("path");
// @ts-ignore
const tar = require('tar');
// @ts-ignore
const JSZip = require('jszip');
const zlib = require('node:zlib');
const { pipeline } = require('node:stream');
// const os = require("os");
// hier werden die aus evtl. Archiven(tar.gz, gz, zip) extrahierten Skripte temporär abgelegt. Wird bei Skriptstart angelegt wenn nicht vorhanden
const tmpRoot = '../../scriptTmp';// os.tmpdir();
const tmpPath = Path.resolve(tmpRoot, scriptName.slice(10));
// Ignoriert Fehler in der JSONL Datenbank
const ignoreJsonlErrors = true;
// komprimiere JSONL Datenbank beim Einlesen
const compressJsonl = false;
// wichtig damit der mirrorPath nicht überschrieben wird und somit alle Skripte gelöscht werden
const mirrorPath = getObject("system.adapter.javascript.0").native.mirrorPath;
start();
async function start() {
   if (dbg) console.error('Debugmode aktiv, unbedingt auch in der JS-Instanz den Debug-Modus aktivieren!!!');
   fse.ensureDirSync(tmpPath);
   fse.emptyDirSync(tmpPath);
   fse.ensureDirSync(outputPath);
   if (outputPath != mirrorPath) {
       fse.emptyDirSync(outputPath);
   }
   try { await main(); }
   catch (e) { return console.error('Ein Fehler ist aufgetreten!') }
   stopScript(scriptName);
}
async function main() {
   let dataFile = '';
   let allScripts = {};
   switch (inputFile.toLocaleLowerCase()) {
       case 'autoobjects':
           inputPath = '../../iobroker-data/backup-objects';
           inputFile = await getNewestFile(inputPath, /objects\.jsonl\.gz/);//await getLatestObjects();
           break;
       case 'autobackup':
           inputPath = '../../backups';
           inputFile = await getNewestFile(inputPath, /^[2i.+\.tar\.gz]/);//await getLatestBackup();
           break;
       case 'autoscripts':
           inputPath = '../../backups';
           inputFile = await getNewestFile(inputPath, /^javascript.+\.tar\.gz/);//await getLatestScripts();
           break;
       default:
   }
   if (inputFile === '' || inputFile === undefined) {
       return console.error(`[main()] - Keine Datei in ${inputPath} gefunden!`);
   }
   if (!(await fse.pathExists(Path.resolve(inputPath, inputFile)))) {
       return console.error(`[main()] - Die Datei ${inputFile} in ${inputPath} wurde nicht gefunden!`);
   }
   const fullArchiveName = Path.resolve(inputPath, inputFile);
   if (inputFile.indexOf('.tar.gz') > 0) {
       dataFile = await tarExtract(fullArchiveName);
       if (dataFile.endsWith('backup.json')) {
           allScripts = await handleBackup(dataFile);
       } else if (dataFile.endsWith('script.json')) {
           allScripts = await handleJson(dataFile);
       }
   } else if (inputFile.endsWith('backup.json')) {
       allScripts = await handleBackup(fullArchiveName);
   } else if (inputFile.endsWith('script.json')) {
       allScripts = await handleJson(fullArchiveName);
   } else if (inputFile.indexOf('.jsonl.gz') > 0) {
       dataFile = await gzipExtract(fullArchiveName);
       allScripts = await handleJsonl(dataFile);
   } else if (inputFile.endsWith('objects.jsonl')) {
       allScripts = await handleJsonl(fullArchiveName);
   } else if (inputFile.indexOf('.json.gz') > 0) {
       dataFile = await gzipExtract(fullArchiveName);
       allScripts = await handleJson(dataFile);
   } else if (inputFile.endsWith('objects.json')) {
       allScripts = await handleJson(fullArchiveName);
   } else if (inputFile.indexOf('.zip') > 0) {
       dataFile = await zipExtract(fullArchiveName);
       if (dataFile)
           allScripts = await handleExport(dataFile);
       else
           return console.warn('Keine passenden Dateien gefunden, Filter prüfen!');
   } else if (inputFile) {
       try {
           let b = fse.lstatSync(fullArchiveName).isDirectory()
       } catch (e) {
           return console.error('[main()] - Fehler: ' + e);
       }
       allScripts = await handleExport(inputFile, inputPath);
   } else {
       return console.error('[main()] - Fehler: Variable inputFile falsch belegt')
   }
   if (dbg) {
       console.debug('[main()] - datafile = ' + (dataFile != '' ? dataFile : inputFile))
   };
   handleScripts(allScripts);
}
async function handleScripts(allScripts) {
   let res = {};
   let tableData = [];
   for (let key in allScripts) {
       let fileExtension = "";
       let script = allScripts[key];
       const keyNew = key + scriptSuffix;
       const folder = key.replace('script.js.', '').replace(script.common.name, '').replace(/\.$/, '');
       // exclude Filter
       if (isExcludedFolder(folder)) continue;
       // folder Filter
       if (!isIncludedFolder(folder)) continue;
       // script Filter
       if (!isName(script.common.name)) continue;
       // Typ Filter
       if (!isType(script.common.engineType)) continue;
       // Inhalts Filter
       if (!isContent(script.common.source)) continue;
       script.common.enabled = false;
       script.common.debug = false;
       script.common.verbose = false;
       script.common.expert = false;
       if (!existsObject(keyNew) && restoreToSystemDB) {
           if (dbg) console.debug('[handleSripts()] - Key: ' + key);
           const oldName = script.common.name;
           script.common.name += scriptSuffix;
           await createScriptFolder(keyNew.slice(0, keyNew.length - script.common.name.length - 1));
           await setObjectAsync(keyNew, { type: "script", common: script.common, native: {} });
           script.common.name = oldName;
       }
       if (restoreToFilesystem) {
           let data = script.common.source;
           switch (script.common.engineType.toLowerCase()) {
               case "blockly":
                   fileExtension = ".xml";
                   if (script.common.source.length)
                       data = handleBlockly(data);
                   else console.warn("217 Leeres Skript: " + script.common.name);
                   break;
               case "rules":
                   fileExtension = ".js";
                   break;
               case "javascript/js":
                   fileExtension = ".js";
                   break;
               case "typescript/ts":
                   fileExtension = ".ts";
                   break;
               default:
                   fileExtension = ".js";
           }
           if (data && data.length) {
               if (dbg) console.debug('[handleSripts()] - Key: ' + key);
               if (dbg) console.debug('[handleSripts()] - Pfad: ' + Path.resolve(outputPath, key.substring(10)));
               let scriptName = key.split('.').pop();
               let scriptPfad = key.slice(10, key.length - scriptName.length - 1).replace(/\./g, '/');
               scriptPfad = Path.resolve(outputPath, scriptPfad);
               fse.ensureDirSync(scriptPfad);
               fse.writeFile(
                   Path.resolve(scriptPfad, scriptName) + fileExtension,
                   data,
                   (e) => {
                       if (e) console.error("[handleSripts()] - Fehler beim Schreiben der Datei:" + e.code);
                   }
               );
           } else {
               if (dbg) console.debug('[handleSripts()] - No source data: ' + key);
           }
       }
       if (generateTable) {
           tableData = generateJsonTable(script, tableData);
       }
       if (dbg) console.debug('[handleSripts()] - Key: ' + key)
       res[key.substring(10)] = script.common.engineType;
   }
   if (generateTable) {
       if (!tableData.length) {
           tableData.push({ Warnung: 'Keine Daten vorhanden, Filter prüfen!' });
       } else {
           tableData.sort((a, b) => a.Name.localeCompare(b.Name));
           let no = 0;
           tableData.forEach(rec => rec.Nr = ++no);
       }
       if (await createDatapoints(idBase)) {
           setState(`${idBase}.${idJson}`, JSON.stringify(tableData));
       }
   }
   if (generateListing) {
       let b = {};
       if (Object.keys(res).length) {
           // sortieren
           let a = Object.keys(res).sort();
           for (let i = 0; i < a.length; i++) {
               b[i + 1 + " " + a[i]] = res[a[i]];
           }
       } else {
           b.Warnung = 'Keine Daten vorhanden, Filter prüfen!';
       }
       const fullFileName = Path.resolve(outputPath, '__Listing_' + inputFile + ".json");
       fse.writeFile(fullFileName, JSON.stringify(b, null, 3), (e) => {
           if (e) console.error("[handleSripts()] - Schreibfehler bei Ergebnisdatei");
       });
   }
}
/* extrahiere Skripte aus iobroker Datenbank (neueste Version JSONL)*/
async function handleJsonl(dataFile) {
   const allScripts = {};
   // @ts-ignore
   const DB = require("@alcalzone/jsonl-db").JsonlDB;
   const dbOptions = {
       autoCompress: { onOpen: compressJsonl },
       ignoreReadErrors: ignoreJsonlErrors,
   };
   const db = new DB(dataFile, dbOptions);
   try {
       await db.open();
   } catch (e) {
       console.error(`[handleJsonl()] - Fehler beim Öffnen der Datenbank ${dataFile} in ${inputPath}` + e);
   }
   db.forEach((obj, key) => {
       if (obj.type === "script") {
           allScripts[key] = obj;
       }
   });
   await db.close();
   return allScripts;
}
/* extrahiere Skripte aus iobroker Datenbank (ältere Version JSON)*/
async function handleJson(dataFile) {
   let allData = '';
   const allScripts = [];
   let allObjects = new Object();
   try {
       allData = fse.readFileSync(dataFile, "utf8");
   } catch (e) {
       console.error(`[handleJson()] - Fehler beim Lesen von ${inputFile} in ${inputPath}: ` + e);
   }
   try {
       allObjects = JSON.parse(allData);
   } catch (e) {
       console.error("[handleJson()] - Fehlerhafte Daten: ==> " + e);
   }
   for (let prop in allObjects) {
       const obj = allObjects[prop];
       if (obj.type === "script") {
           allScripts[prop] = obj;
       }
   }
   return allScripts;
}
/* extrahiere Skripte aus backup.json des Backitup-Adapers */
async function handleBackup(dataFile) {
   let allData = '';
   const allScripts = {};
   let allObjects = [];
   try {
       allData = fse.readFileSync(dataFile, "utf8");
   } catch (e) {
       console.error(`[handleBackup()] - Fehler beim Lesen von ${inputFile} in ${inputPath}: ` + e);
   }
   try {
       allObjects = JSON.parse(allData).objects;
   } catch (e) {
       console.error('[handleBackup()] - Fehlerhafte Daten: ==> ' + e);
   }
   for (let obj of allObjects) {
       if (obj.value.type === 'script') {
           allScripts[obj.id] = obj.value;
       }
   }
   return allScripts;
}
/* bearbeite Skripte */
async function handleExport(scriptListx, dir = tmpPath) {
   const allScripts = {};
   const scriptList = scriptListx.trim().split(' ');
   let fileObj = {};
   let sourceData = '';
   scriptList.forEach((file) => {
       const scriptObj = {
           "_id": "script.js.",
           "common": {
               "name": "",
               "engineType": "JavaScript/js",
               "engine": "system.adapter.javascript.0",
               "source": "",
               "enabled": false,
               "debug": false,
               "verbose": false
           },
           "type": "script",
           "native": {},
           "ts": 0,
       }
       const scriptNam = getFileName(file);
       if (dbg) console.debug('[handleExport()] - File: ' + file);
       if (dbg) console.debug('[handleExport()] - Name: ' + scriptNam);
       let scriptData;
       scriptData = fse.readFileSync(Path.resolve(dir, file), 'utf8');
       const regExport = new RegExp(/\/\* -- do not edit/);
       if (regExport.test(scriptData)) {
           const regObj = new RegExp(/(\{.+\})(?:\n-- do not edit prev)/s);
           sourceData = scriptData.substring(scriptData.indexOf('END --*/') + 9);
           if (regObj.test(scriptData)) {
               try {
                   fileObj = JSON.parse(regObj.exec(scriptData)[1]) || '{}';
               } catch (e) {
                   console.error('[handleExport()] - Fehler');
               }
           }
       } else {
           sourceData = scriptData;
       }
       const engineType = getEngineType(sourceData);
       if (dbg) console.debug('[handleExport()] - ' + engineType);
       if (dbg) console.debug('[handleExport()] - Type: ' + engineType);
       if (dbg) console.debug('[handleExport()] - File: ' + file);
       if (dbg) console.debug('[handleExport()] - Id: ' + file.replace(/\//g, '.').replace(/.json$/, ''));
       scriptObj._id = `script.js.${file.replace(/\//g, '.').replace(/.json$|.js$/, '')}`;
       scriptObj.ts = fileObj.ts || Date.now();
       scriptObj.common.name = scriptNam;
       scriptObj.common.source = sourceData;
       scriptObj.common.engineType = fileObj.engineType || engineType;
       scriptObj.common.engine = fileObj.engine || 'system.adapter.javascript.0';
       scriptObj.common.enabled = fileObj.enabled || false;
       scriptObj.common.debug = fileObj.debug || false;
       scriptObj.common.verbose = fileObj.verbose || false;
       if (fileObj && fileObj.engineType && fileObj.engineType.toLowerCase() === 'typescript/ts') {
           scriptObj.common.sourceHash = fileObj.sourceHash;
           scriptObj.common.compiled = fileObj.compiled;
       }
       allScripts[scriptObj._id] = scriptObj;
   })
   return allScripts;
}
function generateJsonTable(scriptData, tableData) {
   const data = scriptData.common.source;
   const dt = scriptData.ts && new Date(scriptData.ts) || 0;
   let Zweck = '-',
       Autor = '-',
       Datum = dt && dt.getFullYear() + '-' + dt.getMonth() + 1 + '-' + dt.getDate() || '-',
       Instance = scriptData.common.engine.split('.').pop(),
       sName = scriptData.common.name
   if (/(Zweck|Purpose):\s+(.*)/.test(data)) {
       if (dbg) console.debug('[generateJsonTable()] - Zweck: ' + /(Zweck|Purpose):\s+(.*)/.exec(data))
       Zweck = /(Zweck|Purpose):\s+(.*)/.exec(data)[2];
   }
   if (/(Autor|Author):\s+(.*)/.test(data)) {
       Autor = /(Autor|Author):\s+(.*)/.exec(data)[2];
   }
   if (/(Datum|Date):\s+(.*)/.test(data)) {
       Datum = /(Datum|Date):\s+(.*)/.exec(data)[2].replace(/(\d+).(\d+).(\d+)/, "$3-$2-$1");
   }
   let p = scriptData._id.lastIndexOf('.');
   let ps = '';
   if (p === 9) ps = '/';
   else ps = '/' + scriptData._id.slice(10, p);
   tableData.push({
       Nr: 0,
       Name: sName,
       Pfad: ps,
       Zweck: Zweck,
       Autor: Autor,
       Datum: Datum,
       Instanz: Instance,
       Typ: scriptData.common.engineType.split('/')[0]
   })
   return tableData;
}
function isName(name) {
   if (dbg) console.debug('[isName()] - Name: ' + name);
   const regExp = new RegExp(scriptFilter.slice(0).join("|"), "i");
   return regExp.test(name);
}
function isExcludedFolder(folder) {
   if (folder === '') folder = 'root/';
   const filter = excludeFolders.slice(0).join("|") || '&&&';
   if (dbg) console.debug('[isExcludedFolder()] -  Folder: ' + folder);
   if (dbg) console.debug('[isExcludedFolder()] - Filter: ' + filter);
   const regExp = new RegExp(filter, "i");
   return regExp.test(folder);
}
function isIncludedFolder(folder) {
   if (folder === '') folder = 'root/';
   const filter = includeFolders.slice(0).join("|");
   if (dbg) console.debug('[isIncludedFolder()] - Folder: ' + folder);
   if (dbg) console.debug('[isIncludedFolder()] - Filter: ' + filter);
   const regExp = new RegExp(filter, "i");
   return regExp.test(folder);
}
function isType(typ) {
   const regExp = new RegExp(typeFilter.slice(0).join("|"), "i");
   return regExp.test(typ);
}
function isContent(source) {
   const regExp = new RegExp(contentFilter.slice(0).join("|"), "i");
   return regExp.test(source);
}
function handleBlockly(source) {
   const pos = source.lastIndexOf("\n");
   if (pos !== -1) {
       source = source.substring(pos + 3);
       if (source.indexOf("JTNDeG1sJTIweG1") > -1) {
           source = decodeURIComponent(
               Buffer.from(source, "base64").toString("utf8")
           );
           return prettifyXml(source);
       }
   } else return prettifyXml(source);
   //from Stackoverflow
   function prettifyXml(xml) {
       var reg = /(>)\s*(<)(\/*)/g;
       xml = xml.replace(/\r|\n/g, ''); //deleting already existing whitespaces
       xml = xml.replace(reg, "$1\r\n$2$3");
       return xml;
   }
}
const tarList = async file => {
   const filenames = []
   await tar.list({
       file,
       onentry: entry => {
           if (/script.json|backup.json/.test(entry.path)) {
               filenames.push(entry.path)
           }
       },
   })
   return filenames
}
const tarExtract = async archive => {
   const fileList = await tarList(archive);
   const strip = fileList.includes('backup/backup.json') ? 1 : 0;
   if (dbg) console.debug(strip + '   ' + fileList);
   const opts = {
       file: archive,
       strip,
       cwd: tmpPath
   }
   await tar.extract(opts, fileList);
   return Path.resolve(tmpPath, fileList[0].replace('backup/', ''));
}
const zipExtract = async archive => {
   return new Promise(async (resolve, reject) => {
       let fileList = [];
       const data = fse.readFileSync(archive)
       const zip = new JSZip();
       const zipObj = await zip.loadAsync(data);
       zipObj.filter((a, b) => false)
       zipObj.folder().forEach(async (fileName, entry) => {
           const fullPath = Path.resolve(tmpPath, fileName);
           const pos = fullPath.lastIndexOf(Path.sep) + 1;
           const realName = fullPath.substring(pos);
           const realPath = fullPath.substring(0, pos - 1);
           if (!entry.dir && isName(realName) && realName.indexOf('_dir.json') < 0) {
               if (dbg) console.debug(realPath + '  = ' + realName);
               fse.ensureDirSync(Path.resolve(tmpPath, realPath));
               fse.writeFileSync(fullPath, Buffer.from(entry._data.compressedContent));
               fileList.push(fileName);
           }
       })
       resolve(fileList.join(' '));
   })
}
const gzipExtract = async archive => {
   return new Promise(async (resolve, reject) => {
       const objectsFile = archive.split(Path.sep).pop().replace('.gz', '').substring(17);
       const gunzip = zlib.createGunzip();
       const readStream = fse.createReadStream(archive);
       const writeStream = fse.createWriteStream(Path.resolve(tmpPath, objectsFile));
       readStream.on('error', e => {
           reject(console.error(e));
       })
       writeStream.on('error', e => {
           reject(console.error(e));
       })
       pipeline(readStream, gunzip, writeStream, e => {
           if (e) reject(console.error(e));
           resolve(Path.resolve(tmpPath, objectsFile));
       })
   })
}
function getFileName(fullName) {
   return fullName.substring(fullName.lastIndexOf('/') + 1, fullName.lastIndexOf('.'));
}
function isBlockly(sourceData) {
   const pos = sourceData.lastIndexOf('\n');
   const testData = sourceData.substring(pos + 1);
   return /^[\/][\/]JTNDeG1sJTIwe/.test(testData);
}
function isRules(sourceData) {
   const pos = sourceData.lastIndexOf('\n');
   const testData = sourceData.substring(pos + 1);
   return /[/][/]{"triggers":/.test(testData);
}
function isTypescript(sourceData) {
   return /(let|const|var)\s*[a-zA-Z0-9]+:*(string|object|number)/.test(sourceData);
}
function getEngineType(sourceData) {
   return isBlockly(sourceData) ? 'Blockly'
       : isRules(sourceData) ? 'Rules'
           : isTypescript(sourceData) ? 'Typescript/ts'
               : 'Javascript/js';
}
async function getNewestFile(dir, filter) {
   return new Promise((resolve, reject) => {
       return fse.readdir(dir, (e, f) => {
           if (e) reject(e);
           const s = f.map(f => {
               let t;
               const ff = Path.resolve(dir, f);
               try {
                   t = fse.lstatSync(ff).mtime.getTime();
               } catch (e) {
                   reject(e)
               }
               return { name: f, time: t }
           })
               .sort((a, b) => b.time - a.time)
               .map(v => v.name)
               .filter(v => filter.test(v));
           resolve(s[0]);
       })
   })
}
// create data points if not existing
async function createDatapoints(idBase) {
   let dp,
       idKey,
       firstRun = false;
   if (!idBase.startsWith('0_userdata.0')) return firstRun;
   firstRun = true;
   const stateAttributes = {};
   stateAttributes[idJson] = { "name": "Skripte Info", "type": "json", "role": "", "read": true, "write": true, "desc": "enthält Skript Tabelle", "def": "" }
   //createScriptFolder(idBase);
   for (let key in stateAttributes) {
       idKey = idBase + '.' + key;
       if (!(await existsStateAsync(idKey))) {
           dp = stateAttributes[key];
           firstRun = true;
           let e = await createStateAsync(idKey, dp);
           if (e) console.error('[createDatapoints()] - createState: ' + e);
       }
   }
   return firstRun;
}
async function createScriptFolder(id) {
   const arr = id.split('.');
   const preId = arr[0] + '.' + arr[1];
   if (preId.length === id.length || (preId != 'script.js' /*&& preId != '0_userdata.0'*/)) return;
   const idNew = id.replace(/[\s"]/g, '_');
   if (!(await existsObjectAsync(idNew))) {
       const obj = new Object({
           "type": "folder",
           "common": {
               name: arr[arr.length - 1]
           },
           "native": {},
       })
       await setObjectAsync(idNew, obj);
   }
   arr.pop();
   id = arr.join('.');
   await createScriptFolder(id);
}
onStop(() => {
   fse.removeSync(tmpPath);
   if (dbg) console.log('[onStop()] - Skript wurde nach Beendigung automatisch gestoppt!');
})