- 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
This commit is contained in:
Skylar Sadlier 2021-08-16 21:56:53 -06:00
parent 61aa32e8a6
commit cd99955115
16 changed files with 524 additions and 401 deletions

View File

@ -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.
### 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.

2
package-lock.json generated
View File

@ -1,5 +1,5 @@
{
"name": "node-red-contrib-matrix-support",
"name": "node-red-contrib-matrix-chat",
"version": "0.0.1",
"lockfileVersion": 2,
"requires": true,

View File

@ -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",

102
src/matrix-react.html Normal file
View File

@ -0,0 +1,102 @@
<script type="text/javascript">
RED.nodes.registerType('matrix-react',{
category: 'matrix',
color: '#00b7ca',
icon: "matrix.png",
outputLabels: ["success", "error"],
inputs:1,
outputs:2,
defaults: {
name: { value: null },
server: { value: "", type: "matrix-server-config" },
roomId: { value: null }
},
label: function() {
return this.name || "React";
},
paletteLabel: 'React'
});
</script>
<script type="text/html" data-template-name="matrix-react">
<div class="form-row">
<label for="node-input-name"><i class="icon-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
</div>
<div class="form-row">
<label for="node-input-server"><i class="fa fa-user"></i> Matrix Server Config</label>
<input type="text" id="node-input-server">
</div>
<div class="form-row">
<label for="node-input-roomId"><i class="fa fa-user"></i> Room ID</label>
<input type="text" id="node-input-roomId">
</div>
<div class="form-row">
<label for="node-input-messageType">
Message Type
</label>
<select id="node-input-messageType">
<option value="m.text">m.text</option>
<option value="m.notice">m.notice</option>
<option value="msg.type">msg.type input</option>
</select>
<div class="form-tips">
It's recommended to use m.notice for bots because the message will render in a lighter text (at least in Element client) for users to distinguish bot and real user messages.
</div>
</div>
<div class="form-row">
<label for="node-input-messageFormat">
Message Format
</label>
<select id="node-input-messageFormat">
<option value="">Default (plaintext)</option>
<option value="html">HTML</option>
<option value="msg.format">msg.format input</option>
</select>
</div>
<div class="form-tips">
Must be a valid <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types" target="_blank">MIME Type</a>
</div>
</script>
<script type="text/html" data-help-name="matrix-react">
<h3>Details</h3>
<p>React to a message in a Matrix room.</p>
<h3>Inputs</h3>
<dl class="message-properties">
<dt>msg.payload
<span class="property-type">String</span>
</dt>
<dd> Usually an emoji but can also be text. </dd>
<dt>msg.roomId
<span class="property-type">String | Null</span>
</dt>
<dd> Room ID to send image to. Optional if configured on the node. If configured on the node this will be ignored.</dd>
<dt class="optional">msg.referenceEventId<br />
msg.eventId
<span class="property-type">String</span>
</dt>
<dd> The eventId of the message to react to. Uses <code>msg.referenceEventId</code> first and falls back to <code>msg.eventId</code>. One of these MUST be defined.</dd>
</dl>
<h3>Outputs</h3>
<ol class="node-ports">
<li>Success
<dl class="message-properties">
<dd>original msg object preserved.</dd>
<dt>msg.eventId <span class="property-type">string</span></dt>
<dd>the eventId from the posted reaction.</dd>
</dl>
</li>
<li>Error
<dl class="message-properties">
<dt>msg.error <span class="property-type">string</span></dt>
<dd>the error that occurred.</dd>
</dl>
</li>
</ol>
</script>

78
src/matrix-react.js Normal file
View File

@ -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);
}

View File

