- matrix-receive node updated so that msg.sender is msg.userId instead (for better node chaining).

- change all references from msg.roomId to msg.topic to conform to Node-RED standards.
- Added node for listing Synapse users server-wide (as long as the caller is an admin)
- Events for Room.timeline are now handled by the server-config node and node's that listen for it bind a listener to that instead of the matrix client.
- Added kick and ban nodes for kicking/banning from a room
- Added node for fetching synapse user list using synapse admin API
- Added node for fetching whois data from a user using Matrix admin API
- Added node for deactivating users using the Synapse admin API
- Can register users using the Synapse admin endpoint v1 (yay legacy)
- Can add users using the Synapse admin endpoint v2
- Add more info to the readme.
This commit is contained in:
Skylar Sadlier 2021-08-18 11:18:29 -06:00
parent cd99955115
commit b33595d5eb
31 changed files with 1874 additions and 109 deletions

View File

@ -1,5 +1,7 @@
# node-red-contrib-matrix-chat
Matrix chat server client for Node-RED
Matrix chat server client for [Node-RED](https://nodered.org/)
***Currently we are in beta. We ask that you open any issues you have on our repository to help us reach a stable well tested version. Things may change & break before our first release so check changelog before updating.***
### Features
@ -7,11 +9,18 @@ The following is supported from this package:
- Receive events from a room (messages, reactions, images, and files)
- Send Images/Files
- Send HTML/Plain Text Message
- Send HTML/Plain Text Notice
- Send HTML/Plain Text Message/Notice
- React to messages
- Register user's on closed registration Synapse servers using `registration_shared_secret` (Admin Only)
- List out users on a Synapse server (Admin Only)
- Get WhoIs info for a Synapse user (Admin Only)
- Add/Edit Synapse users using the v2 API (requires a pre-existing admin account)
- Get a user list from a room
- Kick user from room
- Ban user from room
Therefor you can easily build a bot or even a relay from another chat service.
Therefore, you can easily build a bot, chat relay, or administrate your Matrix server from within [Node-RED](https://nodered.org/).
### Installing
@ -20,6 +29,10 @@ You can either install from within Node-RED by searching for `node-red-contrib-m
npm install node-red-contrib-matrix-chat
```
### Examples
We don't have examples just yet but here is a picture of my flow I use for testing this module while creating it (gives you an idea of what is possible and how easy it is to chain things):
![img.png](example.png)
### Usage
Using this package is very straightforward. Examples coming soon!

BIN
example.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 226 KiB

520
package-lock.json generated
View File

@ -1,14 +1,16 @@
{
"name": "node-red-contrib-matrix-chat",
"version": "0.0.1",
"version": "0.0.3",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"version": "0.0.1",
"license": "ISC",
"version": "0.0.3",
"license": "SEE LICENSE FILE",
"dependencies": {
"matrix-js-sdk": "^12.2.0"
"got": "^11.8.2",
"matrix-js-sdk": "^12.2.0",
"utf8": "^3.0.0"
}
},
"node_modules/@babel/runtime": {
@ -22,6 +24,65 @@
"node": ">=6.9.0"
}
},
"node_modules/@sindresorhus/is": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.0.1.tgz",
"integrity": "sha512-Qm9hBEBu18wt1PO2flE7LPb30BHMQt1eQgbV76YntdNk73XZGpn3izvGTYxbGgzXKgbCjiia0uxTd3aTNQrY/g==",
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sindresorhus/is?sponsor=1"
}
},
"node_modules/@szmarczak/http-timer": {
"version": "4.0.6",
"resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz",
"integrity": "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==",
"dependencies": {
"defer-to-connect": "^2.0.0"
},
"engines": {
"node": ">=10"
}
},
"node_modules/@types/cacheable-request": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.2.tgz",
"integrity": "sha512-B3xVo+dlKM6nnKTcmm5ZtY/OL8bOAOd2Olee9M1zft65ox50OzjEHW91sDiU9j6cvW8Ejg1/Qkf4xd2kugApUA==",
"dependencies": {
"@types/http-cache-semantics": "*",
"@types/keyv": "*",
"@types/node": "*",
"@types/responselike": "*"
}
},
"node_modules/@types/http-cache-semantics": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.1.tgz",
"integrity": "sha512-SZs7ekbP8CN0txVG2xVRH6EgKmEm31BOxA07vkFaETzZz1xh+cbt8BcI0slpymvwhx5dlFnQG2rTlPVQn+iRPQ=="
},
"node_modules/@types/keyv": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.2.tgz",
"integrity": "sha512-/FvAK2p4jQOaJ6CGDHJTqZcUtbZe820qIeTg7o0Shg7drB4JHeL+V/dhSaly7NXx6u8eSee+r7coT+yuJEvDLg==",
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@types/node": {
"version": "16.6.1",
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.6.1.tgz",
"integrity": "sha512-Sr7BhXEAer9xyGuCN3Ek9eg9xPviCF2gfu9kTfuU2HkTVAMYSDeX40fvpmo72n5nansg3nsBjuQBrsS28r+NUw=="
},
"node_modules/@types/responselike": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.0.tgz",
"integrity": "sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==",
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@types/retry": {
"version": "0.12.1",
"resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.1.tgz",
@ -113,6 +174,31 @@
"base-x": "^3.0.2"
}
},
"node_modules/cacheable-lookup": {
"version": "5.0.4",
"resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz",
"integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==",
"engines": {
"node": ">=10.6.0"
}
},
"node_modules/cacheable-request": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.2.tgz",
"integrity": "sha512-pouW8/FmiPQbuGpkXQ9BAPv/Mo5xDGANgSNXzTzJ8DrKGuXOssM4wIQRjfanNRh3Yu5cfYPvcorqbhg2KIJtew==",
"dependencies": {
"clone-response": "^1.0.2",
"get-stream": "^5.1.0",
"http-cache-semantics": "^4.0.0",
"keyv": "^4.0.0",
"lowercase-keys": "^2.0.0",
"normalize-url": "^6.0.1",
"responselike": "^2.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/call-bind": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz",
@ -130,6 +216,14 @@
"resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz",
"integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw="
},
"node_modules/clone-response": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz",
"integrity": "sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws=",
"dependencies": {
"mimic-response": "^1.0.0"
}
},
"node_modules/combined-stream": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
@ -165,6 +259,39 @@
"node": ">=0.10"
}
},
"node_modules/decompress-response": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz",
"integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==",
"dependencies": {
"mimic-response": "^3.1.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/decompress-response/node_modules/mimic-response": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz",
"integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==",
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/defer-to-connect": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz",
"integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==",
"engines": {
"node": ">=10"
}
},
"node_modules/delayed-stream": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
@ -182,6 +309,14 @@
"safer-buffer": "^2.1.0"
}
},
"node_modules/end-of-stream": {
"version": "1.4.4",
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz",
"integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==",
"dependencies": {
"once": "^1.4.0"
}
},
"node_modules/extend": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
@ -244,6 +379,20 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/get-stream": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz",
"integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==",
"dependencies": {
"pump": "^3.0.0"
},
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/getpass": {
"version": "0.1.7",
"resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz",
@ -252,6 +401,30 @@
"assert-plus": "^1.0.0"
}
},
"node_modules/got": {
"version": "11.8.2",
"resolved": "https://registry.npmjs.org/got/-/got-11.8.2.tgz",
"integrity": "sha512-D0QywKgIe30ODs+fm8wMZiAcZjypcCodPNuMz5H9Mny7RJ+IjJ10BdmGW7OM7fHXP+O7r6ZwapQ/YQmMSvB0UQ==",
"dependencies": {
"@sindresorhus/is": "^4.0.0",
"@szmarczak/http-timer": "^4.0.5",
"@types/cacheable-request": "^6.0.1",
"@types/responselike": "^1.0.0",
"cacheable-lookup": "^5.0.3",
"cacheable-request": "^7.0.1",
"decompress-response": "^6.0.0",
"http2-wrapper": "^1.0.0-beta.5.2",
"lowercase-keys": "^2.0.0",
"p-cancelable": "^2.0.0",
"responselike": "^2.0.0"
},
"engines": {
"node": ">=10.19.0"
},
"funding": {
"url": "https://github.com/sindresorhus/got?sponsor=1"
}
},
"node_modules/har-schema": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz",
@ -295,6 +468,11 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/http-cache-semantics": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz",
"integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ=="
},
"node_modules/http-signature": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz",
@ -309,6 +487,18 @@
"npm": ">=1.3.7"
}
},
"node_modules/http2-wrapper": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz",
"integrity": "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==",
"dependencies": {
"quick-lru": "^5.1.1",
"resolve-alpn": "^1.0.0"
},
"engines": {
"node": ">=10.19.0"
}
},
"node_modules/is-typedarray": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
@ -324,6 +514,11 @@
"resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz",
"integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM="
},
"node_modules/json-buffer": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
"integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="
},
"node_modules/json-schema": {
"version": "0.2.3",
"resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz",
@ -353,6 +548,14 @@
"verror": "1.10.0"
}
},
"node_modules/keyv": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.0.3.tgz",
"integrity": "sha512-zdGa2TOpSZPq5mU6iowDARnMBZgtCqJ11dJROFi6tg6kTn4nuUdU09lFyLFSaHrWqpIJ+EBq4E8/Dc0Vx5vLdA==",
"dependencies": {
"json-buffer": "3.0.1"
}
},
"node_modules/loglevel": {
"version": "1.7.1",
"resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.7.1.tgz",
@ -365,6 +568,14 @@
"url": "https://tidelift.com/funding/github/npm/loglevel"
}
},
"node_modules/lowercase-keys": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz",
"integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==",
"engines": {
"node": ">=8"
}
},
"node_modules/matrix-js-sdk": {
"version": "12.2.0",
"resolved": "https://registry.npmjs.org/matrix-js-sdk/-/matrix-js-sdk-12.2.0.tgz",
@ -401,6 +612,25 @@
"node": ">= 0.6"
}
},
"node_modules/mimic-response": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz",
"integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==",
"engines": {
"node": ">=4"
}
},
"node_modules/normalize-url": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz",
"integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==",
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/oauth-sign": {
"version": "0.9.0",
"resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz",
@ -417,6 +647,22 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/once": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
"integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
"dependencies": {
"wrappy": "1"
}
},
"node_modules/p-cancelable": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz",
"integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==",
"engines": {
"node": ">=8"
}
},
"node_modules/p-retry": {
"version": "4.6.1",
"resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.1.tgz",
@ -439,6 +685,15 @@
"resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz",
"integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ=="
},
"node_modules/pump": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
"integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==",
"dependencies": {
"end-of-stream": "^1.1.0",
"once": "^1.3.1"
}
},
"node_modules/punycode": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
@ -461,6 +716,17 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/quick-lru": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz",
"integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==",
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/regenerator-runtime": {
"version": "0.13.9",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz",
@ -505,6 +771,19 @@
"node": ">=0.6"
}
},
"node_modules/resolve-alpn": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.0.tgz",
"integrity": "sha512-e4FNQs+9cINYMO5NMFc6kOUCdohjqFPSgMuwuZAOUWqrfWsen+Yjy5qZFkV5K7VO7tFSLKcUL97olkED7sCBHA=="
},
"node_modules/responselike": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.0.tgz",
"integrity": "sha512-xH48u3FTB9VsZw7R+vvgaKeLKzT6jOogbQhEe/jewwnZgzPcnyWui2Av6JpoYZF/91uueC+lqhWqeURw5/qhCw==",
"dependencies": {
"lowercase-keys": "^2.0.0"
}
},
"node_modules/retry": {
"version": "0.13.1",
"resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz",
@ -615,6 +894,11 @@
"punycode": "^2.1.0"
}
},
"node_modules/utf8": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/utf8/-/utf8-3.0.0.tgz",
"integrity": "sha512-E8VjFIQ/TyQgp+TZfS6l8yp/xWppSAHzidGiRrqe4bK4XP9pTRyKFgGJpO3SN7zdX4DeomTrwaseCHovfpFcqQ=="
},
"node_modules/uuid": {
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz",
@ -636,6 +920,11 @@
"core-util-is": "1.0.2",
"extsprintf": "^1.2.0"
}
},
"node_modules/wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
}
},
"dependencies": {
@ -647,6 +936,56 @@
"regenerator-runtime": "^0.13.4"
}
},
"@sindresorhus/is": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.0.1.tgz",
"integrity": "sha512-Qm9hBEBu18wt1PO2flE7LPb30BHMQt1eQgbV76YntdNk73XZGpn3izvGTYxbGgzXKgbCjiia0uxTd3aTNQrY/g=="
},
"@szmarczak/http-timer": {
"version": "4.0.6",
"resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz",
"integrity": "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==",
"requires": {
"defer-to-connect": "^2.0.0"
}
},
"@types/cacheable-request": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.2.tgz",
"integrity": "sha512-B3xVo+dlKM6nnKTcmm5ZtY/OL8bOAOd2Olee9M1zft65ox50OzjEHW91sDiU9j6cvW8Ejg1/Qkf4xd2kugApUA==",
"requires": {
"@types/http-cache-semantics": "*",
"@types/keyv": "*",
"@types/node": "*",
"@types/responselike": "*"
}
},
"@types/http-cache-semantics": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.1.tgz",
"integrity": "sha512-SZs7ekbP8CN0txVG2xVRH6EgKmEm31BOxA07vkFaETzZz1xh+cbt8BcI0slpymvwhx5dlFnQG2rTlPVQn+iRPQ=="
},
"@types/keyv": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.2.tgz",
"integrity": "sha512-/FvAK2p4jQOaJ6CGDHJTqZcUtbZe820qIeTg7o0Shg7drB4JHeL+V/dhSaly7NXx6u8eSee+r7coT+yuJEvDLg==",
"requires": {
"@types/node": "*"
}
},
"@types/node": {
"version": "16.6.1",
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.6.1.tgz",
"integrity": "sha512-Sr7BhXEAer9xyGuCN3Ek9eg9xPviCF2gfu9kTfuU2HkTVAMYSDeX40fvpmo72n5nansg3nsBjuQBrsS28r+NUw=="
},
"@types/responselike": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.0.tgz",
"integrity": "sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==",
"requires": {
"@types/node": "*"
}
},
"@types/retry": {
"version": "0.12.1",
"resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.1.tgz",
@ -725,6 +1064,25 @@
"base-x": "^3.0.2"
}
},
"cacheable-lookup": {
"version": "5.0.4",
"resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz",
"integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA=="
},
"cacheable-request": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.2.tgz",
"integrity": "sha512-pouW8/FmiPQbuGpkXQ9BAPv/Mo5xDGANgSNXzTzJ8DrKGuXOssM4wIQRjfanNRh3Yu5cfYPvcorqbhg2KIJtew==",
"requires": {
"clone-response": "^1.0.2",
"get-stream": "^5.1.0",
"http-cache-semantics": "^4.0.0",
"keyv": "^4.0.0",
"lowercase-keys": "^2.0.0",
"normalize-url": "^6.0.1",
"responselike": "^2.0.0"
}
},
"call-bind": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz",
@ -739,6 +1097,14 @@
"resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz",
"integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw="
},
"clone-response": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz",
"integrity": "sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws=",
"requires": {
"mimic-response": "^1.0.0"
}
},
"combined-stream": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
@ -765,6 +1131,26 @@
"assert-plus": "^1.0.0"
}
},
"decompress-response": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz",
"integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==",
"requires": {
"mimic-response": "^3.1.0"
},
"dependencies": {
"mimic-response": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz",
"integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ=="
}
}
},
"defer-to-connect": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz",
"integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg=="
},
"delayed-stream": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
@ -779,6 +1165,14 @@
"safer-buffer": "^2.1.0"
}
},
"end-of-stream": {
"version": "1.4.4",
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz",
"integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==",
"requires": {
"once": "^1.4.0"
}
},
"extend": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
@ -829,6 +1223,14 @@
"has-symbols": "^1.0.1"
}
},
"get-stream": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz",
"integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==",
"requires": {
"pump": "^3.0.0"
}
},
"getpass": {
"version": "0.1.7",
"resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz",
@ -837,6 +1239,24 @@
"assert-plus": "^1.0.0"
}
},
"got": {
"version": "11.8.2",
"resolved": "https://registry.npmjs.org/got/-/got-11.8.2.tgz",
"integrity": "sha512-D0QywKgIe30ODs+fm8wMZiAcZjypcCodPNuMz5H9Mny7RJ+IjJ10BdmGW7OM7fHXP+O7r6ZwapQ/YQmMSvB0UQ==",
"requires": {
"@sindresorhus/is": "^4.0.0",
"@szmarczak/http-timer": "^4.0.5",
"@types/cacheable-request": "^6.0.1",
"@types/responselike": "^1.0.0",
"cacheable-lookup": "^5.0.3",
"cacheable-request": "^7.0.1",
"decompress-response": "^6.0.0",
"http2-wrapper": "^1.0.0-beta.5.2",
"lowercase-keys": "^2.0.0",
"p-cancelable": "^2.0.0",
"responselike": "^2.0.0"
}
},
"har-schema": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz",
@ -864,6 +1284,11 @@
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz",
"integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw=="
},
"http-cache-semantics": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz",
"integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ=="
},
"http-signature": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz",
@ -874,6 +1299,15 @@
"sshpk": "^1.7.0"
}
},
"http2-wrapper": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz",
"integrity": "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==",
"requires": {
"quick-lru": "^5.1.1",
"resolve-alpn": "^1.0.0"
}
},
"is-typedarray": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
@ -889,6 +1323,11 @@
"resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz",
"integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM="
},
"json-buffer": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
"integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="
},
"json-schema": {
"version": "0.2.3",
"resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz",
@ -915,11 +1354,24 @@
"verror": "1.10.0"
}
},
"keyv": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.0.3.tgz",
"integrity": "sha512-zdGa2TOpSZPq5mU6iowDARnMBZgtCqJ11dJROFi6tg6kTn4nuUdU09lFyLFSaHrWqpIJ+EBq4E8/Dc0Vx5vLdA==",
"requires": {
"json-buffer": "3.0.1"
}
},
"loglevel": {
"version": "1.7.1",
"resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.7.1.tgz",
"integrity": "sha512-Hesni4s5UkWkwCGJMQGAh71PaLUmKFM60dHvq0zi/vDhhrzuk+4GgNbTXJ12YYQJn6ZKBDNIjYcuQGKudvqrIw=="
},
"lowercase-keys": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz",
"integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA=="
},
"matrix-js-sdk": {
"version": "12.2.0",
"resolved": "https://registry.npmjs.org/matrix-js-sdk/-/matrix-js-sdk-12.2.0.tgz",
@ -950,6 +1402,16 @@
"mime-db": "1.49.0"
}
},
"mimic-response": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz",
"integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ=="
},
"normalize-url": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz",
"integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A=="
},
"oauth-sign": {
"version": "0.9.0",
"resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz",
@ -960,6 +1422,19 @@
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.11.0.tgz",
"integrity": "sha512-jp7ikS6Sd3GxQfZJPyH3cjcbJF6GZPClgdV+EFygjFLQ5FmW/dRUnTd9PQ9k0JhoNDabWFbpF1yCdSWCC6gexg=="
},
"once": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
"integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
"requires": {
"wrappy": "1"
}
},
"p-cancelable": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz",
"integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg=="
},
"p-retry": {
"version": "4.6.1",
"resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.1.tgz",
@ -979,6 +1454,15 @@
"resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz",
"integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ=="
},
"pump": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
"integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==",
"requires": {
"end-of-stream": "^1.1.0",
"once": "^1.3.1"
}
},
"punycode": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
@ -992,6 +1476,11 @@
"side-channel": "^1.0.4"
}
},
"quick-lru": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz",
"integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA=="
},
"regenerator-runtime": {
"version": "0.13.9",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz",
@ -1031,6 +1520,19 @@
}
}
},
"resolve-alpn": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.0.tgz",
"integrity": "sha512-e4FNQs+9cINYMO5NMFc6kOUCdohjqFPSgMuwuZAOUWqrfWsen+Yjy5qZFkV5K7VO7tFSLKcUL97olkED7sCBHA=="
},
"responselike": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.0.tgz",
"integrity": "sha512-xH48u3FTB9VsZw7R+vvgaKeLKzT6jOogbQhEe/jewwnZgzPcnyWui2Av6JpoYZF/91uueC+lqhWqeURw5/qhCw==",
"requires": {
"lowercase-keys": "^2.0.0"
}
},
"retry": {
"version": "0.13.1",
"resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz",
@ -1107,6 +1609,11 @@
"punycode": "^2.1.0"
}
},
"utf8": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/utf8/-/utf8-3.0.0.tgz",
"integrity": "sha512-E8VjFIQ/TyQgp+TZfS6l8yp/xWppSAHzidGiRrqe4bK4XP9pTRyKFgGJpO3SN7zdX4DeomTrwaseCHovfpFcqQ=="
},
"uuid": {
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz",
@ -1121,6 +1628,11 @@
"core-util-is": "1.0.2",
"extsprintf": "^1.2.0"
}
},
"wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
}
}
}

