diff --git a/package.json b/package.json index d997df7..1084edf 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ "matrix-room-kick": "src/matrix-room-kick.js", "matrix-room-ban": "src/matrix-room-ban.js", "matrix-room-users": "src/matrix-room-users.js", - "matrix-room-settings": "src/matrix-room-settings.js", + "matrix-room-state-events": "src/matrix-room-state-events.js", "matrix-synapse-users": "src/matrix-synapse-users.js", "matrix-synapse-register": "src/matrix-synapse-register.js", "matrix-synapse-create-edit-user": "src/matrix-synapse-create-edit-user.js", diff --git a/src/matrix-create-room.js b/src/matrix-create-room.js index f9b7fa2..7ff02b4 100644 --- a/src/matrix-create-room.js +++ b/src/matrix-create-room.js @@ -44,6 +44,7 @@ module.exports = function(RED) { if(!node.server.isConnected()) { node.error("Matrix server connection is currently closed", msg); node.send([null, msg]); + return; } if(!msg.payload) { diff --git a/src/matrix-invite-room.js b/src/matrix-invite-room.js index df6afac..61c7589 100644 --- a/src/matrix-invite-room.js +++ b/src/matrix-invite-room.js @@ -45,6 +45,7 @@ module.exports = function(RED) { if(!node.server.isConnected()) { node.error("Matrix server connection is currently closed", msg); node.send([null, msg]); + return; } msg.topic = node.roomId || msg.topic; diff --git a/src/matrix-join-room.js b/src/matrix-join-room.js index a3b885e..61d54fc 100644 --- a/src/matrix-join-room.js +++ b/src/matrix-join-room.js @@ -32,6 +32,7 @@ module.exports = function(RED) { if(!node.server.isConnected()) { node.error("Matrix server connection is currently closed", msg); node.send([null, msg]); + return; } if(!msg.topic) { diff --git a/src/matrix-leave-room.js b/src/matrix-leave-room.js index ecc171b..daebb33 100644 --- a/src/matrix-leave-room.js +++ b/src/matrix-leave-room.js @@ -38,6 +38,7 @@ module.exports = function(RED) { if(!node.server.isConnected()) { node.error("Matrix server connection is currently closed", msg); node.send([null, msg]); + return; } try { diff --git a/src/matrix-react.js b/src/matrix-react.js index 4ce6b3d..a176277 100644 --- a/src/matrix-react.js +++ b/src/matrix-react.js @@ -34,6 +34,7 @@ module.exports = function(RED) { if(!node.server.isConnected()) { node.error("Matrix server connection is currently closed", msg); node.send([null, msg]); + return; } msg.topic = node.roomId || msg.topic; diff --git a/src/matrix-room-ban.js b/src/matrix-room-ban.js index a7d8435..a0af7db 100644 --- a/src/matrix-room-ban.js +++ b/src/matrix-room-ban.js @@ -34,6 +34,7 @@ module.exports = function(RED) { if(!node.server.isConnected()) { node.error("Matrix server connection is currently closed", msg); node.send([null, msg]); + return; } msg.topic = node.roomId || msg.topic; diff --git a/src/matrix-room-kick.js b/src/matrix-room-kick.js index 926311b..ac3efb0 100644 --- a/src/matrix-room-kick.js +++ b/src/matrix-room-kick.js @@ -34,6 +34,7 @@ module.exports = function(RED) { if(!node.server.isConnected()) { node.error("Matrix server connection is currently closed", msg); node.send([null, msg]); + return; } msg.topic = node.roomId || msg.topic; diff --git a/src/matrix-room-settings.html b/src/matrix-room-settings.html deleted file mode 100644 index edf1c84..0000000 --- a/src/matrix-room-settings.html +++ /dev/null @@ -1,115 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/src/matrix-room-settings.js b/src/matrix-room-settings.js deleted file mode 100644 index 1d952b5..0000000 --- a/src/matrix-room-settings.js +++ /dev/null @@ -1,116 +0,0 @@ -module.exports = function(RED) { - function MatrixRoomSettings(n) { - RED.nodes.createNode(this, n); - - var node = this; - - this.name = n.name; - this.server = RED.nodes.getNode(n.server); - this.roomId = n.roomId; - this.returnValues = n.returnValues; - - if (!node.server) { - node.warn("No configuration node"); - return; - } - node.server.register(node); - - node.status({ fill: "red", shape: "ring", text: "disconnected" }); - - node.server.on("disconnected", function(){ - node.status({ fill: "red", shape: "ring", text: "disconnected" }); - }); - - node.server.on("connected", function() { - node.status({ fill: "green", shape: "ring", text: "connected" }); - }); - - node.on("input", async function (msg) { - if (! node.server || ! node.server.matrixClient) { - msg.error = "No matrix server selected"; - node.error(msg.error, msg); - node.send([null, msg]); - return; - } - - if(!node.server.isConnected()) { - msg.error = "Matrix server connection is currently closed"; - node.error(msg.error, msg); - node.send([null, msg]); - return; - } - - msg.topic = node.roomId || msg.topic; - if(!msg.topic) { - msg.error = "Room must be specified in msg.topic or in configuration"; - node.error(msg.error, msg); - node.send([null, msg]); - return; - } - - let errors = {}, - payload = {}; - - if(msg.payload?.name) { - try { - await node.server.matrixClient.setRoomName(msg.topic, msg.payload.name); - } catch(e) { - node.error("Set room name failed: " + e.message, msg); - errors.name = e.message; - } - } - - if(msg.payload?.topic) { - try { - await node.server.matrixClient.setRoomTopic(msg.topic, msg.payload.topic); - } catch(e) { - node.error("Set room topic failed: " + e.message, msg); - errors.topic = e.message; - } - } - - if(msg.payload?.avatar) { - try { - await node.server.matrixClient.sendStateEvent( - msg.topic, - "m.room.avatar", - { - "info": msg.payload?.avatar_info || undefined, - "url": msg.payload.avatar - }, - ""); - } catch(e) { - node.error("Set room avatar failed: " + e.message, msg); - errors.topic = e.message; - } - } - - if(Object.keys(errors).length) { - msg.errors = errors; - } - - if(node.returnValues) { - // return current settings - let join_rules = await node.server.matrixClient.getStateEvent(msg.topic, "m.room.join_rules", ""); - msg.payload = { - "name": (await node.server.matrixClient.getStateEvent(msg.topic, "m.room.name", ""))?.name, - "topic": (await node.server.matrixClient.getStateEvent(msg.topic, "m.room.topic", ""))?.topic, - "avatar": (await node.server.matrixClient.getStateEvent(msg.topic, "m.room.avatar", ""))?.url, - "encrypted": node.server.matrixClient.isRoomEncrypted(msg.topic), - "power_levels": await node.server.matrixClient.getStateEvent(msg.topic, "m.room.power_levels", ""), - "aliases": (await node.server.matrixClient.getLocalAliases(msg.topic))?.aliases, - "guest_access": (await node.server.matrixClient.getStateEvent(msg.topic, "m.room.guest_access", ""))?.guest_access, - "join_rule": join_rules?.join_rule, - "join_allow_rules": join_rules?.allow - }; - } - - node.send([msg, null]); - }); - - node.on("close", function() { - node.server.deregister(node); - }); - } - RED.nodes.registerType("matrix-room-settings", MatrixRoomSettings); -} \ No newline at end of file diff --git a/src/matrix-room-state-events.html b/src/matrix-room-state-events.html new file mode 100644 index 0000000..51a9a15 --- /dev/null +++ b/src/matrix-room-state-events.html @@ -0,0 +1,356 @@ + + + + + + \ No newline at end of file diff --git a/src/matrix-room-state-events.js b/src/matrix-room-state-events.js new file mode 100644 index 0000000..e824ae7 --- /dev/null +++ b/src/matrix-room-state-events.js @@ -0,0 +1,273 @@ +module.exports = function(RED) { + function MatrixRoomSettings(n) { + RED.nodes.createNode(this, n); + + var node = this; + + this.name = n.name; + this.server = RED.nodes.getNode(n.server); + this.roomId = n.roomId; + this.returnValues = n.returnValues; + this.rules = n.rules; + + if (!node.server) { + node.warn("No configuration node"); + return; + } + node.server.register(node); + + node.status({ fill: "red", shape: "ring", text: "disconnected" }); + + node.server.on("disconnected", function(){ + node.status({ fill: "red", shape: "ring", text: "disconnected" }); + }); + + node.server.on("connected", function() { + node.status({ fill: "green", shape: "ring", text: "connected" }); + }); + + node.on("input", async function (msg) { + if (! node.server || ! node.server.matrixClient) { + msg.error = "No matrix server selected"; + node.error(msg.error, msg); + node.send([null, msg]); + return; + } + + if(!node.server.isConnected()) { + msg.error = "Matrix server connection is currently closed"; + node.error(msg.error, msg); + node.send([null, msg]); + return; + } + + msg.topic = node.roomId || msg.topic; + if(!msg.topic) { + msg.error = "Room must be specified in msg.topic or in configuration"; + node.error(msg.error, msg); + node.send([null, msg]); + return; + } + + let getterErrors = {}, + setterErrors = {}; + + if(!Array.isArray(node.rules) || !node.rules.length) { + node.warn("No rules configured, skipping", msg); + return msg; + } + + function getToValue(msg, rule) { + var value = rule.to; + if (rule.tot === 'json') { + try { + value = JSON.parse(rule.to); + } catch(e) { + throw new Error("Invalid JSON"); + } + } else if (rule.tot === 'bin') { + try { + value = Buffer.from(JSON.parse(rule.to)) + } catch(e) { + throw new Error("Invalid Binary"); + } + } + if (rule.tot === "msg") { + value = RED.util.getMessageProperty(msg,rule.to); + } else if ((rule.tot === 'flow') || (rule.tot === 'global')) { + RED.util.evaluateNodeProperty(rule.to, rule.tot, node, msg, (err,value) => { + if (err) { + throw new Error("Invalid value evaluation"); + } else { + return value; + } + }); + return + } else if (rule.tot === 'date') { + value = Date.now(); + } else if (rule.tot === 'jsonata') { + RED.util.evaluateJSONataExpression(rule.to,msg, (err, value) => { + if (err) { + throw new Error("Invalid expression"); + } else { + return value; + } + }); + return; + } + return value; + } + + function setToValue(value, rule) { + if(rule.tot === 'global' || rule.tot === 'flow') { + var contextKey = RED.util.parseContextStore(rule.to); + if (/\[msg/.test(contextKey.key)) { + // The key has a nest msg. reference to evaluate first + contextKey.key = RED.util.normalisePropertyExpression(contextKey.key, msg, true) + } + var target = node.context()[rule.tot]; + var callback = err => { + if (err) { + node.error(err, msg); + getterErrors[rule.p] = err.message; + } + } + target.set(contextKey.key, value, contextKey.store, callback); + } else if(rule.tot === 'msg') { + if (!RED.util.setMessageProperty(msg, rule.to, value)) { + node.warn(RED._("change.errors.no-override",{property:rule.to})); + } + } + } + + for(let rule of node.rules) { + // [ + // { + // "t": "set", + // "p": "m.room.topic", + // "to": "asdf", + // "tot": "str" + // }, ... + // ] + + let cachedGetters = {}; + if(rule.t === 'set') { + let value; + try { + value = getToValue(msg, rule); + switch(rule.p) { + case "m.room.name": + await node.server.matrixClient.sendStateEvent( + msg.topic, + "m.room.name", + typeof value === "string" + ? { name: value } + : value); + break; + case "m.room.topic": + if(typeof value === "string") { + await node.server.matrixClient.setRoomTopic(msg.topic, value); + } else { + await node.server.matrixClient.sendStateEvent( + msg.topic, + "m.room.topic", + value + ); + } + break; + case "m.room.avatar": + await node.server.matrixClient.sendStateEvent( + msg.topic, + "m.room.avatar", + typeof value === "string" + ? { "url": value } + : value, + ""); + break; + case "m.room.power_levels": + if(typeof value !== 'object') { + setterErrors[rule.p] = "m.room.power_levels content must be object"; + } else { + await node.server.matrixClient.sendStateEvent( + msg.topic, + "m.room.power_levels", + value, + ""); + } + break; + case "m.room.guest_access": + await node.server.matrixClient.sendStateEvent( + msg.topic, + "m.room.guest_access", + typeof value === "string" + ? { "guest_access": value } + : value, + ""); + break; + case "m.room.join_rules": + if(typeof value !== 'object') { + setterErrors[rule.p] = "m.room.join_rules content must be object"; + } else { + await node.server.matrixClient.sendStateEvent( + msg.topic, + "m.room.join_rules", + value, + ""); + } + break; + case "m.room.canonical_alias": + if(typeof value !== 'object') { + setterErrors[rule.p] = "m.room.canonical_alias content must be object"; + } else { + await node.server.matrixClient.sendStateEvent( + msg.topic, + "m.room.canonical_alias", + value, + ""); + } + break; + default: + if(typeof value !== 'object') { + setterErrors[rule.p] = "Custom event content must be object"; + } else { + await node.server.matrixClient.sendStateEvent( + msg.topic, + rule.p, + value, + ""); + } + break; + } + } catch(e) { + setterErrors[rule.p] = e.message; + } + } else if(rule.t === 'get') { + let value; + if(cachedGetters.hasOwnProperty(rule.p)) { + value = cachedGetters[rule.p]; + } else { + try { + // we may want to fetch from local storage in the future, this is how to do that + // const room = this.getRoom(roomId); + // const ev = room.currentState.getStateEvents(EventType.RoomEncryption, ""); + value = await node.server.matrixClient.getStateEvent(msg.topic, rule.p, ""); + switch(rule.p) { + case "m.room.name": + value = value?.name + break; + case "m.room.topic": + value = value?.topic + break; + case "m.room.avatar": + value = value?.url + break; + case "m.room.guest_access": + value = value?.guest_access; + break; + } + setToValue(value, rule); + } catch(e) { + getterErrors[rule.p] = e; + } + } + + } + } + + if(Object.keys(setterErrors).length) { + msg.setter_errors = setterErrors; + } + + if(Object.keys(getterErrors).length) { + msg.getter_errors = getterErrors; + } + + node.send([msg, null]); + }); + + node.on("close", function() { + node.server.deregister(node); + }); + } + RED.nodes.registerType("matrix-room-state-events", MatrixRoomSettings); +} \ No newline at end of file diff --git a/src/matrix-room-users.js b/src/matrix-room-users.js index 1a5347e..8a801c6 100644 --- a/src/matrix-room-users.js +++ b/src/matrix-room-users.js @@ -34,6 +34,7 @@ module.exports = function(RED) { if(!node.server.isConnected()) { node.error("Matrix server connection is currently closed", msg); node.send([null, msg]); + return; } let roomId = node.roomId || msg.topic; diff --git a/src/matrix-send-file.js b/src/matrix-send-file.js index ad89615..65e9344 100644 --- a/src/matrix-send-file.js +++ b/src/matrix-send-file.js @@ -34,6 +34,7 @@ module.exports = function(RED) { if(!node.server.isConnected()) { node.error("Matrix server connection is currently closed", msg); node.send([null, msg]); + return; } msg.topic = node.roomId || msg.topic; diff --git a/src/matrix-send-image.js b/src/matrix-send-image.js index 2e15e8f..29e2173 100644 --- a/src/matrix-send-image.js +++ b/src/matrix-send-image.js @@ -34,6 +34,7 @@ module.exports = function(RED) { if(!node.server.isConnected()) { node.error("Matrix server connection is currently closed", msg); node.send([null, msg]); + return; } msg.topic = node.roomId || msg.topic; diff --git a/src/matrix-server-config.js b/src/matrix-server-config.js index 81ea4cf..920f3af 100644 --- a/src/matrix-server-config.js +++ b/src/matrix-server-config.js @@ -379,7 +379,7 @@ module.exports = function(RED) { if(node.e2ee){ node.log("Initializing crypto..."); await node.matrixClient.initCrypto(); - node.matrixClient.setGlobalErrorOnUnknownDevices(false); + node.matrixClient.getCrypto().globalBlacklistUnverifiedDevices = false; // prevent errors from unverified devices } node.log("Connecting to Matrix server..."); await node.matrixClient.startClient({ diff --git a/src/matrix-synapse-create-edit-user.js b/src/matrix-synapse-create-edit-user.js index 43be104..e13aa7f 100644 --- a/src/matrix-synapse-create-edit-user.js +++ b/src/matrix-synapse-create-edit-user.js @@ -44,6 +44,7 @@ module.exports = function(RED) { if(!node.server.isConnected()) { node.error("Matrix server connection is currently closed", msg); node.send([null, msg]); + return; } if(!msg.userId) { diff --git a/src/matrix-synapse-deactivate-user.js b/src/matrix-synapse-deactivate-user.js index b6e909a..62aaa49 100644 --- a/src/matrix-synapse-deactivate-user.js +++ b/src/matrix-synapse-deactivate-user.js @@ -44,6 +44,7 @@ module.exports = function(RED) { if(!node.server.isConnected()) { node.error("Matrix server connection is currently closed", msg); node.send([null, msg]); + return; } if(!msg.userId) { diff --git a/src/matrix-synapse-join-room.js b/src/matrix-synapse-join-room.js index 3fd51c2..9c683b3 100644 --- a/src/matrix-synapse-join-room.js +++ b/src/matrix-synapse-join-room.js @@ -43,6 +43,7 @@ module.exports = function(RED) { if(!node.server.isConnected()) { node.error("Matrix server connection is currently closed", msg); node.send([null, msg]); + return; } msg.topic = node.roomId || msg.topic; diff --git a/src/matrix-synapse-users.js b/src/matrix-synapse-users.js index f2079c6..e49ff24 100644 --- a/src/matrix-synapse-users.js +++ b/src/matrix-synapse-users.js @@ -33,6 +33,7 @@ module.exports = function(RED) { if(!node.server.isConnected()) { node.error("Matrix server connection is currently closed", msg); node.send([null, msg]); + return; } let queryParams = { diff --git a/src/matrix-whois-user.js b/src/matrix-whois-user.js index 15de6d8..70e335d 100644 --- a/src/matrix-whois-user.js +++ b/src/matrix-whois-user.js @@ -45,6 +45,7 @@ module.exports = function(RED) { if(!node.server.isConnected()) { node.error("Matrix server connection is currently closed", msg); node.send([null, msg]); + return; } if(!msg.userId) {