@ -41,58 +41,134 @@
</div>
<div class="form-row">
<input
type="checkbox"
id="node-config-input-ignoreText"
style="width: auto; margin-left: 125px; vertical-align: top"
type="checkbox"
id="node-input-ignoreText"
style="width: auto; margin-left: 125px; vertical-align: top"
/>
<label for="node-config-input-ignoreText" style="width: auto">
<label for="node-input-ignoreText" style="width: auto">
Ignore text
</label>
</div>
<div class="form-row">
<input
type="checkbox"
id="node-config-input-ignoreReactions"
style="width: auto; margin-left: 125px; vertical-align: top"
type="checkbox"
id="node-input-ignoreReactions"
style="width: auto; margin-left: 125px; vertical-align: top"
/>
<label for="node-config-input-ignoreReactions" style="width: auto">
<label for="node-input-ignoreReactions" style="width: auto">
Ignore reactions
</label>
</div>
<div class="form-row">
<input
type="checkbox"
id="node-config-input-ignoreFiles"
style="width: auto; margin-left: 125px; vertical-align: top"
type="checkbox"
id="node-input-ignoreFiles"
style="width: auto; margin-left: 125px; vertical-align: top"
/>
<label for="node-config-input-ignoreFiles" style="width: auto">
<label for="node-input-ignoreFiles" style="width: auto">
Ignore files
</label>
</div>
<div class="form-row">
<input
type="checkbox"
id="node-config-input-ignoreImages"
style="width: auto; margin-left: 125px; vertical-align: top"
type="checkbox"
id="node-input-ignoreImages"
style="width: auto; margin-left: 125px; vertical-align: top"
/>
<label for="node-config-input-ignoreImages" style="width: auto">
<label for="node-input-ignoreImages" style="width: auto">
Ignore images
</label>
</div>
</script>
<script type="text/html" data-help-name="matrix-receive">
<p>Receive messages from a matrix server on all rooms or a specified room.</p>
<p>Receive events from matrix.</p>
<h3>Outputs</h3>
<ol class="node-ports">
<li>Message
<li>Always Returned
<dl class="message-properties">
<dt>topic <span class="property-type">string</span></dt>
<dd>the room the message originated from.</dd>
<dt>msg.type <span class="property-type">string</span></dt>
<dd>
the message type. This is one of m.text, m.reaction, m.file, or m.image
</dd>
</dl>
<dl class="message-properties">
<dt>payload <span class="property-type">string</span></dt>
<dd>the message from the server.</dd>
<dt>msg.payload <span class="property-type">string</span></dt>
<dd>the body from the message's content.</dd>
</dl>
<dl class="message-properties">
<dt>msg.sender <span class="property-type">string</span></dt>
<dd>the sender of the message. Example: @john:matrix.org</dd>
</dl>
<dl class="message-properties">
<dt>msg.roomId <span class="property-type">string</span></dt>
<dd>the ID of the room. Example: !OGEhHVWSdvArJzumhm:matrix.org</dd>
</dl>
<dl class="message-properties">
<dt>msg.event <span class="property-type">object</span></dt>
<dd>the event object returned by the Matrix server</dd>
</dl>
<dl class="message-properties">
<dt>msg.content <span class="property-type">object</span></dt>
<dd>the message's content object</dd>
</dl>
</li>
<li><code>msg.type</code> == '<strong>m.text</strong>'
<div class="form-tips" style="margin-bottom: 12px;">
Doesn't return anything extra
</div>
</li>
<li><code>msg.type</code> == '<strong>m.reaction</strong>'
<dl class="message-properties">
<dt>msg.info <span class="property-type">object</span></dt>
<dd>the content's info.</dd>
</dl>
<dl class="message-properties">
<dt>msg.referenceEventId <span class="property-type">string</span></dt>
<dd>the message that the reaction relates to</dd>
</dl>
<dl class="message-properties">
<dt>msg.payload <span class="property-type">string</span></dt>
<dd>the key of the reaction's content</dd>
</dl>
</li>
<li><code>msg.type</code> == '<strong>m.file</strong>'
<dl class="message-properties">
<dt>msg.file.info <span class="property-type">string</span></dt>
<dd>the content's info.</dd>
</dl>
<dl class="message-properties">
<dt>msg.file.url <span class="property-type">string</span></dt>
<dd>the file's URL</dd>
</dl>
</li>
<li><code>msg.type</code> == '<strong>m.image</strong>'
<dl class="message-properties">
<dt>msg.image.info <span class="property-type">string</span></dt>
<dd>the image info.</dd>
</dl>
<dl class="message-properties">
<dt>msg.file.url <span class="property-type">string</span></dt>
<dd>the image's URL</dd>
</dl>
<dl class="message-properties">
<dt>msg.file.thumbnail_url <span class="property-type">string</span></dt>
<dd>the image's thumbnail URL</dd>
</dl>
</li>
</ol>

View File

