├── .github └── dependabot.yml ├── .gitignore ├── .travis.yml ├── .vscode └── launch.json ├── LICENSE ├── README.md ├── calllist.html ├── calllist.js ├── callmonitor.html ├── callmonitor.js ├── config.html ├── config.js ├── contact.html ├── contact.js ├── examples ├── callmonitor.json ├── callmonitor.png ├── presence.json └── presence.png ├── fritz.html ├── fritz.js ├── icons └── fritz.png ├── package-lock.json └── package.json /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "npm" # See documentation for possible values 9 | directory: "/" # Location of package manifests 10 | schedule: 11 | interval: "daily" 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # node-waf configuration 20 | .lock-wscript 21 | 22 | # Compiled binary addons (http://nodejs.org/api/addons.html) 23 | build/Release 24 | 25 | # Dependency directory 26 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 27 | node_modules 28 | 29 | /data/* 30 | !/data/settings.js 31 | !/data/flows.json -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | deploy: 3 | provider: npm 4 | email: "webmaster@mapero.de" 5 | api_key: 6 | secure: "Qf52BAHkMoYdub7CUx3wC0gSoQO/4ZuidSWVDiWCxVdYjsAu6t+UOG4+Ctlq7YLXYoIsthD0X/QEsmZFrRdfUWMkHOVfxR32tdeJKpI4l/4Lrwy4YOddUBGbXu0yEHDInX47esCRM9cT+CQxMiksgNlNLcShBquY0dcLFENBzbIXGgXm1zuFQQVXzYybZGXk3DaCPzwy0a8jezLq6Nx0SbWyV7yaEqo2h7hsiEmRr2Lff0LiIeVwUnKxRz8Xq1ZL7zIUSQFShGwCaBTNU5mBabWcgfSaloQWJX8ziLHm7iTgpKUM8+xlD8eHYQ3Byf4Q3u9I8XI1NZ7bUZ13/cpzd2BBWku5Osz+RmO3fleC/+mJrzuf2CKmsrFQCd0gwY61nSeph6VdvCKEjJEnm6uOuHb7mpKm98cY7oS9UWUwK/nJb7k717y32UWesSmn+Wu7/UrGq6iN3+V/wPZ1co+EDF2u8oSuwmCA74j53Xgd1dO8SHeVtR01uQE7GhgEyGf4Zd6mMeEk7q4ElVrDM7qizVzYCZH7Ynd8fKiZrHTk9t/B+Ierob8YmuHid7rkOMAWAKYV/WcRYKoAN7ynlHzMoYY2cDP1+bg2MPqQYY8w0DKUKBXiTw1zTVi67YrsyD7T18qIu07c3/a966vpP/d9xKjyqahOCY03tG+D7TGSveE=" 7 | on: 8 | tags: true 9 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "type": "node", 6 | "request": "launch", 7 | "name": "Launch via node", 8 | "runtimeExecutable": "node", 9 | "runtimeArgs": [ 10 | "--nolazy", 11 | "--inspect-brk=9229", 12 | "node_modules/node-red/red.js", 13 | "-v" 14 | ], 15 | "console": "integratedTerminal", 16 | "skipFiles": [ 17 | "/**" 18 | ] 19 | } 20 | ] 21 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 mapero 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![npm version](https://badge.fury.io/js/node-red-contrib-fritz.svg)](https://badge.fury.io/js/node-red-contrib-fritz) 2 | [![Build Status](https://travis-ci.org/bashGroup/node-red-contrib-fritz.svg?branch=master)](https://travis-ci.org/bashGroup/node-red-contrib-fritz) 3 | 4 | [![NPM](https://nodei.co/npm/node-red-contrib-fritz.png?compact=true)](https://nodei.co/npm/node-red-contrib-fritz/) 5 | 6 | # node-red-contrib-fritz 7 | 8 | This node for the node-RED application provides easy access to your avm fritzbox. You can read and write the configuration of your fritzbox including the VoIP and Dect configuration. 9 | 10 | ## Installation 11 | Just install this module in your node-RED configuration folder 12 | 13 | ```bash 14 | cd ~/.node-red 15 | npm install node-red-contrib-fritz 16 | ``` 17 | 18 | Or globally: 19 | 20 | ```bash 21 | npm install -g node-red-contrib-fritz 22 | ``` 23 | 24 | ## Usage 25 | You can use the generic node to access every function provided by the development api of your fritzbox. First create a configuration including the hostname (e.g. fritz.box), username (e.g. admin) and password of your fritzbox. If this informations are provided you can use the search button to discover available services. Select the service you want to use. Select the action the same way. 26 | 27 | The `msg.payload` of the incoming message will be used as arguments. You have to provide a json object with argument name as key and the value, e.g. `{ "NewEnable" : 1, "NewUrl" : "string" }`. Available arguments will be shown in the the hint after you selected the action. 28 | You can override the settings if you provide `msg.provider` ("IGD" or "TR064"), `msg.service` or/and `msg.action`. 29 | 30 | Note: To use the callmonitor node, you have to activate the callmonitor service in your FritzBox (Otherwise the node will emit the error `connect ECONNREFUSED`). This can be done by using a telephone which is connected to the FritzBox. Call `#96*5*`to enable the callmonitor service. Use `#96*4*` if you want disable the feature again. 31 | 32 | 33 | ## Examples 34 | 35 | ### Presence Detection 36 | 37 | ![Presence](/examples/presence.png) 38 | 39 | ```json 40 | [{"id":"f80158f1.d27ab8","type":"inject","z":"5517edea.ed19e4","name":"20:82:C0:26:86:FE","topic":"","payload":"{\"NewMACAddress\": \"20:82:C0:26:86:FE\" }","payloadType":"json","repeat":"","crontab":"","once":false,"x":350,"y":140,"wires":[["7b27936b.08bc8c"]]},{"id":"7b27936b.08bc8c","type":"fritzbox-in","z":"5517edea.ed19e4","device":"28b24ff3.2b8f1","name":"","service":"urn:dslforum-org:service:Hosts:1","action":"GetSpecificHostEntry","arguments":"{\"NewMACAddress\":\"value\"}","x":530,"y":140,"wires":[["12274598.0e46da"]]},{"id":"12274598.0e46da","type":"debug","z":"5517edea.ed19e4","name":"","active":true,"console":"false","complete":"false","x":690,"y":140,"wires":[]},{"id":"28b24ff3.2b8f1","type":"fritzbox-config","z":"","name":"","host":"192.168.80.1","port":"49000","ssl":false}] 41 | ``` 42 | 43 | ### Callmonitor 44 | 45 | ![Callmonitor](/examples/callmonitor.png) 46 | 47 | ```json 48 | [{"id":"49ea9337.0f9fdc","type":"fritzbox-callmonitor","z":"8d4a73b4.140f","device":"28b24ff3.2b8f1","name":"","topic":"","x":240,"y":100,"wires":[["635c2f29.f18ad"]]},{"id":"635c2f29.f18ad","type":"fritzbox-contact","z":"8d4a73b4.140f","device":"28b24ff3.2b8f1","name":"","topic":"","phonebook":"0","ccode":"DE","x":450,"y":100,"wires":[["6a4a06bc.f70b48"]]},{"id":"6a4a06bc.f70b48","type":"debug","z":"8d4a73b4.140f","name":"","active":true,"console":"false","complete":"false","x":630,"y":100,"wires":[]},{"id":"28b24ff3.2b8f1","type":"fritzbox-config","z":"","name":"","host":"192.168.80.1","port":"49000","ssl":false}] 49 | ``` 50 | 51 | Note: Don't forget to enable the callmonitor service in your FritzBox. See above for instructions. 52 | 53 | 54 | ## Contributing 55 | 1. Fork it! 56 | 2. Create your feature branch: `git checkout -b my-new-feature` 57 | 3. Commit your changes: `git commit -am 'Add some feature'` 58 | 4. Push to the branch: `git push origin my-new-feature` 59 | 5. Submit a pull request :D 60 | 61 | [![NPM](https://mapero.github.io/paypal.png)](https://www.paypal.me/JochenScheib/2) 62 | 63 | ## Credits 64 | Jochen Scheib 65 | 66 | ## License 67 | MIT 68 | -------------------------------------------------------------------------------- /calllist.html: -------------------------------------------------------------------------------- 1 | 44 | 45 | 66 | 67 | 103 | 104 | -------------------------------------------------------------------------------- /calllist.js: -------------------------------------------------------------------------------- 1 | var axios = require('axios') 2 | https = require('https') 3 | xml2js = require("xml2js"); 4 | var parser = xml2js.Parser({explicitRoot: false, explicitArray: false}); 5 | 6 | const httpclient = axios.create({ 7 | httpsAgent: new https.Agent({ 8 | rejectUnauthorized: false 9 | }) 10 | }); 11 | 12 | module.exports = function(RED) { 13 | function FritzboxList(n) { 14 | RED.nodes.createNode(this,n); 15 | var node = this; 16 | node.max = n.max; 17 | node.maxdays = n.maxdays; 18 | node.action = n.action ? n.action : "GetCallList"; 19 | node.listurl = n.listurl ? n.listurl : "NewCallListURL"; 20 | node.phonebookId = n.id; 21 | node.config = RED.nodes.getNode(n.device); 22 | 23 | node.config.on('statusUpdate', node.status); 24 | 25 | node.on('input', function(msg) { 26 | if(node.config.state === "ready" && node.config.fritzbox) { 27 | var args = {}; 28 | var action = node.action; 29 | var queryParams = {}; 30 | var urlkey = node.listurl; 31 | 32 | switch (action) { 33 | case "GetCallList": 34 | if(n.maxdays) { 35 | queryParams["days"] = n.maxdays; 36 | } 37 | if(n.max) { 38 | queryParams["max"] = n.max; 39 | } 40 | break; 41 | case "GetPhonebook": 42 | if (typeof msg.payload === "object" && msg.payload.NewPhonebookID) { 43 | args = msg.payload; 44 | } else { 45 | args = { 46 | NewPhonebookID: node.phonebookId ? node.phonebookId : 0 47 | } 48 | } 49 | break; 50 | } 51 | node.config.fritzbox.services["urn:dslforum-org:service:X_AVM-DE_OnTel:1"].actions[action](args) 52 | .then(function(response) { 53 | var url = response[urlkey]; 54 | return httpclient.get(url, {params: queryParams}) 55 | }).then(function(result) { 56 | return parser.parseStringPromise(result.data) 57 | }).then(function(result) { 58 | msg.payload = result; 59 | node.send(msg); 60 | }).catch(function(error) { 61 | node.error(`Receiving Calllist / Phonebook failed. Error: ${error}`, msg); 62 | }); 63 | } else { 64 | node.warn("Device not ready."); 65 | node.config.reinit(); 66 | } 67 | }); 68 | 69 | node.on('close', function() { 70 | node.config.removeListener('statusUpdate', node.status); 71 | }); 72 | } 73 | RED.nodes.registerType("fritzbox-calllist", FritzboxList); 74 | RED.nodes.registerType("fritzbox-phonebook", FritzboxList); 75 | } 76 | 77 | -------------------------------------------------------------------------------- /callmonitor.html: -------------------------------------------------------------------------------- 1 | 20 | 21 | 35 | 36 | 71 | -------------------------------------------------------------------------------- /callmonitor.js: -------------------------------------------------------------------------------- 1 | var net = require('net'); 2 | 3 | module.exports = function(RED) { 4 | 5 | function FritzboxCallmonitor(n) { 6 | RED.nodes.createNode(this,n); 7 | var node = this; 8 | node.topic = n.topic; 9 | node.config = RED.nodes.getNode(n.device); 10 | 11 | var client = new net.Socket(); 12 | client.setKeepAlive(true, 118000); 13 | var connections = {}; 14 | var timeout; 15 | var options = { 16 | port: 1012, 17 | host: node.config.host, 18 | }; 19 | 20 | var reconnect = function() { 21 | node.log('Connecting to fritzbox...'); 22 | if(timeout) clearTimeout(timeout); 23 | client.connect(options); 24 | }; 25 | 26 | node.status({fill:"yellow",shape:"ring",text:"Initialization"}); 27 | 28 | client.on('connect', function() { 29 | node.log('Connected to fritzbox'); 30 | node.status({fill:"green",shape:"dot",text:"Connected"}); 31 | }); 32 | 33 | client.on('close', function(hadError) { 34 | if(hadError) { 35 | node.error("Disconnected with Error"); 36 | node.status({fill:"red",shape:"ring",text:"Disconnected with Error"}); 37 | } else { 38 | node.warn("Disconnected"); 39 | node.status({fill:"red",shape:"ring",text:"Disconnected"}); 40 | } 41 | node.log("Reconnecting in 30sec"); 42 | timeout = setTimeout(reconnect, 30000); 43 | }); 44 | 45 | client.on('error', function(e) { 46 | node.error(e); 47 | }); 48 | 49 | client.on('timeout', function() { 50 | node.error('Connection to fritzbox timed out'); 51 | client.end(); 52 | }); 53 | 54 | client.on('data', function(data) { 55 | const raw = data.toString(); 56 | const array = raw.split(";"); 57 | const type = array[1]; 58 | const id = array[2]; 59 | const timestamp = array[0]; 60 | let message; 61 | 62 | if (connections[id] !== undefined) { 63 | message = connections[id] 64 | } else { 65 | message = {} 66 | } 67 | 68 | switch(type) { 69 | case "CALL": 70 | message.type = "OUTBOUND"; 71 | message.id = id; 72 | message.timestamp = timestamp; 73 | message.caller = array[4]; 74 | message.callee = array[5]; 75 | message.extension = array[3]; 76 | connections[id] = message; 77 | break; 78 | case "RING": 79 | message.type = "INBOUND"; 80 | message.id = id; 81 | message.timestamp = timestamp; 82 | message.caller = array[3]; 83 | message.callee = array[4]; 84 | connections[id] = message; 85 | break; 86 | case "CONNECT": 87 | message.type = "CONNECT"; 88 | message.id = id; 89 | message.timestamp = timestamp; 90 | message.extension = array[3]; 91 | connections[id] = message; 92 | break; 93 | case "DISCONNECT": 94 | switch(message.type) { 95 | case "INBOUND": 96 | message.type = "MISSED"; 97 | break; 98 | case "CONNECT": 99 | message.type = "DISCONNECT"; 100 | break; 101 | case "OUTBOUND": 102 | message.type = "UNREACHED"; 103 | break; 104 | } 105 | message.id = id; 106 | message.timestamp = timestamp; 107 | message.duration = array[3]; 108 | delete connections[id]; 109 | break; 110 | } 111 | node.send({ 112 | topic: node.topic ? node.topic : "", 113 | payload: message 114 | }); 115 | }); 116 | 117 | reconnect(); 118 | 119 | node.on('close', function() { 120 | client.setKeepAlive(false, 118000); 121 | client.end(function() { 122 | client.removeAllListeners(); 123 | node.log('Disconnected from fritzbox'); 124 | }); 125 | if(timeout) clearTimeout(timeout); 126 | }); 127 | 128 | 129 | } 130 | RED.nodes.registerType("fritzbox-callmonitor", FritzboxCallmonitor); 131 | 132 | }; 133 | -------------------------------------------------------------------------------- /config.html: -------------------------------------------------------------------------------- 1 | 91 | 92 | -------------------------------------------------------------------------------- /config.js: -------------------------------------------------------------------------------- 1 | var Fritzbox = require("fritzbox") 2 | var parseStringPromise = require('xml2js').parseStringPromise; 3 | 4 | module.exports = function(RED) { 5 | 6 | RED.httpAdmin.get('/fritzbox/services', function(req, res, next) { 7 | var config = { 8 | host: req.query.url 9 | }; 10 | 11 | var fritzbox = new Fritzbox.Fritzbox(config); 12 | 13 | Promise.all([fritzbox.initIGDDevice(), fritzbox.initTR064Device()].map(p => p.catch(error => null))) 14 | .then(function() { 15 | var services = Object.keys(fritzbox.services); 16 | res.end(JSON.stringify(services)); 17 | }).catch(function(error) { 18 | res.status(500); 19 | res.end(error.message); 20 | }); 21 | }); 22 | 23 | RED.httpAdmin.get('/fritzbox/actions', function(req, res, next) { 24 | var service = req.query.service; 25 | 26 | var config = { 27 | host: req.query.url 28 | }; 29 | 30 | var fritzbox = new Fritzbox.Fritzbox(config); 31 | 32 | Promise.all([fritzbox.initIGDDevice(), fritzbox.initTR064Device()].map(p => p.catch(error => null))) 33 | .then(function() { 34 | var actions = fritzbox.services[service].actionsInfo; 35 | res.end(JSON.stringify(actions)); 36 | }).catch(function(error) { 37 | res.status(500); 38 | res.end(error.message); 39 | }); 40 | }); 41 | 42 | RED.httpAdmin.get('/fritzbox/users', function(req, res, next) { 43 | var host = req.query.url; 44 | 45 | var config = { 46 | host: host 47 | }; 48 | 49 | var fritzbox = new Fritzbox.Fritzbox(config); 50 | 51 | fritzbox.initTR064Device().then(function() { 52 | return fritzbox.services["urn:dslforum-org:service:LANConfigSecurity:1"].actions["X_AVM-DE_GetUserList"]() 53 | }).then(function(result) { 54 | return parseStringPromise(result['NewX_AVM-DE_UserList'], {explicitRoot: false, explicitArray: false, ignoreAttrs: true}); 55 | }).then(function(json) { 56 | res.end(JSON.stringify(json)); 57 | }).catch(function(error) { 58 | res.status(500); 59 | res.end(error.message); 60 | }); 61 | }); 62 | 63 | 64 | 65 | function FritzboxConfig(n) { 66 | RED.nodes.createNode(this, n); 67 | var node = this; 68 | node.host = n.host; 69 | node.timer = null; 70 | node.state = null; 71 | 72 | if(!node.host) return; 73 | 74 | var config = { 75 | host: n.host, 76 | user: node.credentials.username, 77 | password: node.credentials.password, 78 | port: n.port, 79 | ssl: n.ssl 80 | }; 81 | 82 | node.fritzbox = new Fritzbox.Fritzbox(config); 83 | 84 | // Initialize fritzbox configuration by gathering services and actions 85 | node.reinit = async function() { 86 | await Promise.all([node.fritzbox.initIGDDevice(), node.fritzbox.initTR064Device()]) 87 | .then(function() { 88 | updateStatus("ready"); 89 | }).catch(function(error) { 90 | node.error(`Initialization of device failed ${error}. Check configuration.`); 91 | updateStatus("error"); 92 | }); 93 | }; 94 | 95 | var updateStatus = function(status) { 96 | node.state = status; 97 | switch(status) { 98 | case "init": 99 | node.emit("statusUpdate", {fill:"yellow",shape:"ring",text:"Initializing device ..."}); 100 | break; 101 | case "ready": 102 | node.emit("statusUpdate", {fill:"green",shape:"dot",text:"Ready"}); 103 | break; 104 | case "error": 105 | node.emit("statusUpdate", {fill:"red",shape:"ring",text:"Error"}); 106 | break; 107 | } 108 | }; 109 | node.reinit(); 110 | } 111 | RED.nodes.registerType("fritzbox-config", FritzboxConfig, { 112 | credentials: { 113 | username: {type: "text"}, 114 | password: {type: "password"} 115 | } 116 | }); 117 | } -------------------------------------------------------------------------------- /contact.html: -------------------------------------------------------------------------------- 1 | 39 | 40 | 63 | 64 | 86 | -------------------------------------------------------------------------------- /contact.js: -------------------------------------------------------------------------------- 1 | var parser = require("xml2js").Parser({explicitRoot: false, explicitArray: false, mergeAttrs: true}); 2 | var axios = require('axios') 3 | https = require('https'); 4 | var PNU = require('google-libphonenumber').PhoneNumberUtil; 5 | var phoneUtil = PNU.getInstance(); 6 | 7 | const httpclient = axios.create({ 8 | httpsAgent: new https.Agent({ 9 | rejectUnauthorized: false 10 | }) 11 | }); 12 | 13 | 14 | module.exports = function(RED) { 15 | 16 | RED.httpAdmin.get('/fritzbox/phonebook/regioncodes', function(req, res, next) { 17 | res.end(JSON.stringify(phoneUtil.getSupportedRegions())); 18 | }); 19 | 20 | function FritzBoxContact(n) { 21 | RED.nodes.createNode(this,n); 22 | var node = this; 23 | node.topic = n.topic; 24 | node.phonebook = n.phonebook || 0; 25 | node.ccode = n.ccode; 26 | node.config = RED.nodes.getNode(n.device); 27 | 28 | var statusupdate = function(status) { 29 | node.status = status; 30 | }; 31 | 32 | node.config.on('statusUpdate', statusupdate); 33 | 34 | var simpleSearch = function(contacts, number) { 35 | var result = []; 36 | contacts.forEach(function(contact) { 37 | if(Array.isArray(contact.telephony.number)) { 38 | contact.telephony.number.forEach(function(number) { 39 | if(number._.includes(number)) { 40 | result.push(contact); 41 | } 42 | }); 43 | } else { 44 | if(contact.telephony.number._.includes(number)) { 45 | result.push(contact); 46 | } 47 | } 48 | }); 49 | return result; 50 | } 51 | 52 | var queryphonebook = function(phonenumber) { 53 | // Query the phonebook 54 | return node.config.fritzbox.services["urn:dslforum-org:service:X_AVM-DE_OnTel:1"].actions.GetPhonebook({'NewPhonebookID': node.phonebook}) 55 | .then(function(url) { 56 | // Follow the phonebook link and parse the XML 57 | return httpclient.get(url.NewPhonebookURL); 58 | }).then(function(result) { 59 | return parser.parseStringPromise(result.data) 60 | }).then(function(result) { 61 | // Parse the incoming number 62 | var inNumber; 63 | try { 64 | inNumber = phoneUtil.parse(phonenumber, node.ccode); 65 | } catch(e) { 66 | node.warn(`The provided number ${phonenumber} is not valid for region ${node.ccode}: ${e}`); 67 | return []; 68 | } 69 | if(!phoneUtil.isValidNumber(inNumber)) { 70 | return simpleSearch(result.phonebook.contact, phonenumber); 71 | } 72 | 73 | // Search the phonebook for the number 74 | var contacts = []; 75 | result.phonebook.contact.forEach(function(contact) { 76 | function matchNumber(number) { 77 | if (number._.startsWith('**')) return; 78 | if (number._.includes('@')) return; 79 | try { 80 | var numberDE = phoneUtil.parse(number._, node.ccode); 81 | if(phoneUtil.isValidNumber(numberDE) && phoneUtil.isNumberMatch(inNumber, numberDE) === PNU.MatchType.EXACT_MATCH) { 82 | contacts.push(contact); 83 | } 84 | } catch(e) { 85 | node.warn(`The invalid phonebook number ${number._} for region ${node.ccode} will be ignored: ${e}`); 86 | } 87 | }; 88 | 89 | if(Array.isArray(contact.telephony.number)) { 90 | contact.telephony.number.forEach(matchNumber); 91 | } else { 92 | matchNumber(contact.telephony.number); 93 | } 94 | }); 95 | return contacts; 96 | }) 97 | }; 98 | 99 | node.on('input', function(msg) { 100 | if(node.config.state === "ready" && node.config.fritzbox) { 101 | if(msg.payload !== null && 102 | typeof msg.payload === 'object' && 103 | !Array.isArray(msg.payload) && 104 | msg.payload.callee !== undefined && 105 | msg.payload.caller !== undefined ) { 106 | var caller = queryphonebook(msg.payload.caller).then(function(contacts) { 107 | msg.payload.caller_contacts = contacts; 108 | }); 109 | var callee = queryphonebook(msg.payload.callee).then(function(contacts) { 110 | msg.payload.callee_contacts = contacts; 111 | }); 112 | Promise.all([caller, callee]).then(function() { 113 | node.send(msg); 114 | }).catch(function(e) { 115 | node.warn(e); 116 | node.send(msg); 117 | }); 118 | } else if (msg.payload !== null && typeof msg.payload === 'string') { 119 | queryphonebook(msg.payload).then(function(contacts) { 120 | msg.payload = contacts; 121 | node.send(msg); 122 | }).catch(function(e) { 123 | node.warn(e); 124 | msg.payload = []; 125 | node.send(msg); 126 | }); 127 | } else { 128 | node.warn("Invalid input"); 129 | node.send(msg); 130 | } 131 | } else { 132 | node.error("Device not ready.", msg); 133 | node.config.reinit(); 134 | } 135 | }); 136 | 137 | node.on('close', function() { 138 | node.config.removeListener('statusUpdate', statusupdate); 139 | }); 140 | 141 | } 142 | RED.nodes.registerType("fritzbox-contact", FritzBoxContact); 143 | }; 144 | -------------------------------------------------------------------------------- /examples/callmonitor.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "49ea9337.0f9fdc", 4 | "type": "fritzbox-callmonitor", 5 | "z": "8d4a73b4.140f", 6 | "device": "28b24ff3.2b8f1", 7 | "name": "", 8 | "topic": "", 9 | "x": 240, 10 | "y": 100, 11 | "wires": [ 12 | [ 13 | "635c2f29.f18ad" 14 | ] 15 | ] 16 | }, 17 | { 18 | "id": "635c2f29.f18ad", 19 | "type": "fritzbox-contact", 20 | "z": "8d4a73b4.140f", 21 | "device": "28b24ff3.2b8f1", 22 | "name": "", 23 | "topic": "", 24 | "phonebook": "0", 25 | "ccode": "DE", 26 | "x": 450, 27 | "y": 100, 28 | "wires": [ 29 | [ 30 | "6a4a06bc.f70b48" 31 | ] 32 | ] 33 | }, 34 | { 35 | "id": "6a4a06bc.f70b48", 36 | "type": "debug", 37 | "z": "8d4a73b4.140f", 38 | "name": "", 39 | "active": true, 40 | "console": "false", 41 | "complete": "false", 42 | "x": 630, 43 | "y": 100, 44 | "wires": [] 45 | }, 46 | { 47 | "id": "28b24ff3.2b8f1", 48 | "type": "fritzbox-config", 49 | "z": "", 50 | "name": "", 51 | "host": "192.168.80.1", 52 | "port": "49000", 53 | "ssl": false 54 | } 55 | ] 56 | -------------------------------------------------------------------------------- /examples/callmonitor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bashGroup/node-red-contrib-fritz/5cb501c53e19f860b7900dd7735b5a76aba50410/examples/callmonitor.png -------------------------------------------------------------------------------- /examples/presence.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "f80158f1.d27ab8", 4 | "type": "inject", 5 | "z": "5517edea.ed19e4", 6 | "name": "{\" NewMacAddress\": \"20:82:C0:26:86:FE\" }", 7 | "topic": "", 8 | "payload": "{\"NewMACAddress\": \"20:82:C0:26:86:FE\" }", 9 | "payloadType": "json", 10 | "repeat": "", 11 | "crontab": "", 12 | "once": false, 13 | "x": 260, 14 | "y": 100, 15 | "wires": [ 16 | [ 17 | "7b27936b.08bc8c" 18 | ] 19 | ] 20 | }, 21 | { 22 | "id": "7b27936b.08bc8c", 23 | "type": "fritzbox-in", 24 | "z": "5517edea.ed19e4", 25 | "device": "28b24ff3.2b8f1", 26 | "name": "", 27 | "service": "urn:dslforum-org:service:Hosts:1", 28 | "action": "GetSpecificHostEntry", 29 | "arguments": "{\"NewMACAddress\":\"value\"}", 30 | "x": 510, 31 | "y": 100, 32 | "wires": [ 33 | [ 34 | "12274598.0e46da" 35 | ] 36 | ] 37 | }, 38 | { 39 | "id": "12274598.0e46da", 40 | "type": "debug", 41 | "z": "5517edea.ed19e4", 42 | "name": "", 43 | "active": true, 44 | "console": "false", 45 | "complete": "false", 46 | "x": 670, 47 | "y": 100, 48 | "wires": [] 49 | }, 50 | { 51 | "id": "28b24ff3.2b8f1", 52 | "type": "fritzbox-config", 53 | "z": "5517edea.ed19e4", 54 | "name": "", 55 | "host": "192.168.80.1", 56 | "port": "49000", 57 | "ssl": false 58 | } 59 | ] 60 | -------------------------------------------------------------------------------- /examples/presence.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bashGroup/node-red-contrib-fritz/5cb501c53e19f860b7900dd7735b5a76aba50410/examples/presence.png -------------------------------------------------------------------------------- /fritz.html: -------------------------------------------------------------------------------- 1 | 141 | 142 | 168 | 169 | -------------------------------------------------------------------------------- /fritz.js: -------------------------------------------------------------------------------- 1 | var Fritzbox = require("fritzbox") 2 | 3 | 4 | module.exports = function(RED) { 5 | 6 | function FritzboxIn(n) { 7 | RED.nodes.createNode(this,n); 8 | var node = this; 9 | node.service = n.service; 10 | node.action = n.action; 11 | node.config = RED.nodes.getNode(n.device); 12 | 13 | node.config.on('statusUpdate', node.status); 14 | 15 | node.on('input', function(msg) { 16 | if(node.config.state === "ready" && node.config.fritzbox) { 17 | var service = msg.service ? msg.service : node.service; 18 | var action = msg.action ? msg.action : node.action; 19 | if(action === ""){ 20 | node.error("No action found. Did you select any?", msg) 21 | return; 22 | } 23 | if(node.config.fritzbox.options.ssl && node.config.fritzbox.options.port == 49000){ 24 | node.warn("SSL option selected with Standard Port 49000. Should be 49443?"); 25 | } 26 | if(node.config.fritzbox.services[service] === undefined){ 27 | node.error("No Services response received.", msg); 28 | return; 29 | } 30 | node.config.fritzbox.services[service].actions[action](msg.payload) 31 | .then(function(result) { 32 | msg.payload = result; 33 | node.send(msg); 34 | }).catch(function(error) { 35 | node.error(`Action failed with error: ${error}`, msg); 36 | }); 37 | } else { 38 | node.warn("Device not ready."); 39 | node.config.reinit(); 40 | } 41 | }); 42 | 43 | node.on('close', function() { 44 | node.config.removeListener('statusUpdate', node.status); 45 | }); 46 | } 47 | RED.nodes.registerType("fritzbox-in", FritzboxIn); 48 | }; 49 | -------------------------------------------------------------------------------- /icons/fritz.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bashGroup/node-red-contrib-fritz/5cb501c53e19f860b7900dd7735b5a76aba50410/icons/fritz.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "node-red-contrib-fritz", 3 | "version": "1.5.0", 4 | "description": "Get full access to all functions of your fritzbox. Including Callmonitor, Presence Detection and much more", 5 | "dependencies": { 6 | "axios": "^1.6.0", 7 | "fritzbox": "^1.1.4", 8 | "google-libphonenumber": "^3.2.33", 9 | "xml2js": "^0.6.2" 10 | }, 11 | "devDependencies": { 12 | "node-red": "^3.1.0", 13 | "node-red-node-test-helper": "^0.3.2", 14 | "npm-run-all": "^4.1.5" 15 | }, 16 | "scripts": {}, 17 | "engines": { 18 | "node": ">=5.0.0" 19 | }, 20 | "repository": { 21 | "type": "git", 22 | "url": "https://github.com/bashgroup/node-red-contrib-fritz.git" 23 | }, 24 | "license": "MIT", 25 | "keywords": [ 26 | "node-red", 27 | "fritzbox", 28 | "fritz", 29 | "router", 30 | "tr064", 31 | "presence", 32 | "avm", 33 | "callmonitor", 34 | "phonebook" 35 | ], 36 | "node-red": { 37 | "nodes": { 38 | "fritzbox": "fritz.js", 39 | "fritzbox-callmonitor": "callmonitor.js", 40 | "fritzbox-contact": "contact.js", 41 | "fritzbox-config": "config.js", 42 | "fritzbox-calllist": "calllist.js" 43 | } 44 | }, 45 | "author": { 46 | "name": "Jochen Scheib" 47 | } 48 | } 49 | --------------------------------------------------------------------------------