mirror of
https://github.com/Skylar-Tech/node-red-contrib-matrix-chat.git
synced 2026-05-18 05:03:37 -06:00
Compare commits
24 Commits
45ff930518
...
dev
| Author | SHA1 | Date | |
|---|---|---|---|
| 99909a77c3 | |||
| aadd82d820 | |||
| 4e6fa50a67 | |||
| 58bf2dcb54 | |||
| c15893bab5 | |||
| f0af0e92fe | |||
| 04de0b4eb3 | |||
| 8cb52112c1 | |||
| 54a9972bbc | |||
| ad34f018ab | |||
| 20345787d2 | |||
| 99c19923c6 | |||
| 093d59893e | |||
| 913f5dfcb9 | |||
| e0947dd3bc | |||
| 8287f3c08a | |||
| 2a78524a90 | |||
| d01838ac84 | |||
| 2059f8455d | |||
| 0cb8ecf8aa | |||
| 77f2c4be46 | |||
| cf82daf5da | |||
| 487241e097 | |||
| 3b161f1ad9 |
@@ -5,6 +5,8 @@
|
|||||||
|
|
||||||
Join our public Matrix room for help: [#node-red-contrib-matrix-chat:skylar.tech](https://app.element.io/#/room/#node-red-contrib-matrix-chat:skylar.tech)
|
Join our public Matrix room for help: [#node-red-contrib-matrix-chat:skylar.tech](https://app.element.io/#/room/#node-red-contrib-matrix-chat:skylar.tech)
|
||||||
|
|
||||||
|
[](https://ko-fi.com/B0B51BM7C)
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
|
||||||
Supported functionality in this package includes:
|
Supported functionality in this package includes:
|
||||||
@@ -59,7 +61,9 @@ Interested in helping? Contributions to finalize E2EE support are welcome!
|
|||||||
|
|
||||||
This module includes a node to register users using the Synapse secret registration endpoint. It returns both an `access_token` and a `device_id`, perfect for setting up the bot.
|
This module includes a node to register users using the Synapse secret registration endpoint. It returns both an `access_token` and a `device_id`, perfect for setting up the bot.
|
||||||
|
|
||||||
[See how to register a user here](https://github.com/Skylar-Tech/node-red-contrib-matrix-chat/tree/master/examples#readme).
|
[Guide on registering a user via the web browser](https://skylar.tech/matrix-chat-bot-module-for-node-red/)
|
||||||
|
|
||||||
|
[Guide on registering using shared secret registration](https://github.com/Skylar-Tech/node-red-contrib-matrix-chat/tree/master/examples#readme) (for server owners)
|
||||||
|
|
||||||
### Other Packages
|
### Other Packages
|
||||||
|
|
||||||
|
|||||||
+205
-210
@@ -22,59 +22,47 @@ To try out any of the examples:
|
|||||||
|
|
||||||
## Index
|
## Index
|
||||||
|
|
||||||
|
**Click the ▶ example to ▼ expand**
|
||||||
|
|
||||||
### User Management
|
### User Management
|
||||||
|
|
||||||
- [Get or set current user display name](#get-or-set-current-user-display-name)
|
<details>
|
||||||
- [Set user avatar using URL](#set-user-avatar-using-url)
|
<summary>Get or set current user display name</summary>
|
||||||
- [Fetch user info by userId](#fetch-user-info-by-userid)
|
|
||||||
- [Create User with Shared Secret Registration](#create-user-with-shared-secret-registration)
|
|
||||||
- [Create/Edit Synapse User](#createedit-synapse-user)
|
|
||||||
- [Deactivate User](#deactivate-user)
|
|
||||||
- [Force User to Join Room](#force-user-to-join-room)
|
|
||||||
|
|
||||||
### Message Handling
|
[View JSON](get-set-displayname.json)
|
||||||
|
|
||||||
- [Respond to "ping" with "pong"](#respond-to-ping-with-pong)
|
This flow lets you get or set the displayname for the current user.
|
||||||
- [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)
|
|
||||||
- [Remove Messages Containing "delete"](#remove-messages-containing-delete)
|
|
||||||
|
|
||||||
### Event Handling
|

|
||||||
|
|
||||||
- [Sending Typing Events to a Room](#sending-typing-events-to-a-room)
|
</details>
|
||||||
- [Mark all received events as read](#mark-all-received-events-as-read)
|
|
||||||
- [Fetch event by eventId and roomId](#fetch-event-by-eventid-and-roomid)
|
|
||||||
- [Paginate the entire history of a given room](#paginate-the-entire-history-of-a-given-room)
|
|
||||||
- [Paginate related events to a given eventId](#paginate-related-events-to-a-given-eventid)
|
|
||||||
|
|
||||||
### Room Management
|
<details>
|
||||||
|
<summary>Set user avatar using URL</summary>
|
||||||
|
|
||||||
- [Set room name and topic](#set-room-name-and-topic)
|
[View JSON](set-avatar-from-url.json)
|
||||||
- [Accept Room Invites from Specific User](#accept-room-invites-from-specific-user)
|
|
||||||
- [Leave Room When Someone Says "bye"](#leave-room-when-someone-says-bye)
|
|
||||||
- [Respond to "newroom" by Creating a New Room and Inviting User](#respond-to-newroom-by-creating-a-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)
|
|
||||||
- [Kick/Ban User from Room](#kickban-user-from-room)
|
|
||||||
|
|
||||||
### User Information
|
Inject a URL to an image and Node-RED will fetch the contents, upload to matrix, then set the user avatar to the new mxc_url.
|
||||||
|
|
||||||
- [Respond to "users" with Full List of Server Users](#respond-to-users-with-full-list-of-server-users)
|
This is a good example of how to use the upload file node.
|
||||||
- [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 "rooms \<user_id\>" with User's Rooms](#respond-to-rooms-user_id-with-users-rooms)
|
|
||||||
- [Respond to "room_users" with Current Room's Users](#respond-to-room_users-with-current-rooms-users)
|
|
||||||
|
|
||||||
### Advanced Features
|

|
||||||
|
|
||||||
- [Use Function Node to Run Any Command](#use-function-node-to-run-any-command)
|
</details>
|
||||||
- [Download & Store All Received Files/Images](#download--store-all-received-filesimages)
|
|
||||||
|
|
||||||
---
|
<details>
|
||||||
|
<summary>Fetch user info by userId</summary>
|
||||||
|
|
||||||
## User Management
|
[View JSON](get-user.json)
|
||||||
|
|
||||||
### Create User with Shared Secret Registration
|
Note this only works for users that the bot shares a room with. It will attempt to fetch the user from local storage first and if not found will query the server for the data.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Create User with Shared Secret Registration</summary>
|
||||||
|
|
||||||
[View JSON](shared-secret-registration.json)
|
[View JSON](shared-secret-registration.json)
|
||||||
|
|
||||||
@@ -89,9 +77,10 @@ Use this flow to create users on servers with closed registration. You can also
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
---
|
</details>
|
||||||
|
|
||||||
### Create/Edit Synapse User
|
<details>
|
||||||
|
<summary>Create/Edit Synapse User</summary>
|
||||||
|
|
||||||
[View JSON](add-user-with-admin-user.json)
|
[View JSON](add-user-with-admin-user.json)
|
||||||
|
|
||||||
@@ -99,9 +88,10 @@ Allows an administrator to create or modify a user account with a specified `msg
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
---
|
</details>
|
||||||
|
|
||||||
### Deactivate User
|
<details>
|
||||||
|
<summary>Deactivate User</summary>
|
||||||
|
|
||||||
[View JSON](deactivate-user.json)
|
[View JSON](deactivate-user.json)
|
||||||
|
|
||||||
@@ -114,9 +104,10 @@ If you send "deactivate_user @test:example.com", the bot will deactivate the `@t
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
---
|
</details>
|
||||||
|
|
||||||
### Force User to Join Room
|
<details>
|
||||||
|
<summary>Force User to Join Room</summary>
|
||||||
|
|
||||||
[View JSON](force-join-room.json)
|
[View JSON](force-join-room.json)
|
||||||
|
|
||||||
@@ -129,11 +120,25 @@ If you send "force_join @test:example.com !320j90mf0394f:example.com", the bot w
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
---
|
</details>
|
||||||
|
|
||||||
## Message Handling
|
### Message Handling
|
||||||
|
|
||||||
### Respond to "ping" with "pong"
|
<details>
|
||||||
|
<summary>Upload file and send to room</summary>
|
||||||
|
|
||||||
|
[View JSON](send-image-to-room.json)
|
||||||
|
|
||||||
|
This flow will download an image from a given URL and upload it to the matrix server then send it to a room.
|
||||||
|
|
||||||
|
This isn't just for images and supports any sort of file format. Videos, images, and audio files will have metadata detected automatically and appended to the message (duration, dimensions, thumbnail, etc)
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Respond to "ping" with "pong"</summary>
|
||||||
|
|
||||||
[View JSON](respond-ping-pong.json)
|
[View JSON](respond-ping-pong.json)
|
||||||
|
|
||||||
@@ -141,9 +146,10 @@ Use this flow to respond to anyone who says "ping" with "pong" in the same room.
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
---
|
</details>
|
||||||
|
|
||||||
### Respond to "html" with an HTML Message
|
<details>
|
||||||
|
<summary>Respond to "html" with an HTML Message</summary>
|
||||||
|
|
||||||
[View JSON](respond-to-html-with-html.json)
|
[View JSON](respond-to-html-with-html.json)
|
||||||
|
|
||||||
@@ -151,39 +157,10 @@ Use this flow to respond to anyone who says "html" with an example HTML message.
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
---
|
</details>
|
||||||
|
|
||||||
### Respond to "image" with an Uploaded Image
|
<details>
|
||||||
|
<summary>Respond to "react" with a Reaction</summary>
|
||||||
[View JSON](respond-image-with-image.json)
|
|
||||||
|
|
||||||
You will need an image on the machine running Node-RED. In this example, `example.png` exists inside the Node-RED directory.
|
|
||||||
|
|
||||||
**Instructions:**
|
|
||||||
|
|
||||||
1. Place the image file (`example.png`) in the appropriate directory.
|
|
||||||
2. Import the flow and deploy it.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Respond to "file" with an Uploaded File
|
|
||||||
|
|
||||||
[View JSON](respond-file-with-file.json)
|
|
||||||
|
|
||||||
You will need a file on the machine running Node-RED. In this example, `sample.pdf` exists inside the Node-RED directory.
|
|
||||||
|
|
||||||
**Instructions:**
|
|
||||||
|
|
||||||
1. Place the file (`sample.pdf`) in the appropriate directory.
|
|
||||||
2. Import the flow and deploy it.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Respond to "react" with a Reaction
|
|
||||||
|
|
||||||
[View JSON](respond-react-with-reaction.json)
|
[View JSON](respond-react-with-reaction.json)
|
||||||
|
|
||||||
@@ -191,9 +168,10 @@ Gives a 👍 reaction when someone says "react".
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
---
|
</details>
|
||||||
|
|
||||||
### Remove Messages Containing "delete"
|
<details>
|
||||||
|
<summary>Remove Messages Containing "delete"</summary>
|
||||||
|
|
||||||
[View JSON](delete-event.json)
|
[View JSON](delete-event.json)
|
||||||
|
|
||||||
@@ -203,11 +181,99 @@ Any messages containing "delete" will be removed by the client.
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
---
|
</details>
|
||||||
|
|
||||||
## Room Management
|
### Event Handling
|
||||||
|
|
||||||
### Accept Room Invites from Specific User
|
<details>
|
||||||
|
<summary>Sending Typing Events to a Room</summary>
|
||||||
|
|
||||||
|
[View JSON](send-typing-events.json)
|
||||||
|
|
||||||
|
You can indicate to a room that the bot is typing and also cancel the typing event. This can be useful for making bots feel more interactive (e.g., show typing while requesting an API endpoint).
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Mark all received events as read</summary>
|
||||||
|
|
||||||
|
[View JSON](mark-all-read.json)
|
||||||
|
|
||||||
|
With this flow anytime an event is received by the bot it will mark it as read.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Fetch event by eventId and roomId</summary>
|
||||||
|
|
||||||
|
[View JSON](get-event.json)
|
||||||
|
|
||||||
|
Fetch an event from Matrix by eventId and roomId
|
||||||
|
|
||||||
|
**Instructions:**
|
||||||
|
|
||||||
|
- Change the inject node to contain a proper eventId and roomId (topic)
|
||||||
|
- Inject the payload and you should see the result contain the event data
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Paginate the entire history of a given room</summary>
|
||||||
|
|
||||||
|
[View JSON](paginate-room-history.json)
|
||||||
|
|
||||||
|
This flow iterates the entire history of a room (outputting for every page we hit).
|
||||||
|
|
||||||
|
There is a configurable delay (currently set at 1000ms) in this flow. This is recommended, so you are not bogging down the server.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Paginate related events to a given eventId</summary>
|
||||||
|
|
||||||
|
[View JSON](fetch-event-relations.json)
|
||||||
|
|
||||||
|
Paginate through the related events to a given eventId. Related events being reactions, thread messages, message modifications, message removals, etc. This outputs once per iterated page.
|
||||||
|
|
||||||
|
If you would rather have it output one massive list at the end of pagination use this flow:
|
||||||
|
[View Aggregated Flow JSON](fetch-event-relations-aggregated.json)
|
||||||
|
|
||||||
|
**Instructions:**
|
||||||
|
|
||||||
|
- Change the inject node to contain a proper eventId and roomId (topic)
|
||||||
|
- Inject the payload and you should see the result contain a list of related events for the given eventId
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
### Room Management
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Set room name and topic</summary>
|
||||||
|
|
||||||
|
[View JSON](set-room-name-and-topic.json)
|
||||||
|
|
||||||
|
Changes the specified room's name and topic to the injected values.
|
||||||
|
|
||||||
|
There are a bunch of different settings you can change, this is just an example for these two fields to show how it's done.
|
||||||
|
|
||||||
|
This node can also be used to read these values.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Accept Room Invites from Specific User</summary>
|
||||||
|
|
||||||
[View JSON](accept-room-invites.json)
|
[View JSON](accept-room-invites.json)
|
||||||
|
|
||||||
@@ -215,9 +281,10 @@ Automatically accept room invites from a specific user.
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
---
|
</details>
|
||||||
|
|
||||||
### Leave Room When Someone Says "bye"
|
<details>
|
||||||
|
<summary>Leave Room When Someone Says "bye"</summary>
|
||||||
|
|
||||||
[View JSON](leave-room-bye.json)
|
[View JSON](leave-room-bye.json)
|
||||||
|
|
||||||
@@ -225,9 +292,10 @@ Leaves the room when someone says "bye".
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
---
|
</details>
|
||||||
|
|
||||||
### Respond to "newroom" by Creating a New Room and Inviting User
|
<details>
|
||||||
|
<summary>Respond to "newroom" by Creating a New Room and Inviting User</summary>
|
||||||
|
|
||||||
[View JSON](respond-newroom-invite.json)
|
[View JSON](respond-newroom-invite.json)
|
||||||
|
|
||||||
@@ -235,19 +303,21 @@ When someone sends "newroom", a new room will be created, and the user who sent
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
---
|
</details>
|
||||||
|
|
||||||
### Respond to "joinroom \<room_id_or_alias\>" by Joining Mentioned Room
|
<details>
|
||||||
|
<summary>Respond to "joinroom <room_id_or_alias>" by Joining Mentioned Room</summary>
|
||||||
|
|
||||||
[View JSON](respond-joinroom.json)
|
[View JSON](respond-joinroom.json)
|
||||||
|
|
||||||
When someone sends "joinroom \<room_id_or_alias\>", the bot will join the mentioned room.
|
When someone sends "joinroom <room_id_or_alias>", the bot will join the mentioned room.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
---
|
</details>
|
||||||
|
|
||||||
### Kick/Ban User from Room
|
<details>
|
||||||
|
<summary>Kick/Ban User from Room</summary>
|
||||||
|
|
||||||
[View JSON](room-kick-ban.json)
|
[View JSON](room-kick-ban.json)
|
||||||
|
|
||||||
@@ -258,11 +328,12 @@ When someone sends "joinroom \<room_id_or_alias\>", the bot will join the mentio
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
---
|
</details>
|
||||||
|
|
||||||
## User Information
|
### User Information
|
||||||
|
|
||||||
### Respond to "users" with Full List of Server Users
|
<details>
|
||||||
|
<summary>Respond to "users" with Full List of Server Users</summary>
|
||||||
|
|
||||||
[View JSON](respond-users-list.json)
|
[View JSON](respond-users-list.json)
|
||||||
|
|
||||||
@@ -275,9 +346,10 @@ When someone sends the text "users", they receive an HTML message containing all
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
---
|
</details>
|
||||||
|
|
||||||
### Respond to "whois \<user_id\>" with Information about the User's Session
|
<details>
|
||||||
|
<summary>Respond to "whois <user_id>" with Information about the User's Session</summary>
|
||||||
|
|
||||||
[View JSON](respond-whois.json)
|
[View JSON](respond-whois.json)
|
||||||
|
|
||||||
@@ -290,13 +362,14 @@ Lists out the user's session info, including IP address, last seen time, and use
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
---
|
</details>
|
||||||
|
|
||||||
### Respond to "rooms \<user_id\>" with User's Rooms
|
<details>
|
||||||
|
<summary>Respond to "rooms <user_id>" with User's Rooms</summary>
|
||||||
|
|
||||||
[View JSON](respond-rooms.json)
|
[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.
|
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.
|
||||||
|
|
||||||
**Notes:**
|
**Notes:**
|
||||||
|
|
||||||
@@ -306,9 +379,10 @@ Responds to "rooms \<user_id\>" with that user's rooms. If the message is just "
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
---
|
</details>
|
||||||
|
|
||||||
### Respond to "room_users" with Current Room's Users
|
<details>
|
||||||
|
<summary>Respond to "room_users" with Current Room's Users</summary>
|
||||||
|
|
||||||
[View JSON](respond-room-users.json)
|
[View JSON](respond-room-users.json)
|
||||||
|
|
||||||
@@ -318,11 +392,12 @@ Lists the users participating in the current room.
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
---
|
</details>
|
||||||
|
|
||||||
## Advanced Features
|
### Advanced Features
|
||||||
|
|
||||||
### Use Function Node to Run Any Command
|
<details>
|
||||||
|
<summary>Use Function Node to Run Any Command</summary>
|
||||||
|
|
||||||
[View JSON](custom-redact-function-node.json)
|
[View JSON](custom-redact-function-node.json)
|
||||||
|
|
||||||
@@ -337,19 +412,10 @@ To view the available functions, check out the [`client.ts` file from `matrix-js
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
---
|
</details>
|
||||||
|
|
||||||
### Sending Typing Events to a Room
|
<details>
|
||||||
|
<summary>Download & Store All Received Files/Images</summary>
|
||||||
[View JSON](send-typing-events.json)
|
|
||||||
|
|
||||||
You can indicate to a room that the bot is typing and also cancel the typing event. This can be useful for making bots feel more interactive (e.g., show typing while requesting an API endpoint).
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Download & Store All Received Files/Images
|
|
||||||
|
|
||||||
[View JSON](store-received-files.json)
|
[View JSON](store-received-files.json)
|
||||||
|
|
||||||
@@ -363,109 +429,38 @@ Downloads received files/images. If the file is encrypted, it will decrypt it fo
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
---
|
</details>
|
||||||
|
|
||||||
### Fetch event by eventId and roomId
|
### Deprecated
|
||||||
|
|
||||||
[View JSON](get-event.json)
|
<details>
|
||||||
|
<summary>Respond to "image" with an uploaded image</summary>
|
||||||
|
|
||||||
Fetch an event from Matrix by eventId and roomId
|
[View JSON](respond-image-with-image.json)
|
||||||
|
|
||||||
|
You will need an image on the machine running Node-RED. In this example, `example.png` exists inside the Node-RED directory.
|
||||||
|
|
||||||
**Instructions:**
|
**Instructions:**
|
||||||
|
|
||||||
- Change the inject node to contain a proper eventId and roomId (topic)
|
1. Place the image file (`example.png`) in the appropriate directory.
|
||||||
- Inject the payload and you should see the result contain the event data
|
2. Import the flow and deploy it.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
---
|
</details>
|
||||||
|
|
||||||
### Paginate related events to a given eventId
|
<details>
|
||||||
|
<summary>Respond to "file" with an uploaded file</summary>
|
||||||
|
|
||||||
[View JSON](fetch-event-relations.json)
|
[View JSON](respond-file-with-file.json)
|
||||||
|
|
||||||
Paginate through the related events to a given eventId. Related events being reactions, thread messages, message modifications, message removals, etc. This outputs once per iterated page.
|
|
||||||
|
|
||||||
If you would rather have it output one massive list at the end of pagination use this flow:
|
|
||||||
[View Aggregated Flow JSON](fetch-event-relations-aggregated.json)
|
|
||||||
|
|
||||||
|
You will need a file on the machine running Node-RED. In this example, `sample.pdf` exists inside the Node-RED directory.
|
||||||
|
|
||||||
**Instructions:**
|
**Instructions:**
|
||||||
|
|
||||||
- Change the inject node to contain a proper eventId and roomId (topic)
|
1. Place the file (`sample.pdf`) in the appropriate directory.
|
||||||
- Inject the payload and you should see the result contain a list of related events for the given eventId
|
2. Import the flow and deploy it.
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Mark all received events as read
|
|
||||||
|
|
||||||
[View JSON](mark-all-read.json)
|
|
||||||
|
|
||||||
With this flow anytime an event is received by the bot it will mark it as read.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Paginate the entire history of a given room
|
|
||||||
|
|
||||||
[View JSON](paginate-room-history.json)
|
|
||||||
|
|
||||||
This flow iterates the entire history of a room (outputting for every page we hit).
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Fetch user info by userId
|
|
||||||
|
|
||||||
[View JSON](get-user.json)
|
|
||||||
|
|
||||||
Note this only works for users that the bot shares a room with. It will attempt to fetch the user from local storage first and if not found will query the server for the data.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Get or set current user display name
|
|
||||||
|
|
||||||
[View JSON](get-set-displayname.json)
|
|
||||||
|
|
||||||
This flow lets you get or set the displayname for the current user.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Set user avatar using URL
|
|
||||||
|
|
||||||
[View JSON](set-avatar-from-url.json)
|
|
||||||
|
|
||||||
Inject a URL to an image and Node-RED will fetch the contents, upload to matrix, then set the user avatar to the new mxc_url.
|
|
||||||
|
|
||||||
This is a good example of how to use the upload file node.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Set room name and topic
|
|
||||||
|
|
||||||
[View JSON](set-room-name-and-topic.json)
|
|
||||||
|
|
||||||
Changes the specified room's name and topic to the injected values.
|
|
||||||
|
|
||||||
There are a bunch of different settings you can change, this is just an example for these two fields to show how it's done.
|
|
||||||
|
|
||||||
This node can also be used to read these values.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
</details>
|
||||||
|
|||||||
@@ -15,9 +15,9 @@
|
|||||||
"973dd418b00172c8",
|
"973dd418b00172c8",
|
||||||
"3edbea9403d7c347"
|
"3edbea9403d7c347"
|
||||||
],
|
],
|
||||||
"x": 754,
|
"x": 854,
|
||||||
"y": 1339,
|
"y": 1339,
|
||||||
"w": 932,
|
"w": 832,
|
||||||
"h": 182
|
"h": 182
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -113,7 +113,7 @@
|
|||||||
"once": false,
|
"once": false,
|
||||||
"onceDelay": 0.1,
|
"onceDelay": 0.1,
|
||||||
"topic": "!example:skylar.tech",
|
"topic": "!example:skylar.tech",
|
||||||
"x": 900,
|
"x": 1000,
|
||||||
"y": 1480,
|
"y": 1480,
|
||||||
"wires": [
|
"wires": [
|
||||||
[
|
[
|
||||||
|
|||||||
@@ -0,0 +1,142 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"id": "f4a0c2a9d7eed027",
|
||||||
|
"type": "group",
|
||||||
|
"z": "8fd89a0b44c61e76",
|
||||||
|
"name": "Send an uploaded file to a room",
|
||||||
|
"style": {
|
||||||
|
"label": true
|
||||||
|
},
|
||||||
|
"nodes": [
|
||||||
|
"8d475ab136d1ee7e",
|
||||||
|
"2524f5a9a7ea2444",
|
||||||
|
"9a149a36d6ab6470",
|
||||||
|
"9da1ed1dc33930bb",
|
||||||
|
"f93782c346d0e6ef"
|
||||||
|
],
|
||||||
|
"x": 754,
|
||||||
|
"y": 2719,
|
||||||
|
"w": 992,
|
||||||
|
"h": 82
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "8d475ab136d1ee7e",
|
||||||
|
"type": "matrix-upload-file",
|
||||||
|
"z": "8fd89a0b44c61e76",
|
||||||
|
"g": "f4a0c2a9d7eed027",
|
||||||
|
"name": "",
|
||||||
|
"server": null,
|
||||||
|
"inputType": "msg",
|
||||||
|
"inputValue": "payload",
|
||||||
|
"fileNameType": "msg",
|
||||||
|
"fileNameValue": "filename",
|
||||||
|
"contentType": "",
|
||||||
|
"generateThumbnails": true,
|
||||||
|
"x": 1270,
|
||||||
|
"y": 2760,
|
||||||
|
"wires": [
|
||||||
|
[
|
||||||
|
"2524f5a9a7ea2444"
|
||||||
|
],
|
||||||
|
[]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "2524f5a9a7ea2444",
|
||||||
|
"type": "matrix-send-message",
|
||||||
|
"z": "8fd89a0b44c61e76",
|
||||||
|
"g": "f4a0c2a9d7eed027",
|
||||||
|
"name": "",
|
||||||
|
"server": null,
|
||||||
|
"roomId": "",
|
||||||
|
"message": "",
|
||||||
|
"messageType": "m.text",
|
||||||
|
"messageFormat": "",
|
||||||
|
"replaceMessage": false,
|
||||||
|
"threadReplyType": "msg",
|
||||||
|
"threadReplyValue": "isThread",
|
||||||
|
"x": 1440,
|
||||||
|
"y": 2760,
|
||||||
|
"wires": [
|
||||||
|
[
|
||||||
|
"f93782c346d0e6ef"
|
||||||
|
],
|
||||||
|
[]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "9a149a36d6ab6470",
|
||||||
|
"type": "http request",
|
||||||
|
"z": "8fd89a0b44c61e76",
|
||||||
|
"g": "f4a0c2a9d7eed027",
|
||||||
|
"name": "",
|
||||||
|
"method": "GET",
|
||||||
|
"ret": "bin",
|
||||||
|
"paytoqs": "ignore",
|
||||||
|
"url": "",
|
||||||
|
"tls": "",
|
||||||
|
"persist": false,
|
||||||
|
"proxy": "",
|
||||||
|
"insecureHTTPParser": false,
|
||||||
|
"authType": "",
|
||||||
|
"senderr": false,
|
||||||
|
"headers": [],
|
||||||
|
"x": 1110,
|
||||||
|
"y": 2760,
|
||||||
|
"wires": [
|
||||||
|
[
|
||||||
|
"8d475ab136d1ee7e"
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "9da1ed1dc33930bb",
|
||||||
|
"type": "inject",
|
||||||
|
"z": "8fd89a0b44c61e76",
|
||||||
|
"g": "f4a0c2a9d7eed027",
|
||||||
|
"name": "",
|
||||||
|
"props": [
|
||||||
|
{
|
||||||
|
"p": "url",
|
||||||
|
"v": "https://nodered.org/about/resources/media/node-red-icon.png",
|
||||||
|
"vt": "str"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"p": "filename",
|
||||||
|
"v": "avatar.jpg",
|
||||||
|
"vt": "str"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"p": "topic",
|
||||||
|
"vt": "str"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"repeat": "",
|
||||||
|
"crontab": "",
|
||||||
|
"once": false,
|
||||||
|
"onceDelay": 0.1,
|
||||||
|
"topic": "!example:skylar.tech",
|
||||||
|
"x": 900,
|
||||||
|
"y": 2760,
|
||||||
|
"wires": [
|
||||||
|
[
|
||||||
|
"9a149a36d6ab6470"
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "f93782c346d0e6ef",
|
||||||
|
"type": "debug",
|
||||||
|
"z": "8fd89a0b44c61e76",
|
||||||
|
"g": "f4a0c2a9d7eed027",
|
||||||
|
"name": "Debug Output",
|
||||||
|
"active": true,
|
||||||
|
"tosidebar": true,
|
||||||
|
"console": false,
|
||||||
|
"tostatus": false,
|
||||||
|
"complete": "true",
|
||||||
|
"x": 1620,
|
||||||
|
"y": 2760,
|
||||||
|
"wires": []
|
||||||
|
}
|
||||||
|
]
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 22 KiB |
Generated
+133
-735
File diff suppressed because it is too large
Load Diff
+2
-3
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "node-red-contrib-matrix-chat",
|
"name": "node-red-contrib-matrix-chat",
|
||||||
"version": "0.8.0",
|
"version": "0.9.2",
|
||||||
"description": "Matrix chat server client for Node-RED",
|
"description": "Matrix chat server client for Node-RED",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"abort-controller": "^3.0.0",
|
"abort-controller": "^3.0.0",
|
||||||
@@ -9,12 +9,11 @@
|
|||||||
"got": "^12.0.2",
|
"got": "^12.0.2",
|
||||||
"image-size": "^1.0.2",
|
"image-size": "^1.0.2",
|
||||||
"isomorphic-webcrypto": "^2.3.8",
|
"isomorphic-webcrypto": "^2.3.8",
|
||||||
"matrix-js-sdk": "^28.0.0",
|
"matrix-js-sdk": "34.11.1",
|
||||||
"mime": "^3.0.0",
|
"mime": "^3.0.0",
|
||||||
"node-fetch": "^3.3.0",
|
"node-fetch": "^3.3.0",
|
||||||
"node-localstorage": "^2.2.1",
|
"node-localstorage": "^2.2.1",
|
||||||
"olm": "https://gitlab.matrix.org/matrix-org/olm/-/package_files/2572/download",
|
"olm": "https://gitlab.matrix.org/matrix-org/olm/-/package_files/2572/download",
|
||||||
"request": "^2.88.2",
|
|
||||||
"sharp": "^0.33.4",
|
"sharp": "^0.33.4",
|
||||||
"tmp": "^0.2.1",
|
"tmp": "^0.2.1",
|
||||||
"utf8": "^3.0.0"
|
"utf8": "^3.0.0"
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
const {RelationType, EventType, Direction} = require("matrix-js-sdk");
|
const sdkPromise = import("matrix-js-sdk");
|
||||||
|
|
||||||
module.exports = function(RED) {
|
module.exports = function(RED) {
|
||||||
function MatrixFetchRelations(n) {
|
function MatrixFetchRelations(n) {
|
||||||
@@ -49,14 +49,17 @@ module.exports = function(RED) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
const sdk = await sdkPromise;
|
||||||
|
const Direction = sdk.Direction;
|
||||||
|
|
||||||
function evaluateNodePropertySafe(value, type, node, msg) {
|
function evaluateNodePropertySafe(value, type, node, msg) {
|
||||||
try {
|
try {
|
||||||
return RED.util.evaluateNodeProperty(value, type, node, msg);
|
return RED.util.evaluateNodeProperty(value, type, node, msg);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e instanceof TypeError) {
|
if (e instanceof TypeError) {
|
||||||
return undefined; // Handle TypeError and return undefined
|
return undefined;
|
||||||
}
|
}
|
||||||
throw e; // Re-throw other errors to prevent masking issues
|
throw e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,14 +19,18 @@
|
|||||||
},
|
},
|
||||||
oneditprepare: function() {
|
oneditprepare: function() {
|
||||||
$("#node-input-roomId").typedInput({
|
$("#node-input-roomId").typedInput({
|
||||||
type: this.roomIdType,
|
|
||||||
types: ['msg','flow','global','str'],
|
types: ['msg','flow','global','str'],
|
||||||
}).typedInput('value', this.roomIdValue);
|
typeField: "#node-input-roomId"
|
||||||
|
});
|
||||||
|
$("#node-input-roomId").typedInput("type", this.roomIdType || "msg");
|
||||||
|
$("#node-input-roomId").typedInput("value", this.roomIdValue || "topic");
|
||||||
|
|
||||||
$("#node-input-eventId").typedInput({
|
$("#node-input-eventId").typedInput({
|
||||||
type: this.eventIdType,
|
|
||||||
types: ['msg','flow','global','str'],
|
types: ['msg','flow','global','str'],
|
||||||
}).typedInput('value', this.eventIdValue);
|
typeField: "#node-input-eventId"
|
||||||
|
});
|
||||||
|
$("#node-input-eventId").typedInput("type", this.eventIdType || "msg");
|
||||||
|
$("#node-input-eventId").typedInput("value", this.eventIdValue || "eventId");
|
||||||
},
|
},
|
||||||
oneditsave: function() {
|
oneditsave: function() {
|
||||||
this.roomIdType = $("#node-input-roomId").typedInput('type');
|
this.roomIdType = $("#node-input-roomId").typedInput('type');
|
||||||
@@ -58,18 +62,6 @@
|
|||||||
<label for="node-input-eventId"><i class="fa fa-file"></i> Event ID</label>
|
<label for="node-input-eventId"><i class="fa fa-file"></i> Event ID</label>
|
||||||
<input type="text" id="node-input-eventId">
|
<input type="text" id="node-input-eventId">
|
||||||
</div>
|
</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>
|
||||||
|
|
||||||
<script type="text/html" data-help-name="matrix-get-event">
|
<script type="text/html" data-help-name="matrix-get-event">
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
const {TimelineWindow, RelationType, Filter} = require("matrix-js-sdk");
|
|
||||||
const crypto = require('crypto');
|
const crypto = require('crypto');
|
||||||
module.exports = function(RED) {
|
|
||||||
function MatrixReceiveMessage(n) {
|
|
||||||
RED.nodes.createNode(this, n);
|
|
||||||
|
|
||||||
|
module.exports = function(RED) {
|
||||||
|
function MatrixMarkRead(n) {
|
||||||
|
RED.nodes.createNode(this, n);
|
||||||
let node = this;
|
let node = this;
|
||||||
this.name = n.name;
|
this.name = n.name;
|
||||||
this.server = RED.nodes.getNode(n.server);
|
this.server = RED.nodes.getNode(n.server);
|
||||||
@@ -38,7 +37,7 @@ module.exports = function(RED) {
|
|||||||
let value = property;
|
let value = property;
|
||||||
if (type === "msg") {
|
if (type === "msg") {
|
||||||
value = RED.util.getMessageProperty(msg, property);
|
value = RED.util.getMessageProperty(msg, property);
|
||||||
} else if ((type === 'flow') || (type === 'global')) {
|
} else if (type === 'flow' || type === 'global') {
|
||||||
try {
|
try {
|
||||||
value = RED.util.evaluateNodeProperty(property, type, node, msg);
|
value = RED.util.evaluateNodeProperty(property, type, node, msg);
|
||||||
} catch (e2) {
|
} catch (e2) {
|
||||||
@@ -66,7 +65,7 @@ module.exports = function(RED) {
|
|||||||
throw new Error(`Event ${eventId} not found in room ${roomId}.`);
|
throw new Error(`Event ${eventId} not found in room ${roomId}.`);
|
||||||
}
|
}
|
||||||
|
|
||||||
await node.server.matrixClient.sendReceipt(event, "m.read")
|
await node.server.matrixClient.sendReceipt(event, "m.read");
|
||||||
node.send([msg, null]);
|
node.send([msg, null]);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
msg.error = `Room pagination error: ${e}`;
|
msg.error = `Room pagination error: ${e}`;
|
||||||
@@ -79,5 +78,5 @@ module.exports = function(RED) {
|
|||||||
node.server.deregister(node);
|
node.server.deregister(node);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
RED.nodes.registerType("matrix-mark-read", MatrixReceiveMessage);
|
RED.nodes.registerType("matrix-mark-read", MatrixMarkRead);
|
||||||
}
|
}
|
||||||
+19
-19
@@ -1,7 +1,8 @@
|
|||||||
const {TimelineWindow, RelationType, Filter} = require("matrix-js-sdk");
|
const sdkPromise = import("matrix-js-sdk");
|
||||||
const crypto = require('crypto');
|
const crypto = require('crypto');
|
||||||
|
|
||||||
module.exports = function(RED) {
|
module.exports = function(RED) {
|
||||||
function MatrixReceiveMessage(n) {
|
function MatrixPaginateRoom(n) {
|
||||||
RED.nodes.createNode(this, n);
|
RED.nodes.createNode(this, n);
|
||||||
|
|
||||||
let node = this;
|
let node = this;
|
||||||
@@ -43,7 +44,7 @@ module.exports = function(RED) {
|
|||||||
let value = property;
|
let value = property;
|
||||||
if (type === "msg") {
|
if (type === "msg") {
|
||||||
value = RED.util.getMessageProperty(msg, property);
|
value = RED.util.getMessageProperty(msg, property);
|
||||||
} else if ((type === 'flow') || (type === 'global')) {
|
} else if (type === 'flow' || type === 'global') {
|
||||||
try {
|
try {
|
||||||
value = RED.util.evaluateNodeProperty(property, type, node, msg);
|
value = RED.util.evaluateNodeProperty(property, type, node, msg);
|
||||||
} catch(e2) {
|
} catch(e2) {
|
||||||
@@ -61,17 +62,15 @@ module.exports = function(RED) {
|
|||||||
if (type === 'global' || type === 'flow') {
|
if (type === 'global' || type === 'flow') {
|
||||||
var contextKey = RED.util.parseContextStore(property);
|
var contextKey = RED.util.parseContextStore(property);
|
||||||
if (/\[msg/.test(contextKey.key)) {
|
if (/\[msg/.test(contextKey.key)) {
|
||||||
// The key has a nest msg. reference to evaluate first
|
// The key has a nested msg. reference that must be evaluated first
|
||||||
contextKey.key = RED.util.normalisePropertyExpression(contextKey.key, msg, true)
|
contextKey.key = RED.util.normalisePropertyExpression(contextKey.key, msg, true);
|
||||||
}
|
}
|
||||||
var target = node.context()[type];
|
var target = node.context()[type];
|
||||||
var callback = err => {
|
target.set(contextKey.key, value, contextKey.store, err => {
|
||||||
if (err) {
|
if (err) {
|
||||||
node.error(err, msg);
|
node.error(err, msg);
|
||||||
getterErrors[rule.p] = err.message;
|
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
target.set(contextKey.key, value, contextKey.store, callback);
|
|
||||||
} else if (type === 'msg') {
|
} else if (type === 'msg') {
|
||||||
if (!RED.util.setMessageProperty(msg, property, value)) {
|
if (!RED.util.setMessageProperty(msg, property, value)) {
|
||||||
node.warn(RED._("change.errors.no-override", { property: property }));
|
node.warn(RED._("change.errors.no-override", { property: property }));
|
||||||
@@ -80,6 +79,12 @@ module.exports = function(RED) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// Dynamically load the SDK
|
||||||
|
const sdk = await sdkPromise;
|
||||||
|
const TimelineWindow = sdk.TimelineWindow;
|
||||||
|
const RelationType = sdk.RelationType;
|
||||||
|
// (Filter was imported originally but is not used, so we omit it.)
|
||||||
|
|
||||||
let roomId = getToValue(msg, node.roomType, node.roomValue),
|
let roomId = getToValue(msg, node.roomType, node.roomValue),
|
||||||
paginateBackwards = getToValue(msg, node.paginateBackwardsType, node.paginateBackwardsValue),
|
paginateBackwards = getToValue(msg, node.paginateBackwardsType, node.paginateBackwardsValue),
|
||||||
pageSize = getToValue(msg, node.pageSizeType, node.pageSizeValue),
|
pageSize = getToValue(msg, node.pageSizeType, node.pageSizeValue),
|
||||||
@@ -101,24 +106,19 @@ module.exports = function(RED) {
|
|||||||
moreMessages = true;
|
moreMessages = true;
|
||||||
if (!timelineWindow) {
|
if (!timelineWindow) {
|
||||||
let timelineSet = room.getUnfilteredTimelineSet();
|
let timelineSet = room.getUnfilteredTimelineSet();
|
||||||
// node.debug(JSON.stringify(timelineSet.getFilter()));
|
// MatrixClient's option initialSyncLimit gets set to the filter we are using,
|
||||||
|
// so override that value with our pageSize.
|
||||||
// MatrixClient's option initialSyncLimit gets set to the filter we are using
|
|
||||||
// so override that value with our pageSize
|
|
||||||
timelineWindow = new TimelineWindow(node.server.matrixClient, timelineSet);
|
timelineWindow = new TimelineWindow(node.server.matrixClient, timelineSet);
|
||||||
await timelineWindow.load(msg.eventId || null, pageSize);
|
await timelineWindow.load(msg.eventId || null, pageSize);
|
||||||
node.timelineWindows.set(pageKey, timelineWindow);
|
node.timelineWindows.set(pageKey, timelineWindow);
|
||||||
} else {
|
} else {
|
||||||
moreMessages = await timelineWindow.paginate(paginateBackwards ? 'b' : 'f', pageSize); // b for backwards f for forwards
|
moreMessages = await timelineWindow.paginate(paginateBackwards ? 'b' : 'f', pageSize); // 'b' for backwards, 'f' for forwards
|
||||||
if (moreMessages) {
|
if (moreMessages) {
|
||||||
await timelineWindow.unpaginate(pageSize, !paginateBackwards);
|
await timelineWindow.unpaginate(pageSize, !paginateBackwards);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MatrixEvent objects are massive so this throws an encode error for the string being too long
|
// To avoid errors converting massive MatrixEvent objects to JSON, we omit them.
|
||||||
// since msg objects convert to JSON
|
|
||||||
// msg.payload = moreMessages ? timelineWindow.getEvents() : false;
|
|
||||||
|
|
||||||
msg.payload = false;
|
msg.payload = false;
|
||||||
msg.start = timelineWindow.getTimelineIndex('b')?.index;
|
msg.start = timelineWindow.getTimelineIndex('b')?.index;
|
||||||
msg.end = timelineWindow.getTimelineIndex('f')?.index;
|
msg.end = timelineWindow.getTimelineIndex('f')?.index;
|
||||||
@@ -152,5 +152,5 @@ module.exports = function(RED) {
|
|||||||
node.server.deregister(node);
|
node.server.deregister(node);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
RED.nodes.registerType("matrix-paginate-room", MatrixReceiveMessage);
|
RED.nodes.registerType("matrix-paginate-room", MatrixPaginateRoom);
|
||||||
}
|
}
|
||||||
@@ -13,6 +13,7 @@
|
|||||||
acceptOwnEvents: {"value": false},
|
acceptOwnEvents: {"value": false},
|
||||||
acceptText: {"value": true},
|
acceptText: {"value": true},
|
||||||
acceptEmotes: {"value": true},
|
acceptEmotes: {"value": true},
|
||||||
|
acceptNotices: {"value": true},
|
||||||
acceptStickers: {"value": true},
|
acceptStickers: {"value": true},
|
||||||
acceptReactions: {"value": true},
|
acceptReactions: {"value": true},
|
||||||
acceptFiles: {"value": true},
|
acceptFiles: {"value": true},
|
||||||
@@ -66,6 +67,16 @@
|
|||||||
Accept text <code style="text-transform: none;">m.text</code>
|
Accept text <code style="text-transform: none;">m.text</code>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="form-row" style="margin-bottom:0;">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
id="node-input-acceptNotices"
|
||||||
|
style="width: auto; margin-left: 125px; vertical-align: top"
|
||||||
|
/>
|
||||||
|
<label for="node-input-acceptNotices" style="width: auto">
|
||||||
|
Accept notices <code style="text-transform: none;">m.notice</code>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
<div class="form-row" style="margin-bottom:0;">
|
<div class="form-row" style="margin-bottom:0;">
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
const {RelationType} = require("matrix-js-sdk");
|
|
||||||
module.exports = function(RED) {
|
module.exports = function(RED) {
|
||||||
function MatrixReceiveMessage(n) {
|
function MatrixReceiveMessage(n) {
|
||||||
RED.nodes.createNode(this, n);
|
RED.nodes.createNode(this, n);
|
||||||
@@ -10,6 +9,7 @@ module.exports = function(RED) {
|
|||||||
this.acceptOwnEvents = n.acceptOwnEvents;
|
this.acceptOwnEvents = n.acceptOwnEvents;
|
||||||
this.acceptText = n.acceptText;
|
this.acceptText = n.acceptText;
|
||||||
this.acceptEmotes = n.acceptEmotes;
|
this.acceptEmotes = n.acceptEmotes;
|
||||||
|
this.acceptNotices = n.acceptNotices;
|
||||||
this.acceptStickers = n.acceptStickers;
|
this.acceptStickers = n.acceptStickers;
|
||||||
this.acceptReactions = n.acceptReactions;
|
this.acceptReactions = n.acceptReactions;
|
||||||
this.acceptFiles = n.acceptFiles;
|
this.acceptFiles = n.acceptFiles;
|
||||||
@@ -69,6 +69,10 @@ module.exports = function(RED) {
|
|||||||
if (!node.acceptEmotes) return;
|
if (!node.acceptEmotes) return;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case 'm.notice':
|
||||||
|
if (!node.acceptNotices) return;
|
||||||
|
break;
|
||||||
|
|
||||||
case 'm.text':
|
case 'm.text':
|
||||||
if (!node.acceptText) return;
|
if (!node.acceptText) return;
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -89,6 +89,9 @@
|
|||||||
<label for="node-input-threadReply"><i class="fa fa-commenting-o"></i> Thread Reply</label>
|
<label for="node-input-threadReply"><i class="fa fa-commenting-o"></i> Thread Reply</label>
|
||||||
<input type="text" id="node-input-threadReply">
|
<input type="text" id="node-input-threadReply">
|
||||||
</div>
|
</div>
|
||||||
|
<div class="form-row form-tips">
|
||||||
|
If true and <code>msg.content.['m.relates_to'].event_id</code> or <code>msg.eventId</code> (parsed in that order) is provided the message will be a thread reply to the original event.
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="form-row">
|
<div class="form-row">
|
||||||
<label for="node-input-messageFormat">
|
<label for="node-input-messageFormat">
|
||||||
|
|||||||
+16
-10
@@ -1,9 +1,8 @@
|
|||||||
const {RelationType} = require("matrix-js-sdk");
|
const sdkPromise = import("matrix-js-sdk");
|
||||||
|
|
||||||
module.exports = function(RED) {
|
module.exports = function(RED) {
|
||||||
function MatrixSendImage(n) {
|
function MatrixSendImage(n) {
|
||||||
RED.nodes.createNode(this, n);
|
RED.nodes.createNode(this, n);
|
||||||
|
|
||||||
var node = this;
|
var node = this;
|
||||||
|
|
||||||
this.name = n.name;
|
this.name = n.name;
|
||||||
@@ -68,12 +67,17 @@ module.exports = function(RED) {
|
|||||||
node.status({ fill: "green", shape: "ring", text: "connected" });
|
node.status({ fill: "green", shape: "ring", text: "connected" });
|
||||||
});
|
});
|
||||||
|
|
||||||
node.on("input", function (msg) {
|
// Make the input handler async so we can await the dynamic import.
|
||||||
|
node.on("input", async function (msg) {
|
||||||
|
// Await the SDK import and get the RelationType constant.
|
||||||
|
const sdk = await sdkPromise;
|
||||||
|
const RelationType = sdk.RelationType;
|
||||||
|
|
||||||
function getToValue(msg, type, property) {
|
function getToValue(msg, type, property) {
|
||||||
let value = property;
|
let value = property;
|
||||||
if (type === "msg") {
|
if (type === "msg") {
|
||||||
value = RED.util.getMessageProperty(msg, property);
|
value = RED.util.getMessageProperty(msg, property);
|
||||||
} else if ((type === 'flow') || (type === 'global')) {
|
} else if (type === 'flow' || type === 'global') {
|
||||||
try {
|
try {
|
||||||
value = RED.util.evaluateNodeProperty(property, type, node, msg);
|
value = RED.util.evaluateNodeProperty(property, type, node, msg);
|
||||||
} catch(e2) {
|
} catch(e2) {
|
||||||
@@ -120,14 +124,14 @@ module.exports = function(RED) {
|
|||||||
} else {
|
} else {
|
||||||
if (msgType === 'msg.type') {
|
if (msgType === 'msg.type') {
|
||||||
if (!msg.type) {
|
if (!msg.type) {
|
||||||
node.error("msg.type type is set to be passed in via msg.type but was not defined", msg);
|
node.error("msg.type is set to be passed in via msg.type but was not defined", msg);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
msgType = msg.type;
|
msgType = msg.type;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (msgFormat === 'msg.format') {
|
if (msgFormat === 'msg.format') {
|
||||||
if(!msg.format) {
|
if (!Object.hasOwn(msg, 'format')) {
|
||||||
node.error("Message format is set to be passed in via msg.format but was not defined", msg);
|
node.error("Message format is set to be passed in via msg.format but was not defined", msg);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -165,9 +169,11 @@ module.exports = function(RED) {
|
|||||||
};
|
};
|
||||||
content['body'] = ' * ' + content['body'];
|
content['body'] = ' * ' + content['body'];
|
||||||
} else if (threadReply) {
|
} else if (threadReply) {
|
||||||
// if incoming message is a reply to a thread we fetch the thread parent from the m.relates_to property
|
// If incoming message is a reply to a thread we fetch the thread parent from m.relates_to,
|
||||||
// otherwise fallback to msg.eventId
|
// otherwise fallback to msg.eventId.
|
||||||
let threadParent = (msg?.content?.['m.relates_to']?.rel_type === RelationType.Thread ? msg?.content?.['m.relates_to']?.event_id : null) || msg.eventId;
|
let threadParent = (msg?.content?.['m.relates_to']?.rel_type === RelationType.Thread
|
||||||
|
? msg?.content?.['m.relates_to']?.event_id
|
||||||
|
: null) || msg.eventId;
|
||||||
if (threadParent) {
|
if (threadParent) {
|
||||||
content["m.relates_to"] = {
|
content["m.relates_to"] = {
|
||||||
"rel_type": RelationType.Thread,
|
"rel_type": RelationType.Thread,
|
||||||
@@ -202,4 +208,4 @@ module.exports = function(RED) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
RED.nodes.registerType("matrix-send-message", MatrixSendImage);
|
RED.nodes.registerType("matrix-send-message", MatrixSendImage);
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -36,7 +36,8 @@
|
|||||||
name: { value: null },
|
name: { value: null },
|
||||||
autoAcceptRoomInvites: { value: true },
|
autoAcceptRoomInvites: { value: true },
|
||||||
enableE2ee: { type: "checkbox", value: true },
|
enableE2ee: { type: "checkbox", value: true },
|
||||||
global: { type: "checkbox", value: true }
|
global: { type: "checkbox", value: true },
|
||||||
|
allowUnknownDevices: { type: "checkbox", value: false }
|
||||||
},
|
},
|
||||||
icon: "matrix.png",
|
icon: "matrix.png",
|
||||||
label: function() {
|
label: function() {
|
||||||
@@ -130,6 +131,20 @@
|
|||||||
<code style="white-space: normal;">let client = global.get("matrixClient['@bot:example.com']");</code>
|
<code style="white-space: normal;">let client = global.get("matrixClient['@bot:example.com']");</code>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="form-row">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
id="node-config-input-allowUnknownDevices"
|
||||||
|
style="width: auto; margin-left: 125px; vertical-align: top"
|
||||||
|
/>
|
||||||
|
<label for="node-config-input-allowUnknownDevices" style="width: auto">
|
||||||
|
Allow unverified devices in rooms
|
||||||
|
</label>
|
||||||
|
<div class="form-tips" style="margin-bottom: 12px;">
|
||||||
|
Allow sending messages to a room with unknown devices which have not been verified.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
$("#matrix-login-btn").on("click", function() {
|
$("#matrix-login-btn").on("click", function() {
|
||||||
function prettyPrintJson(json) {
|
function prettyPrintJson(json) {
|
||||||
|
|||||||
+47
-30
@@ -1,20 +1,33 @@
|
|||||||
const {RelationType, TimelineWindow} = require("matrix-js-sdk");
|
|
||||||
|
|
||||||
global.Olm = require('olm');
|
global.Olm = require('olm');
|
||||||
const fs = require("fs-extra");
|
const fs = require("fs-extra");
|
||||||
const sdk = require("matrix-js-sdk");
|
let RelationType, sdk, LocalStorageCryptoStore, RoomEvent, RoomMemberEvent, HttpApiEvent, ClientEvent, MemoryStore;
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
const mod = await import("matrix-js-sdk");
|
||||||
|
RelationType = mod.RelationType;
|
||||||
|
// matrix-js-sdk doesn't export a default – the top-level export is the same object:
|
||||||
|
sdk = mod;
|
||||||
|
|
||||||
|
RoomEvent = mod.RoomEvent;
|
||||||
|
RoomMemberEvent = mod.RoomMemberEvent;
|
||||||
|
HttpApiEvent = mod.HttpApiEvent;
|
||||||
|
ClientEvent = mod.ClientEvent;
|
||||||
|
MemoryStore = mod.MemoryStore;
|
||||||
|
|
||||||
|
// For LocalStorageCryptoStore, specify the file extension for Node 20+:
|
||||||
|
const cmod = await import("matrix-js-sdk/lib/crypto/store/localStorage-crypto-store.js");
|
||||||
|
LocalStorageCryptoStore = cmod.LocalStorageCryptoStore;
|
||||||
|
})();
|
||||||
|
|
||||||
const { resolve } = require('path');
|
const { resolve } = require('path');
|
||||||
const { LocalStorage } = require('node-localstorage');
|
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
|
require("abort-controller/polyfill"); // polyfill abort-controller if we don't have it
|
||||||
if (!globalThis.fetch) {
|
if (!globalThis.fetch) {
|
||||||
// polyfill fetch if we don't have it
|
// polyfill fetch if we don't have it
|
||||||
if (!globalThis.fetch) {
|
if (!globalThis.fetch) {
|
||||||
import('node-fetch').then(({ default: fetch, Headers, Request, Response }) => {
|
import('node-fetch').then(({ default: fetch, Headers, Request, Response }) => {
|
||||||
Object.assign(globalThis, { fetch, Headers, Request, Response })
|
Object.assign(globalThis, { fetch, Headers, Request, Response });
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -26,8 +39,13 @@ module.exports = function(RED) {
|
|||||||
typeof loggingSettings.console.level !== 'undefined' &&
|
typeof loggingSettings.console.level !== 'undefined' &&
|
||||||
['info','debug','trace'].indexOf(loggingSettings.console.level.toLowerCase()) >= 0
|
['info','debug','trace'].indexOf(loggingSettings.console.level.toLowerCase()) >= 0
|
||||||
) {
|
) {
|
||||||
const { logger } = require('matrix-js-sdk/lib/logger');
|
import('matrix-js-sdk/lib/logger.js')
|
||||||
|
.then(({ logger }) => {
|
||||||
logger.disableAll();
|
logger.disableAll();
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.error("Error loading logger module:", err);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function MatrixFolderNameFromUserId(name) {
|
function MatrixFolderNameFromUserId(name) {
|
||||||
@@ -56,10 +74,11 @@ module.exports = function(RED) {
|
|||||||
this.autoAcceptRoomInvites = n.autoAcceptRoomInvites;
|
this.autoAcceptRoomInvites = n.autoAcceptRoomInvites;
|
||||||
this.e2ee = n.enableE2ee || false;
|
this.e2ee = n.enableE2ee || false;
|
||||||
this.globalAccess = n.global;
|
this.globalAccess = n.global;
|
||||||
|
this.allowUnknownDevices = n.allowUnknownDevices || false;
|
||||||
this.initializedAt = new Date();
|
this.initializedAt = new Date();
|
||||||
node.initialSyncLimit = 25;
|
node.initialSyncLimit = 25;
|
||||||
|
|
||||||
// Keep track of all consumers of this node to be able to catch errors
|
// Keep track of all consumers of this node to catch errors
|
||||||
node.register = function(consumerNode) {
|
node.register = function(consumerNode) {
|
||||||
node.users[consumerNode.id] = consumerNode;
|
node.users[consumerNode.id] = consumerNode;
|
||||||
};
|
};
|
||||||
@@ -99,7 +118,7 @@ module.exports = function(RED) {
|
|||||||
device_id = this.matrixClient.getDeviceId();
|
device_id = this.matrixClient.getDeviceId();
|
||||||
|
|
||||||
if(!device_id && node.enableE2ee) {
|
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.", {})
|
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 {
|
} else {
|
||||||
if(!stored_device_id || stored_device_id !== device_id) {
|
if(!stored_device_id || stored_device_id !== device_id) {
|
||||||
node.log(`Saving Device ID (old:${stored_device_id} new:${device_id})`);
|
node.log(`Saving Device ID (old:${stored_device_id} new:${device_id})`);
|
||||||
@@ -116,7 +135,7 @@ module.exports = function(RED) {
|
|||||||
node.matrixClient.setDeviceDetails(device_id, {
|
node.matrixClient.setDeviceDetails(device_id, {
|
||||||
display_name: node.deviceLabel
|
display_name: node.deviceLabel
|
||||||
}).then(
|
}).then(
|
||||||
function(response) {},
|
function() {},
|
||||||
function(error) {
|
function(error) {
|
||||||
node.error("Failed to set device label: " + error, {});
|
node.error("Failed to set device label: " + error, {});
|
||||||
}
|
}
|
||||||
@@ -153,9 +172,7 @@ module.exports = function(RED) {
|
|||||||
localStorage: localStorage,
|
localStorage: localStorage,
|
||||||
}),
|
}),
|
||||||
userId: this.userId,
|
userId: this.userId,
|
||||||
deviceId: (this.deviceId || getStoredDeviceId(localStorage)) || undefined,
|
deviceId: (this.deviceId || getStoredDeviceId(localStorage)) || undefined
|
||||||
request
|
|
||||||
// verificationMethods: ["m.sas.v1"]
|
|
||||||
});
|
});
|
||||||
|
|
||||||
node.debug(`hasLazyLoadMembersEnabled=${node.matrixClient.hasLazyLoadMembersEnabled()}`);
|
node.debug(`hasLazyLoadMembersEnabled=${node.matrixClient.hasLazyLoadMembersEnabled()}`);
|
||||||
@@ -195,15 +212,15 @@ module.exports = function(RED) {
|
|||||||
node.matrixClient.on(RoomEvent.Timeline, async function(event, room, toStartOfTimeline, removed, data) {
|
node.matrixClient.on(RoomEvent.Timeline, async function(event, room, toStartOfTimeline, removed, data) {
|
||||||
if (toStartOfTimeline) {
|
if (toStartOfTimeline) {
|
||||||
node.log("Ignoring" + (event.isEncrypted() ? ' encrypted' : '') +" timeline event [" + (event.getContent()['msgtype'] || event.getType()) + "]: (" + room.name + ") " + event.getId() + " for reason: paginated result");
|
node.log("Ignoring" + (event.isEncrypted() ? ' encrypted' : '') +" timeline event [" + (event.getContent()['msgtype'] || event.getType()) + "]: (" + room.name + ") " + event.getId() + " for reason: paginated result");
|
||||||
return; // ignore paginated results
|
return;
|
||||||
}
|
}
|
||||||
if (!data || !data.liveEvent) {
|
if (!data || !data.liveEvent) {
|
||||||
node.log("Ignoring" + (event.isEncrypted() ? ' encrypted' : '') +" timeline event [" + (event.getContent()['msgtype'] || event.getType()) + "]: (" + room.name + ") " + event.getId() + " for reason: old message");
|
node.log("Ignoring" + (event.isEncrypted() ? ' encrypted' : '') +" timeline event [" + (event.getContent()['msgtype'] || event.getType()) + "]: (" + room.name + ") " + event.getId() + " for reason: old message");
|
||||||
return; // ignore old message (we only want live events)
|
return;
|
||||||
}
|
}
|
||||||
if(node.initializedAt > event.getDate()) {
|
if(node.initializedAt > event.getDate()) {
|
||||||
node.log("Ignoring" + (event.isEncrypted() ? ' encrypted' : '') +" timeline event [" + (event.getContent()['msgtype'] || event.getType()) + "]: (" + room.name + ") " + event.getId() + " for reason: old message before init");
|
node.log("Ignoring" + (event.isEncrypted() ? ' encrypted' : '') +" timeline event [" + (event.getContent()['msgtype'] || event.getType()) + "]: (" + room.name + ") " + event.getId() + " for reason: old message before init");
|
||||||
return; // skip events that occurred before our client initialized
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -378,7 +395,6 @@ module.exports = function(RED) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
node.matrixClient.on(HttpApiEvent.SessionLoggedOut, async function(errorObj){
|
node.matrixClient.on(HttpApiEvent.SessionLoggedOut, async function(errorObj){
|
||||||
// Example if user auth token incorrect:
|
// Example if user auth token incorrect:
|
||||||
// {
|
// {
|
||||||
@@ -401,6 +417,7 @@ module.exports = function(RED) {
|
|||||||
node.log("Initializing crypto...");
|
node.log("Initializing crypto...");
|
||||||
await node.matrixClient.initCrypto();
|
await node.matrixClient.initCrypto();
|
||||||
node.matrixClient.getCrypto().globalBlacklistUnverifiedDevices = false; // prevent errors from unverified devices
|
node.matrixClient.getCrypto().globalBlacklistUnverifiedDevices = false; // prevent errors from unverified devices
|
||||||
|
node.matrixClient.getCrypto().globalErrorOnUnknownDevices = !node.allowUnknownDevices;
|
||||||
}
|
}
|
||||||
node.log("Connecting to Matrix server...");
|
node.log("Connecting to Matrix server...");
|
||||||
await node.matrixClient.startClient({
|
await node.matrixClient.startClient({
|
||||||
@@ -450,7 +467,7 @@ module.exports = function(RED) {
|
|||||||
node.error("Auth check failed: " + err, {});
|
node.error("Auth check failed: " + err, {});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
);
|
||||||
})();
|
})();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -475,20 +492,19 @@ module.exports = function(RED) {
|
|||||||
deviceId = req.body.deviceId || undefined,
|
deviceId = req.body.deviceId || undefined,
|
||||||
displayName = req.body.displayName || undefined;
|
displayName = req.body.displayName || undefined;
|
||||||
|
|
||||||
const matrixClient = sdk.createClient({
|
(async () => {
|
||||||
|
const mod = await import("matrix-js-sdk");
|
||||||
|
const matrixClient = mod.createClient({
|
||||||
baseUrl: baseUrl,
|
baseUrl: baseUrl,
|
||||||
deviceId: deviceId,
|
deviceId: deviceId,
|
||||||
timelineSupport: true,
|
timelineSupport: true,
|
||||||
localTimeoutMs: '30000',
|
localTimeoutMs: '30000'
|
||||||
request
|
|
||||||
});
|
});
|
||||||
|
|
||||||
new TimelineWindow()
|
|
||||||
|
|
||||||
matrixClient.timelineSupport = true;
|
matrixClient.timelineSupport = true;
|
||||||
|
|
||||||
matrixClient.login(
|
matrixClient.login('m.login.password', {
|
||||||
'm.login.password', {
|
|
||||||
identifier: {
|
identifier: {
|
||||||
type: 'm.id.user',
|
type: 'm.id.user',
|
||||||
user: userId,
|
user: userId,
|
||||||
@@ -512,7 +528,11 @@ module.exports = function(RED) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
})().catch(err => {
|
||||||
|
res.json({ result: 'error', message: err.toString() });
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
function upgradeDirectoryIfNecessary(node, storageDir) {
|
function upgradeDirectoryIfNecessary(node, storageDir) {
|
||||||
let oldStorageDir = './matrix-local-storage',
|
let oldStorageDir = './matrix-local-storage',
|
||||||
@@ -553,9 +573,6 @@ module.exports = function(RED) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* If a device ID is stored we will use that for the client
|
|
||||||
*/
|
|
||||||
function getStoredDeviceId(localStorage) {
|
function getStoredDeviceId(localStorage) {
|
||||||
let deviceId = localStorage.getItem('my_device_id');
|
let deviceId = localStorage.getItem('my_device_id');
|
||||||
if(deviceId === "null" || !deviceId) {
|
if(deviceId === "null" || !deviceId) {
|
||||||
@@ -571,4 +588,4 @@ module.exports = function(RED) {
|
|||||||
localStorage.setItem('my_device_id', deviceId);
|
localStorage.setItem('my_device_id', deviceId);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
Reference in New Issue
Block a user