@ -10,6 +10,8 @@ module.exports = function(RED) {
this.ignoreReactions = n.ignoreReactions;
this.ignoreFiles = n.ignoreFiles;
this.ignoreImages = n.ignoreImages;
this.roomId = n.roomId;
this.roomIds = this.roomId ? this.roomId.split(',') : [];
node.status({ fill: "red", shape: "ring", text: "disconnected" });
@ -18,7 +20,7 @@ module.exports = function(RED) {
return;
}
node.server.on("disconnected", function(){
node.server.on("disconnected", function() {
node.status({ fill: "red", shape: "ring", text: "disconnected" });
});
@ -26,48 +28,41 @@ module.exports = function(RED) {
node.status({ fill: "green", shape: "ring", text: "connected" });
node.server.matrixClient.on("Room.timeline", function(event, room, toStartOfTimeline, data) {
console.log("Room.timeline", [event, room]);
if (toStartOfTimeline) {
console.log("MESSAGED SKIPPED: toStartOfTimeline");
return; // ignore paginated results
}
if (
event.getType() !== "m.room.message"
&& event.getType() !== "m.reaction"
) {
console.log("MESSAGED SKIPPED: TYPE");
return; // only keep messages
}
if (!event.getSender() || event.getSender() === node.server.userId) {
console.log("MESSAGED SKIPPED: SENDER");
return; // ignore our own messages
}
if (!event.getUnsigned() || event.getUnsigned().age > 1000) {
console.log("MESSAGED SKIPPED: UNSIGNED");
return; // ignore old messages
}
// if node has a room ID set we only listen on that room
if(node.roomId) {
let roomIds = node.roomId.split(',');
if(roomIds.indexOf(msg.roomId) === -1) {
return;
}
if(node.roomIds.length && node.roomIds.indexOf(room.roomId) === -1) {
console.log("SKIASDJIOJSADIJASD", node.roomIds);
return;
}
let content = event.getContent(),
msg = {};
let msg = {
content: event.getContent(),
type : (event.getContent().msgtype || event.getType()) || null,
payload : event.getContent().body || null,
sender : event.getSender(),
roomId : room.roomId,
eventId : event.getId(),
event : event,
};
msg.type = (content.msgtype || event.getType()) || null;
msg.payload = event.getContent().body;
msg.sender = event.getSender();
msg.roomId = room.roomId;
msg.eventId = event.getId();
msg.event = event;
node.log("Received chat message [" + msg.type + "]: (" + room.name + ") " + event.getSender() + " :: " + event.getContent().body);
node.log("Received chat message [" + msg.type + "]: (" + room.name + ") " + event.getSender() + " :: " + msg.content.body);
let knownMessageType = true;
switch(msg.type) {
case 'm.text':
if(node.ignoreText) return;
@ -75,30 +70,34 @@ module.exports = function(RED) {
case 'm.reaction':
if(node.ignoreReactions) return;
msg.info = event.getContent()["m.relates_to"].info;
msg.eventId = event.getContent()["m.relates_to"].event_id;
msg.payload = event.getContent()["m.relates_to"].key;
msg.info = msg.content["m.relates_to"].info;
msg.referenceEventId = msg.content["m.relates_to"].event_id;
msg.payload = msg.content["m.relates_to"].key;
break;
case 'm.file':
if(node.ignoreFiles) return;
msg.file = {
info: event.getContent().info,
url: node.server.matrixClient.mxcUrlToHttp(event.getContent().url)
};
msg.url = node.server.matrixClient.mxcUrlToHttp(msg.content.url);
msg.mxc_url = msg.content.url;
break;
case 'm.image':
if(node.ignoreImages) return;
msg.image = {
info: event.getContent().info,
url: node.server.matrixClient.mxcUrlToHttp(event.getContent().url),
thumbnail_url: node.server.matrixClient.mxcUrlToHttp(event.getContent().info.thumbnail_url)
};
msg.url = node.server.matrixClient.mxcUrlToHttp(msg.content.url);
msg.mxc_url = msg.content.url;
msg.thumbnail_url = node.server.matrixClient.mxcUrlToHttp(msg.content.info.thumbnail_url);
msg.thumbnail_mxc_url = msg.content.info.thumbnail_url;
break;
default:
knownMessageType = false;
}
node.send(msg);
if(knownMessageType) {
node.send(msg);
} else {
node.warn("Uknown message type: " + msg.type);
}
});
});
}

View File

@ -42,7 +42,8 @@
</script>
<script type="text/html" data-help-name="matrix-send-file">
<p>Send a message to a matrix room.</p>
<h3>Details</h3>
<p>This node will send a file to a Matrix chat room. Supports direct linking to a File In node.</p>
<h3>Inputs</h3>
<dl class="message-properties">
@ -87,9 +88,6 @@
</li>
</ol>
<h3>Details</h3>
<p>This node will send a file to a Matrix chat room. You can link this directly to a File In node.</p>
<h3>References</h3>
<ul>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types">MIME Types</a> - description of <code>msg.contentType</code> format</li>

View File

@ -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("File message sent: " + e);
msg.eventId = e.event_id;
node.send([msg, null]);
})
.catch(function(e){
node.warn("Error sending file message " + e);
msg.error = e;
node.send([null, msg]);
});
return;
}
if(!msg.payload) {
node.error('msg.payload is required');
return;

View File

@ -42,7 +42,8 @@
</script>
<script type="text/html" data-help-name="matrix-send-image">
<p>Send an image to a matrix room.</p>
<h3>Details</h3>
<p>This node will send an image to a Matrix chat room. You can link this directly to a File In node.</p>
<h3>Inputs</h3>
<dl class="message-properties">
@ -70,6 +71,11 @@
<span class="property-type">String | Null</span>
</dt>
<dd> this will be the display name the client will see when rendered in their chat client. If this is left empty the it uses <code>msg.filename</code>. If <code>msg.filename</code> is also undefined it sets it to empty string</dd>
<dt class="optional">msg.content
<span class="property-type">Object | Null</span>
</dt>
<dd> craft your own msg.content to send to the server. If defined then <code>msg.filename</code>, <code>msg.contentType</code>, <code>msg.body</code>, and <code>msg.payload</code> aren't required since the file already exists.</dd>
</dl>
<h3>Outputs</h3>
@ -77,6 +83,9 @@
<li>Success
<dl class="message-properties">
<dd>original msg object preserved.</dd>
<dt>msg.eventId <span class="property-type">string</span></dt>
<dd>the eventId from the posted message.</dd>
</dl>
</li>
<li>Error
@ -86,12 +95,4 @@
</dl>
</li>
</ol>
<h3>Details</h3>
<p>This node will send an image to a Matrix chat room. You can link this directly to a File In node.</p>
<h3>References</h3>
<ul>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types">MIME Types</a> - description of <code>msg.contentType</code> format</li>
</ul>
</script>

View File

@ -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]);

