Initial commit of an alpha version.

This commit is contained in:
Skylar Sadlier 2021-08-14 09:01:36 -06:00
parent 46a976e060
commit 61aa32e8a6
15 changed files with 2341 additions and 0 deletions

1126
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

34
package.json Normal file
View File

@ -0,0 +1,34 @@
{
"name": "node-red-contrib-matrix-support",
"version": "0.0.1",
"description": "Matrix chat server support 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-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"
}
},
"keywords": [
"node-red",
"matrix",
"support",
"bot",
"chat"
],
"repository": {
"type": "git",
"url": "https://github.com/skylar-tech/node-red-contrib-matrix-support"
},
"author": {
"name": "Skylar Sadlier",
"url": "https://skylar.tech"
},
"license": "SEE LICENSE FILE"
}

BIN
src/icons/matrix.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

99
src/matrix-receive.html Normal file
View File

@ -0,0 +1,99 @@
<script type="text/javascript">
RED.nodes.registerType('matrix-receive',{
category: 'matrix',
color: '#00b7ca',
icon: "matrix.png",
outputLabels: ["message"],
inputs:0,
outputs:1,
defaults: {
name: { value: null },
server: { value: "", type: "matrix-server-config" },
roomId: {"value": null},
ignoreText: {"value": false},
ignoreReactions: {"value": false},
ignoreFiles: {"value": false},
ignoreImages: {"value": false},
},
label: function() {
return this.name || "Matrix Receive";
},
paletteLabel: 'Receive'
});
</script>
<script type="text/html" data-template-name="matrix-receive">
<div class="form-row">
<label for="node-input-name"><i class="fa fa-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-server"></i> Matrix Server</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-tips">Enter a single room, comma separated list of rooms, or leave blank to get from all</div>
<div class="form-row" style="margin-left: 100px;margin-top:10px;font-weight:bold;">
Timeline event filters
</div>
<div class="form-row">
<input
type="checkbox"
id="node-config-input-ignoreText"
style="width: auto; margin-left: 125px; vertical-align: top"
/>
<label for="node-config-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"
/>
<label for="node-config-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"
/>
<label for="node-config-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"
/>
<label for="node-config-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>
<h3>Outputs</h3>
<ol class="node-ports">
<li>Message
<dl class="message-properties">
<dt>topic <span class="property-type">string</span></dt>
<dd>the room the message originated from.</dd>
</dl>
<dl class="message-properties">
<dt>payload <span class="property-type">string</span></dt>
<dd>the message from the server.</dd>
</dl>
</li>
</ol>
</script>

106
src/matrix-receive.js Normal file
View File

@ -0,0 +1,106 @@
module.exports = function(RED) {
function MatrixReceiveMessage(n) {
RED.nodes.createNode(this, n);
let node = this;
this.name = n.name;
this.server = RED.nodes.getNode(n.server);
this.ignoreText = n.ignoreText;
this.ignoreReactions = n.ignoreReactions;
this.ignoreFiles = n.ignoreFiles;
this.ignoreImages = n.ignoreImages;
node.status({ fill: "red", shape: "ring", text: "disconnected" });
if (!node.server) {
node.error("No configuration node");
return;
}
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.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;
}
}
let content = event.getContent(),
msg = {};
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);
switch(msg.type) {
case 'm.text':
if(node.ignoreText) return;
break;
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;
break;
case 'm.file':
if(node.ignoreFiles) return;
msg.file = {
info: event.getContent().info,
url: node.server.matrixClient.mxcUrlToHttp(event.getContent().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)
};
break;
}
node.send(msg);
});
});
}
RED.nodes.registerType("matrix-receive", MatrixReceiveMessage);
}

97
src/matrix-send-file.html Normal file
View File

@ -0,0 +1,97 @@
<script type="text/javascript">
RED.nodes.registerType('matrix-send-file',{
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 },
contentType: { value: null }
},
label: function() {
return this.name || "Send File";
},
paletteLabel: 'Send File'
});
</script>
<script type="text/html" data-template-name="matrix-send-file">
<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-contentType"><i class="fa fa-user"></i> Content-Type</label>
<input type="text" id="node-input-contentType" placeholder="application/pdf">
</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-send-file">
<p>Send 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>
</dt>
<dd> the contents of the file to upload. </dd>
<dt>msg.roomId
<span class="property-type">String | Null</span>
</dt>
<dd> Room ID to send file to. Optional if configured on the node. Overrides node configuration if set.</dd>
<dt class="optional">msg.filename
<span class="property-type">String | Null</span>
</dt>
<dd> name of the file to upload (optional). Overrides node configuration.</dd>
<dt class="optional">msg.contentType
<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>
</dl>
<h3>Outputs</h3>
<ol class="node-ports">
<li>Success
<dl class="message-properties">
<dd>original msg object preserved.</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>
<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>
</ul>
</script>

