81 Commits
0.1.8 ... dev

Author SHA1 Message Date
Skylar Sadlier
ce8be4a30f Update readme 2022-12-06 22:10:26 -07:00
Skylar Sadlier
3e808cabec - Set version to 0.7.0 2022-12-06 21:53:30 -07:00
Skylar Sadlier
2fdc7482ce - Add back in persistent storage
- Receive node now accepts video files
2022-12-06 21:49:05 -07:00
Skylar Sadlier
c7f9d56df2 - Updated code for new authedRequest argument format
- matrix-js-sdk updated to 22.0.0
- fs-extra updated to 11.1.0
- minimum version set to 14.14
2022-12-06 18:47:41 -07:00
Skylar Sadlier
3c042ae47d - rename "Invite Room" node to "Invite to Room" to distinguish from new node
- new node "Invite Room" for receiving room invites
- new node "Leave Room" for leaving a room
- updated examples with new nodes
2022-12-05 23:40:05 -07:00
Skylar Sadlier
0a34870fa3 Merge pull request #71 from jacotec/master
Pass invites to the receive object
2022-12-05 21:20:11 -07:00
Skylar Sadlier
768a1c8ce0 Merge pull request #73 from WesleyE/master
Verify login on the relative matrix-chat/login URI
2022-12-05 21:19:03 -07:00
Skylar Sadlier
22dd9b4ca3 - matrix-js-sdk updated to v21.2.0
- added node-fetch polyfill for older nodejs versions
- added abort-controller polyfill for older nodejs versions
2022-12-05 21:18:16 -07:00
Skylar Sadlier
462f9670c2 - matrix-js-sdk updated to v20.0.0
- olm updated to v3.2.13 https://gitlab.matrix.org/matrix-org/olm/-/packages/136
- console messages now only appear if Node-RED's settings.js `logging.console.level` is info, debug, or trace
2022-12-05 19:49:55 -07:00
Wesley Elfring
e4b01c40c2 Verify login on the relative matrix-chat/login URI 2022-11-30 13:11:33 +01:00
Marco Jakobs
908d60835d Pass invites to the receive object
This commit passes a room invite to the receive node, so you can react to it.

Sample invitation:

