mirror of
https://github.com/Skylar-Tech/node-red-contrib-matrix-chat.git
synced 2025-04-20 04:53:06 -06:00
- Fix Room Pagination node event data being unaccessible #28
- Add new node `Get Event` that will give you room data for a corresponding roomId and eventId #117 - Add new node `Fetch Event Relations` that can paginate through events related to another event #119 - README updates
This commit is contained in:
parent
6bbd1d5119
commit
02826e2769
83
README.md
83
README.md
@ -1,75 +1,72 @@
|
||||
# node-red-contrib-matrix-chat
|
||||
[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.***
|
||||
***Currently in beta. Please report any issues in our repository to help us reach a stable, well-tested release. Breaking changes may occur before our first stable release, so be sure to check the 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)
|
||||
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)
|
||||
|
||||
### Features
|
||||
|
||||
The following is supported from this package:
|
||||
Supported functionality in this package includes:
|
||||
|
||||
- End-to-end encryption
|
||||
- [Currently a WIP](#end-to-end-encryption-notes)
|
||||
- Can also use [pantalaimon](https://github.com/matrix-org/pantalaimon) as an alternative solution to E2EE (if you need multiple sessions synced up with keys)
|
||||
- Receive events from a room (messages, reactions, images, audio, locations, and files) whether encrypted or not
|
||||
- Fetch and modify room state events (for changing room settings)
|
||||
- Paginate room history
|
||||
- Send Images/Files (sending files to e2ee room doesn't currently encrypt them yet)
|
||||
- Edit messages
|
||||
- Send typing events (Bot is typing ...)
|
||||
- Delete events (messages, reactions, etc)
|
||||
- Decrypt files in e2ee rooms
|
||||
- Send HTML/Plain Text Message/Notice
|
||||
- React to messages
|
||||
- Register user's on closed registration Synapse servers using `registration_shared_secret` (Admin Only)
|
||||
- List out users on a Synapse server (Admin Only)
|
||||
- Get WhoIs info for a Synapse user (Admin Only)
|
||||
- Add/Edit Synapse users using the v2 API (requires a pre-existing admin account)
|
||||
- Deactivate users on Synapse servers (Admin Only)
|
||||
- Get a user list from a room
|
||||
- Kick user from room
|
||||
- Ban user from room
|
||||
- Join, Create, Invite, and Leave rooms
|
||||
- Synapse admin API to force add user to room (requires bot to be in same room already)
|
||||
- **End-to-end encryption (E2EE)**
|
||||
- [Work in progress](#end-to-end-encryption-notes)
|
||||
- Alternative: Use [Pantalaimon](https://github.com/matrix-org/pantalaimon) for E2EE key synchronization across sessions
|
||||
- **Receive events** from rooms: Messages, reactions, images, audio, locations, files, encrypted or unencrypted
|
||||
- **Fetch/modify room state**: Update room settings
|
||||
- **Paginate room history**
|
||||
- **Send files** (encryption support for files coming soon)
|
||||
- **Send/edit messages** (supports plain text and HTML formats)
|
||||
- **Send typing notifications**
|
||||
- **Delete events** (messages, reactions, etc.)
|
||||
- **Decrypt files** in E2EE rooms
|
||||
- **React to messages**
|
||||
- **Admin tools**:
|
||||
- Register users on closed Synapse servers (`registration_shared_secret`)
|
||||
- Manage users, including listing, adding, editing, deactivating (Synapse API)
|
||||
- Force-add users to rooms
|
||||
- **Room management**: Invite, kick, ban, join, create, and leave rooms
|
||||
|
||||
|
||||
Therefore, you can easily build a bot, chat relay, or administrate your Matrix server from within [Node-RED](https://nodered.org/).
|
||||
These features allow you to easily build bots, set up chat relays, or even administrate your Matrix server directly from [Node-RED](https://nodered.org/).
|
||||
|
||||
### 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:
|
||||
Install through Node-RED's UI by searching for `node-red-contrib-matrix-chat`, or use the following command inside your Node-RED directory:
|
||||
|
||||
```bash
|
||||
npm install node-red-contrib-matrix-chat
|
||||
```
|
||||
|
||||
### Usage
|
||||
We have examples! [Check them out](https://github.com/Skylar-Tech/node-red-contrib-matrix-chat/tree/master/examples#readme)
|
||||
|
||||
#### Extra functionality
|
||||
You are not limited by just the nodes we have created. If you turn on global access when setting up your Matrix Client you can access the client directly from any function node to write your own logic.
|
||||
Explore our [examples](https://github.com/Skylar-Tech/node-red-contrib-matrix-chat/tree/master/examples#readme) to see the module in action.
|
||||
|
||||
View an example [here](https://github.com/Skylar-Tech/node-red-contrib-matrix-chat/tree/master/examples#use-function-node-to-run-any-command)
|
||||
#### Extending functionality
|
||||
|
||||
You're not limited to just the nodes we've created. Enable global access in your Matrix Client to directly interact with the client from function nodes and create custom 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).
|
||||
- This module doesn't handle encryption key synchronization between devices. It’s recommended to use the bot exclusively in Node-RED to prevent issues with E2EE messages.
|
||||
- **Storage:** Keys for E2EE are saved in a folder called `matrix-client-storage` within your Node-RED directory. Back up this folder regularly! If lost, you won’t be able to decrypt messages from E2EE rooms.
|
||||
- To move your bot to a different installation, migrate this folder and ensure the old and new clients don't run simultaneously.
|
||||
|
||||
Want to contribute? Any help on getting the last pieces of e2ee figured out would be greatly appreciated :)
|
||||
Interested in helping? Contributions to finalize E2EE support are welcome!
|
||||
|
||||
### Generate user
|
||||
You will need a user to use this module. Luckily this module comes with a node that allows you to register users to a homeserver using the secret registration endpoint. This is perfect because it returns an `access_token` as well as a `device_id` which is exactly what we need.
|
||||
|
||||
[Click here](https://github.com/Skylar-Tech/node-red-contrib-matrix-chat/tree/master/examples#readme) to see how to generate a user using secret registration
|
||||
### Registering a User
|
||||
|
||||
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).
|
||||
|
||||
### Other Packages
|
||||
|
||||
- [node-red-contrib-gamedig](https://www.npmjs.com/package/node-red-contrib-gamedig) - Query game servers from Node-RED!
|
||||
- [node-red-contrib-gamedig](https://www.npmjs.com/package/node-red-contrib-gamedig) - Query game servers from Node-RED.
|
||||
|
||||
### Contributing
|
||||
All contributions are welcome! If you do add a feature please do a pull request so that everyone benefits :)
|
||||
|
||||
Sharing is caring!
|
||||
We welcome all contributions! Please submit a pull request if you add a feature so the whole community can benefit.
|
||||
|
||||
**Sharing is caring!**
|
@ -265,3 +265,6 @@ If you say "force_join @test:example.com !320j90mf0394f:example.com" the bot wil
|
||||
Note: This requires the bot to be a server admin. This also only works for rooms on the same server.
|
||||
|
||||

|
||||
|
||||
### Paginate room history
|
||||
|
||||
|
@ -50,7 +50,9 @@
|
||||
"matrix-synapse-deactivate-user": "src/matrix-synapse-deactivate-user.js",
|
||||
"matrix-synapse-join-room": "src/matrix-synapse-join-room.js",
|
||||
"matrix-whois-user": "src/matrix-whois-user.js",
|
||||
"matrix-paginate-room": "src/matrix-paginate-room.js"
|
||||
"matrix-paginate-room": "src/matrix-paginate-room.js",
|
||||
"matrix-get-event": "src/matrix-get-event.js",
|
||||
"matrix-event-relations": "src/matrix-event-relations.js"
|
||||
}
|
||||
},
|
||||
"engines": {
|
||||
|
230
src/matrix-event-relations.html
Normal file
230
src/matrix-event-relations.html
Normal file
@ -0,0 +1,230 @@
|
||||
<script type="text/javascript">
|
||||
RED.nodes.registerType('matrix-fetch-relations', {
|
||||
category: 'matrix',
|
||||
color: '#00b7ca',
|
||||
icon: "matrix.png",
|
||||
outputLabels: ["Related Events", "Error"],
|
||||
inputs: 1,
|
||||
outputs: 2,
|
||||
defaults: {
|
||||
name: { value: null },
|
||||
server: { type: "matrix-server-config" },
|
||||
roomType: { value: "msg" },
|
||||
roomValue: { value: "topic" },
|
||||
eventIdType: { value: "msg" },
|
||||
eventIdValue: { value: "eventId" },
|
||||
relationTypeType: { value: "json" },
|
||||
relationTypeValue: { value: "null" },
|
||||
eventTypeType: { value: "json" },
|
||||
eventTypeValue: { value: "null" },
|
||||
directionType: { value: "str" },
|
||||
directionValue: { value: "b" },
|
||||
limitType: { value: "json" },
|
||||
limitValue: { value: "null" },
|
||||
recurseType: { value: "bool" },
|
||||
recurseValue: { value: "false" },
|
||||
fromType: { value: "msg" },
|
||||
fromValue: { value: "payload.next_batch" },
|
||||
toType: { value: "json" },
|
||||
toValue: { value: "null" },
|
||||
},
|
||||
label: function () {
|
||||
return this.name || "Fetch Event Relations";
|
||||
},
|
||||
paletteLabel: 'Fetch Event Relations',
|
||||
oneditprepare: function () {
|
||||
$("#node-input-room").typedInput({
|
||||
type: this.roomType,
|
||||
types: ['msg', 'flow', 'global', 'str'],
|
||||
})
|
||||
.typedInput('value', this.roomValue)
|
||||
.typedInput('type', this.roomType);
|
||||
|
||||
$("#node-input-eventId").typedInput({
|
||||
types: ['msg', 'flow', 'global', 'str'],
|
||||
})
|
||||
.typedInput('value', this.eventIdValue)
|
||||
.typedInput('type', this.eventIdType);
|
||||
|
||||
$("#node-input-relationType").typedInput({
|
||||
types: ['msg', 'flow', 'global', 'str', 'json'],
|
||||
})
|
||||
.typedInput('value', this.relationTypeValue)
|
||||
.typedInput('type', this.relationTypeType);
|
||||
|
||||
$("#node-input-eventType").typedInput({
|
||||
types: ['msg', 'flow', 'global', 'str', 'json'],
|
||||
})
|
||||
.typedInput('value', this.eventTypeValue)
|
||||
.typedInput('type', this.eventTypeType);
|
||||
|
||||
$("#node-input-direction").typedInput({
|
||||
types: ['msg', 'flow', 'global', 'str'],
|
||||
})
|
||||
.typedInput('value', this.directionValue)
|
||||
.typedInput('type', this.directionType);
|
||||
|
||||
$("#node-input-limit").typedInput({
|
||||
types: ['msg', 'flow', 'global', 'num', 'json'],
|
||||
})
|
||||
.typedInput('value', this.limitValue)
|
||||
.typedInput('type', this.limitType);
|
||||
|
||||
$("#node-input-recurse").typedInput({
|
||||
types: ['msg', 'flow', 'global', 'bool', 'json'],
|
||||
})
|
||||
.typedInput('value', this.recurseValue)
|
||||
.typedInput('type', this.recurseType);
|
||||
|
||||
$("#node-input-from").typedInput({
|
||||
types: ['msg', 'flow', 'global', 'str', 'json'],
|
||||
})
|
||||
.typedInput('value', this.fromValue)
|
||||
.typedInput('type', this.fromType);
|
||||
|
||||
$("#node-input-to").typedInput({
|
||||
types: ['msg', 'flow', 'global', 'str', 'json'],
|
||||
})
|
||||
.typedInput('value', this.toValue)
|
||||
.typedInput('type', this.toType);
|
||||
},
|
||||
oneditsave: function () {
|
||||
this.roomType = $("#node-input-room").typedInput('type');
|
||||
this.roomValue = $("#node-input-room").typedInput('value');
|
||||
this.eventIdType = $("#node-input-eventId").typedInput('type');
|
||||
this.eventIdValue = $("#node-input-eventId").typedInput('value');
|
||||
this.relationTypeType = $("#node-input-relationType").typedInput('type');
|
||||
this.relationTypeValue = $("#node-input-relationType").typedInput('value');
|
||||
this.eventTypeType = $("#node-input-eventType").typedInput('type');
|
||||
this.eventTypeValue = $("#node-input-eventType").typedInput('value');
|
||||
this.directionType = $("#node-input-direction").typedInput('type');
|
||||
this.directionValue = $("#node-input-direction").typedInput('value');
|
||||
this.limitType = $("#node-input-limit").typedInput('type');
|
||||
this.limitValue = $("#node-input-limit").typedInput('value');
|
||||
this.recurseType = $("#node-input-recurse").typedInput('type');
|
||||
this.recurseValue = $("#node-input-recurse").typedInput('value');
|
||||
this.fromType = $("#node-input-from").typedInput('type');
|
||||
this.fromValue = $("#node-input-from").typedInput('value');
|
||||
this.toType = $("#node-input-to").typedInput('type');
|
||||
this.toValue = $("#node-input-to").typedInput('value');
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<script type="text/html" data-template-name="matrix-fetch-relations">
|
||||
<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-server"></i> Matrix Server</label>
|
||||
<input type="text" id="node-input-server">
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<label for="node-input-room"><i class="fa fa-comments"></i> Room</label>
|
||||
<input type="text" id="node-input-room">
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<label for="node-input-eventId"><i class="fa fa-key"></i> Event ID</label>
|
||||
<input type="text" id="node-input-eventId">
|
||||
<div class="form-tips" style="margin-top: 10px;">The event ID for which relations are being fetched.</div>
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<label for="node-input-relationType"><i class="fa fa-link"></i> Relation Type</label>
|
||||
<input type="text" id="node-input-relationType">
|
||||
<div class="form-tips" style="margin-top: 10px;">The type of relations (e.g., m.in_reply_to, m.replace, m.annotation, m.thread, m.reference) to be fetched for the event. You can read more in the <a href="https://spec.matrix.org/v1.11/client-server-api/#relationship-types" target="_blank">Matrix specification</a>.</div>
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<label for="node-input-eventType"><i class="fa fa-file"></i> Event Type</label>
|
||||
<input type="text" id="node-input-eventType">
|
||||
<div class="form-tips" style="margin-top: 10px;">The type of event (e.g., m.reaction, m.annotation) to be retrieved.</div>
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<label for="node-input-direction"><i class="fa fa-arrow-left"></i> Direction</label>
|
||||
<input type="text" id="node-input-direction">
|
||||
<div class="form-tips" style="margin-top: 10px;">Set the direction of pagination. Can be "b" for backwards or "f" for forwards. Defaults to "b".</div>
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<label for="node-input-limit"><i class="fa fa-arrow-left"></i> Limit</label>
|
||||
<input type="text" id="node-input-limit">
|
||||
<div class="form-tips" style="margin-top: 10px;">The maximum number of results to return in a single chunk. Recommended to leave as null to use the server default.</div>
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<label for="node-input-recurse"><i class="fa fa-arrow-left"></i> Recurse</label>
|
||||
<input type="text" id="node-input-recurse">
|
||||
<div class="form-tips" style="margin-top: 10px;">
|
||||
Whether to additionally include events which only relate indirectly to the given event, i.e. events related to the given event via two or more direct relationships.
|
||||
<br><br>
|
||||
If set to false, only events which have a direct relation with the given event will be included.
|
||||
<br><br>
|
||||
If set to true, events which have an indirect relation with the given event will be included additionally up to a certain depth level. Homeservers SHOULD traverse at least 3 levels of relationships. Implementations MAY perform more but MUST be careful to not infinitely recurse.
|
||||
<br><br>
|
||||
The default value is false.
|
||||
<br><br>
|
||||
Added in v1.10
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<label for="node-input-from"><i class="fa fa-arrow-left"></i> From</label>
|
||||
<input type="text" id="node-input-from">
|
||||
<div class="form-tips" style="margin-top: 10px;">
|
||||
The pagination token to start returning results from. If not supplied, results start at the most recent topological event known to the server.
|
||||
<br><br>
|
||||
Can be a next_batch or prev_batch token from a previous call, or a returned start token from /messages, or a next_batch token from /sync.
|
||||
<br><br>
|
||||
Defaults to msg.payload.next_batch to easily fetch the next batch in a recursive loop.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<label for="node-input-to"><i class="fa fa-arrow-left"></i> To</label>
|
||||
<input type="text" id="node-input-to">
|
||||
<div class="form-tips" style="margin-top: 10px;">
|
||||
The pagination token to stop returning results at. If not supplied, results continue up to limit or until there are no more events.
|
||||
<br><br>
|
||||
Like from, this can be a previous token from a prior call to this endpoint or from /messages or /sync.
|
||||
</div>
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script type="text/html" data-help-name="matrix-fetch-relations">
|
||||
<p>
|
||||
The Matrix Fetch Relations node allows you to retrieve event relations for a specific event within a Matrix room.
|
||||
</p>
|
||||
|
||||
<h3>Inputs</h3>
|
||||
<ul>
|
||||
<li><strong>msg</strong> (<em>default</em>): Triggers the fetching of relations based on the provided parameters.</li>
|
||||
</ul>
|
||||
|
||||
<h3>Outputs</h3>
|
||||
<ul>
|
||||
<li>
|
||||
<strong>Output 1 (Related Events)</strong>: Returns an array of events with their relations.
|
||||
Each event contains details such as:
|
||||
<ul>
|
||||
<li><code>msg.payload.chunk</code> - Array of related events.</li>
|
||||
<li><code>msg.payload.prev_batch</code> - The previous batch token for pagination.</li>
|
||||
<li><code>msg.payload.next_batch</code> - The next batch token for pagination.</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<strong>Output 2 (Error)</strong>: If an error occurs during the fetch process, the error message is sent to this output.
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<h3>Dynamic Properties</h3>
|
||||
<p>Properties such as <strong>Room ID</strong>, <strong>Event ID</strong>, <strong>Relation Type</strong>, and <strong>Event Type</strong> can be dynamically set using message, flow, or global context variables.</p>
|
||||
|
||||
<h3>Usage</h3>
|
||||
<p>To fetch event relations, trigger this node with a <code>msg</code> input. The node will fetch the relations for the given event ID and return the related events.</p>
|
||||
</script>
|
108
src/matrix-event-relations.js
Normal file
108
src/matrix-event-relations.js
Normal file
@ -0,0 +1,108 @@
|
||||
const {RelationType, EventType, Direction} = require("matrix-js-sdk");
|
||||
|
||||
module.exports = function(RED) {
|
||||
function MatrixFetchRelations(n) {
|
||||
RED.nodes.createNode(this, n);
|
||||
|
||||
let node = this;
|
||||
this.name = n.name;
|
||||
this.server = RED.nodes.getNode(n.server);
|
||||
this.roomType = n.roomType;
|
||||
this.roomValue = n.roomValue;
|
||||
this.eventIdType = n.eventIdType;
|
||||
this.eventIdValue = n.eventIdValue;
|
||||
this.relationTypeType = n.relationTypeType;
|
||||
this.relationTypeValue = n.relationTypeValue;
|
||||
this.eventTypeType = n.eventTypeType;
|
||||
this.eventTypeValue = n.eventTypeValue;
|
||||
this.directionType = n.directionType;
|
||||
this.directionValue = n.directionValue;
|
||||
this.limitType = n.limitType;
|
||||
this.limitValue = n.limitValue;
|
||||
this.recurseType = n.recurseType;
|
||||
this.recurseValue = n.recurseValue;
|
||||
this.fromType = n.fromType;
|
||||
this.fromValue = n.fromValue;
|
||||
this.toType = n.toType;
|
||||
this.toValue = n.toValue;
|
||||
|
||||
node.status({ fill: "red", shape: "ring", text: "disconnected" });
|
||||
|
||||
if (!node.server) {
|
||||
node.error("No configuration node", {});
|
||||
return;
|
||||
}
|
||||
node.server.register(node);
|
||||
|
||||
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", async function(msg) {
|
||||
if (!node.server || !node.server.matrixClient) {
|
||||
node.error("No matrix server selected", msg);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
function evaluateNodePropertySafe(value, type, node, msg) {
|
||||
try {
|
||||
return RED.util.evaluateNodeProperty(value, type, node, msg);
|
||||
} catch (e) {
|
||||
if (e instanceof TypeError) {
|
||||
return undefined; // Handle TypeError and return undefined
|
||||
}
|
||||
throw e; // Re-throw other errors to prevent masking issues
|
||||
}
|
||||
}
|
||||
|
||||
let roomId = RED.util.evaluateNodeProperty(node.roomValue, node.roomType, node, msg),
|
||||
eventId = RED.util.evaluateNodeProperty(node.eventIdValue, node.eventIdType, node, msg),
|
||||
relationType = RED.util.evaluateNodeProperty(node.relationTypeValue, node.relationTypeType, node, msg),
|
||||
eventType = RED.util.evaluateNodeProperty(node.eventTypeValue, node.eventTypeType, node, msg),
|
||||
direction = RED.util.evaluateNodeProperty(node.directionValue, node.directionType, node, msg) || Direction.Backward,
|
||||
limit = RED.util.evaluateNodeProperty(node.limitValue, node.limitType, node, msg),
|
||||
recurse = RED.util.evaluateNodeProperty(node.recurseValue, node.recurseType, node, msg),
|
||||
from = evaluateNodePropertySafe(node.fromValue, node.fromType, node, msg),
|
||||
to = evaluateNodePropertySafe(node.toValue, node.toType, node, msg);
|
||||
|
||||
let opts = { dir: direction };
|
||||
if(limit) {
|
||||
opts.limit = limit;
|
||||
}
|
||||
if(recurse === true || recurse === false) {
|
||||
opts.recurse = recurse;
|
||||
}
|
||||
if(from) {
|
||||
opts.from = from;
|
||||
}
|
||||
if(to) {
|
||||
opts.to = to;
|
||||
}
|
||||
|
||||
msg.payload = await node.server.matrixClient.fetchRelations(
|
||||
roomId,
|
||||
eventId,
|
||||
relationType || null,
|
||||
eventType || null,
|
||||
opts
|
||||
);
|
||||
node.send([msg, null]);
|
||||
} catch (e) {
|
||||
msg.error = `Event relations pagination error: ${e.stack}`;
|
||||
node.error(msg.error, msg);
|
||||
node.send([null, msg]);
|
||||
}
|
||||
});
|
||||
|
||||
node.on("close", function() {
|
||||
node.server.deregister(node);
|
||||
});
|
||||
}
|
||||
|
||||
RED.nodes.registerType("matrix-fetch-relations", MatrixFetchRelations);
|
||||
};
|
103
src/matrix-get-event.html
Normal file
103
src/matrix-get-event.html
Normal file
@ -0,0 +1,103 @@
|
||||
<script type="text/javascript">
|
||||
RED.nodes.registerType('matrix-get-event',{
|
||||
category: 'matrix',
|
||||
color: '#00b7ca',
|
||||
icon: "matrix.png",
|
||||
outputLabels: ["success", "error"],
|
||||
inputs: 1,
|
||||
outputs: 2,
|
||||
defaults: {
|
||||
name: { value: null },
|
||||
server: { type: "matrix-server-config" },
|
||||
roomIdType: { value: "msg" },
|
||||
roomIdValue: { value: "topic" },
|
||||
eventIdType: { value: "msg" },
|
||||
eventIdValue: { value: "eventId" }
|
||||
},
|
||||
label: function() {
|
||||
return this.name || "Get Event";
|
||||
},
|
||||
oneditprepare: function() {
|
||||
$("#node-input-roomId").typedInput({
|
||||
type: this.roomIdType,
|
||||
types:['msg','flow','global','str'],
|
||||
}).typedInput('value', this.roomIdValue);
|
||||
|
||||
$("#node-input-eventId").typedInput({
|
||||
type: this.eventIdType,
|
||||
types:['msg','flow','global','str'],
|
||||
}).typedInput('value', this.eventIdValue);
|
||||
},
|
||||
oneditsave: function() {
|
||||
this.roomIdType = $("#node-input-roomId").typedInput('type');
|
||||
this.roomIdValue = $("#node-input-roomId").typedInput('value');
|
||||
this.eventIdType = $("#node-input-eventId").typedInput('type');
|
||||
this.eventIdValue = $("#node-input-eventId").typedInput('value');
|
||||
},
|
||||
paletteLabel: 'Get Event'
|
||||
});
|
||||
</script>
|
||||
|
||||
<script type="text/html" data-template-name="matrix-get-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-file"></i> Room ID</label>
|
||||
<input type="text" id="node-input-roomId">
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<label for="node-input-eventId"><i class="fa fa-file"></i> Event ID</label>
|
||||
<input type="text" id="node-input-eventId">
|
||||
</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-get-event">
|
||||
<h3>Details</h3>
|
||||
<p>Get an event from a Matrix room using the Room ID and Event ID. Returns the event object as the payload.</p>
|
||||
|
||||
<h3>Inputs</h3>
|
||||
<dl class="message-properties">
|
||||
<dt>msg.payload.roomId <span class="property-type">string</span></dt>
|
||||
<dd>The Room ID from which the event will be fetched.</dd>
|
||||
|
||||
<dt>msg.payload.eventId <span class="property-type">string</span></dt>
|
||||
<dd>The Event ID of the message to fetch.</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>The fetched event object.</dd>
|
||||
</dl>
|
||||
</li>
|
||||
<li>Error
|
||||
<dl class="message-properties">
|
||||
<dt>msg.payload <span class="property-type">string</span></dt>
|
||||
<dd>Error details.</dd>
|
||||
</dl>
|
||||
</li>
|
||||
</ol>
|
||||
</script>
|
87
src/matrix-get-event.js
Normal file
87
src/matrix-get-event.js
Normal file
@ -0,0 +1,87 @@
|
||||
module.exports = function(RED) {
|
||||
function MatrixGetEvent(n) {
|
||||
RED.nodes.createNode(this, n);
|
||||
|
||||
let node = this;
|
||||
|
||||
this.name = n.name;
|
||||
this.server = RED.nodes.getNode(n.server);
|
||||
this.roomIdType = n.roomIdType;
|
||||
this.roomIdValue = n.roomIdValue;
|
||||
this.eventIdType = n.eventIdType;
|
||||
this.eventIdValue = n.eventIdValue;
|
||||
|
||||
node.status({ fill: "red", shape: "ring", text: "disconnected" });
|
||||
|
||||
if (!node.server) {
|
||||
node.error("No configuration node", {});
|
||||
return;
|
||||
}
|
||||
node.server.register(node);
|
||||
|
||||
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', async function(msg) {
|
||||
if (! node.server || ! node.server.matrixClient) {
|
||||
node.error("No matrix server selected", msg);
|
||||
node.send([null, msg]);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!node.server.isConnected()) {
|
||||
node.error("Matrix server connection is currently closed", msg);
|
||||
node.send([null, msg]);
|
||||
return;
|
||||
}
|
||||
|
||||
function getToValue(msg, type, property) {
|
||||
let value = property;
|
||||
if (type === "msg") {
|
||||
value = RED.util.getMessageProperty(msg, property);
|
||||
} else if ((type === 'flow') || (type === 'global')) {
|
||||
try {
|
||||
value = RED.util.evaluateNodeProperty(property, type, node, msg);
|
||||
} catch(e2) {
|
||||
throw new Error("Invalid value evaluation");
|
||||
}
|
||||
} else if(type === "bool") {
|
||||
value = (property === 'true');
|
||||
} else if(type === "num") {
|
||||
value = Number(property);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
try {
|
||||
let roomId = getToValue(msg, node.roomIdType, node.roomIdValue),
|
||||
eventId = getToValue(msg, node.eventIdType, node.eventIdValue);
|
||||
|
||||
if(!roomId) {
|
||||
node.error('Missing roomId', msg);
|
||||
return;
|
||||
} else if(!eventId) {
|
||||
node.error('Missing eventId', msg);
|
||||
return;
|
||||
}
|
||||
|
||||
msg.payload = await node.server.matrixClient.fetchRoomEvent(roomId, eventId);
|
||||
node.send([msg, null]);
|
||||
} catch(e) {
|
||||
node.error("Failed to get event " + msg.topic + ": " + e, msg);
|
||||
msg.payload = e;
|
||||
node.send([null, msg]);
|
||||
}
|
||||
});
|
||||
|
||||
node.on("close", function() {
|
||||
node.server.deregister(node);
|
||||
});
|
||||
}
|
||||
RED.nodes.registerType("matrix-get-event", MatrixGetEvent);
|
||||
}
|
@ -3,7 +3,7 @@
|
||||
category: 'matrix',
|
||||
color: '#00b7ca',
|
||||
icon: "matrix.png",
|
||||
outputLabels: ["message"],
|
||||
outputLabels: ["Paginated Data", "Error"],
|
||||
inputs: 1,
|
||||
outputs: 2,
|
||||
defaults: {
|
||||
@ -78,207 +78,56 @@
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<label for="node-input-paginateKey"><i class="fa fa-comments"></i> Pagination Key</label>
|
||||
<label for="node-input-paginateKey"><i class="fa fa-key"></i> Pagination Key</label>
|
||||
<input type="text" id="node-input-paginateKey">
|
||||
<div class="form-tips" style="margin-top: 10px;">A unique key to identify the current pagination session. If not provided, a new key will be generated for each session.</div>
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<label for="node-input-paginateBackwards"><i class="fa fa-commenting-o"></i> Paginate Backwards</label>
|
||||
<label for="node-input-paginateBackwards"><i class="fa fa-arrow-left"></i> Paginate Backwards</label>
|
||||
<input type="text" id="node-input-paginateBackwards">
|
||||
<div class="form-tips" style="margin-top: 10px;">Set to true to paginate backwards (older events). Set to false to paginate forwards (newer events).</div>
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<label for="node-input-pageSize"><i class="fa fa-commenting-o"></i> Page Size</label>
|
||||
<label for="node-input-pageSize"><i class="fa fa-list"></i> Page Size</label>
|
||||
<input type="text" id="node-input-pageSize">
|
||||
<div class="form-tips" style="margin-top: 10px;">Set the number of events to retrieve per pagination call. It's recommended to keep this value at or below 25 to match the current initial synchronization limit. Adjust this based on your server’s load and capacity to avoid throttling or performance issues.</div>
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script type="text/html" data-help-name="matrix-paginate-room">
|
||||
<p>Receive events from Matrix.</p>
|
||||
<p>
|
||||
The Matrix Paginate Room node allows you to retrieve historical or future events from a Matrix room, moving forwards or backwards through the event timeline.
|
||||
</p>
|
||||
|
||||
<h3>Inputs</h3>
|
||||
<ul>
|
||||
<li><strong>msg</strong> (<em>default</em>): Triggers the pagination action based on the provided room and parameters.</li>
|
||||
</ul>
|
||||
|
||||
<h3>Outputs</h3>
|
||||
<ul class="node-ports">
|
||||
<li>Always Returned
|
||||
<dl class="message-properties">
|
||||
<dt>msg.type <span class="property-type">string</span></dt>
|
||||
<dd>
|
||||
the message type. For example this will be either m.text, m.reaction, m.file, m.image, etc
|
||||
</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>
|
||||
</dl>
|
||||
|
||||
<dl class="message-properties">
|
||||
<dt>msg.redacted <span class="property-type">bool</span></dt>
|
||||
<dd> returns true if the message was redacted (such as deleted by the user).</dd>
|
||||
</dl>
|
||||
|
||||
<dl class="message-properties">
|
||||
<dt>msg.payload <span class="property-type">string</span></dt>
|
||||
<dd>the body from the message's content.</dd>
|
||||
</dl>
|
||||
|
||||
<dl class="message-properties">
|
||||
<dt>msg.userId <span class="property-type">string</span></dt>
|
||||
<dd>the User ID of the message sender. Example: @john:matrix.org</dd>
|
||||
</dl>
|
||||
|
||||
<dl class="message-properties">
|
||||
<dt>msg.topic <span class="property-type">string</span></dt>
|
||||
<dd>the ID of the room. Example: !OGEhHVWSdvArJzumhm:matrix.org</dd>
|
||||
</dl>
|
||||
|
||||
<dl class="message-properties">
|
||||
<dt>msg.event <span class="property-type">object</span></dt>
|
||||
<dd>the event object returned by the Matrix server</dd>
|
||||
</dl>
|
||||
|
||||
<dl class="message-properties">
|
||||
<dt>msg.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>
|
||||
</dl>
|
||||
<ul>
|
||||
<li>
|
||||
<strong>Output 1 (Paginated Data)</strong>: Returns an array of events if more messages are available. Each event contains details such as:
|
||||
<ul>
|
||||
<li><code>msg.encrypted</code> (boolean) - Indicates if the message was encrypted (end-to-end encryption).</li>
|
||||
<li><code>msg.redacted</code> (boolean) - Indicates if the message was redacted (deleted or hidden).</li>
|
||||
<li><code>msg.payload</code> (string) - The message body or content.</li>
|
||||
<li><code>msg.userId</code> (string) - The user ID of the message sender.</li>
|
||||
<li><code>msg.topic</code> (string) - The room ID of the message's origin.</li>
|
||||
<li><code>msg.eventId</code> (string) - The event ID.</li>
|
||||
<li><code>msg.type</code> (string) - The type of message (e.g., <code>m.text</code>, <code>m.image</code>, <code>m.reaction</code>, etc.).</li>
|
||||
</ul>
|
||||
</li>
|
||||
|
||||
<li><code>msg.type</code> == '<strong>m.text</strong>'
|
||||
<div class="form-tips" style="margin-bottom: 12px;">
|
||||
Doesn't return anything extra
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li><code>msg.type</code> == '<strong>m.reaction</strong>'
|
||||
<dl class="message-properties">
|
||||
<dt>msg.referenceEventId <span class="property-type">string</span></dt>
|
||||
<dd>the message that the reaction relates to</dd>
|
||||
</dl>
|
||||
|
||||
<dl class="message-properties">
|
||||
<dt>msg.payload <span class="property-type">string</span></dt>
|
||||
<dd>the key of the reaction's content</dd>
|
||||
</dl>
|
||||
</li>
|
||||
|
||||
<li><code>msg.type</code> == '<strong>m.emote</strong>'
|
||||
<div class="form-tips" style="margin-bottom: 12px;">
|
||||
Doesn't return anything extra
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li><code>msg.type</code> == '<strong>m.sticker</strong>'
|
||||
<dl class="message-properties">
|
||||
<dt>msg.url <span class="property-type">string</span></dt>
|
||||
<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.filename <span class="property-type">string</span></dt>
|
||||
<dd>the file's parsed filename</dd>
|
||||
</dl>
|
||||
|
||||
<dl class="message-properties">
|
||||
<dt>msg.url <span class="property-type">string</span></dt>
|
||||
<dd>the file's URL</dd>
|
||||
</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.filename <span class="property-type">string</span></dt>
|
||||
<dd>the image's parsed filename</dd>
|
||||
</dl>
|
||||
|
||||
<dl class="message-properties">
|
||||
<dt>msg.url <span class="property-type">string</span></dt>
|
||||
<dd>the image's URL</dd>
|
||||
</dl>
|
||||
|
||||
<dl class="message-properties">
|
||||
<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>
|
||||
|
||||
<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>
|
||||
<strong>Output 2 (Error)</strong>: If an error occurs during pagination, the error message is sent to this output.
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<h3>Dynamic Properties</h3>
|
||||
<p>Some inputs like <strong>Room</strong>, <strong>Pagination Key</strong>, and <strong>Page Size</strong> can be dynamically set using message, flow, or global context variables.</p>
|
||||
|
||||
<h3>Usage</h3>
|
||||
<p>To paginate through a room's timeline, trigger this node with a <code>msg</code> input. The first run will start the timeline, and a unique pagination key will be generated. Future calls can use this key to continue from where you left off. Use the "Paginate Backwards" option to move through older events or set it to false to move forwards through newer events.</p>
|
||||
</script>
|
@ -101,7 +101,8 @@ module.exports = function(RED) {
|
||||
moreMessages = true;
|
||||
if(!timelineWindow) {
|
||||
let timelineSet = room.getUnfilteredTimelineSet();
|
||||
node.debug(JSON.stringify(timelineSet.getFilter()));
|
||||
// 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
|
||||
timelineWindow = new TimelineWindow(node.server.matrixClient, timelineSet);
|
||||
@ -135,7 +136,7 @@ module.exports = function(RED) {
|
||||
// user : node.matrixClient.getUser(event.getSender()),
|
||||
topic : event.getRoomId(),
|
||||
eventId : event.getId(),
|
||||
event : event,
|
||||
event : event.getEffectiveEvent(),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user