89
src/matrix-send-file.js Normal file
View File

@ -0,0 +1,89 @@
module.exports = function(RED) {
function MatrixSendFile(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.contentType = n.contentType;
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.warn("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.warn("Room must be specified in msg.roomId or in configuration");
return;
}
if(!msg.payload) {
node.error('msg.payload is required');
return;
}
msg.contentType = msg.contentType || node.contentType;
if(!msg.contentType) {
node.error('msg.contentType is required');
return;
}
node.log("Uploading file " + msg.filename);
node.server.matrixClient.uploadContent(
msg.payload, {
name: msg.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.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){
const content = {
msgtype: 'm.file',
url: file.content_uri,
body: (msg.body || msg.filename) || "",
};
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.error = e;
node.send([null, msg]);
});
});
}
RED.nodes.registerType("matrix-send-file", MatrixSendFile);
}

View File

@ -0,0 +1,97 @@
<script type="text/javascript">
RED.nodes.registerType('matrix-send-image',{
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 },
contentType: { value: null }
},
label: function() {
return this.name || "Send Image";
},
paletteLabel: 'Send Image'
});
</script>
<script type="text/html" data-template-name="matrix-send-image">
<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-contentType"><i class="fa fa-user"></i> Content-Type</label>
<input type="text" id="node-input-contentType" placeholder="application/png">
</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-send-image">
<p>Send an image 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>
</dt>
<dd> the contents of the image to upload. </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
<span class="property-type">String | Null</span>
</dt>
<dd> name of the image file to upload (optional). Overrides node configuration.</dd>
<dt class="optional">msg.contentType
<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>
</dl>
<h3>Outputs</h3>
<ol class="node-ports">
<li>Success
<dl class="message-properties">
<dd>original msg object preserved.</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>
<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>

83
src/matrix-send-image.js Normal file
View File

@ -0,0 +1,83 @@
module.exports = function(RED) {
function MatrixSendImage(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.contentType = n.contentType;
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.warn("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.warn("Room must be specified in msg.roomId or in configuration");
return;
}
if(!msg.payload) {
node.error('msg.payload is required');
return;
}
msg.contentType = msg.contentType || node.contentType;
if(!msg.contentType) {
node.error('msg.contentType is required');
return;
}
node.log("Uploading image " + msg.filename);
node.server.matrixClient.uploadContent(
msg.payload, {
name: msg.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.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){
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){
node.warn("Error uploading image message " + e);
msg.matrixError = e;
node.send([null, msg]);
});
});
}
RED.nodes.registerType("matrix-send-image", MatrixSendImage);
}

View File

@ -0,0 +1,103 @@
<script type="text/javascript">
RED.nodes.registerType('matrix-send-message',{
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 },
htmlMessage: { value: false }
},
label: function() {
return this.name || "Send Message";
},
paletteLabel: 'Send Message'
});
</script>
<script type="text/html" data-template-name="matrix-send-message">
<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">
<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>
</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-send-message">
<p>Send an image 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>
</dt>
<dd> the contents of the image to upload. </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
<span class="property-type">String | Null</span>
</dt>
<dd> name of the image file to upload (optional). Overrides node configuration.</dd>
<dt class="optional">msg.contentType
<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>
</dl>
<h3>Outputs</h3>
<ol class="node-ports">
<li>Success
<dl class="message-properties">
<dd>original msg object preserved.</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>
<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

@ -0,0 +1,77 @@
module.exports = function(RED) {
function MatrixSendImage(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.htmlMessage = n.htmlMessage;
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.warn("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.warn("Room must be specified in msg.roomId or in configuration");
return;
}
if(!msg.payload) {
node.error('msg.payload is required');
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]);
});
}
});
}
RED.nodes.registerType("matrix-send-message", MatrixSendImage);
}

78
src/matrix-send.html Normal file
View File

@ -0,0 +1,78 @@
<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>

175
src/matrix-send.js Normal file
View File

@ -0,0 +1,175 @@
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