View File

@ -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 @@
<input type="text" id="node-input-roomId">
</div>
<div class="form-row">
<input
type="checkbox"
id="node-input-htmlMessage"
style="width: auto; margin-left: 125px; vertical-align: top"
/>
<label for="node-input-htmlMessage" style="width: auto">
HTML/Markdown message
<label for="node-input-messageType">
Message Type
</label>
<select id="node-input-messageType">
<option value="m.text">m.text</option>
<option value="m.notice">m.notice</option>
<option value="msg.type">msg.type input</option>
</select>
<div class="form-tips">
It's recommended to use m.notice for bots because the message will render in a lighter text (at least in Element client) for users to distinguish bot and real user messages.
</div>
</div>
<div class="form-row">
<label for="node-input-messageFormat">
Message Format
</label>
<select id="node-input-messageFormat">
<option value="">Default (plaintext)</option>
<option value="html">HTML</option>
<option value="msg.format">msg.format input</option>
</select>
</div>
<div class="form-tips">
Must be a valid <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types" target="_blank">MIME Type</a>
@ -48,34 +62,35 @@
</script>
<script type="text/html" data-help-name="matrix-send-message">
<p>Send an image to a matrix room.</p>
<h3>Details</h3>
<p>Sends a message to a Matrix room.</p>
<h3>Inputs</h3>
<dl class="message-properties">
<dt>msg.payload
<span class="property-type">File | String | Buffer | ReadStream | Blob</span>
<span class="property-type">String</span>
</dt>
<dd> the contents of the image to upload. </dd>
<dd> the message text. </dd>
<dt class="optional">msg.formatted_payload
<span class="property-type">String</span>
</dt>
<dd> the formatted HTML message (uses msg.payload if not defined). This only affects HTML messages.</dd>
<dt>msg.roomId
<span class="property-type">String | Null</span>
</dt>
<dd> Room ID to send image to. Optional if configured on the node. Overrides node configuration if set.</dd>
<dt class="optional">msg.filename
<dt class="optional">msg.type
<span class="property-type">String | Null</span>
</dt>
<dd> name of the image file to upload (optional). Overrides node configuration.</dd>
<dd> This is only used and required when configured so on the node. Must be set to either 'm.text' or 'm.notice'</dd>
<dt class="optional">msg.contentType
<dt class="optional">msg.format
<span class="property-type">String | Null</span>
</dt>
<dd> Content <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types" target="_blank">MIME Type</a>. Optional if configured on the node. Overrides node configuration if set.</dd>
<dt class="optional">msg.body
<span class="property-type">String | Null</span>
</dt>
<dd> this will be the display name the client will see when rendered in their chat client. If this is left empty the it uses <code>msg.filename</code>. If <code>msg.filename</code> is also undefined it sets it to empty string</dd>
<dd> This is only used and required when configured so on the node. Set to null for plain text and 'html' for HTML.</dd>
</dl>
<h3>Outputs</h3>
@ -83,6 +98,9 @@
<li>Success
<dl class="message-properties">
<dd>original msg object preserved.</dd>
<dt>msg.eventId <span class="property-type">string</span></dt>
<dd>the eventId from the posted message.</dd>
</dl>
</li>
<li>Error
@ -92,12 +110,4 @@
</dl>
</li>
</ol>
<h3>Details</h3>
<p>This node will send an image to a Matrix chat room. You can link this directly to a File In node.</p>
<h3>References</h3>
<ul>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types">MIME Types</a> - description of <code>msg.contentType</code> format</li>
</ul>
</script>

