Compare commits
73 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0d14d149d3 | |||
| bde0bc7e53 | |||
| 1f37650763 | |||
| 0166620ef0 | |||
| 6dcadeb8d2 | |||
| 1a08750828 | |||
| 8e601a88e1 | |||
| e0e45c6b18 | |||
| a336ce1beb | |||
| 978a039498 | |||
| f10fdbfab2 | |||
| ddfa90bd77 | |||
|
|
78f8ab7abb | ||
|
|
ce8be4a30f | ||
|
|
3e808cabec | ||
|
|
2fdc7482ce | ||
|
|
c7f9d56df2 | ||
|
|
3c042ae47d | ||
|
|
0a34870fa3 | ||
|
|
768a1c8ce0 | ||
|
|
22dd9b4ca3 | ||
|
|
462f9670c2 | ||
|
|
e4b01c40c2 | ||
|
|
908d60835d | ||
|
|
4c17a21008 | ||
|
|
bd4f6ea486 | ||
|
|
5ef0b6a11f | ||
|
|
97f27e61c6 | ||
|
|
7bdadc0fe9 | ||
|
|
5f129560aa | ||
|
|
00bc14e1c7 | ||
|
|
9f41b67174 | ||
|
|
4e93b7253e | ||
|
|
ecb4427217 | ||
|
|
3e70369cae | ||
|
|
2ce80e2906 | ||
|
|
0c657caf5f | ||
|
|
848fd0ec9d | ||
|
|
fef40f4ea9 | ||
|
|
595fbca3df | ||
|
|
487f17a439 | ||
|
|
68cb5a026e | ||
|
|
d01733c647 | ||
|
|
ed146e98d8 | ||
|
|
3dc6363a88 | ||
|
|
380e548425 | ||
|
|
27dd4d81a2 | ||
|
|
de3c58044f | ||
|
|
db1901ed1e | ||
|
|
ac68060825 | ||
|
|
d304cb95f6 | ||
|
|
85764c08d3 | ||
|
|
050be29d64 | ||
|
|
33823dea25 | ||
|
|
d826c58b51 | ||
|
|
d0ba671452 | ||
|
|
282c8ffc80 | ||
|
|
6dd2ec75f0 | ||
|
|
ae386b90a6 | ||
|
|
4d1ec9e65c | ||
|
|
2fcd463eb6 | ||
|
|
b44629c84e | ||
|
|
9f0a962991 | ||
|
|
f3df8c6f97 | ||
|
|
d6ab098063 | ||
|
|
614920c40c | ||
|
|
58d7d23b2f | ||
|
|
25c92b787a | ||
|
|
0b9c59a7be | ||
|
|
9c836c9262 | ||
|
|
9c62a73b15 | ||
|
|
447b18835c | ||
|
|
2341cbef10 |
BIN
@matrix-org_olm-3.2.8.tgz
Normal file
31
README.md
@@ -1,9 +1,6 @@
|
||||
# node-red-contrib-matrix-chat
|
||||
Matrix chat server client for [Node-RED](https://nodered.org/)
|
||||
# node-red-contrib-matrix-chat-jw
|
||||
[Matrix](https://matrix.org/) 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
|
||||
|
||||
@@ -11,8 +8,10 @@ The following is supported from this package:
|
||||
|
||||
- End-to-end encryption
|
||||
- [Currently a WIP](#end-to-end-encryption-notes)
|
||||
- Receive events from a room (messages, reactions, images, and files) whether encrypted or not
|
||||
- Receive events from a room (messages, reactions, images, audio, locations, and files) whether encrypted or not
|
||||
- Send Images/Files (sending files to e2ee room doesn't currently encrypt them yet)
|
||||
- Edit messages
|
||||
- Delete events (messages, reactions, etc)
|
||||
- Decrypt files in e2ee rooms
|
||||
- Send HTML/Plain Text Message/Notice
|
||||
- React to messages
|
||||
@@ -24,9 +23,7 @@ The following is supported from this package:
|
||||
- Get a user list from a room
|
||||
- Kick user from room
|
||||
- Ban user from room
|
||||
- Join a room
|
||||
- Create a room
|
||||
- Invite to a room
|
||||
- Join, Create, Invite, and Leave rooms
|
||||
- Synapse admin API to force add user to room (requires bot to be in same room already)
|
||||
|
||||
|
||||
@@ -36,16 +33,12 @@ Therefore, you can easily build a bot, chat relay, or administrate your Matrix s
|
||||
|
||||
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
|
||||
npm install node-red-contrib-matrix-chat-jw
|
||||
```
|
||||
|
||||
### 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.
|
||||
@@ -57,15 +50,5 @@ Want to contribute? Any help on getting the last pieces of e2ee figured out woul
|
||||
### Generate user
|
||||
You will need a user to use this module. Luckily this module comes with a node that allows you to register users to a homeserver using the secret registration endpoint. This is perfect because it returns an `access_token` as well as a `device_id` which is exactly what we need.
|
||||
|
||||
[Click here](https://github.com/Skylar-Tech/node-red-contrib-matrix-chat/tree/master/examples#readme) to see how to generate a user using secret registration
|
||||
|
||||
|
||||
|
||||
### 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!
|
||||
|
||||
@@ -15,7 +15,18 @@ Build something cool with these nodes? Feel free to submit a pull request to sha
|
||||
- [Respond to "image" with an uploaded image](#respond-to-image-with-an-uploaded-image)
|
||||
- [Respond to "file" with an uploaded file](#respond-to-file-with-an-uploaded-file)
|
||||
- [Respond to "react" with a reaction](#respond-to-react-with-a-reaction)
|
||||
- [Accept room invites from specific user](#accept-room-invites-from-specific-user)
|
||||
- [Leave room when someone says "bye"](#leave-room-when-someone-says-bye)
|
||||
- [Remove messages containing "delete"](#remove-messages-containing-delete)
|
||||
- [Respond to "users" with full list of server users](#respond-to-users-with-full-list-of-server-users)
|
||||
- [Respond to "newroom" by creating new room and inviting user](#respond-to-newroom-by-creating-new-room-and-inviting-user)
|
||||
- [Respond to "joinroom <room_id_or_alias>" by joining mentioned room](#respond-to-joinroom-room_id_or_alias-by-joining-mentioned-room)
|
||||
- [Respond to "rooms <user_id>" with user's rooms (list server's rooms if <user_id> is left blank)](#respond-to-rooms-user_id-with-users-rooms-list-servers-rooms-if-user_id-is-left-blank)
|
||||
- [Respond to "whois <user_id>" with information about the user's session](#respond-to-whois-user_id-with-information-about-the-users-session)
|
||||
- [Respond to "room_users" with current room's users](#respond-to-room_users-with-current-rooms-users)
|
||||
- [Download & store all received files/images](#download--store-all-received-filesimages)
|
||||
- [Kick/Ban user from room](#kickban-user-from-room)
|
||||
- [Deactivate user](#deactivate-user)
|
||||
|
||||
|
||||
### Create user with Shared Secret Registration
|
||||
@@ -28,7 +39,7 @@ Edit the object on the inject node to the user/pass combo you want to create and
|
||||
|
||||
**Note:** This only works on Synapse servers.
|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
### Create/Edit Synapse User
|
||||
@@ -37,28 +48,30 @@ Edit the object on the inject node to the user/pass combo you want to create and
|
||||
|
||||
Allows an administrator to create or modify a user account with a specified `msg.userId`.
|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
### 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.
|
||||
If we do not have a node for something you want to do you can do this manually with a function node. We now have a node for removing events but this is still a good example.
|
||||
|
||||
**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)
|
||||
[View JSON](respond-ping-pong.json)
|
||||
|
||||
Use this flow to respond to anyone that says "ping" with "pong" into the same room.
|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
|
||||
@@ -68,7 +81,7 @@ Use this flow to respond to anyone that says "ping" with "pong" into the same ro
|
||||
|
||||
Use this flow to respond to anyone that says "html" with an example HTML message. This shows how easy it is to send HTML.
|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
|
||||
@@ -78,7 +91,7 @@ Use this flow to respond to anyone that says "html" with an example HTML message
|
||||
|
||||
You will need an image on the machine running Node-RED. In this case example.png exists inside the Node-RED directory.
|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
|
||||
@@ -88,7 +101,7 @@ You will need an image on the machine running Node-RED. In this case example.png
|
||||
|
||||
You will need a file on the machine running Node-RED. In this case sample.pdf exists inside the Node-RED directory.
|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
|
||||
@@ -98,7 +111,37 @@ You will need a file on the machine running Node-RED. In this case sample.pdf ex
|
||||
|
||||
Give a 👍 reaction when someone says "react"
|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
|
||||
### Accept room invites from specific user
|
||||
|
||||
[View JSON](accept-room-invites.json)
|
||||
|
||||
Accept room invites from specific user.
|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
### Leave room when someone says bye
|
||||
|
||||
[View JSON](leave-room-bye.json)
|
||||
|
||||
Leave room when someone says "bye".
|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
### Remove messages containing "delete"
|
||||
|
||||
[View JSON](delete-event.json)
|
||||
|
||||
Any messages containing "delete" will try to be removed by the client.
|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
@@ -106,8 +149,109 @@ Give a 👍 reaction when someone says "react"
|
||||
|
||||
[View JSON](respond-users-list.json)
|
||||
|
||||
When someone sends the text "users" they get a HTML message back containing all the current users on the server.
|
||||
When someone sends the text "users" they get a HTML message back containing all the current users on the server. If your server has a lot of users this paginates and sends a message with 25 users per message.
|
||||
|
||||
This requires admin privileges.
|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
### Respond to "newroom" by creating new room and inviting user
|
||||
|
||||
[View JSON](respond-users-list.json)
|
||||
|
||||
When someone sends "newroom" a new room will be created and the user that said the message will be invited. The client will also send a welcome message into the new room.
|
||||
|
||||

|
||||
|
||||
|
||||
### Respond to "joinroom <room_id_or_alias>" by joining mentioned room
|
||||
|
||||
[View JSON](respond-joinroom.json)
|
||||
|
||||
When someone sends "newroom" a new room will be created and the user that said the message will be invited. The client will also send a welcome message into the new room.
|
||||
|
||||

|
||||
|
||||
### Respond to "rooms <user_id>" with user's rooms (list server's rooms if <user_id> is left blank)
|
||||
|
||||
[View JSON](respond-rooms.json)
|
||||
|
||||
Responds to "rooms <user_id>" with that user's rooms. If the message is just "rooms" it responds with a list of all rooms the server is participating in.
|
||||
|
||||
Note: If there are a lot of rooms this may fail to send the message as it is too large. This also only works for user's that are on the current server.
|
||||
|
||||
This requires admin privileges.
|
||||
|
||||

|
||||
|
||||
|
||||
### Respond to "whois <user_id>" with information about the user's session
|
||||
|
||||
[View JSON](respond-whois.json)
|
||||
|
||||
This lists out the user's session info. Each session contains the IP address, when it was last seen, and the user agent. Useful to find out more about a specific user on your server.
|
||||
|
||||
Note: If there are a lot of sessions this may fail to send the message as it is too large. This also only works for user's that are on the current server.
|
||||
|
||||
This requires admin privileges.
|
||||
|
||||

|
||||
|
||||
|
||||
### Respond to "room_users" with current room's users
|
||||
|
||||
[View JSON](respond-room-users.json)
|
||||
|
||||
List out the users participating in a room.
|
||||
|
||||
Note: If there are a lot of users in the room this will fail to send due to a large message error.
|
||||
|
||||

|
||||
|
||||
|
||||
### Download & store all received files/images
|
||||
|
||||
[View JSON](store-received-files.json)
|
||||
|
||||
Download received files/images. If the file is encrypted it will decrypt it for you. The decrypt node downloads the file for you otherwise you need to use a HTTP Request node to download the file.
|
||||
|
||||
Note: You may need to edit the storage directory for this to work. Default action is to create a `downloads` folder in the Node-RED directory and places files in that but there is a good chance your Node-RED instance doesn't have access to write to this directory.
|
||||
|
||||

|
||||
|
||||
|
||||
### Kick/Ban user from room
|
||||
|
||||
[View JSON](room-kick-ban.json)
|
||||
|
||||
If you say "kick @test:example.com" the bot will kick @test:example.com from the current room.
|
||||
|
||||
If you say "ban @test:example.com" the bot will ban @test:example.com from the current room.
|
||||
|
||||
Note: This requires the bot to have permissions to kick/ban in the current room.
|
||||
|
||||

|
||||
|
||||
|
||||
### Deactivate user
|
||||
|
||||
[View JSON](deactivate-user.json)
|
||||
|
||||
If you say "deactivate_user @test:example.com" the bot will deactivate the @test:example.com account on the server.
|
||||
|
||||
Note: This requires the bot to be a server admin.
|
||||
|
||||
WARNING: Accounts that are deleted cannot be restored. If you want to temp-disable edit the user instead.
|
||||
|
||||

|
||||
|
||||
### Force user to join room
|
||||
|
||||
[View JSON](force-join-room.json)
|
||||
|
||||
If you say "force_join @test:example.com !320j90mf0394f:example.com" the bot will force the user `@test:example.com` into room `!320j90mf0394f:example.com`
|
||||
|
||||
Note: This requires the bot to be a server admin. This also only works for rooms on the same server.
|
||||
|
||||

|
||||
94
examples/accept-room-invites.json
Normal file
@@ -0,0 +1,94 @@
|
||||
[
|
||||
{
|
||||
"id": "64f76474ff7a3727",
|
||||
"type": "matrix-room-invite",
|
||||
"z": "f025a8b9fbd1b054",
|
||||
"name": "",
|
||||
"server": "8da0ef83f77f8e24",
|
||||
"roomId": null,
|
||||
"x": 270,
|
||||
"y": 2380,
|
||||
"wires": [
|
||||
[
|
||||
"22f6056fa5bc5bd0"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "6d6f304a0a6342b8",
|
||||
"type": "matrix-join-room",
|
||||
"z": "f025a8b9fbd1b054",
|
||||
"name": "",
|
||||
"server": "8da0ef83f77f8e24",
|
||||
"x": 770,
|
||||
"y": 2380,
|
||||
"wires": [
|
||||
[
|
||||
"1409ebb4a0e65663"
|
||||
],
|
||||
[
|
||||
"1409ebb4a0e65663"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "22f6056fa5bc5bd0",
|
||||
"type": "switch",
|
||||
"z": "f025a8b9fbd1b054",
|
||||
"name": "msg.userId == @skylord123:skylar.tech",
|
||||
"property": "userId",
|
||||
"propertyType": "msg",
|
||||
"rules": [
|
||||
{
|
||||
"t": "eq",
|
||||
"v": "@skylord123:skylar.tech",
|
||||
"vt": "str"
|
||||
}
|
||||
],
|
||||
"checkall": "true",
|
||||
"repair": false,
|
||||
"outputs": 1,
|
||||
"x": 520,
|
||||
"y": 2380,
|
||||
"wires": [
|
||||
[
|
||||
"6d6f304a0a6342b8"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "043f74e6d76b1eb0",
|
||||
"type": "comment",
|
||||
"z": "f025a8b9fbd1b054",
|
||||
"name": "Only accept room invites from specific user",
|
||||
"info": "",
|
||||
"x": 360,
|
||||
"y": 2340,
|
||||
"wires": []
|
||||
},
|
||||
{
|
||||
"id": "1409ebb4a0e65663",
|
||||
"type": "debug",
|
||||
"z": "f025a8b9fbd1b054",
|
||||
"name": "",
|
||||
"active": true,
|
||||
"tosidebar": true,
|
||||
"console": false,
|
||||
"tostatus": false,
|
||||
"complete": "true",
|
||||
"targetType": "full",
|
||||
"statusVal": "",
|
||||
"statusType": "auto",
|
||||
"x": 910,
|
||||
"y": 2380,
|
||||
"wires": []
|
||||
},
|
||||
{
|
||||
"id": "8da0ef83f77f8e24",
|
||||
"type": "matrix-server-config",
|
||||
"name": null,
|
||||
"autoAcceptRoomInvites": false,
|
||||
"enableE2ee": true,
|
||||
"global": true
|
||||
}
|
||||
]
|
||||
BIN
examples/accept-room-invites.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
116
examples/deactivate-user.json
Normal file
@@ -0,0 +1,116 @@
|
||||
[
|
||||
{
|
||||
"id": "843c48978ab1b0a0",
|
||||
"type": "switch",
|
||||
"z": "f025a8b9fbd1b054",
|
||||
"name": "is from me",
|
||||
"property": "userId",
|
||||
"propertyType": "msg",
|
||||
"rules": [
|
||||
{
|
||||
"t": "eq",
|
||||
"v": "@skylord123:skylar.tech",
|
||||
"vt": "str"
|
||||
}
|
||||
],
|
||||
"checkall": "true",
|
||||
"repair": false,
|
||||
"outputs": 1,
|
||||
"x": 590,
|
||||
"y": 3260,
|
||||
"wires": [
|
||||
[
|
||||
"b77d071077487ddc"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "93ea76ce5e65a250",
|
||||
"type": "matrix-receive",
|
||||
"z": "f025a8b9fbd1b054",
|
||||
"name": "",
|
||||
"server": null,
|
||||
"roomId": "",
|
||||
"acceptText": true,
|
||||
"acceptEmotes": false,
|
||||
"acceptStickers": false,
|
||||
"acceptReactions": false,
|
||||
"acceptFiles": false,
|
||||
"acceptImages": false,
|
||||
"x": 420,
|
||||
"y": 3260,
|
||||
"wires": [
|
||||
[
|
||||
"843c48978ab1b0a0"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "d6795b522954bccd",
|
||||
"type": "comment",
|
||||
"z": "f025a8b9fbd1b054",
|
||||
"name": "Respond to \"deactivate_user <user_id>\" by deactivating the user on the server",
|
||||
"info": "",
|
||||
"x": 590,
|
||||
"y": 3220,
|
||||
"wires": []
|
||||
},
|
||||
{
|
||||
"id": "b77d071077487ddc",
|
||||
"type": "switch",
|
||||
"z": "f025a8b9fbd1b054",
|
||||
"name": "msg.payload filter",
|
||||
"property": "payload",
|
||||
"propertyType": "msg",
|
||||
"rules": [
|
||||
{
|
||||
"t": "regex",
|
||||
"v": "^deactivate_user",
|
||||
"vt": "str",
|
||||
"case": false
|
||||
}
|
||||
],
|
||||
"checkall": "true",
|
||||
"repair": false,
|
||||
"outputs": 1,
|
||||
"x": 770,
|
||||
"y": 3260,
|
||||
"wires": [
|
||||
[
|
||||
"3c0e1e130e91206e"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "3c0e1e130e91206e",
|
||||
"type": "function",
|
||||
"z": "f025a8b9fbd1b054",
|
||||
"name": "",
|
||||
"func": "let matches = msg.payload.match(/^deactivate_user ?(\\@.*)?/);\nmsg.userId = matches[1].trim() ? matches[1].trim() : null;\nif(!msg.userId) {\n return null;\n}\nreturn msg;",
|
||||
"outputs": 1,
|
||||
"noerr": 0,
|
||||
"initialize": "",
|
||||
"finalize": "",
|
||||
"libs": [],
|
||||
"x": 420,
|
||||
"y": 3320,
|
||||
"wires": [
|
||||
[
|
||||
"1f4870b7171cc70f"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "1f4870b7171cc70f",
|
||||
"type": "matrix-synapse-deactivate-user",
|
||||
"z": "f025a8b9fbd1b054",
|
||||
"name": "",
|
||||
"server": null,
|
||||
"x": 610,
|
||||
"y": 3320,
|
||||
"wires": [
|
||||
[],
|
||||
[]
|
||||
]
|
||||
}
|
||||
]
|
||||
BIN
examples/deactivate-user.png
Normal file
|
After Width: | Height: | Size: 24 KiB |
73
examples/delete-event.json
Normal file
@@ -0,0 +1,73 @@
|
||||
[
|
||||
{
|
||||
"id": "fed9197df27197a4",
|
||||
"type": "matrix-receive",
|
||||
"z": "f025a8b9fbd1b054",
|
||||
"name": "",
|
||||
"server": null,
|
||||
"roomId": "",
|
||||
"acceptText": true,
|
||||
"acceptEmotes": true,
|
||||
"acceptStickers": true,
|
||||
"acceptReactions": true,
|
||||
"acceptFiles": true,
|
||||
"acceptImages": true,
|
||||
"x": 340,
|
||||
"y": 1560,
|
||||
"wires": [
|
||||
[
|
||||
"b289bb4fed9fa166"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "b289bb4fed9fa166",
|
||||
"type": "switch",
|
||||
"z": "f025a8b9fbd1b054",
|
||||
"name": "",
|
||||
"property": "payload",
|
||||
"propertyType": "msg",
|
||||
"rules": [
|
||||
{
|
||||
"t": "cont",
|
||||
"v": "delete",
|
||||
"vt": "str"
|
||||
}
|
||||
],
|
||||
"checkall": "true",
|
||||
"repair": false,
|
||||
"outputs": 1,
|
||||
"x": 490,
|
||||
"y": 1560,
|
||||
"wires": [
|
||||
[
|
||||
"48766b632ab2e6a1"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "48766b632ab2e6a1",
|
||||
"type": "matrix-delete-event",
|
||||
"z": "f025a8b9fbd1b054",
|
||||
"name": "",
|
||||
"server": null,
|
||||
"roomId": "",
|
||||
"reason": "Requested deletion",
|
||||
"x": 630,
|
||||
"y": 1560,
|
||||
"wires": [
|
||||
[],
|
||||
[]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "11f9cbbed7b95c83",
|
||||
"type": "comment",
|
||||
"z": "f025a8b9fbd1b054",
|
||||
"name": "Delete messages containing \"delete\"",
|
||||
"info": "",
|
||||
"x": 480,
|
||||
"y": 1520,
|
||||
"wires": []
|
||||
}
|
||||
]
|
||||
BIN
examples/delete-event.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
117
examples/force-join-room.json
Normal file
@@ -0,0 +1,117 @@
|
||||
[
|
||||
{
|
||||
"id": "897b8de477d7d0df",
|
||||
"type": "switch",
|
||||
"z": "f025a8b9fbd1b054",
|
||||
"name": "is from me",
|
||||
"property": "userId",
|
||||
"propertyType": "msg",
|
||||
"rules": [
|
||||
{
|
||||
"t": "eq",
|
||||
"v": "@skylord123:skylar.tech",
|
||||
"vt": "str"
|
||||
}
|
||||
],
|
||||
"checkall": "true",
|
||||
"repair": false,
|
||||
"outputs": 1,
|
||||
"x": 470,
|
||||
"y": 3460,
|
||||
"wires": [
|
||||
[
|
||||
"1f73f36092a9536b"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "973b2caeb68f3a60",
|
||||
"type": "matrix-receive",
|
||||
"z": "f025a8b9fbd1b054",
|
||||
"name": "",
|
||||
"server": null,
|
||||
"roomId": "",
|
||||
"acceptText": true,
|
||||
"acceptEmotes": false,
|
||||
"acceptStickers": false,
|
||||
"acceptReactions": false,
|
||||
"acceptFiles": false,
|
||||
"acceptImages": false,
|
||||
"x": 300,
|
||||
"y": 3460,
|
||||
"wires": [
|
||||
[
|
||||
"897b8de477d7d0df"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "be5c7290e6223b7b",
|
||||
"type": "comment",
|
||||
"z": "f025a8b9fbd1b054",
|
||||
"name": "Say \"force_join <user_id> <room_id_or_alias>\" to force a user into a room",
|
||||
"info": "",
|
||||
"x": 460,
|
||||
"y": 3420,
|
||||
"wires": []
|
||||
},
|
||||
{
|
||||
"id": "1f73f36092a9536b",
|
||||
"type": "switch",
|
||||
"z": "f025a8b9fbd1b054",
|
||||
"name": "msg.payload filter",
|
||||
"property": "payload",
|
||||
"propertyType": "msg",
|
||||
"rules": [
|
||||
{
|
||||
"t": "regex",
|
||||
"v": "^deactivate_user",
|
||||
"vt": "str",
|
||||
"case": false
|
||||
}
|
||||
],
|
||||
"checkall": "true",
|
||||
"repair": false,
|
||||
"outputs": 1,
|
||||
"x": 650,
|
||||
"y": 3460,
|
||||
"wires": [
|
||||
[
|
||||
"6a2a73bc9dfdaece"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "6a2a73bc9dfdaece",
|
||||
"type": "function",
|
||||
"z": "f025a8b9fbd1b054",
|
||||
"name": "",
|
||||
"func": "let matches = msg.payload.match(/^force_join (\\@.*) (.*)/);\nmsg.userId = matches[1].trim() ? matches[1].trim() : null;\nif(!msg.userId) {\n return null;\n}\nmsg.topic = matches[2].trim() ? matches[2].trim() : null;\nif(!msg.topic) {\n return null;\n}\nreturn msg;",
|
||||
"outputs": 1,
|
||||
"noerr": 0,
|
||||
"initialize": "",
|
||||
"finalize": "",
|
||||
"libs": [],
|
||||
"x": 300,
|
||||
"y": 3520,
|
||||
"wires": [
|
||||
[
|
||||
"212e060e320918d3"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "212e060e320918d3",
|
||||
"type": "matrix-synapse-join-room",
|
||||
"z": "f025a8b9fbd1b054",
|
||||
"name": "",
|
||||
"server": null,
|
||||
"roomId": "",
|
||||
"x": 490,
|
||||
"y": 3520,
|
||||
"wires": [
|
||||
[],
|
||||
[]
|
||||
]
|
||||
}
|
||||
]
|
||||
BIN
examples/force-join-room.png
Normal file
|
After Width: | Height: | Size: 23 KiB |
103
examples/leave-room-bye.json
Normal file
@@ -0,0 +1,103 @@
|
||||
[
|
||||
{
|
||||
"id": "997c354038202dba",
|
||||
"type": "comment",
|
||||
"z": "f025a8b9fbd1b054",
|
||||
"name": "Leave room when someone says \"bye\"",
|
||||
"info": "",
|
||||
"x": 350,
|
||||
"y": 2520,
|
||||
"wires": []
|
||||
},
|
||||
{
|
||||
"id": "69c4ea189be94feb",
|
||||
"type": "matrix-receive",
|
||||
"z": "f025a8b9fbd1b054",
|
||||
"name": "",
|
||||
"server": "8da0ef83f77f8e24",
|
||||
"roomId": "",
|
||||
"acceptText": true,
|
||||
"acceptEmotes": true,
|
||||
"acceptStickers": true,
|
||||
"acceptReactions": true,
|
||||
"acceptFiles": true,
|
||||
"acceptAudio": true,
|
||||
"acceptImages": true,
|
||||
"acceptLocations": true,
|
||||
"x": 280,
|
||||
"y": 2560,
|
||||
"wires": [
|
||||
[
|
||||
"19e1d64b63ae8a1f"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "19e1d64b63ae8a1f",
|
||||
"type": "switch",
|
||||
"z": "f025a8b9fbd1b054",
|
||||
"name": "msg.payload == bye",
|
||||
"property": "payload",
|
||||
"propertyType": "msg",
|
||||
"rules": [
|
||||
{
|
||||
"t": "eq",
|
||||
"v": "bye",
|
||||
"vt": "str"
|
||||
}
|
||||
],
|
||||
"checkall": "true",
|
||||
"repair": false,
|
||||
"outputs": 1,
|
||||
"x": 480,
|
||||
"y": 2560,
|
||||
"wires": [
|
||||
[
|
||||
"db0e51f8e7793f92"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "db0e51f8e7793f92",
|
||||
"type": "matrix-leave-room",
|
||||
"z": "f025a8b9fbd1b054",
|
||||
"name": "",
|
||||
"server": "8da0ef83f77f8e24",
|
||||
"roomId": null,
|
||||
"x": 670,
|
||||
"y": 2560,
|
||||
"wires": [
|
||||
[
|
||||
"3791f551bf0e4fc4"
|
||||
],
|
||||
[
|
||||
"3791f551bf0e4fc4"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "3791f551bf0e4fc4",
|
||||
"type": "debug",
|
||||
"z": "f025a8b9fbd1b054",
|
||||
"name": "",
|
||||
"active": true,
|
||||
"tosidebar": true,
|
||||
"console": false,
|
||||
"tostatus": false,
|
||||
"complete": "true",
|
||||
"targetType": "full",
|
||||
"statusVal": "",
|
||||
"statusType": "auto",
|
||||
"x": 810,
|
||||
"y": 2560,
|
||||
"wires": []
|
||||
},
|
||||
{
|
||||
"id": "8da0ef83f77f8e24",
|
||||
"type": "matrix-server-config",
|
||||
"name": null,
|
||||
"autoAcceptRoomInvites": false,
|
||||
"enableE2ee": true,
|
||||
"global": true
|
||||
}
|
||||
]
|
||||
BIN
examples/leave-room-bye.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
90
examples/respond-joinroom.json
Normal file
@@ -0,0 +1,90 @@
|
||||
[
|
||||
{
|
||||
"id": "9a737b0c57b35063",
|
||||
"type": "switch",
|
||||
"z": "f025a8b9fbd1b054",
|
||||
"name": "is from me",
|
||||
"property": "userId",
|
||||
"propertyType": "msg",
|
||||
"rules": [
|
||||
{
|
||||
"t": "eq",
|
||||
"v": "@skylord123:skylar.tech",
|
||||
"vt": "str"
|
||||
}
|
||||
],
|
||||
"checkall": "true",
|
||||
"repair": false,
|
||||
"outputs": 1,
|
||||
"x": 430,
|
||||
"y": 2020,
|
||||
"wires": [
|
||||
[
|
||||
"2a44927d9317a4b4"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "f377ad37af7dc49e",
|
||||
"type": "matrix-receive",
|
||||
"z": "f025a8b9fbd1b054",
|
||||
"name": "",
|
||||
"server": null,
|
||||
"roomId": "",
|
||||
"acceptText": true,
|
||||
"acceptEmotes": true,
|
||||
"acceptStickers": true,
|
||||
"acceptReactions": true,
|
||||
"acceptFiles": true,
|
||||
"acceptImages": true,
|
||||
"x": 260,
|
||||
"y": 2020,
|
||||
"wires": [
|
||||
[
|
||||
"9a737b0c57b35063"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "f7112a1d2808cfbb",
|
||||
"type": "comment",
|
||||
"z": "f025a8b9fbd1b054",
|
||||
"name": "Respond to \"joinroom <room_id_or_alias>\" by joining the mentioned room",
|
||||
"info": "",
|
||||
"x": 440,
|
||||
"y": 1980,
|
||||
"wires": []
|
||||
},
|
||||
{
|
||||
"id": "2a44927d9317a4b4",
|
||||
"type": "function",
|
||||
"z": "f025a8b9fbd1b054",
|
||||
"name": "msg.payload == \"joinroom\"",
|
||||
"func": "if(!msg.payload.startsWith(\"joinroom\")) {\n return null;\n}\n\nlet split = msg.payload.split(\" \");\nif(split.length < 2) {\n return; // no room provided\n}\n\nmsg.topic = split[1];\nreturn msg;",
|
||||
"outputs": 1,
|
||||
"noerr": 0,
|
||||
"initialize": "",
|
||||
"finalize": "",
|
||||
"libs": [],
|
||||
"x": 640,
|
||||
"y": 2020,
|
||||
"wires": [
|
||||
[
|
||||
"a34d05c3b6f6bd27"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "a34d05c3b6f6bd27",
|
||||
"type": "matrix-join-room",
|
||||
"z": "f025a8b9fbd1b054",
|
||||
"name": "",
|
||||
"server": null,
|
||||
"x": 850,
|
||||
"y": 2020,
|
||||
"wires": [
|
||||
[],
|
||||
[]
|
||||
]
|
||||
}
|
||||
]
|
||||
BIN
examples/respond-joinroom.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
157
examples/respond-newroom-invite.json
Normal file
@@ -0,0 +1,157 @@
|
||||
[
|
||||
{
|
||||
"id": "ea0921f33b58e337",
|
||||
"type": "switch",
|
||||
"z": "f025a8b9fbd1b054",
|
||||
"name": "msg.payload is \"newroom\"",
|
||||
"property": "payload",
|
||||
"propertyType": "msg",
|
||||
"rules": [
|
||||
{
|
||||
"t": "eq",
|
||||
"v": "newroom",
|
||||
"vt": "str"
|
||||
}
|
||||
],
|
||||
"checkall": "true",
|
||||
"repair": false,
|
||||
"outputs": 1,
|
||||
"x": 640,
|
||||
"y": 1820,
|
||||
"wires": [
|
||||
[
|
||||
"d7ce5027bc1e2eee"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "9f9a24376b94c977",
|
||||
"type": "switch",
|
||||
"z": "f025a8b9fbd1b054",
|
||||
"name": "is from me",
|
||||
"property": "userId",
|
||||
"propertyType": "msg",
|
||||
"rules": [
|
||||
{
|
||||
"t": "eq",
|
||||
"v": "@skylord123:skylar.tech",
|
||||
"vt": "str"
|
||||
}
|
||||
],
|
||||
"checkall": "true",
|
||||
"repair": false,
|
||||
"outputs": 1,
|
||||
"x": 430,
|
||||
"y": 1820,
|
||||
"wires": [
|
||||
[
|
||||
"ea0921f33b58e337"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "3bce23488138c013",
|
||||
"type": "matrix-receive",
|
||||
"z": "f025a8b9fbd1b054",
|
||||
"name": "",
|
||||
"server": null,
|
||||
"roomId": "",
|
||||
"acceptText": true,
|
||||
"acceptEmotes": true,
|
||||
"acceptStickers": true,
|
||||
"acceptReactions": true,
|
||||
"acceptFiles": true,
|
||||
"acceptImages": true,
|
||||
"x": 260,
|
||||
"y": 1820,
|
||||
"wires": [
|
||||
[
|
||||
"9f9a24376b94c977"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "d7ce5027bc1e2eee",
|
||||
"type": "matrix-create-room",
|
||||
"z": "f025a8b9fbd1b054",
|
||||
"name": "",
|
||||
"server": null,
|
||||
"x": 250,
|
||||
"y": 1880,
|
||||
"wires": [
|
||||
[
|
||||
"735b9d9fd1401e96"
|
||||
],
|
||||
[]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "735b9d9fd1401e96",
|
||||
"type": "matrix-invite-room",
|
||||
"z": "f025a8b9fbd1b054",
|
||||
"name": "",
|
||||
"server": null,
|
||||
"roomId": "",
|
||||
"x": 410,
|
||||
"y": 1880,
|
||||
"wires": [
|
||||
[
|
||||
"089f2a7f0c079b6b"
|
||||
],
|
||||
[]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "222ee3c08028c1df",
|
||||
"type": "matrix-send-message",
|
||||
"z": "f025a8b9fbd1b054",
|
||||
"name": "",
|
||||
"server": null,
|
||||
"roomId": "",
|
||||
"messageType": "m.text",
|
||||
"messageFormat": "",
|
||||
"x": 760,
|
||||
"y": 1880,
|
||||
"wires": [
|
||||
[],
|
||||
[]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "089f2a7f0c079b6b",
|
||||
"type": "change",
|
||||
"z": "f025a8b9fbd1b054",
|
||||
"name": "",
|
||||
"rules": [
|
||||
{
|
||||
"t": "set",
|
||||
"p": "payload",
|
||||
"pt": "msg",
|
||||
"to": "Welcome to my new room!",
|
||||
"tot": "str"
|
||||
}
|
||||
],
|
||||
"action": "",
|
||||
"property": "",
|
||||
"from": "",
|
||||
"to": "",
|
||||
"reg": false,
|
||||
"x": 580,
|
||||
"y": 1880,
|
||||
"wires": [
|
||||
[
|
||||
"222ee3c08028c1df"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "48a1fd26227f277e",
|
||||
"type": "comment",
|
||||
"z": "f025a8b9fbd1b054",
|
||||
"name": "Respond to \"newroom\" by creating new room and inviting person that said it then send welcome message",
|
||||
"info": "",
|
||||
"x": 520,
|
||||
"y": 1780,
|
||||
"wires": []
|
||||
}
|
||||
]
|
||||
BIN
examples/respond-newroom-invite.png
Normal file
|
After Width: | Height: | Size: 28 KiB |
134
examples/respond-room-users.json
Normal file
@@ -0,0 +1,134 @@
|
||||
[
|
||||
{
|
||||
"id": "a7581a6224cbd24d",
|
||||
"type": "matrix-room-users",
|
||||
"z": "f025a8b9fbd1b054",
|
||||
"name": "",
|
||||
"server": null,
|
||||
"roomId": "",
|
||||
"x": 440,
|
||||
"y": 2800,
|
||||
"wires": [
|
||||
[
|
||||
"3e37b5fded58782d"
|
||||
],
|
||||
[]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "2764620d2d514996",
|
||||
"type": "switch",
|
||||
"z": "f025a8b9fbd1b054",
|
||||
"name": "is from me",
|
||||
"property": "userId",
|
||||
"propertyType": "msg",
|
||||
"rules": [
|
||||
{
|
||||
"t": "eq",
|
||||
"v": "@skylord123:skylar.tech",
|
||||
"vt": "str"
|
||||
}
|
||||
],
|
||||
"checkall": "true",
|
||||
"repair": false,
|
||||
"outputs": 1,
|
||||
"x": 610,
|
||||
"y": 2740,
|
||||
"wires": [
|
||||
[
|
||||
"f487951ea1b22a95"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "7ed8fc36ba75298a",
|
||||
"type": "matrix-receive",
|
||||
"z": "f025a8b9fbd1b054",
|
||||
"name": "",
|
||||
"server": null,
|
||||
"roomId": "",
|
||||
"acceptText": true,
|
||||
"acceptEmotes": true,
|
||||
"acceptStickers": true,
|
||||
"acceptReactions": true,
|
||||
"acceptFiles": true,
|
||||
"acceptImages": true,
|
||||
"x": 440,
|
||||
"y": 2740,
|
||||
"wires": [
|
||||
[
|
||||
"2764620d2d514996"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "f487951ea1b22a95",
|
||||
"type": "switch",
|
||||
"z": "f025a8b9fbd1b054",
|
||||
"name": "msg.payload filter",
|
||||
"property": "payload",
|
||||
"propertyType": "msg",
|
||||
"rules": [
|
||||
{
|
||||
"t": "eq",
|
||||
"v": "room_users",
|
||||
"vt": "str"
|
||||
}
|
||||
],
|
||||
"checkall": "true",
|
||||
"repair": false,
|
||||
"outputs": 1,
|
||||
"x": 790,
|
||||
"y": 2740,
|
||||
"wires": [
|
||||
[
|
||||
"a7581a6224cbd24d"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "3e37b5fded58782d",
|
||||
"type": "function",
|
||||
"z": "f025a8b9fbd1b054",
|
||||
"name": "Room List (for user or all)",
|
||||
"func": "let payload = null;\nif(!msg.payload || !msg.payload.joined) {\n payload = 'No users in room.';\n} else {\n payload = `Room ${msg.topic} users:`;\n payload += '<ul>';\n for(let user_id in msg.payload.joined) {\n payload += `<li>${msg.payload.joined[user_id].display_name} - ${user_id}</li>`;\n }\n payload += '</ul>'\n}\n\nif(payload){\n msg.payload = payload;\n}\nreturn msg;",
|
||||
"outputs": 1,
|
||||
"noerr": 0,
|
||||
"initialize": "",
|
||||
"finalize": "",
|
||||
"libs": [],
|
||||
"x": 650,
|
||||
"y": 2800,
|
||||
"wires": [
|
||||
[
|
||||
"86a4895c4fe46657"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "86a4895c4fe46657",
|
||||
"type": "matrix-send-message",
|
||||
"z": "f025a8b9fbd1b054",
|
||||
"name": "Send HTML Message",
|
||||
"server": null,
|
||||
"roomId": "",
|
||||
"messageType": "m.text",
|
||||
"messageFormat": "html",
|
||||
"x": 880,
|
||||
"y": 2800,
|
||||
"wires": [
|
||||
[],
|
||||
[]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "cae1a37fe034b8f8",
|
||||
"type": "comment",
|
||||
"z": "f025a8b9fbd1b054",
|
||||
"name": "Respond to \"room_users\" with that room's users",
|
||||
"info": "",
|
||||
"x": 600,
|
||||
"y": 2700,
|
||||
"wires": []
|
||||
}
|
||||
]
|
||||
BIN
examples/respond-room-users.png
Normal file
|
After Width: | Height: | Size: 26 KiB |
151
examples/respond-rooms.json
Normal file
@@ -0,0 +1,151 @@
|
||||
[
|
||||
{
|
||||
"id": "2eb2dd1b5dbdd03b",
|
||||
"type": "comment",
|
||||
"z": "f025a8b9fbd1b054",
|
||||
"name": "Response to \"rooms\" with all rooms",
|
||||
"info": "",
|
||||
"x": 580,
|
||||
"y": 2500,
|
||||
"wires": []
|
||||
},
|
||||
{
|
||||
"id": "f3415a10f124d586",
|
||||
"type": "function",
|
||||
"z": "f025a8b9fbd1b054",
|
||||
"name": "",
|
||||
"func": "let matches = msg.payload.match(/^rooms ?(\\@.*)?/);\nmsg.userId = matches[1] ? matches[1] : null;\nreturn msg;",
|
||||
"outputs": 1,
|
||||
"noerr": 0,
|
||||
"initialize": "",
|
||||
"finalize": "",
|
||||
"libs": [],
|
||||
"x": 420,
|
||||
"y": 2620,
|
||||
"wires": [
|
||||
[
|
||||
"2cae98732747989d"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "2cae98732747989d",
|
||||
"type": "function",
|
||||
"z": "f025a8b9fbd1b054",
|
||||
"name": "Room List (for user or all)",
|
||||
"func": "let matrixClient = global.get(\"matrixClient['@bot:example.com']\"),\n matrixOnline = global.get(\"matrixClientOnline['@bot:example.com']\");\n\nif(!matrixOnline) {\n msg.payload = 'Matrix client not connected.';\n return [null, msg];\n}\n\nif(msg.userId) {\n matrixClient.http\n .authedRequest(\n undefined,\n 'GET',\n `/_synapse/admin/v1/users/${msg.userId}/joined_rooms`,\n {}, // query params\n undefined,\n { prefix: '' }\n ).then(function(e){\n msg.payload = e;\n if(!e.joined_rooms) {\n msg.payload = 'User is not part of any rooms.';\n } else {\n msg.payload = `${msg.userId}'s rooms:`;\n msg.payload += '<ul>';\n e.joined_rooms.forEach(function(room_id){\n msg.payload += `<li>${room_id}</li>`;\n });\n msg.payload += '</ul>'\n }\n node.send([msg, null]);\n }).catch(function(e){\n node.warn(\"Error fetching server user list \" + e);\n msg.payload = e;\n node.send([null, msg]);\n });\n} else {\n matrixClient.http\n .authedRequest(\n undefined,\n 'GET',\n `/_synapse/admin/v1/rooms`,\n {}, // query params\n undefined,\n { prefix: '' }\n ).then(function(e){\n msg.payload = e;\n if(!e.rooms) {\n msg.payload = 'Server has no rooms.';\n } else {\n msg.payload = `Server rooms:`;\n msg.payload += '<ul>';\n e.rooms.forEach(function(room){\n msg.payload += `<li>${room.name} - ${room.room_id} (members: ${room.joined_members} | creator: ${room.creator})</li>`;\n });\n msg.payload += '</ul>'\n }\n node.send([msg, null]);\n }).catch(function(e){\n node.warn(\"Error fetching server user list \" + e);\n msg.payload = e;\n node.send([null, msg]);\n });\n}",
|
||||
"outputs": 2,
|
||||
"noerr": 0,
|
||||
"initialize": "",
|
||||
"finalize": "",
|
||||
"libs": [],
|
||||
"x": 610,
|
||||
"y": 2620,
|
||||
"wires": [
|
||||
[
|
||||
"d7508993ec1ce895"
|
||||
],
|
||||
[
|
||||
"d7508993ec1ce895"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "2f8c696ec0dc8f6d",
|
||||
"type": "comment",
|
||||
"z": "f025a8b9fbd1b054",
|
||||
"name": "Respond to \"rooms <user_id>\" with that user's rooms",
|
||||
"info": "",
|
||||
"x": 590,
|
||||
"y": 2540,
|
||||
"wires": []
|
||||
},
|
||||
{
|
||||
"id": "d7508993ec1ce895",
|
||||
"type": "matrix-send-message",
|
||||
"z": "f025a8b9fbd1b054",
|
||||
"name": "Send HTML Message",
|
||||
"server": null,
|
||||
"roomId": "",
|
||||
"messageType": "m.text",
|
||||
"messageFormat": "html",
|
||||
"x": 840,
|
||||
"y": 2620,
|
||||
"wires": [
|
||||
[],
|
||||
[]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "13a9a0331352338a",
|
||||
"type": "switch",
|
||||
"z": "f025a8b9fbd1b054",
|
||||
"name": "is from me",
|
||||
"property": "userId",
|
||||
"propertyType": "msg",
|
||||
"rules": [
|
||||
{
|
||||
"t": "eq",
|
||||
"v": "@skylord123:skylar.tech",
|
||||
"vt": "str"
|
||||
}
|
||||
],
|
||||
"checkall": "true",
|
||||
"repair": false,
|
||||
"outputs": 1,
|
||||
"x": 590,
|
||||
"y": 2580,
|
||||
"wires": [
|
||||
[
|
||||
"afe88b8e115f661f"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "e261cc12da739901",
|
||||
"type": "matrix-receive",
|
||||
"z": "f025a8b9fbd1b054",
|
||||
"name": "",
|
||||
"server": null,
|
||||
"roomId": "",
|
||||
"acceptText": true,
|
||||
"acceptEmotes": true,
|
||||
"acceptStickers": true,
|
||||
"acceptReactions": true,
|
||||
"acceptFiles": true,
|
||||
"acceptImages": true,
|
||||
"x": 420,
|
||||
"y": 2580,
|
||||
"wires": [
|
||||
[
|
||||
"13a9a0331352338a"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "afe88b8e115f661f",
|
||||
"type": "switch",
|
||||
"z": "f025a8b9fbd1b054",
|
||||
"name": "msg.payload filter",
|
||||
"property": "payload",
|
||||
"propertyType": "msg",
|
||||
"rules": [
|
||||
{
|
||||
"t": "regex",
|
||||
"v": "^rooms",
|
||||
"vt": "str",
|
||||
"case": false
|
||||
}
|
||||
],
|
||||
"checkall": "true",
|
||||
"repair": false,
|
||||
"outputs": 1,
|
||||
"x": 770,
|
||||
"y": 2580,
|
||||
"wires": [
|
||||
[
|
||||
"f3415a10f124d586"
|
||||
]
|
||||
]
|
||||
}
|
||||
]
|
||||
BIN
examples/respond-rooms.png
Normal file
|
After Width: | Height: | Size: 27 KiB |
@@ -1,27 +1,257 @@
|
||||
[
|
||||
{
|
||||
"id": "a9b412957063b06c",
|
||||
"type": "matrix-receive",
|
||||
"id": "49e4b3a1ee3e9e1e",
|
||||
"type": "matrix-synapse-users",
|
||||
"z": "f025a8b9fbd1b054",
|
||||
"name": "",
|
||||
"roomId": "",
|
||||
"ignoreText": false,
|
||||
"ignoreReactions": false,
|
||||
"ignoreFiles": false,
|
||||
"ignoreImages": false,
|
||||
"x": 460,
|
||||
"y": 120,
|
||||
"server": null,
|
||||
"x": 610,
|
||||
"y": 2240,
|
||||
"wires": [
|
||||
[
|
||||
"4aa45cd8653ba898"
|
||||
"d4978f7c2dad7ecf"
|
||||
],
|
||||
[
|
||||
"da9d25c324cb727e"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "4aa45cd8653ba898",
|
||||
"id": "e199d3db1ea6757c",
|
||||
"type": "matrix-send-message",
|
||||
"z": "f025a8b9fbd1b054",
|
||||
"name": "Send HTML Notice",
|
||||
"server": null,
|
||||
"roomId": "",
|
||||
"messageType": "m.notice",
|
||||
"messageFormat": "html",
|
||||
"x": 970,
|
||||
"y": 2240,
|
||||
"wires": [
|
||||
[
|
||||
"e248881d6749ba70"
|
||||
],
|
||||
[
|
||||
"da9d25c324cb727e"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "d4978f7c2dad7ecf",
|
||||
"type": "function",
|
||||
"z": "f025a8b9fbd1b054",
|
||||
"name": "",
|
||||
"func": "msg.next_token = msg.payload.next_token || false;\nlet new_payload = '<ul>';\nmsg.payload.users.forEach(function(user, index){\n if(msg.guests == 'false' && user.is_guest) {\n return;\n }\n new_payload += '<li>'+ JSON.stringify(user, null, 2).replace(/(?:\\r\\n|\\r|\\n)/g, \"\\n<br>\"); + '</li>'\n});\nmsg.payload = new_payload + '</ul>';\n\nreturn msg;",
|
||||
"outputs": 1,
|
||||
"noerr": 0,
|
||||
"initialize": "",
|
||||
"finalize": "",
|
||||
"libs": [],
|
||||
"x": 800,
|
||||
"y": 2240,
|
||||
"wires": [
|
||||
[
|
||||
"e199d3db1ea6757c"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "880cac7307d9a786",
|
||||
"type": "comment",
|
||||
"z": "f025a8b9fbd1b054",
|
||||
"name": "Response to \"users\" with full server user list (excluding guests)",
|
||||
"info": "",
|
||||
"x": 620,
|
||||
"y": 2160,
|
||||
"wires": []
|
||||
},
|
||||
{
|
||||
"id": "469d2ebd9995747a",
|
||||
"type": "matrix-send-message",
|
||||
"z": "f025a8b9fbd1b054",
|
||||
"name": "",
|
||||
"server": null,
|
||||
"roomId": "",
|
||||
"messageType": "m.notice",
|
||||
"messageFormat": "",
|
||||
"x": 600,
|
||||
"y": 2300,
|
||||
"wires": [
|
||||
[],
|
||||
[]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "da9d25c324cb727e",
|
||||
"type": "change",
|
||||
"z": "f025a8b9fbd1b054",
|
||||
"name": "",
|
||||
"rules": [
|
||||
{
|
||||
"t": "set",
|
||||
"p": "payload",
|
||||
"pt": "msg",
|
||||
"to": "error",
|
||||
"tot": "msg"
|
||||
}
|
||||
],
|
||||
"action": "",
|
||||
"property": "",
|
||||
"from": "",
|
||||
"to": "",
|
||||
"reg": false,
|
||||
"x": 420,
|
||||
"y": 2300,
|
||||
"wires": [
|
||||
[
|
||||
"469d2ebd9995747a"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "86577e484ca26aca",
|
||||
"type": "change",
|
||||
"z": "f025a8b9fbd1b054",
|
||||
"name": "",
|
||||
"rules": [
|
||||
{
|
||||
"t": "set",
|
||||
"p": "guests",
|
||||
"pt": "msg",
|
||||
"to": "false",
|
||||
"tot": "str"
|
||||
},
|
||||
{
|
||||
"t": "set",
|
||||
"p": "limit",
|
||||
"pt": "msg",
|
||||
"to": "25",
|
||||
"tot": "num"
|
||||
},
|
||||
{
|
||||
"t": "set",
|
||||
"p": "order_by",
|
||||
"pt": "msg",
|
||||
"to": "creation_ts",
|
||||
"tot": "str"
|
||||
}
|
||||
],
|
||||
"action": "",
|
||||
"property": "",
|
||||
"from": "",
|
||||
"to": "",
|
||||
"reg": false,
|
||||
"x": 420,
|
||||
"y": 2240,
|
||||
"wires": [
|
||||
[
|
||||
"49e4b3a1ee3e9e1e"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "e248881d6749ba70",
|
||||
"type": "switch",
|
||||
"z": "f025a8b9fbd1b054",
|
||||
"name": "msg.payload == \"users\"",
|
||||
"name": "More to paginate?",
|
||||
"property": "next_token",
|
||||
"propertyType": "msg",
|
||||
"rules": [
|
||||
{
|
||||
"t": "istype",
|
||||
"v": "string",
|
||||
"vt": "string"
|
||||
}
|
||||
],
|
||||
"checkall": "true",
|
||||
"repair": false,
|
||||
"outputs": 1,
|
||||
"x": 1190,
|
||||
"y": 2240,
|
||||
"wires": [
|
||||
[
|
||||
"11d7fcf6335bc75b"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "11d7fcf6335bc75b",
|
||||
"type": "change",
|
||||
"z": "f025a8b9fbd1b054",
|
||||
"name": "",
|
||||
"rules": [
|
||||
{
|
||||
"t": "set",
|
||||
"p": "from",
|
||||
"pt": "msg",
|
||||
"to": "next_token",
|
||||
"tot": "msg"
|
||||
}
|
||||
],
|
||||
"action": "",
|
||||
"property": "",
|
||||
"from": "",
|
||||
"to": "",
|
||||
"reg": false,
|
||||
"x": 1370,
|
||||
"y": 2240,
|
||||
"wires": [
|
||||
[
|
||||
"49e4b3a1ee3e9e1e"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "8b48e27a118999ec",
|
||||
"type": "switch",
|
||||
"z": "f025a8b9fbd1b054",
|
||||
"name": "is from me",
|
||||
"property": "userId",
|
||||
"propertyType": "msg",
|
||||
"rules": [
|
||||
{
|
||||
"t": "eq",
|
||||
"v": "@skylord123:skylar.tech",
|
||||
"vt": "str"
|
||||
}
|
||||
],
|
||||
"checkall": "true",
|
||||
"repair": false,
|
||||
"outputs": 1,
|
||||
"x": 590,
|
||||
"y": 2200,
|
||||
"wires": [
|
||||
[
|
||||
"7a64319e26e8b689"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "553da6dd77e5e03b",
|
||||
"type": "matrix-receive",
|
||||
"z": "f025a8b9fbd1b054",
|
||||
"name": "",
|
||||
"server": null,
|
||||
"roomId": "",
|
||||
"acceptText": true,
|
||||
"acceptEmotes": true,
|
||||
"acceptStickers": true,
|
||||
"acceptReactions": true,
|
||||
"acceptFiles": true,
|
||||
"acceptImages": true,
|
||||
"x": 420,
|
||||
"y": 2200,
|
||||
"wires": [
|
||||
[
|
||||
"8b48e27a118999ec"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "7a64319e26e8b689",
|
||||
"type": "switch",
|
||||
"z": "f025a8b9fbd1b054",
|
||||
"name": "msg.payload filter",
|
||||
"property": "payload",
|
||||
"propertyType": "msg",
|
||||
"rules": [
|
||||
@@ -34,70 +264,12 @@
|
||||
"checkall": "true",
|
||||
"repair": false,
|
||||
"outputs": 1,
|
||||
"x": 670,
|
||||
"y": 120,
|
||||
"x": 770,
|
||||
"y": 2200,
|
||||
"wires": [
|
||||
[
|
||||
"b95a674179d93416"
|
||||
"86577e484ca26aca"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "b95a674179d93416",
|
||||
"type": "matrix-synapse-users",
|
||||
"z": "f025a8b9fbd1b054",
|
||||
"name": "",
|
||||
"x": 890,
|
||||
"y": 120,
|
||||
"wires": [
|
||||
[
|
||||
"19b654e5f28d93c4"
|
||||
],
|
||||
[]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "8720c66e867f89f6",
|
||||
"type": "matrix-send-message",
|
||||
"z": "f025a8b9fbd1b054",
|
||||
"name": "Send HTML Notice",
|
||||
"roomId": "",
|
||||
"messageType": "m.notice",
|
||||
"messageFormat": "html",
|
||||
"x": 1230,
|
||||
"y": 120,
|
||||
"wires": [
|
||||
[],
|
||||
[]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "19b654e5f28d93c4",
|
||||
"type": "function",
|
||||
"z": "f025a8b9fbd1b054",
|
||||
"name": "",
|
||||
"func": "let new_payload = '<ul>';\nmsg.payload.users.forEach(function(user, index){\n new_payload += '<li>'+ JSON.stringify(user, null, 2).replace(/(?:\\r\\n|\\r|\\n)/g, \"\\n<br>\"); + '</li>'\n});\nmsg.payload = new_payload + '</ul>';\n\nreturn msg;",
|
||||
"outputs": 1,
|
||||
"noerr": 0,
|
||||
"initialize": "",
|
||||
"finalize": "",
|
||||
"libs": [],
|
||||
"x": 1060,
|
||||
"y": 120,
|
||||
"wires": [
|
||||
[
|
||||
"8720c66e867f89f6"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "563da45ab1747c2b",
|
||||
"type": "comment",
|
||||
"z": "f025a8b9fbd1b054",
|
||||
"name": "Response to \"users\" with full server user list",
|
||||
"info": "",
|
||||
"x": 550,
|
||||
"y": 80,
|
||||
"wires": []
|
||||
}
|
||||
]
|
||||
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 44 KiB |
153
examples/respond-whois.json
Normal file
@@ -0,0 +1,153 @@
|
||||
[
|
||||
{
|
||||
"id": "93df869346a728d3",
|
||||
"type": "comment",
|
||||
"z": "f025a8b9fbd1b054",
|
||||
"name": "Response to \"whois <user_id>\" with user information",
|
||||
"info": "",
|
||||
"x": 610,
|
||||
"y": 2360,
|
||||
"wires": []
|
||||
},
|
||||
{
|
||||
"id": "656ed1e444fd2e0a",
|
||||
"type": "function",
|
||||
"z": "f025a8b9fbd1b054",
|
||||
"name": "",
|
||||
"func": "let matches = msg.payload.match(/^whois ?(\\@.*)?/);\nmsg.userId = matches[1].trim() ? matches[1].trim() : msg.userId;\nreturn msg;",
|
||||
"outputs": 1,
|
||||
"noerr": 0,
|
||||
"initialize": "",
|
||||
"finalize": "",
|
||||
"libs": [],
|
||||
"x": 440,
|
||||
"y": 2440,
|
||||
"wires": [
|
||||
[
|
||||
"4869676dab929d20"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "4869676dab929d20",
|
||||
"type": "matrix-whois-user",
|
||||
"z": "f025a8b9fbd1b054",
|
||||
"name": "",
|
||||
"server": null,
|
||||
"x": 590,
|
||||
"y": 2440,
|
||||
"wires": [
|
||||
[
|
||||
"a894fcabf9e41dca"
|
||||
],
|
||||
[]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "a894fcabf9e41dca",
|
||||
"type": "function",
|
||||
"z": "f025a8b9fbd1b054",
|
||||
"name": "",
|
||||
"func": "try { \n var sessions = msg.payload.devices[\"\"].sessions || null;\n} catch (error) { \n var sessions = null;\n}\nlet html = \"whois (session) information for <strong>\" + msg.userId + \"</strong>\\n<ol>\";\nfor(let session of sessions) {\n try { \n var connections = session.connections;\n } catch (error) { \n var connections = null;\n }\n for(let connection of connections) {\n html += \"\\n<li><pre><code>\"+JSON.stringify(connection, null, 2)+\"</code></pre></li>\"\n }\n}\nhtml += \"</ol>\"\nmsg.payload = html;\nreturn msg;",
|
||||
"outputs": 1,
|
||||
"noerr": 0,
|
||||
"initialize": "",
|
||||
"finalize": "",
|
||||
"libs": [],
|
||||
"x": 740,
|
||||
"y": 2440,
|
||||
"wires": [
|
||||
[
|
||||
"898b8974f2b24861"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "898b8974f2b24861",
|
||||
"type": "matrix-send-message",
|
||||
"z": "f025a8b9fbd1b054",
|
||||
"name": "Send HTML Notice",
|
||||
"server": null,
|
||||
"roomId": "",
|
||||
"messageType": "m.notice",
|
||||
"messageFormat": "html",
|
||||
"x": 910,
|
||||
"y": 2440,
|
||||
"wires": [
|
||||
[],
|
||||
[]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "c8855c79f194796e",
|
||||
"type": "switch",
|
||||
"z": "f025a8b9fbd1b054",
|
||||
"name": "is from me",
|
||||
"property": "userId",
|
||||
"propertyType": "msg",
|
||||
"rules": [
|
||||
{
|
||||
"t": "eq",
|
||||
"v": "@skylord123:skylar.tech",
|
||||
"vt": "str"
|
||||
}
|
||||
],
|
||||
"checkall": "true",
|
||||
"repair": false,
|
||||
"outputs": 1,
|
||||
"x": 590,
|
||||
"y": 2400,
|
||||
"wires": [
|
||||
[
|
||||
"ae1537a16b9e3b4c"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "39109d4f02d241ca",
|
||||
"type": "matrix-receive",
|
||||
"z": "f025a8b9fbd1b054",
|
||||
"name": "",
|
||||
"server": null,
|
||||
"roomId": "",
|
||||
"acceptText": true,
|
||||
"acceptEmotes": true,
|
||||
"acceptStickers": true,
|
||||
"acceptReactions": true,
|
||||
"acceptFiles": true,
|
||||
"acceptImages": true,
|
||||
"x": 420,
|
||||
"y": 2400,
|
||||
"wires": [
|
||||
[
|
||||
"c8855c79f194796e"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "ae1537a16b9e3b4c",
|
||||
"type": "switch",
|
||||
"z": "f025a8b9fbd1b054",
|
||||
"name": "msg.payload filter",
|
||||
"property": "payload",
|
||||
"propertyType": "msg",
|
||||
"rules": [
|
||||
{
|
||||
"t": "regex",
|
||||
"v": "^whois",
|
||||
"vt": "str",
|
||||
"case": false
|
||||
}
|
||||
],
|
||||
"checkall": "true",
|
||||
"repair": false,
|
||||
"outputs": 1,
|
||||
"x": 770,
|
||||
"y": 2400,
|
||||
"wires": [
|
||||
[
|
||||
"656ed1e444fd2e0a"
|
||||
]
|
||||
]
|
||||
}
|
||||
]
|
||||
BIN
examples/respond-whois.png
Normal file
|
After Width: | Height: | Size: 22 KiB |
211
examples/room-kick-ban.json
Normal file
@@ -0,0 +1,211 @@
|
||||
[
|
||||
{
|
||||
"id": "d78dd157085ed843",
|
||||
"type": "matrix-room-kick",
|
||||
"z": "f025a8b9fbd1b054",
|
||||
"name": "",
|
||||
"server": null,
|
||||
"roomId": "",
|
||||
"x": 570,
|
||||
"y": 2980,
|
||||
"wires": [
|
||||
[
|
||||
"44d49fcaad67790c"
|
||||
],
|
||||
[
|
||||
"44d49fcaad67790c"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "32a99c44b50fe3f3",
|
||||
"type": "matrix-room-ban",
|
||||
"z": "f025a8b9fbd1b054",
|
||||
"name": "",
|
||||
"server": null,
|
||||
"roomId": "",
|
||||
"x": 570,
|
||||
"y": 3080,
|
||||
"wires": [
|
||||
[
|
||||
"04628a976e7acf1b"
|
||||
],
|
||||
[
|
||||
"04628a976e7acf1b"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "5e786bbae3b1a368",
|
||||
"type": "switch",
|
||||
"z": "f025a8b9fbd1b054",
|
||||
"name": "is from me",
|
||||
"property": "userId",
|
||||
"propertyType": "msg",
|
||||
"rules": [
|
||||
{
|
||||
"t": "eq",
|
||||
"v": "@skylord123:skylar.tech",
|
||||
"vt": "str"
|
||||
}
|
||||
],
|
||||
"checkall": "true",
|
||||
"repair": false,
|
||||
"outputs": 1,
|
||||
"x": 230,
|
||||
"y": 3020,
|
||||
"wires": [
|
||||
[
|
||||
"5b3ddae8335383e7"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "ca278a6ffc3b9d6b",
|
||||
"type": "matrix-receive",
|
||||
"z": "f025a8b9fbd1b054",
|
||||
"name": "",
|
||||
"server": null,
|
||||
"roomId": "",
|
||||
"acceptText": true,
|
||||
"acceptEmotes": false,
|
||||
"acceptStickers": false,
|
||||
"acceptReactions": false,
|
||||
"acceptFiles": false,
|
||||
"acceptImages": false,
|
||||
"x": 220,
|
||||
"y": 2960,
|
||||
"wires": [
|
||||
[
|
||||
"5e786bbae3b1a368"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "b6830f430157be4e",
|
||||
"type": "comment",
|
||||
"z": "f025a8b9fbd1b054",
|
||||
"name": "Respond to \"room_ban <user_id>\" by banning user from room",
|
||||
"info": "",
|
||||
"x": 580,
|
||||
"y": 3040,
|
||||
"wires": []
|
||||
},
|
||||
{
|
||||
"id": "8bffe425d3c1bf24",
|
||||
"type": "comment",
|
||||
"z": "f025a8b9fbd1b054",
|
||||
"name": "Respond to \"room_kick <user_id>\" by kicking user from room",
|
||||
"info": "",
|
||||
"x": 580,
|
||||
"y": 2940,
|
||||
"wires": []
|
||||
},
|
||||
{
|
||||
"id": "5b3ddae8335383e7",
|
||||
"type": "switch",
|
||||
"z": "f025a8b9fbd1b054",
|
||||
"name": "Kick or Ban",
|
||||
"property": "payload",
|
||||
"propertyType": "msg",
|
||||
"rules": [
|
||||
{
|
||||
"t": "regex",
|
||||
"v": "^room_kick",
|
||||
"vt": "str",
|
||||
"case": false
|
||||
},
|
||||
{
|
||||
"t": "regex",
|
||||
"v": "^room_ban",
|
||||
"vt": "str",
|
||||
"case": false
|
||||
}
|
||||
],
|
||||
"checkall": "true",
|
||||
"repair": false,
|
||||
"outputs": 2,
|
||||
"x": 230,
|
||||
"y": 3060,
|
||||
"wires": [
|
||||
[
|
||||
"a19b38afd7b52834"
|
||||
],
|
||||
[
|
||||
"925c59f485bf7247"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "925c59f485bf7247",
|
||||
"type": "function",
|
||||
"z": "f025a8b9fbd1b054",
|
||||
"name": "",
|
||||
"func": "let matches = msg.payload.match(/^room_ban ?(\\@.*)?/);\nmsg.userId = matches[1].trim() ? matches[1].trim() : null;\nif(!msg.userId) {\n return null;\n}\nreturn msg;",
|
||||
"outputs": 1,
|
||||
"noerr": 0,
|
||||
"initialize": "",
|
||||
"finalize": "",
|
||||
"libs": [],
|
||||
"x": 420,
|
||||
"y": 3080,
|
||||
"wires": [
|
||||
[
|
||||
"32a99c44b50fe3f3"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "a19b38afd7b52834",
|
||||
"type": "function",
|
||||
"z": "f025a8b9fbd1b054",
|
||||
"name": "",
|
||||
"func": "let matches = msg.payload.match(/^room_kick ?(\\@.*)?/);\nmsg.userId = matches[1].trim() ? matches[1].trim() : null;\nif(!msg.userId) {\n return null;\n}\nreturn msg;",
|
||||
"outputs": 1,
|
||||
"noerr": 0,
|
||||
"initialize": "",
|
||||
"finalize": "",
|
||||
"libs": [],
|
||||
"x": 420,
|
||||
"y": 2980,
|
||||
"wires": [
|
||||
[
|
||||
"d78dd157085ed843"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "04628a976e7acf1b",
|
||||
"type": "debug",
|
||||
"z": "f025a8b9fbd1b054",
|
||||
"name": "",
|
||||
"active": true,
|
||||
"tosidebar": true,
|
||||
"console": false,
|
||||
"tostatus": false,
|
||||
"complete": "true",
|
||||
"targetType": "full",
|
||||
"statusVal": "",
|
||||
"statusType": "auto",
|
||||
"x": 710,
|
||||
"y": 3080,
|
||||
"wires": []
|
||||
},
|
||||
{
|
||||
"id": "44d49fcaad67790c",
|
||||
"type": "debug",
|
||||
"z": "f025a8b9fbd1b054",
|
||||
"name": "",
|
||||
"active": true,
|
||||
"tosidebar": true,
|
||||
"console": false,
|
||||
"tostatus": false,
|
||||
"complete": "true",
|
||||
"targetType": "full",
|
||||
"statusVal": "",
|
||||
"statusType": "auto",
|
||||
"x": 710,
|
||||
"y": 2980,
|
||||
"wires": []
|
||||
}
|
||||
]
|
||||
BIN
examples/room-kick-ban.png
Normal file
|
After Width: | Height: | Size: 36 KiB |
166
examples/store-received-files.json
Normal file
@@ -0,0 +1,166 @@
|
||||
[
|
||||
{
|
||||
"id": "d7573ed76980dc64",
|
||||
"type": "matrix-receive",
|
||||
"z": "f025a8b9fbd1b054",
|
||||
"name": "",
|
||||
"server": null,
|
||||
"roomId": "",
|
||||
"acceptText": false,
|
||||
"acceptEmotes": false,
|
||||
"acceptStickers": false,
|
||||
"acceptReactions": false,
|
||||
"acceptFiles": true,
|
||||
"acceptImages": true,
|
||||
"x": 1740,
|
||||
"y": 600,
|
||||
"wires": [
|
||||
[
|
||||
"4d4e992ed895413f"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "addcccdfea9935b1",
|
||||
"type": "comment",
|
||||
"z": "f025a8b9fbd1b054",
|
||||
"name": "Download & store received files/images and decrypt if necessary",
|
||||
"info": "",
|
||||
"x": 1970,
|
||||
"y": 560,
|
||||
"wires": []
|
||||
},
|
||||
{
|
||||
"id": "4d4e992ed895413f",
|
||||
"type": "switch",
|
||||
"z": "f025a8b9fbd1b054",
|
||||
"name": "",
|
||||
"property": "encrypted",
|
||||
"propertyType": "msg",
|
||||
"rules": [
|
||||
{
|
||||
"t": "true"
|
||||
},
|
||||
{
|
||||
"t": "else"
|
||||
}
|
||||
],
|
||||
"checkall": "true",
|
||||
"repair": false,
|
||||
"outputs": 2,
|
||||
"x": 1890,
|
||||
"y": 600,
|
||||
"wires": [
|
||||
[
|
||||
"660782188696fc62"
|
||||
],
|
||||
[
|
||||
"857bce169036e3cb"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "660782188696fc62",
|
||||
"type": "matrix-decrypt-file",
|
||||
"z": "f025a8b9fbd1b054",
|
||||
"name": null,
|
||||
"x": 2050,
|
||||
"y": 600,
|
||||
"wires": [
|
||||
[
|
||||
"cdbaf88f6631322e"
|
||||
],
|
||||
[]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "b62db6caf4697242",
|
||||
"type": "file",
|
||||
"z": "f025a8b9fbd1b054",
|
||||
"name": "",
|
||||
"filename": "",
|
||||
"appendNewline": true,
|
||||
"createDir": false,
|
||||
"overwriteFile": "true",
|
||||
"encoding": "none",
|
||||
"x": 2240,
|
||||
"y": 660,
|
||||
"wires": [
|
||||
[]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "857bce169036e3cb",
|
||||
"type": "http request",
|
||||
"z": "f025a8b9fbd1b054",
|
||||
"name": "",
|
||||
"method": "GET",
|
||||
"ret": "bin",
|
||||
"paytoqs": "ignore",
|
||||
"url": "",
|
||||
"tls": "",
|
||||
"persist": false,
|
||||
"proxy": "",
|
||||
"authType": "",
|
||||
"senderr": false,
|
||||
"x": 1910,
|
||||
"y": 660,
|
||||
"wires": [
|
||||
[
|
||||
"7d0be52f8c3f1b54"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "7d0be52f8c3f1b54",
|
||||
"type": "switch",
|
||||
"z": "f025a8b9fbd1b054",
|
||||
"name": "",
|
||||
"property": "statusCode",
|
||||
"propertyType": "msg",
|
||||
"rules": [
|
||||
{
|
||||
"t": "eq",
|
||||
"v": "200",
|
||||
"vt": "num"
|
||||
}
|
||||
],
|
||||
"checkall": "true",
|
||||
"repair": false,
|
||||
"outputs": 1,
|
||||
"x": 2050,
|
||||
"y": 660,
|
||||
"wires": [
|
||||
[
|
||||
"cdbaf88f6631322e"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "cdbaf88f6631322e",
|
||||
"type": "change",
|
||||
"z": "f025a8b9fbd1b054",
|
||||
"name": "set directory",
|
||||
"rules": [
|
||||
{
|
||||
"t": "set",
|
||||
"p": "filename",
|
||||
"pt": "msg",
|
||||
"to": "\"downloads/\" & msg.filename",
|
||||
"tot": "jsonata"
|
||||
}
|
||||
],
|
||||
"action": "",
|
||||
"property": "",
|
||||
"from": "",
|
||||
"to": "",
|
||||
"reg": false,
|
||||
"x": 2230,
|
||||
"y": 600,
|
||||
"wires": [
|
||||
[
|
||||
"b62db6caf4697242"
|
||||
]
|
||||
]
|
||||
}
|
||||
]
|
||||
BIN
examples/store-received-files.png
Normal file
|
After Width: | Height: | Size: 24 KiB |
16684
package-lock.json
generated
35
package.json
@@ -1,28 +1,34 @@
|
||||
{
|
||||
"name": "node-red-contrib-matrix-chat",
|
||||
"version": "0.2.4",
|
||||
"name": "node-red-contrib-matrix-chat-jw",
|
||||
"version": "0.7.3",
|
||||
"description": "Matrix chat server client for Node-RED",
|
||||
"dependencies": {
|
||||
"fs-extra": "^9.1.0",
|
||||
"got": "^11.8.2",
|
||||
"abort-controller": "^3.0.0",
|
||||
"fs-extra": "^11.1.0",
|
||||
"got": "^12.0.2",
|
||||
"isomorphic-webcrypto": "^2.3.8",
|
||||
"matrix-js-sdk": "^12.2.0",
|
||||
"matrix-js-sdk": "^22.0.0",
|
||||
"node-fetch": "^3.3.0",
|
||||
"node-localstorage": "^2.2.1",
|
||||
"olm": "https://packages.matrix.org/npm/olm/olm-3.2.1.tgz",
|
||||
"process": "^0.11.10",
|
||||
"olm": "https://gitlab.matrix.org/matrix-org/olm/-/package_files/472/download",
|
||||
"request": "^2.88.2",
|
||||
"utf8": "^3.0.0"
|
||||
},
|
||||
"node-red": {
|
||||
"version": ">=1.3.0",
|
||||
"nodes": {
|
||||
"matrix-server-config": "src/matrix-server-config.js",
|
||||
"matrix-receive": "src/matrix-receive.js",
|
||||
"matrix-send-message": "src/matrix-send-message.js",
|
||||
"matrix-delete-event": "src/matrix-delete-event.js",
|
||||
"matrix-send-file": "src/matrix-send-file.js",
|
||||
"matrix-send-image": "src/matrix-send-image.js",
|
||||
"matrix-react": "src/matrix-react.js",
|
||||
"matrix-create-room": "src/matrix-create-room.js",
|
||||
"matrix-invite-room": "src/matrix-invite-room.js",
|
||||
"matrix-room-invite": "src/matrix-room-invite.js",
|
||||
"matrix-join-room": "src/matrix-join-room.js",
|
||||
"matrix-leave-room": "src/matrix-leave-room.js",
|
||||
"matrix-crypt-file": "src/matrix-crypt-file.js",
|
||||
"matrix-room-kick": "src/matrix-room-kick.js",
|
||||
"matrix-room-ban": "src/matrix-room-ban.js",
|
||||
@@ -35,20 +41,23 @@
|
||||
"matrix-room-users": "src/matrix-room-users.js"
|
||||
}
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
},
|
||||
"keywords": [
|
||||
"node-red",
|
||||
"matrix",
|
||||
"support",
|
||||
"bot",
|
||||
"chat"
|
||||
"chat",
|
||||
"chatbot",
|
||||
"federated"
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/skylar-tech/node-red-contrib-matrix-chat"
|
||||
"url": "https://git-x.kongjian.xin/Matrix/node-red-contrib-matrix-chat"
|
||||
},
|
||||
"author": {
|
||||
"name": "Skylar Sadlier",
|
||||
"url": "https://skylar.tech"
|
||||
"name": "jingweiht",
|
||||
"url": "https://jingweiht.com"
|
||||
},
|
||||
"license": "SEE LICENSE FILE"
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
|
||||
<script type="text/html" data-template-name="matrix-create-room">
|
||||
<div class="form-row">
|
||||
<label for="node-input-name"><i class="icon-tag"></i> Name</label>
|
||||
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
|
||||
<input type="text" id="node-input-name" placeholder="Name">
|
||||
</div>
|
||||
|
||||
|
||||
@@ -18,14 +18,14 @@
|
||||
|
||||
<script type="text/html" data-template-name="matrix-decrypt-file">
|
||||
<div class="form-row">
|
||||
<label for="node-input-name"><i class="icon-tag"></i> Name</label>
|
||||
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
|
||||
<input type="text" id="node-input-name" placeholder="Name">
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script type="text/html" data-help-name="matrix-decrypt-file">
|
||||
<h3>Details</h3>
|
||||
<p>Files sent in an encrypted room are themselves encrypted. Use this node to encrypt/decrypt files. Note: This node will download the encrypted file so be cautious of large downloads.</p>
|
||||
<p>Files sent in an encrypted room are themselves encrypted. Use this node to decrypt files. Note: This node will download the encrypted file so be cautious of large downloads.</p>
|
||||
|
||||
<h3>Inputs</h3>
|
||||
<dl class="message-properties">
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
module.exports = function(RED) {
|
||||
const got = require('got');
|
||||
const crypto = require('isomorphic-webcrypto');
|
||||
|
||||
function MatrixDecryptFile(n) {
|
||||
@@ -10,6 +9,8 @@ module.exports = function(RED) {
|
||||
this.name = n.name;
|
||||
|
||||
node.on("input", async function (msg) {
|
||||
const { got } = await import('got');
|
||||
|
||||
if(!msg.type) {
|
||||
node.error('msg.type is required.');
|
||||
return;
|
||||
|
||||
96
src/matrix-delete-event.html
Normal file
@@ -0,0 +1,96 @@
|
||||
<script type="text/javascript">
|
||||
RED.nodes.registerType('matrix-delete-event',{
|
||||
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 },
|
||||
reason: { value: "" },
|
||||
},
|
||||
label: function() {
|
||||
return this.name||"Delete Event";
|
||||
},
|
||||
paletteLabel: 'Delete Event'
|
||||
});
|
||||
</script>
|
||||
|
||||
<script type="text/html" data-template-name="matrix-delete-event">
|
||||
<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-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-comments"></i> Room ID</label>
|
||||
<input type="text" id="node-input-roomId" placeholder="msg.topic">
|
||||
<pre class="form-tips" id="node-input-roomId-error" style="color: #721c24;background-color: #f8d7da;border-color: #f5c6cb;margin-bottom: 12px;margin-top: 12px;display:none;"></pre>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-reason"><i class="fa fa-sticky-note"></i> Reason</label>
|
||||
<input type="text" id="node-input-reason" placeholder="msg.reason">
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
$(function(){
|
||||
$("#node-input-roomId").on("keyup", function() {
|
||||
if($(this).val() && !$(this).val().startsWith("!")) {
|
||||
$("#node-input-roomId-error").html(`Room IDs start with exclamation point "!"<br />Example: !OGEhHVWSdvArJzumhm:matrix.org`).show();
|
||||
} else {
|
||||
$("#node-input-roomId-error").hide();
|
||||
}
|
||||
}).trigger('keyup');
|
||||
});
|
||||
</script>
|
||||
</script>
|
||||
|
||||
<script type="text/html" data-help-name="matrix-delete-event">
|
||||
<h3>Details</h3>
|
||||
<p>Delete an event in a room</p>
|
||||
|
||||
<dl class="message-properties">
|
||||
<dt>msg.topic
|
||||
<span class="property-type">string</span>
|
||||
</dt>
|
||||
<dd> Room ID from where the event should be deleted from. Optional if configured on the node. If configured on the node this input will be overridden.</dd>
|
||||
<dt>msg.eventId
|
||||
<span class="property-type">string</span>
|
||||
</dt>
|
||||
<dd>Event ID of the Event which should be deleted.</dd>
|
||||
<dt>msg.reason
|
||||
<span class="property-type">string</span>
|
||||
</dt>
|
||||
<dd>Reason why the event is deleted. Default an empty string</dd>
|
||||
</dl>
|
||||
|
||||
<h3>Outputs</h3>
|
||||
<ol class="node-ports">
|
||||
<li>Success
|
||||
<dl class="message-properties">
|
||||
<dt>msg.eventId <span class="property-type">string</span></dt>
|
||||
<dd>the eventId from the deleted event.</dd>
|
||||
</dl>
|
||||
<dl class="message-properties">
|
||||
<dt>msg.deleted <span class="property-type">boolean</span></dt>
|
||||
<dd>True, if the event is deleted</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 class="message-properties">
|
||||
<dt>msg.deleted <span class="property-type">boolean</span></dt>
|
||||
<dd>False, if the event is not deleted</dd>
|
||||
</dl>
|
||||
</dl>
|
||||
</li>
|
||||
</ol>
|
||||
</script>
|
||||
75
src/matrix-delete-event.js
Normal file
@@ -0,0 +1,75 @@
|
||||
module.exports = function(RED) {
|
||||
function MatrixDeleteEvent(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.reason = n.reason
|
||||
|
||||
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(!msg.eventId) {
|
||||
node.error("eventId is missing");
|
||||
node.send([null, msg])
|
||||
return;
|
||||
}
|
||||
|
||||
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]);
|
||||
return;
|
||||
}
|
||||
|
||||
msg.topic = node.roomId || msg.topic;
|
||||
if(!msg.topic) {
|
||||
node.warn("Room must be specified in msg.topic or in configuration");
|
||||
return;
|
||||
}
|
||||
|
||||
msg.reason = node.reason || msg.reason;
|
||||
|
||||
if(!msg.reason) {
|
||||
msg.reason = '';
|
||||
}
|
||||
|
||||
node.server.matrixClient.redactEvent(msg.topic, msg.eventId, undefined,{
|
||||
reason: msg.reason
|
||||
})
|
||||
|
||||
.then(function(e) {
|
||||
msg.deleted = true
|
||||
node.send([msg, null]);
|
||||
})
|
||||
.catch(function(e){
|
||||
node.warn("Error deleting event " + e);
|
||||
msg.error = e;
|
||||
msg.deleted = false
|
||||
node.send([null, msg]);
|
||||
});
|
||||
});
|
||||
}
|
||||
RED.nodes.registerType("matrix-delete-event",MatrixDeleteEvent);
|
||||
}
|
||||
@@ -12,15 +12,15 @@
|
||||
roomId: { value: null },
|
||||
},
|
||||
label: function() {
|
||||
return this.name || "Room Invite";
|
||||
return this.name || "Invite to Room";
|
||||
},
|
||||
paletteLabel: 'Room Invite'
|
||||
paletteLabel: 'Invite to Room'
|
||||
});
|
||||
</script>
|
||||
|
||||
<script type="text/html" data-template-name="matrix-invite-room">
|
||||
<div class="form-row">
|
||||
<label for="node-input-name"><i class="icon-tag"></i> Name</label>
|
||||
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
|
||||
<input type="text" id="node-input-name" placeholder="Name">
|
||||
</div>
|
||||
|
||||
@@ -30,9 +30,21 @@
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<label for="node-input-roomId"><i class="fa fa-user"></i> Room ID</label>
|
||||
<label for="node-input-roomId"><i class="fa fa-comments"></i> Room ID</label>
|
||||
<input type="text" id="node-input-roomId" placeholder="msg.topic">
|
||||
<pre class="form-tips" id="node-input-roomId-error" style="color: #721c24;background-color: #f8d7da;border-color: #f5c6cb;margin-bottom: 12px;margin-top: 12px;display:none;"></pre>
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
$(function(){
|
||||
$("#node-input-roomId").on("keyup", function() {
|
||||
if($(this).val() && !$(this).val().startsWith("!")) {
|
||||
$("#node-input-roomId-error").html(`Room IDs start with exclamation point "!"<br />Example: !OGEhHVWSdvArJzumhm:matrix.org`).show();
|
||||
} else {
|
||||
$("#node-input-roomId-error").hide();
|
||||
}
|
||||
}).trigger('keyup');
|
||||
});
|
||||
</script>
|
||||
</script>
|
||||
|
||||
<script type="text/html" data-help-name="matrix-invite-room">
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
|
||||
<script type="text/html" data-template-name="matrix-join-room">
|
||||
<div class="form-row">
|
||||
<label for="node-input-name"><i class="icon-tag"></i> Name</label>
|
||||
<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">
|
||||
|
||||
@@ -38,11 +38,6 @@ module.exports = function(RED) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(!msg.userId) {
|
||||
node.error("msg.userId was not set.");
|
||||
return;
|
||||
}
|
||||
|
||||
node.server.matrixClient.joinRoom(msg.topic, msg.joinOpts || {})
|
||||
.then(function(e) {
|
||||
msg.payload = e;
|
||||
|
||||
63
src/matrix-leave-room.html
Normal file
@@ -0,0 +1,63 @@
|
||||
<script type="text/javascript">
|
||||
RED.nodes.registerType('matrix-leave-room', {
|
||||
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 || "Leave Room";
|
||||
},
|
||||
paletteLabel: 'Leave Room'
|
||||
});
|
||||
</script>
|
||||
|
||||
<script type="text/html" data-template-name="matrix-leave-room">
|
||||
<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-user"></i> Matrix Server Config</label>
|
||||
<input type="text" id="node-input-server">
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script type="text/html" data-help-name="matrix-leave-room">
|
||||
<h3>Details</h3>
|
||||
<p>
|
||||
This node leaves a room
|
||||
</p>
|
||||
|
||||
<h3>Inputs</h3>
|
||||
<dl class="message-properties">
|
||||
<dt>msg.topic
|
||||
<span class="property-type">string</span>
|
||||
</dt>
|
||||
<dd> The room identifier to leave: for example, <code>!h8zld9j31:example.com</code>.</dd>
|
||||
</dl>
|
||||
|
||||
<h3>Outputs</h3>
|
||||
<ol class="node-ports">
|
||||
<li>Success
|
||||
<dl class="message-properties">
|
||||
<dt>msg.payload <span class="property-type">object</span></dt>
|
||||
<dd>Returns the same message that was received</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>
|
||||
54
src/matrix-leave-room.js
Normal file
@@ -0,0 +1,54 @@
|
||||
module.exports = function(RED) {
|
||||
function MatrixLeaveRoom(n) {
|
||||
RED.nodes.createNode(this, n);
|
||||
|
||||
let node = this;
|
||||
|
||||
this.name = n.name;
|
||||
this.server = RED.nodes.getNode(n.server);
|
||||
this.roomId = n.roomId;
|
||||
|
||||
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.on('input', function(msg) {
|
||||
if (! node.server || ! node.server.matrixClient) {
|
||||
node.error("No matrix server selected");
|
||||
return;
|
||||
}
|
||||
|
||||
if(!msg.topic) {
|
||||
node.error('No room provided in msg.topic');
|
||||
return;
|
||||
}
|
||||
|
||||
if(!node.server.isConnected()) {
|
||||
node.error("Matrix server connection is currently closed");
|
||||
node.send([null, msg]);
|
||||
}
|
||||
|
||||
try {
|
||||
node.log("Leaving room " + msg.topic);
|
||||
node.server.matrixClient.leave(msg.topic);
|
||||
node.send([msg, null]);
|
||||
} catch(e) {
|
||||
node.error("Failed to leave room " + msg.topic + ": " + e);
|
||||
msg.payload = e;
|
||||
node.send([null, msg]);
|
||||
}
|
||||
});
|
||||
}
|
||||
RED.nodes.registerType("matrix-leave-room", MatrixLeaveRoom);
|
||||
}
|
||||
@@ -9,7 +9,8 @@
|
||||
defaults: {
|
||||
name: { value: null },
|
||||
server: { value: "", type: "matrix-server-config" },
|
||||
roomId: { value: null }
|
||||
roomId: { value: null },
|
||||
reaction: { value: null }
|
||||
},
|
||||
label: function() {
|
||||
return this.name || "React";
|
||||
@@ -20,7 +21,7 @@
|
||||
|
||||
<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>
|
||||
<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">
|
||||
@@ -28,9 +29,25 @@
|
||||
<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>
|
||||
<label for="node-input-roomId"><i class="fa fa-comments"></i> Room ID</label>
|
||||
<input type="text" id="node-input-roomId" placeholder="msg.topic">
|
||||
<pre class="form-tips" id="node-input-roomId-error" style="color: #721c24;background-color: #f8d7da;border-color: #f5c6cb;margin-bottom: 12px;margin-top: 12px;display:none;"></pre>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-reaction"><i class="fa fa-thumbs-up"></i> Reaction</label>
|
||||
<input type="text" id="node-input-reaction" placeholder="msg.payload">
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
$(function(){
|
||||
$("#node-input-roomId").on("keyup", function() {
|
||||
if($(this).val() && !$(this).val().startsWith("!")) {
|
||||
$("#node-input-roomId-error").html(`Room IDs start with exclamation point "!"<br />Example: !OGEhHVWSdvArJzumhm:matrix.org`).show();
|
||||
} else {
|
||||
$("#node-input-roomId-error").hide();
|
||||
}
|
||||
}).trigger('keyup');
|
||||
});
|
||||
</script>
|
||||
</script>
|
||||
|
||||
<script type="text/html" data-help-name="matrix-react">
|
||||
@@ -42,7 +59,7 @@
|
||||
<dt>msg.payload
|
||||
<span class="property-type">string</span>
|
||||
</dt>
|
||||
<dd> Usually an emoji but can also be text. </dd>
|
||||
<dd> Usually an emoji but can also be text. If configured on the node this is ignored otherwise it required. </dd>
|
||||
|
||||
<dt>msg.topic
|
||||
<span class="property-type">string | null</span>
|
||||
|
||||
@@ -7,6 +7,7 @@ module.exports = function(RED) {
|
||||
this.name = n.name;
|
||||
this.server = RED.nodes.getNode(n.server);
|
||||
this.roomId = n.roomId;
|
||||
this.reaction = n.reaction;
|
||||
|
||||
if (!node.server) {
|
||||
node.warn("No configuration node");
|
||||
@@ -40,8 +41,9 @@ module.exports = function(RED) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(!msg.payload) {
|
||||
node.error('msg.payload is required');
|
||||
let payload = n.reaction || msg.payload;
|
||||
if(!payload) {
|
||||
node.error('msg.payload must be defined or the reaction configured on the node.');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -53,16 +55,14 @@ module.exports = function(RED) {
|
||||
|
||||
msg.type = 'm.reaction';
|
||||
|
||||
node.server.matrixClient.sendCompleteEvent(
|
||||
node.server.matrixClient.sendEvent(
|
||||
msg.topic,
|
||||
'm.reaction',
|
||||
{
|
||||
type: 'm.reaction',
|
||||
content: {
|
||||
"m.relates_to": {
|
||||
event_id: eventId,
|
||||
key: msg.payload,
|
||||
rel_type: "m.annotation"
|
||||
}
|
||||
"m.relates_to": {
|
||||
event_id: eventId,
|
||||
key: payload,
|
||||
rel_type: "m.annotation"
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
@@ -15,7 +15,10 @@
|
||||
acceptStickers: {"value": true},
|
||||
acceptReactions: {"value": true},
|
||||
acceptFiles: {"value": true},
|
||||
acceptAudio: {"value": true},
|
||||
acceptImages: {"value": true},
|
||||
acceptVideos: {"value": true},
|
||||
acceptLocations: {"value": true},
|
||||
},
|
||||
label: function() {
|
||||
return this.name || "Matrix Receive";
|
||||
@@ -34,8 +37,9 @@
|
||||
<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>
|
||||
<label for="node-input-roomId"><i class="fa fa-comments"></i> Room ID</label>
|
||||
<input type="text" id="node-input-roomId">
|
||||
<pre class="form-tips" id="node-input-roomId-error" style="color: #721c24;background-color: #f8d7da;border-color: #f5c6cb;margin-bottom: 12px;margin-top: 12px;display:none;"></pre>
|
||||
</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;">
|
||||
@@ -48,7 +52,7 @@
|
||||
style="width: auto; margin-left: 125px; vertical-align: top"
|
||||
/>
|
||||
<label for="node-input-acceptText" style="width: auto">
|
||||
Accept text <code>m.text</code>
|
||||
Accept text <code style="text-transform: none;">m.text</code>
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
@@ -58,7 +62,7 @@
|
||||
style="width: auto; margin-left: 125px; vertical-align: top"
|
||||
/>
|
||||
<label for="node-input-acceptEmotes" style="width: auto">
|
||||
Accept emotes <code>m.emote</code>
|
||||
Accept emotes <code style="text-transform: none;">m.emote</code>
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
@@ -68,7 +72,7 @@
|
||||
style="width: auto; margin-left: 125px; vertical-align: top"
|
||||
/>
|
||||
<label for="node-input-acceptStickers" style="width: auto">
|
||||
Accept stickers <code>m.sticker</code>
|
||||
Accept stickers <code style="text-transform: none;">m.sticker</code>
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
@@ -78,7 +82,7 @@
|
||||
style="width: auto; margin-left: 125px; vertical-align: top"
|
||||
/>
|
||||
<label for="node-input-acceptReactions" style="width: auto">
|
||||
Accept reactions <code>m.reaction</code>
|
||||
Accept reactions <code style="text-transform: none;">m.reaction</code>
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
@@ -88,7 +92,17 @@
|
||||
style="width: auto; margin-left: 125px; vertical-align: top"
|
||||
/>
|
||||
<label for="node-input-acceptFiles" style="width: auto">
|
||||
Accept files <code>m.file</code>
|
||||
Accept files <code style="text-transform: none;">m.file</code>
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="node-input-acceptAudio"
|
||||
style="width: auto; margin-left: 125px; vertical-align: top"
|
||||
/>
|
||||
<label for="node-input-acceptAudio" style="width: auto">
|
||||
Accept files <code style="text-transform: none;">m.audio</code>
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
@@ -98,9 +112,40 @@
|
||||
style="width: auto; margin-left: 125px; vertical-align: top"
|
||||
/>
|
||||
<label for="node-input-acceptImages" style="width: auto">
|
||||
Accept images <code>m.image</code>
|
||||
Accept images <code style="text-transform: none;">m.image</code>
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="node-input-acceptVideos"
|
||||
style="width: auto; margin-left: 125px; vertical-align: top"
|
||||
/>
|
||||
<label for="node-input-acceptVideos" style="width: auto">
|
||||
Accept videos <code style="text-transform: none;">m.video</code>
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="node-input-acceptLocations"
|
||||
style="width: auto; margin-left: 125px; vertical-align: top"
|
||||
/>
|
||||
<label for="node-input-acceptLocations" style="width: auto">
|
||||
Accept locations <code style="text-transform: none;">m.location</code>
|
||||
</label>
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
$(function(){
|
||||
$("#node-input-roomId").on("keyup", function() {
|
||||
if($(this).val() && !$(this).val().startsWith("!")) {
|
||||
$("#node-input-roomId-error").html(`Room IDs start with exclamation point "!"<br />Example: !OGEhHVWSdvArJzumhm:matrix.org`).show();
|
||||
} else {
|
||||
$("#node-input-roomId-error").hide();
|
||||
}
|
||||
}).trigger('keyup');
|
||||
});
|
||||
</script>
|
||||
</script>
|
||||
|
||||
<script type="text/html" data-help-name="matrix-receive">
|
||||
@@ -116,6 +161,11 @@
|
||||
</dd>
|
||||
</dl>
|
||||
|
||||
<dl class="message-properties">
|
||||
<dt>msg.isDM <span class="property-type">bool</span></dt>
|
||||
<dd> returns true if message is from a direct message room.</dd>
|
||||
</dl>
|
||||
|
||||
<dl class="message-properties">
|
||||
<dt>msg.encrypted <span class="property-type">bool</span></dt>
|
||||
<dd> returns true if message was encrypted (e2ee).</dd>
|
||||
@@ -204,6 +254,11 @@
|
||||
</li>
|
||||
|
||||
<li><code>msg.type</code> == '<strong>m.file</strong>'
|
||||
<dl class="message-properties">
|
||||
<dt>msg.filename <span class="property-type">string</span></dt>
|
||||
<dd>the file's parsed filename</dd>
|
||||
</dl>
|
||||
|
||||
<dl class="message-properties">
|
||||
<dt>msg.url <span class="property-type">string</span></dt>
|
||||
<dd>the file's URL</dd>
|
||||
@@ -215,7 +270,44 @@
|
||||
</dl>
|
||||
</li>
|
||||
|
||||
<li><code>msg.type</code> == '<strong>m.audio</strong>'
|
||||
<dl class="message-properties">
|
||||
<dt>msg.filename <span class="property-type">string</span></dt>
|
||||
<dd>the image's parsed filename</dd>
|
||||
</dl>
|
||||
|
||||
<dl class="message-properties">
|
||||
<dt>msg.mimetype <span class="property-type">string</span></dt>
|
||||
<dd>audio file mimetype (ex: audio/ogg)</dd>
|
||||
</dl>
|
||||
|
||||
<dl class="message-properties">
|
||||
<dt>msg.url <span class="property-type">string</span></dt>
|
||||
<dd>the file's URL</dd>
|
||||
</dl>
|
||||
|
||||
<dl class="message-properties">
|
||||
<dt>msg.mxc_url <span class="property-type">string</span></dt>
|
||||
<dd>the file's Matrix URL</dd>
|
||||
</dl>
|
||||
|
||||
<dl class="message-properties">
|
||||
<dt>msg.duration <span class="property-type">integer</span></dt>
|
||||
<dd>duration of audio file in milliseconds</dd>
|
||||
</dl>
|
||||
|
||||
<dl class="message-properties">
|
||||
<dt>msg.waveform <span class="property-type">array[int]</span></dt>
|
||||
<dd>waveform of the audio clip</dd>
|
||||
</dl>
|
||||
</li>
|
||||
|
||||
<li><code>msg.type</code> == '<strong>m.image</strong>'
|
||||
<dl class="message-properties">
|
||||
<dt>msg.filename <span class="property-type">string</span></dt>
|
||||
<dd>the image's parsed filename</dd>
|
||||
</dl>
|
||||
|
||||
<dl class="message-properties">
|
||||
<dt>msg.url <span class="property-type">string</span></dt>
|
||||
<dd>the image's URL</dd>
|
||||
@@ -236,5 +328,12 @@
|
||||
<dd>the image's thumbnail Matrix URL</dd>
|
||||
</dl>
|
||||
</li>
|
||||
|
||||
<li><code>msg.type</code> == '<strong>m.location</strong>'
|
||||
<dl class="message-properties">
|
||||
<dt>msg.geo_uri <span class="property-type">string</span></dt>
|
||||
<dd>URI format of the geolocation</dd>
|
||||
</dl>
|
||||
</li>
|
||||
</ul>
|
||||
</script>
|
||||
@@ -11,7 +11,10 @@ module.exports = function(RED) {
|
||||
this.acceptStickers = n.acceptStickers;
|
||||
this.acceptReactions = n.acceptReactions;
|
||||
this.acceptFiles = n.acceptFiles;
|
||||
this.acceptAudio = n.acceptAudio;
|
||||
this.acceptImages = n.acceptImages;
|
||||
this.acceptVideos = n.acceptVideos;
|
||||
this.acceptLocations = n.acceptLocations;
|
||||
this.roomId = n.roomId;
|
||||
this.roomIds = this.roomId ? this.roomId.split(',') : [];
|
||||
|
||||
@@ -22,7 +25,7 @@ module.exports = function(RED) {
|
||||
return;
|
||||
}
|
||||
|
||||
node.server.on("disconnected", function() {
|
||||
node.server.on("disconnected", function(){
|
||||
node.status({ fill: "red", shape: "ring", text: "disconnected" });
|
||||
});
|
||||
|
||||
@@ -30,43 +33,12 @@ module.exports = function(RED) {
|
||||
node.status({ fill: "green", shape: "ring", text: "connected" });
|
||||
});
|
||||
|
||||
node.server.on("Room.timeline", async function(event, room, toStartOfTimeline, data) {
|
||||
if (toStartOfTimeline) {
|
||||
return; // ignore paginated results
|
||||
}
|
||||
if (!event.getSender() || event.getSender() === node.server.userId) {
|
||||
return; // ignore our own messages
|
||||
}
|
||||
if (!event.getUnsigned() || event.getUnsigned().age > 1000) {
|
||||
return; // ignore old messages
|
||||
}
|
||||
|
||||
node.server.on("Room.timeline", async function(event, room, toStartOfTimeline, removed, data, msg) {
|
||||
// if node has a room ID set we only listen on that room
|
||||
if(node.roomIds.length && node.roomIds.indexOf(room.roomId) === -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await node.server.matrixClient.decryptEventIfNeeded(event);
|
||||
} catch (error) {
|
||||
node.error(error);
|
||||
return;
|
||||
}
|
||||
|
||||
let msg = {
|
||||
encrypted : event.isEncrypted(),
|
||||
redacted : event.isRedacted(),
|
||||
content : event.getContent(),
|
||||
type : (event.getContent()['msgtype'] || event.getType()) || null,
|
||||
payload : (event.getContent()['body'] || event.getContent()) || null,
|
||||
userId : event.getSender(),
|
||||
topic : event.getRoomId(),
|
||||
eventId : event.getId(),
|
||||
event : event,
|
||||
};
|
||||
|
||||
node.log("Received" + (msg.encrypted ? ' encrypted' : '') +" timeline event [" + msg.type + "]: (" + room.name + ") " + event.getSender() + " :: " + msg.content.body);
|
||||
|
||||
switch(msg.type) {
|
||||
case 'm.emote':
|
||||
if(!node.acceptEmotes) return;
|
||||
@@ -93,6 +65,7 @@ module.exports = function(RED) {
|
||||
|
||||
case 'm.file':
|
||||
if(!node.acceptFiles) return;
|
||||
msg.filename = msg.content.filename || msg.content.body;
|
||||
if(msg.encrypted) {
|
||||
msg.url = node.server.matrixClient.mxcUrlToHttp(msg.content.file.url);
|
||||
msg.mxc_url = msg.content.file.url;
|
||||
@@ -102,9 +75,30 @@ module.exports = function(RED) {
|
||||
}
|
||||
break;
|
||||
|
||||
case 'm.audio':
|
||||
if(!node.acceptAudio) return;
|
||||
if(msg.encrypted) {
|
||||
msg.url = node.server.matrixClient.mxcUrlToHttp(msg.content.file.url);
|
||||
msg.mxc_url = msg.content.file.url;
|
||||
} else {
|
||||
msg.url = node.server.matrixClient.mxcUrlToHttp(msg.content.url);
|
||||
msg.mxc_url = msg.content.url;
|
||||
}
|
||||
|
||||
if('org.matrix.msc1767.file' in msg.content) {
|
||||
msg.filename = msg.content['org.matrix.msc1767.file'].name;
|
||||
msg.mimetype = msg.content['org.matrix.msc1767.file'].mimetype;
|
||||
}
|
||||
|
||||
if('org.matrix.msc1767.audio' in msg.content) {
|
||||
msg.duration = msg.content['org.matrix.msc1767.audio'].duration;
|
||||
msg.waveform = msg.content['org.matrix.msc1767.audio'].waveform;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'm.image':
|
||||
if(!node.acceptImages) return;
|
||||
|
||||
msg.filename = msg.content.filename || msg.content.body;
|
||||
if(msg.encrypted) {
|
||||
msg.url = node.server.matrixClient.mxcUrlToHttp(msg.content.file.url);
|
||||
msg.mxc_url = msg.content.file.url;
|
||||
@@ -118,6 +112,29 @@ module.exports = function(RED) {
|
||||
}
|
||||
break;
|
||||
|
||||
|
||||
case 'm.video':
|
||||
if(!node.acceptVideos) return;
|
||||
msg.filename = msg.content.filename || msg.content.body;
|
||||
if(msg.encrypted) {
|
||||
msg.url = node.server.matrixClient.mxcUrlToHttp(msg.content.file.url);
|
||||
msg.mxc_url = msg.content.file.url;
|
||||
msg.thumbnail_url = node.server.matrixClient.mxcUrlToHttp(msg.content.info.thumbnail_file.url);
|
||||
msg.thumbnail_mxc_url = msg.content.info.thumbnail_file.url;
|
||||
} else {
|
||||
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;
|
||||
|
||||
case 'm.location':
|
||||
if(!node.acceptLocations) return;
|
||||
msg.geo_uri = msg.content.geo_uri;
|
||||
msg.payload = msg.content.body;
|
||||
break;
|
||||
|
||||
case 'm.reaction':
|
||||
if(!node.acceptReactions) return;
|
||||
msg.info = msg.content["m.relates_to"].info;
|
||||
|
||||
@@ -9,7 +9,8 @@
|
||||
defaults: {
|
||||
name: { value: null },
|
||||
server: { value: "", type: "matrix-server-config" },
|
||||
roomId: { value: null }
|
||||
roomId: { value: null },
|
||||
reason: { value: null }
|
||||
},
|
||||
label: function() {
|
||||
return this.name || "Room Ban";
|
||||
@@ -20,7 +21,7 @@
|
||||
|
||||
<script type="text/html" data-template-name="matrix-room-ban">
|
||||
<div class="form-row">
|
||||
<label for="node-input-name"><i class="icon-tag"></i> Name</label>
|
||||
<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">
|
||||
@@ -28,9 +29,25 @@
|
||||
<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>
|
||||
<label for="node-input-roomId"><i class="fa fa-comments"></i> Room ID</label>
|
||||
<input type="text" id="node-input-roomId" placeholder="msg.topic">
|
||||
<pre class="form-tips" id="node-input-roomId-error" style="color: #721c24;background-color: #f8d7da;border-color: #f5c6cb;margin-bottom: 12px;margin-top: 12px;display:none;"></pre>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-reason"><i class="fa fa-comment"></i> Reason</label>
|
||||
<input type="text" id="node-input-reason" placeholder="msg.topic">
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
$(function(){
|
||||
$("#node-input-roomId").on("keyup", function() {
|
||||
if($(this).val() && !$(this).val().startsWith("!")) {
|
||||
$("#node-input-roomId-error").html(`Room IDs start with exclamation point "!"<br />Example: !OGEhHVWSdvArJzumhm:matrix.org`).show();
|
||||
} else {
|
||||
$("#node-input-roomId-error").hide();
|
||||
}
|
||||
}).trigger('keyup');
|
||||
});
|
||||
</script>
|
||||
</script>
|
||||
|
||||
<script type="text/html" data-help-name="matrix-room-ban">
|
||||
@@ -52,7 +69,7 @@
|
||||
<dt class="optional">msg.reason
|
||||
<span class="property-type">string</span>
|
||||
</dt>
|
||||
<dd> Reason for banning the user.</dd>
|
||||
<dd> Reason for banning the user. If configured on the node it will overwrite this input</dd>
|
||||
</dl>
|
||||
|
||||
<h3>Outputs</h3>
|
||||
|
||||
@@ -7,6 +7,7 @@ module.exports = function(RED) {
|
||||
this.name = n.name;
|
||||
this.server = RED.nodes.getNode(n.server);
|
||||
this.roomId = n.roomId;
|
||||
this.reason = n.reason;
|
||||
|
||||
if (!node.server) {
|
||||
node.warn("No configuration node");
|
||||
@@ -45,7 +46,7 @@ module.exports = function(RED) {
|
||||
return;
|
||||
}
|
||||
|
||||
node.server.matrixClient.ban(msg.topic, msg.userId, msg.reason || undefined)
|
||||
node.server.matrixClient.ban(msg.topic, msg.userId, n.reason || msg.reason || undefined)
|
||||
.then(function(e) {
|
||||
node.log("Successfully banned " + msg.userId + " from " + msg.topic);
|
||||
msg.eventId = e.event_id;
|
||||
|
||||
80
src/matrix-room-invite.html
Normal file
@@ -0,0 +1,80 @@
|
||||
<script type="text/javascript">
|
||||
RED.nodes.registerType('matrix-room-invite', {
|
||||
category: 'matrix',
|
||||
color: '#00b7ca',
|
||||
icon: "matrix.png",
|
||||
outputLabels: ["success", "error"],
|
||||
inputs: 0,
|
||||
outputs: 1,
|
||||
defaults: {
|
||||
name: { value: null },
|
||||
server: { value: "", type: "matrix-server-config" },
|
||||
roomId: { value: null },
|
||||
},
|
||||
label: function() {
|
||||
return this.name || "Room Invite";
|
||||
},
|
||||
paletteLabel: 'Room Invite'
|
||||
});
|
||||
</script>
|
||||
|
||||
<script type="text/html" data-template-name="matrix-room-invite">
|
||||
<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-user"></i> Matrix Server Config</label>
|
||||
<input type="text" id="node-input-server">
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script type="text/html" data-help-name="matrix-room-invite">
|
||||
<h3>Details</h3>
|
||||
<p>
|
||||
This node receives room invites.
|
||||
</p>
|
||||
|
||||
<h3>Outputs</h3>
|
||||
<ol class="node-ports">
|
||||
<li>Success
|
||||
<dl class="message-properties">
|
||||
<dt>msg.type <span class="property-type">string</span></dt>
|
||||
<dd>Always <code>m.room.member</code></dd>
|
||||
</dl>
|
||||
|
||||
<dl class="message-properties">
|
||||
<dt>msg.userId <span class="property-type">string</span></dt>
|
||||
<dd>ID of the user the invite is from</dd>
|
||||
</dl>
|
||||
|
||||
<dl class="message-properties">
|
||||
<dt>msg.topic <span class="property-type">string</span></dt>
|
||||
<dd>The room identifier for the invite: for example, <code>!h8zld9j31:example.com</code>.</dd>
|
||||
</dl>
|
||||
|
||||
<dl class="message-properties">
|
||||
<dt>msg.topicName <span class="property-type">string</span></dt>
|
||||
<dd>The invited room name.</dd>
|
||||
</dl>
|
||||
|
||||
<dl class="message-properties">
|
||||
<dt>msg.event <span class="property-type">object</span></dt>
|
||||
<dd>The event object for this invite to get extra details.</dd>
|
||||
</dl>
|
||||
|
||||
<dl class="message-properties">
|
||||
<dt>msg.eventId <span class="property-type">object</span></dt>
|
||||
<dd>The ID of the event for this invite.</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>
|
||||
31
src/matrix-room-invite.js
Normal file
@@ -0,0 +1,31 @@
|
||||
module.exports = function(RED) {
|
||||
function MatrixRoomInvite(n) {
|
||||
RED.nodes.createNode(this, n);
|
||||
|
||||
let node = this;
|
||||
|
||||
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.');
|
||||
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.server.on("Room.invite", async function(msg) {
|
||||
node.send(msg);
|
||||
});
|
||||
}
|
||||
RED.nodes.registerType("matrix-room-invite", MatrixRoomInvite);
|
||||
}
|
||||
@@ -9,7 +9,8 @@
|
||||
defaults: {
|
||||
name: { value: null },
|
||||
server: { value: "", type: "matrix-server-config" },
|
||||
roomId: { value: null }
|
||||
roomId: { value: null },
|
||||
reason: { value: null }
|
||||
},
|
||||
label: function() {
|
||||
return this.name || "Room Kick";
|
||||
@@ -20,7 +21,7 @@
|
||||
|
||||
<script type="text/html" data-template-name="matrix-room-kick">
|
||||
<div class="form-row">
|
||||
<label for="node-input-name"><i class="icon-tag"></i> Name</label>
|
||||
<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">
|
||||
@@ -28,9 +29,25 @@
|
||||
<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>
|
||||
<label for="node-input-roomId"><i class="fa fa-comments"></i> Room ID</label>
|
||||
<input type="text" id="node-input-roomId" placeholder="msg.topic">
|
||||
<pre class="form-tips" id="node-input-roomId-error" style="color: #721c24;background-color: #f8d7da;border-color: #f5c6cb;margin-bottom: 12px;margin-top: 12px;display:none;"></pre>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-reason"><i class="fa fa-comment"></i> Reason</label>
|
||||
<input type="text" id="node-input-reason" placeholder="msg.topic">
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
$(function(){
|
||||
$("#node-input-roomId").on("keyup", function() {
|
||||
if($(this).val() && !$(this).val().startsWith("!")) {
|
||||
$("#node-input-roomId-error").html(`Room IDs start with exclamation point "!"<br />Example: !OGEhHVWSdvArJzumhm:matrix.org`).show();
|
||||
} else {
|
||||
$("#node-input-roomId-error").hide();
|
||||
}
|
||||
}).trigger('keyup');
|
||||
});
|
||||
</script>
|
||||
</script>
|
||||
|
||||
<script type="text/html" data-help-name="matrix-room-kick">
|
||||
@@ -52,7 +69,7 @@
|
||||
<dt class="optional">msg.reason
|
||||
<span class="property-type">string</span>
|
||||
</dt>
|
||||
<dd> Reason for kicking the user.</dd>
|
||||
<dd> Reason for kicking the user. If configured on the node it will overwrite this input</dd>
|
||||
</dl>
|
||||
|
||||
<h3>Outputs</h3>
|
||||
|
||||
@@ -7,6 +7,7 @@ module.exports = function(RED) {
|
||||
this.name = n.name;
|
||||
this.server = RED.nodes.getNode(n.server);
|
||||
this.roomId = n.roomId;
|
||||
this.reason = n.reason;
|
||||
|
||||
if (!node.server) {
|
||||
node.warn("No configuration node");
|
||||
@@ -45,7 +46,7 @@ module.exports = function(RED) {
|
||||
return;
|
||||
}
|
||||
|
||||
node.server.matrixClient.kick(msg.topic, msg.userId, msg.reason || undefined)
|
||||
node.server.matrixClient.kick(msg.topic, msg.userId, n.reason || msg.reason || undefined)
|
||||
.then(function(e) {
|
||||
node.log("Successfully kicked " + msg.userId + " from " + msg.topic);
|
||||
msg.eventId = e.event_id;
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
|
||||
<script type="text/html" data-template-name="matrix-room-users">
|
||||
<div class="form-row">
|
||||
<label for="node-input-name"><i class="icon-tag"></i> Name</label>
|
||||
<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">
|
||||
@@ -30,10 +30,19 @@
|
||||
<div class="form-row">
|
||||
<label for="node-input-server"><i class="fa fa-user"></i> Room Id</label>
|
||||
<input type="text" id="node-input-roomId" placeholder="msg.topic">
|
||||
<pre class="form-tips" id="node-input-roomId-error" style="color: #721c24;background-color: #f8d7da;border-color: #f5c6cb;margin-bottom: 12px;margin-top: 12px;display:none;"></pre>
|
||||
</div>
|
||||
<div class="form-tips">
|
||||
This only works on Synapse servers. The user also must be an administrator.
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
$(function(){
|
||||
$("#node-input-roomId").on("keyup", function() {
|
||||
if($(this).val() && !$(this).val().startsWith("!")) {
|
||||
$("#node-input-roomId-error").html(`Room IDs start with exclamation point "!"<br />Example: !OGEhHVWSdvArJzumhm:matrix.org`).show();
|
||||
} else {
|
||||
$("#node-input-roomId-error").hide();
|
||||
}
|
||||
}).trigger('keyup');
|
||||
});
|
||||
</script>
|
||||
</script>
|
||||
|
||||
<script type="text/html" data-help-name="matrix-room-users">
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
|
||||
<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>
|
||||
<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">
|
||||
@@ -29,8 +29,9 @@
|
||||
<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>
|
||||
<label for="node-input-roomId"><i class="fa fa-comments"></i> Room ID</label>
|
||||
<input type="text" id="node-input-roomId" placeholder="msg.topic">
|
||||
<pre class="form-tips" id="node-input-roomId-error" style="color: #721c24;background-color: #f8d7da;border-color: #f5c6cb;margin-bottom: 12px;margin-top: 12px;display:none;"></pre>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-contentType"><i class="fa fa-user"></i> Content-Type</label>
|
||||
@@ -39,6 +40,17 @@
|
||||
<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: application/pdf) or left empty
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
$(function(){
|
||||
$("#node-input-roomId").on("keyup", function() {
|
||||
if($(this).val() && !$(this).val().startsWith("!")) {
|
||||
$("#node-input-roomId-error").html(`Room IDs start with exclamation point "!"<br />Example: !OGEhHVWSdvArJzumhm:matrix.org`).show();
|
||||
} else {
|
||||
$("#node-input-roomId-error").hide();
|
||||
}
|
||||
}).trigger('keyup');
|
||||
});
|
||||
</script>
|
||||
</script>
|
||||
|
||||
<script type="text/html" data-help-name="matrix-send-file">
|
||||
|
||||
@@ -80,7 +80,7 @@ module.exports = function(RED) {
|
||||
.sendMessage(msg.topic, content)
|
||||
.then(function(e) {
|
||||
node.log("File message sent: " + e);
|
||||
msg.eventId = e.eventId;
|
||||
msg.eventId = e.event_id;
|
||||
node.send([msg, null]);
|
||||
})
|
||||
.catch(function(e){
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
|
||||
<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>
|
||||
<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">
|
||||
@@ -29,8 +29,9 @@
|
||||
<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>
|
||||
<label for="node-input-roomId"><i class="fa fa-comments"></i> Room ID</label>
|
||||
<input type="text" id="node-input-roomId" placeholder="msg.topic">
|
||||
<pre class="form-tips" id="node-input-roomId-error" style="color: #721c24;background-color: #f8d7da;border-color: #f5c6cb;margin-bottom: 12px;margin-top: 12px;display:none;"></pre>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-contentType"><i class="fa fa-user"></i> Content-Type</label>
|
||||
@@ -39,11 +40,22 @@
|
||||
<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
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
$(function(){
|
||||
$("#node-input-roomId").on("keyup", function() {
|
||||
if($(this).val() && !$(this).val().startsWith("!")) {
|
||||
$("#node-input-roomId-error").html(`Room IDs start with exclamation point "!"<br />Example: !OGEhHVWSdvArJzumhm:matrix.org`).show();
|
||||
} else {
|
||||
$("#node-input-roomId-error").hide();
|
||||
}
|
||||
}).trigger('keyup');
|
||||
});
|
||||
</script>
|
||||
</script>
|
||||
|
||||
<script type="text/html" data-help-name="matrix-send-image">
|
||||
<h3>Details</h3>
|
||||
<p>This node will send an image to a Matrix chat room. Supports direct linking to a File In node.</p>
|
||||
<p>This node will send an image to a Matrix chat room. Supports direct linking to a File In node. It's recommended you set <code>msg.contentType</code> so the client knows how to render the image (otherwise the image could show blank).</p>
|
||||
|
||||
<h3>Inputs</h3>
|
||||
<dl class="message-properties">
|
||||
|
||||
@@ -72,20 +72,28 @@ module.exports = function(RED) {
|
||||
})
|
||||
.then(function(file){
|
||||
node.server.matrixClient
|
||||
.sendImageMessage(msg.topic, file.content_uri, {}, (msg.body || msg.filename) || "")
|
||||
.sendImageMessage(
|
||||
msg.topic,
|
||||
file.content_uri,
|
||||
{},
|
||||
(msg.body || msg.filename) || null,
|
||||
null
|
||||
)
|
||||
.then(function(e) {
|
||||
node.log("Image message sent: " + e);
|
||||
msg.eventId = e.event_id;
|
||||
msg.content_uri_mxc = file.content_uri;
|
||||
msg.content_uri = node.server.matrixClient.mxcUrlToHttp(file.content_uri);
|
||||
node.send([msg, null]);
|
||||
})
|
||||
.catch(function(e){
|
||||
node.warn("Error sending image message " + e);
|
||||
node.warn("Error sending image message: " + e);
|
||||
msg.error = e;
|
||||
node.send([null, msg]);
|
||||
});
|
||||
})
|
||||
.catch(function(e){
|
||||
node.warn("Error uploading image message " + e);
|
||||
node.warn("Error uploading image message: " + e);
|
||||
msg.error = e;
|
||||
node.send([null, msg]);
|
||||
});
|
||||
|
||||
@@ -10,8 +10,10 @@
|
||||
name: { value: null },
|
||||
server: { value: "", type: "matrix-server-config" },
|
||||
roomId: { value: null },
|
||||
message: { value: null },
|
||||
messageType: { value: 'm.text' },
|
||||
messageFormat: { value: '' },
|
||||
replaceMessage : { value: false }
|
||||
},
|
||||
label: function() {
|
||||
return this.name || "Send Message";
|
||||
@@ -22,7 +24,7 @@
|
||||
|
||||
<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>
|
||||
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
|
||||
<input type="text" id="node-input-name" placeholder="Name">
|
||||
</div>
|
||||
|
||||
@@ -32,8 +34,25 @@
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<label for="node-input-roomId"><i class="fa fa-user"></i> Room ID</label>
|
||||
<label for="node-input-roomId"><i class="fa fa-comments"></i> Room ID</label>
|
||||
<input type="text" id="node-input-roomId" placeholder="msg.topic">
|
||||
<pre class="form-tips" id="node-input-roomId-error" style="color: #721c24;background-color: #f8d7da;border-color: #f5c6cb;margin-bottom: 12px;margin-top: 12px;display:none;"></pre>
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<label for="node-input-message"><i class="fa fa-comment"></i> Message</label>
|
||||
<textarea id="node-input-message" placeholder="msg.payload" style="width: 70%;"></textarea>
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="node-input-replaceMessage"
|
||||
style="width: auto; margin-left: 105px; vertical-align: top"
|
||||
/>
|
||||
<label for="node-input-replaceMessage" style="width: auto;max-width:50%;">
|
||||
Update existing message if <code>msg.eventId</code> is set
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
@@ -43,6 +62,8 @@
|
||||
<select id="node-input-messageType">
|
||||
<option value="m.text">m.text</option>
|
||||
<option value="m.notice">m.notice</option>
|
||||
<option value="m.location">m.location</option>
|
||||
<option value="m.live">m.live</option>
|
||||
<option value="msg.type">msg.type input</option>
|
||||
</select>
|
||||
</div>
|
||||
@@ -60,6 +81,17 @@
|
||||
<option value="msg.format">msg.format input</option>
|
||||
</select>
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
$(function(){
|
||||
$("#node-input-roomId").on("keyup", function() {
|
||||
if($(this).val() && !$(this).val().startsWith("!")) {
|
||||
$("#node-input-roomId-error").html(`Room IDs start with exclamation point "!"<br />Example: !OGEhHVWSdvArJzumhm:matrix.org`).show();
|
||||
} else {
|
||||
$("#node-input-roomId-error").hide();
|
||||
}
|
||||
}).trigger('keyup');
|
||||
});
|
||||
</script>
|
||||
</script>
|
||||
|
||||
<script type="text/html" data-help-name="matrix-send-message">
|
||||
@@ -76,12 +108,17 @@
|
||||
<dt>msg.payload
|
||||
<span class="property-type">string</span>
|
||||
</dt>
|
||||
<dd> the message text. </dd>
|
||||
<dd> the message text. If configured on the node this is ignored otherwise it required. </dd>
|
||||
|
||||
<dt>msg.replace
|
||||
<span class="property-type">bool</span>
|
||||
</dt>
|
||||
<dd> If true and <code>msg.eventId</code> is present it will update an existing message. Posts a new message if false or <code>msg.eventId</code> is missing. </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>
|
||||
<dd> the formatted HTML message (uses <code>msg.payload</code> if not defined). This only affects HTML messages.</dd>
|
||||
|
||||
<dt class="optional">msg.type
|
||||
<span class="property-type">string | null</span>
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
const {RelationType} = require("matrix-js-sdk");
|
||||
|
||||
module.exports = function(RED) {
|
||||
function MatrixSendImage(n) {
|
||||
RED.nodes.createNode(this, n);
|
||||
@@ -9,6 +11,8 @@ module.exports = function(RED) {
|
||||
this.roomId = n.roomId;
|
||||
this.messageType = n.messageType;
|
||||
this.messageFormat = n.messageFormat;
|
||||
this.replaceMessage = n.replaceMessage;
|
||||
this.message = n.message;
|
||||
|
||||
// taken from https://github.com/matrix-org/synapse/blob/master/synapse/push/mailer.py
|
||||
this.allowedTags = [
|
||||
@@ -89,6 +93,7 @@ module.exports = function(RED) {
|
||||
if(!node.server.isConnected()) {
|
||||
node.error("Matrix server connection is currently closed");
|
||||
node.send([null, msg]);
|
||||
return;
|
||||
}
|
||||
|
||||
msg.topic = node.roomId || msg.topic;
|
||||
@@ -97,28 +102,65 @@ module.exports = function(RED) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(!msg.payload) {
|
||||
node.error('msg.payload is required');
|
||||
let payload = n.message || msg.payload;
|
||||
if(!payload) {
|
||||
node.error('msg.payload must be defined or the message configured on the node.');
|
||||
return;
|
||||
}
|
||||
|
||||
let content = {
|
||||
msgtype: msgType,
|
||||
body: msg.payload.toString()
|
||||
body: payload.toString()
|
||||
};
|
||||
|
||||
|
||||
//zhongjin
|
||||
|
||||
if (msgType === 'm.location') {
|
||||
content.body = '长沙火车站, 车站中路, 五里牌街道, 芙蓉区, 长沙市, 湖南省, 410001, 中国';
|
||||
content.geo_uri = "geo:28.1974439,113.0054238";
|
||||
}
|
||||
|
||||
if (msgType === 'm.live') {
|
||||
msg.topic = "org.matrix.msc3672.beacon_info";
|
||||
content.body = "Live location";
|
||||
content.timeout= 3600000;
|
||||
content.live = true;
|
||||
}
|
||||
|
||||
//end
|
||||
|
||||
if(msgFormat === 'html') {
|
||||
content.format = "org.matrix.custom.html";
|
||||
content.formatted_body =
|
||||
(typeof msg.formatted_payload !== 'undefined' && msg.formatted_payload)
|
||||
? msg.formatted_payload.toString()
|
||||
: msg.payload.toString();
|
||||
: payload.toString();
|
||||
}
|
||||
|
||||
if((node.replaceMessage || msg.replace) && msg.eventId) {
|
||||
content['m.new_content'] = {
|
||||
msgtype: content.msgtype,
|
||||
body: content.body
|
||||
};
|
||||
if('format' in content) {
|
||||
content['m.new_content']['format'] = content['format'];
|
||||
}
|
||||
if('formatted_body' in content) {
|
||||
content['m.new_content']['formatted_body'] = content['formatted_body'];
|
||||
}
|
||||
|
||||
content['m.relates_to'] = {
|
||||
rel_type: RelationType.Replace,
|
||||
event_id: msg.eventId
|
||||
};
|
||||
content['body'] = ' * ' + content['body'];
|
||||
}
|
||||
|
||||
node.server.matrixClient.sendMessage(msg.topic, content)
|
||||
.then(function(e) {
|
||||
node.log("Message sent: " + msg.payload);
|
||||
msg.eventId = e.eventId;
|
||||
node.log("Message sent: " + payload);
|
||||
msg.eventId = e.event_id;
|
||||
node.send([msg, null]);
|
||||
})
|
||||
.catch(function(e){
|
||||
|
||||
@@ -1,12 +1,36 @@
|
||||
<style>
|
||||
.matrix-loader {
|
||||
border: 8px solid #f3f3f3;
|
||||
border-radius: 50%;
|
||||
border-top: 8px solid #3498db;
|
||||
border-bottom: 8px solid #3498db;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
-webkit-animation: matrix-spin 2s linear infinite;
|
||||
animation: matrix-spin 2s linear infinite;
|
||||
}
|
||||
|
||||
@-webkit-keyframes matrix-spin {
|
||||
0% { -webkit-transform: rotate(0deg); }
|
||||
100% { -webkit-transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
@keyframes matrix-spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
</style>
|
||||
|
||||
<script type="text/javascript">
|
||||
RED.nodes.registerType('matrix-server-config',{
|
||||
category: 'config',
|
||||
color: '#00b7ca',
|
||||
credentials: {
|
||||
userId: { type: "text", required: true },
|
||||
deviceLabel: { type: "text", required: false },
|
||||
accessToken: { type: "password", required: true },
|
||||
deviceId: { type: "text", required: true },
|
||||
url: { type: "text", required: true }
|
||||
deviceId: { type: "text", required: false },
|
||||
url: { type: "text", required: true },
|
||||
},
|
||||
defaults: {
|
||||
name: { value: null },
|
||||
@@ -26,28 +50,51 @@
|
||||
<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>
|
||||
<label for="node-config-input-deviceLabel"><i class="fa fa-tag"></i> Device Label</label>
|
||||
<input type="text" placeholder="Node-RED" id="node-config-input-deviceLabel">
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<label for="node-config-input-url"><i class="fa fa-server"></i> Server URL</label>
|
||||
<input type="text" placeholder="https://matrix.org" id="node-config-input-url">
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<label for="node-config-input-userId"><i class="fa fa-user"></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-password"><i class="fa fa-key"></i> Password</label>
|
||||
<input type="password" placeholder="" id="node-config-input-password">
|
||||
</div>
|
||||
<div class="form-tips" style="margin-bottom: 12px;">
|
||||
Password is never saved and is only used to fetch an access token using the button below.
|
||||
</div>
|
||||
<pre class="form-tips" id="matrix-chat-login-error" style="color: #721c24;background-color: #f8d7da;border-color: #f5c6cb;margin-bottom: 12px;display:none;"></pre>
|
||||
<pre class="form-tips" id="matrix-chat-login-success" style="color: #155724;background-color: #d4edda;border-color: #c3e6cb;margin-bottom: 12px;display:none;"></pre>
|
||||
<button class="ui-button ui-corner-all ui-widget primary" id="matrix-login-btn">Update Access Token</button>
|
||||
<div class="matrix-loader" id="matrix-access-token-loader" style="display:none;"></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. You can also generate them using the Shared Secret Registration node.
|
||||
You can either provide/generate an access token yourself or use the login button above to do it automatically. View the <a href="javascript:$('#red-ui-tab-help-link-button').click();">node docs</a> to figure out how to generate an Access Token manually. If you generated a user with shared secret registration you will already have an access token you can place here.
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<label for="node-config-input-deviceId"><i class="fa fa-key"></i> Device ID</label>
|
||||
<label for="node-config-input-deviceId"><i class="fa fa-desktop"></i> Device ID</label>
|
||||
<input type="text" id="node-config-input-deviceId">
|
||||
</div>
|
||||
<div class="form-tips" style="margin-bottom: 12px;">
|
||||
This can either be an existing Device ID attached to the above Access Token or you can enter a unique value to set a new one.
|
||||
If you have a Device ID for this auth token you can place it here, or you can leave it blank to have one automatically generated (this field will always be blank if auto generated as it is saved internally). DO NOT manually set one, this must be generated by the server.<br />
|
||||
WARNING: If you change this after the client has already initialized you will break encryption. Your Device ID is tied to your encryption keys.
|
||||
</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"
|
||||
@@ -65,7 +112,7 @@
|
||||
style="width: auto; margin-left: 125px; vertical-align: top"
|
||||
/>
|
||||
<label for="node-config-input-enableE2ee" style="width: auto;max-width:50%;">
|
||||
Enable end-to-end encryption (requires a Device ID to be set.)
|
||||
Enable end-to-end encryption
|
||||
</label>
|
||||
</div>
|
||||
|
||||
@@ -79,10 +126,79 @@
|
||||
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>
|
||||
If global access is enabled you can access the client directly within 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 type="text/javascript">
|
||||
$("#matrix-login-btn").on("click", function() {
|
||||
function prettyPrintJson(json) {
|
||||
try{
|
||||
return typeof json === 'object' ? JSON.stringify(json, null, 2) : json;
|
||||
}
|
||||
catch (error){
|
||||
return json;
|
||||
}
|
||||
}
|
||||
|
||||
let userId = $("#node-config-input-userId").val(),
|
||||
userPassword = $("#node-config-input-password").val(),
|
||||
serverUrl = $("#node-config-input-url").val();
|
||||
|
||||
if(!userId) {
|
||||
alert("User ID is required to fetch access token.");
|
||||
return;
|
||||
}
|
||||
if(!userPassword) {
|
||||
alert("Password is required to fetch access token.");
|
||||
return;
|
||||
}
|
||||
if(!serverUrl) {
|
||||
alert("Server URL is required to fetch access token.");
|
||||
return;
|
||||
}
|
||||
|
||||
$("#matrix-login-btn, #matrix-chat-login-error, #matrix-chat-login-success").hide();
|
||||
$("#matrix-access-token-loader").show();
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: 'matrix-chat/login',
|
||||
dataType: 'json',
|
||||
data: {
|
||||
'userId': userId,
|
||||
'password': userPassword,
|
||||
'baseUrl': serverUrl,
|
||||
'displayName': $("#node-config-input-deviceLabel").val(),
|
||||
}
|
||||
}).then(
|
||||
function(data) {
|
||||
if(data.result && data.result === 'ok') {
|
||||
$("#matrix-chat-login-error").hide();
|
||||
$("#matrix-chat-login-success")
|
||||
.html("Login Successful! Auth Token and Device ID have been set below.")
|
||||
.show();
|
||||
|
||||
$("#node-config-input-accessToken").val(data.token);
|
||||
$("#node-config-input-deviceId").val(data.device_id);
|
||||
} else if(data.result && data.result === 'error') {
|
||||
$("#matrix-chat-login-success").hide();
|
||||
$("#matrix-chat-login-error")
|
||||
.html(data.message ? ('Failed to login: <br />' + prettyPrintJson(data.message)) : 'Failed to login')
|
||||
.show();
|
||||
}
|
||||
$("#matrix-login-btn").show();
|
||||
$("#matrix-access-token-loader").hide();
|
||||
}, function() {
|
||||
$("#matrix-chat-login-success").hide();
|
||||
$("#matrix-chat-login-error")
|
||||
.html("Failed to login due to server error communicating with Node-RED")
|
||||
.show();
|
||||
$("#matrix-login-btn").show();
|
||||
$("#matrix-access-token-loader").hide();
|
||||
}
|
||||
);
|
||||
});
|
||||
</script>
|
||||
</script>
|
||||
|
||||
<script type="text/html" data-help-name="matrix-server-config">
|
||||
@@ -98,4 +214,4 @@
|
||||
<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>
|
||||
</script>
|
||||
|
||||
@@ -1,49 +1,78 @@
|
||||
global.Olm = require('olm');
|
||||
const fs = require("fs-extra");
|
||||
const sdk = require("matrix-js-sdk");
|
||||
const { resolve } = require('path');
|
||||
const { LocalStorage } = require('node-localstorage');
|
||||
const { LocalStorageCryptoStore } = require('matrix-js-sdk/lib/crypto/store/localStorage-crypto-store');
|
||||
const {RoomEvent, RoomMemberEvent, HttpApiEvent, ClientEvent, MemoryStore} = require("matrix-js-sdk");
|
||||
const request = require("request");
|
||||
require("abort-controller/polyfill"); // polyfill abort-controller if we don't have it
|
||||
if (!globalThis.fetch) {
|
||||
// polyfill fetch if we don't have it
|
||||
if (!globalThis.fetch) {
|
||||
import('node-fetch').then(({ default: fetch, Headers, Request, Response }) => {
|
||||
Object.assign(globalThis, { fetch, Headers, Request, Response })
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = function(RED) {
|
||||
// disable logging if set to "off"
|
||||
let loggingSettings = RED.settings.get('logging');
|
||||
if(
|
||||
typeof loggingSettings.console !== 'undefined' &&
|
||||
typeof loggingSettings.console.level !== 'undefined' &&
|
||||
['info','debug','trace'].indexOf(loggingSettings.console.level.toLowerCase()) >= 0
|
||||
) {
|
||||
const { logger } = require('matrix-js-sdk/lib/logger');
|
||||
logger.disableAll();
|
||||
}
|
||||
|
||||
function MatrixFolderNameFromUserId(name) {
|
||||
return name.replace(/[^a-z0-9]/gi, '_').toLowerCase();
|
||||
}
|
||||
|
||||
function MatrixServerNode(n) {
|
||||
let storageDir = './matrix-client-storage';
|
||||
|
||||
// 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
|
||||
|
||||
let node = this,
|
||||
storageDir = RED.settings.userDir + '/matrix-client-storage';
|
||||
RED.nodes.createNode(this, n);
|
||||
node.setMaxListeners(1000);
|
||||
|
||||
let node = this;
|
||||
node.log("Initializing Matrix Server Config node");
|
||||
|
||||
if(!this.credentials) {
|
||||
this.credentials = {};
|
||||
}
|
||||
|
||||
node.setMaxListeners(1000);
|
||||
|
||||
this.connected = null;
|
||||
this.name = n.name;
|
||||
this.userId = this.credentials.userId;
|
||||
this.deviceLabel = this.credentials.deviceLabel || null;
|
||||
this.deviceId = this.credentials.deviceId || null;
|
||||
this.url = this.credentials.url;
|
||||
this.autoAcceptRoomInvites = n.autoAcceptRoomInvites;
|
||||
this.enableE2ee = n.enableE2ee || false;
|
||||
this.e2ee = (this.enableE2ee && this.deviceId);
|
||||
this.e2ee = n.enableE2ee || false;
|
||||
|
||||
this.globalAccess = n.global;
|
||||
this.initializedAt = new Date();
|
||||
|
||||
if(!this.userId) {
|
||||
node.log("Matrix connection failed: missing user ID in configuration.");
|
||||
return;
|
||||
}
|
||||
|
||||
let localStorageDir = storageDir + '/' + MatrixFolderNameFromUserId(this.userId),
|
||||
localStorage = new LocalStorage(localStorageDir),
|
||||
initialSetup = false;
|
||||
|
||||
let retryStartTimeout = null;
|
||||
|
||||
if(!this.credentials.accessToken) {
|
||||
node.log("Matrix connection failed: missing access token.");
|
||||
node.error("Matrix connection failed: missing access token in configuration.");
|
||||
} else if(!this.url) {
|
||||
node.log("Matrix connection failed: missing server URL.");
|
||||
} else if(!this.userId) {
|
||||
node.log("Matrix connection failed: missing user ID.");
|
||||
node.error("Matrix connection failed: missing server URL in configuration.");
|
||||
} else {
|
||||
node.setConnected = function(connected, cb) {
|
||||
node.setConnected = async function(connected, cb) {
|
||||
if (node.connected !== connected) {
|
||||
node.connected = connected;
|
||||
if(typeof cb === 'function') {
|
||||
@@ -53,6 +82,45 @@ module.exports = function(RED) {
|
||||
if (connected) {
|
||||
node.log("Matrix server connection ready.");
|
||||
node.emit("connected");
|
||||
if(!initialSetup) {
|
||||
// store Device ID internally
|
||||
let stored_device_id = getStoredDeviceId(localStorage),
|
||||
device_id = this.matrixClient.getDeviceId();
|
||||
|
||||
if(!device_id && node.enableE2ee) {
|
||||
node.error("Failed to auto detect deviceId for this auth token. You will need to manually specify one. You may need to login to create a new deviceId.")
|
||||
} else {
|
||||
if(!stored_device_id || stored_device_id !== device_id) {
|
||||
node.log(`Saving Device ID (old:${stored_device_id} new:${device_id})`);
|
||||
storeDeviceId(localStorage, device_id);
|
||||
}
|
||||
|
||||
// update device label
|
||||
if(node.deviceLabel) {
|
||||
node.matrixClient
|
||||
.getDevice(device_id)
|
||||
.then(
|
||||
function(response) {
|
||||
if(response.display_name !== node.deviceLabel) {
|
||||
node.matrixClient.setDeviceDetails(device_id, {
|
||||
display_name: node.deviceLabel
|
||||
}).then(
|
||||
function(response) {},
|
||||
function(error) {
|
||||
node.error("Failed to set device label: " + error);
|
||||
}
|
||||
);
|
||||
}
|
||||
},
|
||||
function(error) {
|
||||
node.error("Failed to fetch device: " + error);
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
initialSetup = true;
|
||||
}
|
||||
} else {
|
||||
node.emit("disconnected");
|
||||
}
|
||||
@@ -64,18 +132,19 @@ module.exports = function(RED) {
|
||||
};
|
||||
node.setConnected(false);
|
||||
|
||||
let localStorageDir = storageDir + '/' + MatrixFolderNameFromUserId(this.userId);
|
||||
|
||||
fs.ensureDirSync(storageDir); // create storage directory if it doesn't exist
|
||||
upgradeDirectoryIfNecessary(node, storageDir);
|
||||
const localStorage = new LocalStorage(localStorageDir);
|
||||
node.matrixClient = sdk.createClient({
|
||||
baseUrl: this.url,
|
||||
accessToken: this.credentials.accessToken,
|
||||
sessionStore: new sdk.WebStorageSessionStore(localStorage),
|
||||
cryptoStore: new LocalStorageCryptoStore(localStorage),
|
||||
store: new MemoryStore({
|
||||
localStorage: localStorage,
|
||||
}),
|
||||
userId: this.userId,
|
||||
deviceId: this.deviceId || undefined,
|
||||
deviceId: (this.deviceId || getStoredDeviceId(localStorage)) || undefined,
|
||||
request
|
||||
// verificationMethods: ["m.sas.v1"]
|
||||
});
|
||||
|
||||
// set globally if configured to do so
|
||||
@@ -83,13 +152,19 @@ module.exports = function(RED) {
|
||||
this.context().global.set('matrixClient["'+this.userId+'"]', node.matrixClient);
|
||||
}
|
||||
|
||||
node.on('close', function(done) {
|
||||
if(node.matrixClient) {
|
||||
node.matrixClient.close();
|
||||
function stopClient() {
|
||||
if(node.matrixClient && node.matrixClient.clientRunning) {
|
||||
node.matrixClient.stopClient();
|
||||
node.setConnected(false);
|
||||
}
|
||||
|
||||
if(retryStartTimeout) {
|
||||
clearTimeout(retryStartTimeout);
|
||||
}
|
||||
}
|
||||
|
||||
node.on('close', function(done) {
|
||||
stopClient();
|
||||
done();
|
||||
});
|
||||
|
||||
@@ -97,10 +172,69 @@ module.exports = function(RED) {
|
||||
return node.connected;
|
||||
};
|
||||
|
||||
node.matrixClient.on("Room.timeline", async function(event, room, toStartOfTimeline, data) {
|
||||
node.emit("Room.timeline", event, room, toStartOfTimeline, data);
|
||||
node.matrixClient.on(RoomEvent.Timeline, async function(event, room, toStartOfTimeline, removed, data) {
|
||||
if (toStartOfTimeline) {
|
||||
return; // ignore paginated results
|
||||
}
|
||||
if (!event.getSender() || event.getSender() === node.userId) {
|
||||
return; // ignore our own messages
|
||||
}
|
||||
if (!data || !data.liveEvent) {
|
||||
return; // ignore old message (we only want live events)
|
||||
}
|
||||
if(node.initializedAt > event.getDate()) {
|
||||
return; // skip events that occurred before our client initialized
|
||||
}
|
||||
|
||||
try {
|
||||
await node.matrixClient.decryptEventIfNeeded(event);
|
||||
} catch (error) {
|
||||
node.error(error);
|
||||
return;
|
||||
}
|
||||
|
||||
const isDmRoom = (room) => {
|
||||
// Find out if this is a direct message room.
|
||||
let isDM = !!room.getDMInviter();
|
||||
const allMembers = room.currentState.getMembers();
|
||||
if (!isDM && allMembers.length <= 2) {
|
||||
// if not a DM, but there are 2 users only
|
||||
// double check DM (needed because getDMInviter works only if you were invited, not if you invite)
|
||||
// hence why we check for each member
|
||||
if (allMembers.some((m) => m.getDMInviter())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return allMembers.length <= 2 && isDM;
|
||||
};
|
||||
|
||||
let msg = {
|
||||
encrypted : event.isEncrypted(),
|
||||
redacted : event.isRedacted(),
|
||||
content : event.getContent(),
|
||||
type : (event.getContent()['msgtype'] || event.getType()) || null,
|
||||
payload : (event.getContent()['body'] || event.getContent()) || null,
|
||||
isDM : isDmRoom(room),
|
||||
userId : event.getSender(),
|
||||
topic : event.getRoomId(),
|
||||
eventId : event.getId(),
|
||||
event : event
|
||||
};
|
||||
|
||||
node.log("Received" + (msg.encrypted ? ' encrypted' : '') +" timeline event [" + msg.type + "]: (" + room.name + ") " + event.getSender() + " :: " + msg.content.body + (toStartOfTimeline ? ' [PAGINATED]' : ''));
|
||||
node.emit("Room.timeline", event, room, toStartOfTimeline, removed, data, msg);
|
||||
});
|
||||
|
||||
/**
|
||||
* Fires when we want to suggest to the user that they restore their megolm keys
|
||||
* from backup or by cross-signing the device.
|
||||
*
|
||||
* @event module:client~MatrixClient#"crypto.suggestKeyRestore"
|
||||
*/
|
||||
// node.matrixClient.on("crypto.suggestKeyRestore", function(){
|
||||
//
|
||||
// });
|
||||
|
||||
// node.matrixClient.on("RoomMember.typing", async function(event, member) {
|
||||
// let isTyping = member.typing;
|
||||
// let roomId = member.roomId;
|
||||
@@ -118,55 +252,105 @@ module.exports = function(RED) {
|
||||
// });
|
||||
|
||||
// handle auto-joining rooms
|
||||
node.matrixClient.on("RoomMember.membership", async function(event, member) {
|
||||
|
||||
node.matrixClient.on(RoomMemberEvent.Membership, async function(event, member) {
|
||||
if(node.initializedAt > event.getDate()) {
|
||||
return; // skip events that occurred before our client initialized
|
||||
}
|
||||
|
||||
if (member.membership === "invite" && member.userId === node.userId) {
|
||||
node.log("Got invite to join room " + member.roomId);
|
||||
console.log(event);
|
||||
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);
|
||||
}
|
||||
|
||||
let room = node.matrixClient.getRoom(event.getRoomId());
|
||||
node.emit("Room.invite", {
|
||||
type : 'm.room.member',
|
||||
userId : event.getSender(),
|
||||
topic : event.getRoomId(),
|
||||
topicName : (room ? room.name : null) || null,
|
||||
event : event,
|
||||
eventId : event.getId(),
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
node.matrixClient.on("sync", async function(state, prevState, data) {
|
||||
switch (state) {
|
||||
case "ERROR":
|
||||
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, function(){
|
||||
node.log("Matrix client stopped");
|
||||
});
|
||||
break;
|
||||
|
||||
case "SYNCING":
|
||||
case "PREPARED":
|
||||
node.setConnected(true, function(){
|
||||
node.log("Matrix client connected");
|
||||
});
|
||||
break;
|
||||
|
||||
// case "PREPARED":
|
||||
// // the client instance is ready to be queried.
|
||||
// node.log("Matrix server connection ready.");
|
||||
// node.setConnected(true);
|
||||
// break;
|
||||
node.matrixClient.on(ClientEvent.Sync, async function(state, prevState, data) {
|
||||
node.debug("SYNC [STATE=" + state + "] [PREVSTATE=" + prevState + "]");
|
||||
if(prevState === null && state === "PREPARED" ) {
|
||||
// Occurs when the initial sync is completed first time.
|
||||
// This involves setting up filters and obtaining push rules.
|
||||
node.setConnected(true, function(){
|
||||
node.log("Matrix client connected");
|
||||
});
|
||||
} else if(prevState === null && state === "ERROR") {
|
||||
// Occurs when the initial sync failed first time.
|
||||
node.setConnected(false, function(){
|
||||
node.error("Failed to connect to Matrix server");
|
||||
});
|
||||
} else if(prevState === "ERROR" && state === "PREPARED") {
|
||||
// Occurs when the initial sync succeeds
|
||||
// after previously failing.
|
||||
node.setConnected(true, function(){
|
||||
node.log("Matrix client connected");
|
||||
});
|
||||
} else if(prevState === "PREPARED" && state === "SYNCING") {
|
||||
// Occurs immediately after transitioning to PREPARED.
|
||||
// Starts listening for live updates rather than catching up.
|
||||
node.setConnected(true, function(){
|
||||
node.log("Matrix client connected");
|
||||
});
|
||||
} else if(prevState === "SYNCING" && state === "RECONNECTING") {
|
||||
// Occurs when the live update fails.
|
||||
node.setConnected(false, function(){
|
||||
node.error("Connection to Matrix server lost");
|
||||
});
|
||||
} else if(prevState === "RECONNECTING" && state === "RECONNECTING") {
|
||||
// Can occur if the update calls continue to fail,
|
||||
// but the keepalive calls (to /versions) succeed.
|
||||
node.setConnected(false, function(){
|
||||
node.error("Connection to Matrix server lost");
|
||||
});
|
||||
} else if(prevState === "RECONNECTING" && state === "ERROR") {
|
||||
// Occurs when the keepalive call also fails
|
||||
node.setConnected(false, function(){
|
||||
node.error("Connection to Matrix server lost");
|
||||
});
|
||||
} else if(prevState === "ERROR" && state === "SYNCING") {
|
||||
// Occurs when the client has performed a
|
||||
// live update after having previously failed.
|
||||
node.setConnected(true, function(){
|
||||
node.log("Matrix client connected");
|
||||
});
|
||||
} else if(prevState === "ERROR" && state === "ERROR") {
|
||||
// Occurs when the client has failed to
|
||||
// keepalive for a second time or more.
|
||||
node.setConnected(false, function(){
|
||||
node.error("Connection to Matrix server lost");
|
||||
});
|
||||
} else if(prevState === "SYNCING" && state === "SYNCING") {
|
||||
// Occurs when the client has performed a live update.
|
||||
// This is called <i>after</i> processing.
|
||||
node.setConnected(true, function(){
|
||||
node.log("Matrix client connected");
|
||||
});
|
||||
} else if(state === "STOPPED") {
|
||||
// Occurs once the client has stopped syncing or
|
||||
// trying to sync after stopClient has been called.
|
||||
node.setConnected(false, function(){
|
||||
node.error("Connection to Matrix server lost");
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
node.matrixClient.on("Session.logged_out", async function(errorObj){
|
||||
|
||||
node.matrixClient.on(HttpApiEvent.SessionLoggedOut, async function(errorObj){
|
||||
// Example if user auth token incorrect:
|
||||
// {
|
||||
// errcode: 'M_UNKNOWN_TOKEN',
|
||||
@@ -178,7 +362,8 @@ module.exports = function(RED) {
|
||||
// httpStatus: 401
|
||||
// }
|
||||
|
||||
node.error("[Session.logged_out] " + errorObj);
|
||||
node.error("Authentication failure: " + errorObj);
|
||||
stopClient();
|
||||
});
|
||||
|
||||
async function run() {
|
||||
@@ -189,29 +374,114 @@ module.exports = function(RED) {
|
||||
node.matrixClient.setGlobalErrorOnUnknownDevices(false);
|
||||
}
|
||||
node.log("Connecting to Matrix server...");
|
||||
await node.matrixClient.startClient({ initialSyncLimit: 8 });
|
||||
} catch(error){
|
||||
await node.matrixClient.startClient({
|
||||
initialSyncLimit: 8
|
||||
});
|
||||
} catch(error) {
|
||||
node.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
run().catch((error) => node.error(error));
|
||||
// do an authed request and only continue if we don't get an error
|
||||
// this prevent the matrix client from crashing Node-RED on invalid auth token
|
||||
(function checkAuthTokenThenStart() {
|
||||
if(node.matrixClient.clientRunning) {
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* We do a /whoami request before starting for a few reasons:
|
||||
* - validate our auth token
|
||||
* - make sure auth token belongs to provided node.userId
|
||||
* - fetch device_id if possible (only available on Synapse >= v1.40.0 under MSC2033)
|
||||
*/
|
||||
node.matrixClient.whoami()
|
||||
.then(
|
||||
function(data) {
|
||||
if((typeof data['device_id'] === undefined || !data['device_id']) && !node.deviceId && !getStoredDeviceId(localStorage)) {
|
||||
node.error("/whoami request did not return device_id. You will need to manually set one in your configuration because this cannot be automatically fetched.");
|
||||
}
|
||||
if('device_id' in data && data['device_id'] && !node.deviceId) {
|
||||
// if we have no device_id configured lets use the one
|
||||
// returned by /whoami for this access_token
|
||||
node.matrixClient.deviceId = data['device_id'];
|
||||
}
|
||||
|
||||
// make sure our userId matches the access token's
|
||||
if(data['user_id'].toLowerCase() !== node.userId.toLowerCase()) {
|
||||
node.error(`User ID provided is ${node.userId} but token belongs to ${data['user_id']}`);
|
||||
return;
|
||||
}
|
||||
run().catch((error) => node.error(error));
|
||||
},
|
||||
function(err) {
|
||||
// if the error isn't authentication related retry in a little bit
|
||||
if(err.code !== "M_UNKNOWN_TOKEN") {
|
||||
retryStartTimeout = setTimeout(checkAuthTokenThenStart, 15000);
|
||||
node.error("Auth check failed: " + err);
|
||||
}
|
||||
}
|
||||
)
|
||||
})();
|
||||
}
|
||||
}
|
||||
|
||||
RED.nodes.registerType("matrix-server-config", MatrixServerNode, {
|
||||
credentials: {
|
||||
userId: { type:"text", required: true },
|
||||
accessToken: { type:"text", required: true },
|
||||
deviceId: { type: "text", required: true },
|
||||
url: { type: "text", required: true },
|
||||
deviceLabel: { type: "text", required: false },
|
||||
userId: { type: "text", required: true },
|
||||
accessToken: { type: "text", required: true },
|
||||
deviceId: { type: "text", required: false },
|
||||
url: { type: "text", required: true }
|
||||
}
|
||||
});
|
||||
|
||||
function upgradeDirectoryIfNecessary(node, storageDir) {
|
||||
let oldStorageDir = './matrix-local-storage';
|
||||
RED.httpAdmin.post(
|
||||
"/matrix-chat/login",
|
||||
RED.auth.needsPermission('flows.write'),
|
||||
function(req, res) {
|
||||
let userId = req.body.userId || undefined,
|
||||
password = req.body.password || undefined,
|
||||
baseUrl = req.body.baseUrl || undefined,
|
||||
deviceId = req.body.deviceId || undefined,
|
||||
displayName = req.body.displayName || undefined;
|
||||
|
||||
// if the old storage location exists lets move it to it's new location
|
||||
const matrixClient = sdk.createClient({
|
||||
baseUrl: baseUrl,
|
||||
deviceId: deviceId,
|
||||
localTimeoutMs: '30000',
|
||||
request
|
||||
});
|
||||
|
||||
matrixClient.login(
|
||||
'm.login.password', {
|
||||
user: userId,
|
||||
password: password,
|
||||
initial_device_display_name: displayName
|
||||
})
|
||||
.then(
|
||||
function(response) {
|
||||
res.json({
|
||||
'result': 'ok',
|
||||
'token': response.access_token,
|
||||
'device_id': response.device_id,
|
||||
'user_id': response.user_id,
|
||||
});
|
||||
},
|
||||
function(err) {
|
||||
res.json({
|
||||
'result': 'error',
|
||||
'message': err
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
function upgradeDirectoryIfNecessary(node, storageDir) {
|
||||
let oldStorageDir = './matrix-local-storage',
|
||||
oldStorageDir2 = './matrix-client-storage';
|
||||
|
||||
// if the old storage location exists lets move it to the new location
|
||||
if(fs.pathExistsSync(oldStorageDir)){
|
||||
RED.nodes.eachNode(function(n){
|
||||
try {
|
||||
@@ -224,7 +494,7 @@ module.exports = function(RED) {
|
||||
fs.copySync(oldStorageDir, dir);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
node.error(err);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -232,5 +502,36 @@ module.exports = function(RED) {
|
||||
node.log("archiving old config folder '" + oldStorageDir + "' to '" + oldStorageDir + "-backup");
|
||||
fs.renameSync(oldStorageDir, oldStorageDir + "-backup");
|
||||
}
|
||||
|
||||
if(RED.settings.userDir !== resolve('./') && resolve(oldStorageDir2) !== resolve(storageDir)) {
|
||||
// user directory does not match running directory
|
||||
// check if we stored stuff in wrong directory and move it
|
||||
if(fs.pathExistsSync(oldStorageDir2)){
|
||||
fs.ensureDirSync(storageDir);
|
||||
node.log("found old '" + oldStorageDir2 + "' path, copying to new location '" + storageDir);
|
||||
fs.copySync(oldStorageDir2, storageDir);
|
||||
// rename folder to keep as a backup (and so we don't run again)
|
||||
fs.renameSync(oldStorageDir2, oldStorageDir2 + "-backup");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If a device ID is stored we will use that for the client
|
||||
*/
|
||||
function getStoredDeviceId(localStorage) {
|
||||
let deviceId = localStorage.getItem('my_device_id');
|
||||
if(deviceId === "null" || !deviceId) {
|
||||
return null;
|
||||
}
|
||||
return deviceId;
|
||||
}
|
||||
|
||||
function storeDeviceId(localStorage, deviceId) {
|
||||
if(!deviceId) {
|
||||
return false;
|
||||
}
|
||||
localStorage.setItem('my_device_id', deviceId);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -19,7 +19,7 @@
|
||||
|
||||
<script type="text/html" data-template-name="matrix-synapse-create-edit-user">
|
||||
<div class="form-row">
|
||||
<label for="node-input-name"><i class="icon-tag"></i> Name</label>
|
||||
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
|
||||
<input type="text" id="node-input-name" placeholder="Name">
|
||||
</div>
|
||||
|
||||
|
||||
@@ -52,7 +52,6 @@ module.exports = function(RED) {
|
||||
|
||||
node.server.matrixClient.http
|
||||
.authedRequest(
|
||||
undefined,
|
||||
'PUT',
|
||||
node.encodeUri(
|
||||
"/_synapse/admin/v2/users/$userId",
|
||||
@@ -62,13 +61,13 @@ module.exports = function(RED) {
|
||||
msg.payload,
|
||||
{ prefix: '' }
|
||||
).then(function(e){
|
||||
msg.payload = e;
|
||||
node.send([msg, null]);
|
||||
}).catch(function(e){
|
||||
node.warn("Error creating/editing user " + e);
|
||||
msg.error = e;
|
||||
node.send([null, msg]);
|
||||
});
|
||||
msg.payload = e;
|
||||
node.send([msg, null]);
|
||||
}).catch(function(e){
|
||||
node.warn("Error creating/editing user " + e);
|
||||
msg.error = e;
|
||||
node.send([null, msg]);
|
||||
});
|
||||
});
|
||||
}
|
||||
RED.nodes.registerType("matrix-synapse-create-edit-user", MatrixSynapseCreateEditUser);
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
|
||||
<script type="text/html" data-template-name="matrix-synapse-deactivate-user">
|
||||
<div class="form-row">
|
||||
<label for="node-input-name"><i class="icon-tag"></i> Name</label>
|
||||
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
|
||||
<input type="text" id="node-input-name" placeholder="Name">
|
||||
</div>
|
||||
|
||||
|
||||
@@ -56,7 +56,6 @@ module.exports = function(RED) {
|
||||
);
|
||||
node.server.matrixClient.http
|
||||
.authedRequest(
|
||||
undefined,
|
||||
'POST',
|
||||
path,
|
||||
undefined,
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
|
||||
<script type="text/html" data-template-name="matrix-synapse-join-room">
|
||||
<div class="form-row">
|
||||
<label for="node-input-name"><i class="icon-tag"></i> Name</label>
|
||||
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
|
||||
<input type="text" id="node-input-name" placeholder="Name">
|
||||
</div>
|
||||
|
||||
@@ -30,13 +30,25 @@
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<label for="node-input-roomId"><i class="fa fa-user"></i> Room ID</label>
|
||||
<label for="node-input-roomId"><i class="fa fa-comments"></i> Room ID</label>
|
||||
<input type="text" id="node-input-roomId" placeholder="msg.topic">
|
||||
<pre class="form-tips" id="node-input-roomId-error" style="color: #721c24;background-color: #f8d7da;border-color: #f5c6cb;margin-bottom: 12px;margin-top: 12px;display:none;"></pre>
|
||||
</div>
|
||||
|
||||
<div class="form-tips" style="margin-bottom: 12px;">
|
||||
User must be an admin to use this endpoint.
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
$(function(){
|
||||
$("#node-input-roomId").on("keyup", function() {
|
||||
if($(this).val() && !$(this).val().startsWith("!")) {
|
||||
$("#node-input-roomId-error").html(`Room IDs start with exclamation point "!"<br />Example: !OGEhHVWSdvArJzumhm:matrix.org`).show();
|
||||
} else {
|
||||
$("#node-input-roomId-error").hide();
|
||||
}
|
||||
}).trigger('keyup');
|
||||
});
|
||||
</script>
|
||||
</script>
|
||||
|
||||
<script type="text/html" data-help-name="matrix-synapse-join-room">
|
||||
|
||||
@@ -60,7 +60,6 @@ module.exports = function(RED) {
|
||||
// we need the status code, so set onlydata to false for this request
|
||||
node.server.matrixClient.http
|
||||
.authedRequest(
|
||||
undefined,
|
||||
'POST',
|
||||
node.encodeUri(
|
||||
"/_synapse/admin/v1/join/$room_id_or_alias",
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
|
||||
<script type="text/html" data-template-name="matrix-synapse-register">
|
||||
<div class="form-row">
|
||||
<label for="node-input-name"><i class="icon-tag"></i> Name</label>
|
||||
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
|
||||
<input type="text" id="node-input-name" placeholder="Name">
|
||||
</div>
|
||||
|
||||
@@ -78,7 +78,10 @@
|
||||
<dt class="optional">msg.payload.user_type
|
||||
<span class="property-type">string | null</span>
|
||||
</dt>
|
||||
<dd> Set the user type. Leave this to null if you don't know what it is for. Check <a href="https://github.com/matrix-org/synapse/blob/master/synapse/api/constants.py">here</a> and look for <code>class UserTypes</code> to figure out what is valid.</dd>
|
||||
<dd>
|
||||
Set the user type. Leave this to null if you don't know what it is for. Check <a href="https://github.com/matrix-org/synapse/blob/master/synapse/api/constants.py">here</a> and look for <code>class UserTypes</code> to figure out what is valid.<br>
|
||||
NOTE: Testing shows that "bot" and "support" do not work for this. Generate a normal user to use as a bot.
|
||||
</dd>
|
||||
</dl>
|
||||
|
||||
<h3>Outputs</h3>
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
module.exports = function(RED) {
|
||||
const got = require("got");
|
||||
const utf8 = require('utf8');
|
||||
const crypto = require('crypto');
|
||||
|
||||
@@ -22,7 +21,8 @@ module.exports = function(RED) {
|
||||
return;
|
||||
}
|
||||
|
||||
node.on("input", function (msg) {
|
||||
node.on("input", async function (msg) {
|
||||
const { got } = await import('got');
|
||||
|
||||
if(!msg.payload.username) {
|
||||
node.error("msg.payload.username is required");
|
||||
@@ -34,7 +34,7 @@ module.exports = function(RED) {
|
||||
return;
|
||||
}
|
||||
|
||||
(async () => {
|
||||
await (async () => {
|
||||
try {
|
||||
var response = await got.get(this.server + '/_synapse/admin/v1/register', {
|
||||
responseType: 'json'
|
||||
@@ -62,7 +62,13 @@ module.exports = function(RED) {
|
||||
.update(utf8.encode(msg.payload.password))
|
||||
.update("\x00")
|
||||
.update(msg.payload.admin ? "admin" : "notadmin")
|
||||
.digest('hex');
|
||||
|
||||
if(msg.payload.user_type || null) {
|
||||
hmac.update("\x00")
|
||||
.update(msg.payload.user_type);
|
||||
}
|
||||
|
||||
hmac = hmac.digest('hex');
|
||||
|
||||
try {
|
||||
response = await got.post(this.server + '/_synapse/admin/v1/register', {
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
|
||||
<script type="text/html" data-template-name="matrix-synapse-users">
|
||||
<div class="form-row">
|
||||
<label for="node-input-name"><i class="icon-tag"></i> Name</label>
|
||||
<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">
|
||||
|
||||
@@ -48,7 +48,6 @@ module.exports = function(RED) {
|
||||
|
||||
node.server.matrixClient.http
|
||||
.authedRequest(
|
||||
undefined,
|
||||
'GET',
|
||||
"/_synapse/admin/v2/users",
|
||||
queryParams,
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
|
||||
<script type="text/html" data-template-name="matrix-whois-user">
|
||||
<div class="form-row">
|
||||
<label for="node-input-name"><i class="icon-tag"></i> Name</label>
|
||||
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
|
||||
<input type="text" id="node-input-name" placeholder="Name">
|
||||
</div>
|
||||
|
||||
|
||||
@@ -53,7 +53,6 @@ module.exports = function(RED) {
|
||||
// we need the status code, so set onlydata to false for this request
|
||||
node.server.matrixClient.http
|
||||
.authedRequest(
|
||||
undefined,
|
||||
'GET',
|
||||
node.encodeUri(
|
||||
"/_matrix/client/r0/admin/whois/$userId",
|
||||
|
||||