HDsvcHGiTYFxxxxxxx:mydomain.de : msg : Object
object
type: "r.invite"
payload: "Invitation"
userId: "@relay:mydomain.de"
topic: "!HDsvcHGiTYFxxxxxxx:mydomain.de"
_msgid: "ef1fd7f3c8ae82e6"
2022-11-25 20:56:06 +01:00
Skylar Sadlier
4c17a21008 Merge pull request #63 from Skylar-Tech/dev
Version 0.6.1
2022-04-19 13:03:47 -06:00
Skylar Sadlier
bd4f6ea486 Update version to 0.6.1
- Fix: Error thrown in folder migration if new and old path are same
2022-04-19 13:01:00 -06:00
Skylar Sadlier
5ef0b6a11f Fix: Error thrown in folder migration if new and old path are same 2022-04-19 12:58:52 -06:00
Skylar Sadlier
97f27e61c6 Merge pull request #62 from Skylar-Tech/61-audio-messages
61 audio messages
2022-03-28 00:45:22 -06:00
Skylar Sadlier
7bdadc0fe9 Closes #209
- Can now receive audio files
- Can now receive locations
- Readme updated
2022-03-28 00:42:52 -06:00
Skylar Sadlier
5f129560aa Couple of little changes:
- Fix matrix-receive message types displaying in uppercase when it should be lowercase
- Readme updated with some extra things that are now possible since last release
- Link to matrix.org project in our README.md
2022-03-28 00:42:19 -06:00
Skylar Sadlier
00bc14e1c7 Merge pull request #59 from Skylar-Tech/dev
Version 0.5.8
2022-03-18 13:50:28 -06:00
Skylar Sadlier
9f41b67174 Version 0.5.8 2022-03-18 13:49:38 -06:00
Skylar Sadlier
4e93b7253e Revert examples 2022-03-18 13:37:24 -06:00
Skylar Sadlier
ecb4427217 - Revert device verification/e2ee stuff. It's not ready. 2022-03-18 13:34:46 -06:00
Skylar Sadlier
3e70369cae WIP 2022-03-18 00:23:28 -06:00
Skylar Sadlier
2ce80e2906 Update version to 0.5.5
- `fs-extra` and `got` dependencies updates
2022-03-17 21:06:15 -06:00
Skylar Sadlier
0c657caf5f Update version to 0.5.0 2022-03-17 20:05:06 -06:00
Skylar Sadlier
848fd0ec9d Merge pull request #58 from Skylar-Tech/48-key-sharing
48 key sharing
2022-03-17 20:03:56 -06:00
Skylar Sadlier
fef40f4ea9 - Update matrix-device-verification node description with super basic info on how to use it 2022-03-17 20:03:48 -06:00
Skylar Sadlier
595fbca3df - Update main readme with new verification notes 2022-03-17 19:58:25 -06:00
Skylar Sadlier
487f17a439 Update examples to move function example above verification ones. 2022-03-17 19:54:13 -06:00
Skylar Sadlier
68cb5a026e Update examples to include one for receiving and requesting device verification 2022-03-17 19:51:14 -06:00
Skylar Sadlier
d01733c647 closes #48
- Device verification can now be requested or received
2022-03-17 19:38:29 -06:00
Skylar Sadlier
ed146e98d8 Closes #56 Closes #50
- can now specify message in node's configuration for matrix-send-message
- can now specify reaction in node's configuration for matrix-react
- reason can now be configured on both matrix-room-kick and matrix-room-ban
- icons updated for various nodes
- fix tag for name configuration field for various nodes
- roomId input is now validated to ensure it starts with ! and if not shows an error
2022-03-17 17:21:44 -06:00
Skylar Sadlier
3dc6363a88 Closes #56
- can now specify message in node's configuration for matrix-send-message
- can now specify reaction in node's configuration for matrix-react
- reason can now be configured on both matrix-room-kick and matrix-room-ban
- icons updated for various nodes
2022-03-17 16:01:27 -06:00
Skylar Sadlier
380e548425 Closes #54
- New option for matrix-send-message node that allows replacing existing message if enabled and `msg.eventId` is passed in.
2022-03-17 15:37:10 -06:00
Skylar Sadlier
27dd4d81a2 name should default to null 2022-03-17 14:47:01 -06:00
Skylar Sadlier
de3c58044f Add example for delete-event node 2022-03-17 14:40:53 -06:00
Skylar Sadlier
db1901ed1e Closes #29
- msg.isDM is now returned on received messages to help determine if it originated from a direct message room
2022-03-17 14:30:10 -06:00
Skylar Sadlier
ac68060825 - matrix-crypt-file documentation updated (it cannot encrypt, only decrypts)
- matrix-js-sdk update from 15.5.0 to 16.0.0
- Fixed matrix-js-sdk breaking changes
- @matrix-org_olm-3.2.8.tgz added to package since there is no npm release for this. It fixes some errors so we need it.
2022-03-17 14:23:22 -06:00
Skylar Sadlier
d304cb95f6 Merge pull request #55 from Skylar-Tech/51-device_id-fix
- We now use /whoami to validate the auth token since it gives us the…
2022-03-17 13:29:48 -06:00
Skylar Sadlier
85764c08d3 Merge pull request #52 from aikitori/feature/delete-message
Add Delete Message Node
2022-03-17 12:26:40 -06:00
aikitori
050be29d64 rename node 2022-03-13 10:58:17 +01:00
Skylar Sadlier
33823dea25 - We now use /whoami to validate the auth token since it gives us the user_id and device_id for the given auth_token (device_id is only available on Synapse 1.40 onwards)
- Error is now thrown if device_id cannot be automatically detected
- Do not store `null` into my_device_id and ignore if it has already been set. Fixes #51
2022-03-12 16:23:43 -07:00
aikitori
d826c58b51 Add Delete Message Node 2022-03-12 16:48:52 +01:00
Skylar Sadlier
d0ba671452 - Fix force-join-room example links 2022-02-09 12:58:32 -07:00
Skylar Sadlier
282c8ffc80 - Bump version to 0.4.6
- Added example for force joining a room
- Deactivate a user example was missing json file contents
2022-02-09 12:48:52 -07:00
Skylar Sadlier
6dd2ec75f0 - Move client storage to the Node-RED user directory.
- Remove mention of Device ID being required for encryption (since it can now auto generate if not provided this could make people think they have to set it when that isn't the case)
- matrix-js-sdk updated from ^v15.3.0 to ^v15.5.0
- got updated from ^11.8.2 to ^12.0.1 (this also required us to change how we import this library in code)
- Node-RED version requirement added for >=v1.3.0
- NodeJS version requirement added for >=v14.0.0
- removed `process` dependency
- send-image node fixed so error doesn't get thrown (`matrix-js-sdk` updated causing some errors)
- updated send-image node docs to explain that msg.contentType is necessary for some clients to render the image (otherwise it could display as a blank message in the room).
- If a matrix server configuration node was missing it's User ID it would throw a TypeError instead of telling the user the actual issue.
- Updated user list example: it now paginates all users on the server (if you had a lot of users the message would fail to send because it was too large)
- Added example for creating a room and inviting a user
- Added example for joining a mentioned room
- Added example for listing out a user's or server's rooms
- Added example for getting session data from a user via whois info
- Added example for getting a room's user list
- Added example for downloading & storing received files/images
- Added example for kicking/banning user from a room.
- Added example for deactivating a user
- Removed message in the room-users config stating it only works if you are an admin
- Receive node now outputs `msg.filename` for files and images
2022-02-09 12:13:27 -07:00
Skylar Sadlier
ae386b90a6 Fix olm library reference. 2022-02-03 11:33:12 -07:00
Skylar Sadlier
4d1ec9e65c - Make it clear that manually generating a Device ID is bad behavior
- Make DeviceID no longer required for matrix-server-config
2022-02-03 10:36:09 -07:00
Skylar Sadlier
2fcd463eb6 - Update release to 0.4.0 2022-02-03 10:16:19 -07:00
Skylar Sadlier
b44629c84e - When checking authentication we should retry startup if the error we receive isn't related to invalid auth token (M_UNKNOWN_TOKEN) 2022-02-03 10:15:52 -07:00
Skylar Sadlier
9f0a962991 - Fixed: Reactions weren't working because sendCompleteEvent is a private method. 2022-02-03 09:40:32 -07:00
Skylar Sadlier
f3df8c6f97 This comment is no longer necessary since we implemented it. 2022-01-28 11:17:23 -07:00
Skylar Sadlier
d6ab098063 Fixed example readme linking to wrong json file Closes #38 2022-01-28 10:57:35 -07:00
Skylar Sadlier
614920c40c Fixed: matrix-join-room was requiring userId when it didn't need it Closes #39 2022-01-28 10:55:38 -07:00
Skylar Sadlier
58d7d23b2f - Device ID is now stored inside the matrix-client-storage directory within Node-RED's config folder. This way if you leave the Device ID blank it will automatically generate one and save it. Previously if you didn't set a Device ID it would automatically generate one every time the client started which would break encryption (since device_id is tied to your encryption keys).
- You can now generate an Access Token and Device ID from just a username and password (use the Update Access Token button when configuring a client).
- Updated the look of the matrix-server-config node (icons and reorganization)
- Invalid auth token will no longer crash Node-RED on deployment/start. We do an authorized request before starting the client to see if the auth token is valid. If it is not the client will not start and all nodes for this client will show disconnected and an error will display in the debug toolbar.
- Updated matrix olm library from 3.2.1 to 3.2.8
- Setting a Device Label from the server-config node will now update your device's label. If you view the sessions for the client you will see it gets updated after deployment.
2022-01-28 10:53:10 -07:00
Skylar Sadlier
25c92b787a - Messages used to get ignored if they were over 1 second old. This caused issues with messages being ignored on servers that took longer than a second to respond (*cough* matrix.org *cough*). Now it accepts all new messages. Moving forward any messages that are sent before Node-RED starts or gets deployed will be ignored (so that old messages don't pop up since the cache clears every deploy/node-red restart so old messages look like new ones).
- When the matrix-server node would shut down (from a deployment) it wasn't properly shutting the old instance of the client down. This would cause messages to start duplicating X times (X being however many times you deployed since you last started Node-RED). This is now fixed.
- Upgraded to matrix-js-sdk 15.3.0
- Message processing is now done on the matrix-server node. Before if you had multiple matrix-receive nodes it would output one line per matrix-receive node into the log. Now it only outputs from the matrix-server node. Note that all messages get logged that are sent to rooms the bot is in whereas before it would only log message in rooms the matrix-receive node was in.
- Fixed Shared secret registration failing if user_type was defined.
2022-01-14 12:56:14 -07:00
Skylar Sadlier
0b9c59a7be Fix eventId not being returned from matrix-send-file and matrix-send-message nodes 2021-12-19 00:34:51 -07:00
Skylar Sadlier
9c836c9262 Set version 0.2.6 2021-11-25 00:04:09 -07:00
Skylar Sadlier
9c62a73b15 Merge pull request #35 from Skylar-Tech/dev
Dev
2021-11-25 00:02:01 -07:00
Skylar Sadlier
447b18835c Closes #34 - Fix connection state logic 2021-11-25 00:00:31 -07:00
Skylar Sadlier
2341cbef10 Closes #32 - fixed leaking unencrypted messages during startup 2021-11-24 23:57:56 -07:00
Skylar Sadlier
73d802ff0d Merge pull request #27 from Skylar-Tech/dev
Release 0.2.4
2021-09-20 09:27:16 -06:00
Skylar Sadlier
cf1bd78ee1 Version 0.2.4
- Can now configure a room on the invite-room node (instead of having to pass it in on the msg)
- Changed how the connection event was handled by nodes
- matrix-synapse-join-room node was broken (was trying to access a variable that was never defined).
- olm library updated to 3.2.1 to hopefully fix "account.generate_fallback_key is not a function" Fixes #26
- Added description to the server configuration node that describes how to create a user for use with this module
- Update link to create room api for matrix-create-room.html
2021-09-20 09:24:40 -06:00
Skylar Sadlier
a10667a0e8 Update README.md
Add matrix room link for support
2021-09-03 12:07:17 -06:00
Skylar Sadlier
aaecaaec39 Formatted html message wasn't converting to a string. 2021-09-03 11:19:19 -06:00
Skylar Sadlier
be5416a68e guests query param for synapse user list request needs to be a string but our docs say it is a bool. Lets auto convert it so we don't cause a BC problem. 2021-09-03 10:16:49 -06:00
Skylar Sadlier
f8c5344a4e Update version to 0.2.1 2021-09-03 08:56:43 -06:00
Skylar Sadlier
3c99bf21a9 Merge pull request #23 from Skylar-Tech/dev
0.2.1
2021-09-03 08:56:02 -06:00
Skylar Sadlier
63728d86a5 Merge pull request #22 from Skylar-Tech/21-global-client
Global access to matrix client so user's can do whatever they want
2021-09-03 08:51:32 -06:00
Skylar Sadlier
ff66222687 Fix global variable for matrix client online status not setting to false when connection fails 2021-09-03 08:43:13 -06:00
Skylar Sadlier
fa92f79304 Move installing section of readme above usage 2021-09-03 08:28:27 -06:00
Skylar Sadlier
f70d73b011 - Fix typo in example json file name 2021-09-03 08:26:46 -06:00
Skylar Sadlier
6a310de708 Closes #21
- Can now access the matrix client globally so it can be used in function nodes (this way you are not limited by only the nodes we have published)
- Added example for using the Matrix Client in a function to redact messages. This should be a great example to show people what is possible.
2021-09-03 08:24:02 -06:00
Skylar Sadlier
d5519a6828 Merge pull request #19 from Skylar-Tech/dev
0.2.0 release
2021-09-02 19:32:49 -06:00
Skylar Sadlier
2cf8ba1e67 - Update readme so it references new storage location 2021-09-02 11:09:48 -06:00
Skylar Sadlier
9d080b6c1a Set version to 0.2.0 2021-09-02 11:00:19 -06:00
Skylar Sadlier
755e18da50 Merge pull request #18 from Skylar-Tech/9-fix-node-docs
9 fix node docs
2021-09-02 10:53:24 -06:00
Skylar Sadlier
4bd2dce434 - Docs for various nodes updated (closes #9)
- Fixed some nodes not matching how their docs describe how they function (such as node configuration should always take precedence over inputs)
2021-09-02 10:51:47 -06:00
Skylar Sadlier
b698389491 - matrix-create-room had wrong input information listed and wrong API reference URL (fixes #14)
- fixed docs & node for matrix-receive
2021-09-01 20:50:23 -06:00
Skylar Sadlier
14badaeeb5 - fs-extra added as a dependency to handle config folder upgrading
- config folder now upgrades automatically to the new structure
- fixed error preventing matrix client from connecting to servers (just stays disconnected)
2021-09-01 20:48:39 -06:00
Skylar Sadlier
ae7e766a98 Merge pull request #17 from Skylar-Tech/15-fix-storage
Split storage directory up based on User ID
2021-09-01 13:38:32 -06:00
Skylar Sadlier
daafe33e86 Fix clients using the same directory for local storage instead of being unique 2021-09-01 11:09:50 -06:00
76 changed files with 16373 additions and 4805 deletions

BIN
@matrix-org_olm-3.2.8.tgz Normal file

Binary file not shown.

View File

@@ -1,16 +1,20 @@
# node-red-contrib-matrix-chat
Matrix chat server client for [Node-RED](https://nodered.org/)
[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
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
@@ -22,17 +26,12 @@ 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)
Therefore, you can easily build a bot, chat relay, or administrate your Matrix server from within [Node-RED](https://nodered.org/).
### Usage
We have examples! [Check them out](https://github.com/Skylar-Tech/node-red-contrib-matrix-chat/tree/master/examples#readme)
### Installing
You can either install from within Node-RED by searching for `node-red-contrib-matrix-chat` or run this from within your Node-RED directory:
@@ -40,10 +39,18 @@ You can either install from within Node-RED by searching for `node-red-contrib-m
npm install node-red-contrib-matrix-chat
```
### 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.
### Usage
We have examples! [Check them out](https://github.com/Skylar-Tech/node-red-contrib-matrix-chat/tree/master/examples#readme)
This module stores a folder in your Node-RED directory called `matrix-local-storage` and is it vital that you periodically back this up if you are using e2ee. This is where the client stores all the keys necessary to decrypt messages and if lost you will lose access to e2e rooms. If you move your client to another NR install make sure to migrate this folder as well (and do not let both the old and new client run at same time).
#### 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.
This module stores a folder in your Node-RED directory called `matrix-client-storage` and is it vital that you periodically back this up if you are using e2ee. This is where the client stores all the keys necessary to decrypt messages and if lost you will lose access to e2e rooms. If you move your client to another NR install make sure to migrate this folder as well (and do not let both the old and new client run at same time).
Want to contribute? Any help on getting the last pieces of e2ee figured out would be greatly appreciated :)
@@ -61,4 +68,4 @@ You will need a user to use this module. Luckily this module comes with a node t
### Contributing
All contributions are welcome! If you do add a feature please do a pull request so that everyone benefits :)
Sharing is caring!
Sharing is caring!

View File

@@ -9,12 +9,24 @@ Build something cool with these nodes? Feel free to submit a pull request to sha
- [Create User with Shared Secret Registration](#create-user-with-shared-secret-registration)
- [Create/Edit Synapse User](#createedit-synapse-user)
- [Use function node to run any command](#use-function-node-to-run-any-command)
- [Respond to "ping" with "pong"](#respond-to-ping-with-pong)
- [Respond to "html" with an HTML message](#respond-to-html-with-an-html-message)
- [Respond to "image" with an uploaded image](#respond-to-image-with-an-uploaded-image)
- [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
@@ -27,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.
![img.png](shared-secret-registration.png)
![shared-secret-registration.png](shared-secret-registration.png)
### Create/Edit Synapse User
@@ -36,16 +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`.
![img.png](add-user-with-admin-user.png)
![add-user-with-admin-user.png](add-user-with-admin-user.png)
### 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 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).
![custom-redact-function-node.png](custom-redact-function-node.png)
### 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.
![img.png](respond-ping-pong.png)
![respond-ping-pong.png](respond-ping-pong.png)
@@ -55,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.
![img.png](respond-to-html-with-html.png)
![respond-to-html-with-html.png](respond-to-html-with-html.png)
@@ -65,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.
![img_1.png](respond-image-with-image.png)
![respond-image-with-image.png](respond-image-with-image.png)
@@ -75,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.
![img.png](respond-file-with-file.png)
![respond-file-with-file.png](respond-file-with-file.png)
@@ -85,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"
![img.png](respond-react-with-reaction.png)
![respond-react-with-reaction.png](respond-react-with-reaction.png)
### Accept room invites from specific user
[View JSON](accept-room-invites.json)
Accept room invites from specific user.
![accept-room-invites.png](accept-room-invites.png)
### Leave room when someone says bye
[View JSON](leave-room-bye.json)
Leave room when someone says "bye".
![leave-room-bye.png](leave-room-bye.png)
### Remove messages containing "delete"
[View JSON](delete-event.json)
Any messages containing "delete" will try to be removed by the client.
![respond-react-with-reaction.png](delete-event.png)
@@ -93,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.
![img.png](respond-users-list.png)
![respond-users-list.png](respond-users-list.png)
### 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-newroom-invite.png](respond-newroom-invite.png)
### 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-joinroom.png](respond-joinroom.png)
### 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-rooms.png](respond-rooms.png)
### 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-whois.png](respond-whois.png)
### 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.
![respond-room-users.png](respond-room-users.png)
### 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.
![store-received-files.png](store-received-files.png)
### 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.
![room-kick-ban.png](room-kick-ban.png)
### 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.
![room-kick-ban.png](deactivate-user.png)
### 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.
![room-kick-ban.png](force-join-room.png)

View 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
}
]

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

View File

@@ -0,0 +1,113 @@
[
{
"id": "89eed7ddf7a96070",
"type": "function",
"z": "f025a8b9fbd1b054",
"name": "Redact Message",
"func": "let matrixClient = global.get(\"matrixClient['@bot:example.com']\"),\n matrixOnline = global.get(\"matrixClientOnline['@bot:example.com']\");\n\nif(!matrixOnline) {\n msg.error = 'Matrix client not connected.';\n return [null, msg];\n}\n \nmatrixClient.redactEvent(msg.topic, msg.eventId, undefined, {\n reason: 'Redacted, muhahaha!'\n})\n .then(function(e) {\n msg.eventId = e.eventId;\n node.send([msg, null]);\n })\n .catch(function(e){\n node.warn(\"Error sending message \" + e);\n msg.error = e;\n node.send([null, msg]);\n });",
"outputs": 2,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 730,
"y": 1580,
"wires": [
[
"5decd492e2da79d9"
],
[
"fadcf8c32bdfd8be"
]
]
},
{
"id": "5decd492e2da79d9",
"type": "debug",
"z": "f025a8b9fbd1b054",
"name": "",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "true",
"targetType": "full",
"statusVal": "",
"statusType": "auto",
"x": 890,
"y": 1560,
"wires": []
},
{
"id": "f3636f4221511a81",
"type": "comment",
"z": "f025a8b9fbd1b054",
"name": "If message is \"redact\" then redact the message.",
"info": "",
"x": 420,
"y": 1540,
"wires": []
},
{
"id": "9f777828840bb9a2",
"type": "matrix-receive",
"z": "f025a8b9fbd1b054",
"name": "",
"roomId": "",
"acceptText": true,
"acceptEmotes": false,
"acceptStickers": false,
"acceptReactions": false,
"acceptFiles": false,
"acceptImages": false,
"x": 320,
"y": 1580,
"wires": [
[
"21a00b09f8c85399"
]
]
},
{
"id": "21a00b09f8c85399",
"type": "switch",
"z": "f025a8b9fbd1b054",
"name": "filter on msg.payload",
"property": "payload",
"propertyType": "msg",
"rules": [
{
"t": "eq",
"v": "redact",
"vt": "str"
}
],
"checkall": "true",
"repair": false,
"outputs": 1,
"x": 520,
"y": 1580,
"wires": [
[
"89eed7ddf7a96070"
]
]
},
{
"id": "fadcf8c32bdfd8be",
"type": "debug",
"z": "f025a8b9fbd1b054",
"name": "",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "true",
"targetType": "full",
"statusVal": "",
"statusType": "auto",
"x": 890,
"y": 1600,
"wires": []
}
]

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

View 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": [
[],
[]
]
}
]

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View 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": [
[],
[]
]
}
]

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View 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": [
[],
[]
]
}
]

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

View 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": []
}
]

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

View 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": []
}
]

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

151
examples/respond-rooms.json Normal file
View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

View File

@@ -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": []
}
]

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 44 KiB

153
examples/respond-whois.json Normal file
View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

211
examples/room-kick-ban.json Normal file
View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

View 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"
]
]
}
]

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

16856
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,26 +1,34 @@
{
"name": "node-red-contrib-matrix-chat",
"version": "0.1.8",
"version": "0.7.0",
"description": "Matrix chat server client for Node-RED",
"dependencies": {
"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",
"olm": "https://packages.matrix.org/npm/olm/olm-3.1.4.tgz",
"utf8": "^3.0.0",
"node-localstorage": "^2.2.1"
"matrix-js-sdk": "^22.0.0",
"node-fetch": "^3.3.0",
"node-localstorage": "^2.2.1",
"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",
@@ -33,12 +41,15 @@
"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",

View 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>
@@ -27,30 +27,19 @@
<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-tips" style="margin-bottom: 12px;">
User must be an admin to use this endpoint.
</div>
</script>
<script type="text/html" data-help-name="matrix-create-room">
<h3>Details</h3>
<p>
Create a room with the defined options in <code>msg.payload</code>
</p>
<a href="https://matrix-org.github.io/synapse/develop/admin_api/room_membership.html#edit-room-membership-api" target="_blank">Synapse API Endpoint Information</a>
<p>Create a Matrix room</p>
<a href="https://matrix.org/docs/spec/client_server/r0.4.0#id266" target="_blank">Matrix API Endpoint Information</a>
<h3>Inputs</h3>
<dl class="message-properties">
<dt>msg.payload
<span class="property-type">string</span>
<dt class="optional">msg.payload
<span class="property-type">string | object | null</span>
</dt>
<dd> a list of options to pass to the /createRoom API. <a href="https://matrix.org/docs/spec/client_server/r0.4.0#id266">Click here</a> for information about what can be passed.</dd>
<dt>msg.userId
<span class="property-type">string</span>
</dt>
<dd> User's ID that will be set into the room.</dd>
<dd> a list of options to pass to the /createRoom API. <a href="https://matrix.org/docs/spec/client_server/latest#post-matrix-client-r0-createroom">Click here</a> for information about what can be passed. If this is a string it just sets the room name and ignores other options.</dd>
</dl>
<h3>Outputs</h3>

View File

@@ -53,7 +53,6 @@ module.exports = function(RED) {
};
}
// we need the status code, so set onlydata to false for this request
node.server.matrixClient
.createRoom(msg.payload || {})
.then(function(e){

View File

@@ -18,29 +18,29 @@
<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 encryted file (required to decrypt)</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">
<dt>msg.content
<span class="property-type">Object</span>
<span class="property-type">object</span>
</dt>
<dd> content of the decrypted message</dd>
<dt>msg.content.file
<span class="property-type">Object</span>
<span class="property-type">object</span>
</dt>
<dd> the information needed to decode the file</dd>
<dt>msg.url
<span class="property-type">String | Null</span>
<span class="property-type">string | null</span>
</dt>
<dd> the decoded mxc url.</dd>
</dl>
@@ -57,6 +57,9 @@
<dt>msg.filename <span class="property-type">string</span></dt>
<dd>filename of the decoded file (if content.filename isn't defined on the message we fallback to content.body).</dd>
<dt>msg.thumbnail_payload <span class="property-type">buffer</span></dt>
<dd>If the file is an image then this property is set to the buffer of the thumbnail. If not an image this is left off.</dd>
</dl>
</li>
<li>Error

View File

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

View 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>

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

View File

@@ -8,18 +8,19 @@
outputs: 2,
defaults: {
name: { value: null },
server: { value: "", type: "matrix-server-config" }
server: { value: "", type: "matrix-server-config" },
roomId: { value: null },
},
label: function() {
return this.name || "Room Invite";
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>
@@ -27,12 +28,29 @@
<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>
<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">
<h3>Details</h3>
<p>
This API invites a user to participate in a particular room. They do not start participating in the room until they actually join the room.
This node invites a user to participate in a particular room. They do not start participating in the room until they actually join the room. The client must be in the room and have permissions to invite.
</p>
<a href="https://matrix-org.github.io/synapse/develop/admin_api/room_membership.html#edit-room-membership-api" target="_blank">Synapse API Endpoint Information</a>
@@ -41,7 +59,7 @@
<dt>msg.topic
<span class="property-type">string</span>
</dt>
<dd> The room identifier to invite to: for example, <code>!h8zld9j31:example.com. If configured on the node it overrides this input and is no longer required.</code>.</dd>
<dd> The room identifier to invite to: for example, <code>!h8zld9j31:example.com</code>. Ignored if configured on the node, otherwise required.</dd>
<dt>msg.userId
<span class="property-type">string</span>

View File

@@ -6,6 +6,7 @@ module.exports = function(RED) {
this.name = n.name;
this.server = RED.nodes.getNode(n.server);
this.roomId = n.roomId;
if(!this.server) {
node.error('Server must be configured on the node.');
@@ -47,7 +48,7 @@ module.exports = function(RED) {
msg.topic = node.roomId || msg.topic;
if(!msg.topic) {
node.error("msg.topic must be defined or configured on the node.");
node.error("room must be defined in either msg.topic or in node config");
return;
}
@@ -58,7 +59,7 @@ module.exports = function(RED) {
msg.payload = e;
node.send([msg, null]);
}).catch(function(e){
node.warn("Error creating room " + e);
node.warn("Error inviting to room " + e);
msg.error = e;
node.send([null, msg]);
});

View File

@@ -19,32 +19,29 @@
<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">
<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-tips">
Room ID must either be defined here or passed in via <code>msg.topic</code>. The config takes precedence over the input.
</div>
</script>
<script type="text/html" data-help-name="matrix-join-room">
<h3>Details</h3>
<p>This API starts a user participating in a particular room, if that user is allowed to participate in that room. After this call, the client is allowed to see all current state events in the room, and all subsequent events associated with the room until the user leaves the room.</p>
<p>This node starts the client participating in a particular room, if the client is allowed to participate in that room. After this call, the client is allowed to see all current state events in the room, and all subsequent events associated with the room until the user leaves the room.</p>
<a href="https://matrix.org/docs/spec/client_server/latest#id291" target="_blank">Matrix API Endpoint Information</a>
<h3>Inputs</h3>
<dl class="message-properties">
<dt>msg.topic
<span class="property-type">String</span>
<span class="property-type">string</span>
</dt>
<dd> The room identifier or alias to join.</dd>
<dt class="optional">msg.joinOpts
<span class="property-type">Object</span>
<span class="property-type">object | null</span>
</dt>
<dd> Extra options that can be passed when joining a room.</dd>
@@ -62,11 +59,6 @@
<span class="property-type">[string]</span>
</dt>
<dd> The servers to attempt to join the room through. One of the servers must be participating in the room.</dd>
<dt class="optional">msg.reason
<span class="property-type">String</span>
</dt>
<dd> Reason for kicking the user.</dd>
</dl>
<h3>Outputs</h3>

View File

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

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

View File

@@ -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,35 +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>
<input type="text" id="node-input-roomId">
<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-messageType">
Message Type
</label>
<select id="node-input-messageType">
<option value="m.text">m.text</option>
<option value="m.notice">m.notice</option>
<option value="msg.type">msg.type input</option>
</select>
<div class="form-tips">
It's recommended to use m.notice for bots because the message will render in a lighter text (at least in Element client) for users to distinguish bot and real user messages.
</div>
</div>
<div class="form-row">
<label for="node-input-messageFormat">
Message Format
</label>
<select id="node-input-messageFormat">
<option value="">Default (plaintext)</option>
<option value="html">HTML</option>
<option value="msg.format">msg.format input</option>
</select>
</div>
<div class="form-tips">
Must be a valid <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types" target="_blank">MIME Type</a>
<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">
@@ -66,18 +57,18 @@
<h3>Inputs</h3>
<dl class="message-properties">
<dt>msg.payload
<span class="property-type">String</span>
<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>
<span class="property-type">string | null</span>
</dt>
<dd> Room ID to send image to. Optional if configured on the node. If configured on the node this will be ignored.</dd>
<dt>msg.referenceEventId<br />
msg.eventId
<span class="property-type">String</span>
<span class="property-type">string</span>
</dt>
<dd> One of these is required. This is the eventId of the message to react to. Uses <code>msg.referenceEventId</code> first and falls back to <code>msg.eventId</code>.</dd>
</dl>
@@ -86,8 +77,6 @@
<ol class="node-ports">
<li>Success
<dl class="message-properties">
<dd>original msg object preserved.</dd>
<dt>msg.eventId <span class="property-type">string</span></dt>
<dd>the eventId from the posted reaction.</dd>
</dl>

View File

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

View File

@@ -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,16 +112,47 @@
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">
<p>Receive events from Matrix.</p>
<h3>Outputs</h3>
<ol class="node-ports">
<ul class="node-ports">
<li>Always Returned
<dl class="message-properties">
<dt>msg.type <span class="property-type">string</span></dt>
@@ -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>
@@ -146,6 +196,11 @@
<dd>the event object returned by the Matrix server</dd>
</dl>
<dl class="message-properties">
<dt>msg.eventId <span class="property-type">object</span></dt>
<dd>The event ID, e.g. $143350589368169JsLZx:localhost</dd>
</dl>
<dl class="message-properties">
<dt>msg.content <span class="property-type">object</span></dt>
<dd>the message's content object</dd>
@@ -159,11 +214,6 @@
</li>
<li><code>msg.type</code> == '<strong>m.reaction</strong>'
<dl class="message-properties">
<dt>msg.info <span class="property-type">object</span></dt>
<dd>the content's info.</dd>
</dl>
<dl class="message-properties">
<dt>msg.referenceEventId <span class="property-type">string</span></dt>
<dd>the message that the reaction relates to</dd>
@@ -187,39 +237,103 @@
<dd>URL to the sticker image</dd>
</dl>
<dl class="message-properties">
<dt>msg.mxc_url <span class="property-type">string</span></dt>
<dd>Matrix URL to the sticker image</dd>
</dl>
<dl class="message-properties">
<dt>msg.thumbnail_url <span class="property-type">string</span></dt>
<dd>URL to the thumbnail of the sticker</dd>
</dl>
<dl class="message-properties">
<dt>msg.thumbnail_mxc_url <span class="property-type">string</span></dt>
<dd>Matrix URL to the thumbnail of the sticker</dd>
</dl>
</li>
<li><code>msg.type</code> == '<strong>m.file</strong>'
<dl class="message-properties">
<dt>msg.file.info <span class="property-type">string</span></dt>
<dd>the content's info.</dd>
<dt>msg.filename <span class="property-type">string</span></dt>
<dd>the file's parsed filename</dd>
</dl>
<dl class="message-properties">
<dt>msg.file.url <span class="property-type">string</span></dt>
<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>
</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.image.info <span class="property-type">string</span></dt>
<dd>the image info.</dd>
<dt>msg.filename <span class="property-type">string</span></dt>
<dd>the image's parsed filename</dd>
</dl>
<dl class="message-properties">
<dt>msg.file.url <span class="property-type">string</span></dt>
<dt>msg.url <span class="property-type">string</span></dt>
<dd>the image's URL</dd>
</dl>
<dl class="message-properties">
<dt>msg.file.thumbnail_url <span class="property-type">string</span></dt>
<dt>msg.mxc_url <span class="property-type">string</span></dt>
<dd>the image's Matrix URL</dd>
</dl>
<dl class="message-properties">
<dt>msg.thumbnail_url <span class="property-type">string</span></dt>
<dd>the image's thumbnail URL</dd>
</dl>
<dl class="message-properties">
<dt>msg.thumbnail_mxc_url <span class="property-type">string</span></dt>
<dd>the image's thumbnail Matrix URL</dd>
</dl>
</li>
</ol>
<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>

View File

@@ -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;
@@ -81,16 +53,19 @@ module.exports = function(RED) {
if(msg.content.info) {
if(msg.content.info.thumbnail_url) {
msg.thumbnail_url = node.server.matrixClient.mxcUrlToHttp(msg.content.info.thumbnail_url);
msg.thumbnail_mxc_url = msg.content.info.thumbnail_url;
}
if(msg.content.url) {
msg.url = node.server.matrixClient.mxcUrlToHttp(msg.content.url);
msg.mxc_url = msg.content.url;
}
}
break;
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;
@@ -100,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;
@@ -116,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;

View File

@@ -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,12 +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-tips">
Room ID must either be defined here or passed in via <code>msg.topic</code>. The config takes precedence over the input.
<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">
@@ -42,27 +56,28 @@
<h3>Inputs</h3>
<dl class="message-properties">
<dt class="optional">msg.userId
<span class="property-type">String</span>
<dt>msg.userId
<span class="property-type">string</span>
</dt>
<dd> The ID of the user to ban.</dd>
<dt class="optional">msg.topic
<span class="property-type">String | Null</span>
<span class="property-type">string | null</span>
</dt>
<dd> The room to ban the user from. Required if not defined on the config. Config takes precedence.</dd>
<dd> The room to ban the user from. Ignored if configured on the node, otherwise required.</dd>
<dt class="optional">msg.reason
<span class="property-type">String</span>
<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>
<ol class="node-ports">
<li>Success
<dl class="message-properties">
<dd>original msg object preserved. Node doesn't set anything.</dd>
<dt>msg.eventId <span class="property-type">string</span></dt>
<dd>the eventId from the ban event.</dd>
</dl>
</li>
<li>Error

View File

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

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

View File

@@ -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,12 +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-tips">
Room ID must either be defined here or passed in via <code>msg.topic</code>. The config takes precedence over the input.
<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">
@@ -43,26 +57,27 @@
<h3>Inputs</h3>
<dl class="message-properties">
<dt>msg.userId
<span class="property-type">String</span>
<span class="property-type">string</span>
</dt>
<dd> The ID of the user to kick.</dd>
<dt class="optional">msg.topic
<span class="property-type">String | Null</span>
<span class="property-type">string | null</span>
</dt>
<dd> The room to kick the user from. Required if not defined on the config. Config takes precedence.</dd>
<dd> The room to kick the user from. Ignored if configured on the node, otherwise required.</dd>
<dt class="optional">msg.reason
<span class="property-type">String</span>
<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>
<ol class="node-ports">
<li>Success
<dl class="message-properties">
<dd>original msg object preserved. Node doesn't set anything.</dd>
<dt>msg.eventId <span class="property-type">string</span></dt>
<dd>the eventId from the kick event.</dd>
</dl>
</li>
<li>Error

View File

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

View File

@@ -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,23 +30,32 @@
<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">
<h3>Details</h3>
<p>A list of members of the room. If you are joined to the room then this will be the current members of the room. If you have left the room then this will be the members of the room when you left.</p>
<p>This node returns a map of MXIDs to member info objects for members of the room. The current user must be in the room for it to work, unless it is an Application Service in which case any of the AS's users must be in the room. This API is primarily for Application Services and should be faster to respond than /members as it can be implemented more efficiently on the server.</p>
<a href="https://matrix.org/docs/spec/client_server/latest#get-matrix-client-r0-rooms-roomid-joined-members" target="_blank">Matrix API Endpoint Information</a>
<h3>Inputs</h3>
<dl class="message-properties">
<dt class="optional">msg.topic
<span class="property-type">Integer</span>
<span class="property-type">integer</span>
</dt>
<dd> Room ID to get member list from. Required if not configured on the node.</dd>
<dd> Room ID to get member list from. Ignored if configured on the node, otherwise it's required.</dd>
</dl>
<h3>Outputs</h3>

View File

@@ -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,16 +29,28 @@
<input type="text" id="node-input-server">
</div>
<div class="form-row">
<label for="node-input-roomId"><i class="fa fa-user"></i> Room ID</label>
<input type="text" id="node-input-roomId">
<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>
<input type="text" id="node-input-contentType" placeholder="application/pdf">
<input type="text" id="node-input-contentType" placeholder="msg.contentType">
</div>
<div class="form-tips">
Must be a valid <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types" target="_blank">MIME Type</a>
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">
@@ -48,36 +60,42 @@
<h3>Inputs</h3>
<dl class="message-properties">
<dt>msg.payload
<span class="property-type">File | String | Buffer | ReadStream | Blob</span>
<span class="property-type">File | string | Buffer | ReadStream | Blob</span>
</dt>
<dd> the contents of the file to upload. </dd>
<dt>msg.topic
<span class="property-type">String | Null</span>
<span class="property-type">string</span>
</dt>
<dd> Room ID to send file to. Optional if configured on the node. Overrides node configuration if set.</dd>
<dd> Room ID to send file to. Ignored if configured on the node, otherwise required.</dd>
<dt class="optional">msg.filename
<span class="property-type">String | Null</span>
<span class="property-type">string | null</span>
</dt>
<dd> name of the file to upload (optional). Overrides node configuration.</dd>
<dt class="optional">msg.contentType
<span class="property-type">String | Null</span>
</dt>
<dd> Content <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types" target="_blank">MIME Type</a>. Optional if configured on the node. Overrides node configuration if set.</dd>
<dd> name of the file to upload (optional). Note: If this is not defined the file will receive a randomly generated name (without an extension).</dd>
<dt class="optional">msg.body
<span class="property-type">String | Null</span>
<span class="property-type">string | null</span>
</dt>
<dd> this will be the display name the client will see when rendered in their chat client. If this is left empty the it uses <code>msg.filename</code>. If <code>msg.filename</code> is also undefined it sets it to empty string</dd>
<dd> this will be the display name the client will see when rendered in their chat client. If this is left empty it will just display as "Attachment".</dd>
<dt class="optional">msg.contentType
<span class="property-type">string | null</span>
</dt>
<dd> Content <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types" target="_blank">MIME Type</a>. Optional but highly recommended (so the client receiving knows what type of file it is).</dd>
<dt class="optional">msg.content
<span class="property-type">object | null</span>
</dt>
<dd> craft your own msg.content to send to the server. If defined then <code>msg.filename</code>, <code>msg.contentType</code>, <code>msg.body</code>, and <code>msg.payload</code> aren't required since the file already exists.</dd>
</dl>
<h3>Outputs</h3>
<ol class="node-ports">
<li>Success
<dl class="message-properties">
<dd>original msg object preserved.</dd>
<dt>msg.eventId <span class="property-type">string</span></dt>
<dd>the eventId from the posted message.</dd>
</dl>
</li>
<li>Error

View File

@@ -61,17 +61,12 @@ module.exports = function(RED) {
return;
}
msg.contentType = msg.contentType || node.contentType;
if(!msg.contentType) {
node.error('msg.contentType is required');
return;
}
msg.contentType = node.contentType || msg.contentType || null;
node.log("Uploading file " + msg.filename);
node.server.matrixClient.uploadContent(
msg.payload, {
name: msg.filename || null, // Name to give the file on the server.
rawResponse: (msg.rawResponse || false), // Return the raw body, rather than parsing the JSON.
rawResponse: false, // Return the raw body, rather than parsing the JSON.
type: msg.contentType, // Content-type for the upload. Defaults to file.type, or applicaton/octet-stream.
onlyContentUri: false // Just return the content URI, rather than the whole body. Defaults to false. Ignored if opts.rawResponse is true.
})
@@ -79,18 +74,18 @@ module.exports = function(RED) {
const content = {
msgtype: 'm.file',
url: file.content_uri,
body: (msg.body || msg.filename) || "",
body: msg.body || "",
};
node.server.matrixClient
.sendMessage(msg.topic, content)
.then(function(imgResp) {
node.log("File message sent: " + imgResp);
msg.eventId = e.eventId;
.then(function(e) {
node.log("File message sent: " + e);
msg.eventId = e.event_id;
node.send([msg, null]);
})
.catch(function(e){
node.warn("Error sending file message " + e);
msg.matrixError = e;
msg.error = e;
node.send([null, msg]);
});
}).catch(function(e){

View File

@@ -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,51 +29,63 @@
<input type="text" id="node-input-server">
</div>
<div class="form-row">
<label for="node-input-roomId"><i class="fa fa-user"></i> Room ID</label>
<input type="text" id="node-input-roomId">
<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>
<input type="text" id="node-input-contentType" placeholder="application/png">
<input type="text" id="node-input-contentType" placeholder="msg.contentType">
</div>
<div class="form-tips">
Must be a valid <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types" target="_blank">MIME Type</a>
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. You can link this directly 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">
<dt>msg.payload
<span class="property-type">File | String | Buffer | ReadStream | Blob</span>
<span class="property-type">File | string | Buffer | ReadStream | Blob</span>
</dt>
<dd> the contents of the image to upload. </dd>
<dt>msg.topic
<span class="property-type">String | Null</span>
<span class="property-type">string</span>
</dt>
<dd> Room ID to send image to. Optional if configured on the node. Overrides node configuration if set.</dd>
<dd> Room ID to send image to. Ignored if configured on the node, otherwise required.</dd>
<dt class="optional">msg.filename
<span class="property-type">String | Null</span>
<span class="property-type">string | null</span>
</dt>
<dd> name of the image file to upload (optional). Overrides node configuration.</dd>
<dt class="optional">msg.contentType
<span class="property-type">String | Null</span>
</dt>
<dd> Content <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types" target="_blank">MIME Type</a>. Optional if configured on the node. Overrides node configuration if set.</dd>
<dd> name of the image to upload (optional). Note: If this is not defined the file will receive a randomly generated name (without an extension).</dd>
<dt class="optional">msg.body
<span class="property-type">String | Null</span>
<span class="property-type">string | null</span>
</dt>
<dd> this will be the display name the client will see when rendered in their chat client. If this is left empty the it uses <code>msg.filename</code>. If <code>msg.filename</code> is also undefined it sets it to empty string</dd>
<dd> this will be the display name the client will see when rendered in their chat client. If this is left empty it will just display as "Attachment".</dd>
<dt class="optional">msg.contentType
<span class="property-type">string | null</span>
</dt>
<dd> Content <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types" target="_blank">MIME Type</a>. Overridden if configured on the node. Optional but highly recommended (so the client receiving knows what type of file it is).</dd>
<dt class="optional">msg.content
<span class="property-type">Object | Null</span>
<span class="property-type">object | null</span>
</dt>
<dd> craft your own msg.content to send to the server. If defined then <code>msg.filename</code>, <code>msg.contentType</code>, <code>msg.body</code>, and <code>msg.payload</code> aren't required since the file already exists.</dd>
</dl>
@@ -82,8 +94,6 @@
<ol class="node-ports">
<li>Success
<dl class="message-properties">
<dd>original msg object preserved.</dd>
<dt>msg.eventId <span class="property-type">string</span></dt>
<dd>the eventId from the posted message.</dd>
</dl>

View File

@@ -61,37 +61,40 @@ module.exports = function(RED) {
return;
}
msg.contentType = msg.contentType || node.contentType;
if(!msg.contentType) {
node.error('msg.contentType is required');
return;
}
msg.contentType = node.contentType || msg.contentType || null;
node.log("Uploading image " + msg.filename);
node.server.matrixClient.uploadContent(
msg.payload, {
name: msg.filename || null, // Name to give the file on the server.
rawResponse: (msg.rawResponse || false), // Return the raw body, rather than parsing the JSON.
rawResponse: false, // Return the raw body, rather than parsing the JSON.
type: msg.contentType, // Content-type for the upload. Defaults to file.type, or applicaton/octet-stream.
onlyContentUri: false // Just return the content URI, rather than the whole body. Defaults to false. Ignored if opts.rawResponse is true.
})
.then(function(file){
node.server.matrixClient
.sendImageMessage(msg.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);
msg.matrixError = e;
node.warn("Error uploading image message: " + e);
msg.error = e;
node.send([null, msg]);
});
});

View File

@@ -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,17 +24,37 @@
<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>
<div class="form-row">
<label for="node-input-server"><i class="fa fa-user"></i> Matrix Server Config</label>
<input type="text" id="node-input-server">
</div>
<div class="form-row">
<label for="node-input-roomId"><i class="fa fa-user"></i> Room ID</label>
<input type="text" id="node-input-roomId">
<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">
<label for="node-input-messageType">
Message Type
@@ -42,10 +64,11 @@
<option value="m.notice">m.notice</option>
<option value="msg.type">msg.type input</option>
</select>
<div class="form-tips">
It's recommended to use m.notice for bots because the message will render in a lighter text (at least in Element client) for users to distinguish bot and real user messages.
</div>
</div>
<div class="form-row form-tips">
It's recommended to use m.notice for bots because the message will render in a lighter text (at least in Element client) for users to distinguish bot and real user messages.
</div>
<div class="form-row">
<label for="node-input-messageFormat">
Message Format
@@ -56,49 +79,60 @@
<option value="msg.format">msg.format input</option>
</select>
</div>
<div class="form-tips">
Must be a valid <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types" target="_blank">MIME Type</a>
</div>
<script 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">
<h3>Details</h3>
<p>Sends a message to a Matrix room.</p>
<p>Sends a message to a Matrix room. Messages are auto encrypted if necessary.</p>
<h3>Inputs</h3>
<dl class="message-properties">
<dt>msg.payload
<span class="property-type">String</span>
<dt>msg.topic
<span class="property-type">string</span>
</dt>
<dd> the message text. </dd>
<dd> Room ID to send image to. Optional if configured on the node. If configured on the node this input will be overridden.</dd>
<dt>msg.payload
<span class="property-type">string</span>
</dt>
<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>
<span class="property-type">string</span>
</dt>
<dd> the formatted HTML message (uses msg.payload if not defined). This only affects HTML messages.</dd>
<dt>msg.topic
<span class="property-type">String | Null</span>
</dt>
<dd> Room ID to send image to. Optional if configured on the node. Overrides node configuration if set.</dd>
<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>
<span class="property-type">string | null</span>
</dt>
<dd> This is only used and required when configured so on the node. Must be set to either 'm.text' or 'm.notice'</dd>
<dd> This is only used and required when configured so on the node. Must be set to either <code>'m.text'</code> or <code>'m.notice'</code></dd>
<dt class="optional">msg.format
<span class="property-type">String | Null</span>
<span class="property-type">string | null</span>
</dt>
<dd> This is only used and required when configured so on the node. Set to null for plain text and 'html' for HTML.</dd>
<dd> This is only used and required when configured so on the node. Set to <code>null</code> for plain text and <code>'html'</code> for HTML.</dd>
</dl>
<h3>Outputs</h3>
<ol class="node-ports">
<li>Success
<dl class="message-properties">
<dd>original msg object preserved.</dd>
<dt>msg.eventId <span class="property-type">string</span></dt>
<dd>the eventId from the posted message.</dd>
</dl>

View File

@@ -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,25 +102,48 @@ 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()
};
if(msgFormat === 'html') {
content.format = "org.matrix.custom.html";
content.formatted_body = msg.formatted_payload || msg.payload;
content.formatted_body =
(typeof msg.formatted_payload !== 'undefined' && msg.formatted_payload)
? msg.formatted_payload.toString()
: 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){

View File

@@ -1,17 +1,42 @@
<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 },
autoAcceptRoomInvites: { value: true },
enableE2ee: { type: "checkbox", value: true }
enableE2ee: { type: "checkbox", value: true },
global: { type: "checkbox", value: true }
},
icon: "matrix.png",
label: function() {
@@ -25,35 +50,58 @@
<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"
id="node-config-input-autoAcceptRoomInvites"
style="width: auto; margin-left: 125px; vertical-align: top"
/>
<label for="node-config-input-autoAcceptRoomInvites" style="width: auto">
<label for="node-config-input-autoAcceptRoomInvites" style="width: auto;max-width:50%;">
Auto join invited rooms
</label>
</div>
@@ -63,17 +111,107 @@
id="node-config-input-enableE2ee"
style="width: auto; margin-left: 125px; vertical-align: top"
/>
<label for="node-config-input-enableE2ee" style="width: auto">
Enable end-to-end encryption (requires DeviceID)
<label for="node-config-input-enableE2ee" style="width: auto;max-width:50%;">
Enable end-to-end encryption
</label>
</div>
<div class="form-tips" style="margin-bottom: 12px;">
E2ee requires a Device ID to be set.
<div class="form-row">
<input
type="checkbox"
id="node-config-input-global"
style="width: auto; margin-left: 125px; vertical-align: top"
/>
<label for="node-config-input-global" style="width: auto">
Global access to Matrix Client
</label>
<div class="form-tips" style="margin-bottom: 12px;">
If 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">
<h3>Details</h3>
<p>Matrix client configuration node</p>
</script>
<p>Matrix client connection configuration</p>
<h3>Setting up an account</h3>
<div>
<p>
You need an account for your client to use. If you are going to be using End-to-End Encryption you should generate the bot and only use it within Node-RED otherwise if you have other clients connected on the same user it could cause problems with e2ee (key sharing is currently not supported).
</p>
<p>If you have access to the server directly you can use Shared Secret Registration as described <a href="https://github.com/Skylar-Tech/node-red-contrib-matrix-chat/tree/master/examples#create-user-with-shared-secret-registration" target="_blank" style="text-decoration: underline;">here</a>.</p>
<p>If this is a server you do not administrate/have access to follow these instructions:</p>
<ol><li>In a private/incognito browser window, open Element.</li><li>Log in to the account you want to get the access token for, such as the bot's account. <strong>Do not setup key storage</strong>.</li><li>Click on the bot's name in the top left corner then "Settings".</li><li>(Optional) Set your bot's display name and avatar.</li><li>Click the "Help &amp; About" tab (left side of the dialog).</li><li>Scroll to the bottom and click the <code>&lt;click to reveal&gt;</code> part of <code>Access Token: &lt;click to reveal&gt;</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>

View File

@@ -1,81 +1,240 @@
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) {
const sdk = require("matrix-js-sdk");
const { LocalStorage } = require('node-localstorage');
const localStorage = new LocalStorage('./matrix-local-storage');
const { LocalStorageCryptoStore } = require('matrix-js-sdk/lib/crypto/store/localStorage-crypto-store');
// 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) {
// 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 = false;
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 = async function(connected, cb) {
if (node.connected !== connected) {
node.connected = connected;
if(typeof cb === 'function') {
cb(connected);
}
if (connected) {
node.log("Matrix server connection ready.");
node.emit("connected");
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");
}
if(this.globalAccess) {
this.context().global.set('matrixClientOnline["'+this.userId+'"]', connected);
}
}
};
node.setConnected(false);
fs.ensureDirSync(storageDir); // create storage directory if it doesn't exist
upgradeDirectoryIfNecessary(node, storageDir);
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"]
});
node.on('close', function(done) {
if(node.matrixClient) {
node.matrixClient.close();
// set globally if configured to do so
if(this.globalAccess) {
this.context().global.set('matrixClient["'+this.userId+'"]', node.matrixClient);
}
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();
});
node.setConnected = function(connected) {
if (node.connected !== connected) {
node.connected = connected;
if (connected) {
node.log("Matrix server connection ready.");
node.emit("connected");
} else {
node.emit("disconnected");
}
}
};
node.isConnected = function() {
return node.connected;
};
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;
@@ -93,48 +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.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");
node.setConnected(false);
break;
case "RECONNECTING":
case "STOPPED":
node.setConnected(false);
break;
case "SYNCING":
break;
case "PREPARED":
node.setConnected(true);
break;
// case "PREPARED":
// // the client instance is ready to be queried.
// node.log("Matrix server connection ready.");
// node.setConnected(true);
// break;
});
} 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',
@@ -146,39 +362,176 @@ module.exports = function(RED) {
// httpStatus: 401
// }
node.error("[Session.logged_out] " + errorObj);
node.error("Authentication failure: " + errorObj);
stopClient();
});
async function run() {
if(node.e2ee){
const initCrypto = ms => new Promise(res => node.matrixClient.initCrypto());
try {
await initCrypto();
} catch(error){
node.error(error);
}
node.matrixClient.setGlobalErrorOnUnknownDevices(false);
}
const startClient = ms => new Promise(res => node.matrixClient.startClient({ initialSyncLimit: 8 }));
try {
await startClient();
} catch(error){
if(node.e2ee){
node.log("Initializing crypto...");
await node.matrixClient.initCrypto();
node.matrixClient.setGlobalErrorOnUnknownDevices(false);
}
node.log("Connecting to Matrix server...");
await node.matrixClient.startClient({
initialSyncLimit: 8
});
} catch(error) {
node.error(error);
}
}
node.log("Connecting to Matrix server...");
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 }
}
});
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;
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 {
if(n.type !== 'matrix-server-config') return;
let { userId } = RED.nodes.getCredentials(n.id);
let dir = storageDir + '/' + MatrixFolderNameFromUserId(userId);
if(!fs.pathExistsSync(dir)) {
fs.ensureDirSync(dir);
node.log("found old '" + oldStorageDir + "' path, copying to new location '" + dir);
fs.copySync(oldStorageDir, dir);
}
} catch (err) {
node.error(err);
}
});
// rename folder to keep as a backup (and so we don't run again)
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;
}
}

View File

@@ -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>
@@ -46,7 +46,7 @@
<dd> ID of user to create/edit (ex: @bob:example.com)</dd>
<dt>msg.payload
<span class="property-type">Object</span>
<span class="property-type">object</span>
</dt>
<dd> Details of the new user to create. <a href="https://matrix-org.github.io/synapse/develop/admin_api/user_admin_api.html#create-or-modify-account" target="_blank">Click here</a> to see what valid data you can pass via this input. If the user already exists then optional parameters default to the current value.</dd>
</dl>
@@ -55,8 +55,6 @@
<ol class="node-ports">
<li>Success
<dl class="message-properties">
<dd>original msg object preserved.</dd>
<dt>msg.payload <span class="property-type">string</span></dt>
<dd>the eventId from the posted message.</dd>
</dl>

View File

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

View File

@@ -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>
@@ -29,7 +29,7 @@
</div>
<div class="form-tips" style="margin-bottom: 12px;">
User must be an admin to use this endpoint. Only works with Synapse servers. This is permanent so make sure you actually want to do it.
User must be an admin to use this endpoint. Only works with Synapse servers. This is permanent so be cautious.
</div>
</script>
@@ -79,7 +79,8 @@
<ol class="node-ports">
<li>Success
<dl class="message-properties">
<dd>original msg object preserved.</dd>
<dt>msg.error <span class="property-type">object</span></dt>
<dd>empty object response</dd>
</dl>
</li>
<li>Error

View File

@@ -55,7 +55,12 @@ module.exports = function(RED) {
{ $userId: msg.userId },
);
node.server.matrixClient.http
.authedRequest(undefined, 'POST', path, undefined, { "erase": (msg.erase || false) }, { prefix: '' })
.authedRequest(
'POST',
path,
undefined,
{"erase": (msg.erase || false)},
{"prefix": '' })
.then(function(e){
msg.payload = e;
node.send([msg, null]);

View File

@@ -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">
@@ -51,7 +63,7 @@
<dt>msg.topic
<span class="property-type">string</span>
</dt>
<dd> The room identifier or alias to join: for example, <code>!h8zld9j31:example.com.</code>. Not required if set in the config and will be ignored even if set.</dd>
<dd> The room identifier or alias to join: for example, <code>!h8zld9j31:example.com.</code>. Ignored if configured on the node, otherwise required.</dd>
<dt>msg.userId
<span class="property-type">string</span>
@@ -63,8 +75,8 @@
<ol class="node-ports">
<li>Success
<dl class="message-properties">
<dt>msg.payload <span class="property-type">string</span></dt>
<dd>This returns data directly from the API endpoint. <a href="https://matrix-org.github.io/synapse/develop/admin_api/user_admin_api.html#query-current-sessions-for-a-user" target="_blank">Click here</a> to see what this returns.</dd>
<dt>msg.topic <span class="property-type">string</span></dt>
<dd> the ID of the room we just added the user to.</dd>
</dl>
</li>
<li>Error

View File

@@ -46,8 +46,8 @@ module.exports = function(RED) {
node.send([null, msg]);
}
let roomId = node.roomId || msg.topic;
if(!roomId) {
msg.topic = node.roomId || msg.topic;
if(!msg.topic) {
node.error("room must be defined in either msg.topic or in node config");
return;
}
@@ -60,17 +60,16 @@ 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",
{ $room_id_or_alias: roomId },
{ $room_id_or_alias: msg.topic },
),
undefined,
{ "user_id": msg.userId },
{ prefix: '' }
).then(function(e){
msg.payload = e;
msg.topic = e.room_id;
node.send([msg, null]);
}).catch(function(e){
node.warn("Error joining user to room " + e);

View File

@@ -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>
@@ -51,42 +51,43 @@
<h3>Inputs</h3>
<dl class="message-properties">
<dt>msg.payload
<span class="property-type">Object</span>
<span class="property-type">object</span>
</dt>
<dd> Details of the new user to create. </dd>
<dt class="optional">msg.payload.displayname
<span class="property-type">String | null</span>
<span class="property-type">string | null</span>
</dt>
<dd> Set the displayname for the user (default to username if not set). </dd>
<dt>msg.payload.username
<span class="property-type">Object</span>
<span class="property-type">object</span>
</dt>
<dd> Username for the new user. </dd>
<dt>msg.payload.password
<span class="property-type">String</span>
<span class="property-type">string</span>
</dt>
<dd> Password for the new user. </dd>
<dt>msg.payload.admin
<span class="property-type">Bool</span>
<span class="property-type">bool</span>
</dt>
<dd> If true, the new user will be an admin. Default to false. </dd>
<dt class="optional">msg.payload.user_type
<span class="property-type">String | null</span>
<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>
<ol class="node-ports">
<li>Success
<dl class="message-properties">
<dd>original msg object preserved.</dd>
<dl class="message-properties">`
<dt>msg.eventId <span class="property-type">string</span></dt>
<dd>the eventId from the posted message.</dd>
</dl>

View File

@@ -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', {

View File

@@ -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">
@@ -39,22 +39,22 @@
<h3>Inputs</h3>
<dl class="message-properties">
<dt class="optional">msg.from
<span class="property-type">Integer</span>
<span class="property-type">integer</span>
</dt>
<dd> Is optional but used for pagination, denoting the offset in the returned results. This should be treated as an opaque value and not explicitly set to anything other than the return value of next_token from a previous call. Defaults to 0.</dd>
<dd> Is optional but used for pagination, denoting the offset in the returned results. This should be treated as an opaque value and not explicitly set to anything other than the return value of <code>msg.next_token</code> from a previous call. Defaults to 0.</dd>
<dt class="optional">msg.limit
<span class="property-type">Integer</span>
<span class="property-type">integer</span>
</dt>
<dd> limit - representing a positive integer - Is optional but is used for pagination, denoting the maximum number of items to return in this call. Defaults to 100.</dd>
<dt class="optional">msg.guests
<span class="property-type">Bool</span>
<span class="property-type">bool</span>
</dt>
<dd> Is optional and if false will exclude guest users. Defaults to true to include guest users.</dd>
<dt class="optional">msg.order_by
<span class="property-type">String</span>
<span class="property-type">string</span>
</dt>
<dd>
The method by which to sort the returned list of users.
@@ -85,14 +85,41 @@
<dt>msg.payload <span class="property-type">object</span></dt>
<dd>the response object from the server.</dd>
<dt>msg.payload.next_token <span class="property-type">string</span></dt>
<dd>string representing a positive integer - Indication for pagination. If this is unset then there are no more users to paginate through.</dd>
<dt>msg.payload.total <span class="property-type">integer</span></dt>
<dd>Total number of users.</dd>
<dt>msg.payload.users <span class="property-type">array</span></dt>
<dd>list of users from the Matrix server. <a href="https://matrix-org.github.io/synapse/develop/admin_api/user_admin_api.html#list-accounts" target="_blank">Click here</a> for details on what this contains (or do a test and dump the output). We would put the details here in the doc but Synapse is constantly changing so it's best to reference the official Synapse docs.</dd>
<dd>list of users from the Matrix server. <a href="https://matrix-org.github.io/synapse/develop/admin_api/user_admin_api.html#list-accounts" target="_blank">Click here</a> for details on what this contains (or do a debug on the output).</dd>
<dt>msg.next_token <span class="property-type">string</span></dt>
<dd>string representing a positive integer - Indication for pagination. See above (input msg.from)</dd>
<dt>msg.payload.users[].name <span class="property-type">string</span></dt>
<dd>Fully-qualified user ID (ex. <code>@user:server.com</code>).</dd>
<dt>msg.total <span class="property-type">integer</span></dt>
<dd>Total number of media.</dd>
<dt>msg.payload.users[].is_guest <span class="property-type">bool</span></dt>
<dd>Status if that user is a guest account.</dd>
<dt>msg.payload.users[].admin <span class="property-type">string</span></dt>
<dd>Status if that user is a server administrator.</dd>
<dt>msg.payload.users[].user_type <span class="property-type">string</span></dt>
<dd>Type of the user. Normal users are type <code>None</code>. This allows user type specific behaviour. There are also types <code>support</code> and <code>bot</code>.</dd>
<dt>msg.payload.users[].deactivated <span class="property-type">bool</span></dt>
<dd>Status if that user has been marked as deactivated.</dd>
<dt>msg.payload.users[].shadow_banned <span class="property-type">bool</span></dt>
<dd>Status if that user has been marked as shadow banned.</dd>
<dt>msg.payload.users[].displayname <span class="property-type">string</span></dt>
<dd>The user's display name if they have set one.</dd>
<dt>msg.payload.users[].avatar_url <span class="property-type">string</span></dt>
<dd>The user's avatar URL if they have set one.</dd>
<dt>msg.payload.users[].creation_ts <span class="property-type">integer</span></dt>
<dd>The user's creation timestamp in ms.</dd>
</dl>
</li>
<li>Error

View File

@@ -39,7 +39,7 @@ module.exports = function(RED) {
};
if(msg.guests) {
queryParams['guests'] = msg.guests;
queryParams['guests'] = msg.guests ? 'true' : 'false';
}
if(msg.order_by) {
@@ -48,7 +48,6 @@ module.exports = function(RED) {
node.server.matrixClient.http
.authedRequest(
undefined,
'GET',
"/_synapse/admin/v2/users",
queryParams,
@@ -58,7 +57,7 @@ module.exports = function(RED) {
msg.payload = e;
node.send([msg, null]);
}).catch(function(e){
node.warn("Error fetching user list " + e);
node.warn("Error fetching server user list " + e);
msg.error = e;
node.send([null, msg]);
});

View File

@@ -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>
@@ -36,6 +36,7 @@
<script type="text/html" data-help-name="matrix-whois-user">
<h3>Details</h3>
<p>This node returns information about the active sessions for a specific user.</p>
<a href="https://matrix-org.github.io/synapse/develop/admin_api/user_admin_api.html#query-current-sessions-for-a-user" target="_blank">Matrix API Endpoint Information</a>
<h3>Inputs</h3>
<dl class="message-properties">
@@ -49,8 +50,29 @@
<ol class="node-ports">
<li>Success
<dl class="message-properties">
<dt>msg.payload <span class="property-type">string</span></dt>
<dd>This returns data directly from the API endpoint. <a href="https://matrix-org.github.io/synapse/develop/admin_api/user_admin_api.html#query-current-sessions-for-a-user" target="_blank">Click here</a> to see what this returns.</dd>
<dt>msg.payload <span class="property-type">object</span></dt>
<dd>This returns data directly from the API endpoint. <a href="https://matrix-org.github.io/synapse/develop/admin_api/user_admin_api.html#query-current-sessions-for-a-user" target="_blank">Click here</a> to see more info.</dd>
<dt>msg.payload.user_id <span class="property-type">string</span></dt>
<dd>The Matrix user ID of the user.</dd>
<dt>msg.payload.devices <span class="property-type">object</span></dt>
<dd>Each key in this object is an identifier for one of the user's devices.</dd>
<dt>msg.payload.devices.X.sessions <span class="property-type">array</span></dt>
<dd>A user's sessions (i.e. what they did with an access token from one login).</dd>
<dt>msg.payload.devices.X.sessions[].connections <span class="property-type">array</span></dt>
<dd>Information about particular connections in the session.</dd>
<dt>msg.payload.devices.X.sessions[].connections[].ip <span class="property-type">string</span></dt>
<dd>Most recently seen IP address of the session.</dd>
<dt>msg.payload.devices.X.sessions[].connections[].last_seen <span class="property-type">integer</span></dt>
<dd>Unix timestamp that the session was last active.</dd>
<dt>msg.payload.devices.X.sessions[].connections[].user_agent <span class="property-type">string</span></dt>
<dd>User agent string last seen in the session.</dd>
</dl>
</li>
<li>Error

View File

@@ -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",
@@ -63,13 +62,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 fetching user whois information " + e);
msg.error = e;
node.send([null, msg]);
});
});
}
RED.nodes.registerType("matrix-whois-user", MatrixWhoIsUser);