View File

@ -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);

View File

@ -1,78 +0,0 @@
<script type="text/javascript">
RED.nodes.registerType('matrix-send',{
category: 'matrix',
color: '#00b7ca',
icon: "matrix.png",
outputLabels: ["success", "error"],
inputs:1,
outputs:2,
defaults: {
name: { value: null },
matrixServer: { value: "", type: "matrix-server-config" },
room: { value: null }
},
label: function() {
return this.name || "matrix-send";
}
});
</script>
<script type="text/html" data-template-name="matrix-send">
<div class="form-row">
<label for="node-input-name"><i class="icon-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
</div>
<div class="form-row">
<label for="node-input-matrixServer"><i class="fa fa-user"></i> Matrix Server Config</label>
<input type="text" id="node-input-matrixServer">
</div>
<div class="form-row">
<label for="node-input-roomId"><i class="fa fa-user"></i> Room ID</label>
<input type="text" id="node-input-roomId">
</div>
</script>
<script type="text/html" data-help-name="matrix-send">
<p>Send a message to a matrix room.</p>
<h3>Inputs</h3>
<dl class="message-properties">
<dt>payload
<span class="property-type">string</span>
</dt>
<dd> the payload of the message to publish. </dd>
<dt class="optional">topic <span class="property-type">string</span></dt>
<dd> the Matrix room ID to send the message to. Setting it will override the room ID configured on the node.</dd>
</dl>
<h3>Outputs</h3>
<ol class="node-ports">
<li>Standard output
<dl class="message-properties">
<dt>payload <span class="property-type">string</span></dt>
<dd>the standard output of the command.</dd>
</dl>
</li>
<li>Standard error
<dl class="message-properties">
<dt>payload <span class="property-type">string</span></dt>
<dd>the standard error of the command.</dd>
</dl>
</li>
</ol>
<h3>Details</h3>
<p><code>msg.payload</code> is used as the payload of the published message.
If it contains an Object it will be converted to a JSON string before being sent.
If it contains a binary Buffer the message will be published as-is.</p>
<p>The topic used can be configured in the node or, if left blank, can be set
by <code>msg.topic</code>.</p>
<p>Likewise the QoS and retain values can be configured in the node or, if left
blank, set by <code>msg.qos</code> and <code>msg.retain</code> respectively.</p>
<h3>References</h3>
<ul>
<li><a>Twitter API docs</a> - full description of <code>msg.tweet</code> property</li>
<li><a>GitHub</a> - the nodes github repository</li>
</ul>
</script>

View File

@ -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);
}

View File

@ -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;