From 27fedd5bfc2de0cf932addb63290e32cdd00f195 Mon Sep 17 00:00:00 2001 From: foxthefox <16841643+foxthefox@users.noreply.github.com> Date: Sat, 11 Feb 2023 22:40:58 +0100 Subject: [PATCH 1/3] 1.0.2 --- README.md | 4 +- lib/data/getriggerlistinfos.xml | 5 + lib/data/test_api_response.xml | 33 ++++- lib/fritz_ahaapi.js | 52 +++++++- lib/fritz_mockserver.js | 12 +- lib/xml2json.js | 208 ++++++++++++++++++++++++++++++++ package-lock.json | 5 - package.json | 9 +- 8 files changed, 308 insertions(+), 20 deletions(-) create mode 100644 lib/data/getriggerlistinfos.xml create mode 100644 lib/xml2json.js diff --git a/README.md b/README.md index 913e735..75747da 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,7 @@ const logout = await fritz.logout_SID(); see the example.js. ## API Calls -* todo for 1.0.2 +* todo for 1.0.3 ## Changelog ### 1.0.2 @@ -51,7 +51,7 @@ see the example.js. * (foxthefox) implementation of new commands from API version 1.57 ### 1.0.1 -* skipping usage of chalk, figlet +* (foxthefox) skipping usage of chalk, figlet ### 1.0.0 * (foxthefox) common js module with 2 named exports Fritz and FritzEmu diff --git a/lib/data/getriggerlistinfos.xml b/lib/data/getriggerlistinfos.xml new file mode 100644 index 0000000..fdd48b2 --- /dev/null +++ b/lib/data/getriggerlistinfos.xml @@ -0,0 +1,5 @@ + + + Trigger AlertOn + + \ No newline at end of file diff --git a/lib/data/test_api_response.xml b/lib/data/test_api_response.xml index 107f229..66bafd4 100644 --- a/lib/data/test_api_response.xml +++ b/lib/data/test_api_response.xml @@ -660,4 +660,35 @@ 1617368283 - + + 11934 0318081 + 407 + 1 + 31.20 + 0x0feb + HAN-FUN + 1 + 0 + HAN-FUN #2 + + + 11934 0318081-1 + 2001 + 8208 + 0.0 + 0x0feb + HAN-FUN + 1 + 0 + HAN-FUN #2 + + 407 + 514 + 256 + + + 0 + 1670596133 + + + \ No newline at end of file diff --git a/lib/fritz_ahaapi.js b/lib/fritz_ahaapi.js index 24f1bcd..b1df123 100644 --- a/lib/fritz_ahaapi.js +++ b/lib/fritz_ahaapi.js @@ -67,6 +67,16 @@ class Fritz { } } + // get trigger information (XML) + async getTriggerListInfos() { + try { + const body = this.executeCommand2('gettriggerlistinfos', '', 1); + return Promise.resolve(body); + } catch (error) { + return Promise.reject(error); + } + } + // get basic device stats (XML) /** * other generic functions @@ -250,11 +260,12 @@ class Fritz { } } - // set color hue or saturation + // set color hue or saturation, limited to the colordefaults /** * @param {string} ain * @param {string | number | boolean | null} saturation * @param {string | number | boolean | null} hue + * duration is not supportet */ async setColor(ain, saturation, hue) { try { @@ -269,6 +280,40 @@ class Fritz { } } + // set color hue or saturation, free in the HSV-color schema + /** + * @param {string} ain + * @param {string | number | boolean | null} saturation + * @param {string | number | boolean | null} hue + * duration is not supportet + */ + async setUnmappedColor(ain, saturation, hue) { + try { + const body = await this.executeCommand2( + 'setunmappedcolor&saturation=' + saturation + '&hue=' + hue + '&duration=0', + ain, + 1 + ); + return Promise.resolve('OK'); + } catch (error) { + return Promise.reject(error); + } + } + + // set trigger active + /** + * @param {string} ain + * @param {string | number | boolean} active + */ + async setTriggerActive(ain, active) { + try { + const val = active === 1 || active === '1' || active === true ? '1' : '0'; + const body = await this.executeCommand2('settriggeractive¶m=' + val, ain, 1); + return Promise.resolve(active); + } catch (error) { + return Promise.reject(error); + } + } // apply template /** * @param {string} ain @@ -496,7 +541,8 @@ class Fritz { path: LOGIN_SID_ROUTE, method: 'GET', headers: { - 'Content-Type': 'application/x-www-form-urlencoded' + 'Content-Type': 'application/x-www-form-urlencoded', + 'User-Agent': 'fritzdect_aha_nodejs/1.0.0' }, rejectUnauthorized: false }; @@ -521,6 +567,7 @@ class Fritz { // resolve on end res.on('end', () => { if (responseBody) { + // login probleme abfangen issue#377 fritzdect const challenge = responseBody.match('(.*?)')[1]; const blocktime = Math.floor(responseBody.match('(.*?)')[1]); const pbkf2 = challenge.startsWith('2$') ? true : false; @@ -634,6 +681,7 @@ class Fritz { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', + 'User-Agent': 'fritzdect_aha_nodejs/1.0.0', 'Content-Length': Buffer.byteLength(xFormBody) }, rejectUnauthorized: false diff --git a/lib/fritz_mockserver.js b/lib/fritz_mockserver.js index d4788a4..d93734f 100644 --- a/lib/fritz_mockserver.js +++ b/lib/fritz_mockserver.js @@ -3,7 +3,7 @@ const http = require('http'); const fs = require('fs'); const { parse } = require('querystring'); -const parser = require('xml2json-light'); +const parser = require('./xml2json.js'); const crypto = require('crypto'); const path = require('path'); @@ -11,8 +11,9 @@ console.log('PATH ist ' + path.join(__dirname, './data/')); const xmlDevicesGroups = fs.readFileSync(path.join(__dirname, './data/') + 'test_api_response.xml'); //var xmlDevicesGroups = fs.readFileSync('./test.xml'); -const xmlTemplate = fs.readFileSync(path.join(__dirname, './data/') + 'template_answer.xml'); +const xmlTemplate = fs.readFileSync(path.join(__dirname, './data/') + 'template_new_fail.xml'); const xmlTempStat = fs.readFileSync(path.join(__dirname, './data/') + 'devicestat_temp_answer.xml'); +const xmlTriggerlist = fs.readFileSync(path.join(__dirname, './data/') + 'getriggerlistinfos.xml'); const xmlPowerStats = fs.readFileSync(path.join(__dirname, './data/') + 'devicestat_power_answer.xml'); const xmlColorDefaults = fs.readFileSync(path.join(__dirname, './data/') + 'color_defaults.xml'); const hkr_batt = fs.readFileSync(path.join(__dirname, './data/') + 'hkr_response.xml'); @@ -706,6 +707,11 @@ function homeautoswitchAnswer( case 'setmetadata': break; case 'gettriggerlistinfo': + // todo empty templates + response.writeHead(200, { 'xmlDevicesGroups-Type': 'application/json' }); + response.write(String(xmlTriggerlist)); + response.end(); + return response; break; case 'settriggeractive': break; @@ -756,7 +762,6 @@ class FritzEmu { this.debugmode = debugmode; this.deviceresponse = null; } - // S7WebAPI setupHttpServer(callback) { console.log('\x1b[33m', ' _____ ____ _ _ _ _ '); console.log('\x1b[33m', ' | ___| __ ) / \\ | | | | / \\ '); @@ -876,6 +881,7 @@ class FritzEmu { break; } } + console.log('\x1b[0m'); if (reqstring[0] == '/login_sid.lua') { if (version == 2) { diff --git a/lib/xml2json.js b/lib/xml2json.js new file mode 100644 index 0000000..852ea4f --- /dev/null +++ b/lib/xml2json.js @@ -0,0 +1,208 @@ +/* +copy of https://github.com/enkidoo-ai/xml2json +The MIT License (MIT) +Copyright (c) 2016 Société Enkidoo Technologies Inc. + +extended by corrections for +- tag names also in subhierarchy causing maximum stack trace fault +- tags with 2digit length +- only one clean + +all part of PR #8 +*/ + +'use strict'; + +module.exports = { + xml2json: xml2json +}; + +//*********************************************************************** +// Main function. Clears the given xml and then starts the recursion +//*********************************************************************** +function xml2json(xmlStr) { + xmlStr = cleanXML(xmlStr); + return xml2jsonRecurse(xmlStr); +} + +//*********************************************************************** +// Recursive function that creates a JSON object with a given XML string. +//*********************************************************************** +function xml2jsonRecurse(xmlStr) { + var obj = {}, + tagName, + indexClosingTag, + inner_substring, + tempVal, + openingTag; + + while (xmlStr.match(/<[^\/][^>]*>/)) { + openingTag = xmlStr.match(/<[^\/][^>]*>/)[0]; + tagName = openingTag.substring(1, openingTag.length - 1); + indexClosingTag = xmlStr.indexOf(openingTag.replace('<', ']*>/)) { + //no need for cleanXML again + //tempVal = xml2json(inner_substring); + tempVal = xml2jsonRecurse(inner_substring); + } else { + tempVal = inner_substring; + } + // account for array or obj // + if (obj[tagName] === undefined) { + obj[tagName] = tempVal; + } else if (Array.isArray(obj[tagName])) { + obj[tagName].push(tempVal); + } else { + obj[tagName] = [ obj[tagName], tempVal ]; + } + + xmlStr = xmlStr.substring(openingTag.length * 2 + 1 + inner_substring.length); + } + + return obj; +} + +//***************************************************************** +// Removes some characters that would break the recursive function. +//***************************************************************** +function cleanXML(xmlStr) { + xmlStr = xmlStr.replace(//g, ''); //remove commented lines + xmlStr = xmlStr.replace(/\n|\t|\r/g, ''); //replace special characters + xmlStr = xmlStr.replace(/ {1,}<|\t{1,} {1,}|>\t{1,}/g, '>'); //replace trailing spaces and tabs + xmlStr = xmlStr.replace(/<\?[^>]*\?>/g, ''); //delete docType tags + xmlStr = replaceSelfClosingTags(xmlStr); //replace self closing tags + xmlStr = replaceAloneValues(xmlStr); //replace the alone tags values + xmlStr = replaceAttributes(xmlStr); //replace attributes + return xmlStr; +} + +//************************************************************************************************************ +// Replaces all the self closing tags with attributes with another tag containing its attribute as a property. +// The function works if the tag contains multiple attributes. +// +// Example : '' becomes +// 'attrValue' +//************************************************************************************************************ +function replaceSelfClosingTags(xmlStr) { + var selfClosingTags = xmlStr.match(/<[^/][^>]*\/>/g); + if (selfClosingTags) { + for (var i = 0; i < selfClosingTags.length; i++) { + var oldTag = selfClosingTags[i]; + var tempTag = oldTag.substring(0, oldTag.length - 2); + tempTag += '>'; + + var tagName = oldTag.match(/[^<][\w+$]*/)[0]; + var closingTag = ''; + var newTag = '<' + tagName + '>'; + + //beobachten was mit einstelligen Attributwerten passiert + var attrs = tempTag.match(/(\S+)=["']?((?:.(?!["']?\s+(?:\S+)=|[>"']))+.)["']?/g); + + if (attrs) { + for (var j = 0; j < attrs.length; j++) { + var attr = attrs[j]; + var attrName = attr.substring(0, attr.indexOf('=')); + var attrValue = attr.substring(attr.indexOf('"') + 1, attr.lastIndexOf('"')); + + newTag += '<' + attrName + '>' + attrValue + ''; + } + } + + newTag += closingTag; + xmlStr = xmlStr.replace(oldTag, newTag); + } + } + + return xmlStr; +} + +//************************************************************************************************* +// Replaces all the tags with attributes and a value with a new tag. +// +// Example : 'tagValue' becomes +// 'attrValue<_@attribute>tagValue' +//************************************************************************************************* +function replaceAloneValues(xmlStr) { + var tagsWithAttributesAndValue = xmlStr.match(/<[^\/][^>][^<]+\s+.[^<]+[=][^<]+>{1}([^<]+)/g); + + if (tagsWithAttributesAndValue) { + for (var i = 0; i < tagsWithAttributesAndValue.length; i++) { + var oldTag = tagsWithAttributesAndValue[i]; + var oldTagName = oldTag.substring(0, oldTag.indexOf('>') + 1); + var oldTagValue = oldTag.substring(oldTag.indexOf('>') + 1); + + var newTag = oldTagName + '<_@attribute>' + oldTagValue + ''; + xmlStr = xmlStr.replace(oldTag, newTag); + } + } + + return xmlStr; +} + +//***************************************************************************************************************** +// Replaces all the tags with attributes with another tag containing its attribute as a property. +// The function works if the tag contains multiple attributes. +// +// Example : '' becomes 'attrValue' +//***************************************************************************************************************** +function replaceAttributes(xmlStr) { + // the following line doesnt catch 2 digit tags + //var tagsWithAttributes = xmlStr.match(/<[^\/][^>][^<]+\s+.[^<]+[=][^<]+>/g); + // 2 digits tags are catched + var tagsWithAttributes = xmlStr.match(/<[^>][^<]+\s+.[^<]+[=][^<]+>/g); + if (tagsWithAttributes) { + for (var i = 0; i < tagsWithAttributes.length; i++) { + var oldTag = tagsWithAttributes[i]; + var tagName = oldTag.match(/[^<][\w+$]*/)[0]; + var newTag = '<' + tagName + '>'; + var attrs = oldTag.match(/(\S+)=["']?((?:.(?!["']?\s+(?:\S+)=|[>"']))+.)["']?/g); + + if (attrs) { + for (var j = 0; j < attrs.length; j++) { + var attr = attrs[j]; + var attrName = attr.substring(0, attr.indexOf('=')); + var attrValue = attr.substring(attr.indexOf('"') + 1, attr.lastIndexOf('"')); + + newTag += '<' + attrName + '>' + attrValue + ''; + } + } + xmlStr = xmlStr.replace(oldTag, newTag); + } + } + + return xmlStr; +} diff --git a/package-lock.json b/package-lock.json index aae3ca2..d1bdce9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1377,11 +1377,6 @@ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "dev": true }, - "xml2json-light": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/xml2json-light/-/xml2json-light-1.0.6.tgz", - "integrity": "sha512-6CSibpteBS4B8/fzJaj6TDtWatIlonSFfVVK3TLM23mlTOxkMgVA4b2FaGeTIrrhOMdDZ8X1/dvo4mfBtsU4yw==" - }, "y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", diff --git a/package.json b/package.json index 01e34d1..2447205 100644 --- a/package.json +++ b/package.json @@ -1,10 +1,9 @@ { "name": "fritzdect-aha-nodejs", - "version": "1.0.1", + "version": "1.0.2", "description": "NodeJS library using the AHA api of Fritzbox to control DECT smarthome devices.", "main": "index.js", "dependencies": { - "xml2json-light": "^1.0.6", "command-line-args": "^5.2.0", "command-line-usage": "^6.1.1" }, @@ -21,11 +20,7 @@ "type": "git", "url": "git+https://github.com/foxthefox/fritzdect-aha-nodejs.git" }, - "keywords": [ - "DECT", - "Fritzbox", - "AHA-api" - ], + "keywords": [ "DECT", "Fritzbox", "AHA-api" ], "author": "foxthefox@wysiwis.net", "license": "MIT", "bugs": { From 49cdc771b95d8f3f34d5fd19a8f45f29a4ab7a8e Mon Sep 17 00:00:00 2001 From: foxthefox <16841643+foxthefox@users.noreply.github.com> Date: Sat, 11 Feb 2023 22:42:19 +0100 Subject: [PATCH 2/3] 1.0.2 --- lib/data/template_new_fail.xml | 49 ++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 lib/data/template_new_fail.xml diff --git a/lib/data/template_new_fail.xml b/lib/data/template_new_fail.xml new file mode 100644 index 0000000..63f16e1 --- /dev/null +++ b/lib/data/template_new_fail.xml @@ -0,0 +1,49 @@ + + +