- 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:
parent
61aa32e8a6
commit
cd99955115
35
README.md
35
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.
|
||||
### 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
2
package-lock.json
generated
@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "node-red-contrib-matrix-support",
|
||||
"name": "node-red-contrib-matrix-chat",
|
||||
"version": "0.0.1",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
|
14
package.json
14
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",
|
||||
|
102
src/matrix-react.html
Normal file
102
src/matrix-react.html
Normal 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
78
src/matrix-react.js
Normal 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);
|
||||
}
|
@ -42,57 +42,133 @@
|
||||
<div class="form-row">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="node-config-input-ignoreText"
|
||||
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"
|
||||
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"
|
||||
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"
|
||||
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>
|
||||
|
@ -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) {
|
||||
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;
|
||||
}
|
||||
|
||||
if(knownMessageType) {
|
||||
node.send(msg);
|
||||
} else {
|
||||
node.warn("Uknown message type: " + msg.type);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -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>
|
||||
|
@ -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;
|
||||
|
@ -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>
|
@ -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;
|
||||
.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.matrixError = e;
|
||||
msg.error = e;
|
||||
node.send([null, msg]);
|
||||
});
|
||||
}).catch(function(e){
|
||||
})
|
||||
.catch(function(e){
|
||||
node.warn("Error uploading image message " + e);
|
||||
msg.matrixError = e;
|
||||
node.send([null, msg]);
|
||||
|
@ -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>
|
@ -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);
|
||||
|
@ -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>
|
@ -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);
|
||||
}
|
@ -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;
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user