Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
73d802ff0d | ||
|
|
cf1bd78ee1 | ||
|
|
a10667a0e8 | ||
|
|
aaecaaec39 | ||
|
|
be5416a68e | ||
|
|
f8c5344a4e | ||
|
|
3c99bf21a9 | ||
|
|
63728d86a5 | ||
|
|
ff66222687 | ||
|
|
fa92f79304 | ||
|
|
f70d73b011 | ||
|
|
6a310de708 |
17
README.md
17
README.md
@@ -3,6 +3,8 @@ Matrix chat server client for [Node-RED](https://nodered.org/)
|
||||
|
||||
***Currently we are in beta. We ask that you open any issues you have on our repository to help us reach a stable well tested version. Things may change & break before our first release so check changelog before updating.***
|
||||
|
||||
If you need help with this feel free to join our public matrix room at [#node-red-contrib-matrix-chat:skylar.tech](https://app.element.io/#/room/#node-red-contrib-matrix-chat:skylar.tech)
|
||||
|
||||
### Features
|
||||
|
||||
The following is supported from this package:
|
||||
@@ -30,9 +32,6 @@ The following is supported from this package:
|
||||
|
||||
Therefore, you can easily build a bot, chat relay, or administrate your Matrix server from within [Node-RED](https://nodered.org/).
|
||||
|
||||
### Usage
|
||||
We have examples! [Check them out](https://github.com/Skylar-Tech/node-red-contrib-matrix-chat/tree/master/examples#readme)
|
||||
|
||||
### 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:
|
||||
@@ -40,8 +39,16 @@ You can either install from within Node-RED by searching for `node-red-contrib-m
|
||||
npm install node-red-contrib-matrix-chat
|
||||
```
|
||||
|
||||
### Usage
|
||||
We have examples! [Check them out](https://github.com/Skylar-Tech/node-red-contrib-matrix-chat/tree/master/examples#readme)
|
||||
|
||||
#### Extra functionality
|
||||
You are not limited by just the nodes we have created. If you turn on global access when setting up your Matrix Client you can access the client directly from any function node to write your own logic.
|
||||
|
||||
View an example [here](https://github.com/Skylar-Tech/node-red-contrib-matrix-chat/tree/master/examples#use-function-node-to-run-any-command)
|
||||
|
||||
### End-to-End Encryption Notes
|
||||
Currently this module has no way of getting encryption keys from other devices on the same account. Therefore it is recommended you use the bot exclusively with Node-RED after it's creation. Failure to do so will lead to your bot being unable to receive messages from e2ee rooms it joined from another client. Shared secret registration makes this super easy since it returns a token and device ID.
|
||||
Currently, this module has no way of getting encryption keys from other devices on the same account. Therefore it is recommended you use the bot exclusively with Node-RED after it's creation. Failure to do so will lead to your bot being unable to receive messages from e2ee rooms it joined from another client. Shared secret registration makes this super easy since it returns a token and device ID.
|
||||
|
||||
This module stores a folder in your Node-RED directory called `matrix-client-storage` and is it vital that you periodically back this up if you are using e2ee. This is where the client stores all the keys necessary to decrypt messages and if lost you will lose access to e2e rooms. If you move your client to another NR install make sure to migrate this folder as well (and do not let both the old and new client run at same time).
|
||||
|
||||
@@ -61,4 +68,4 @@ You will need a user to use this module. Luckily this module comes with a node t
|
||||
### Contributing
|
||||
All contributions are welcome! If you do add a feature please do a pull request so that everyone benefits :)
|
||||
|
||||
Sharing is caring!
|
||||
Sharing is caring!
|
||||
|
||||
@@ -9,6 +9,7 @@ Build something cool with these nodes? Feel free to submit a pull request to sha
|
||||
|
||||
- [Create User with Shared Secret Registration](#create-user-with-shared-secret-registration)
|
||||
- [Create/Edit Synapse User](#createedit-synapse-user)
|
||||
- [Use function node to run any command](#use-function-node-to-run-any-command)
|
||||
- [Respond to "ping" with "pong"](#respond-to-ping-with-pong)
|
||||
- [Respond to "html" with an HTML message](#respond-to-html-with-an-html-message)
|
||||
- [Respond to "image" with an uploaded image](#respond-to-image-with-an-uploaded-image)
|
||||
@@ -39,6 +40,18 @@ Allows an administrator to create or modify a user account with a specified `msg
|
||||

|
||||
|
||||
|
||||
### Use function node to run any command
|
||||
|
||||
[View JSON](custom-redact-function-node.json)
|
||||
|
||||
If we do not have a node for something you want to do (such as redacting events/messages) you can do this manually with a function node.
|
||||
|
||||
**Note:** You should make sure to catch any errors in your function node otherwise you could cause Node-RED to crash.
|
||||
|
||||
To view what sort of functions you have access to check out the `client.ts` file from `matrix-js-sdk` [here](https://github.com/matrix-org/matrix-js-sdk/blob/master/src/client.ts).
|
||||
|
||||

|
||||
|
||||
### Respond to "ping" with "pong"
|
||||
|
||||
[View JSON](respond-to-html-with-html.json)
|
||||
|
||||
113
examples/custom-redact-function-node.json
Normal file
113
examples/custom-redact-function-node.json
Normal file
@@ -0,0 +1,113 @@
|
||||
[
|
||||
{
|
||||
"id": "89eed7ddf7a96070",
|
||||
"type": "function",
|
||||
"z": "f025a8b9fbd1b054",
|
||||
"name": "Redact Message",
|
||||
"func": "let matrixClient = global.get(\"matrixClient['@bot:example.com']\"),\n matrixOnline = global.get(\"matrixClientOnline['@bot:example.com']\");\n\nif(!matrixOnline) {\n msg.error = 'Matrix client not connected.';\n return [null, msg];\n}\n \nmatrixClient.redactEvent(msg.topic, msg.eventId, undefined, {\n reason: 'Redacted, muhahaha!'\n})\n .then(function(e) {\n msg.eventId = e.eventId;\n node.send([msg, null]);\n })\n .catch(function(e){\n node.warn(\"Error sending message \" + e);\n msg.error = e;\n node.send([null, msg]);\n });",
|
||||
"outputs": 2,
|
||||
"noerr": 0,
|
||||
"initialize": "",
|
||||
"finalize": "",
|
||||
"libs": [],
|
||||
"x": 730,
|
||||
"y": 1580,
|
||||
"wires": [
|
||||
[
|
||||
"5decd492e2da79d9"
|
||||
],
|
||||
[
|
||||
"fadcf8c32bdfd8be"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "5decd492e2da79d9",
|
||||
"type": "debug",
|
||||
"z": "f025a8b9fbd1b054",
|
||||
"name": "",
|
||||
"active": true,
|
||||
"tosidebar": true,
|
||||
"console": false,
|
||||
"tostatus": false,
|
||||
"complete": "true",
|
||||
"targetType": "full",
|
||||
"statusVal": "",
|
||||
"statusType": "auto",
|
||||
"x": 890,
|
||||
"y": 1560,
|
||||
"wires": []
|
||||
},
|
||||
{
|
||||
"id": "f3636f4221511a81",
|
||||
"type": "comment",
|
||||
"z": "f025a8b9fbd1b054",
|
||||
"name": "If message is \"redact\" then redact the message.",
|
||||
"info": "",
|
||||
"x": 420,
|
||||
"y": 1540,
|
||||
"wires": []
|
||||
},
|
||||
{
|
||||
"id": "9f777828840bb9a2",
|
||||
"type": "matrix-receive",
|
||||
"z": "f025a8b9fbd1b054",
|
||||
"name": "",
|
||||
"roomId": "",
|
||||
"acceptText": true,
|
||||
"acceptEmotes": false,
|
||||
"acceptStickers": false,
|
||||
"acceptReactions": false,
|
||||
"acceptFiles": false,
|
||||
"acceptImages": false,
|
||||
"x": 320,
|
||||
"y": 1580,
|
||||
"wires": [
|
||||
[
|
||||
"21a00b09f8c85399"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "21a00b09f8c85399",
|
||||
"type": "switch",
|
||||
"z": "f025a8b9fbd1b054",
|
||||
"name": "filter on msg.payload",
|
||||
"property": "payload",
|
||||
"propertyType": "msg",
|
||||
"rules": [
|
||||
{
|
||||
"t": "eq",
|
||||
"v": "redact",
|
||||
"vt": "str"
|
||||
}
|
||||
],
|
||||
"checkall": "true",
|
||||
"repair": false,
|
||||
"outputs": 1,
|
||||
"x": 520,
|
||||
"y": 1580,
|
||||
"wires": [
|
||||
[
|
||||
"89eed7ddf7a96070"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "fadcf8c32bdfd8be",
|
||||
"type": "debug",
|
||||
"z": "f025a8b9fbd1b054",
|
||||
"name": "",
|
||||
"active": true,
|
||||
"tosidebar": true,
|
||||
"console": false,
|
||||
"tostatus": false,
|
||||
"complete": "true",
|
||||
"targetType": "full",
|
||||
"statusVal": "",
|
||||
"statusType": "auto",
|
||||
"x": 890,
|
||||
"y": 1600,
|
||||
"wires": []
|
||||
}
|
||||
]
|
||||
BIN
examples/custom-redact-function-node.png
Normal file
BIN
examples/custom-redact-function-node.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 18 KiB |
1275
package-lock.json
generated
1275
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "node-red-contrib-matrix-chat",
|
||||
"version": "0.2.0",
|
||||
"version": "0.2.4",
|
||||
"description": "Matrix chat server client for Node-RED",
|
||||
"dependencies": {
|
||||
"fs-extra": "^9.1.0",
|
||||
@@ -8,7 +8,8 @@
|
||||
"isomorphic-webcrypto": "^2.3.8",
|
||||
"matrix-js-sdk": "^12.2.0",
|
||||
"node-localstorage": "^2.2.1",
|
||||
"olm": "https://packages.matrix.org/npm/olm/olm-3.1.4.tgz",
|
||||
"olm": "https://packages.matrix.org/npm/olm/olm-3.2.1.tgz",
|
||||
"process": "^0.11.10",
|
||||
"utf8": "^3.0.0"
|
||||
},
|
||||
"node-red": {
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
<dt class="optional">msg.payload
|
||||
<span class="property-type">string | object | null</span>
|
||||
</dt>
|
||||
<dd> a list of options to pass to the /createRoom API. <a href="https://matrix.org/docs/spec/client_server/r0.4.0#id266">Click here</a> for information about what can be passed. If this is a string it just sets the room name and ignores other options.</dd>
|
||||
<dd> a list of options to pass to the /createRoom API. <a href="https://matrix.org/docs/spec/client_server/latest#post-matrix-client-r0-createroom">Click here</a> for information about what can be passed. If this is a string it just sets the room name and ignores other options.</dd>
|
||||
</dl>
|
||||
|
||||
<h3>Outputs</h3>
|
||||
|
||||
@@ -8,7 +8,8 @@
|
||||
outputs: 2,
|
||||
defaults: {
|
||||
name: { value: null },
|
||||
server: { value: "", type: "matrix-server-config" }
|
||||
server: { value: "", type: "matrix-server-config" },
|
||||
roomId: { value: null },
|
||||
},
|
||||
label: function() {
|
||||
return this.name || "Room Invite";
|
||||
@@ -27,6 +28,11 @@
|
||||
<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" placeholder="msg.topic">
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script type="text/html" data-help-name="matrix-invite-room">
|
||||
|
||||
@@ -6,6 +6,7 @@ module.exports = function(RED) {
|
||||
|
||||
this.name = n.name;
|
||||
this.server = RED.nodes.getNode(n.server);
|
||||
this.roomId = n.roomId;
|
||||
|
||||
if(!this.server) {
|
||||
node.error('Server must be configured on the node.');
|
||||
@@ -47,7 +48,7 @@ module.exports = function(RED) {
|
||||
|
||||
msg.topic = node.roomId || msg.topic;
|
||||
if(!msg.topic) {
|
||||
node.error("msg.topic must be defined or configured on the node.");
|
||||
node.error("room must be defined in either msg.topic or in node config");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
</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">
|
||||
<input type="text" id="node-input-contentType" placeholder="msg.contentType">
|
||||
</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> (ex: image/png) or left empty
|
||||
|
||||
@@ -109,7 +109,10 @@ module.exports = function(RED) {
|
||||
|
||||
if(msgFormat === 'html') {
|
||||
content.format = "org.matrix.custom.html";
|
||||
content.formatted_body = msg.formatted_payload || msg.payload;
|
||||
content.formatted_body =
|
||||
(typeof msg.formatted_payload !== 'undefined' && msg.formatted_payload)
|
||||
? msg.formatted_payload.toString()
|
||||
: msg.payload.toString();
|
||||
}
|
||||
|
||||
node.server.matrixClient.sendMessage(msg.topic, content)
|
||||
|
||||
@@ -11,7 +11,8 @@
|
||||
defaults: {
|
||||
name: { value: null },
|
||||
autoAcceptRoomInvites: { value: true },
|
||||
enableE2ee: { type: "checkbox", value: true }
|
||||
enableE2ee: { type: "checkbox", value: true },
|
||||
global: { type: "checkbox", value: true }
|
||||
},
|
||||
icon: "matrix.png",
|
||||
label: function() {
|
||||
@@ -53,7 +54,7 @@
|
||||
id="node-config-input-autoAcceptRoomInvites"
|
||||
style="width: auto; margin-left: 125px; vertical-align: top"
|
||||
/>
|
||||
<label for="node-config-input-autoAcceptRoomInvites" style="width: auto">
|
||||
<label for="node-config-input-autoAcceptRoomInvites" style="width: auto;max-width:50%;">
|
||||
Auto join invited rooms
|
||||
</label>
|
||||
</div>
|
||||
@@ -63,17 +64,38 @@
|
||||
id="node-config-input-enableE2ee"
|
||||
style="width: auto; margin-left: 125px; vertical-align: top"
|
||||
/>
|
||||
<label for="node-config-input-enableE2ee" style="width: auto">
|
||||
Enable end-to-end encryption (requires DeviceID)
|
||||
<label for="node-config-input-enableE2ee" style="width: auto;max-width:50%;">
|
||||
Enable end-to-end encryption (requires a Device ID to be set.)
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="form-tips" style="margin-bottom: 12px;">
|
||||
E2ee requires a Device ID to be set.
|
||||
<div class="form-row">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="node-config-input-global"
|
||||
style="width: auto; margin-left: 125px; vertical-align: top"
|
||||
/>
|
||||
<label for="node-config-input-global" style="width: auto">
|
||||
Global access to Matrix Client
|
||||
</label>
|
||||
<div class="form-tips" style="margin-bottom: 12px;">
|
||||
If enabled this allows you to access the matrix client directly with a Function node. This way you can do <a href="https://github.com/Skylar-Tech/node-red-contrib-matrix-chat/tree/master/examples#use-function-node-to-run-any-command" target="_blank">whatever you want</a> with the client. Example:<br>
|
||||
<code style="white-space: normal;">let client = global.get("matrixClient['@bot:example.com']");</code>
|
||||
</div>
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script type="text/html" data-help-name="matrix-server-config">
|
||||
<h3>Details</h3>
|
||||
<p>Matrix client configuration node</p>
|
||||
<p>Matrix client connection configuration</p>
|
||||
|
||||
<h3>Setting up an account</h3>
|
||||
<div>
|
||||
<p>
|
||||
You need an account for your client to use. If you are going to be using End-to-End Encryption you should generate the bot and only use it within Node-RED otherwise if you have other clients connected on the same user it could cause problems with e2ee (key sharing is currently not supported).
|
||||
</p>
|
||||
<p>If you have access to the server directly you can use Shared Secret Registration as described <a href="https://github.com/Skylar-Tech/node-red-contrib-matrix-chat/tree/master/examples#create-user-with-shared-secret-registration" target="_blank" style="text-decoration: underline;">here</a>.</p>
|
||||
<p>If this is a server you do not administrate/have access to follow these instructions:</p>
|
||||
<ol><li>In a private/incognito browser window, open Element.</li><li>Log in to the account you want to get the access token for, such as the bot's account. <strong>Do not setup key storage</strong>.</li><li>Click on the bot's name in the top left corner then "Settings".</li><li>(Optional) Set your bot's display name and avatar.</li><li>Click the "Help & About" tab (left side of the dialog).</li><li>Scroll to the bottom and click the <code><click to reveal></code> part of <code>Access Token: <click to reveal></code>.</li><li>Copy your access token to a safe place, like the bot's configuration file.</li><li><strong>Do not log out.</strong> Instead, just close the window. If you used a private browsing session, you should be able to still use Element for your own account. Logging out deletes the access token from the server, making the bot unable to use it.</li></ol>
|
||||
</div>
|
||||
</script>
|
||||
@@ -26,7 +26,7 @@ module.exports = function(RED) {
|
||||
|
||||
node.setMaxListeners(1000);
|
||||
|
||||
this.connected = false;
|
||||
this.connected = null;
|
||||
this.name = n.name;
|
||||
this.userId = this.credentials.userId;
|
||||
this.deviceId = this.credentials.deviceId || null;
|
||||
@@ -34,6 +34,7 @@ module.exports = function(RED) {
|
||||
this.autoAcceptRoomInvites = n.autoAcceptRoomInvites;
|
||||
this.enableE2ee = n.enableE2ee || false;
|
||||
this.e2ee = (this.enableE2ee && this.deviceId);
|
||||
this.globalAccess = n.global;
|
||||
|
||||
if(!this.credentials.accessToken) {
|
||||
node.log("Matrix connection failed: missing access token.");
|
||||
@@ -42,6 +43,27 @@ module.exports = function(RED) {
|
||||
} else if(!this.userId) {
|
||||
node.log("Matrix connection failed: missing user ID.");
|
||||
} else {
|
||||
node.setConnected = function(connected, cb) {
|
||||
if (node.connected !== connected) {
|
||||
node.connected = connected;
|
||||
if(typeof cb === 'function') {
|
||||
cb(connected);
|
||||
}
|
||||
|
||||
if (connected) {
|
||||
node.log("Matrix server connection ready.");
|
||||
node.emit("connected");
|
||||
} else {
|
||||
node.emit("disconnected");
|
||||
}
|
||||
|
||||
if(this.globalAccess) {
|
||||
this.context().global.set('matrixClientOnline["'+this.userId+'"]', connected);
|
||||
}
|
||||
}
|
||||
};
|
||||
node.setConnected(false);
|
||||
|
||||
let localStorageDir = storageDir + '/' + MatrixFolderNameFromUserId(this.userId);
|
||||
|
||||
fs.ensureDirSync(storageDir); // create storage directory if it doesn't exist
|
||||
@@ -56,6 +78,11 @@ module.exports = function(RED) {
|
||||
deviceId: this.deviceId || undefined,
|
||||
});
|
||||
|
||||
// set globally if configured to do so
|
||||
if(this.globalAccess) {
|
||||
this.context().global.set('matrixClient["'+this.userId+'"]', node.matrixClient);
|
||||
}
|
||||
|
||||
node.on('close', function(done) {
|
||||
if(node.matrixClient) {
|
||||
node.matrixClient.close();
|
||||
@@ -66,18 +93,6 @@ module.exports = function(RED) {
|
||||
done();
|
||||
});
|
||||
|
||||
node.setConnected = function(connected) {
|
||||
if (node.connected !== connected) {
|
||||
node.connected = connected;
|
||||
if (connected) {
|
||||
node.log("Matrix server connection ready.");
|
||||
node.emit("connected");
|
||||
} else {
|
||||
node.emit("disconnected");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
node.isConnected = function() {
|
||||
return node.connected;
|
||||
};
|
||||
@@ -120,20 +135,27 @@ module.exports = function(RED) {
|
||||
node.matrixClient.on("sync", async function(state, prevState, data) {
|
||||
switch (state) {
|
||||
case "ERROR":
|
||||
node.error("Connection to Matrix server lost");
|
||||
node.setConnected(false);
|
||||
node.setConnected(false, function(){
|
||||
node.error("Connection to Matrix server lost");
|
||||
});
|
||||
break;
|
||||
|
||||
case "RECONNECTING":
|
||||
node.setConnected(false, function(){
|
||||
node.log("Trying to reconnect to matrix server");
|
||||
});
|
||||
break;
|
||||
case "STOPPED":
|
||||
node.setConnected(false);
|
||||
node.setConnected(false, function(){
|
||||
node.log("Matrix client stopped");
|
||||
});
|
||||
break;
|
||||
|
||||
case "SYNCING":
|
||||
break;
|
||||
|
||||
case "PREPARED":
|
||||
node.setConnected(true);
|
||||
node.setConnected(true, function(){
|
||||
node.log("Matrix client connected");
|
||||
});
|
||||
break;
|
||||
|
||||
// case "PREPARED":
|
||||
|
||||
@@ -64,7 +64,7 @@ module.exports = function(RED) {
|
||||
'POST',
|
||||
node.encodeUri(
|
||||
"/_synapse/admin/v1/join/$room_id_or_alias",
|
||||
{ $room_id_or_alias: roomId },
|
||||
{ $room_id_or_alias: msg.topic },
|
||||
),
|
||||
undefined,
|
||||
{ "user_id": msg.userId },
|
||||
|
||||
@@ -39,7 +39,7 @@ module.exports = function(RED) {
|
||||
};
|
||||
|
||||
if(msg.guests) {
|
||||
queryParams['guests'] = msg.guests;
|
||||
queryParams['guests'] = msg.guests ? 'true' : 'false';
|
||||
}
|
||||
|
||||
if(msg.order_by) {
|
||||
|
||||
Reference in New Issue
Block a user