From fc2f6ed3c7dbbd689084c3f23608390db6678bc9 Mon Sep 17 00:00:00 2001 From: Andreas Martens Date: Fri, 29 Jan 2021 16:46:15 +0000 Subject: [PATCH 01/69] join a MUC if we're already connected to server and refactor error handling (#749) --- social/xmpp/92-xmpp.js | 195 +++++++++++++++++++++-------------------- 1 file changed, 100 insertions(+), 95 deletions(-) diff --git a/social/xmpp/92-xmpp.js b/social/xmpp/92-xmpp.js index 59e0ac36..69a0cd13 100644 --- a/social/xmpp/92-xmpp.js +++ b/social/xmpp/92-xmpp.js @@ -57,7 +57,7 @@ module.exports = function(RED) { this.register = function(xmppThat) { if (RED.settings.verbose || LOGITALL) {that.log("registering "+xmppThat.id); } that.users[xmppThat.id] = xmppThat; - // So we could start the connection here, but we already have the logic in the thats. + // So we could start the connection here, but we already have the logic in the nodes that takes care of that. // if (Object.keys(that.users).length === 1) { // this.client.start(); // } @@ -211,6 +211,66 @@ module.exports = function(RED) { } }); + function joinMUC(node, xmpp, name) { + // the presence with the muc x element signifies we want to join the muc + // if we want to support passwords, we need to add that as a child of the x element + // (third argument to the x/muc/children ) + // We also turn off chat history (maxstanzas 0) because that's not what this node is about. + var stanza = xml('presence', + {"to": name}, + xml("x",'http://jabber.org/protocol/muc'), + { maxstanzas:0, seconds:1 } + ); + node.serverConfig.used(node); + xmpp.send(stanza); + + } + + // separated out since we want the same functionality from both in and out nodes + function errorHandler(node, err){ + if (!node.quiet) { + node.quiet = true; + // if the error has a "stanza" then we've probably done something wrong and the + // server is unhappy with us + if (err.hasOwnProperty("stanza")) { + if (err.stanza.name === 'stream:error') { node.error("stream:error - bad login id/pwd ?",err); } + else { node.error(err.stanza.name,err); } + node.status({fill:"red",shape:"ring",text:"bad login"}); + } + // The error might be a string + else if (err == "TimeoutError") { + // OK, this happens with OpenFire, suppress it. + node.status({fill:"grey",shape:"dot",text:"opening"}); + node.log("Timed out! ",err); + // node.status({fill:"red",shape:"ring",text:"XMPP timeout"}); + } + else if (err === "XMPP authentication failure") { + node.error(err,err); + node.status({fill:"red",shape:"ring",text:"XMPP authentication failure"}); + } + // or it might have a name that tells us what's wrong + else if (err.name === "SASLError") { + node.error("Authorization error! "+err.condition,err); + node.status({fill:"red",shape:"ring",text:"XMPP authorization failure"}); + } + + // or it might have the errno set. + else if (err.errno === "ETIMEDOUT") { + node.error("Timeout connecting to server",err); + node.status({fill:"red",shape:"ring",text:"timeout"}); + } + else if (err.errno === "ENOTFOUND") { + node.error("Server doesn't exist "+xmpp.options.service,err); + node.status({fill:"red",shape:"ring",text:"bad address"}); + } + // nothing we've seen before! + else { + node.error("Unknown error: "+err,err); + node.status({fill:"red",shape:"ring",text:"node-red:common.status.error"}); + } + } + } + function XmppInNode(n) { RED.nodes.createNode(this,n); this.server = n.server; @@ -220,6 +280,9 @@ module.exports = function(RED) { this.sendAll = n.sendObject; // Yes, it's called "from", don't ask me why; I don't know why this.from = n.to || ""; + this.quiet = false; + // MUC == Multi-User-Chat == chatroom + this.muc = this.join && (this.from !== "") var node = this; var xmpp = this.serverConfig.client; @@ -237,26 +300,27 @@ module.exports = function(RED) { disconnect: Socket is disconnected */ - xmpp.on('online', async address => { + // if we're already connected, then do the actions now, otherwise register a callback + if(xmpp.status === "online") { node.status({fill:"green",shape:"dot",text:"node-red:common.status.connected"}); - if ((node.join) && (node.from !== "")) { - var to = node.from+'/'+node.nick; - // the presence with the muc x element signifies we want to join the muc - // if we want to support passwords, we need to add that as a child of the x element - // (third argument to the x/muc/children ) - // We also turn off chat history (maxstanzas 0) because that's not what this node is about. - var stanza = xml('presence', - {"to": to}, - xml("x",'http://jabber.org/protocol/muc'), - { maxstanzas:0, seconds:1 } - ); - node.serverConfig.used(node); - xmpp.send(stanza).catch(error => {node.warn("Got error when sending presence: "+error)}); + if(node.muc) { + joinMUC(node, xmpp, node.from+'/'+node.nick); + } + } + // sod it, register it anyway, that way things will work better on a reconnect: + xmpp.on('online', async address => { + node.quiet = false; + node.status({fill:"green",shape:"dot",text:"node-red:common.status.connected"}); + if (node.muc) { + // if we want to use a chatroom, we need to tell the server we want to join it + joinMUC(node, xmpp, node.from+'/'+node.nick); } }); xmpp.on('connecting', async address => { - node.status({fill:"grey",shape:"dot",text:"node-red:common.status.connecting"}); + if(!node.quiet) { + node.status({fill:"grey",shape:"dot",text:"node-red:common.status.connecting"}); + } }); xmpp.on('connect', async address => { node.status({fill:"grey",shape:"dot",text:"node-red:common.status.connected"}); @@ -281,39 +345,7 @@ module.exports = function(RED) { // Should we listen on other's status (chatstate) or a chatroom state (groupbuddy)? xmpp.on('error', err => { if (RED.settings.verbose || LOGITALL) { node.log("XMPP Error: "+err); } - if (err.hasOwnProperty("stanza")) { - if (err.stanza.name === 'stream:error') { node.error("stream:error - bad login id/pwd ?",err); } - else { node.error(err.stanza.name,err); } - node.status({fill:"red",shape:"ring",text:"bad login"}); - } - else { - if (err.errno === "ETIMEDOUT") { - node.error("Timeout connecting to server",err); - node.status({fill:"red",shape:"ring",text:"timeout"}); - } - if (err.errno === "ENOTFOUND") { - node.error("Server doesn't exist "+xmpp.options.service,err); - node.status({fill:"red",shape:"ring",text:"bad address"}); - } - else if (err === "XMPP authentication failure") { - node.error("Authentication failure! "+err,err); - node.status({fill:"red",shape:"ring",text:"XMPP authentication failure"}); - } - else if (err.name === "SASLError") { - node.error("Authorization error! "+err.condition,err); - node.status({fill:"red",shape:"ring",text:"XMPP authorization failure"}); - } - else if (err == "TimeoutError") { - // Suppress it! - node.warn("Timed out! "); - node.status({fill:"grey",shape:"dot",text:"opening"}); - //node.status({fill:"red",shape:"ring",text:"XMPP timeout"}); - } - else { - node.error(err,err); - node.status({fill:"red",shape:"ring",text:"node-red:common.status.error"}); - } - } + errorHandler(node, err); }); // Meat of it, a stanza object contains chat messages (and other things) @@ -408,7 +440,6 @@ module.exports = function(RED) { } catch(e) { node.error("Bad xmpp configuration; service: "+xmpp.options.service+" jid: "+node.serverConfig.jid); - node.warn(e); node.warn(e.stack); node.status({fill:"red",shape:"ring",text:"node-red:common.status.disconnected"}); } @@ -429,6 +460,9 @@ module.exports = function(RED) { this.join = n.join || false; this.sendAll = n.sendObject; this.to = n.to || ""; + this.quiet = false; + // MUC == Multi-User-Chat == chatroom + this.muc = this.join && (this.to !== "") var node = this; var xmpp = this.serverConfig.client; @@ -446,27 +480,26 @@ module.exports = function(RED) { disconnect: Socket is disconnected */ - xmpp.on('online', function(data) { + // if we're already connected, then do the actions now, otherwise register a callback + if(xmpp.status === "online") { node.status({fill:"green",shape:"dot",text:"node-red:common.status.connected"}); - if ((node.join) && (node.to !== "")) { - // disable chat history - var to = node.to+'/'+node.nick; - // the presence with the muc x element signifies we want to join the muc - // if we want to support passwords, we need to add that as a child of the x element - // (third argument to the x/muc/children ) - // We also turn off chat history (maxstanzas 0) because that's not what this node is about. - var stanza = xml('presence', - {"to": to}, - xml("x",'http://jabber.org/protocol/muc'), - { maxstanzas:0, seconds:1 } - ); - node.serverConfig.used(node); - xmpp.send(stanza); + if(node.muc){ + joinMUC(node, xmpp, node.to+'/'+node.nick); + } + } + // sod it, register it anyway, that way things will work better on a reconnect: + xmpp.on('online', function(data) { + node.quiet = false; + node.status({fill:"green",shape:"dot",text:"node-red:common.status.connected"}); + if (node.muc) { + joinMUC(node, xmpp,node.to+"/"+node.nick); } }); xmpp.on('connecting', async address => { - node.status({fill:"grey",shape:"dot",text:"node-red:common.status.connecting"}); + if(!node.quiet) { + node.status({fill:"grey",shape:"dot",text:"node-red:common.status.connecting"}); + } }); xmpp.on('connect', async address => { node.status({fill:"grey",shape:"dot",text:"node-red:common.status.connected"}); @@ -490,35 +523,7 @@ module.exports = function(RED) { xmpp.on('error', function(err) { if (RED.settings.verbose || LOGITALL) { node.log(err); } - if (err.hasOwnProperty("stanza")) { - if (err.stanza.name === 'stream:error') { node.error("stream:error - bad login id/pwd ?",err); } - else { node.error(err.stanza.name,err); } - node.status({fill:"red",shape:"ring",text:"bad login"}); - } - else { - if (err.errno === "ETIMEDOUT") { - node.error("Timeout connecting to server",err); - node.status({fill:"red",shape:"ring",text:"timeout"}); - } - else if (err.errno === "ENOTFOUND") { - node.error("Server doesn't exist "+xmpp.options.service,err); - node.status({fill:"red",shape:"ring",text:"bad address"}); - } - else if (err === "XMPP authentication failure") { - node.error(err,err); - node.status({fill:"red",shape:"ring",text:"XMPP authentication failure"}); - } - else if (err == "TimeoutError") { - // OK, this happens with OpenFire, suppress it. - node.status({fill:"grey",shape:"dot",text:"opening"}); - node.log("Timed out! ",err); - // node.status({fill:"red",shape:"ring",text:"XMPP timeout"}); - } - else { - node.error("Unknown error: "+err,err); - node.status({fill:"red",shape:"ring",text:"node-red:common.status.error"}); - } - } + errorHandler(node, err) }); //register with config From 77fac0331980e8d20a73fa1b8a11ffe4d3d89fd8 Mon Sep 17 00:00:00 2001 From: Dave Conway-Jones Date: Fri, 29 Jan 2021 16:48:54 +0000 Subject: [PATCH 02/69] bump xmpp --- social/xmpp/package.json | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/social/xmpp/package.json b/social/xmpp/package.json index 44381f89..09ed319b 100644 --- a/social/xmpp/package.json +++ b/social/xmpp/package.json @@ -1,10 +1,13 @@ { "name": "node-red-node-xmpp", - "version": "0.3.1", + "version": "0.3.2", "description": "A Node-RED node to talk to an XMPP server", "dependencies": { - "@xmpp/client": "^0.11.1" + "@xmpp/client": "^0.12.0" }, + "bundledDependencies": [ + "@xmpp/client" + ], "repository": { "type": "git", "url": "https://github.com/node-red/node-red-nodes/tree/master/social/xmpp" From 778fd683542c134ff16bf38a8fea68e9c772d642 Mon Sep 17 00:00:00 2001 From: Dave Conway-Jones Date: Fri, 29 Jan 2021 16:53:50 +0000 Subject: [PATCH 03/69] add contributor --- social/xmpp/package.json | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/social/xmpp/package.json b/social/xmpp/package.json index 09ed319b..e9c9149b 100644 --- a/social/xmpp/package.json +++ b/social/xmpp/package.json @@ -26,5 +26,10 @@ "name": "Dave Conway-Jones", "email": "ceejay@vnet.ibm.com", "url": "http://nodered.org" - } + }, + "contributors": [ + { + "name": "@tweek" + } + ] } From 886158485b4f9936f631804a228976324356c8fb Mon Sep 17 00:00:00 2001 From: Andreas Martens Date: Tue, 2 Feb 2021 09:14:11 +0000 Subject: [PATCH 04/69] XMPP: ability to dynamically join chat rooms (#752) * join a MUC if we're already connected to server and refactor error handling * ability to dynamically join a MUC when provided as msg.topic --- social/xmpp/92-xmpp.js | 35 ++++++++++++++++++++++++++--------- 1 file changed, 26 insertions(+), 9 deletions(-) diff --git a/social/xmpp/92-xmpp.js b/social/xmpp/92-xmpp.js index 69a0cd13..c324d8d7 100644 --- a/social/xmpp/92-xmpp.js +++ b/social/xmpp/92-xmpp.js @@ -50,6 +50,8 @@ module.exports = function(RED) { this.connected = false; // store the nodes that have us as config so we know when to tear it all down. this.users = {}; + // Store the chatrooms (MUC) that we've joined (sent "presence" XML to) already + this.MUCs = {}; // helper variable, because "this" changes definition inside a callback var that = this; @@ -216,14 +218,23 @@ module.exports = function(RED) { // if we want to support passwords, we need to add that as a child of the x element // (third argument to the x/muc/children ) // We also turn off chat history (maxstanzas 0) because that's not what this node is about. - var stanza = xml('presence', - {"to": name}, - xml("x",'http://jabber.org/protocol/muc'), - { maxstanzas:0, seconds:1 } - ); - node.serverConfig.used(node); - xmpp.send(stanza); - + // Yes, there's a race condition, but it's not a huge problem to send two messages + // so we don't care. + if (name in node.serverConfig.MUCs) { + if (RED.settings.verbose || LOGITALL) { + node.log("already joined MUC "+name); + } + } + else { + var stanza = xml('presence', + {"to": name}, + xml("x",'http://jabber.org/protocol/muc'), + { maxstanzas:0, seconds:1 } + ); + node.serverConfig.used(node); + node.serverConfig.MUCs[name] = "joined"; + xmpp.send(stanza); + } } // separated out since we want the same functionality from both in and out nodes @@ -579,7 +590,13 @@ module.exports = function(RED) { var to = node.to || msg.topic || ""; if (to !== "") { var message; - var type = node.join? "groupchat":"chat"; + var type = "chat"; + if (node.join) { + // we want to connect to groupchat / chatroom / MUC + type = "groupchat"; + // joinMUC will do nothing if we're already joined + joinMUC(node, xmpp, to+'/'+node.nick); + } if (node.sendAll) { message = xml( "message", From f99e52d1fafc7ba30a65fa91288b8b5f6fa34e97 Mon Sep 17 00:00:00 2001 From: Dave Conway-Jones Date: Tue, 2 Feb 2021 10:12:32 +0000 Subject: [PATCH 05/69] Merge branch 'master' of https://github.com/node-red/node-red-nodes merge upstream --- social/xmpp/92-xmpp.js | 35 ++++++++++++++++++++++++++--------- social/xmpp/package.json | 2 +- 2 files changed, 27 insertions(+), 10 deletions(-) diff --git a/social/xmpp/92-xmpp.js b/social/xmpp/92-xmpp.js index 69a0cd13..c324d8d7 100644 --- a/social/xmpp/92-xmpp.js +++ b/social/xmpp/92-xmpp.js @@ -50,6 +50,8 @@ module.exports = function(RED) { this.connected = false; // store the nodes that have us as config so we know when to tear it all down. this.users = {}; + // Store the chatrooms (MUC) that we've joined (sent "presence" XML to) already + this.MUCs = {}; // helper variable, because "this" changes definition inside a callback var that = this; @@ -216,14 +218,23 @@ module.exports = function(RED) { // if we want to support passwords, we need to add that as a child of the x element // (third argument to the x/muc/children ) // We also turn off chat history (maxstanzas 0) because that's not what this node is about. - var stanza = xml('presence', - {"to": name}, - xml("x",'http://jabber.org/protocol/muc'), - { maxstanzas:0, seconds:1 } - ); - node.serverConfig.used(node); - xmpp.send(stanza); - + // Yes, there's a race condition, but it's not a huge problem to send two messages + // so we don't care. + if (name in node.serverConfig.MUCs) { + if (RED.settings.verbose || LOGITALL) { + node.log("already joined MUC "+name); + } + } + else { + var stanza = xml('presence', + {"to": name}, + xml("x",'http://jabber.org/protocol/muc'), + { maxstanzas:0, seconds:1 } + ); + node.serverConfig.used(node); + node.serverConfig.MUCs[name] = "joined"; + xmpp.send(stanza); + } } // separated out since we want the same functionality from both in and out nodes @@ -579,7 +590,13 @@ module.exports = function(RED) { var to = node.to || msg.topic || ""; if (to !== "") { var message; - var type = node.join? "groupchat":"chat"; + var type = "chat"; + if (node.join) { + // we want to connect to groupchat / chatroom / MUC + type = "groupchat"; + // joinMUC will do nothing if we're already joined + joinMUC(node, xmpp, to+'/'+node.nick); + } if (node.sendAll) { message = xml( "message", diff --git a/social/xmpp/package.json b/social/xmpp/package.json index e9c9149b..4f65efd9 100644 --- a/social/xmpp/package.json +++ b/social/xmpp/package.json @@ -1,6 +1,6 @@ { "name": "node-red-node-xmpp", - "version": "0.3.2", + "version": "0.3.3", "description": "A Node-RED node to talk to an XMPP server", "dependencies": { "@xmpp/client": "^0.12.0" From 1c34e39d8a8f0fed6563e72f94fea119f4743e33 Mon Sep 17 00:00:00 2001 From: Andreas Martens Date: Tue, 2 Feb 2021 16:55:55 +0000 Subject: [PATCH 06/69] XMPP: improve debugging and error handling (#753) --- social/xmpp/92-xmpp.js | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/social/xmpp/92-xmpp.js b/social/xmpp/92-xmpp.js index c324d8d7..4ba648ed 100644 --- a/social/xmpp/92-xmpp.js +++ b/social/xmpp/92-xmpp.js @@ -115,10 +115,14 @@ module.exports = function(RED) { text = textObj.getText(); } } - if (RED.settings.verbose || LOGITALL) {that.log("Culprit: "+that.lastUsed); } + if (RED.settings.verbose || LOGITALL) {that.log("Culprit: "+that.lastUsed.id); } if ("undefined" !== typeof that.lastUsed) { that.lastUsed.status({fill:"red",shape:"ring",text:text}); that.lastUsed.warn(text); + if (that.lastUsed.join) { + // it was trying to MUC things up + clearMUC(that); + } } if (RED.settings.verbose || LOGITALL) { that.log("We did wrong: "+text); @@ -237,6 +241,13 @@ module.exports = function(RED) { } } + function clearMUC(config) { + //something has happened, so clear out our presence indicators + if (RED.settings.verbose || LOGITALL) { + config.log("cleared all MUC membership"); + } + config.MUCs = {}; + } // separated out since we want the same functionality from both in and out nodes function errorHandler(node, err){ if (!node.quiet) { @@ -250,8 +261,9 @@ module.exports = function(RED) { } // The error might be a string else if (err == "TimeoutError") { - // OK, this happens with OpenFire, suppress it. - node.status({fill:"grey",shape:"dot",text:"opening"}); + // OK, this happens with OpenFire, suppress it, but invalidate MUC membership as it will need to be re-established. + clearMUC(node.serverConfig); + node.status({fill:"grey",shape:"dot",text:"TimeoutError"}); node.log("Timed out! ",err); // node.status({fill:"red",shape:"ring",text:"XMPP timeout"}); } @@ -271,7 +283,7 @@ module.exports = function(RED) { node.status({fill:"red",shape:"ring",text:"timeout"}); } else if (err.errno === "ENOTFOUND") { - node.error("Server doesn't exist "+xmpp.options.service,err); + node.error("Server doesn't exist "+node.serverConfig.server,err); node.status({fill:"red",shape:"ring",text:"bad address"}); } // nothing we've seen before! From bbf9a3b7e469732ef10bf38bfbd6bc72c07bb7e9 Mon Sep 17 00:00:00 2001 From: Dave Conway-Jones Date: Tue, 2 Feb 2021 19:21:28 +0000 Subject: [PATCH 07/69] bump xmpp package --- social/xmpp/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/social/xmpp/package.json b/social/xmpp/package.json index 4f65efd9..ffd2b95e 100644 --- a/social/xmpp/package.json +++ b/social/xmpp/package.json @@ -1,6 +1,6 @@ { "name": "node-red-node-xmpp", - "version": "0.3.3", + "version": "0.3.4", "description": "A Node-RED node to talk to an XMPP server", "dependencies": { "@xmpp/client": "^0.12.0" From a57bf5383fad55d76709cb9ccdc6bd7384cb34b1 Mon Sep 17 00:00:00 2001 From: Dave Conway-Jones Date: Fri, 5 Feb 2021 10:35:54 +0000 Subject: [PATCH 08/69] Daemon node handle parameters with spaces to close #754 --- utility/daemon/daemon.js | 3 ++- utility/daemon/package.json | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/utility/daemon/daemon.js b/utility/daemon/daemon.js index 6d6546a6..bc231a5a 100644 --- a/utility/daemon/daemon.js +++ b/utility/daemon/daemon.js @@ -6,7 +6,8 @@ module.exports = function(RED) { function DaemonNode(n) { RED.nodes.createNode(this,n); this.cmd = n.command; - this.args = n.args.trim().split(" ") || []; + //this.args = n.args.trim().split(" ") || []; + this.args = n.args.trim().match(/("[^"]*")|[^ ]+/g); this.cr = n.cr; this.op = n.op; this.redo = n.redo; diff --git a/utility/daemon/package.json b/utility/daemon/package.json index 308023c4..bfae623a 100644 --- a/utility/daemon/package.json +++ b/utility/daemon/package.json @@ -1,6 +1,6 @@ { "name" : "node-red-node-daemon", - "version" : "0.1.2", + "version" : "0.1.3", "description" : "A Node-RED node that runs and monitors a long running system command.", "dependencies" : { }, From fbe2078324cd9e5dd1a338a7c455d53aae81aaaf Mon Sep 17 00:00:00 2001 From: Brian Orpin Date: Sun, 7 Feb 2021 22:32:58 +0000 Subject: [PATCH 09/69] Return all feed data (#743) --- io/emoncms/88-emoncms.html | 4 +++- io/emoncms/88-emoncms.js | 9 ++++++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/io/emoncms/88-emoncms.html b/io/emoncms/88-emoncms.html index 65789671..7fd323d7 100644 --- a/io/emoncms/88-emoncms.html +++ b/io/emoncms/88-emoncms.html @@ -83,7 +83,9 @@ - @@ -66,7 +65,8 @@ }); - - - - - - - - - - - - - - diff --git a/social/xmpp/92-xmpp.js b/social/xmpp/92-xmpp.js index 4ba648ed..60df52d0 100644 --- a/social/xmpp/92-xmpp.js +++ b/social/xmpp/92-xmpp.js @@ -15,13 +15,13 @@ module.exports = function(RED) { if ("undefined" === typeof n.server || n.server === "") { this.server = n.user.split('@')[1]; } - else{ + else { this.server = n.server; } if ("undefined" === typeof n.port || n.port === "") { this.port = 5222; } - else{ + else { this.port = parseInt(n.port); } @@ -50,7 +50,7 @@ module.exports = function(RED) { this.connected = false; // store the nodes that have us as config so we know when to tear it all down. this.users = {}; - // Store the chatrooms (MUC) that we've joined (sent "presence" XML to) already + // store the chatrooms (MUC) that we've joined (sent "presence" XML to) already this.MUCs = {}; // helper variable, because "this" changes definition inside a callback var that = this; @@ -75,7 +75,8 @@ module.exports = function(RED) { if (Object.keys(that.users).length === 0) { if (that.client && that.client.connected) { return that.client.stop(done); - } else { + } + else { return done(); } } @@ -84,6 +85,7 @@ module.exports = function(RED) { // store the last node to use us, in case we get an error back this.lastUsed = undefined; + // function for a node to tell us it has just sent a message to our server // so we know which node to blame if it all goes Pete Tong this.used = function(xmppThat) { @@ -91,11 +93,10 @@ module.exports = function(RED) { that.lastUsed = xmppThat; } - // Some errors come back as a message :-( // this means we need to figure out which node might have sent it // we also deal with subscriptions (i.e. presence information) here - this.client.on('stanza', async (stanza) =>{ + this.client.on('stanza', async (stanza) => { if (stanza.is('message')) { if (stanza.attrs.type == 'error') { if (RED.settings.verbose || LOGITALL) { @@ -106,17 +107,17 @@ module.exports = function(RED) { if (err) { var textObj = err.getChild('text'); var text = "node-red:common.status.error"; - if ("undefined" !== typeof textObj) { + if (typeof textObj !== "undefined") { text = textObj.getText(); } - else{ + else { textObj = err.getChild('code'); - if ("undefined" !== typeof textObj) { + if (typeof textObj !== "undefined") { text = textObj.getText(); } } if (RED.settings.verbose || LOGITALL) {that.log("Culprit: "+that.lastUsed.id); } - if ("undefined" !== typeof that.lastUsed) { + if (typeof that.lastUsed !== "undefined") { that.lastUsed.status({fill:"red",shape:"ring",text:text}); that.lastUsed.warn(text); if (that.lastUsed.join) { @@ -165,12 +166,10 @@ module.exports = function(RED) { var query = stanza.getChild('query'); if (RED.settings.verbose || LOGITALL) {that.log("result!"); } if (RED.settings.verbose || LOGITALL) {that.log(query); } - } } }); - // We shouldn't have any errors here that the input/output nodes can't handle // if you need to see everything though; uncomment this block // this.client.on('error', err => { @@ -217,6 +216,24 @@ module.exports = function(RED) { } }); + function getItems(thing,id,xmpp) { + // Now try to get a list of all items/conference rooms available on this server + var stanza = xml('iq', + {type:'get', id:id, to:thing}, + xml('query', 'http://jabber.org/protocol/disco#items') + ); + xmpp.send(stanza); + } + + function getInfo(thing,id,xmpp) { + // Now try to get a list of all info about a thing + var stanza = xml('iq', + {type:'get', id:id, to:thing}, + xml('query', 'http://jabber.org/protocol/disco#info') + ); + xmpp.send(stanza); + } + function joinMUC(node, xmpp, name) { // the presence with the muc x element signifies we want to join the muc // if we want to support passwords, we need to add that as a child of the x element @@ -231,13 +248,15 @@ module.exports = function(RED) { } else { var stanza = xml('presence', - {"to": name}, - xml("x",'http://jabber.org/protocol/muc'), - { maxstanzas:0, seconds:1 } - ); + {"to":name}, + xml("x",'http://jabber.org/protocol/muc', + xml("history", {maxstanzas:0, seconds:1}) // We don't want any history + ) + ); node.serverConfig.used(node); node.serverConfig.MUCs[name] = "joined"; xmpp.send(stanza); + if (RED.settings.verbose || LOGITALL) { node.log("JOINED",name); } } } @@ -248,6 +267,7 @@ module.exports = function(RED) { } config.MUCs = {}; } + // separated out since we want the same functionality from both in and out nodes function errorHandler(node, err){ if (!node.quiet) { @@ -276,7 +296,6 @@ module.exports = function(RED) { node.error("Authorization error! "+err.condition,err); node.status({fill:"red",shape:"ring",text:"XMPP authorization failure"}); } - // or it might have the errno set. else if (err.errno === "ETIMEDOUT") { node.error("Timeout connecting to server",err); @@ -293,7 +312,8 @@ module.exports = function(RED) { } } } - + + function XmppInNode(n) { RED.nodes.createNode(this,n); this.server = n.server; @@ -306,6 +326,8 @@ module.exports = function(RED) { this.quiet = false; // MUC == Multi-User-Chat == chatroom this.muc = this.join && (this.from !== "") + // list of possible rooms - queried from server + this.roomsFound = {}; var node = this; var xmpp = this.serverConfig.client; @@ -324,24 +346,30 @@ module.exports = function(RED) { */ // if we're already connected, then do the actions now, otherwise register a callback - if(xmpp.status === "online") { - node.status({fill:"green",shape:"dot",text:"node-red:common.status.connected"}); - if(node.muc) { - joinMUC(node, xmpp, node.from+'/'+node.nick); - } - } + // if (xmpp.status === "online") { + // node.status({fill:"green",shape:"dot",text:"node-red:common.status.connected"}); + // if (node.muc) { + // joinMUC(node, xmpp, node.from+'/'+node.nick); + // } + // } // sod it, register it anyway, that way things will work better on a reconnect: xmpp.on('online', async address => { node.quiet = false; node.status({fill:"green",shape:"dot",text:"node-red:common.status.connected"}); if (node.muc) { - // if we want to use a chatroom, we need to tell the server we want to join it - joinMUC(node, xmpp, node.from+'/'+node.nick); + if (node.from.toUpperCase() === "ALL_ROOMS") { + // try to get list of all rooms and join them all. + getItems(this.serverConfig.server,this.serverConfig.id,xmpp); + } + else { + // if we want to use a chatroom, we need to tell the server we want to join it + joinMUC(node, xmpp, node.from+'/'+node.nick); + } } }); xmpp.on('connecting', async address => { - if(!node.quiet) { + if (!node.quiet) { node.status({fill:"grey",shape:"dot",text:"node-red:common.status.connecting"}); } }); @@ -372,8 +400,7 @@ module.exports = function(RED) { }); // Meat of it, a stanza object contains chat messages (and other things) - xmpp.on('stanza', async (stanza) =>{ - // node.log("Received stanza"); + xmpp.on('stanza', async (stanza) => { if (RED.settings.verbose || LOGITALL) {node.log(stanza); } if (stanza.is('message')) { if (stanza.attrs.type == 'chat') { @@ -385,7 +412,7 @@ module.exports = function(RED) { msg.topic = stanza.attrs.from } else { msg.topic = ids[0]; } - // if (RED.settings.verbose || LOGITALL) {node.log("Received a message from "+stanza.attrs.from); } + // if (RED.settings.verbose || LOGITALL) {node.log("Received a message from "+stanza.attrs.from); } if (!node.join && ((node.from === "") || (node.from === stanza.attrs.to))) { node.send([msg,null]); } @@ -397,12 +424,12 @@ module.exports = function(RED) { var from = parts[1]; var body = stanza.getChild('body'); var payload = ""; - if ("undefined" !== typeof body) { + if (typeof body !== "undefined") { payload = body.getText(); } var msg = { topic:from, payload:payload, room:conference }; - if (stanza.attrs.from != node.nick) { - if ((node.join) && (node.from === conference)) { + if (from && stanza.attrs.from != node.nick && from != node.nick) { + if (node.from.toUpperCase() === "ALL_ROOMS" || node.from === conference) { node.send([msg,null]); } } @@ -411,26 +438,28 @@ module.exports = function(RED) { else if (stanza.is('presence')) { if (['subscribe','subscribed','unsubscribe','unsubscribed'].indexOf(stanza.attrs.type) > -1) { // this isn't for us, let the config node deal with it. - } - else{ + else { + var state = stanza.getChild('show'); + if (state) { state = state.getText(); } + else { state = "available"; } var statusText=""; if (stanza.attrs.type === 'unavailable') { // the user might not exist, but the server doesn't tell us that! statusText = "offline"; + state = "offline"; } var status = stanza.getChild('status'); - if ("undefined" !== typeof status) { + if (typeof status !== "undefined") { statusText = status.getText(); } // right, do we care if there's no status? if (statusText !== "") { var from = stanza.attrs.from; - var state = stanza.attrs.show; var msg = {topic:from, payload: {presence:state, status:statusText} }; node.send([null,msg]); } - else{ + else { if (RED.settings.verbose || LOGITALL) { node.log("not propagating blank status"); node.log(stanza); @@ -438,6 +467,32 @@ module.exports = function(RED) { } } } + else if (stanza.attrs.type === 'result') { + // AM To-Do check for 'bind' result with our current jid + var query = stanza.getChild('query'); + if (RED.settings.verbose || LOGITALL) {this.log("result!"); } + if (RED.settings.verbose || LOGITALL) {this.log(query); } + + // handle query for list of rooms available + if (query && query.attrs.hasOwnProperty("xmlns") && query.attrs["xmlns"] === "http://jabber.org/protocol/disco#items") { + var _items = stanza.getChild('query').getChildren('item'); + for (var i = 0; i<_items.length; i++) { + if ( _items[i].attrs.jid.indexOf('@') === -1 ) { + // if no @ in jid then it's probably the root or the room server so ask again + getItems(_items[i].attrs.jid,this.serverConfig.jid,xmpp); + } + else { + node.roomsFound[_items[i].attrs.name] = _items[i].attrs.jid; + var name = _items[i].attrs.jid+'/'+node.serverConfig.username; + if (!(name in node.serverConfig.MUCs)) { + if (RED.settings.verbose || LOGITALL) {this.log("Need to Join room:"+name); } + joinMUC(node, xmpp, name) + } + } + } + if (RED.settings.verbose || LOGITALL) {this.log("ROOMS:"+this.server+this.roomsFound); } + } + } }); // xmpp.on('subscribe', from => { @@ -451,7 +506,7 @@ module.exports = function(RED) { if (xmpp.status === "online") { node.status({fill:"green",shape:"dot",text:"node-red:common.status.connected"}); } - else{ + else { node.status({fill:"grey",shape:"dot",text:"node-red:common.status.connecting"}); if (xmpp.status === "offline") { if (RED.settings.verbose || LOGITALL) { @@ -504,23 +559,25 @@ module.exports = function(RED) { */ // if we're already connected, then do the actions now, otherwise register a callback - if(xmpp.status === "online") { - node.status({fill:"green",shape:"dot",text:"node-red:common.status.connected"}); - if(node.muc){ - joinMUC(node, xmpp, node.to+'/'+node.nick); - } - } + // if (xmpp.status === "online") { + // node.status({fill:"green",shape:"dot",text:"node-red:common.status.connected"}); + // if (node.muc){ + // // if we want to use a chatroom, we need to tell the server we want to join it + // joinMUC(node, xmpp, node.from+'/'+node.nick); + // } + // } // sod it, register it anyway, that way things will work better on a reconnect: xmpp.on('online', function(data) { node.quiet = false; node.status({fill:"green",shape:"dot",text:"node-red:common.status.connected"}); if (node.muc) { - joinMUC(node, xmpp,node.to+"/"+node.nick); + // if we want to use a chatroom, we need to tell the server we want to join it + joinMUC(node, xmpp, node.from+'/'+node.nick); } }); xmpp.on('connecting', async address => { - if(!node.quiet) { + if (!node.quiet) { node.status({fill:"grey",shape:"dot",text:"node-red:common.status.connecting"}); } }); @@ -555,7 +612,7 @@ module.exports = function(RED) { if (xmpp.status === "online") { node.status({fill:"green",shape:"dot",text:"online"}); } - else{ + else { node.status({fill:"grey",shape:"dot",text:"node-red:common.status.connecting"}); if (xmpp.status === "offline") { xmpp.start().catch(error => { @@ -571,9 +628,7 @@ module.exports = function(RED) { node.on("input", function(msg) { if (msg.presence) { if (['away', 'dnd', 'xa', 'chat'].indexOf(msg.presence) > -1 ) { - var stanza = xml('presence', - {"show":msg.presence}, - xml('status',{},msg.payload)); + var stanza = xml('presence', {"show":msg.presence}, xml('status', {}, msg.payload)); node.serverConfig.used(node); xmpp.send(stanza); } @@ -581,22 +636,22 @@ module.exports = function(RED) { } else if (msg.command) { if (msg.command === "subscribe") { - var stanza = xml('presence', - {type:'subscribe', to: msg.payload}); + var stanza = xml('presence', {type:'subscribe', to:msg.payload}); node.serverConfig.used(node); xmpp.send(stanza); } else if (msg.command === "get") { var to = node.to || msg.topic || ""; var stanza = xml('iq', - {type:'get', id:node.id, to: to}, + {type:'get', id:node.id, to:to}, xml('query', 'http://jabber.org/protocol/muc#admin', - xml('item',{affiliation:msg.payload}))); + xml('item', {affiliation:msg.payload}) + ) + ); node.serverConfig.used(node); if (RED.settings.verbose || LOGITALL) {node.log("sending stanza "+stanza.toString()); } xmpp.send(stanza); } - } else { var to = node.to || msg.topic || ""; diff --git a/social/xmpp/package.json b/social/xmpp/package.json index ffd2b95e..7d1aa843 100644 --- a/social/xmpp/package.json +++ b/social/xmpp/package.json @@ -1,6 +1,6 @@ { "name": "node-red-node-xmpp", - "version": "0.3.4", + "version": "0.4.0", "description": "A Node-RED node to talk to an XMPP server", "dependencies": { "@xmpp/client": "^0.12.0" From 7485760db97a8e3d1d29150e50596eb8977050e8 Mon Sep 17 00:00:00 2001 From: Dave Conway-Jones Date: Tue, 23 Feb 2021 11:37:53 +0000 Subject: [PATCH 15/69] xmpp node - use empty field to signify all rooms --- social/xmpp/92-xmpp.html | 2 +- social/xmpp/92-xmpp.js | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/social/xmpp/92-xmpp.html b/social/xmpp/92-xmpp.html index e7659951..1b81a8ef 100644 --- a/social/xmpp/92-xmpp.html +++ b/social/xmpp/92-xmpp.html @@ -17,7 +17,7 @@ -
Note: By setting Buddy to "ALL_ROOMS" and ticking "Is a chat room", +
Note: By leaving Buddy empty and ticking "Is a chat room", the node will try to listen to all the rooms the user has access to.
diff --git a/social/xmpp/92-xmpp.js b/social/xmpp/92-xmpp.js index 60df52d0..d056ef51 100644 --- a/social/xmpp/92-xmpp.js +++ b/social/xmpp/92-xmpp.js @@ -322,10 +322,10 @@ module.exports = function(RED) { this.join = n.join || false; this.sendAll = n.sendObject; // Yes, it's called "from", don't ask me why; I don't know why - this.from = n.to || ""; + this.from = (n.to || "").trim(); this.quiet = false; // MUC == Multi-User-Chat == chatroom - this.muc = this.join && (this.from !== "") + //this.muc = this.join && (this.from !== "") // list of possible rooms - queried from server this.roomsFound = {}; var node = this; @@ -356,8 +356,8 @@ module.exports = function(RED) { xmpp.on('online', async address => { node.quiet = false; node.status({fill:"green",shape:"dot",text:"node-red:common.status.connected"}); - if (node.muc) { - if (node.from.toUpperCase() === "ALL_ROOMS") { + if (node.join) { + if (node.from === "") { // try to get list of all rooms and join them all. getItems(this.serverConfig.server,this.serverConfig.id,xmpp); } @@ -428,11 +428,11 @@ module.exports = function(RED) { payload = body.getText(); } var msg = { topic:from, payload:payload, room:conference }; - if (from && stanza.attrs.from != node.nick && from != node.nick) { - if (node.from.toUpperCase() === "ALL_ROOMS" || node.from === conference) { - node.send([msg,null]); - } + //if (from && stanza.attrs.from != node.nick && from != node.nick) { + if (from && node.join && (node.from === "" || node.from === conference)) { + node.send([msg,null]); } + //} } } else if (stanza.is('presence')) { From 40362ee985a52290883176acdde7c7def8cc844a Mon Sep 17 00:00:00 2001 From: Dave Conway-Jones Date: Tue, 23 Feb 2021 12:45:55 +0000 Subject: [PATCH 16/69] Let xmpp in handle a list of rooms --- social/xmpp/92-xmpp.html | 1 + social/xmpp/92-xmpp.js | 13 ++++++++----- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/social/xmpp/92-xmpp.html b/social/xmpp/92-xmpp.html index 1b81a8ef..dbf3eb9a 100644 --- a/social/xmpp/92-xmpp.html +++ b/social/xmpp/92-xmpp.html @@ -19,6 +19,7 @@
Note: By leaving Buddy empty and ticking "Is a chat room", the node will try to listen to all the rooms the user has access to. + You can specify multiple rooms by separating them by a :
diff --git a/social/xmpp/92-xmpp.js b/social/xmpp/92-xmpp.js index d056ef51..ce6b90d9 100644 --- a/social/xmpp/92-xmpp.js +++ b/social/xmpp/92-xmpp.js @@ -322,7 +322,8 @@ module.exports = function(RED) { this.join = n.join || false; this.sendAll = n.sendObject; // Yes, it's called "from", don't ask me why; I don't know why - this.from = (n.to || "").trim(); + // (because it's where you are asking to get messages from...) + this.from = ((n.to || "").split(':')).map(s => s.trim()); this.quiet = false; // MUC == Multi-User-Chat == chatroom //this.muc = this.join && (this.from !== "") @@ -357,13 +358,15 @@ module.exports = function(RED) { node.quiet = false; node.status({fill:"green",shape:"dot",text:"node-red:common.status.connected"}); if (node.join) { - if (node.from === "") { + if (node.from[0] === "") { // try to get list of all rooms and join them all. getItems(this.serverConfig.server,this.serverConfig.id,xmpp); } else { // if we want to use a chatroom, we need to tell the server we want to join it - joinMUC(node, xmpp, node.from+'/'+node.nick); + for (var i=0; i Date: Tue, 23 Feb 2021 22:15:57 +0000 Subject: [PATCH 17/69] add info command to xmpp nodes --- social/xmpp/92-xmpp.js | 52 +++++++++++++++++++++++++----------------- 1 file changed, 31 insertions(+), 21 deletions(-) diff --git a/social/xmpp/92-xmpp.js b/social/xmpp/92-xmpp.js index ce6b90d9..e575ee90 100644 --- a/social/xmpp/92-xmpp.js +++ b/social/xmpp/92-xmpp.js @@ -161,12 +161,6 @@ module.exports = function(RED) { that.lastUsed.warn(stanza.getChild('error')); } } - else if (stanza.attrs.type === 'result') { - // AM To-Do check for 'bind' result with our current jid - var query = stanza.getChild('query'); - if (RED.settings.verbose || LOGITALL) {that.log("result!"); } - if (RED.settings.verbose || LOGITALL) {that.log(query); } - } } }); @@ -225,15 +219,6 @@ module.exports = function(RED) { xmpp.send(stanza); } - function getInfo(thing,id,xmpp) { - // Now try to get a list of all info about a thing - var stanza = xml('iq', - {type:'get', id:id, to:thing}, - xml('query', 'http://jabber.org/protocol/disco#info') - ); - xmpp.send(stanza); - } - function joinMUC(node, xmpp, name) { // the presence with the muc x element signifies we want to join the muc // if we want to support passwords, we need to add that as a child of the x element @@ -404,7 +389,7 @@ module.exports = function(RED) { // Meat of it, a stanza object contains chat messages (and other things) xmpp.on('stanza', async (stanza) => { - if (RED.settings.verbose || LOGITALL) {node.log(stanza); } + if (RED.settings.verbose || LOGITALL) { node.log(stanza); } if (stanza.is('message')) { if (stanza.attrs.type == 'chat') { var body = stanza.getChild('body'); @@ -415,7 +400,7 @@ module.exports = function(RED) { msg.topic = stanza.attrs.from } else { msg.topic = ids[0]; } - // if (RED.settings.verbose || LOGITALL) {node.log("Received a message from "+stanza.attrs.from); } + // if (RED.settings.verbose || LOGITALL) { node.log("Received a message from "+stanza.attrs.from); } if (!node.join && ((node.from[0] === "") || (node.from.includes(stanza.attrs.to)))) { node.send([msg,null]); } @@ -473,8 +458,8 @@ module.exports = function(RED) { else if (stanza.attrs.type === 'result') { // AM To-Do check for 'bind' result with our current jid var query = stanza.getChild('query'); - if (RED.settings.verbose || LOGITALL) {this.log("result!"); } - if (RED.settings.verbose || LOGITALL) {this.log(query); } + if (RED.settings.verbose || LOGITALL) { this.log("result!"); } + if (RED.settings.verbose || LOGITALL) { this.log(query); } // handle query for list of rooms available if (query && query.attrs.hasOwnProperty("xmlns") && query.attrs["xmlns"] === "http://jabber.org/protocol/disco#items") { @@ -495,6 +480,21 @@ module.exports = function(RED) { } if (RED.settings.verbose || LOGITALL) {this.log("ROOMS:"+this.server+this.roomsFound); } } + if (query && query.attrs.hasOwnProperty("xmlns") && query.attrs["xmlns"] === "http://jabber.org/protocol/disco#info") { + var fe = []; + var _items = stanza.getChild('query').getChildren('feature'); + for (var i = 0; i<_items.length; i++) { + fe.push(_items[i].attrs); + } + var id = [] + var _idents = stanza.getChild('query').getChildren('identity'); + for (var i = 0; i<_idents.length; i++) { + id.push(_idents[i].attrs); + } + var from = stanza.attrs.from; + var msg = {topic:from, payload: { identity:id, features:fe} }; + node.send([null,msg]); + } } }); @@ -652,7 +652,17 @@ module.exports = function(RED) { ) ); node.serverConfig.used(node); - if (RED.settings.verbose || LOGITALL) {node.log("sending stanza "+stanza.toString()); } + if (RED.settings.verbose || LOGITALL) { node.log("sending stanza "+stanza.toString()); } + xmpp.send(stanza); + } + else if (msg.command === "info") { + var to = node.to || msg.topic || ""; + var stanza = xml('iq', + {type:'get', id:node.id, to:to}, + xml('query', 'http://jabber.org/protocol/disco#info') + ); + node.serverConfig.used(node); + if (RED.settings.verbose || LOGITALL) { node.log("sending stanza "+stanza.toString()); } xmpp.send(stanza); } } @@ -698,7 +708,7 @@ module.exports = function(RED) { }); node.on("close", function(removed, done) { - if (RED.settings.verbose || LOGITALL) {node.log("Closing"); } + if (RED.settings.verbose || LOGITALL) { node.log("Closing"); } node.status({fill:"red",shape:"ring",text:"node-red:common.status.disconnected"}); node.serverConfig.deregister(node, done); }); From c368e3bcd472a567885dda060c618cb94fa995dd Mon Sep 17 00:00:00 2001 From: Dave Conway-Jones Date: Thu, 25 Feb 2021 09:05:11 +0000 Subject: [PATCH 18/69] xmpp auto create rooms correctly and auto join every 60 secs if required --- social/xmpp/92-xmpp.js | 146 ++++++++++++++++++++++++--------------- social/xmpp/package.json | 2 +- 2 files changed, 91 insertions(+), 57 deletions(-) diff --git a/social/xmpp/92-xmpp.js b/social/xmpp/92-xmpp.js index e575ee90..d6b2bb0c 100644 --- a/social/xmpp/92-xmpp.js +++ b/social/xmpp/92-xmpp.js @@ -97,6 +97,7 @@ module.exports = function(RED) { // this means we need to figure out which node might have sent it // we also deal with subscriptions (i.e. presence information) here this.client.on('stanza', async (stanza) => { + //console.log("STAN",stanza.toString()) if (stanza.is('message')) { if (stanza.attrs.type == 'error') { if (RED.settings.verbose || LOGITALL) { @@ -106,27 +107,27 @@ module.exports = function(RED) { var err = stanza.getChild('error'); if (err) { var textObj = err.getChild('text'); - var text = "node-red:common.status.error"; + var text = "error"; if (typeof textObj !== "undefined") { text = textObj.getText(); } else { - textObj = err.getChild('code'); + textObj = err.getAttr('code'); if (typeof textObj !== "undefined") { - text = textObj.getText(); + text = textObj; } } if (RED.settings.verbose || LOGITALL) {that.log("Culprit: "+that.lastUsed.id); } if (typeof that.lastUsed !== "undefined") { - that.lastUsed.status({fill:"red",shape:"ring",text:text}); - that.lastUsed.warn(text); + that.lastUsed.status({fill:"red",shape:"ring",text:"error "+text}); + that.lastUsed.warn("Error "+text); if (that.lastUsed.join) { // it was trying to MUC things up clearMUC(that); } } if (RED.settings.verbose || LOGITALL) { - that.log("We did wrong: "+text); + that.log("We did wrong: Error "+text); that.log(stanza); } @@ -151,6 +152,24 @@ module.exports = function(RED) { that.log("Was told we've "+stanza.attrs.type+" from "+stanza.attrs.from+" but we don't really care"); } } + if (stanza.attrs.to.indexOf(that.jid) !== -1) { + var _x = stanza.getChild("x") + if (_x !== undefined) { + var _stat = _x.getChildren("status"); + for (var i = 0; i<_stat.length; i++) { + if (_stat[i].attrs.code == 201) { + if (RED.settings.verbose || LOGITALL) {that.log("created new room"); } + var stanza = xml('iq', + {type:'set', id:that.id, from:that.jid, to:stanza.attrs.from.split('/')[0]}, + xml('query', 'http://jabber.org/protocol/muc#owner', + xml('x', {xmlns:'jabber:x:data', type:'submit'}) + ) + ); + that.client.send(stanza); + } + } + } + } } else if (stanza.is('iq')) { if (RED.settings.verbose || LOGITALL) {that.log("got an iq query"); } @@ -188,18 +207,22 @@ module.exports = function(RED) { // gets called when the node is destroyed, e.g. if N-R is being stopped. this.on("close", async done => { - if (that.client.connected) { - await that.client.send(xml('presence', {type: 'unavailable'})); - try{ - if (RED.settings.verbose || LOGITALL) { - that.log("Calling stop() after close, status is "+that.client.status); - } - await that.client.stop().then(that.log("XMPP client stopped")).catch(error=>{that.warn("Got an error whilst closing xmpp session: "+error)}); - } - catch(e) { - that.warn(e); - } + const rooms = Object.keys(this.MUCs) + for (const room of rooms) { + await that.client.send(xml('presence', {to:room, type:'unavailable'})); } + // if (that.client.connected) { + await that.client.send(xml('presence', {type:'unavailable'})); + try{ + if (RED.settings.verbose || LOGITALL) { + that.log("Calling stop() after close, status is "+that.client.status); + } + await that.client.stop().then(that.log("XMPP client stopped")).catch(error=>{that.warn("Got an error whilst closing xmpp session: "+error)}); + } + catch(e) { + that.warn(e); + } + // } done(); }); } @@ -227,9 +250,7 @@ module.exports = function(RED) { // Yes, there's a race condition, but it's not a huge problem to send two messages // so we don't care. if (name in node.serverConfig.MUCs) { - if (RED.settings.verbose || LOGITALL) { - node.log("already joined MUC "+name); - } + if (RED.settings.verbose || LOGITALL) { node.log("already joined MUC "+name); } } else { var stanza = xml('presence', @@ -240,8 +261,8 @@ module.exports = function(RED) { ); node.serverConfig.used(node); node.serverConfig.MUCs[name] = "joined"; + if (RED.settings.verbose || LOGITALL) { node.log("JOINED "+name); } xmpp.send(stanza); - if (RED.settings.verbose || LOGITALL) { node.log("JOINED",name); } } } @@ -293,7 +314,7 @@ module.exports = function(RED) { // nothing we've seen before! else { node.error("Unknown error: "+err,err); - node.status({fill:"red",shape:"ring",text:"node-red:common.status.error"}); + node.status({fill:"red",shape:"ring",text:"error"}); } } } @@ -312,10 +333,21 @@ module.exports = function(RED) { this.quiet = false; // MUC == Multi-User-Chat == chatroom //this.muc = this.join && (this.from !== "") - // list of possible rooms - queried from server - this.roomsFound = {}; var node = this; + var joinrooms = function() { + if (node.from[0] === "") { + // try to get list of all rooms and join them all. + getItems(node.serverConfig.server, node.serverConfig.id, xmpp); + } + else { + // if we want to use a chatroom, we need to tell the server we want to join it + for (var i=0; i { node.quiet = false; - node.status({fill:"green",shape:"dot",text:"node-red:common.status.connected"}); + node.status({fill:"green",shape:"dot",text:"connected"}); if (node.join) { - if (node.from[0] === "") { - // try to get list of all rooms and join them all. - getItems(this.serverConfig.server,this.serverConfig.id,xmpp); - } - else { - // if we want to use a chatroom, we need to tell the server we want to join it - for (var i=0; i { if (!node.quiet) { - node.status({fill:"grey",shape:"dot",text:"node-red:common.status.connecting"}); + node.status({fill:"grey",shape:"dot",text:"connecting"}); } }); xmpp.on('connect', async address => { - node.status({fill:"grey",shape:"dot",text:"node-red:common.status.connected"}); + node.status({fill:"grey",shape:"dot",text:"connected"}); }); xmpp.on('opening', async address => { node.status({fill:"grey",shape:"dot",text:"opening"}); @@ -374,7 +398,7 @@ module.exports = function(RED) { node.status({fill:"grey",shape:"dot",text:"closing"}); }); xmpp.on('close', async address => { - node.status({fill:"grey",shape:"dot",text:"closed"}); + node.status({fill:"grey",shape:"ring",text:"closed"}); }); xmpp.on('disconnecting', async address => { node.status({fill:"grey",shape:"dot",text:"disconnecting"}); @@ -391,6 +415,7 @@ module.exports = function(RED) { xmpp.on('stanza', async (stanza) => { if (RED.settings.verbose || LOGITALL) { node.log(stanza); } if (stanza.is('message')) { + // console.log(stanza.toString()) if (stanza.attrs.type == 'chat') { var body = stanza.getChild('body'); if (body) { @@ -470,15 +495,16 @@ module.exports = function(RED) { getItems(_items[i].attrs.jid,this.serverConfig.jid,xmpp); } else { - node.roomsFound[_items[i].attrs.name] = _items[i].attrs.jid; var name = _items[i].attrs.jid+'/'+node.serverConfig.username; if (!(name in node.serverConfig.MUCs)) { - if (RED.settings.verbose || LOGITALL) {this.log("Need to Join room:"+name); } - joinMUC(node, xmpp, name) + if (RED.settings.verbose || LOGITALL) { node.log("Need to Join room:"+name); } + joinMUC(node, xmpp, name); + } + else { + if (RED.settings.verbose || LOGITALL) { node.log("Already joined:"+name); } } } } - if (RED.settings.verbose || LOGITALL) {this.log("ROOMS:"+this.server+this.roomsFound); } } if (query && query.attrs.hasOwnProperty("xmlns") && query.attrs["xmlns"] === "http://jabber.org/protocol/disco#info") { var fe = []; @@ -507,26 +533,30 @@ module.exports = function(RED) { // Now actually make the connection try { if (xmpp.status === "online") { - node.status({fill:"green",shape:"dot",text:"node-red:common.status.connected"}); + node.status({fill:"green",shape:"dot",text:"connected"}); } else { - node.status({fill:"grey",shape:"dot",text:"node-red:common.status.connecting"}); + node.status({fill:"grey",shape:"dot",text:"connecting"}); if (xmpp.status === "offline") { if (RED.settings.verbose || LOGITALL) { node.log("starting xmpp client"); } - xmpp.start().catch(error => {node.warn("Got error on start: "+error); node.warn("XMPP Status is now: "+xmpp.status)}); + xmpp.start().catch(error => { + node.warn("Got error on start: "+error); + node.warn("XMPP Status is now: "+xmpp.status) + }); } } } catch(e) { node.error("Bad xmpp configuration; service: "+xmpp.options.service+" jid: "+node.serverConfig.jid); node.warn(e.stack); - node.status({fill:"red",shape:"ring",text:"node-red:common.status.disconnected"}); + node.status({fill:"red",shape:"ring",text:"disconnected"}); } node.on("close", function(removed, done) { - node.status({fill:"red",shape:"ring",text:"node-red:common.status.disconnected"}); + if (node.jointick) { clearInterval(node.jointick); } + node.status({fill:"grey",shape:"ring",text:"disconnected"}); node.serverConfig.deregister(node, done); }); } @@ -563,7 +593,7 @@ module.exports = function(RED) { // if we're already connected, then do the actions now, otherwise register a callback // if (xmpp.status === "online") { - // node.status({fill:"green",shape:"dot",text:"node-red:common.status.connected"}); + // node.status({fill:"green",shape:"dot",text:"connected"}); // if (node.muc){ // // if we want to use a chatroom, we need to tell the server we want to join it // joinMUC(node, xmpp, node.from+'/'+node.nick); @@ -572,7 +602,7 @@ module.exports = function(RED) { // sod it, register it anyway, that way things will work better on a reconnect: xmpp.on('online', function(data) { node.quiet = false; - node.status({fill:"green",shape:"dot",text:"node-red:common.status.connected"}); + node.status({fill:"green",shape:"dot",text:"connected"}); if (node.muc) { // if we want to use a chatroom, we need to tell the server we want to join it joinMUC(node, xmpp, node.from+'/'+node.nick); @@ -581,11 +611,11 @@ module.exports = function(RED) { xmpp.on('connecting', async address => { if (!node.quiet) { - node.status({fill:"grey",shape:"dot",text:"node-red:common.status.connecting"}); + node.status({fill:"grey",shape:"dot",text:"connecting"}); } }); xmpp.on('connect', async address => { - node.status({fill:"grey",shape:"dot",text:"node-red:common.status.connected"}); + node.status({fill:"grey",shape:"dot",text:"connected"}); }); xmpp.on('opening', async address => { node.status({fill:"grey",shape:"dot",text:"opening"}); @@ -609,6 +639,11 @@ module.exports = function(RED) { errorHandler(node, err) }); + xmpp.on('stanza', async (stanza) => { + // if (stanza.is('presence')) { + // } + }); + //register with config this.serverConfig.register(this); // Now actually make the connection @@ -616,13 +651,13 @@ module.exports = function(RED) { node.status({fill:"green",shape:"dot",text:"online"}); } else { - node.status({fill:"grey",shape:"dot",text:"node-red:common.status.connecting"}); + node.status({fill:"grey",shape:"dot",text:"connecting"}); if (xmpp.status === "offline") { xmpp.start().catch(error => { node.error("Bad xmpp configuration; service: "+xmpp.options.service+" jid: "+node.serverConfig.jid); node.warn(error); node.warn(error.stack); - node.status({fill:"red",shape:"ring",text:"node-red:common.status.error"}); + node.status({fill:"red",shape:"ring",text:"error"}); }); } } @@ -683,7 +718,6 @@ module.exports = function(RED) { { type: type, to: to }, xml("body", {}, JSON.stringify(msg)) ); - } else if (msg.payload) { if (typeof(msg.payload) === "object") { @@ -709,7 +743,7 @@ module.exports = function(RED) { node.on("close", function(removed, done) { if (RED.settings.verbose || LOGITALL) { node.log("Closing"); } - node.status({fill:"red",shape:"ring",text:"node-red:common.status.disconnected"}); + node.status({fill:"grey",shape:"ring",text:"disconnected"}); node.serverConfig.deregister(node, done); }); } diff --git a/social/xmpp/package.json b/social/xmpp/package.json index 7d1aa843..c249eb03 100644 --- a/social/xmpp/package.json +++ b/social/xmpp/package.json @@ -1,6 +1,6 @@ { "name": "node-red-node-xmpp", - "version": "0.4.0", + "version": "0.4.1", "description": "A Node-RED node to talk to an XMPP server", "dependencies": { "@xmpp/client": "^0.12.0" From d69da748437b59fc5e8dc9298af087a1043cc761 Mon Sep 17 00:00:00 2001 From: Pablo Acosta-Serafini <63065092+pmacostapdi@users.noreply.github.com> Date: Fri, 26 Feb 2021 09:38:28 -0500 Subject: [PATCH 19/69] timeswitch node: time zone support; do not mark as misconfigured when sunrise/sunset not used and lat/lon not given (#757) * timeswitch node: a) do not mark node as misconfigured if sunrise and sunset are not used and latitude/longitude are not given. b) support for specifying time zone when on and/or off times are not specified as sunrise and/or sunset. * Replaced moment dependency with spacetime * Timezone defaults to UTC for compatibility with previous node version --- time/timeswitch/package.json | 1 + time/timeswitch/timeswitch.html | 646 +++++++++++++++++++++++++++++++- time/timeswitch/timeswitch.js | 9 +- 3 files changed, 649 insertions(+), 7 deletions(-) diff --git a/time/timeswitch/package.json b/time/timeswitch/package.json index acaf2bf0..6d0fca3b 100644 --- a/time/timeswitch/package.json +++ b/time/timeswitch/package.json @@ -3,6 +3,7 @@ "version" : "0.0.8", "description" : "A Node-RED node to provide a simple timeswitch to schedule daily on/off events.", "dependencies" : { + "spacetime": "^6.12.5", "suncalc": "^1.8.0" }, "repository" : { diff --git a/time/timeswitch/timeswitch.html b/time/timeswitch/timeswitch.html index 910cca6a..52506229 100644 --- a/time/timeswitch/timeswitch.html +++ b/time/timeswitch/timeswitch.html @@ -214,6 +214,616 @@ +
+ + +
+
@@ -280,6 +890,15 @@ @@ -73,6 +90,10 @@
+
+ + +
@@ -98,6 +119,9 @@ join: {value:false}, sendObject: {value:false} }, + credentials: { + password: {type:"password"} + }, inputs:1, outputs:0, icon: "xmpp.png", @@ -107,6 +131,16 @@ }, labelStyle: function() { return (this.name)?"node_label_italic":""; + }, + oneditprepare: function() { + $('#node-input-join').change(function() { + if ($("#node-input-join").is(':checked') && $("#node-input-to").val() && $("#node-input-to").val().indexOf(':') === -1) { $("#node-room-pwd").show(); } + else { $("#node-room-pwd").hide(); } + }); + $('#node-input-to').change(function() { + if ($("#node-input-join").is(':checked') && $("#node-input-to").val() && $("#node-input-to").val().indexOf(':') === -1) { $("#node-room-pwd").show(); } + else { $("#node-room-pwd").hide(); } + }); } }); @@ -127,7 +161,7 @@
- +
diff --git a/social/xmpp/92-xmpp.js b/social/xmpp/92-xmpp.js index 7219c06e..d7eb0f7b 100644 --- a/social/xmpp/92-xmpp.js +++ b/social/xmpp/92-xmpp.js @@ -119,8 +119,8 @@ module.exports = function(RED) { } if (RED.settings.verbose || LOGITALL) {that.log("Culprit: "+that.lastUsed.id); } if (typeof that.lastUsed !== "undefined") { - that.lastUsed.status({fill:"red",shape:"ring",text:"error "+text}); - that.lastUsed.warn("Error "+text); + that.lastUsed.status({fill:"yellow",shape:"dot",text:"warning. "+text}); + that.lastUsed.warn("Warning. "+text); if (that.lastUsed.join) { // it was trying to MUC things up clearMUC(that); @@ -260,6 +260,15 @@ module.exports = function(RED) { xml("history", {maxstanzas:0, seconds:1}) // We don't want any history ) ); + if (node.hasOwnProperty("credentials") && node.credentials.hasOwnProperty("password")) { + stanza = xml('presence', + {"to":name}, + xml("x",'http://jabber.org/protocol/muc', + xml("history", {maxstanzas:0, seconds:1}), // We don't want any history + xml("password", {}, node.credentials.password) // Add the password + ) + ); + } node.serverConfig.used(node); node.serverConfig.MUCs[mu] = "joined"; if (RED.settings.verbose || LOGITALL) { node.log("JOINED "+mu); } @@ -458,6 +467,29 @@ module.exports = function(RED) { // this isn't for us, let the config node deal with it. } else { + if (stanza.attrs.type === 'error') { + var error = stanza.getChild('error'); + if (error.attrs.code) { + try { + var reas = error.toString().split('><')[1].split(" xml")[0].trim(); + if (reas == "registration-required") { reas = "membership-required"; } + } + catch(e) {}; + var msg = { + topic:stanza.attrs.from, + payload: { + code:error.attrs.code, + status:"error", + reason:reas, + name:node.serverConfig.MUCs[stanza.attrs.from.split('/')[0]] + } + }; + node.send([null,msg]); + node.status({fill:"red",shape:"ring",text:"error : "+error.attrs.code+", "+error.attrs.type+", "+reas}); + node.error(error.attrs.type+" error. "+error.attrs.code+" "+reas,msg); + } + } + var state = stanza.getChild('show'); if (state) { state = state.getText(); } else { state = "available"; } @@ -573,7 +605,11 @@ module.exports = function(RED) { node.serverConfig.deregister(node, done); }); } - RED.nodes.registerType("xmpp in",XmppInNode); + RED.nodes.registerType("xmpp in",XmppInNode,{ + credentials: { + password: {type: "password"} + } + }); function XmppOutNode(n) { @@ -653,8 +689,27 @@ module.exports = function(RED) { }); xmpp.on('stanza', async (stanza) => { - // if (stanza.is('presence')) { - // } + if (stanza.attrs.type === 'error') { + var error = stanza.getChild('error'); + if (error.attrs.code) { + try { + var reas = error.toString().split('><')[1].split(" xml")[0].trim(); + if (reas == "registration-required") { reas = "membership-required"; } + } + catch(e) {}; + var msg = { + topic:stanza.attrs.from, + payload: { + code:error.attrs.code, + status:"error", + reason:reas, + name:node.serverConfig.MUCs[stanza.attrs.from.split('/')[0]] + } + }; + node.status({fill:"red",shape:"ring",text:"error : "+error.attrs.code+", "+error.attrs.type+", "+reas}); + node.error(error.attrs.type+" error. "+error.attrs.code+" "+reas,msg); + } + } }); //register with config @@ -723,16 +778,15 @@ module.exports = function(RED) { // joinMUC will do nothing if we're already joined joinMUC(node, xmpp, to+'/'+node.nick); } - // if (msg.subject) { - // var stanza = xml( - // "message", - // { type:type, to:to }, - // xml("subject", {}, msg.subject.toString()) - // ); - // node.serverConfig.used(node); - // console.log("SENDING",stanza.toString()) - // xmpp.send(stanza); - // } + if (msg.subject) { + var stanza = xml( + "message", + { type:type, to:to, from:node.serverConfig.jid }, + xml("subject", {}, msg.subject.toString()) + ); + node.serverConfig.used(node); + xmpp.send(stanza); + } if (node.sendAll) { message = xml( "message", @@ -756,8 +810,10 @@ module.exports = function(RED) { ); } } - node.serverConfig.used(node); - xmpp.send(message); + if (message) { + node.serverConfig.used(node); + xmpp.send(message); + } } } }); @@ -768,5 +824,9 @@ module.exports = function(RED) { node.serverConfig.deregister(node, done); }); } - RED.nodes.registerType("xmpp out",XmppOutNode); + RED.nodes.registerType("xmpp out",XmppOutNode,{ + credentials: { + password: {type: "password"} + } + }); } diff --git a/social/xmpp/package.json b/social/xmpp/package.json index 4bafae0a..67f60f84 100644 --- a/social/xmpp/package.json +++ b/social/xmpp/package.json @@ -1,6 +1,6 @@ { "name": "node-red-node-xmpp", - "version": "0.4.2", + "version": "0.5.0", "description": "A Node-RED node to talk to an XMPP server", "dependencies": { "@xmpp/client": "^0.12.0" From fe42b6971279e3ea212cf7c8b63bc2b9743922a0 Mon Sep 17 00:00:00 2001 From: Dave Conway-Jones Date: Thu, 11 Mar 2021 19:09:13 +0000 Subject: [PATCH 25/69] let daemon accept real json array of parameters to close #768 --- utility/daemon/daemon.html | 1 + utility/daemon/daemon.js | 9 ++++++++- utility/daemon/package.json | 2 +- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/utility/daemon/daemon.html b/utility/daemon/daemon.html index 8b89688c..2d932800 100644 --- a/utility/daemon/daemon.html +++ b/utility/daemon/daemon.html @@ -52,6 +52,7 @@ diff --git a/analysis/mlsentiment/mlsentiment.html b/analysis/mlsentiment/mlsentiment.html index d6b75a8e..c8394fe5 100644 --- a/analysis/mlsentiment/mlsentiment.html +++ b/analysis/mlsentiment/mlsentiment.html @@ -118,35 +118,6 @@ - - diff --git a/function/PID/pidcontrol.html b/function/PID/pidcontrol.html index 06116a32..005808b4 100644 --- a/function/PID/pidcontrol.html +++ b/function/PID/pidcontrol.html @@ -24,14 +24,6 @@ The damping factors are typically in the range 0 - 1.
- - - - diff --git a/function/smooth/17-smooth.html b/function/smooth/17-smooth.html index 75103f05..81643a1b 100644 --- a/function/smooth/17-smooth.html +++ b/function/smooth/17-smooth.html @@ -45,16 +45,6 @@
Tip: This node ONLY works with numbers.
- - diff --git a/hardware/Arduino/35-arduino.html b/hardware/Arduino/35-arduino.html index b492fa28..dd509759 100644 --- a/hardware/Arduino/35-arduino.html +++ b/hardware/Arduino/35-arduino.html @@ -24,14 +24,6 @@
- - - - + + diff --git a/hardware/pigpiod/locales/en-US/pi-gpiod.html b/hardware/pigpiod/locales/en-US/pi-gpiod.html new file mode 100644 index 00000000..471b6c10 --- /dev/null +++ b/hardware/pigpiod/locales/en-US/pi-gpiod.html @@ -0,0 +1,33 @@ + + + + diff --git a/hardware/pigpiod/pi-gpiod.html b/hardware/pigpiod/pi-gpiod.html index 7d5a39f9..113028a5 100644 --- a/hardware/pigpiod/pi-gpiod.html +++ b/hardware/pigpiod/pi-gpiod.html @@ -167,22 +167,6 @@
- - - - + + diff --git a/hardware/sensehat/sensehat.html b/hardware/sensehat/sensehat.html index 41c42dd6..c85cce2b 100644 --- a/hardware/sensehat/sensehat.html +++ b/hardware/sensehat/sensehat.html @@ -1,132 +1,33 @@ - - - - - - - - - - - + + + + + + diff --git a/social/irc/91-irc.html b/social/irc/91-irc.html index 2849914d..f3a00627 100644 --- a/social/irc/91-irc.html +++ b/social/irc/91-irc.html @@ -15,64 +15,6 @@
- - - - - - + + diff --git a/social/notify/57-notify.html b/social/notify/57-notify.html index fe3244a2..db576917 100644 --- a/social/notify/57-notify.html +++ b/social/notify/57-notify.html @@ -10,13 +10,6 @@ - - diff --git a/storage/leveldb/67-leveldb.html b/storage/leveldb/67-leveldb.html index 60b75414..ae7969ce 100644 --- a/storage/leveldb/67-leveldb.html +++ b/storage/leveldb/67-leveldb.html @@ -6,7 +6,7 @@
- +