mirror of
https://github.com/Skylar-Tech/node-red-contrib-matrix-chat.git
synced 2025-07-08 03:31:05 -06:00
Compare commits
3 Commits
fd605005d1
...
785e0cd7be
Author | SHA1 | Date | |
---|---|---|---|
785e0cd7be | |||
2e9633e113 | |||
1859696122 |
@ -39,7 +39,8 @@
|
||||
"matrix-synapse-create-edit-user": "src/matrix-synapse-create-edit-user.js",
|
||||
"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-whois-user": "src/matrix-whois-user.js",
|
||||
"matrix-typing": "src/matrix-typing.js"
|
||||
}
|
||||
},
|
||||
"engines": {
|
||||
|
@ -44,6 +44,7 @@ module.exports = function(RED) {
|
||||
try {
|
||||
node.log("Leaving room " + msg.topic);
|
||||
node.server.matrixClient.leave(msg.topic);
|
||||
node.server.matrixClient.store.removeRoom(msg.topic);
|
||||
node.send([msg, null]);
|
||||
} catch(e) {
|
||||
node.error("Failed to leave room " + msg.topic + ": " + e, msg);
|
||||
|
@ -49,6 +49,11 @@
|
||||
<span class="property-type">string|object</span>
|
||||
</dt>
|
||||
<dd> You configure what room state events in the node configuration. <code style="white-space: normal;">m.room.name</code>, <code style="white-space: normal;">m.room.avatar</code>, and <code style="white-space: normal;">m.room.guest_access</code> allow you to pass a string to set their value but all other room state events will require the full content object (find this by referencing the <a href="https://spec.matrix.org/latest/client-server-api" target="_blank">Matrix Client-Server docs</a>)</dd>
|
||||
|
||||
<dt class="optional">msg.state_key
|
||||
<span class="property-type">string</span>
|
||||
</dt>
|
||||
<dd> Required for some events such as <code style="white-space: normal;">m.space.parent</code> and <code style="white-space: normal;">m.room.child</code> to set the referenced child/parent room</dd>
|
||||
</dl>
|
||||
|
||||
<h3>Outputs</h3>
|
||||
@ -69,7 +74,7 @@
|
||||
<dt class="optional">dynamic
|
||||
<span class="property-type">string|object</span>
|
||||
</dt>
|
||||
<dd> You configure what room state events to output in the node configuration. <code style="white-space: normal;">m.room.name</code>, <code style="white-space: normal;">m.room.avatar</code>, and <code style="white-space: normal;">m.room.guest_access</code> will come back as strings otherwise you will get the full content object of the event (find this by referencing the <a href="https://spec.matrix.org/latest/client-server-api" target="_blank">Matrix Client-Server docs</a>)</dd>
|
||||
<dd> You configure what room state events to output in the node configuration. <code style="white-space: normal;">m.room.name</code>, <code style="white-space: normal;">m.room.avatar</code>, and <code style="white-space: normal;">m.room.guest_access</code> will come back as strings otherwise you will get the full content object of the event (find this by referencing the <a href="https://spec.matrix.org/latest/client-server-api" target="_blank">Matrix Client-Server docs</a>). Additionally there is a setting when configuring a getter called "Fetch from local storage" that if enabled will search the local storage for the room and try to fetch the state event that way and fallback to hitting the server if that isn't possible.</dd>
|
||||
</li>
|
||||
</ol>
|
||||
</script>
|
||||
@ -77,13 +82,18 @@
|
||||
<script type="text/javascript">
|
||||
(function(){
|
||||
var roomEventTypeOptions = [
|
||||
{ value: "m.room.name", label: "m.room.name"},
|
||||
{ value: "m.room.topic", label: "m.room.topic"},
|
||||
{ value: "m.room.avatar", label: "m.room.avatar"},
|
||||
{ value: "m.room.power_levels", label: "m.room.power_levels"},
|
||||
{ value: "m.room.guest_access", label: "m.room.guest_access"},
|
||||
{ value: "m.room.join_rules", label: "m.room.join_rules"},
|
||||
{ value: "m.room.canonical_alias", label: "m.room.canonical_alias"}
|
||||
{ value: "m.room.name", label: "m.room.name" },
|
||||
{ value: "m.room.topic", label: "m.room.topic" },
|
||||
{ value: "m.room.avatar", label: "m.room.avatar" },
|
||||
{ value: "m.room.power_levels", label: "m.room.power_levels" },
|
||||
{ value: "m.room.guest_access", label: "m.room.guest_access" },
|
||||
{ value: "m.room.join_rules", label: "m.room.join_rules" },
|
||||
{ value: "m.room.canonical_alias", label: "m.room.canonical_alias" },
|
||||
{ value: "m.room.history_visibility", label: "m.room.history_visibility" },
|
||||
{ value: "m.room.server_acl", label: "m.room.server_acl" },
|
||||
{ value: "m.room.pinned_events", label: "m.room.pinned_events"},
|
||||
{ value: "m.space.child", label: "m.space.child" },
|
||||
{ value: "m.space.parent", label: "m.space.parent" },
|
||||
];
|
||||
var defaultRules = [{
|
||||
t: "set",
|
||||
@ -156,15 +166,17 @@
|
||||
.appendTo(row2_1)
|
||||
.typedInput({
|
||||
default: defaultType || (type === 'set' ? 'str' : 'msg'),
|
||||
types: (type === 'set' ? ['msg','flow','global','str','num','bool','json','bin','date','jsonata'] : ['msg', 'flow', 'global'])
|
||||
types: (type === 'set' ? ['msg','flow','global','str','json','jsonata'] : ['msg', 'flow', 'global'])
|
||||
});
|
||||
|
||||
var dcLabel = $('<label style="padding-left: 130px;"></label>').appendTo(row2_2);
|
||||
var lsLabel = $('<label style="padding-left: 130px;"></label>').appendTo(row2_2);
|
||||
var localStorageEl = $('<input type="checkbox" class="node-input-rule-property-localStorage" style="width: auto; margin: 0 6px 0 0">').appendTo(lsLabel);
|
||||
$('<span>').text("Fetch from local storage").appendTo(lsLabel);
|
||||
|
||||
propValInput.on("change", function(evt,type,val) {
|
||||
row2_2.toggle(type === "msg" || type === "flow" || type === "global" || type === "env");
|
||||
})
|
||||
return [propValInput];
|
||||
return [propValInput, localStorageEl];
|
||||
}
|
||||
|
||||
$('#node-input-rule-container').css('min-height','150px').css('min-width','450px').editableList({
|
||||
@ -259,6 +271,7 @@
|
||||
.appendTo(row4);
|
||||
|
||||
let propertyValue = null;
|
||||
let localStorageEl = null;
|
||||
let fromValue = null;
|
||||
let toValue = null;
|
||||
|
||||
@ -277,12 +290,18 @@
|
||||
if (!propertyValue) {
|
||||
var parts = createPropertyValue(row2_1, row2_2, type);
|
||||
propertyValue = parts[0];
|
||||
localStorageEl = parts[1];
|
||||
} else {
|
||||
propertyValue.typedInput('types', (type === 'set' ? ['msg','flow','global','str','num','bool','json','bin','date','jsonata','env'] : ['msg', 'flow', 'global']));
|
||||
propertyValue.typedInput('types', (type === 'set' ? ['msg','flow','global','str','json','jsonata'] : ['msg', 'flow', 'global']));
|
||||
}
|
||||
|
||||
propertyValue.typedInput('show');
|
||||
row2.show();
|
||||
if(type === 'get') {
|
||||
localStorageEl.parent().show();
|
||||
} else {
|
||||
localStorageEl.parent().hide();
|
||||
}
|
||||
row3.hide();
|
||||
row4.hide();
|
||||
});
|
||||
@ -292,7 +311,14 @@
|
||||
if (rule.t === "set" || rule.t === "get") {
|
||||
var parts = createPropertyValue(row2_1, row2_2, rule.t, rule.tot);
|
||||
propertyValue = parts[0];
|
||||
localStorageEl = parts[1];
|
||||
propertyValue.typedInput('value',rule.to);
|
||||
localStorageEl.prop("checked", !!rule.ls);
|
||||
if(rule.t === 'get') {
|
||||
localStorageEl.parent().show();
|
||||
} else {
|
||||
localStorageEl.parent().hide();
|
||||
}
|
||||
}
|
||||
selectField.change();
|
||||
container[0].appendChild(fragment);
|
||||
@ -319,6 +345,10 @@
|
||||
to:rule.find(".node-input-rule-property-value").typedInput('value'),
|
||||
tot:rule.find(".node-input-rule-property-value").typedInput('type')
|
||||
};
|
||||
|
||||
if (r.t === "get" && rule.find(".node-input-rule-property-localStorage").prop("checked")) {
|
||||
r.ls = true;
|
||||
}
|
||||
node.rules.push(r);
|
||||
});
|
||||
},
|
||||
|
@ -74,24 +74,19 @@ module.exports = function(RED) {
|
||||
if (rule.tot === "msg") {
|
||||
value = RED.util.getMessageProperty(msg,rule.to);
|
||||
} else if ((rule.tot === 'flow') || (rule.tot === 'global')) {
|
||||
RED.util.evaluateNodeProperty(rule.to, rule.tot, node, msg, (err,value) => {
|
||||
if (err) {
|
||||
throw new Error("Invalid value evaluation");
|
||||
} else {
|
||||
return value;
|
||||
}
|
||||
});
|
||||
return
|
||||
try {
|
||||
value = RED.util.evaluateNodeProperty(rule.to, rule.tot, node, msg);
|
||||
} catch(e2) {
|
||||
throw new Error("Invalid value evaluation");
|
||||
}
|
||||
} else if (rule.tot === 'date') {
|
||||
value = Date.now();
|
||||
} else if (rule.tot === 'jsonata') {
|
||||
RED.util.evaluateJSONataExpression(rule.to,msg, (err, value) => {
|
||||
if (err) {
|
||||
throw new Error("Invalid expression");
|
||||
} else {
|
||||
return value;
|
||||
}
|
||||
});
|
||||
try {
|
||||
value = RED.util.evaluateJSONataExpression(rule.to,msg);
|
||||
} catch(e3) {
|
||||
throw new Error("Invalid expression");
|
||||
}
|
||||
return;
|
||||
}
|
||||
return value;
|
||||
@ -140,11 +135,11 @@ module.exports = function(RED) {
|
||||
msg.topic,
|
||||
"m.room.name",
|
||||
typeof value === "string"
|
||||
? { name: value }
|
||||
? {name: value}
|
||||
: value);
|
||||
break;
|
||||
case "m.room.topic":
|
||||
if(typeof value === "string") {
|
||||
if (typeof value === "string") {
|
||||
await node.server.matrixClient.setRoomTopic(msg.topic, value);
|
||||
} else {
|
||||
await node.server.matrixClient.sendStateEvent(
|
||||
@ -159,27 +154,7 @@ module.exports = function(RED) {
|
||||
msg.topic,
|
||||
"m.room.avatar",
|
||||
typeof value === "string"
|
||||
? { "url": value }
|
||||
: value,
|
||||
"");
|
||||
break;
|
||||
case "m.room.power_levels":
|
||||
if(typeof value !== 'object') {
|
||||
setterErrors[rule.p] = "m.room.power_levels content must be object";
|
||||
} else {
|
||||
await node.server.matrixClient.sendStateEvent(
|
||||
msg.topic,
|
||||
"m.room.power_levels",
|
||||
value,
|
||||
"");
|
||||
}
|
||||
break;
|
||||
case "m.room.guest_access":
|
||||
await node.server.matrixClient.sendStateEvent(
|
||||
msg.topic,
|
||||
"m.room.guest_access",
|
||||
typeof value === "string"
|
||||
? { "guest_access": value }
|
||||
? {"url": value}
|
||||
: value,
|
||||
"");
|
||||
break;
|
||||
@ -205,15 +180,50 @@ module.exports = function(RED) {
|
||||
"");
|
||||
}
|
||||
break;
|
||||
case "m.space.parent":
|
||||
if (typeof value !== 'object') {
|
||||
setterErrors[rule.p] = "m.space.parent content must be object";
|
||||
} else if (!msg.state_key) {
|
||||
setterErrors[rule.p] = "m.space.parent required msg.state_key input to be set to the child roomId";
|
||||
}else {
|
||||
await node.server.matrixClient.sendStateEvent(
|
||||
msg.topic,
|
||||
"m.room.power_levels",
|
||||
value,
|
||||
msg.state_key);
|
||||
}
|
||||
break;
|
||||
case "m.space.child":
|
||||
if (typeof value !== 'object') {
|
||||
setterErrors[rule.p] = "m.space.child content must be object";
|
||||
} else if (!msg.state_key) {
|
||||
setterErrors[rule.p] = "m.space.child required msg.state_key input to be set to the parent roomId";
|
||||
}else {
|
||||
await node.server.matrixClient.sendStateEvent(
|
||||
msg.topic,
|
||||
"m.room.power_levels",
|
||||
value,
|
||||
msg.state_key);
|
||||
}
|
||||
break;
|
||||
case "m.room.guest_access":
|
||||
await node.server.matrixClient.sendStateEvent(
|
||||
msg.topic,
|
||||
"m.room.guest_access",
|
||||
typeof value === "string"
|
||||
? { "guest_access": value }
|
||||
: value,
|
||||
"");
|
||||
break;
|
||||
default:
|
||||
if(typeof value !== 'object') {
|
||||
setterErrors[rule.p] = "Custom event content must be object";
|
||||
setterErrors[rule.p] = `${rule.p} content must be object`;
|
||||
} else {
|
||||
await node.server.matrixClient.sendStateEvent(
|
||||
msg.topic,
|
||||
rule.p,
|
||||
value,
|
||||
"");
|
||||
msg.state_key || "");
|
||||
}
|
||||
break;
|
||||
}
|
||||
@ -226,23 +236,33 @@ module.exports = function(RED) {
|
||||
value = cachedGetters[rule.p];
|
||||
} else {
|
||||
try {
|
||||
// we may want to fetch from local storage in the future, this is how to do that
|
||||
// const room = this.getRoom(roomId);
|
||||
// const ev = room.currentState.getStateEvents(EventType.RoomEncryption, "");
|
||||
value = await node.server.matrixClient.getStateEvent(msg.topic, rule.p, "");
|
||||
switch(rule.p) {
|
||||
case "m.room.name":
|
||||
value = value?.name
|
||||
break;
|
||||
case "m.room.topic":
|
||||
value = value?.topic
|
||||
break;
|
||||
case "m.room.avatar":
|
||||
value = value?.url
|
||||
break;
|
||||
case "m.room.guest_access":
|
||||
value = value?.guest_access;
|
||||
break;
|
||||
if(rule.ls) {
|
||||
// we opted to lookup from local storage, will fallback to server if necessary
|
||||
let room = node.server.matrixClient.getRoom(msg.topic);
|
||||
if(room) {
|
||||
value = await room.getLiveTimeline().getState("f").getStateEvents(rule.p, "");
|
||||
}
|
||||
}
|
||||
if(!value) {
|
||||
// fetch the latest state event by type from server
|
||||
value = await node.server.matrixClient.getStateEvent(msg.topic, rule.p, "");
|
||||
if(value) {
|
||||
// normalize some simpler events for easier access
|
||||
switch(rule.p) {
|
||||
case "m.room.name":
|
||||
value = value?.name
|
||||
break;
|
||||
case "m.room.topic":
|
||||
value = value?.topic
|
||||
break;
|
||||
case "m.room.avatar":
|
||||
value = value?.url
|
||||
break;
|
||||
case "m.room.guest_access":
|
||||
value = value?.guest_access;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
setToValue(value, rule);
|
||||
} catch(e) {
|
||||
|
@ -174,6 +174,13 @@ module.exports = function(RED) {
|
||||
|
||||
node.on('close', function(done) {
|
||||
stopClient();
|
||||
if(node.globalAccess) {
|
||||
try {
|
||||
node.context().global.delete('matrixClient["'+node.userId+'"]');
|
||||
} catch(e){
|
||||
node.error(e.message, {});
|
||||
}
|
||||
}
|
||||
done();
|
||||
});
|
||||
|
||||
|
108
src/matrix-typing.html
Normal file
108
src/matrix-typing.html
Normal file
@ -0,0 +1,108 @@
|
||||
<script type="text/javascript">
|
||||
RED.nodes.registerType('matrix-typing', {
|
||||
category: 'matrix',
|
||||
color: '#00b7ca',
|
||||
icon: "matrix.png",
|
||||
outputLabels: ["success", "error"],
|
||||
inputs: 1,
|
||||
outputs: 2,
|
||||
defaults: {
|
||||
name: { value: null },
|
||||
server: { type: "matrix-server-config" },
|
||||
roomType: { value: "msg" },
|
||||
roomValue: { value: "topic" },
|
||||
typingType: { value: "bool" },
|
||||
typingValue: { value: true },
|
||||
timeoutMsType: { value: "num" },
|
||||
timeoutMsValue: { value: 20000 },
|
||||
},
|
||||
label: function() {
|
||||
return this.name || "Typing";
|
||||
},
|
||||
oneditprepare: function() {
|
||||
$("#node-input-room").typedInput({
|
||||
type: this.roomType,
|
||||
types:['msg','flow','global','str'],
|
||||
}).typedInput('value', this.roomValue);
|
||||
|
||||
$("#node-input-typing").typedInput({
|
||||
types:['msg','flow','global','bool'],
|
||||
})
|
||||
.typedInput('value', this.typingValue)
|
||||
.typedInput('type', this.typingType);
|
||||
|
||||
$("#node-input-timeoutMs").typedInput({
|
||||
types:['msg','flow','global','num'],
|
||||
})
|
||||
.typedInput('value', this.timeoutMsValue)
|
||||
.typedInput('type', this.timeoutMsType);
|
||||
},
|
||||
oneditsave: function() {
|
||||
this.roomType = $("#node-input-room").typedInput('type');
|
||||
this.roomValue = $("#node-input-room").typedInput('value');
|
||||
this.typingType = $("#node-input-typing").typedInput('type');
|
||||
this.typingValue = $("#node-input-typing").typedInput('value');
|
||||
this.timeoutMsType = $("#node-input-timeoutMs").typedInput('type');
|
||||
this.timeoutMsValue = $("#node-input-timeoutMs").typedInput('value');
|
||||
},
|
||||
paletteLabel: 'Typing'
|
||||
});
|
||||
</script>
|
||||
|
||||
<script type="text/html" data-template-name="matrix-typing">
|
||||
<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-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-room"><i class="fa fa-commenting-o"></i> Is Typing</label>
|
||||
<input type="text" id="node-input-typing">
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<label for="node-input-room"><i class="fa fa-clock-o"></i> Timeout Milliseconds</label>
|
||||
<input type="text" id="node-input-timeoutMs">
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script type="text/html" data-help-name="matrix-typing">
|
||||
<h3>Details</h3>
|
||||
<p>
|
||||
Sends typing event to a room
|
||||
</p>
|
||||
|
||||
<h3>Inputs</h3>
|
||||
<dl class="message-properties">
|
||||
<dt>dynamic
|
||||
<span class="property-type">any</span>
|
||||
</dt>
|
||||
<dd> The inputs are configurable on the node.</dd>
|
||||
</dl>
|
||||
|
||||
<h3>Outputs</h3>
|
||||
<ol class="node-ports">
|
||||
<li>Success
|
||||
<dl class="message-properties">
|
||||
<dd>Returns from first output on success</dd>
|
||||
</dl>
|
||||
</li>
|
||||
<li>Error
|
||||
<dl class="message-properties">
|
||||
<dt>msg.error <span class="property-type">string</span></dt>
|
||||
<dd>the error that occurred.</dd>
|
||||
</dl>
|
||||
</li>
|
||||
</ol>
|
||||
|
||||
</script>
|
86
src/matrix-typing.js
Normal file
86
src/matrix-typing.js
Normal file
@ -0,0 +1,86 @@
|
||||
module.exports = function(RED) {
|
||||
function MatrixTyping(n) {
|
||||
RED.nodes.createNode(this, n);
|
||||
|
||||
let node = this;
|
||||
|
||||
this.name = n.name;
|
||||
this.server = RED.nodes.getNode(n.server);
|
||||
this.roomId = n.roomId;
|
||||
this.roomType = n.roomType;
|
||||
this.roomValue = n.roomValue;
|
||||
this.typingType = n.typingType;
|
||||
this.typingValue = n.typingValue;
|
||||
this.timeoutMsType = n.timeoutMsType;
|
||||
this.timeoutMsValue = n.timeoutMsValue;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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');
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
try {
|
||||
let roomId = getToValue(msg, node.roomType, node.roomValue),
|
||||
typing = getToValue(msg, node.typingType, node.typingValue),
|
||||
timeoutMs = getToValue(msg, node.timeoutMsType, node.timeoutMsValue);
|
||||
|
||||
if(!roomId) {
|
||||
node.error('No room provided in msg.topic', msg);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log("sending typing",roomId, typing, timeoutMs);
|
||||
await node.server.matrixClient.sendTyping(roomId, typing, timeoutMs);
|
||||
node.send([msg, null]);
|
||||
} catch(e) {
|
||||
node.error("Failed to send typing event " + msg.topic + ": " + e, msg);
|
||||
msg.payload = e;
|
||||
node.send([null, msg]);
|
||||
}
|
||||
});
|
||||
|
||||
node.on("close", function() {
|
||||
node.server.deregister(node);
|
||||
});
|
||||
}
|
||||
RED.nodes.registerType("matrix-typing", MatrixTyping);
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user