@ -0,0 +1,63 @@
<script type="text/javascript">
RED.nodes.registerType('matrix-server-config',{
category: 'config',
color: '#00b7ca',
credentials: {
userId: { type: "text", required: true },
accessToken: { type: "password", required: true },
url: { type: "text", required: true }
},
defaults: {
name: { value: null },
autoAcceptRoomInvites: { value: true }
},
icon: "matrix.png",
label: function() {
return this.name || undefined;
}
});
</script>
<script type="text/html" data-template-name="matrix-server-config">
<div class="form-row">
<label for="node-config-input-name"><i class="fa fa-tag"></i> Name</label>
<input type="text" id="node-config-input-name" placeholder="Name">
</div>
<div class="form-row">
<label for="node-config-input-userId"><i class="fa fa-server"></i> User ID</label>
<input type="text" placeholder="@example:matrix.org" id="node-config-input-userId">
</div>
<div class="form-row">
<label for="node-config-input-accessToken"><i class="fa fa-key"></i> Access Token</label>
<input type="text" id="node-config-input-accessToken">
</div>
<div class="form-tips" style="margin-bottom: 12px;">
View the <a href="javascript:$('#red-ui-tab-help-link-button').click();">node docs</a> to figure out how to generate an Access Token.
</div>
<div class="form-row">
<label for="node-config-input-url"><i class="fa fa-globe"></i> Server URL</label>
<input type="text" placeholder="https://matrix.org" id="node-config-input-url">
</div>
<div class="form-row">
<input
type="checkbox"
id="node-config-input-autoAcceptRoomInvites"
style="width: auto; margin-left: 125px; vertical-align: top"
/>
<label for="node-config-input-autoAcceptRoomInvites" style="width: auto">
Auto join invited rooms
</label>
</div>
</script>
<script type="text/html" data-help-name="matrix-server-config">
<p>Some useful help text to introduce the node.</p>
<h3>Outputs</h3>
<dl class="message-properties">
<dt>payload
<span class="property-type">string | buffer</span>
</dt>
<h3>Details</h3>
<p>Some more information about the node.</p>
</dl>
</script>

114
src/matrix-server-config.js Normal file
View File

@ -0,0 +1,114 @@
module.exports = function(RED) {
let sdk = require("matrix-js-sdk");
function MatrixServerNode(n) {
// we should add support for getting access token automatically from username/password
// ref: https://matrix.org/docs/guides/usage-of-the-matrix-js-sdk#login-with-an-access-token
RED.nodes.createNode(this, n);
let node = this;
node.log("Initializing Matrix Server Config node");
if(!this.credentials) {
this.credentials = {};
}
this.connected = false;
this.name = n.name;
this.userId = this.credentials.userId;
this.url = this.credentials.url;
this.autoAcceptRoomInvites = n.autoAcceptRoomInvites;
if(!this.credentials.accessToken) {
node.log("Matrix connection failed: missing access token.");
} else if(!this.url) {
node.log("Matrix connection failed: missing server URL.");
} else if(!this.userId) {
node.log("Matrix connection failed: missing user ID.");
} else {
node.log("Initializing Matrix Server Config node");
node.matrixClient = sdk.createClient({
baseUrl: this.url,
accessToken: this.credentials.accessToken,
userId: this.userId
});
node.on('close', function(done) {
if(node.matrixClient) {
node.matrixClient.close();
node.matrixClient.stopClient();
node.setConnected(false);
}
done();
});
node.setConnected = function(connected) {
if (node.connected !== connected) {
node.connected = connected;
if (connected) {
node.emit("connected");
} else {
node.emit("disconnected");
}
}
};
node.isConnected = function() {
return node.connected;
};
// handle auto-joining rooms
node.matrixClient.on("RoomMember.membership", function(event, member) {
if (member.membership === "invite" && member.userId === node.userId) {
if(node.autoAcceptRoomInvites) {
node.matrixClient.joinRoom(member.roomId).then(function() {
node.log("Automatically accepted invitation to join room " + member.roomId);
}).catch(function(e) {
node.warn("Cannot join room (could be from being kicked/banned) " + member.roomId + ": " + e);
});
} else {
node.log("Got invite to join room " + member.roomId);
}
}
});
node.matrixClient.on("sync", function(state, prevState, data) {
switch (state) {
case "ERROR":
node.error("Connection to Matrix server lost");
console.log(state, prevState, data);
node.setConnected(false);
break;
case "STOPPED":
node.setConnected(false);
break;
case "SYNCING":
node.setConnected(true);
break;
case "PREPARED":
// the client instance is ready to be queried.
node.log("Matrix server connection ready.");
node.setConnected(true);
break;
}
});
node.log("Connecting to Matrix server...");
node.matrixClient.startClient();
}
}
RED.nodes.registerType("matrix-server-config", MatrixServerNode, {
credentials: {
userId: { type:"text", required: true },
accessToken: { type:"text", required: true },
url: { type: "text", required: true },
}
});
}