From cd99955115e88964f8482cae11d437f8d8f418cd Mon Sep 17 00:00:00 2001 From: Skylar Sadlier Date: Mon, 16 Aug 2021 21:56:53 -0600 Subject: [PATCH] - New react node for reacting to messages - made input and output nodes more compatible with each other (for relay purposes). - File/Image input nodes can be combined with a File In node now - Remove console.log debug lines - set version to 0.0.3 - send message node updated to handle message formats and types. - Fixed ignore properties on matrix receive node not saving - created basic readme - WIP on node docs --- README.md | 35 ++++++- package-lock.json | 2 +- package.json | 14 +-- src/matrix-react.html | 102 ++++++++++++++++++++ src/matrix-react.js | 78 ++++++++++++++++ src/matrix-receive.html | 120 +++++++++++++++++++----- src/matrix-receive.js | 69 +++++++------- src/matrix-send-file.html | 6 +- src/matrix-send-file.js | 15 +++ src/matrix-send-image.html | 19 ++-- src/matrix-send-image.js | 41 +++++--- src/matrix-send-message.html | 66 +++++++------ src/matrix-send-message.js | 104 +++++++++++++++------ src/matrix-send.html | 78 ---------------- src/matrix-send.js | 175 ----------------------------------- src/matrix-server-config.js | 1 - 16 files changed, 524 insertions(+), 401 deletions(-) create mode 100644 src/matrix-react.html create mode 100644 src/matrix-react.js delete mode 100644 src/matrix-send.html delete mode 100644 src/matrix-send.js diff --git a/README.md b/README.md index c7e7a8d..b5abb1b 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,33 @@ -# node-red-contrib-matrix-support -Matrix chat server support for Node-RED +# node-red-contrib-matrix-chat +Matrix chat server client for Node-RED -This is currently a work in progress but we are getting close to a first release. \ No newline at end of file +### Features + +The following is supported from this package: + +- Receive events from a room (messages, reactions, images, and files) +- Send Images/Files +- Send HTML/Plain Text Message +- Send HTML/Plain Text Notice +- React to messages + +Therefor you can easily build a bot or even a relay from another chat service. + +### Installing + +You can either install from within Node-RED by searching for `node-red-contrib-matrix-chat` or run this from within your Node-RED directory: +```bash +npm install node-red-contrib-matrix-chat +``` + +### Usage + +Using this package is very straightforward. Examples coming soon! + +### Other Packages + +- [node-red-contrib-gamedig](https://www.npmjs.com/package/node-red-contrib-gamedig) - Query game servers from Node-RED! + +### Contributing +All contributions are welcome! If you do add a feature please do a pull request so that everyone benefits :) +Sharing is caring. \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 92c9121..fb595eb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,5 +1,5 @@ { - "name": "node-red-contrib-matrix-support", + "name": "node-red-contrib-matrix-chat", "version": "0.0.1", "lockfileVersion": 2, "requires": true, diff --git a/package.json b/package.json index b39590b..a47cd77 100644 --- a/package.json +++ b/package.json @@ -1,18 +1,18 @@ { - "name": "node-red-contrib-matrix-support", - "version": "0.0.1", - "description": "Matrix chat server support for Node-RED", + "name": "node-red-contrib-matrix-chat", + "version": "0.0.3", + "description": "Matrix chat server client for Node-RED", "dependencies": { "matrix-js-sdk": "^12.2.0" }, "node-red": { "nodes": { "matrix-server-config": "src/matrix-server-config.js", - "matrix-send": "src/matrix-send.js", + "matrix-receive": "src/matrix-receive.js", + "matrix-send-message": "src/matrix-send-message.js", "matrix-send-file": "src/matrix-send-file.js", "matrix-send-image": "src/matrix-send-image.js", - "matrix-send-message": "src/matrix-send-message.js", - "matrix-receive": "src/matrix-receive.js" + "matrix-react": "src/matrix-react.js" } }, "keywords": [ @@ -24,7 +24,7 @@ ], "repository": { "type": "git", - "url": "https://github.com/skylar-tech/node-red-contrib-matrix-support" + "url": "https://github.com/skylar-tech/node-red-contrib-matrix-chat" }, "author": { "name": "Skylar Sadlier", diff --git a/src/matrix-react.html b/src/matrix-react.html new file mode 100644 index 0000000..df4fa1c --- /dev/null +++ b/src/matrix-react.html @@ -0,0 +1,102 @@ + + + + + \ No newline at end of file diff --git a/src/matrix-react.js b/src/matrix-react.js new file mode 100644 index 0000000..8fb9fe7 --- /dev/null +++ b/src/matrix-react.js @@ -0,0 +1,78 @@ +module.exports = function(RED) { + function MatrixReact(n) { + RED.nodes.createNode(this, n); + + var node = this; + + this.name = n.name; + this.server = RED.nodes.getNode(n.server); + this.roomId = n.roomId; + + if (!node.server) { + node.warn("No configuration node"); + return; + } + + 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", function (msg) { + if (!node.server || !node.server.matrixClient) { + node.error("No matrix server selected"); + return; + } + + if(!node.server.isConnected()) { + node.error("Matrix server connection is currently closed"); + node.send([null, msg]); + } + + msg.roomId = node.roomId || msg.roomId; + if(!msg.roomId) { + node.error("Room must be specified in msg.roomId or in configuration"); + return; + } + + if(!msg.payload) { + node.error('msg.payload is required'); + return; + } + + let eventId = msg.referenceEventId || msg.eventId; + if(!eventId) { + node.error('Either msg.referenceEventId or msg.eventId must be defined to react to a message.'); + return; + } + + node.server.matrixClient.sendCompleteEvent( + msg.roomId, + { + type: 'm.reaction', + content: { + "m.relates_to": { + event_id: eventId, + key: msg.payload, + rel_type: "m.annotation" + } + } + } + ) + .then(function(e) { + msg.eventId = e.event_id; + node.send([msg, null]); + }) + .catch(function(e){ + msg.error = e; + node.send([null, msg]); + }); + }); + } + RED.nodes.registerType("matrix-react", MatrixReact); +} \ No newline at end of file diff --git a/src/matrix-receive.html b/src/matrix-receive.html index e2807a3..0eb1272 100644 --- a/src/matrix-receive.html +++ b/src/matrix-receive.html @@ -41,58 +41,134 @@
-
-
-
-
\ No newline at end of file diff --git a/src/matrix-send-image.js b/src/matrix-send-image.js index 110e518..ed63b2f 100644 --- a/src/matrix-send-image.js +++ b/src/matrix-send-image.js @@ -41,6 +41,21 @@ module.exports = function(RED) { return; } + if(msg.content) { + node.server.matrixClient.sendMessage(msg.roomId, msg.content) + .then(function(e) { + node.log("Image message sent: " + e); + msg.eventId = e.event_id; + node.send([msg, null]); + }) + .catch(function(e){ + node.warn("Error sending image message " + e); + msg.error = e; + node.send([null, msg]); + }); + return; + } + if(!msg.payload) { node.error('msg.payload is required'); return; @@ -59,20 +74,22 @@ module.exports = function(RED) { rawResponse: (msg.rawResponse || false), // Return the raw body, rather than parsing the JSON. type: msg.contentType, // Content-type for the upload. Defaults to file.type, or applicaton/octet-stream. onlyContentUri: false // Just return the content URI, rather than the whole body. Defaults to false. Ignored if opts.rawResponse is true. - }).then(function(file){ + }) + .then(function(file){ node.server.matrixClient .sendImageMessage(msg.roomId, file.content_uri, {}, (msg.body || msg.filename) || "") - .then(function(imgResp) { - node.log("Image message sent: " + imgResp); - msg.eventId = e.eventId; - node.send([msg, null]); - }) - .catch(function(e){ - node.warn("Error sending image message " + e); - msg.matrixError = e; - node.send([null, msg]); - }); - }).catch(function(e){ + .then(function(e) { + node.log("Image message sent: " + e); + msg.eventId = e.event_id; + node.send([msg, null]); + }) + .catch(function(e){ + node.warn("Error sending image message " + e); + msg.error = e; + node.send([null, msg]); + }); + }) + .catch(function(e){ node.warn("Error uploading image message " + e); msg.matrixError = e; node.send([null, msg]); diff --git a/src/matrix-send-message.html b/src/matrix-send-message.html index 23fb70d..27d24d2 100644 --- a/src/matrix-send-message.html +++ b/src/matrix-send-message.html @@ -10,7 +10,8 @@ name: { value: null }, server: { value: "", type: "matrix-server-config" }, roomId: { value: null }, - htmlMessage: { value: false } + messageType: { value: 'm.text' }, + messageFormat: { value: '' }, }, label: function() { return this.name || "Send Message"; @@ -33,14 +34,27 @@
- -
+
+ +
Must be a valid MIME Type @@ -48,34 +62,35 @@ \ No newline at end of file diff --git a/src/matrix-send-message.js b/src/matrix-send-message.js index 98eea3d..3d929d0 100644 --- a/src/matrix-send-message.js +++ b/src/matrix-send-message.js @@ -7,7 +7,44 @@ module.exports = function(RED) { this.name = n.name; this.server = RED.nodes.getNode(n.server); this.roomId = n.roomId; - this.htmlMessage = n.htmlMessage; + this.messageType = n.messageType; + this.messageFormat = n.messageFormat; + + // taken from https://github.com/matrix-org/synapse/blob/master/synapse/push/mailer.py + this.allowedTags = [ + "font", // custom to matrix for IRC-style font coloring + "del", // for markdown + // deliberately no h1/h2 to stop people shouting. + "h3", + "h4", + "h5", + "h6", + "blockquote", + "p", + "a", + "ul", + "ol", + "nl", + "li", + "b", + "i", + "u", + "strong", + "em", + "strike", + "code", + "hr", + "br", + "div", + "table", + "thead", + "caption", + "tbody", + "tr", + "th", + "td", + "pre", + ]; if (!node.server) { node.warn("No configuration node"); @@ -25,7 +62,26 @@ module.exports = function(RED) { }); node.on("input", function (msg) { - if (! node.server || ! node.server.matrixClient) { + let msgType = node.messageType, + msgFormat = node.messageFormat; + + if(msgType === 'msg.type') { + if(!msg.type) { + node.error("Message type is set to be passed in via msg.type but was not defined"); + return; + } + msgType = msg.type; + } + + if(msgFormat === 'msg.format') { + if(!msg.format) { + node.error("Message format is set to be passed in via msg.format but was not defined"); + return; + } + msgFormat = msg.format; + } + + if (!node.server || !node.server.matrixClient) { node.warn("No matrix server selected"); return; } @@ -46,31 +102,27 @@ module.exports = function(RED) { return; } - if(this.htmlMessage) { - node.server.matrixClient.sendHtmlMessage(msg.roomId, msg.payload.toString(), msg.payload.toString()) - .then(function(e) { - node.log("Message sent: " + msg.payload); - msg.eventId = e.eventId; - node.send([msg, null]); - }) - .catch(function(e){ - node.warn("Error sending message " + e); - msg.matrixError = e; - node.send([null, msg]); - }); - } else { - node.server.matrixClient.sendTextMessage(msg.roomId, msg.payload.toString()) - .then(function(e) { - node.log("Message sent: " + msg.payload); - msg.eventId = e.eventId; - node.send([msg, null]); - }) - .catch(function(e){ - node.warn("Error sending message " + e); - msg.matrixError = e; - node.send([null, msg]); - }); + let content = { + msgtype: msgType, + body: msg.payload.toString() + }; + + if(msgFormat === 'html') { + content.format = "org.matrix.custom.html"; + content.formatted_body = msg.formatted_payload || msg.payload; } + + node.server.matrixClient.sendMessage(msg.roomId, content) + .then(function(e) { + node.log("Message sent: " + msg.payload); + msg.eventId = e.eventId; + node.send([msg, null]); + }) + .catch(function(e){ + node.warn("Error sending message " + e); + msg.error = e; + node.send([null, msg]); + }); }); } RED.nodes.registerType("matrix-send-message", MatrixSendImage); diff --git a/src/matrix-send.html b/src/matrix-send.html deleted file mode 100644 index eb0de15..0000000 --- a/src/matrix-send.html +++ /dev/null @@ -1,78 +0,0 @@ - - - - - \ No newline at end of file diff --git a/src/matrix-send.js b/src/matrix-send.js deleted file mode 100644 index 3cdaa6c..0000000 --- a/src/matrix-send.js +++ /dev/null @@ -1,175 +0,0 @@ -module.exports = function(RED) { - function MatrixSendMessage(n) { - RED.nodes.createNode(this, n); - - var node = this; - - this.name = n.name; - this.server = RED.nodes.getNode(n.matrixServer); - this.room = n.room; - - if (!node.server) { - node.warn("No configuration node"); - return; - } - - 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.matrixClient.joinRoom(node.room, {syncRoom:false}) // should we really skip syncing the room? - // .then(function(joinedRoom) { - // node.log("Joined " + node.room); - // node.room = joinedRoom.roomId; - // node.updateConnectionState(true); - // }).catch(function(e) { - // node.warn("Error joining " + node.room + ": " + e); - // }); - }); - - node.on("input", function (msg) { - if (! node.server || ! node.server.matrixClient) { - node.warn("No matrix server configuration"); - return; - } - - if(!node.server.isConnected()) { - node.warn("Matrix server connection is currently closed"); - node.send([null, msg]); - } - - if (msg.payload) { - node.log("Sending message " + msg.payload); - - if(!msg.roomId) { - msg.roomId = node.room; - } - - if(!msg.roomId) { - node.warn("Room must be specified in msg.roomId or in configuration"); - return; - } - - // @todo add checks to make sure required properties are filled out instead of throwing an exception - switch(msg.type || null) { - case 'react': - /** - * React to another event (message) - * msg.roomId - required - * - */ - node.server.matrixClient.sendCompleteEvent( - msg.roomId, - { - type: 'm.reaction', - content: { - "m.relates_to": { - event_id: msg.eventId, - "key": msg.payload, - "rel_type": "m.annotation" - } - } - } - ) - .then(function(e) { - msg.eventId = e.event_id; - node.send([msg, null]); - }) - .catch(function(e){ - msg.matrixError = e; - node.send([null, msg]); - }); - break; - - case 'image': - node.server.matrixClient.uploadContent( - msg.image.content, { - name: msg.image.filename || null, // Name to give the file on the server. - rawResponse: (msg.rawResponse || false), // Return the raw body, rather than parsing the JSON. - type: msg.image.type, // Content-type for the upload. Defaults to file.type, or applicaton/octet-stream. - onlyContentUri: false // Just return the content URI, rather than the whole body. Defaults to false. Ignored if opts.rawResponse is true. - }).then(function(file){ - node.server.matrixClient - .sendImageMessage(msg.roomId, file.content_uri, {}, msg.payload) - .then(function(imgResp) { - node.log("Image message sent: " + imgResp); - msg.eventId = e.eventId; - node.send([msg, null]); - }) - .catch(function(e){ - node.warn("Error sending image message " + e); - msg.matrixError = e; - node.send([null, msg]); - }); - }).catch(function(e){ - node.warn("Error uploading image message " + e); - msg.matrixError = e; - node.send([null, msg]); - }); - break; - - case 'file': - if(!msg.file) { - node.error('msg.file must be defined to send a file'); - } - - if(!msg.file.type) { - node.error('msg.file.type must be set to a valid content-type header (i.e. application/pdf)'); - } - - node.server.matrixClient.uploadContent( - msg.file.content, { - name: msg.file.filename || null, // Name to give the file on the server. - rawResponse: (msg.rawResponse || false), // Return the raw body, rather than parsing the JSON. - type: msg.file.type, // Content-type for the upload. Defaults to file.type, or applicaton/octet-stream. - onlyContentUri: false // Just return the content URI, rather than the whole body. Defaults to false. Ignored if opts.rawResponse is true. - }).then(function(file){ - const content = { - msgtype: 'm.file', - url: file.content_uri, - body: msg.payload, - }; - node.server.matrixClient - .sendMessage(msg.roomId, content) - .then(function(imgResp) { - node.log("File message sent: " + imgResp); - msg.eventId = e.eventId; - node.send([msg, null]); - }) - .catch(function(e){ - node.warn("Error sending file message " + e); - msg.matrixError = e; - node.send([null, msg]); - }); - }).catch(function(e){ - node.warn("Error uploading file message " + e); - msg.matrixError = e; - node.send([null, msg]); - }); - break; - - default: // default text message - node.server.matrixClient.sendTextMessage(msg.roomId, msg.payload.toString()) - .then(function(e) { - node.log("Message sent: " + msg.payload); - msg.eventId = e.eventId; - node.send([msg, null]); - }).catch(function(e){ - node.warn("Error sending message " + e); - msg.matrixError = e; - node.send([null, msg]); - }); - break; - } - } else { - node.warn("msg.payload is empty"); - } - }); - } - RED.nodes.registerType("matrix-send", MatrixSendMessage); -} \ No newline at end of file diff --git a/src/matrix-server-config.js b/src/matrix-server-config.js index ceb3e9a..8ef7659 100644 --- a/src/matrix-server-config.js +++ b/src/matrix-server-config.js @@ -79,7 +79,6 @@ module.exports = function(RED) { switch (state) { case "ERROR": node.error("Connection to Matrix server lost"); - console.log(state, prevState, data); node.setConnected(false); break;