Compare commits

..

19 Commits

Author SHA1 Message Date
skylord123 fd174e32ff Merge pull request #103 from Skylar-Tech/102-upload-file
New file uploading node
2023-11-05 00:08:24 -06:00
skylord123 e8506d8887 #102 Added new node for file uploading
#102 File upload node automatically detects mime type from name
#102 File upload node automatically fills in information for m.video, m.audio, and m.image (resolution, duration, etc)
#102 File upload node can generate a thumbnail for videos
#102 Send message node now accepts an object to override the message content
2023-11-05 00:06:52 -06:00
skylord123 d7c4bc26bb - #100 add documentation for new typing node and update README
- #100 fix typing event causing server error due to number being string
- Mention using pantalaimon as an alternative for E2EE support in README
2023-10-22 16:56:49 -06:00
skylord123 785e0cd7be - #100 add node for sending typing state to rooms
- fix global and flow variable in getters for Matrix Room States node
2023-10-22 04:32:06 -06:00
skylord123 2e9633e113 - #97 msg.state_key is now allowed as an input to Room State Events node for events that support it (required for m.space.child and m.space.parent)
- #97 added support for m.room.history_visibility, m.room.server_acl, m.room.pinned_events, m.space.child, and m.space.parent
- #97 fix issue with checkbox being hidden on config page when adding new setters/getters on config page
2023-10-22 03:01:33 -06:00
skylord123 1859696122 - #97 added option to fetch state event from local storage and fallback to server if necessary (allows for faster lookups and gives the full event object with information about when/who created it, etc)
- #97 remove num, bool, bin, and data from being options you can set to a state event (currently only objects and sometimes strings are allowed)
- Updated Leave Room node so it deletes the room from local storage
- Updated server config node so it deletes the matrix client from storage during shutdown (possibly solution to #94)
2023-10-22 00:29:12 -06:00
skylord123 fd605005d1 Closes #99
- matrix-server-config now auto populates with first option
2023-10-21 19:47:12 -06:00
skylord123 85de450a1a Merge pull request #98 from Skylar-Tech/97-room-settings
97 room settings
2023-10-21 19:43:29 -06:00
skylord123 e7e0f2967b Issue #97 Room Settings
- Remove unused returnValues config option for Room State Events node
2023-10-21 19:39:32 -06:00
skylord123 611e23b845 Issue #97 Room Settings
- Room Settings node renamed to Room State Events
- Room State Events node allows configuring inputs/outputs from config
- Fix bug with various nodes allowing execution even though matrix server isn't connected
- Replace deprecated matrixClient.setGlobalErrorOnUnknownDevices method
- Update docs for new Room State Events node
2023-10-21 19:33:44 -06:00
skylord123 9d050a0d44 Issue #97 Room Settings
- fix join_allow_rules for Room Setting node not getting correctly
- update Room Setting docs
2023-10-15 14:45:16 -06:00
skylord123 c833a40a84 Issue #97 Room Settings
- Can set room name, topic, and avatar
- Can get name, topic, avatar, encrypted, power_levels, aliases, guest_access, join_rule, and join_allow_rules
2023-10-15 04:17:12 -06:00
skylord123 0e755bc350 - Remove console.log usage 2023-10-14 23:22:38 -06:00
skylord123 c920dd12cb - Fix error with matrix-synapse-register node
- Ensure matrix-server-config's register/deregister methods are always available
2023-10-14 22:26:29 -06:00
bvmensvoort 9661922f78 Pass msg object where possible
As described on https://nodered.org/docs/user-guide/writing-functions#handling-errors
2023-10-14 21:32:46 -06:00
bvmensvoort 20c7182511 Make errors catchable and (de)register at config node of all nodes 2023-10-14 21:32:46 -06:00
bvmensvoort f48ba74a72 Make errors of config node catchable via a catch node 2023-10-14 21:32:46 -06:00
bvmensvoort 124a0cba34 (de)Register consumer nodes at config node
In order for error messages to be catchable
2023-10-14 21:32:46 -06:00
bvmensvoort 8ca11f36d8 Make errors of nodes catchable by a catch node 2023-10-14 21:32:46 -06:00
54 changed files with 2078 additions and 256 deletions
+2
View File
@@ -11,9 +11,11 @@ The following is supported from this package:
- 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
- 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
+10
View File
@@ -24,6 +24,7 @@ Build something cool with these nodes? Feel free to submit a pull request to sha
- [Respond to "rooms <user_id>" with user's rooms (list server's rooms if <user_id> is left blank)](#respond-to-rooms-user_id-with-users-rooms-list-servers-rooms-if-user_id-is-left-blank)
- [Respond to "whois <user_id>" with information about the user's session](#respond-to-whois-user_id-with-information-about-the-users-session)
- [Respond to "room_users" with current room's users](#respond-to-room_users-with-current-rooms-users)
- [Sending typing events to a room](#sending-typing-events-to-a-room)
- [Download & store all received files/images](#download--store-all-received-filesimages)
- [Kick/Ban user from room](#kickban-user-from-room)
- [Deactivate user](#deactivate-user)
@@ -221,6 +222,15 @@ Note: You may need to edit the storage directory for this to work. Default actio
![store-received-files.png](store-received-files.png)
### Sending typing events to a room
[View JSON](send-typing-events.json)
You can tell a room that Node-RED is writing a message and also cancel the writing event. This can be useful for making bots feel more interactive (show typing while requesting API endpoint for example).
![store-received-files.png](send-typing-events.png)
### Kick/Ban user from room
[View JSON](room-kick-ban.json)
+127
View File
@@ -0,0 +1,127 @@
[
{
"id": "541dbfc3f04220cf",
"type": "matrix-typing",
"z": "f025a8b9fbd1b054",
"name": "",
"server": null,
"roomType": "msg",
"roomValue": "topic",
"typingType": "msg",
"typingValue": "typing",
"timeoutMsType": "num",
"timeoutMsValue": "20000",
"x": 1090,
"y": 220,
"wires": [
[
"febf8236f3683963"
],
[
"9db9819ebb6343c8"
]
]
},
{
"id": "d7c3714c657bfe4f",
"type": "inject",
"z": "f025a8b9fbd1b054",
"name": "Start Typing",
"props": [
{
"p": "topic",
"vt": "str"
},
{
"p": "typing",
"v": "true",
"vt": "bool"
}
],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"topic": "!mohVKgDFYUubJQHcuX:skylar.tech",
"x": 910,
"y": 200,
"wires": [
[
"541dbfc3f04220cf"
]
]
},
{
"id": "783121ff1a6bd833",
"type": "inject",
"z": "f025a8b9fbd1b054",
"name": "Stop Typing",
"props": [
{
"p": "topic",
"vt": "str"
},
{
"p": "typing",
"v": "false",
"vt": "bool"
}
],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"topic": "!mohVKgDFYUubJQHcuX:skylar.tech",
"x": 910,
"y": 240,
"wires": [
[
"541dbfc3f04220cf"
]
]
},
{
"id": "9db9819ebb6343c8",
"type": "debug",
"z": "f025a8b9fbd1b054",
"name": "Failure msg",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "true",
"targetType": "full",
"statusVal": "",
"statusType": "auto",
"x": 1270,
"y": 240,
"wires": []
},
{
"id": "febf8236f3683963",
"type": "debug",
"z": "f025a8b9fbd1b054",
"name": "Success msg",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "true",
"targetType": "full",
"statusVal": "",
"statusType": "auto",
"x": 1270,
"y": 200,
"wires": []
},
{
"id": "01e8c4c6303af390",
"type": "comment",
"z": "f025a8b9fbd1b054",
"name": "Send typing events to a room",
"info": "",
"x": 940,
"y": 160,
"wires": []
}
]
Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

+145 -105
View File
@@ -1,23 +1,27 @@
{
"name": "node-red-contrib-matrix-chat",
"version": "0.7.0",
"version": "0.7.1",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "node-red-contrib-matrix-chat",
"version": "0.7.0",
"version": "0.7.1",
"license": "SEE LICENSE FILE",
"dependencies": {
"abort-controller": "^3.0.0",
"fluent-ffmpeg": "^2.1.2",
"fs-extra": "^11.1.0",
"got": "^12.0.2",
"image-size": "^1.0.2",
"isomorphic-webcrypto": "^2.3.8",
"matrix-js-sdk": "^28.0.0",
"mime": "^3.0.0",
"node-fetch": "^3.3.0",
"node-localstorage": "^2.2.1",
"olm": "https://gitlab.matrix.org/matrix-org/olm/-/package_files/2572/download",
"request": "^2.88.2",
"tmp": "^0.2.1",
"utf8": "^3.0.0"
},
"engines": {
@@ -3107,6 +3111,19 @@
"ms": "^2.1.1"
}
},
"node_modules/@expo/devcert/node_modules/tmp": {
"version": "0.0.33",
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz",
"integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==",
"optional": true,
"peer": true,
"dependencies": {
"os-tmpdir": "~1.0.2"
},
"engines": {
"node": ">=0.6.0"
}
},
"node_modules/@expo/env": {
"version": "0.0.5",
"resolved": "https://registry.npmjs.org/@expo/env/-/env-0.0.5.tgz",
@@ -3167,6 +3184,19 @@
"node": ">=10"
}
},
"node_modules/@expo/image-utils/node_modules/mime": {
"version": "2.6.0",
"resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz",
"integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==",
"optional": true,
"peer": true,
"bin": {
"mime": "cli.js"
},
"engines": {
"node": ">=4.0.0"
}
},
"node_modules/@expo/image-utils/node_modules/node-fetch": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
@@ -4992,6 +5022,19 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/@react-native-community/cli-tools/node_modules/mime": {
"version": "2.6.0",
"resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz",
"integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==",
"optional": true,
"peer": true,
"bin": {
"mime": "cli.js"
},
"engines": {
"node": ">=4.0.0"
}
},
"node_modules/@react-native-community/cli-tools/node_modules/mimic-fn": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
@@ -5909,9 +5952,7 @@
"node_modules/async": {
"version": "3.2.4",
"resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz",
"integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==",
"optional": true,
"peer": true
"integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ=="
},
"node_modules/async-limiter": {
"version": "1.0.1",
@@ -6168,9 +6209,7 @@
"node_modules/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"optional": true,
"peer": true
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
},
"node_modules/base-64": {
"version": "0.1.0",
@@ -6336,8 +6375,6 @@
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"optional": true,
"peer": true,
"dependencies": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
@@ -6948,9 +6985,7 @@
"node_modules/concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
"optional": true,
"peer": true
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="
},
"node_modules/connect": {
"version": "3.7.0",
@@ -8137,6 +8172,18 @@
"node": ">=0.4.0"
}
},
"node_modules/fluent-ffmpeg": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/fluent-ffmpeg/-/fluent-ffmpeg-2.1.2.tgz",
"integrity": "sha512-IZTB4kq5GK0DPp7sGQ0q/BWurGHffRtQQwVkiqDgeO6wYJLLV5ZhgNOQ65loZxxuPMKZKZcICCUnaGtlxBiR0Q==",
"dependencies": {
"async": ">=0.2.9",
"which": "^1.1.1"
},
"engines": {
"node": ">=0.8.0"
}
},
"node_modules/fontfaceobserver": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/fontfaceobserver/-/fontfaceobserver-2.3.0.tgz",
@@ -8235,9 +8282,7 @@
"node_modules/fs.realpath": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
"integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
"optional": true,
"peer": true
"integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="
},
"node_modules/fsevents": {
"version": "2.3.3",
@@ -8339,8 +8384,6 @@
"version": "7.1.6",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz",
"integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==",
"optional": true,
"peer": true,
"dependencies": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
@@ -8716,8 +8759,6 @@
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/image-size/-/image-size-1.0.2.tgz",
"integrity": "sha512-xfOoWjceHntRb3qFCrh5ZFORYH8XCdYpASltMhZ/Q0KZiOwjdE/Yl2QCiWdwD+lygV5bMCvauzgu5PxBX/Yerg==",
"optional": true,
"peer": true,
"dependencies": {
"queue": "6.0.2"
},
@@ -8781,8 +8822,6 @@
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
"integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
"optional": true,
"peer": true,
"dependencies": {
"once": "^1.3.0",
"wrappy": "1"
@@ -8791,9 +8830,7 @@
"node_modules/inherits": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
"optional": true,
"peer": true
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
},
"node_modules/ini": {
"version": "1.3.8",
@@ -9098,9 +9135,7 @@
"node_modules/isexe": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
"optional": true,
"peer": true
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="
},
"node_modules/isobject": {
"version": "3.0.1",
@@ -11453,16 +11488,14 @@
}
},
"node_modules/mime": {
"version": "2.6.0",
"resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz",
"integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==",
"optional": true,
"peer": true,
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz",
"integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==",
"bin": {
"mime": "cli.js"
},
"engines": {
"node": ">=4.0.0"
"node": ">=10.0.0"
}
},
"node_modules/mime-db": {
@@ -11509,8 +11542,6 @@
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
"optional": true,
"peer": true,
"dependencies": {
"brace-expansion": "^1.1.7"
},
@@ -12022,8 +12053,6 @@
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
"optional": true,
"peer": true,
"dependencies": {
"wrappy": "1"
}
@@ -12404,8 +12433,6 @@
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
"integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
"optional": true,
"peer": true,
"engines": {
"node": ">=0.10.0"
}
@@ -12886,8 +12913,6 @@
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/queue/-/queue-6.0.2.tgz",
"integrity": "sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA==",
"optional": true,
"peer": true,
"dependencies": {
"inherits": "~2.0.3"
}
@@ -14478,16 +14503,28 @@
}
},
"node_modules/tmp": {
"version": "0.0.33",
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz",
"integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==",
"optional": true,
"peer": true,
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz",
"integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==",
"dependencies": {
"os-tmpdir": "~1.0.2"
"rimraf": "^3.0.0"
},
"engines": {
"node": ">=0.6.0"
"node": ">=8.17.0"
}
},
"node_modules/tmp/node_modules/rimraf": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
"integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
"dependencies": {
"glob": "^7.1.3"
},
"bin": {
"rimraf": "bin.js"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/tmpl": {
@@ -15002,8 +15039,6 @@
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz",
"integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==",
"optional": true,
"peer": true,
"dependencies": {
"isexe": "^2.0.0"
},
@@ -15059,9 +15094,7 @@
"node_modules/wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
"optional": true,
"peer": true
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
},
"node_modules/write-file-atomic": {
"version": "2.4.3",
@@ -17519,6 +17552,16 @@
"requires": {
"ms": "^2.1.1"
}
},
"tmp": {
"version": "0.0.33",
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz",
"integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==",
"optional": true,
"peer": true,
"requires": {
"os-tmpdir": "~1.0.2"
}
}
}
},
@@ -17576,6 +17619,13 @@
"universalify": "^1.0.0"
}
},
"mime": {
"version": "2.6.0",
"resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz",
"integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==",
"optional": true,
"peer": true
},
"node-fetch": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
@@ -19191,6 +19241,13 @@
"is-unicode-supported": "^0.1.0"
}
},
"mime": {
"version": "2.6.0",
"resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz",
"integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==",
"optional": true,
"peer": true
},
"mimic-fn": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
@@ -19753,9 +19810,7 @@
"async": {
"version": "3.2.4",
"resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz",
"integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==",
"optional": true,
"peer": true
"integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ=="
},
"async-limiter": {
"version": "1.0.1",
@@ -19981,9 +20036,7 @@
"balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"optional": true,
"peer": true
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
},
"base-64": {
"version": "0.1.0",
@@ -20123,8 +20176,6 @@
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"optional": true,
"peer": true,
"requires": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
@@ -20582,9 +20633,7 @@
"concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
"optional": true,
"peer": true
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="
},
"connect": {
"version": "3.7.0",
@@ -21524,6 +21573,15 @@
"optional": true,
"peer": true
},
"fluent-ffmpeg": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/fluent-ffmpeg/-/fluent-ffmpeg-2.1.2.tgz",
"integrity": "sha512-IZTB4kq5GK0DPp7sGQ0q/BWurGHffRtQQwVkiqDgeO6wYJLLV5ZhgNOQ65loZxxuPMKZKZcICCUnaGtlxBiR0Q==",
"requires": {
"async": ">=0.2.9",
"which": "^1.1.1"
}
},
"fontfaceobserver": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/fontfaceobserver/-/fontfaceobserver-2.3.0.tgz",
@@ -21598,9 +21656,7 @@
"fs.realpath": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
"integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
"optional": true,
"peer": true
"integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="
},
"fsevents": {
"version": "2.3.3",
@@ -21674,8 +21730,6 @@
"version": "7.1.6",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz",
"integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==",
"optional": true,
"peer": true,
"requires": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
@@ -21949,8 +22003,6 @@
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/image-size/-/image-size-1.0.2.tgz",
"integrity": "sha512-xfOoWjceHntRb3qFCrh5ZFORYH8XCdYpASltMhZ/Q0KZiOwjdE/Yl2QCiWdwD+lygV5bMCvauzgu5PxBX/Yerg==",
"optional": true,
"peer": true,
"requires": {
"queue": "6.0.2"
}
@@ -21998,8 +22050,6 @@
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
"integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
"optional": true,
"peer": true,
"requires": {
"once": "^1.3.0",
"wrappy": "1"
@@ -22008,9 +22058,7 @@
"inherits": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
"optional": true,
"peer": true
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
},
"ini": {
"version": "1.3.8",
@@ -22242,9 +22290,7 @@
"isexe": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
"optional": true,
"peer": true
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="
},
"isobject": {
"version": "3.0.1",
@@ -24103,11 +24149,9 @@
}
},
"mime": {
"version": "2.6.0",
"resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz",
"integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==",
"optional": true,
"peer": true
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz",
"integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A=="
},
"mime-db": {
"version": "1.52.0",
@@ -24138,8 +24182,6 @@
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
"optional": true,
"peer": true,
"requires": {
"brace-expansion": "^1.1.7"
}
@@ -24532,8 +24574,6 @@
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
"optional": true,
"peer": true,
"requires": {
"wrappy": "1"
}
@@ -24824,9 +24864,7 @@
"path-is-absolute": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
"integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
"optional": true,
"peer": true
"integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg=="
},
"path-key": {
"version": "2.0.1",
@@ -25193,8 +25231,6 @@
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/queue/-/queue-6.0.2.tgz",
"integrity": "sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA==",
"optional": true,
"peer": true,
"requires": {
"inherits": "~2.0.3"
}
@@ -26474,13 +26510,21 @@
}
},
"tmp": {
"version": "0.0.33",
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz",
"integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==",
"optional": true,
"peer": true,
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz",
"integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==",
"requires": {
"os-tmpdir": "~1.0.2"
"rimraf": "^3.0.0"
},
"dependencies": {
"rimraf": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
"integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
"requires": {
"glob": "^7.1.3"
}
}
}
},
"tmpl": {
@@ -26887,8 +26931,6 @@
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz",
"integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==",
"optional": true,
"peer": true,
"requires": {
"isexe": "^2.0.0"
}
@@ -26934,9 +26976,7 @@
"wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
"optional": true,
"peer": true
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
},
"write-file-atomic": {
"version": "2.4.3",
+8 -1
View File
@@ -4,14 +4,18 @@
"description": "Matrix chat server client for Node-RED",
"dependencies": {
"abort-controller": "^3.0.0",
"fluent-ffmpeg": "^2.1.2",
"fs-extra": "^11.1.0",
"got": "^12.0.2",
"image-size": "^1.0.2",
"isomorphic-webcrypto": "^2.3.8",
"matrix-js-sdk": "^28.0.0",
"mime": "^3.0.0",
"node-fetch": "^3.3.0",
"node-localstorage": "^2.2.1",
"olm": "https://gitlab.matrix.org/matrix-org/olm/-/package_files/2572/download",
"request": "^2.88.2",
"tmp": "^0.2.1",
"utf8": "^3.0.0"
},
"node-red": {
@@ -23,6 +27,7 @@
"matrix-delete-event": "src/matrix-delete-event.js",
"matrix-send-file": "src/matrix-send-file.js",
"matrix-send-image": "src/matrix-send-image.js",
"matrix-upload-file": "src/matrix-upload-file.js",
"matrix-react": "src/matrix-react.js",
"matrix-create-room": "src/matrix-create-room.js",
"matrix-invite-room": "src/matrix-invite-room.js",
@@ -32,13 +37,15 @@
"matrix-crypt-file": "src/matrix-crypt-file.js",
"matrix-room-kick": "src/matrix-room-kick.js",
"matrix-room-ban": "src/matrix-room-ban.js",
"matrix-room-users": "src/matrix-room-users.js",
"matrix-room-state-events": "src/matrix-room-state-events.js",
"matrix-synapse-users": "src/matrix-synapse-users.js",
"matrix-synapse-register": "src/matrix-synapse-register.js",
"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-room-users": "src/matrix-room-users.js"
"matrix-typing": "src/matrix-typing.js"
}
},
"engines": {
+1 -1
View File
@@ -8,7 +8,7 @@
outputs: 2,
defaults: {
name: { value: null },
server: { value: "", type: "matrix-server-config" }
server: { type: "matrix-server-config" }
},
label: function() {
return this.name || "Create Room";
+8 -2
View File
@@ -8,9 +8,10 @@ module.exports = function(RED) {
this.server = RED.nodes.getNode(n.server);
if(!this.server) {
node.error('Server must be configured on the node.');
node.error('Server must be configured on the node.', {});
return;
}
node.server.register(node);
this.encodeUri = function(pathTemplate, variables) {
for (const key in variables) {
@@ -41,8 +42,9 @@ module.exports = function(RED) {
}
if(!node.server.isConnected()) {
node.error("Matrix server connection is currently closed");
node.error("Matrix server connection is currently closed", msg);
node.send([null, msg]);
return;
}
if(!msg.payload) {
@@ -64,6 +66,10 @@ module.exports = function(RED) {
node.send([null, msg]);
});
});
node.on("close", function() {
node.server.deregister(node);
});
}
RED.nodes.registerType("matrix-create-room", MatrixCreateRoom);
}
+4 -4
View File
@@ -12,22 +12,22 @@ module.exports = function(RED) {
const { got } = await import('got');
if(!msg.type) {
node.error('msg.type is required.');
node.error('msg.type is required.', msg);
return;
}
if(!msg.content) {
node.error('msg.content is required.');
node.error('msg.content is required.', msg);
return;
}
if(!msg.content.file) {
node.error('msg.content.file is required.');
node.error('msg.content.file is required.', msg);
return;
}
if(!msg.url) {
node.error('msg.url is required.');
node.error('msg.url is required.', msg);
return;
}
+1 -1
View File
@@ -8,7 +8,7 @@
outputs:2,
defaults: {
name: { value: null },
server: { value: "", type: "matrix-server-config" },
server: { type: "matrix-server-config" },
roomId: { value: null },
reason: { value: "" },
},
+7 -2
View File
@@ -13,6 +13,7 @@ module.exports = function(RED) {
node.warn("No configuration node");
return;
}
node.server.register(node);
node.status({ fill: "red", shape: "ring", text: "disconnected" });
@@ -27,7 +28,7 @@ module.exports = function(RED) {
node.on('input', function(msg) {
if(!msg.eventId) {
node.error("eventId is missing");
node.error("eventId is missing", msg);
node.send([null, msg])
return;
}
@@ -38,7 +39,7 @@ module.exports = function(RED) {
}
if(!node.server.isConnected()) {
node.error("Matrix server connection is currently closed");
node.error("Matrix server connection is currently closed", msg);
node.send([null, msg]);
return;
}
@@ -70,6 +71,10 @@ module.exports = function(RED) {
node.send([null, msg]);
});
});
node.on("close", function() {
node.server.deregister(node);
});
}
RED.nodes.registerType("matrix-delete-event",MatrixDeleteEvent);
}
View File
+1 -1
View File
@@ -8,7 +8,7 @@
outputs: 2,
defaults: {
name: { value: null },
server: { value: "", type: "matrix-server-config" },
server: { type: "matrix-server-config" },
roomId: { value: null },
},
label: function() {
+10 -4
View File
@@ -9,9 +9,10 @@ module.exports = function(RED) {
this.roomId = n.roomId;
if(!this.server) {
node.error('Server must be configured on the node.');
node.error('Server must be configured on the node.', {});
return;
}
node.server.register(node);
this.encodeUri = function(pathTemplate, variables) {
for (const key in variables) {
@@ -37,18 +38,19 @@ module.exports = function(RED) {
node.on("input", function (msg) {
if (! node.server || ! node.server.matrixClient) {
node.error("No matrix server selected");
node.error("No matrix server selected", msg);
return;
}
if(!node.server.isConnected()) {
node.error("Matrix server connection is currently closed");
node.error("Matrix server connection is currently closed", msg);
node.send([null, msg]);
return;
}
msg.topic = node.roomId || msg.topic;
if(!msg.topic) {
node.error("room must be defined in either msg.topic or in node config");
node.error("room must be defined in either msg.topic or in node config", msg);
return;
}
@@ -64,6 +66,10 @@ module.exports = function(RED) {
node.send([null, msg]);
});
});
node.on("close", function() {
node.server.deregister(node);
});
}
RED.nodes.registerType("matrix-invite-room", MatrixInviteRoom);
}
+1 -1
View File
@@ -8,7 +8,7 @@
outputs:2,
defaults: {
name: { value: null },
server: { value: "", type: "matrix-server-config" }
server: { type: "matrix-server-config" }
},
label: function() {
return this.name || "Join Room";
+10 -4
View File
@@ -11,6 +11,7 @@ module.exports = function(RED) {
node.warn("No configuration node");
return;
}
node.server.register(node);
node.status({ fill: "red", shape: "ring", text: "disconnected" });
@@ -24,17 +25,18 @@ module.exports = function(RED) {
node.on("input", function (msg) {
if (! node.server || ! node.server.matrixClient) {
node.error("No matrix server selected");
node.error("No matrix server selected", msg);
return;
}
if(!node.server.isConnected()) {
node.error("Matrix server connection is currently closed");
node.error("Matrix server connection is currently closed", msg);
node.send([null, msg]);
return;
}
if(!msg.topic) {
node.error("Room must be specified in msg.topic");
node.error("Room must be specified in msg.topic", msg);
return;
}
@@ -46,11 +48,15 @@ module.exports = function(RED) {
node.send([msg, null]);
})
.catch(function(e){
node.error("Error trying to join room " + msg.topic + ":" + e);
node.error("Error trying to join room " + msg.topic + ":" + e, msg);
msg.error = e;
node.send([null, msg]);
});
});
node.on("close", function() {
node.server.deregister(node);
});
}
RED.nodes.registerType("matrix-join-room", MatrixJoinRoom);
}
+1 -1
View File
@@ -8,7 +8,7 @@
outputs: 2,
defaults: {
name: { value: null },
server: { value: "", type: "matrix-server-config" },
server: { type: "matrix-server-config" },
roomId: { value: null },
},
label: function() {
+12 -5
View File
@@ -11,9 +11,10 @@ module.exports = function(RED) {
node.status({ fill: "red", shape: "ring", text: "disconnected" });
if (!node.server) {
node.error("No configuration node");
node.error("No configuration node", {});
return;
}
node.server.register(node);
node.server.on("disconnected", function(){
node.status({ fill: "red", shape: "ring", text: "disconnected" });
@@ -25,30 +26,36 @@ module.exports = function(RED) {
node.on('input', function(msg) {
if (! node.server || ! node.server.matrixClient) {
node.error("No matrix server selected");
node.error("No matrix server selected", msg);
return;
}
if(!msg.topic) {
node.error('No room provided in msg.topic');
node.error('No room provided in msg.topic', msg);
return;
}
if(!node.server.isConnected()) {
node.error("Matrix server connection is currently closed");
node.error("Matrix server connection is currently closed", msg);
node.send([null, msg]);
return;
}
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);
node.error("Failed to leave room " + msg.topic + ": " + e, msg);
msg.payload = e;
node.send([null, msg]);
}
});
node.on("close", function() {
node.server.deregister(node);
});
}
RED.nodes.registerType("matrix-leave-room", MatrixLeaveRoom);
}
+1 -1
View File
@@ -8,7 +8,7 @@
outputs:2,
defaults: {
name: { value: null },
server: { value: "", type: "matrix-server-config" },
server: { type: "matrix-server-config" },
roomId: { value: null },
reaction: { value: null }
},
+11 -5
View File
@@ -13,6 +13,7 @@ module.exports = function(RED) {
node.warn("No configuration node");
return;
}
node.server.register(node);
node.status({ fill: "red", shape: "ring", text: "disconnected" });
@@ -26,30 +27,31 @@ module.exports = function(RED) {
node.on("input", function (msg) {
if (!node.server || !node.server.matrixClient) {
node.error("No matrix server selected");
node.error("No matrix server selected", msg);
return;
}
if(!node.server.isConnected()) {
node.error("Matrix server connection is currently closed");
node.error("Matrix server connection is currently closed", msg);
node.send([null, msg]);
return;
}
msg.topic = node.roomId || msg.topic;
if(!msg.topic) {
node.error("Room must be specified in msg.topic or in configuration");
node.error("Room must be specified in msg.topic or in configuration", msg);
return;
}
let payload = n.reaction || msg.payload;
if(!payload) {
node.error('msg.payload must be defined or the reaction configured on the node.');
node.error('msg.payload must be defined or the reaction configured on the node.', msg);
return;
}
let eventId = msg.referenceEventId || msg.eventId;
if(!eventId) {
node.error('Either msg.referenceEventId or msg.eventId must be defined to react to a message.');
node.error('Either msg.referenceEventId or msg.eventId must be defined to react to a message.', msg);
return;
}
@@ -75,6 +77,10 @@ module.exports = function(RED) {
node.send([null, msg]);
});
});
node.on("close", function() {
node.server.deregister(node);
});
}
RED.nodes.registerType("matrix-react", MatrixReact);
}
+1 -1
View File
@@ -8,7 +8,7 @@
outputs:1,
defaults: {
name: { value: null },
server: { value: "", type: "matrix-server-config" },
server: { type: "matrix-server-config" },
roomId: {"value": null},
acceptText: {"value": true},
acceptEmotes: {"value": true},
+6 -1
View File
@@ -21,9 +21,10 @@ module.exports = function(RED) {
node.status({ fill: "red", shape: "ring", text: "disconnected" });
if (!node.server) {
node.error("No configuration node");
node.error("No configuration node", {});
return;
}
node.server.register(node);
node.server.on("disconnected", function(){
node.status({ fill: "red", shape: "ring", text: "disconnected" });
@@ -149,6 +150,10 @@ module.exports = function(RED) {
node.send(msg);
});
node.on("close", function() {
node.server.deregister(node);
});
}
RED.nodes.registerType("matrix-receive", MatrixReceiveMessage);
}
+1 -1
View File
@@ -8,7 +8,7 @@
outputs:2,
defaults: {
name: { value: null },
server: { value: "", type: "matrix-server-config" },
server: { type: "matrix-server-config" },
roomId: { value: null },
reason: { value: null }
},
+11 -5
View File
@@ -13,6 +13,7 @@ module.exports = function(RED) {
node.warn("No configuration node");
return;
}
node.server.register(node);
node.status({ fill: "red", shape: "ring", text: "disconnected" });
@@ -26,23 +27,24 @@ module.exports = function(RED) {
node.on("input", function (msg) {
if (! node.server || ! node.server.matrixClient) {
node.error("No matrix server selected");
node.error("No matrix server selected", msg);
return;
}
if(!node.server.isConnected()) {
node.error("Matrix server connection is currently closed");
node.error("Matrix server connection is currently closed", msg);
node.send([null, msg]);
return;
}
msg.topic = node.roomId || msg.topic;
if(!msg.topic) {
node.error("Room must be specified in msg.topic or in configuration");
node.error("Room must be specified in msg.topic or in configuration", msg);
return;
}
if(!msg.userId) {
node.error("msg.userId was not set.");
node.error("msg.userId was not set.", msg);
return;
}
@@ -53,11 +55,15 @@ module.exports = function(RED) {
node.send([msg, null]);
})
.catch(function(e){
node.error("Error trying to ban " + msg.userId + " from " + msg.topic);
node.error("Error trying to ban " + msg.userId + " from " + msg.topic, msg);
msg.error = e;
node.send([null, msg]);
});
});
node.on("close", function() {
node.server.deregister(node);
});
}
RED.nodes.registerType("matrix-room-ban", MatrixBan);
}
+1 -1
View File
@@ -8,7 +8,7 @@
outputs: 1,
defaults: {
name: { value: null },
server: { value: "", type: "matrix-server-config" },
server: { type: "matrix-server-config" },
roomId: { value: null },
},
label: function() {
+6 -1
View File
@@ -9,9 +9,10 @@ module.exports = function(RED) {
this.roomId = n.roomId;
if(!this.server) {
node.error('Server must be configured on the node.');
node.error('Server must be configured on the node.', {});
return;
}
node.server.register(node);
node.status({ fill: "red", shape: "ring", text: "disconnected" });
@@ -26,6 +27,10 @@ module.exports = function(RED) {
node.server.on("Room.invite", async function(msg) {
node.send(msg);
});
node.on("close", function() {
node.server.deregister(node);
});
}
RED.nodes.registerType("matrix-room-invite", MatrixRoomInvite);
}
+1 -1
View File
@@ -8,7 +8,7 @@
outputs:2,
defaults: {
name: { value: null },
server: { value: "", type: "matrix-server-config" },
server: { type: "matrix-server-config" },
roomId: { value: null },
reason: { value: null }
},
+11 -5
View File
@@ -13,6 +13,7 @@ module.exports = function(RED) {
node.warn("No configuration node");
return;
}
node.server.register(node);
node.status({ fill: "red", shape: "ring", text: "disconnected" });
@@ -26,23 +27,24 @@ module.exports = function(RED) {
node.on("input", function (msg) {
if (! node.server || ! node.server.matrixClient) {
node.error("No matrix server selected");
node.error("No matrix server selected", msg);
return;
}
if(!node.server.isConnected()) {
node.error("Matrix server connection is currently closed");
node.error("Matrix server connection is currently closed", msg);
node.send([null, msg]);
return;
}
msg.topic = node.roomId || msg.topic;
if(!msg.topic) {
node.error("Room must be specified in msg.topic or in configuration");
node.error("Room must be specified in msg.topic or in configuration", msg);
return;
}
if(!msg.userId) {
node.error("msg.userId was not set.");
node.error("msg.userId was not set.", msg);
return;
}
@@ -53,11 +55,15 @@ module.exports = function(RED) {
node.send([msg, null]);
})
.catch(function(e){
node.error("Error trying to kick " + msg.userId + " from " + msg.topic);
node.error("Error trying to kick " + msg.userId + " from " + msg.topic, msg);
msg.error = e;
node.send([null, msg]);
});
});
node.on("close", function() {
node.server.deregister(node);
});
}
RED.nodes.registerType("matrix-room-kick", MatrixKick);
}
+372
View File
@@ -0,0 +1,372 @@
<script type="text/html" data-template-name="matrix-room-state-events">
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
</div>
<div class="form-row">
<label for="node-input-server"><i class="fa fa-user"></i> Matrix Server Config</label>
<input type="text" id="node-input-server">
</div>
<div class="form-row">
<label for="node-input-roomId"><i class="fa fa-comments"></i> Room ID</label>
<input type="text" id="node-input-roomId" placeholder="msg.topic">
<pre class="form-tips" id="node-input-roomId-error" style="color: #721c24;background-color: #f8d7da;border-color: #f5c6cb;margin-bottom: 12px;margin-top: 12px;display:none;"></pre>
</div>
<div class="form-row" style="margin-bottom:0;">
<label><i class="fa fa-list"></i> <span data-i18n="change.label.rules"></span></label>
</div>
<div class="form-row node-input-rule-container-row">
<ol id="node-input-rule-container"></ol>
</div>
<script type="text/javascript">
$(function(){
$("#node-input-roomId").on("keyup", function() {
if($(this).val() && !$(this).val().startsWith("!")) {
$("#node-input-roomId-error").html(`Room IDs start with exclamation point "!"<br />Example: !OGEhHVWSdvArJzumhm:matrix.org`).show();
} else {
$("#node-input-roomId-error").hide();
}
}).trigger('keyup');
});
</script>
</script>
<script type="text/html" data-help-name="matrix-room-state-events">
<h3>Details</h3>
<p>
Set and Get a list of room state events for a given room. Allows you to set/get room name, topic, avatar, etc.
</p>
<h3>Inputs</h3>
<dl class="message-properties">
<dt class="optional">msg.topic
<span class="property-type">string | null</span>
</dt>
<dd> The room to set/get settings for.</dd>
<dt class="optional">dynamic
<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>
<ol class="node-ports">
<li>Success
<dl class="message-properties">
<dt>msg <span class="property-type">object</span></dt>
<dd>Original message object with modifications based on config.</dd>
</dl>
<dl class="message-properties">
<dt>msg.setter_errors <span class="property-type">undefined|object</span></dt>
<dd>Returned if setting a room state event failed. The key of the object is the room state event type and the value is the error that occurred.</dd>
</dl>
<dl class="message-properties">
<dt>msg.getter_errors <span class="property-type">undefined|object</span></dt>
<dd>Returned if getting a room state event failed. The key of the object is the room state event type and the value is the error that occurred. Note that you will get an error if you try getting a room state event that doesn't exist (such as fetching avatar on a room that doesn't have one).</dd>
</dl>
<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>). 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>
<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.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",
p: roomEventTypeOptions[0].value,
to: "payload",
tot: "msg"
}];
function isInvalidProperty(v,vt) {
if (/msg|flow|global/.test(vt)) {
if (!RED.utils.validatePropertyExpression(v)) {
return "Invalid property: " + v;
}
} else if (vt === "jsonata") {
try{ jsonata(v); } catch(e) {
return "Invalid expression: " + e.message;
}
} else if (vt === "json") {
try{ JSON.parse(v); } catch(e) {
return "Invalid JSON data: " + e.message;
}
}
return false;
}
RED.nodes.registerType('matrix-room-state-events',{
category: 'matrix',
color: '#00b7ca',
icon: "matrix.png",
outputLabels: ["success", "error"],
inputs:1,
outputs:2,
defaults: {
name: { value: null },
server: { type: "matrix-server-config" },
roomId: { value: null },
reason: { value: null },
rules: {
value: defaultRules,
validate: function(rules, opt) {
let msg;
const errors = []
if (!rules || rules.length === 0) { return true }
for (let i=0;i<rules.length;i++) {
const opt = { label: "Rule"+' '+(i+1) }
const r = rules[i];
if (r.t === 'set' || r.t === 'get') {
if ((msg = isInvalidProperty(r.p,r.pt)) !== false) {
return msg;
}
if ((msg = isInvalidProperty(r.to,r.tot)) !== false) {
return msg;
}
}
}
return errors.length ? errors : true;
}
},
},
oneditprepare: function() {
var set = "Set";
var to = "to the value";
var toValueLabel = "to the property";
var search = this._("change.action.search");
var replace = this._("change.action.replace");
var regex = this._("change.label.regex");
function createPropertyValue(row2_1, row2_2, type, defaultType) {
var propValInput = $('<input/>',{class:"node-input-rule-property-value",type:"text"})
.appendTo(row2_1)
.typedInput({
default: defaultType || (type === 'set' ? 'str' : 'msg'),
types: (type === 'set' ? ['msg','flow','global','str','json','jsonata'] : ['msg', 'flow', 'global'])
});
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, localStorageEl];
}
$('#node-input-rule-container').css('min-height','150px').css('min-width','450px').editableList({
addItem: function(container,i,opt) {
var rule = opt;
if (!rule.hasOwnProperty('t')) {
rule = {t:"set",p:roomEventTypeOptions[0].value,to:"payload",tot:"msg"};
}
if (rule.t === "set" && !rule.tot) {
if (rule.to.indexOf("msg.") === 0 && !rule.tot) {
rule.to = rule.to.substring(4);
rule.tot = "msg";
} else {
rule.tot = "str";
}
}
container.css({
overflow: 'hidden',
whiteSpace: 'nowrap'
});
let fragment = document.createDocumentFragment();
var row1 = $('<div/>',{style:"display:flex; align-items: center"}).appendTo(fragment);
var row2 = $('<div/>',{style:"margin-top:8px;"}).appendTo(fragment);
var row3 = $('<div/>',{style:"margin-top:8px;"}).appendTo(fragment);
var row4 = $('<div/>',{style:"display:flex;margin-top:8px;align-items: baseline"}).appendTo(fragment);
var selectField = $('<select/>',{class:"node-input-rule-type",style:"width:110px; margin-right:10px;"}).appendTo(row1);
var selectOptions = [
{v:"set",l:"Set"},
{v:"get",l:"Get"}
];
for (var x=0; x<selectOptions.length; x++) {
selectField.append($("<option></option>").val(selectOptions[x].v).text(selectOptions[x].l));
}
var propertyName = $('<input/>',{class:"node-input-rule-property-name",type:"text"})
.appendTo(row1)
.autoComplete({
minLength:0,
search: function(val) {
if(!val) {
return roomEventTypeOptions.sort(function(A,B){return A.i-B.i});
}
var matches = [];
roomEventTypeOptions.map((x) => x.value).forEach(v => {
var i = v.toLowerCase().indexOf(val.toLowerCase());
if (i > -1) {
matches.push({
value: v,
label: v,
i: i
})
}
});
matches.sort(function(A,B){return A.i-B.i})
return matches
}
})
.on('focus', function(evt){
// following is a fix so autocomplete will show list on focus
if(!evt.isTrigger) {
evt.stopPropagation();
$(this).trigger("keyup.red-ui-autoComplete");
}
}).on("keyup", function(evt) {
// following allows autocomplete to display even when backspace/delete is used
if (evt.keyCode === 8 || evt.keyCode === 46) {
evt.stopPropagation();
$(this).trigger("keyup.red-ui-autoComplete");
}
});
var row2_1 = $('<div/>', {style:"display:flex;align-items: baseline"}).appendTo(row2);
$('<div/>',{style:"display:inline-block;text-align:right; width:120px; padding-right:10px; box-sizing:border-box;"})
.text(toValueLabel)
.appendTo(row2_1);
var row2_2 = $('<div/>', {style:"margin-top: 4px;"}).appendTo(row2);
var row3_1 = $('<div/>', {style:"display:flex;align-items: baseline"}).appendTo(row3);
$('<div/>',{style:"display:inline-block;text-align:right; width:120px; padding-right:10px; box-sizing:border-box;"})
.text(search)
.appendTo(row3_1);
var row3_2 = $('<div/>',{style:"display:flex;margin-top:8px;align-items: baseline"}).appendTo(row3);
$('<div/>',{style:"display:inline-block;text-align:right; width:120px; padding-right:10px; box-sizing:border-box;"})
.text(replace)
.appendTo(row3_2);
$('<div/>',{style:"display:inline-block;text-align:right; width:120px; padding-right:10px; box-sizing:border-box;"})
.text(to)
.appendTo(row4);
let propertyValue = null;
let localStorageEl = null;
let fromValue = null;
let toValue = null;
selectField.on("change", function() {
var type = $(this).val();
if (propertyValue) {
propertyValue.typedInput('hide');
}
if (fromValue) {
fromValue.typedInput('hide');
}
if (toValue) {
toValue.typedInput('hide');
}
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','json','jsonata'] : ['msg', 'flow', 'global']));
}
propertyValue.typedInput('show');
row2.show();
if(type === 'get') {
localStorageEl.parent().show();
} else {
localStorageEl.parent().hide();
}
row3.hide();
row4.hide();
});
selectField.val(rule.t);
propertyName.val(rule.p);
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);
},
removable: true,
sortable: true
});
for (var i=0; i<this.rules.length; i++) {
var rule = this.rules[i];
$("#node-input-rule-container").editableList('addItem',rule);
}
},
oneditsave: function() {
var rules = $("#node-input-rule-container").editableList('items');
var node = this;
node.rules= [];
rules.each(function(i) {
var rule = $(this);
var type = rule.find(".node-input-rule-type").val();
var r = {
t:type,
p:rule.find(".node-input-rule-property-name").val(),
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);
});
},
oneditresize: function(size) {
var rows = $("#dialog-form>div:not(.node-input-rule-container-row)");
var height = size.height;
for (var i=0; i<rows.length; i++) {
height -= $(rows[i]).outerHeight(true);
}
var editorRow = $("#dialog-form>div.node-input-rule-container-row");
height -= (parseInt(editorRow.css("marginTop"))+parseInt(editorRow.css("marginBottom")));
height += 16;
$("#node-input-rule-container").editableList('height',height);
},
label: function() {
return this.name || "Room State Events";
},
paletteLabel: 'Room State Events'
});
})();
</script>
+292
View File
@@ -0,0 +1,292 @@
module.exports = function(RED) {
function MatrixRoomSettings(n) {
RED.nodes.createNode(this, n);
var node = this;
this.name = n.name;
this.server = RED.nodes.getNode(n.server);
this.roomId = n.roomId;
this.rules = n.rules;
if (!node.server) {
node.warn("No configuration node");
return;
}
node.server.register(node);
node.status({ fill: "red", shape: "ring", text: "disconnected" });
node.server.on("disconnected", function(){
node.status({ fill: "red", shape: "ring", text: "disconnected" });
});
node.server.on("connected", function() {
node.status({ fill: "green", shape: "ring", text: "connected" });
});
node.on("input", async function (msg) {
if (! node.server || ! node.server.matrixClient) {
msg.error = "No matrix server selected";
node.error(msg.error, msg);
node.send([null, msg]);
return;
}
if(!node.server.isConnected()) {
msg.error = "Matrix server connection is currently closed";
node.error(msg.error, msg);
node.send([null, msg]);
return;
}
msg.topic = node.roomId || msg.topic;
if(!msg.topic) {
msg.error = "Room must be specified in msg.topic or in configuration";
node.error(msg.error, msg);
node.send([null, msg]);
return;
}
let getterErrors = {},
setterErrors = {};
if(!Array.isArray(node.rules) || !node.rules.length) {
node.warn("No rules configured, skipping", msg);
return msg;
}
function getToValue(msg, rule) {
var value = rule.to;
if (rule.tot === 'json') {
try {
value = JSON.parse(rule.to);
} catch(e) {
throw new Error("Invalid JSON");
}
} else if (rule.tot === 'bin') {
try {
value = Buffer.from(JSON.parse(rule.to))
} catch(e) {
throw new Error("Invalid Binary");
}
}
if (rule.tot === "msg") {
value = RED.util.getMessageProperty(msg,rule.to);
} else if ((rule.tot === 'flow') || (rule.tot === 'global')) {
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') {
try {
value = RED.util.evaluateJSONataExpression(rule.to,msg);
} catch(e3) {
throw new Error("Invalid expression");
}
return;
}
return value;
}
function setToValue(value, rule) {
if(rule.tot === 'global' || rule.tot === 'flow') {
var contextKey = RED.util.parseContextStore(rule.to);
if (/\[msg/.test(contextKey.key)) {
// The key has a nest msg. reference to evaluate first
contextKey.key = RED.util.normalisePropertyExpression(contextKey.key, msg, true)
}
var target = node.context()[rule.tot];
var callback = err => {
if (err) {
node.error(err, msg);
getterErrors[rule.p] = err.message;
}
}
target.set(contextKey.key, value, contextKey.store, callback);
} else if(rule.tot === 'msg') {
if (!RED.util.setMessageProperty(msg, rule.to, value)) {
node.warn(RED._("change.errors.no-override",{property:rule.to}));
}
}
}
for(let rule of node.rules) {
// [
// {
// "t": "set",
// "p": "m.room.topic",
// "to": "asdf",
// "tot": "str"
// }, ...
// ]
let cachedGetters = {};
if(rule.t === 'set') {
let value;
try {
value = getToValue(msg, rule);
switch(rule.p) {
case "m.room.name":
await node.server.matrixClient.sendStateEvent(
msg.topic,
"m.room.name",
typeof value === "string"
? {name: value}
: value);
break;
case "m.room.topic":
if (typeof value === "string") {
await node.server.matrixClient.setRoomTopic(msg.topic, value);
} else {
await node.server.matrixClient.sendStateEvent(
msg.topic,
"m.room.topic",
value
);
}
break;
case "m.room.avatar":
await node.server.matrixClient.sendStateEvent(
msg.topic,
"m.room.avatar",
typeof value === "string"
? {"url": value}
: value,
"");
break;
case "m.room.join_rules":
if(typeof value !== 'object') {
setterErrors[rule.p] = "m.room.join_rules content must be object";
} else {
await node.server.matrixClient.sendStateEvent(
msg.topic,
"m.room.join_rules",
value,
"");
}
break;
case "m.room.canonical_alias":
if(typeof value !== 'object') {
setterErrors[rule.p] = "m.room.canonical_alias content must be object";
} else {
await node.server.matrixClient.sendStateEvent(
msg.topic,
"m.room.canonical_alias",
value,
"");
}
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] = `${rule.p} content must be object`;
} else {
await node.server.matrixClient.sendStateEvent(
msg.topic,
rule.p,
value,
msg.state_key || "");
}
break;
}
} catch(e) {
setterErrors[rule.p] = e.message;
}
} else if(rule.t === 'get') {
let value;
if(cachedGetters.hasOwnProperty(rule.p)) {
value = cachedGetters[rule.p];
} else {
try {
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) {
getterErrors[rule.p] = e;
}
}
}
}
if(Object.keys(setterErrors).length) {
msg.setter_errors = setterErrors;
}
if(Object.keys(getterErrors).length) {
msg.getter_errors = getterErrors;
}
node.send([msg, null]);
});
node.on("close", function() {
node.server.deregister(node);
});
}
RED.nodes.registerType("matrix-room-state-events", MatrixRoomSettings);
}
+1 -1
View File
@@ -8,7 +8,7 @@
outputs:2,
defaults: {
name: { value: null },
server: { value: "", type: "matrix-server-config" },
server: { type: "matrix-server-config" },
roomId: { value: "" }
},
label: function() {
+8 -2
View File
@@ -13,6 +13,7 @@ module.exports = function(RED) {
node.warn("No configuration node");
return;
}
node.server.register(node);
node.status({ fill: "red", shape: "ring", text: "disconnected" });
@@ -31,13 +32,14 @@ module.exports = function(RED) {
}
if(!node.server.isConnected()) {
node.error("Matrix server connection is currently closed");
node.error("Matrix server connection is currently closed", msg);
node.send([null, msg]);
return;
}
let roomId = node.roomId || msg.topic;
if(!roomId) {
node.error("msg.topic is required. Specify in the input or configure the room ID on the node.");
node.error("msg.topic is required. Specify in the input or configure the room ID on the node.", msg);
return;
}
@@ -65,6 +67,10 @@ module.exports = function(RED) {
node.send([null, msg]);
});
});
node.on("close", function() {
node.server.deregister(node);
});
}
RED.nodes.registerType("matrix-room-users", MatrixRoomUsers);
}
+1 -1
View File
@@ -8,7 +8,7 @@
outputs:2,
defaults: {
name: { value: null },
server: { value: "", type: "matrix-server-config" },
server: { type: "matrix-server-config" },
roomId: { value: null },
contentType: { value: null }
},
+8 -2
View File
@@ -13,6 +13,7 @@ module.exports = function(RED) {
node.warn("No configuration node");
return;
}
node.server.register(node);
node.status({ fill: "red", shape: "ring", text: "disconnected" });
@@ -31,8 +32,9 @@ module.exports = function(RED) {
}
if(!node.server.isConnected()) {
node.error("Matrix server connection is currently closed");
node.error("Matrix server connection is currently closed", msg);
node.send([null, msg]);
return;
}
msg.topic = node.roomId || msg.topic;
@@ -57,7 +59,7 @@ module.exports = function(RED) {
}
if(!msg.payload) {
node.error('msg.payload is required');
node.error('msg.payload is required', msg);
return;
}
@@ -94,6 +96,10 @@ module.exports = function(RED) {
node.send([null, msg]);
});
});
node.on("close", function() {
node.server.deregister(node);
});
}
RED.nodes.registerType("matrix-send-file", MatrixSendFile);
}
+1 -1
View File
@@ -8,7 +8,7 @@
outputs:2,
defaults: {
name: { value: null },
server: { value: "", type: "matrix-server-config" },
server: { type: "matrix-server-config" },
roomId: { value: null },
contentType: { value: null }
},
+8 -2
View File
@@ -13,6 +13,7 @@ module.exports = function(RED) {
node.warn("No configuration node");
return;
}
node.server.register(node);
node.status({ fill: "red", shape: "ring", text: "disconnected" });
@@ -31,8 +32,9 @@ module.exports = function(RED) {
}
if(!node.server.isConnected()) {
node.error("Matrix server connection is currently closed");
node.error("Matrix server connection is currently closed", msg);
node.send([null, msg]);
return;
}
msg.topic = node.roomId || msg.topic;
@@ -57,7 +59,7 @@ module.exports = function(RED) {
}
if(!msg.payload) {
node.error('msg.payload is required');
node.error('msg.payload is required', msg);
return;
}
@@ -98,6 +100,10 @@ module.exports = function(RED) {
node.send([null, msg]);
});
});
node.on("close", function() {
node.server.deregister(node);
});
}
RED.nodes.registerType("matrix-send-image", MatrixSendImage);
}
+6 -3
View File
@@ -8,7 +8,7 @@
outputs:2,
defaults: {
name: { value: null },
server: { value: "", type: "matrix-server-config" },
server: { type: "matrix-server-config" },
roomId: { value: null },
message: { value: null },
messageType: { value: 'm.text' },
@@ -43,6 +43,9 @@
<label for="node-input-message"><i class="fa fa-comment"></i> Message</label>
<textarea id="node-input-message" placeholder="msg.payload" style="width: 70%;"></textarea>
</div>
<div class="form-row form-tips">
If message is an object it sets the full content of the message.
</div>
<div class="form-row">
<input
@@ -104,9 +107,9 @@
<dd> Room ID to send image to. Optional if configured on the node. If configured on the node this input will be overridden.</dd>
<dt>msg.payload
<span class="property-type">string</span>
<span class="property-type">string|object</span>
</dt>
<dd> the message text. If configured on the node this is ignored otherwise it required. </dd>
<dd> the message text or an object to customize the full content. If configured on the node this is ignored otherwise it required. </dd>
<dt>msg.replace
<span class="property-type">bool</span>
+55 -45
View File
@@ -54,6 +54,7 @@ module.exports = function(RED) {
node.warn("No configuration node");
return;
}
node.server.register(node);
node.status({ fill: "red", shape: "ring", text: "disconnected" });
@@ -69,29 +70,13 @@ module.exports = function(RED) {
let msgType = node.messageType,
msgFormat = node.messageFormat;
if(msgType === 'msg.type') {
if(!msg.type) {
node.error("msg.type type is set to be passed in via msg.type but was not defined");
return;
}
msgType = msg.type;
}
if(msgFormat === 'msg.format') {
if(!msg.format) {
node.error("Message format is set to be passed in via msg.format but was not defined");
return;
}
msgFormat = msg.format;
}
if (!node.server || !node.server.matrixClient) {
node.warn("No matrix server selected");
return;
}
if(!node.server.isConnected()) {
node.error("Matrix server connection is currently closed");
node.error("Matrix server connection is currently closed", msg);
node.send([null, msg]);
return;
}
@@ -104,40 +89,61 @@ module.exports = function(RED) {
let payload = n.message || msg.payload;
if(!payload) {
node.error('msg.payload must be defined or the message configured on the node.');
node.error('msg.payload must be defined or the message configured on the node.', msg);
return;
}
let content = {
msgtype: msgType,
body: payload.toString()
};
if(msgFormat === 'html') {
content.format = "org.matrix.custom.html";
content.formatted_body =
(typeof msg.formatted_payload !== 'undefined' && msg.formatted_payload)
? msg.formatted_payload.toString()
: payload.toString();
}
if((node.replaceMessage || msg.replace) && msg.eventId) {
content['m.new_content'] = {
msgtype: content.msgtype,
body: content.body
};
if('format' in content) {
content['m.new_content']['format'] = content['format'];
}
if('formatted_body' in content) {
content['m.new_content']['formatted_body'] = content['formatted_body'];
let content = null;
if(typeof payload === 'object') {
content = payload;
} else {
if(msgType === 'msg.type') {
if(!msg.type) {
node.error("msg.type type is set to be passed in via msg.type but was not defined", msg);
return;
}
msgType = msg.type;
}
content['m.relates_to'] = {
rel_type: RelationType.Replace,
event_id: msg.eventId
if(msgFormat === 'msg.format') {
if(!msg.format) {
node.error("Message format is set to be passed in via msg.format but was not defined", msg);
return;
}
msgFormat = msg.format;
}
content = {
msgtype: msgType,
body: payload.toString()
};
content['body'] = ' * ' + content['body'];
if(msgFormat === 'html') {
content.format = "org.matrix.custom.html";
content.formatted_body =
(typeof msg.formatted_payload !== 'undefined' && msg.formatted_payload)
? msg.formatted_payload.toString()
: payload.toString();
}
if((node.replaceMessage || msg.replace) && msg.eventId) {
content['m.new_content'] = {
msgtype: content.msgtype,
body: content.body
};
if('format' in content) {
content['m.new_content']['format'] = content['format'];
}
if('formatted_body' in content) {
content['m.new_content']['formatted_body'] = content['formatted_body'];
}
content['m.relates_to'] = {
rel_type: RelationType.Replace,
event_id: msg.eventId
};
content['body'] = ' * ' + content['body'];
}
}
node.server.matrixClient.sendMessage(msg.topic, content)
@@ -152,6 +158,10 @@ module.exports = function(RED) {
node.send([null, msg]);
});
});
node.on("close", function() {
node.server.deregister(node);
});
}
RED.nodes.registerType("matrix-send-message", MatrixSendImage);
}
+35 -20
View File
@@ -44,6 +44,7 @@ module.exports = function(RED) {
this.credentials = {};
}
this.users = {};
this.connected = null;
this.name = n.name;
this.userId = this.credentials.userId;
@@ -55,6 +56,14 @@ module.exports = function(RED) {
this.globalAccess = n.global;
this.initializedAt = new Date();
// Keep track of all consumers of this node to be able to catch errors
node.register = function(consumerNode) {
node.users[consumerNode.id] = consumerNode;
};
node.deregister = function(consumerNode) {
delete node.users[consumerNode.id];
};
if(!this.userId) {
node.log("Matrix connection failed: missing user ID in configuration.");
@@ -68,9 +77,9 @@ module.exports = function(RED) {
let retryStartTimeout = null;
if(!this.credentials.accessToken) {
node.error("Matrix connection failed: missing access token in configuration.");
node.error("Matrix connection failed: missing access token in configuration.", {});
} else if(!this.url) {
node.error("Matrix connection failed: missing server URL in configuration.");
node.error("Matrix connection failed: missing server URL in configuration.", {});
} else {
node.setConnected = async function(connected, cb) {
if (node.connected !== connected) {
@@ -88,7 +97,7 @@ module.exports = function(RED) {
device_id = this.matrixClient.getDeviceId();
if(!device_id && node.enableE2ee) {
node.error("Failed to auto detect deviceId for this auth token. You will need to manually specify one. You may need to login to create a new deviceId.")
node.error("Failed to auto detect deviceId for this auth token. You will need to manually specify one. You may need to login to create a new deviceId.", {})
} else {
if(!stored_device_id || stored_device_id !== device_id) {
node.log(`Saving Device ID (old:${stored_device_id} new:${device_id})`);
@@ -107,13 +116,13 @@ module.exports = function(RED) {
}).then(
function(response) {},
function(error) {
node.error("Failed to set device label: " + error);
node.error("Failed to set device label: " + error, {});
}
);
}
},
function(error) {
node.error("Failed to fetch device: " + error);
node.error("Failed to fetch device: " + error, {});
}
);
}
@@ -165,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();
});
@@ -189,7 +205,7 @@ module.exports = function(RED) {
try {
await node.matrixClient.decryptEventIfNeeded(event);
} catch (error) {
node.error(error);
node.error(error, {});
return;
}
@@ -260,7 +276,6 @@ module.exports = function(RED) {
if (member.membership === "invite" && member.userId === node.userId) {
node.log("Got invite to join room " + member.roomId);
console.log(event);
if(node.autoAcceptRoomInvites) {
node.matrixClient.joinRoom(member.roomId).then(function() {
node.log("Automatically accepted invitation to join room " + member.roomId);
@@ -292,7 +307,7 @@ module.exports = function(RED) {
} else if(prevState === null && state === "ERROR") {
// Occurs when the initial sync failed first time.
node.setConnected(false, function(){
node.error("Failed to connect to Matrix server");
node.error("Failed to connect to Matrix server", {});
});
} else if(prevState === "ERROR" && state === "PREPARED") {
// Occurs when the initial sync succeeds
@@ -309,18 +324,18 @@ module.exports = function(RED) {
} else if(prevState === "SYNCING" && state === "RECONNECTING") {
// Occurs when the live update fails.
node.setConnected(false, function(){
node.error("Connection to Matrix server lost");
node.error("Connection to Matrix server lost", {});
});
} else if(prevState === "RECONNECTING" && state === "RECONNECTING") {
// Can occur if the update calls continue to fail,
// but the keepalive calls (to /versions) succeed.
node.setConnected(false, function(){
node.error("Connection to Matrix server lost");
node.error("Connection to Matrix server lost", {});
});
} else if(prevState === "RECONNECTING" && state === "ERROR") {
// Occurs when the keepalive call also fails
node.setConnected(false, function(){
node.error("Connection to Matrix server lost");
node.error("Connection to Matrix server lost", {});
});
} else if(prevState === "ERROR" && state === "SYNCING") {
// Occurs when the client has performed a
@@ -332,7 +347,7 @@ module.exports = function(RED) {
// Occurs when the client has failed to
// keepalive for a second time or more.
node.setConnected(false, function(){
node.error("Connection to Matrix server lost");
node.error("Connection to Matrix server lost", {});
});
} else if(prevState === "SYNCING" && state === "SYNCING") {
// Occurs when the client has performed a live update.
@@ -344,7 +359,7 @@ module.exports = function(RED) {
// Occurs once the client has stopped syncing or
// trying to sync after stopClient has been called.
node.setConnected(false, function(){
node.error("Connection to Matrix server lost");
node.error("Connection to Matrix server lost", {});
});
}
});
@@ -362,7 +377,7 @@ module.exports = function(RED) {
// httpStatus: 401
// }
node.error("Authentication failure: " + errorObj);
node.error("Authentication failure: " + errorObj, {});
stopClient();
});
@@ -371,14 +386,14 @@ module.exports = function(RED) {
if(node.e2ee){
node.log("Initializing crypto...");
await node.matrixClient.initCrypto();
node.matrixClient.setGlobalErrorOnUnknownDevices(false);
node.matrixClient.getCrypto().globalBlacklistUnverifiedDevices = false; // prevent errors from unverified devices
}
node.log("Connecting to Matrix server...");
await node.matrixClient.startClient({
initialSyncLimit: 8
});
} catch(error) {
node.error(error);
node.error(error, {});
}
}
@@ -399,7 +414,7 @@ module.exports = function(RED) {
.then(
function(data) {
if((typeof data['device_id'] === undefined || !data['device_id']) && !node.deviceId && !getStoredDeviceId(localStorage)) {
node.error("/whoami request did not return device_id. You will need to manually set one in your configuration because this cannot be automatically fetched.");
node.error("/whoami request did not return device_id. You will need to manually set one in your configuration because this cannot be automatically fetched.", {});
}
if('device_id' in data && data['device_id'] && !node.deviceId) {
// if we have no device_id configured lets use the one
@@ -409,7 +424,7 @@ module.exports = function(RED) {
// make sure our userId matches the access token's
if(data['user_id'].toLowerCase() !== node.userId.toLowerCase()) {
node.error(`User ID provided is ${node.userId} but token belongs to ${data['user_id']}`);
node.error(`User ID provided is ${node.userId} but token belongs to ${data['user_id']}`, {});
return;
}
run().catch((error) => node.error(error));
@@ -418,7 +433,7 @@ module.exports = function(RED) {
// if the error isn't authentication related retry in a little bit
if(err.code !== "M_UNKNOWN_TOKEN") {
retryStartTimeout = setTimeout(checkAuthTokenThenStart, 15000);
node.error("Auth check failed: " + err);
node.error("Auth check failed: " + err, {});
}
}
)
@@ -494,7 +509,7 @@ module.exports = function(RED) {
fs.copySync(oldStorageDir, dir);
}
} catch (err) {
node.error(err);
node.error(err, {});
}
});
+1 -1
View File
@@ -8,7 +8,7 @@
outputs:2,
defaults: {
name: { value: null },
server: { value: "", type: "matrix-server-config" },
server: { type: "matrix-server-config" },
},
label: function() {
return this.name || "Synapse Add/Edit User";
+9 -3
View File
@@ -8,9 +8,10 @@ module.exports = function(RED) {
this.server = RED.nodes.getNode(n.server);
if(!this.server) {
node.error('Server must be configured on the node.');
node.error('Server must be configured on the node.', {});
return;
}
node.server.register(node);
this.encodeUri = function(pathTemplate, variables) {
for (const key in variables) {
@@ -41,12 +42,13 @@ module.exports = function(RED) {
}
if(!node.server.isConnected()) {
node.error("Matrix server connection is currently closed");
node.error("Matrix server connection is currently closed", msg);
node.send([null, msg]);
return;
}
if(!msg.userId) {
node.error("msg.userId must be set to edit/create a user (ex: @user:server.com)");
node.error("msg.userId must be set to edit/create a user (ex: @user:server.com)", msg);
return;
}
@@ -69,6 +71,10 @@ module.exports = function(RED) {
node.send([null, msg]);
});
});
node.on("close", function() {
node.server.deregister(node);
});
}
RED.nodes.registerType("matrix-synapse-create-edit-user", MatrixSynapseCreateEditUser);
}
+1 -1
View File
@@ -8,7 +8,7 @@
outputs:2,
defaults: {
name: { value: null },
server: { value: "", type: "matrix-server-config" },
server: { type: "matrix-server-config" },
},
label: function() {
return this.name || "Synapse Deactivate User";
+9 -3
View File
@@ -8,9 +8,10 @@ module.exports = function(RED) {
this.server = RED.nodes.getNode(n.server);
if(!this.server) {
node.error('Server must be configured on the node.');
node.error('Server must be configured on the node.', {});
return;
}
node.server.register(node);
this.encodeUri = function(pathTemplate, variables) {
for (const key in variables) {
@@ -41,12 +42,13 @@ module.exports = function(RED) {
}
if(!node.server.isConnected()) {
node.error("Matrix server connection is currently closed");
node.error("Matrix server connection is currently closed", msg);
node.send([null, msg]);
return;
}
if(!msg.userId) {
node.error("msg.userId must be set to edit/create a user (ex: @user:server.com)");
node.error("msg.userId must be set to edit/create a user (ex: @user:server.com)", msg);
return;
}
@@ -70,6 +72,10 @@ module.exports = function(RED) {
node.send([null, msg]);
});
});
node.on("close", function() {
node.server.deregister(node);
});
}
RED.nodes.registerType("matrix-synapse-deactivate-user", MatrixSynapseDeactivateUser);
}
+1 -1
View File
@@ -8,7 +8,7 @@
outputs: 2,
defaults: {
name: { value: null },
server: { value: "", type: "matrix-server-config" },
server: { type: "matrix-server-config" },
roomId: { value: null },
},
label: function() {
+9 -5
View File
@@ -9,10 +9,9 @@ module.exports = function(RED) {
this.roomId = n.roomId;
if(!this.server) {
node.error('Server must be configured on the node.');
node.error('Server must be configured on the node.', {});
return;
}
this.encodeUri = function(pathTemplate, variables) {
for (const key in variables) {
if (!variables.hasOwnProperty(key)) {
@@ -42,18 +41,19 @@ module.exports = function(RED) {
}
if(!node.server.isConnected()) {
node.error("Matrix server connection is currently closed");
node.error("Matrix server connection is currently closed", msg);
node.send([null, msg]);
return;
}
msg.topic = node.roomId || msg.topic;
if(!msg.topic) {
node.error("room must be defined in either msg.topic or in node config");
node.error("room must be defined in either msg.topic or in node config", msg);
return;
}
if(!msg.userId) {
node.error("msg.userId is required to set user into a room");
node.error("msg.userId is required to set user into a room", msg);
return;
}
@@ -77,6 +77,10 @@ module.exports = function(RED) {
node.send([null, msg]);
});
});
node.on("close", function() {
node.server.deregister(node);
});
}
RED.nodes.registerType("matrix-synapse-join-room", MatrixJoinRoom);
}
+9 -5
View File
@@ -12,12 +12,12 @@ module.exports = function(RED) {
this.sharedSecret = this.credentials.sharedSecret;
if(!this.server) {
node.error('Server URL must be configured on the node.');
node.error('Server URL must be configured on the node.', {});
return;
}
if(!this.sharedSecret) {
node.error('Shared registration secret must be configured on the node.');
node.error('Shared registration secret must be configured on the node.', {});
return;
}
@@ -25,12 +25,12 @@ module.exports = function(RED) {
const { got } = await import('got');
if(!msg.payload.username) {
node.error("msg.payload.username is required");
node.error("msg.payload.username is required", msg);
return;
}
if(!msg.payload.password) {
node.error("msg.payload.password is required");
node.error("msg.payload.password is required", msg);
return;
}
@@ -50,7 +50,7 @@ module.exports = function(RED) {
var nonce = response.body.nonce;
if(!nonce) {
node.error('Could not get nonce from /_synapse/admin/v1/register');
node.error('Could not get nonce from /_synapse/admin/v1/register', msg);
return;
}
@@ -96,6 +96,10 @@ module.exports = function(RED) {
node.send(msg);
})();
});
node.on("close", function() {
node.server.deregister(node);
});
}
RED.nodes.registerType("matrix-synapse-register", MatrixSynapseRegister, {
credentials: {
+1 -1
View File
@@ -8,7 +8,7 @@
outputs:2,
defaults: {
name: { value: null },
server: { value: "", type: "matrix-server-config" }
server: { type: "matrix-server-config" }
},
label: function() {
return this.name || "Synapse User List";
+8 -1
View File
@@ -12,6 +12,8 @@ module.exports = function(RED) {
return;
}
node.server.register(node);
node.status({ fill: "red", shape: "ring", text: "disconnected" });
node.server.on("disconnected", function(){
@@ -29,8 +31,9 @@ module.exports = function(RED) {
}
if(!node.server.isConnected()) {
node.error("Matrix server connection is currently closed");
node.error("Matrix server connection is currently closed", msg);
node.send([null, msg]);
return;
}
let queryParams = {
@@ -62,6 +65,10 @@ module.exports = function(RED) {
node.send([null, msg]);
});
});
node.on("close", function() {
node.server.deregister(node);
});
}
RED.nodes.registerType("matrix-synapse-users", MatrixSynapseUsers);
}
+112
View File
@@ -0,0 +1,112 @@
<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>
<div class="form-row form-tips">
Timeout Milliseconds is how many milliseconds the server should show the user typing for. Ignored if setting typing to false.
</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>
+88
View File
@@ -0,0 +1,88 @@
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);
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.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;
}
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);
}
+163
View File
@@ -0,0 +1,163 @@
<script type="text/javascript">
RED.nodes.registerType('matrix-upload-file',{
category: 'matrix',
color: '#00b7ca',
icon: "matrix.png",
outputLabels: ["success", "error"],
inputs:1,
outputs:2,
defaults: {
name: { value: null },
server: { type: "matrix-server-config" },
inputType: { value: "msg" },
inputValue: { value: "payload" },
fileNameType: { value: "msg" },
fileNameValue: { value: "filename" },
contentType: { value: null },
generateThumbnails: { type: "checkbox", value: true },
},
label: function() {
return this.name || "Upload File";
},
oneditprepare: function() {
$("#node-input-input").typedInput({
type: this.inputType,
types:['msg','flow','global','str'],
}).typedInput('value', this.inputValue);
$("#node-input-file-name").typedInput({
type: this.fileNameType,
types:['msg','flow','global','str'],
}).typedInput('value', this.fileNameValue);
},
oneditsave: function() {
this.inputType = $("#node-input-input").typedInput('type');
this.inputValue = $("#node-input-input").typedInput('value');
this.fileNameType = $("#node-input-file-name").typedInput('type');
this.fileNameValue = $("#node-input-file-name").typedInput('value');
},
paletteLabel: 'Upload File'
});
</script>
<script type="text/html" data-template-name="matrix-upload-file">
<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-input"><i class="fa fa-file"></i> File Input</label>
<input type="text" id="node-input-input">
</div>
<div class="form-tips" style="margin-bottom: 12px;">
Must be a buffer or string. If it is a string it assumes it is a path to a file on the filesystem.
</div>
<div class="form-row">
<label for="node-input-file-name"><i class="fa fa-file"></i> File Name</label>
<input type="text" id="node-input-file-name">
</div>
<div class="form-tips" style="margin-bottom: 12px;">
Name to give file on remote server. Required if file input is a buffer otherwise it uses the original file name.
</div>
<div class="form-row">
<label for="node-input-contentType"><i class="fa fa-user"></i> Content-Type</label>
<input type="text" id="node-input-contentType" placeholder="msg.contentType">
</div>
<div class="form-tips" style="margin-bottom: 12px;">
Must be a valid <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types" target="_blank">MIME Type</a> (ex: application/pdf) or left undefined to auto detect
</div>
<div class="form-row">
<input
type="checkbox"
id="node-input-generateThumbnails"
style="width: auto; margin-left: 125px; vertical-align: top"
/>
<label for="node-input-generateThumbnails" style="width: auto;max-width:50%;">
Generate m.video thumbnails using ffmpeg
</label>
</div>
<div class="form-tips">
ffmpeg & ffprobe must be installed for thumbnail generation, calculating duration of video or audio file, and getting width & height for videos. This functionality is disabled otherwise.
</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-upload-file">
<h3>Details</h3>
<p>This node will send a file to a Matrix chat room. Supports direct linking to a File In node.</p>
<h3>Inputs</h3>
<dl class="message-properties">
<dt>msg.payload
<span class="property-type">string | Buffer</span>
</dt>
<dd> If this is not a buffer it assumes it is a path to the file. </dd>
<dt>msg.topic
<span class="property-type">string</span>
</dt>
<dd> Room ID to send file to. Ignored if configured on the node, otherwise required.</dd>
<dt class="optional">msg.filename
<span class="property-type">string | null</span>
</dt>
<dd> name of the file to upload. You REALLY should pass this if you want the file type to be detected correctly. If no filename is provided it will be auto-detected but note that this is not perfect (for example: .doc word files are detected incorrectly as application/x-cfb) and only works with binary files.</dd>
<dt class="optional">msg.body
<span class="property-type">string | null</span>
</dt>
<dd> this will be the display name the client will see when rendered in their chat client. If this is left empty it will just display as "Attachment".</dd>
<dt class="optional">msg.contentType
<span class="property-type">string | null</span>
</dt>
<dd> Content <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types" target="_blank">MIME Type</a>. If set this forces the mime type. If you do not provide this it will try to be auto-detected.</dd>
<dt class="optional">msg.content
<span class="property-type">object | null</span>
</dt>
<dd> craft your own msg.content to send to the server. If defined then <code>msg.filename</code>, <code>msg.contentType</code>, <code>msg.body</code>, and <code>msg.payload</code> aren't required since the file already exists.</dd>
</dl>
<h3>Outputs</h3>
<ol class="node-ports">
<li>Success
<dl class="message-properties">
<dt>msg.eventId <span class="property-type">string</span></dt>
<dd>the eventId from the posted message.</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>
<h3>References</h3>
<ul>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types">MIME Types</a> - description of <code>msg.contentType</code> format</li>
</ul>
</script>
+471
View File
@@ -0,0 +1,471 @@
const crypto = require("isomorphic-webcrypto");
const ffmpeg = require('fluent-ffmpeg');
const getImageSize = require('image-size');
const tmp = require('tmp');
const fs = require('fs');
const path = require('path');
module.exports = function(RED) {
function MatrixUploadFile(n) {
RED.nodes.createNode(this, n);
let node = this;
this.name = n.name;
this.server = RED.nodes.getNode(n.server);
this.inputType = n.inputType;
this.inputValue = n.inputValue;
this.fileNameType = n.fileNameType;
this.fileNameValue = n.fileNameValue;
this.contentType = n.contentType;
this.generateThumbnails = n.generateThumbnails;
if (!node.server) {
node.warn("No configuration node");
return;
}
node.server.register(node);
node.status({ fill: "red", shape: "ring", text: "disconnected" });
node.server.on("disconnected", function(){
node.status({ fill: "red", shape: "ring", text: "disconnected" });
});
node.server.on("connected", function() {
node.status({ fill: "green", shape: "ring", text: "connected" });
});
async function detectFileType(filename, bufferOrPath)
{
const Mime = require('mime');
let file = Buffer.isBuffer(bufferOrPath) ? filename : bufferOrPath;
if(file)
{
let type = Mime.getType(file);
let ext = Mime.getExtension(file);
if(type) {
return {ext: ext, mime: type}
}
}
}
function getFileBuffer(data)
{
if(Buffer.isBuffer(data)) {
return data;
}
if (data && RED.settings.fileWorkingDirectory && !path.isAbsolute(data)) {
return fs.readFileSync(path.resolve(path.join(RED.settings.fileWorkingDirectory,data)));
}
return fs.readFileSync(data);
}
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;
}
node.on("input", onInput);
async function onInput(msg)
{
if (! node.server || ! node.server.matrixClient) {
node.warn("No matrix server selected");
return;
}
if(!node.server.isConnected()) {
node.error("Matrix server connection is currently closed", msg);
msg.error = "Matrix server connection is currently closed";
node.send([null, msg]);
return;
}
let bufferOrPath = getToValue(msg, node.inputType, node.inputValue);
if(!bufferOrPath) {
node.error('Missing file path/buffer input', msg);
msg.error = 'Missing file path/buffer input';
node.send([null, msg]);
return;
}
let filename = getToValue(msg, node.fileNameType, node.fileNameValue);
if(!filename || typeof filename !== 'string') {
if(!Buffer.isBuffer(bufferOrPath)) {
filename = path.basename(bufferOrPath);
} else {
node.error('Missing filename, this is required if input is a file buffer', msg);
msg.error = 'Missing filename, this is required if input is a file buffer';
node.send([null, msg]);
return;
}
}
msg.contentType = node.contentType || msg.contentType || null;
let detectedFileType = await detectFileType(filename, bufferOrPath);
node.log("Detected file type " + JSON.stringify(detectedFileType) + " for " + (Buffer.isBuffer(bufferOrPath) ? 'buffer' : `file ${bufferOrPath}`), msg);
let contentType = msg.contentType || detectedFileType?.mime || null,
msgtype = msg.msgtype || null;
if(!contentType) {
node.warn("Content-type failed to detect, falling back to text/plain", msg);
contentType = 'text/plain';
}
if(!msgtype) {
msgtype = autoDetectMatrixMessageType(detectedFileType);
}
let encryptedFile = null;
if(msg.encrypted) {
encryptedFile = await encryptAttachment(getFileBuffer(bufferOrPath));
}
node.log("Uploading file ", msg);
let file;
try {
file = await node.server.matrixClient.uploadContent(
encryptedFile?.data || getFileBuffer(bufferOrPath),
{
name: filename, // Name to give the file on the server.
rawResponse: false, // Return the raw body, rather than parsing the JSON.
type: contentType, // Content-type for the upload. Defaults to file.type, or applicaton/octet-stream.
onlyContentUri: false // Just return the content URI, rather than the whole body. Defaults to false. Ignored if opts.rawResponse is true.
});
} catch(e) {
node.error("Upload content error " + e);
msg.error = e;
node.send([null, msg]);
return;
}
// we call this method when we need a file and cannot use the buffer
// so if we get passed a buffer we write it to a tmp file and return that
// otherwise we just return the string because it's already a file
let tempFile = null;
function getFile(bufferOrFile) {
if(!Buffer.isBuffer(bufferOrFile)) {
return bufferOrFile; // already a file
}
if(tempFile) {
return tempFile;
}
// write buffer to tmp file and return path
let tmpObj = tmp.fileSync({ postfix: `.${detectedFileType.ext}` });
fs.writeFileSync(tmpObj.name, bufferOrFile);
tempFile = tmpObj.name;
return tmpObj.name;
}
function deleteTempFile() {
if(!tempFile) return null;
fs.rmSync(tempFile);
}
// get size of a buffer or file in bytes
function getFileSize(bufferOrPath) {
if(Buffer.isBuffer(bufferOrPath)) {
return Buffer.byteLength(bufferOrPath);
}
return fs.statSync(bufferOrPath).size;
}
async function addThumbnail(buffer) {
let imageSize = getImageSize(Buffer.isBuffer(buffer) ? buffer : buffer.data);
msg.payload.info.thumbnail_info = {
w: imageSize.width,
h: imageSize.height,
size: getFileSize(Buffer.isBuffer(buffer) ? buffer : buffer.data)
}
let uploadedThumbnail = await node.server.matrixClient.uploadContent(
Buffer.isBuffer(buffer) ? buffer : buffer.data,
{
name: "thumbnail.png", // Name to give the file on the server.
rawResponse: false, // Return the raw body, rather than parsing the JSON.
type: "image/png", // Content-type for the upload. Defaults to file.type, or applicaton/octet-stream.
onlyContentUri: false // Just return the content URI, rather than the whole body. Defaults to false. Ignored if opts.rawResponse is true.
});
// delete local file
if(msg.encrypted) {
msg.payload.info.thumbnail_file.url = uploadedThumbnail.content_uri;
} else {
msg.payload.info.thumbnail_url = uploadedThumbnail.content_uri;
}
}
function _ffmpegVideoThumbnail(filepath){
return new Promise((resolve,reject) => {
let filename = `${msg._msgid}-screenshot.png`;
ffmpeg(filepath)
.on('end', async function() {
let path = `/tmp/${filename}`;
let buffer = getFileBuffer(path);
let encryptedThumbnail = null;
if(msg.encrypted) {
encryptedThumbnail = await encryptAttachment(buffer);
msg.payload.info.thumbnail_file = encryptedFile.info;
}
try {
await addThumbnail(encryptedThumbnail || buffer);
fs.rmSync(path); // delete temporary thumbnail file
resolve();
} catch(e) {
return reject(new Error("Thumbnail upload failure: " + e));
}
})
.on('error', function(err) {
return reject(err);
})
.screenshots({
timestamps: [0],
filename: filename,
folder: '/tmp',
size: '320x?'
});
});
}
msg.payload = {};
if(msg.encrypted) {
msg.payload.file = encryptedFile?.info || {};
msg.payload.file.url = file.content_uri;
} else {
msg.payload.url = file.content_uri;
}
msg.payload.msgtype = msgtype;
msg.payload.body = msg.body || msg.filename || "";
msg.payload.info = {
"mimetype": contentType,
"size": getFileSize(bufferOrPath),
};
if(msgtype === 'm.image') {
// detect size of image
try {
let imageSize = getImageSize(buffer);
msg.payload.info.h = imageSize.height;
msg.payload.info.w = imageSize.width;
} catch(e) {
node.error("Failed to get image size: " + e, msg);
}
} else if(msgtype === 'm.audio' && detectedFileType) {
try {
// detect duration of audio clip
let filepath = getFile(bufferOrPath);
let metadata = await _ffprobe(filepath);
let audioStream = metadata?.streams.filter(function(stream){return stream.codec_type === "audio" || false;})[0];
if(audioStream?.duration) {
msg.payload.info.duration = audioStream?.duration * 1000;
}
} catch(e) {
node.error(e, msg);
}
deleteTempFile();
} else if(msgtype === 'm.video' && detectedFileType) {
let filepath = getFile(bufferOrPath);
try {
// detect duration & width/height of video clip
let metadata = await _ffprobe(filepath);
let videoStream = metadata?.streams.filter(function(stream){return stream.codec_type === "video" || false;})[0];
if(videoStream) {
msg.payload.info.duration = videoStream.duration * 1000;
msg.payload.info.w = videoStream.width;
msg.payload.info.h = videoStream.height;
}
} catch(e) {
node.error("ffprobe error: " + e);
}
if(node.generateThumbnails) {
try {
await _ffmpegVideoThumbnail(filepath);
} catch(e) {
node.error("Screenshot generation error: " + e);
}
}
deleteTempFile();
}
node.send(msg, null);
}
node.on("close", function() {
node.server.deregister(node);
});
}
RED.nodes.registerType("matrix-upload-file", MatrixUploadFile);
// the following was taken & modified from https://github.com/matrix-org/browser-encrypt-attachment/blob/master/index.js
/**
* Encrypt an attachment.
* @param {ArrayBuffer} plaintextBuffer The attachment data buffer.
* @return {Promise} A promise that resolves with an object when the attachment is encrypted.
* The object has a "data" key with an ArrayBuffer of encrypted data and an "info" key
* with an object containing the info needed to decrypt the data.
*/
function encryptAttachment(plaintextBuffer) {
let cryptoKey; // The AES key object.
let exportedKey; // The AES key exported as JWK.
let ciphertextBuffer; // ArrayBuffer of encrypted data.
let sha256Buffer; // ArrayBuffer of digest.
let ivArray; // Uint8Array of AES IV
// Generate an IV where the first 8 bytes are random and the high 8 bytes
// are zero. We set the counter low bits to 0 since it makes it unlikely
// that the 64 bit counter will overflow.
ivArray = new Uint8Array(16);
crypto.getRandomValues(ivArray.subarray(0,8));
// Load the encryption key.
return crypto.subtle.generateKey(
{"name": "AES-CTR", length: 256}, true, ["encrypt", "decrypt"]
).then(function(generateKeyResult) {
cryptoKey = generateKeyResult;
// Export the Key as JWK.
return crypto.subtle.exportKey("jwk", cryptoKey);
}).then(function(exportKeyResult) {
exportedKey = exportKeyResult;
// Encrypt the input ArrayBuffer.
// Use half of the iv as the counter by setting the "length" to 64.
return crypto.subtle.encrypt(
{name: "AES-CTR", counter: ivArray, length: 64}, cryptoKey, plaintextBuffer
);
}).then(function(encryptResult) {
ciphertextBuffer = encryptResult;
// SHA-256 the encrypted data.
return crypto.subtle.digest("SHA-256", ciphertextBuffer);
}).then(function (digestResult) {
sha256Buffer = digestResult;
return {
data: ciphertextBuffer,
info: {
v: "v2",
key: exportedKey,
iv: encodeBase64(ivArray),
hashes: {
sha256: encodeBase64(new Uint8Array(sha256Buffer)),
},
},
};
});
}
/**
* Decrypt an attachment.
* @param {ArrayBuffer} ciphertextBuffer The encrypted attachment data buffer.
* @param {Object} info The information needed to decrypt the attachment.
* @param {Object} info.key AES-CTR JWK key object.
* @param {string} info.iv Base64 encoded 16 byte AES-CTR IV.
* @param {string} info.hashes.sha256 Base64 encoded SHA-256 hash of the ciphertext.
* @return {Promise} A promise that resolves with an ArrayBuffer when the attachment is decrypted.
*/
function decryptAttachment(ciphertextBuffer, info) {
if (info === undefined || info.key === undefined || info.iv === undefined
|| info.hashes === undefined || info.hashes.sha256 === undefined) {
throw new Error("Invalid info. Missing info.key, info.iv or info.hashes.sha256 key");
}
let cryptoKey; // The AES key object.
let ivArray = decodeBase64(info.iv);
let expectedSha256base64 = info.hashes.sha256;
// Load the AES from the "key" key of the info object.
return crypto.subtle.importKey(
"jwk", info.key, {"name": "AES-CTR"}, false, ["encrypt", "decrypt"]
).then(function (importKeyResult) {
cryptoKey = importKeyResult;
// Check the sha256 hash
return crypto.subtle.digest("SHA-256", ciphertextBuffer);
}).then(function (digestResult) {
if (encodeBase64(new Uint8Array(digestResult)) !== expectedSha256base64) {
throw new Error("Mismatched SHA-256 digest (expected: " + encodeBase64(new Uint8Array(digestResult)) + ") got (" + expectedSha256base64 + ")");
}
let counterLength;
if (info.v.toLowerCase() === "v1" || info.v.toLowerCase() === "v2") {
// Version 1 and 2 use a 64 bit counter.
counterLength = 64;
} else {
// Version 0 uses a 128 bit counter.
counterLength = 128;
}
return crypto.subtle.decrypt(
{name: "AES-CTR", counter: ivArray, length: counterLength}, cryptoKey, ciphertextBuffer
);
});
}
/**
* Encode a typed array of uint8 as base64.
* @param {Uint8Array} uint8Array The data to encode.
* @return {string} The base64 without padding.
*/
function encodeBase64(uint8Array) {
// Misinterpt the Uint8Array as Latin-1.
// window.btoa expects a unicode string with codepoints in the range 0-255.
// var latin1String = String.fromCharCode.apply(null, uint8Array);
// Use the builtin base64 encoder.
var paddedBase64 = btoa(uint8Array);
// Calculate the unpadded length.
var inputLength = uint8Array.length;
var outputLength = 4 * Math.floor((inputLength + 2) / 3) + (inputLength + 2) % 3 - 2;
// Return the unpadded base64.
return paddedBase64.slice(0, outputLength);
}
/**
* Decode a base64 string to a typed array of uint8.
* This will decode unpadded base64, but will also accept base64 with padding.
* @param {string} base64 The unpadded base64 to decode.
* @return {Uint8Array} The decoded data.
*/
function decodeBase64(base64) {
// Pad the base64 up to the next multiple of 4.
var paddedBase64 = base64 + "===".slice(0, (4 - base64.length % 4) % 4);
// Decode the base64 as a misinterpreted Latin-1 string.
// window.atob returns a unicode string with codepoints in the range 0-255.
var latin1String = atob(paddedBase64);
// Encode the string as a Uint8Array as Latin-1.
var uint8Array = new Uint8Array(latin1String.length);
for (var i = 0; i < latin1String.length; i++) {
uint8Array[i] = latin1String.charCodeAt(i);
}
return uint8Array;
}
function autoDetectMatrixMessageType(fileType) {
switch(fileType ? fileType.mime.split('/')[0].toLowerCase() : undefined) {
case 'video': return 'm.video';
case 'image': return 'm.image';
case 'audio': return 'm.audio';
default: return 'm.file';
}
}
// ffprobe method for getting metadata from a file wrapped in a promise
function _ffprobe(filepath){
return new Promise((resolve,reject) => {
ffmpeg.ffprobe(filepath, function(err, metadata) {
if(err) {
return reject(new Error(err));
}
resolve(metadata);
});
});
}
}
+1 -1
View File
@@ -8,7 +8,7 @@
outputs:2,
defaults: {
name: { value: null },
server: { value: "", type: "matrix-server-config" },
server: { type: "matrix-server-config" },
},
label: function() {
return this.name || "WhoIs User";
+10 -3
View File
@@ -8,10 +8,12 @@ module.exports = function(RED) {
this.server = RED.nodes.getNode(n.server);
if(!this.server) {
node.error('Server must be configured on the node.');
node.error('Server must be configured on the node.', {});
return;
}
node.server.register(node);
this.encodeUri = function(pathTemplate, variables) {
for (const key in variables) {
if (!variables.hasOwnProperty(key)) {
@@ -41,12 +43,13 @@ module.exports = function(RED) {
}
if(!node.server.isConnected()) {
node.error("Matrix server connection is currently closed");
node.error("Matrix server connection is currently closed", msg);
node.send([null, msg]);
return;
}
if(!msg.userId) {
node.error("msg.userId must be set to get user whois data");
node.error("msg.userId must be set to get user whois data", msg);
return;
}
@@ -70,6 +73,10 @@ module.exports = function(RED) {
node.send([null, msg]);
});
});
node.on("close", function() {
node.server.deregister(node);
});
}
RED.nodes.registerType("matrix-whois-user", MatrixWhoIsUser);
}