View File

@ -1,9 +1,11 @@
{
"name": "node-red-contrib-matrix-chat",
"version": "0.0.3",
"version": "0.0.5",
"description": "Matrix chat server client for Node-RED",
"dependencies": {
"matrix-js-sdk": "^12.2.0"
"got": "^11.8.2",
"matrix-js-sdk": "^12.2.0",
"utf8": "^3.0.0"
},
"node-red": {
"nodes": {
@ -12,7 +14,15 @@
"matrix-send-message": "src/matrix-send-message.js",
"matrix-send-file": "src/matrix-send-file.js",
"matrix-send-image": "src/matrix-send-image.js",
"matrix-react": "src/matrix-react.js"
"matrix-react": "src/matrix-react.js",
"matrix-room-kick": "src/matrix-room-kick.js",
"matrix-room-ban": "src/matrix-room-ban.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-whois-user": "src/matrix-whois-user.js",
"matrix-room-users": "src/matrix-room-users.js"
}
},
"keywords": [

View File

@ -70,7 +70,7 @@
</dt>
<dd> Usually an emoji but can also be text. </dd>
<dt>msg.roomId
<dt>msg.topic
<span class="property-type">String | Null</span>
</dt>
<dd> Room ID to send image to. Optional if configured on the node. If configured on the node this will be ignored.</dd>

View File

@ -34,9 +34,9 @@ module.exports = function(RED) {
node.send([null, msg]);
}
msg.roomId = node.roomId || msg.roomId;
if(!msg.roomId) {
node.error("Room must be specified in msg.roomId or in configuration");
msg.topic = node.roomId || msg.topic;
if(!msg.topic) {
node.error("Room must be specified in msg.topic or in configuration");
return;
}
@ -52,7 +52,7 @@ module.exports = function(RED) {
}
node.server.matrixClient.sendCompleteEvent(
msg.roomId,
msg.topic,
{
type: 'm.reaction',
content: {

View File

@ -82,7 +82,7 @@
</script>
<script type="text/html" data-help-name="matrix-receive">
<p>Receive events from matrix.</p>
<p>Receive events from Matrix.</p>
<h3>Outputs</h3>
<ol class="node-ports">
@ -100,12 +100,12 @@
</dl>
<dl class="message-properties">
<dt>msg.sender <span class="property-type">string</span></dt>
<dd>the sender of the message. Example: @john:matrix.org</dd>
<dt>msg.userId <span class="property-type">string</span></dt>
<dd>the User ID of the message sender. Example: @john:matrix.org</dd>
</dl>
<dl class="message-properties">
<dt>msg.roomId <span class="property-type">string</span></dt>
<dt>msg.topic <span class="property-type">string</span></dt>
<dd>the ID of the room. Example: !OGEhHVWSdvArJzumhm:matrix.org</dd>
</dl>

View File

@ -26,79 +26,78 @@ module.exports = function(RED) {
node.server.on("connected", function() {
node.status({ fill: "green", shape: "ring", text: "connected" });
});
node.server.matrixClient.on("Room.timeline", function(event, room, toStartOfTimeline, data) {
if (toStartOfTimeline) {
return; // ignore paginated results
}
if (
event.getType() !== "m.room.message"
&& event.getType() !== "m.reaction"
) {
return; // only keep messages
}
if (!event.getSender() || event.getSender() === node.server.userId) {
return; // ignore our own messages
}
if (!event.getUnsigned() || event.getUnsigned().age > 1000) {
return; // ignore old messages
}
node.server.on("Room.timeline", function(event, room, toStartOfTimeline, data) {
if (toStartOfTimeline) {
return; // ignore paginated results
}
if (
event.getType() !== "m.room.message"
&& event.getType() !== "m.reaction"
) {
return; // only keep messages
}
if (!event.getSender() || event.getSender() === node.server.userId) {
return; // ignore our own messages
}
if (!event.getUnsigned() || event.getUnsigned().age > 1000) {
return; // ignore old messages
}
// if node has a room ID set we only listen on that room
if(node.roomIds.length && node.roomIds.indexOf(room.roomId) === -1) {
console.log("SKIASDJIOJSADIJASD", node.roomIds);
return;
}
// if node has a room ID set we only listen on that room
if(node.roomIds.length && node.roomIds.indexOf(room.roomId) === -1) {
return;
}
let msg = {
content: event.getContent(),
type : (event.getContent().msgtype || event.getType()) || null,
payload : event.getContent().body || null,
sender : event.getSender(),
roomId : room.roomId,
eventId : event.getId(),
event : event,
};
node.log("Received timeline event [" + ((event.getContent().msgtype || event.getType()) || null) + "]: (" + room.name + ") " + event.getSender() + " :: " + event.getContent().body);
node.log("Received chat message [" + msg.type + "]: (" + room.name + ") " + event.getSender() + " :: " + msg.content.body);
let msg = {
content: event.getContent(),
type : (event.getContent().msgtype || event.getType()) || null,
payload : event.getContent().body || null,
userId : event.getSender(),
topic : room.roomId,
eventId : event.getId(),
event : event,
};
let knownMessageType = true;
switch(msg.type) {
case 'm.text':
if(node.ignoreText) return;
break;
let knownMessageType = true;
switch(msg.type) {
case 'm.text':
if(node.ignoreText) return;
break;
case 'm.reaction':
if(node.ignoreReactions) return;
msg.info = msg.content["m.relates_to"].info;
msg.referenceEventId = msg.content["m.relates_to"].event_id;
msg.payload = msg.content["m.relates_to"].key;
break;
case 'm.reaction':
if(node.ignoreReactions) return;
msg.info = msg.content["m.relates_to"].info;
msg.referenceEventId = msg.content["m.relates_to"].event_id;
msg.payload = msg.content["m.relates_to"].key;
break;
case 'm.file':
if(node.ignoreFiles) return;
msg.url = node.server.matrixClient.mxcUrlToHttp(msg.content.url);
msg.mxc_url = msg.content.url;
break;
case 'm.file':
if(node.ignoreFiles) return;
msg.url = node.server.matrixClient.mxcUrlToHttp(msg.content.url);
msg.mxc_url = msg.content.url;
break;
case 'm.image':
if(node.ignoreImages) return;
msg.url = node.server.matrixClient.mxcUrlToHttp(msg.content.url);
msg.mxc_url = msg.content.url;
msg.thumbnail_url = node.server.matrixClient.mxcUrlToHttp(msg.content.info.thumbnail_url);
msg.thumbnail_mxc_url = msg.content.info.thumbnail_url;
break;
case 'm.image':
if(node.ignoreImages) return;
msg.url = node.server.matrixClient.mxcUrlToHttp(msg.content.url);
msg.mxc_url = msg.content.url;
msg.thumbnail_url = node.server.matrixClient.mxcUrlToHttp(msg.content.info.thumbnail_url);
msg.thumbnail_mxc_url = msg.content.info.thumbnail_url;
break;
default:
knownMessageType = false;
}
default:
knownMessageType = false;
}
if(knownMessageType) {
node.send(msg);
} else {
node.warn("Uknown message type: " + msg.type);
}
});
if(knownMessageType) {
node.send(msg);
} else {
node.warn("Uknown message type: " + msg.type);
}
});
}
RED.nodes.registerType("matrix-receive", MatrixReceiveMessage);

75
src/matrix-room-ban.html Normal file
View File

@ -0,0 +1,75 @@
<script type="text/javascript">
RED.nodes.registerType('matrix-room-ban',{
category: 'matrix',
color: '#00b7ca',
icon: "matrix.png",
outputLabels: ["success", "error"],
inputs:1,
outputs:2,
defaults: {
name: { value: null },
server: { value: "", type: "matrix-server-config" },
roomId: { value: null }
},
label: function() {
return this.name || "Room Ban";
},
paletteLabel: 'Room Ban'
});
</script>
<script type="text/html" data-template-name="matrix-room-ban">
<div class="form-row">
<label for="node-input-name"><i class="icon-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
</div>
<div class="form-row">
<label for="node-input-server"><i class="fa fa-user"></i> Matrix Server Config</label>
<input type="text" id="node-input-server">
</div>
<div class="form-row">
<label for="node-input-roomId"><i class="fa fa-user"></i> Room ID</label>
<input type="text" id="node-input-roomId" placeholder="msg.topic">
</div>
<div class="form-tips">
Room ID must either be defined here or passed in via <code>msg.topic</code>. The config takes precedence over the input.
</div>
</script>
<script type="text/html" data-help-name="matrix-room-ban">
<h3>Details</h3>
<p>This node will ban a user from a room as long as the bot has permission to do so.</p>
<h3>Inputs</h3>
<dl class="message-properties">
<dt class="optional">msg.userId
<span class="property-type">String</span>
</dt>
<dd> The ID of the user to ban.</dd>
<dt class="optional">msg.topic
<span class="property-type">String | Null</span>
</dt>
<dd> The room to ban the user from. Required if not defined on the config. Config takes precedence.</dd>
<dt class="optional">msg.reason
<span class="property-type">String</span>
</dt>
<dd> Reason for banning the user.</dd>
</dl>
<h3>Outputs</h3>
<ol class="node-ports">
<li>Success
<dl class="message-properties">
<dd>original msg object preserved. Node doesn't set anything.</dd>
</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>

62
src/matrix-room-ban.js Normal file
View File

@ -0,0 +1,62 @@
module.exports = function(RED) {
function MatrixBan(n) {
RED.nodes.createNode(this, n);
var node = this;
this.name = n.name;
this.server = RED.nodes.getNode(n.server);
this.roomId = n.roomId;
if (!node.server) {
node.warn("No configuration node");
return;
}
node.status({ fill: "red", shape: "ring", text: "disconnected" });
node.server.on("disconnected", function(){
node.status({ fill: "red", shape: "ring", text: "disconnected" });
});
node.server.on("connected", function() {
node.status({ fill: "green", shape: "ring", text: "connected" });
});
node.on("input", function (msg) {
if (! node.server || ! node.server.matrixClient) {
node.error("No matrix server selected");
return;
}
if(!node.server.isConnected()) {
node.error("Matrix server connection is currently closed");
node.send([null, msg]);
}
msg.topic = node.roomId || msg.topic;
if(!msg.topic) {
node.error("Room must be specified in msg.topic or in configuration");
return;
}
if(!msg.userId) {
node.error("msg.userId was not set.");
return;
}
node.server.matrixClient.ban(msg.topic, msg.userId, msg.reason || undefined)
.then(function(e) {
node.log("Successfully banned " + msg.userId + " from " + msg.topic);
msg.eventId = e.event_id;
node.send([msg, null]);
})
.catch(function(e){
node.error("Error trying to ban " + msg.userId + " from " + msg.topic);
msg.error = e;
node.send([null, msg]);
});
});
}
RED.nodes.registerType("matrix-room-ban", MatrixBan);
}

75
src/matrix-room-kick.html Normal file
View File

@ -0,0 +1,75 @@
<script type="text/javascript">
RED.nodes.registerType('matrix-room-kick',{
category: 'matrix',
color: '#00b7ca',
icon: "matrix.png",
outputLabels: ["success", "error"],
inputs:1,
outputs:2,
defaults: {
name: { value: null },
server: { value: "", type: "matrix-server-config" },
roomId: { value: null }
},
label: function() {
return this.name || "Room Kick";
},
paletteLabel: 'Room Kick'
});
</script>
<script type="text/html" data-template-name="matrix-room-kick">
<div class="form-row">
<label for="node-input-name"><i class="icon-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
</div>
<div class="form-row">
<label for="node-input-server"><i class="fa fa-user"></i> Matrix Server Config</label>
<input type="text" id="node-input-server">
</div>
<div class="form-row">
<label for="node-input-roomId"><i class="fa fa-user"></i> Room ID</label>
<input type="text" id="node-input-roomId" placeholder="msg.topic">
</div>
<div class="form-tips">
Room ID must either be defined here or passed in via <code>msg.topic</code>. The config takes precedence over the input.
</div>
</script>
<script type="text/html" data-help-name="matrix-room-kick">
<h3>Details</h3>
<p>This node will kick a user from a room as long as the bot has permission to do so.</p>
<h3>Inputs</h3>
<dl class="message-properties">
<dt>msg.userId
<span class="property-type">String</span>
</dt>
<dd> The ID of the user to kick.</dd>
<dt class="optional">msg.topic
<span class="property-type">String | Null</span>
</dt>
<dd> The room to kick the user from. Required if not defined on the config. Config takes precedence.</dd>
<dt class="optional">msg.reason
<span class="property-type">String</span>
</dt>
<dd> Reason for kicking the user.</dd>
</dl>
<h3>Outputs</h3>
<ol class="node-ports">
<li>Success
<dl class="message-properties">
<dd>original msg object preserved. Node doesn't set anything.</dd>
</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>

62
src/matrix-room-kick.js Normal file
View File

@ -0,0 +1,62 @@
module.exports = function(RED) {
function MatrixKick(n) {
RED.nodes.createNode(this, n);
var node = this;
this.name = n.name;
this.server = RED.nodes.getNode(n.server);
this.roomId = n.roomId;
if (!node.server) {
node.warn("No configuration node");
return;
}
node.status({ fill: "red", shape: "ring", text: "disconnected" });
node.server.on("disconnected", function(){
node.status({ fill: "red", shape: "ring", text: "disconnected" });
});
node.server.on("connected", function() {
node.status({ fill: "green", shape: "ring", text: "connected" });
});
node.on("input", function (msg) {
if (! node.server || ! node.server.matrixClient) {
node.error("No matrix server selected");
return;
}
if(!node.server.isConnected()) {
node.error("Matrix server connection is currently closed");
node.send([null, msg]);
}
msg.topic = node.roomId || msg.topic;
if(!msg.topic) {
node.error("Room must be specified in msg.topic or in configuration");
return;
}
if(!msg.userId) {
node.error("msg.userId was not set.");
return;
}
node.server.matrixClient.kick(msg.topic, msg.userId, msg.reason || undefined)
.then(function(e) {
node.log("Successfully kicked " + msg.userId + " from " + msg.topic);
msg.eventId = e.event_id;
node.send([msg, null]);
})
.catch(function(e){
node.error("Error trying to kick " + msg.userId + " from " + msg.topic);
msg.error = e;
node.send([null, msg]);
});
});
}
RED.nodes.registerType("matrix-room-kick", MatrixKick);
}

View File

@ -0,0 +1,80 @@
<script type="text/javascript">
RED.nodes.registerType('matrix-room-users',{
category: 'matrix',
color: '#00b7ca',
icon: "matrix.png",
outputLabels: ["success", "error"],
inputs:1,
outputs:2,
defaults: {
name: { value: null },
server: { value: "", type: "matrix-server-config" },
roomId: { value: "" }
},
label: function() {
return this.name || "Room User List";
},
paletteLabel: 'Room User List'
});
</script>
<script type="text/html" data-template-name="matrix-room-users">
<div class="form-row">
<label for="node-input-name"><i class="icon-tag"></i> Name</label>
<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-server"><i class="fa fa-user"></i> Room Id</label>
<input type="text" id="node-input-roomId" placeholder="msg.topic">
</div>
<div class="form-tips">
This only works on Synapse servers. The user also must be an administrator.
</div>
</script>
<script type="text/html" data-help-name="matrix-room-users">
<h3>Details</h3>
<p>List out joined users from a Matrix room.</p>
<h3>Inputs</h3>
<dl class="message-properties">
<dt class="optional">msg.topic
<span class="property-type">Integer</span>
</dt>
<dd> Room ID to get member list from. Required if not configured on the node.</dd>
</dl>
<h3>Outputs</h3>
<ol class="node-ports">
<li>Success
<dl class="message-properties">
<dt>msg.payload <span class="property-type">object</span></dt>
<dd>the response object from the server.</dd>
<dt>msg.payload.users <span class="property-type">array</span></dt>
<dd>list of users from the Matrix server. <a href="https://matrix-org.github.io/synapse/develop/admin_api/user_admin_api.html#list-accounts" target="_blank">Click here</a> for details on what this contains (or do a test and dump the output). We would put the details here in the doc but Synapse is constantly changing so it's best to reference the official Synapse docs.</dd>
<dt>msg.next_token <span class="property-type">string</span></dt>
<dd>string representing a positive integer - Indication for pagination. See above (input msg.from)</dd>
<dt>msg.total <span class="property-type">integer</span></dt>
<dd>Total number of media.</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://matrix-org.github.io/synapse/develop/admin_api/user_admin_api.html#list-accounts">Matrix Docs</a> - List Accounts API method information</li>
</ul>
</script>

70
src/matrix-room-users.js Normal file
View File

@ -0,0 +1,70 @@
module.exports = function(RED) {
function MatrixRoomUsers(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.contentType = n.contentType;
if (!node.server) {
node.warn("No configuration node");
return;
}
node.status({ fill: "red", shape: "ring", text: "disconnected" });
node.server.on("disconnected", function(){
node.status({ fill: "red", shape: "ring", text: "disconnected" });
});
node.server.on("connected", function() {
node.status({ fill: "green", shape: "ring", text: "connected" });
});
node.on("input", function (msg) {
if (! node.server || ! node.server.matrixClient) {
node.warn("No matrix server selected");
return;
}
if(!node.server.isConnected()) {
node.error("Matrix server connection is currently closed");
node.send([null, msg]);
}
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.");
return;
}
let queryParams = {
'from': msg.from || 0,
'limit': msg.limit || 100
};
if(msg.guests) {
queryParams['guests'] = msg.guests;
}
if(msg.order_by) {
queryParams['order_by'] = msg.order_by;
}
node.server.matrixClient
.getJoinedRoomMembers(roomId)
.then(function(e){
msg.payload = e;
node.send([msg, null]);
}).catch(function(e){
node.warn("Error fetching room user list " + e);
msg.error = e;
node.send([null, msg]);
});
});
}
RED.nodes.registerType("matrix-room-users", MatrixRoomUsers);
}

View File

@ -52,7 +52,7 @@
</dt>
<dd> the contents of the file to upload. </dd>
<dt>msg.roomId
<dt>msg.topic
<span class="property-type">String | Null</span>
</dt>
<dd> Room ID to send file to. Optional if configured on the node. Overrides node configuration if set.</dd>

View File

@ -35,14 +35,14 @@ module.exports = function(RED) {
node.send([null, msg]);
}
msg.roomId = node.roomId || msg.roomId;
if(!msg.roomId) {
node.warn("Room must be specified in msg.roomId or in configuration");
msg.topic = node.roomId || msg.topic;
if(!msg.topic) {
node.warn("Room must be specified in msg.topic or in configuration");
return;
}
if(msg.content) {
node.server.matrixClient.sendMessage(msg.roomId, msg.content)
if(msg.content && msg.type === 'm.file') {
node.server.matrixClient.sendMessage(msg.topic, msg.content)
.then(function(e) {
node.log("File message sent: " + e);
msg.eventId = e.event_id;
@ -82,7 +82,7 @@ module.exports = function(RED) {
body: (msg.body || msg.filename) || "",
};
node.server.matrixClient
.sendMessage(msg.roomId, content)
.sendMessage(msg.topic, content)
.then(function(imgResp) {
node.log("File message sent: " + imgResp);
msg.eventId = e.eventId;

View File

@ -52,7 +52,7 @@
</dt>
<dd> the contents of the image to upload. </dd>
<dt>msg.roomId
<dt>msg.topic
<span class="property-type">String | Null</span>
</dt>
<dd> Room ID to send image to. Optional if configured on the node. Overrides node configuration if set.</dd>

View File

@ -35,14 +35,14 @@ module.exports = function(RED) {
node.send([null, msg]);
}
msg.roomId = node.roomId || msg.roomId;
if(!msg.roomId) {
node.warn("Room must be specified in msg.roomId or in configuration");
msg.topic = node.roomId || msg.topic;
if(!msg.topic) {
node.warn("Room must be specified in msg.topic or in configuration");
return;
}
if(msg.content) {
node.server.matrixClient.sendMessage(msg.roomId, msg.content)
if(msg.content && msg.type === 'm.image') {
node.server.matrixClient.sendMessage(msg.topic, msg.content)
.then(function(e) {
node.log("Image message sent: " + e);
msg.eventId = e.event_id;
@ -77,7 +77,7 @@ module.exports = function(RED) {
})
.then(function(file){
node.server.matrixClient
.sendImageMessage(msg.roomId, file.content_uri, {}, (msg.body || msg.filename) || "")
.sendImageMessage(msg.topic, file.content_uri, {}, (msg.body || msg.filename) || "")
.then(function(e) {
node.log("Image message sent: " + e);
msg.eventId = e.event_id;

View File

@ -77,7 +77,7 @@
</dt>
<dd> the formatted HTML message (uses msg.payload if not defined). This only affects HTML messages.</dd>
<dt>msg.roomId
<dt>msg.topic
<span class="property-type">String | Null</span>
</dt>
<dd> Room ID to send image to. Optional if configured on the node. Overrides node configuration if set.</dd>

View File

@ -91,9 +91,9 @@ module.exports = function(RED) {
node.send([null, msg]);
}
msg.roomId = node.roomId || msg.roomId;
if(!msg.roomId) {
node.warn("Room must be specified in msg.roomId or in configuration");
msg.topic = node.roomId || msg.topic;
if(!msg.topic) {
node.warn("Room must be specified in msg.topic or in configuration");
return;
}
@ -112,7 +112,7 @@ module.exports = function(RED) {
content.formatted_body = msg.formatted_payload || msg.payload;
}
node.server.matrixClient.sendMessage(msg.roomId, content)
node.server.matrixClient.sendMessage(msg.topic, content)
.then(function(e) {
node.log("Message sent: " + msg.payload);
msg.eventId = e.eventId;

View File

@ -14,6 +14,8 @@ module.exports = function(RED) {
this.credentials = {};
}
node.setMaxListeners(1000);
this.connected = false;
this.name = n.name;
this.userId = this.credentials.userId;
@ -27,8 +29,6 @@ module.exports = function(RED) {
} else if(!this.userId) {
node.log("Matrix connection failed: missing user ID.");
} else {
node.log("Initializing Matrix Server Config node");
node.matrixClient = sdk.createClient({
baseUrl: this.url,
accessToken: this.credentials.accessToken,
@ -49,6 +49,7 @@ module.exports = function(RED) {
if (node.connected !== connected) {
node.connected = connected;
if (connected) {
node.log("Matrix server connection ready.");
node.emit("connected");
} else {
node.emit("disconnected");
@ -60,6 +61,10 @@ module.exports = function(RED) {
return node.connected;
};
node.matrixClient.on("Room.timeline", function(event, room, toStartOfTimeline, data) {
node.emit("Room.timeline", event, room, toStartOfTimeline, data);
});
// handle auto-joining rooms
node.matrixClient.on("RoomMember.membership", function(event, member) {
if (member.membership === "invite" && member.userId === node.userId) {
@ -82,6 +87,8 @@ module.exports = function(RED) {
node.setConnected(false);
break;
case "PREPARED":
case "RECONNECTING":
case "STOPPED":
node.setConnected(false);
break;
@ -90,11 +97,11 @@ module.exports = function(RED) {
node.setConnected(true);
break;
case "PREPARED":
// the client instance is ready to be queried.
node.log("Matrix server connection ready.");
node.setConnected(true);
break;
// case "PREPARED":
// // the client instance is ready to be queried.
// node.log("Matrix server connection ready.");
// node.setConnected(true);
// break;
}
});

View File

@ -0,0 +1,70 @@
<script type="text/javascript">
RED.nodes.registerType('matrix-synapse-create-edit-user', {
category: 'matrix',
color: '#00b7ca',
icon: "matrix.png",
outputLabels: ["success", "error"],
inputs:1,
outputs:2,
defaults: {
name: { value: null },
server: { value: "", type: "matrix-server-config" },
},
label: function() {
return this.name || "Synapse Add/Edit User";
},
paletteLabel: 'Synapse Add/Edit User'
});
</script>
<script type="text/html" data-template-name="matrix-synapse-create-edit-user">
<div class="form-row">
<label for="node-input-name"><i class="icon-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
</div>
<div class="form-row">
<label for="node-input-server"><i class="fa fa-user"></i> Matrix Server Config</label>
<input type="text" id="node-input-server">
</div>
<div class="form-tips" style="margin-bottom: 12px;">
User must be an admin to use this endpoint. Only works with Synapse servers. It is recommended to check if the user exists before creating otherwise it will edit an existing user.
</div>
</script>
<script type="text/html" data-help-name="matrix-synapse-create-edit-user">
<h3>Details</h3>
<p>Add a new user to a Synapse Matrix server. This is only supported on Synapse servers and the API client must be an admin.</p>
<h3>Inputs</h3>
<dl class="message-properties">
<dt>msg.userId
<span class="property-type">string</span>
</dt>
<dd> ID of user to create/edit (ex: @bob:example.com)</dd>
<dt>msg.payload
<span class="property-type">Object</span>
</dt>
<dd> Details of the new user to create. <a href="https://matrix-org.github.io/synapse/develop/admin_api/user_admin_api.html#create-or-modify-account" target="_blank">Click here</a> to see what valid data you can pass via this input.</dd>
</dl>
<h3>Outputs</h3>
<ol class="node-ports">
<li>Success
<dl class="message-properties">
<dd>original msg object preserved.</dd>
<dt>msg.eventId <span class="property-type">string</span></dt>
<dd>the eventId from the posted message.</dd>
</dl>
</li>
<li>Error
<dl class="message-properties">
<dt>msg.error <span class="property-type">string</span></dt>
<dd>the error that occurred.</dd>
</dl>
</li>
</ol>
</script>

View File

@ -0,0 +1,65 @@
module.exports = function(RED) {
function MatrixSynapseCreateEditUser(n) {
RED.nodes.createNode(this, n);
let node = this;
this.name = n.name;
this.server = RED.nodes.getNode(n.server);
if(!this.server) {
node.error('Server must be configured on the node.');
return;
}
this.encodeUri = function(pathTemplate, variables) {
for (const key in variables) {
if (!variables.hasOwnProperty(key)) {
continue;
}
pathTemplate = pathTemplate.replace(
key, encodeURIComponent(variables[key]),
);
}
return pathTemplate;
};
node.on("input", function (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");
node.send([null, msg]);
}
if(!msg.userId) {
node.error("msg.userId must be set to edit/create a user (ex: @user:server.com)");
return;
}
node.server.matrixClient.http
.authedRequest(
undefined,
'PUT',
node.encodeUri(
"/_synapse/admin/v2/users/$userId",
{ $userId: msg.userId.toLowerCase() },
),
undefined,
msg.payload,
{ prefix: '' }
).then(function(e){
msg.payload = e;
node.send([msg, null]);
}).catch(function(e){
node.warn("Error creating/editing user " + e);
msg.error = e;
node.send([null, msg]);
});
});
}
RED.nodes.registerType("matrix-synapse-create-edit-user", MatrixSynapseCreateEditUser);
}

View File

@ -0,0 +1,88 @@
<script type="text/javascript">
RED.nodes.registerType('matrix-synapse-deactivate-user', {
category: 'matrix',
color: '#00b7ca',
icon: "matrix.png",
outputLabels: ["success", "error"],
inputs:1,
outputs:2,
defaults: {
name: { value: null },
server: { value: "", type: "matrix-server-config" },
},
label: function() {
return this.name || "Synapse Deactivate User";
},
paletteLabel: 'Synapse Deactivate User'
});
</script>
<script type="text/html" data-template-name="matrix-synapse-deactivate-user">
<div class="form-row">
<label for="node-input-name"><i class="icon-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
</div>
<div class="form-row">
<label for="node-input-server"><i class="fa fa-user"></i> Matrix Server Config</label>
<input type="text" id="node-input-server">
</div>
<div class="form-tips" style="margin-bottom: 12px;">
User must be an admin to use this endpoint. Only works with Synapse servers. This is permanent so make sure you actually want to do it.
</div>
</script>
<script type="text/html" data-help-name="matrix-synapse-deactivate-user">
<h3>Details</h3>
<p>
Permanently deactivate a Synapse Matrix user. It removes active access tokens, resets the password, and deletes third-party IDs (to prevent the user requesting a password reset). If you don't want this to be permanent edit the user instead. User IDs are not recycled so think this through carefully.
<br>The following actions are performed when deactivating an user (click <a href="https://matrix-org.github.io/synapse/develop/admin_api/user_admin_api.html#deactivate-account" target="_blank">here</a> for more information as this may change):
<ul>
<li>Try to unpind 3PIDs from the identity server</li>
<li>Remove all 3PIDs from the homeserver</li>
<li>Delete all devices and E2EE keys</li>
<li>Delete all access tokens</li>
<li>Delete the password hash</li>
<li>Removal from all rooms the user is a member of</li>
<li>Remove the user from the user directory</li>
<li>Reject all pending invites</li>
<li>Remove all account validity information related to the user</li>
</ul>
</p>
<h3>Inputs</h3>
<dl class="message-properties">
<dt>msg.userId
<span class="property-type">string</span>
</dt>
<dd> ID of user to create/edit (ex: @bob:example.com)</dd>
<dt class="optional">msg.erase
<span class="property-type">string</span>
</dt>
<dd>
The following additional actions are performed during deactivation if set to true (defaults to false)
<ul>
<li>Remove the user's display name</li>
<li>Remove the user's avatar URL</li>
<li>Mark the user as erased</li>
</ul>
</dd>
</dl>
<h3>Outputs</h3>
<ol class="node-ports">
<li>Success
<dl class="message-properties">
<dd>original msg object preserved.</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>

View File

@ -0,0 +1,63 @@
module.exports = function(RED) {
function MatrixSynapseDeactivateUser(n) {
RED.nodes.createNode(this, n);
let node = this;
this.name = n.name;
this.server = RED.nodes.getNode(n.server);
if(!this.server) {
node.error('Server must be configured on the node.');
return;
}
this.encodeUri = function(pathTemplate, variables) {
for (const key in variables) {
if (!variables.hasOwnProperty(key)) {
continue;
}
pathTemplate = pathTemplate.replace(
key, encodeURIComponent(variables[key]),
);
}
return pathTemplate;
};
node.on("input", function (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");
node.send([null, msg]);
}
if(!msg.userId) {
node.error("msg.userId must be set to edit/create a user (ex: @user:server.com)");
return;
}
const path = node.encodeUri(
"/_synapse/admin/v1/deactivate/$userId",
{ $userId: userId },
);
node.server.matrixClient.http
.authedRequest(undefined, 'POST', path, undefined, { "erase": (msg.erase || false) }, { prefix: '' })
.then(function(e){
msg.payload = e;
node.send([msg, null]);
}).catch(function(e){
node.warn("Error deactivating user " + e);
msg.error = e;
node.send([null, msg]);
});
node.server.matrixClient.deactivateSynapseUser(msg.userId)
;
});
}
RED.nodes.registerType("matrix-synapse-deactivate-user", MatrixSynapseDeactivateUser);
}

View File

@ -0,0 +1,100 @@
<script type="text/javascript">
RED.nodes.registerType('matrix-synapse-register', {
category: 'matrix',
color: '#00b7ca',
icon: "matrix.png",
outputLabels: ["success", "error"],
inputs:1,
outputs:2,
credentials: {
server: { type:"text", required: true },
sharedSecret: { type:"text", required: true },
},
defaults: {
name: { value: null },
},
label: function() {
return this.name || "Synapse Register v1";
},
paletteLabel: 'Synapse Register v1'
});
</script>
<script type="text/html" data-template-name="matrix-synapse-register">
<div class="form-row">
<label for="node-input-name"><i class="icon-tag"></i> Name</label>
<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> Server URL</label>
<input type="text" id="node-input-server" placeholder="https://matrix.example.com">
</div>
<div class="form-tips" style="margin-bottom: 12px;">
This only works for Synapse Matrix Servers. You must be the system admin of this server. Make sure the path <code>/_synapse/admin/v1/register</code> is available on your server.
</div>
<div class="form-row">
<label for="node-input-sharedSecret"><i class="fa fa-user"></i> Registration Shared Secret</label>
<input type="text" id="node-input-sharedSecret">
</div>
<div class="form-tips" style="margin-bottom: 12px;">
Public registration does not need to be enabled to register. Using your <code>registration_shared_secret</code> from within your <code>homeserver.yaml</code> server config file will allow Node-RED to register users.
</div>
</script>
<script type="text/html" data-help-name="matrix-synapse-register">
<h3>Details</h3>
<p>Register a client with a Synapse Matrix server using the v1 admin API. This registers users with closed registration by using the <code>registration_shared_secret</code> from Synapse's <code>homeserver.yaml</code> config file. This is mainly used to generate a first time admin user on newly created Matrix servers (as you can use the V2 registration endpoint after you have an admin user).</p>
<h3>Inputs</h3>
<dl class="message-properties">
<dt>msg.payload
<span class="property-type">Object</span>
</dt>
<dd> Details of the new user to create. </dd>
<dt class="optional">msg.payload.displayname
<span class="property-type">String | null</span>
</dt>
<dd> Set the displayname for the user (default to username if not set). </dd>
<dt>msg.payload.username
<span class="property-type">Object</span>
</dt>
<dd> Username for the new user. </dd>
<dt>msg.payload.password
<span class="property-type">String</span>
</dt>
<dd> Password for the new user. </dd>
<dt>msg.payload.admin
<span class="property-type">Bool</span>
</dt>
<dd> If true, the new user will be an admin. Default to false. </dd>
<dt class="optional">msg.payload.user_type
<span class="property-type">String | null</span>
</dt>
<dd> Set the user type. Leave this to null if you don't know what it is for. Check <a href="https://github.com/matrix-org/synapse/blob/master/synapse/api/constants.py">here</a> and look for <code>class UserTypes</code> to figure out what is valid.</dd>
</dl>
<h3>Outputs</h3>
<ol class="node-ports">
<li>Success
<dl class="message-properties">
<dd>original msg object preserved.</dd>
<dt>msg.eventId <span class="property-type">string</span></dt>
<dd>the eventId from the posted message.</dd>
</dl>
</li>
<li>Error
<dl class="message-properties">
<dt>msg.error <span class="property-type">string</span></dt>
<dd>the error that occurred.</dd>
</dl>
</li>
</ol>
</script>

View File

@ -0,0 +1,100 @@
module.exports = function(RED) {
const got = require("got");
const utf8 = require('utf8');
const crypto = require('crypto');
function MatrixSynapseRegister(n) {
RED.nodes.createNode(this, n);
let node = this;
this.name = n.name;
this.server = this.credentials.server;
this.sharedSecret = this.credentials.sharedSecret;
if(!this.server) {
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.');
return;
}
node.on("input", function (msg) {
if(!msg.payload.username) {
node.error("msg.payload.username is required");
return;
}
if(!msg.payload.password) {
node.error("msg.payload.password is required");
return;
}
(async () => {
try {
var response = await got.get(this.server + '/_synapse/admin/v1/register', {
responseType: 'json'
});
} catch (error) {
msg.error = error;
msg.error_extra = 'Failed fetching nonce key from registration endpoint';
delete msg.payload.password;
node.status({fill:"red",shape:"ring",text:"Failure"});
node.send([null, msg]);
return;
}
var nonce = response.body.nonce;
if(!nonce) {
node.error('Could not get nonce from /_synapse/admin/v1/register');
return;
}
let hmac = crypto.createHmac("sha1", node.sharedSecret )
.update(utf8.encode(nonce))
.update("\x00")
.update(utf8.encode(msg.payload.username))
.update("\x00")
.update(utf8.encode(msg.payload.password))
.update("\x00")
.update(msg.payload.admin ? "admin" : "notadmin")
.digest('hex');
try {
response = await got.post(this.server + '/_synapse/admin/v1/register', {
json: {
"nonce": nonce,
"username": msg.payload.username,
"password": msg.payload.password,
"mac": hmac,
"admin": msg.payload.admin || false,
"user_type": msg.payload.user_type || null,
},
responseType: 'json'
});
} catch (error) {
msg.error = error;
msg.error_extra = 'Failed submitting registration request';
delete msg.payload.password;
node.status({fill:"red",shape:"ring",text:"Failure"});
node.send([null, msg]);
return;
}
node.status({fill:"green",shape:"dot",text:"Registered: " + msg.payload.username});
msg.payload = response.body;
node.send(msg);
})();
});
}
RED.nodes.registerType("matrix-synapse-register", MatrixSynapseRegister, {
credentials: {
server: { type:"text", value: null, required: true },
sharedSecret: { type:"text", value: null },
}
});
}

View File

@ -0,0 +1,109 @@
<script type="text/javascript">
RED.nodes.registerType('matrix-synapse-users',{
category: 'matrix',
color: '#00b7ca',
icon: "matrix.png",
outputLabels: ["success", "error"],
inputs:1,
outputs:2,
defaults: {
name: { value: null },
server: { value: "", type: "matrix-server-config" }
},
label: function() {
return this.name || "Synapse User List";
},
paletteLabel: 'Synapse User List'
});
</script>
<script type="text/html" data-template-name="matrix-synapse-users">
<div class="form-row">
<label for="node-input-name"><i class="icon-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
</div>
<div class="form-row">
<label for="node-input-server"><i class="fa fa-user"></i> Matrix Server Config</label>
<input type="text" id="node-input-server">
</div>
<div class="form-tips">
This only works on Synapse servers. The user also must be an administrator.
</div>
</script>
<script type="text/html" data-help-name="matrix-synapse-users">
<h3>Details</h3>
<p>This node lists out users from a Synapse server. Only works on Synapse Matrix servers. User must be an admin to call this API.</p>
<h3>Inputs</h3>
<dl class="message-properties">
<dt class="optional">msg.from
<span class="property-type">Integer</span>
</dt>
<dd> Is optional but used for pagination, denoting the offset in the returned results. This should be treated as an opaque value and not explicitly set to anything other than the return value of next_token from a previous call. Defaults to 0.</dd>
<dt class="optional">msg.limit
<span class="property-type">Integer</span>
</dt>
<dd> limit - representing a positive integer - Is optional but is used for pagination, denoting the maximum number of items to return in this call. Defaults to 100.</dd>
<dt class="optional">msg.guests
<span class="property-type">Bool</span>
</dt>
<dd> Is optional and if false will exclude guest users. Defaults to true to include guest users.</dd>
<dt class="optional">msg.order_by
<span class="property-type">String</span>
</dt>
<dd>
The method by which to sort the returned list of users.
If the ordered field has duplicates, the second order is always by ascending name, which guarantees a stable ordering.
Valid values are:
<ul>
<li><code>name</code> - Users are ordered alphabetically by <code>name</code>. This is the default.</li>
<li><code>is_guest</code> - Users are ordered by <code>is_guest</code> status.</li>
<li><code>admin</code> - Users are ordered by <code>admin</code> status.</li>
<li><code>user_type</code> - Users are ordered alphabetically by <code>user_type</code>.</li>
<li><code>deactivated</code> - Users are ordered by <code>deactivated</code> status.</li>
<li><code>shadow_banned</code> - Users are ordered by <code>shadow_banned</code> status.</li>
<li><code>displayname</code> - Users are ordered alphabetically by <code>displayname</code>.</li>
<li><code>avatar_url</code> - Users are ordered alphabetically by avatar URL.</li>
<li><code>creation_ts</code> - Users are ordered by when the users was created in ms.</li>
</ul>
<p>Caution. The database only has indexes on the columns <code>name</code> and <code>creation_ts</code>.
This means that if a different sort order is used (<code>is_guest</code>, <code>admin</code>,
<code>user_type</code>, <code>deactivated</code>, <code>shadow_banned</code>, <code>avatar_url</code> or <code>displayname</code>),
this can cause a large load on the database, especially for large environments.</p>
</dd>
</dl>
<h3>Outputs</h3>
<ol class="node-ports">
<li>Success
<dl class="message-properties">
<dt>msg.payload <span class="property-type">object</span></dt>
<dd>the response object from the server.</dd>
<dt>msg.payload.users <span class="property-type">array</span></dt>
<dd>list of users from the Matrix server. <a href="https://matrix-org.github.io/synapse/develop/admin_api/user_admin_api.html#list-accounts" target="_blank">Click here</a> for details on what this contains (or do a test and dump the output). We would put the details here in the doc but Synapse is constantly changing so it's best to reference the official Synapse docs.</dd>
<dt>msg.next_token <span class="property-type">string</span></dt>
<dd>string representing a positive integer - Indication for pagination. See above (input msg.from)</dd>
<dt>msg.total <span class="property-type">integer</span></dt>
<dd>Total number of media.</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://matrix-org.github.io/synapse/develop/admin_api/user_admin_api.html#list-accounts">Matrix Docs</a> - List Accounts API method information</li>
</ul>
</script>

View File

@ -0,0 +1,68 @@
module.exports = function(RED) {
function MatrixSynapseUsers(n) {
RED.nodes.createNode(this, n);
var node = this;
this.name = n.name;
this.server = RED.nodes.getNode(n.server);
if (!node.server) {
node.warn("No configuration node");
return;
}
node.status({ fill: "red", shape: "ring", text: "disconnected" });
node.server.on("disconnected", function(){
node.status({ fill: "red", shape: "ring", text: "disconnected" });
});
node.server.on("connected", function() {
node.status({ fill: "green", shape: "ring", text: "connected" });
});
node.on("input", function (msg) {
if (! node.server || ! node.server.matrixClient) {
node.warn("No matrix server selected");
return;
}
if(!node.server.isConnected()) {
node.error("Matrix server connection is currently closed");
node.send([null, msg]);
}
let queryParams = {
'from': msg.from || 0,
'limit': msg.limit || 100
};
if(msg.guests) {
queryParams['guests'] = msg.guests;
}
if(msg.order_by) {
queryParams['order_by'] = msg.order_by;
}
node.server.matrixClient.http
.authedRequest(
undefined,
'GET',
"/_synapse/admin/v2/users",
queryParams,
undefined,
{ prefix: '' }
).then(function(e){
msg.payload = e;
node.send([msg, null]);
}).catch(function(e){
node.warn("Error fetching user list " + e);
msg.error = e;
node.send([null, msg]);
});
});
}
RED.nodes.registerType("matrix-synapse-users", MatrixSynapseUsers);
}

View File

@ -0,0 +1,71 @@
<script type="text/javascript">
RED.nodes.registerType('matrix-whois-user', {
category: 'matrix',
color: '#00b7ca',
icon: "matrix.png",
outputLabels: ["success", "error"],
inputs:1,
outputs:2,
defaults: {
name: { value: null },
server: { value: "", type: "matrix-server-config" },
},
label: function() {
return this.name || "WhoIs User";
},
paletteLabel: 'WhoIs User'
});
</script>
<script type="text/html" data-template-name="matrix-whois-user">
<div class="form-row">
<label for="node-input-name"><i class="icon-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
</div>
<div class="form-row">
<label for="node-input-server"><i class="fa fa-user"></i> Matrix Server Config</label>
<input type="text" id="node-input-server">
</div>
<div class="form-tips" style="margin-bottom: 12px;">
User must be an admin to use this endpoint.
</div>
</script>
<script type="text/html" data-help-name="matrix-whois-user">
<h3>Details</h3>
<p>This node returns information about the active sessions for a specific user.</p>
<h3>Inputs</h3>
<dl class="message-properties">
<dt>msg.userId
<span class="property-type">string</span>
</dt>
<dd> ID of user to do whois request for (ex: @bob:example.com)</dd>
</dl>
<h3>Outputs</h3>
<ol class="node-ports">
<li>Success
<dl class="message-properties">
<dt>msg.payload <span class="property-type">string</span></dt>
<dd>This returns data directly from the API endpoint. <a href="https://matrix-org.github.io/synapse/develop/admin_api/user_admin_api.html#query-current-sessions-for-a-user" target="_blank">Click here</a> to see what this returns.</dd>
</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>Details</h3>
<p>
<ul>
<li><a href="https://matrix-org.github.io/synapse/develop/admin_api/user_admin_api.html#query-current-sessions-for-a-user">Matrix Doc</a> - Doc reference for User Whois API</li>
</ul>
</p>
</script>

66
src/matrix-whois-user.js Normal file
View File

@ -0,0 +1,66 @@
module.exports = function(RED) {
function MatrixWhoIsUser(n) {
RED.nodes.createNode(this, n);
let node = this;
this.name = n.name;
this.server = RED.nodes.getNode(n.server);
if(!this.server) {
node.error('Server must be configured on the node.');
return;
}
this.encodeUri = function(pathTemplate, variables) {
for (const key in variables) {
if (!variables.hasOwnProperty(key)) {
continue;
}
pathTemplate = pathTemplate.replace(
key, encodeURIComponent(variables[key]),
);
}
return pathTemplate;
};
node.on("input", function (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");
node.send([null, msg]);
}
if(!msg.userId) {
node.error("msg.userId must be set to get user whois data");
return;
}
// we need the status code, so set onlydata to false for this request
node.server.matrixClient.http
.authedRequest(
undefined,
'GET',
node.encodeUri(
"/_matrix/client/r0/admin/whois/$userId",
{ $userId: msg.userId.toLowerCase() },
),
undefined,
msg.payload,
{ prefix: '' }
).then(function(e){
msg.payload = e;
node.send([msg, null]);
}).catch(function(e){
node.warn("Error creating/editing user " + e);
msg.error = e;
node.send([null, msg]);
});
});
}
RED.nodes.registerType("matrix-whois-user", MatrixWhoIsUser);
}