mirror of
https://github.com/Skylar-Tech/node-red-contrib-matrix-chat.git
synced 2026-05-19 05:33:18 -06:00
Compare commits
13 Commits
000c28e3b8
..
v0.8.0
| Author | SHA1 | Date | |
|---|---|---|---|
| 5de1274def | |||
| 1b54bc03eb | |||
| 65edc94854 | |||
| 351679ad77 | |||
| 2b2da4faf7 | |||
| 5090e4fbb6 | |||
| 51e649b4cf | |||
| a08709265e | |||
| 57ba70db6c | |||
| a3e1381d53 | |||
| 6dca3aa70e | |||
| b36286d994 | |||
| f14190d9ea |
Generated
+751
-63
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "node-red-contrib-matrix-chat",
|
||||
"version": "0.7.1",
|
||||
"version": "0.8.0",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "node-red-contrib-matrix-chat",
|
||||
"version": "0.7.1",
|
||||
"version": "0.8.0",
|
||||
"license": "SEE LICENSE FILE",
|
||||
"dependencies": {
|
||||
"abort-controller": "^3.0.0",
|
||||
@@ -21,6 +21,7 @@
|
||||
"node-localstorage": "^2.2.1",
|
||||
"olm": "https://gitlab.matrix.org/matrix-org/olm/-/package_files/2572/download",
|
||||
"request": "^2.88.2",
|
||||
"sharp": "^0.33.4",
|
||||
"tmp": "^0.2.1",
|
||||
"utf8": "^3.0.0"
|
||||
},
|
||||
@@ -2644,6 +2645,15 @@
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@emnapi/runtime": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.2.0.tgz",
|
||||
"integrity": "sha512-bV21/9LQmcQeCPEg3BDFtvwL6cwiTMksYNWQQ4KOxCZikEGalWtenoZ0wCiukJINlGCIi2KXx01g4FoH/LxpzQ==",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@expo/bunyan": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@expo/bunyan/-/bunyan-4.0.0.tgz",
|
||||
@@ -3522,6 +3532,437 @@
|
||||
"@hapi/hoek": "^9.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-darwin-arm64": {
|
||||
"version": "0.33.4",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.4.tgz",
|
||||
"integrity": "sha512-p0suNqXufJs9t3RqLBO6vvrgr5OhgbWp76s5gTRvdmxmuv9E1rcaqGUsl3l4mKVmXPkTkTErXediAui4x+8PSA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"glibc": ">=2.26",
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0",
|
||||
"npm": ">=9.6.5",
|
||||
"pnpm": ">=7.1.0",
|
||||
"yarn": ">=3.2.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-darwin-arm64": "1.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-darwin-x64": {
|
||||
"version": "0.33.4",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.4.tgz",
|
||||
"integrity": "sha512-0l7yRObwtTi82Z6ebVI2PnHT8EB2NxBgpK2MiKJZJ7cz32R4lxd001ecMhzzsZig3Yv9oclvqqdV93jo9hy+Dw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"glibc": ">=2.26",
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0",
|
||||
"npm": ">=9.6.5",
|
||||
"pnpm": ">=7.1.0",
|
||||
"yarn": ">=3.2.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-darwin-x64": "1.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-libvips-darwin-arm64": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.2.tgz",
|
||||
"integrity": "sha512-tcK/41Rq8IKlSaKRCCAuuY3lDJjQnYIW1UXU1kxcEKrfL8WR7N6+rzNoOxoQRJWTAECuKwgAHnPvqXGN8XfkHA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"macos": ">=11",
|
||||
"npm": ">=9.6.5",
|
||||
"pnpm": ">=7.1.0",
|
||||
"yarn": ">=3.2.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-libvips-darwin-x64": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.2.tgz",
|
||||
"integrity": "sha512-Ofw+7oaWa0HiiMiKWqqaZbaYV3/UGL2wAPeLuJTx+9cXpCRdvQhCLG0IH8YGwM0yGWGLpsF4Su9vM1o6aer+Fw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"macos": ">=10.13",
|
||||
"npm": ">=9.6.5",
|
||||
"pnpm": ">=7.1.0",
|
||||
"yarn": ">=3.2.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-libvips-linux-arm": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.2.tgz",
|
||||
"integrity": "sha512-iLWCvrKgeFoglQxdEwzu1eQV04o8YeYGFXtfWU26Zr2wWT3q3MTzC+QTCO3ZQfWd3doKHT4Pm2kRmLbupT+sZw==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"glibc": ">=2.28",
|
||||
"npm": ">=9.6.5",
|
||||
"pnpm": ">=7.1.0",
|
||||
"yarn": ">=3.2.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-libvips-linux-arm64": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.2.tgz",
|
||||
"integrity": "sha512-x7kCt3N00ofFmmkkdshwj3vGPCnmiDh7Gwnd4nUwZln2YjqPxV1NlTyZOvoDWdKQVDL911487HOueBvrpflagw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"glibc": ">=2.26",
|
||||
"npm": ">=9.6.5",
|
||||
"pnpm": ">=7.1.0",
|
||||
"yarn": ">=3.2.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-libvips-linux-s390x": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.0.2.tgz",
|
||||
"integrity": "sha512-cmhQ1J4qVhfmS6szYW7RT+gLJq9dH2i4maq+qyXayUSn9/3iY2ZeWpbAgSpSVbV2E1JUL2Gg7pwnYQ1h8rQIog==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"glibc": ">=2.28",
|
||||
"npm": ">=9.6.5",
|
||||
"pnpm": ">=7.1.0",
|
||||
"yarn": ">=3.2.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-libvips-linux-x64": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.2.tgz",
|
||||
"integrity": "sha512-E441q4Qdb+7yuyiADVi5J+44x8ctlrqn8XgkDTwr4qPJzWkaHwD489iZ4nGDgcuya4iMN3ULV6NwbhRZJ9Z7SQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"glibc": ">=2.26",
|
||||
"npm": ">=9.6.5",
|
||||
"pnpm": ">=7.1.0",
|
||||
"yarn": ">=3.2.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-libvips-linuxmusl-arm64": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.2.tgz",
|
||||
"integrity": "sha512-3CAkndNpYUrlDqkCM5qhksfE+qSIREVpyoeHIU6jd48SJZViAmznoQQLAv4hVXF7xyUB9zf+G++e2v1ABjCbEQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"musl": ">=1.2.2",
|
||||
"npm": ">=9.6.5",
|
||||
"pnpm": ">=7.1.0",
|
||||
"yarn": ">=3.2.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-libvips-linuxmusl-x64": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.2.tgz",
|
||||
"integrity": "sha512-VI94Q6khIHqHWNOh6LLdm9s2Ry4zdjWJwH56WoiJU7NTeDwyApdZZ8c+SADC8OH98KWNQXnE01UdJ9CSfZvwZw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"musl": ">=1.2.2",
|
||||
"npm": ">=9.6.5",
|
||||
"pnpm": ">=7.1.0",
|
||||
"yarn": ">=3.2.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-linux-arm": {
|
||||
"version": "0.33.4",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.4.tgz",
|
||||
"integrity": "sha512-RUgBD1c0+gCYZGCCe6mMdTiOFS0Zc/XrN0fYd6hISIKcDUbAW5NtSQW9g/powkrXYm6Vzwd6y+fqmExDuCdHNQ==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"glibc": ">=2.28",
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0",
|
||||
"npm": ">=9.6.5",
|
||||
"pnpm": ">=7.1.0",
|
||||
"yarn": ">=3.2.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-linux-arm": "1.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-linux-arm64": {
|
||||
"version": "0.33.4",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.4.tgz",
|
||||
"integrity": "sha512-2800clwVg1ZQtxwSoTlHvtm9ObgAax7V6MTAB/hDT945Tfyy3hVkmiHpeLPCKYqYR1Gcmv1uDZ3a4OFwkdBL7Q==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"glibc": ">=2.26",
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0",
|
||||
"npm": ">=9.6.5",
|
||||
"pnpm": ">=7.1.0",
|
||||
"yarn": ">=3.2.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-linux-arm64": "1.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-linux-s390x": {
|
||||
"version": "0.33.4",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.4.tgz",
|
||||
"integrity": "sha512-h3RAL3siQoyzSoH36tUeS0PDmb5wINKGYzcLB5C6DIiAn2F3udeFAum+gj8IbA/82+8RGCTn7XW8WTFnqag4tQ==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"glibc": ">=2.31",
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0",
|
||||
"npm": ">=9.6.5",
|
||||
"pnpm": ">=7.1.0",
|
||||
"yarn": ">=3.2.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-linux-s390x": "1.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-linux-x64": {
|
||||
"version": "0.33.4",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.4.tgz",
|
||||
"integrity": "sha512-GoR++s0XW9DGVi8SUGQ/U4AeIzLdNjHka6jidVwapQ/JebGVQIpi52OdyxCNVRE++n1FCLzjDovJNozif7w/Aw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"glibc": ">=2.26",
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0",
|
||||
"npm": ">=9.6.5",
|
||||
"pnpm": ">=7.1.0",
|
||||
"yarn": ">=3.2.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-linux-x64": "1.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-linuxmusl-arm64": {
|
||||
"version": "0.33.4",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.4.tgz",
|
||||
"integrity": "sha512-nhr1yC3BlVrKDTl6cO12gTpXMl4ITBUZieehFvMntlCXFzH2bvKG76tBL2Y/OqhupZt81pR7R+Q5YhJxW0rGgQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"musl": ">=1.2.2",
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0",
|
||||
"npm": ">=9.6.5",
|
||||
"pnpm": ">=7.1.0",
|
||||
"yarn": ">=3.2.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-linuxmusl-arm64": "1.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-linuxmusl-x64": {
|
||||
"version": "0.33.4",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.4.tgz",
|
||||
"integrity": "sha512-uCPTku0zwqDmZEOi4ILyGdmW76tH7dm8kKlOIV1XC5cLyJ71ENAAqarOHQh0RLfpIpbV5KOpXzdU6XkJtS0daw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"musl": ">=1.2.2",
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0",
|
||||
"npm": ">=9.6.5",
|
||||
"pnpm": ">=7.1.0",
|
||||
"yarn": ">=3.2.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-linuxmusl-x64": "1.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-wasm32": {
|
||||
"version": "0.33.4",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.33.4.tgz",
|
||||
"integrity": "sha512-Bmmauh4sXUsUqkleQahpdNXKvo+wa1V9KhT2pDA4VJGKwnKMJXiSTGphn0gnJrlooda0QxCtXc6RX1XAU6hMnQ==",
|
||||
"cpu": [
|
||||
"wasm32"
|
||||
],
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@emnapi/runtime": "^1.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0",
|
||||
"npm": ">=9.6.5",
|
||||
"pnpm": ">=7.1.0",
|
||||
"yarn": ">=3.2.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-win32-ia32": {
|
||||
"version": "0.33.4",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.4.tgz",
|
||||
"integrity": "sha512-99SJ91XzUhYHbx7uhK3+9Lf7+LjwMGQZMDlO/E/YVJ7Nc3lyDFZPGhjwiYdctoH2BOzW9+TnfqcaMKt0jHLdqw==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0",
|
||||
"npm": ">=9.6.5",
|
||||
"pnpm": ">=7.1.0",
|
||||
"yarn": ">=3.2.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-win32-x64": {
|
||||
"version": "0.33.4",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.4.tgz",
|
||||
"integrity": "sha512-3QLocdTRVIrFNye5YocZl+KKpYKP+fksi1QhmOArgx7GyhIbQp/WrJRu176jm8IxromS7RIkzMiMINVdBtC8Aw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0",
|
||||
"npm": ">=9.6.5",
|
||||
"pnpm": ">=7.1.0",
|
||||
"yarn": ">=3.2.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@jest/create-cache-key-function": {
|
||||
"version": "29.6.3",
|
||||
"resolved": "https://registry.npmjs.org/@jest/create-cache-key-function/-/create-cache-key-function-29.6.3.tgz",
|
||||
@@ -6851,11 +7292,22 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/color": {
|
||||
"version": "4.2.3",
|
||||
"resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz",
|
||||
"integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==",
|
||||
"dependencies": {
|
||||
"color-convert": "^2.0.1",
|
||||
"color-string": "^1.9.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.5.0"
|
||||
}
|
||||
},
|
||||
"node_modules/color-convert": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"color-name": "~1.1.4"
|
||||
},
|
||||
@@ -6866,8 +7318,16 @@
|
||||
"node_modules/color-name": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
|
||||
"optional": true
|
||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
|
||||
},
|
||||
"node_modules/color-string": {
|
||||
"version": "1.9.1",
|
||||
"resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz",
|
||||
"integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==",
|
||||
"dependencies": {
|
||||
"color-name": "^1.0.0",
|
||||
"simple-swizzle": "^0.2.2"
|
||||
}
|
||||
},
|
||||
"node_modules/colorette": {
|
||||
"version": "1.4.0",
|
||||
@@ -13568,14 +14028,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/semver": {
|
||||
"version": "7.5.4",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
|
||||
"integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"lru-cache": "^6.0.0"
|
||||
},
|
||||
"version": "7.6.2",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz",
|
||||
"integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==",
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
},
|
||||
@@ -13583,26 +14038,6 @@
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/semver/node_modules/lru-cache": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
|
||||
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"yallist": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/semver/node_modules/yallist": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
|
||||
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
|
||||
"optional": true,
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/send": {
|
||||
"version": "0.18.0",
|
||||
"resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz",
|
||||
@@ -13754,6 +14189,53 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/sharp": {
|
||||
"version": "0.33.4",
|
||||
"resolved": "https://registry.npmjs.org/sharp/-/sharp-0.33.4.tgz",
|
||||
"integrity": "sha512-7i/dt5kGl7qR4gwPRD2biwD2/SvBn3O04J77XKFgL2OnZtQw+AG9wnuS/csmu80nPRHLYE9E41fyEiG8nhH6/Q==",
|
||||
"hasInstallScript": true,
|
||||
"dependencies": {
|
||||
"color": "^4.2.3",
|
||||
"detect-libc": "^2.0.3",
|
||||
"semver": "^7.6.0"
|
||||
},
|
||||
"engines": {
|
||||
"libvips": ">=8.15.2",
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-darwin-arm64": "0.33.4",
|
||||
"@img/sharp-darwin-x64": "0.33.4",
|
||||
"@img/sharp-libvips-darwin-arm64": "1.0.2",
|
||||
"@img/sharp-libvips-darwin-x64": "1.0.2",
|
||||
"@img/sharp-libvips-linux-arm": "1.0.2",
|
||||
"@img/sharp-libvips-linux-arm64": "1.0.2",
|
||||
"@img/sharp-libvips-linux-s390x": "1.0.2",
|
||||
"@img/sharp-libvips-linux-x64": "1.0.2",
|
||||
"@img/sharp-libvips-linuxmusl-arm64": "1.0.2",
|
||||
"@img/sharp-libvips-linuxmusl-x64": "1.0.2",
|
||||
"@img/sharp-linux-arm": "0.33.4",
|
||||
"@img/sharp-linux-arm64": "0.33.4",
|
||||
"@img/sharp-linux-s390x": "0.33.4",
|
||||
"@img/sharp-linux-x64": "0.33.4",
|
||||
"@img/sharp-linuxmusl-arm64": "0.33.4",
|
||||
"@img/sharp-linuxmusl-x64": "0.33.4",
|
||||
"@img/sharp-wasm32": "0.33.4",
|
||||
"@img/sharp-win32-ia32": "0.33.4",
|
||||
"@img/sharp-win32-x64": "0.33.4"
|
||||
}
|
||||
},
|
||||
"node_modules/sharp/node_modules/detect-libc": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz",
|
||||
"integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/shebang-command": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz",
|
||||
@@ -13834,6 +14316,19 @@
|
||||
"node": ">= 5.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/simple-swizzle": {
|
||||
"version": "0.2.2",
|
||||
"resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz",
|
||||
"integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==",
|
||||
"dependencies": {
|
||||
"is-arrayish": "^0.3.1"
|
||||
}
|
||||
},
|
||||
"node_modules/simple-swizzle/node_modules/is-arrayish": {
|
||||
"version": "0.3.2",
|
||||
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz",
|
||||
"integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ=="
|
||||
},
|
||||
"node_modules/sisteransi": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz",
|
||||
@@ -17147,6 +17642,15 @@
|
||||
"to-fast-properties": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"@emnapi/runtime": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.2.0.tgz",
|
||||
"integrity": "sha512-bV21/9LQmcQeCPEg3BDFtvwL6cwiTMksYNWQQ4KOxCZikEGalWtenoZ0wCiukJINlGCIi2KXx01g4FoH/LxpzQ==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"@expo/bunyan": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@expo/bunyan/-/bunyan-4.0.0.tgz",
|
||||
@@ -17896,6 +18400,147 @@
|
||||
"@hapi/hoek": "^9.0.0"
|
||||
}
|
||||
},
|
||||
"@img/sharp-darwin-arm64": {
|
||||
"version": "0.33.4",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.4.tgz",
|
||||
"integrity": "sha512-p0suNqXufJs9t3RqLBO6vvrgr5OhgbWp76s5gTRvdmxmuv9E1rcaqGUsl3l4mKVmXPkTkTErXediAui4x+8PSA==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"@img/sharp-libvips-darwin-arm64": "1.0.2"
|
||||
}
|
||||
},
|
||||
"@img/sharp-darwin-x64": {
|
||||
"version": "0.33.4",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.4.tgz",
|
||||
"integrity": "sha512-0l7yRObwtTi82Z6ebVI2PnHT8EB2NxBgpK2MiKJZJ7cz32R4lxd001ecMhzzsZig3Yv9oclvqqdV93jo9hy+Dw==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"@img/sharp-libvips-darwin-x64": "1.0.2"
|
||||
}
|
||||
},
|
||||
"@img/sharp-libvips-darwin-arm64": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.2.tgz",
|
||||
"integrity": "sha512-tcK/41Rq8IKlSaKRCCAuuY3lDJjQnYIW1UXU1kxcEKrfL8WR7N6+rzNoOxoQRJWTAECuKwgAHnPvqXGN8XfkHA==",
|
||||
"optional": true
|
||||
},
|
||||
"@img/sharp-libvips-darwin-x64": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.2.tgz",
|
||||
"integrity": "sha512-Ofw+7oaWa0HiiMiKWqqaZbaYV3/UGL2wAPeLuJTx+9cXpCRdvQhCLG0IH8YGwM0yGWGLpsF4Su9vM1o6aer+Fw==",
|
||||
"optional": true
|
||||
},
|
||||
"@img/sharp-libvips-linux-arm": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.2.tgz",
|
||||
"integrity": "sha512-iLWCvrKgeFoglQxdEwzu1eQV04o8YeYGFXtfWU26Zr2wWT3q3MTzC+QTCO3ZQfWd3doKHT4Pm2kRmLbupT+sZw==",
|
||||
"optional": true
|
||||
},
|
||||
"@img/sharp-libvips-linux-arm64": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.2.tgz",
|
||||
"integrity": "sha512-x7kCt3N00ofFmmkkdshwj3vGPCnmiDh7Gwnd4nUwZln2YjqPxV1NlTyZOvoDWdKQVDL911487HOueBvrpflagw==",
|
||||
"optional": true
|
||||
},
|
||||
"@img/sharp-libvips-linux-s390x": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.0.2.tgz",
|
||||
"integrity": "sha512-cmhQ1J4qVhfmS6szYW7RT+gLJq9dH2i4maq+qyXayUSn9/3iY2ZeWpbAgSpSVbV2E1JUL2Gg7pwnYQ1h8rQIog==",
|
||||
"optional": true
|
||||
},
|
||||
"@img/sharp-libvips-linux-x64": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.2.tgz",
|
||||
"integrity": "sha512-E441q4Qdb+7yuyiADVi5J+44x8ctlrqn8XgkDTwr4qPJzWkaHwD489iZ4nGDgcuya4iMN3ULV6NwbhRZJ9Z7SQ==",
|
||||
"optional": true
|
||||
},
|
||||
"@img/sharp-libvips-linuxmusl-arm64": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.2.tgz",
|
||||
"integrity": "sha512-3CAkndNpYUrlDqkCM5qhksfE+qSIREVpyoeHIU6jd48SJZViAmznoQQLAv4hVXF7xyUB9zf+G++e2v1ABjCbEQ==",
|
||||
"optional": true
|
||||
},
|
||||
"@img/sharp-libvips-linuxmusl-x64": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.2.tgz",
|
||||
"integrity": "sha512-VI94Q6khIHqHWNOh6LLdm9s2Ry4zdjWJwH56WoiJU7NTeDwyApdZZ8c+SADC8OH98KWNQXnE01UdJ9CSfZvwZw==",
|
||||
"optional": true
|
||||
},
|
||||
"@img/sharp-linux-arm": {
|
||||
"version": "0.33.4",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.4.tgz",
|
||||
"integrity": "sha512-RUgBD1c0+gCYZGCCe6mMdTiOFS0Zc/XrN0fYd6hISIKcDUbAW5NtSQW9g/powkrXYm6Vzwd6y+fqmExDuCdHNQ==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"@img/sharp-libvips-linux-arm": "1.0.2"
|
||||
}
|
||||
},
|
||||
"@img/sharp-linux-arm64": {
|
||||
"version": "0.33.4",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.4.tgz",
|
||||
"integrity": "sha512-2800clwVg1ZQtxwSoTlHvtm9ObgAax7V6MTAB/hDT945Tfyy3hVkmiHpeLPCKYqYR1Gcmv1uDZ3a4OFwkdBL7Q==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"@img/sharp-libvips-linux-arm64": "1.0.2"
|
||||
}
|
||||
},
|
||||
"@img/sharp-linux-s390x": {
|
||||
"version": "0.33.4",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.4.tgz",
|
||||
"integrity": "sha512-h3RAL3siQoyzSoH36tUeS0PDmb5wINKGYzcLB5C6DIiAn2F3udeFAum+gj8IbA/82+8RGCTn7XW8WTFnqag4tQ==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"@img/sharp-libvips-linux-s390x": "1.0.2"
|
||||
}
|
||||
},
|
||||
"@img/sharp-linux-x64": {
|
||||
"version": "0.33.4",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.4.tgz",
|
||||
"integrity": "sha512-GoR++s0XW9DGVi8SUGQ/U4AeIzLdNjHka6jidVwapQ/JebGVQIpi52OdyxCNVRE++n1FCLzjDovJNozif7w/Aw==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"@img/sharp-libvips-linux-x64": "1.0.2"
|
||||
}
|
||||
},
|
||||
"@img/sharp-linuxmusl-arm64": {
|
||||
"version": "0.33.4",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.4.tgz",
|
||||
"integrity": "sha512-nhr1yC3BlVrKDTl6cO12gTpXMl4ITBUZieehFvMntlCXFzH2bvKG76tBL2Y/OqhupZt81pR7R+Q5YhJxW0rGgQ==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"@img/sharp-libvips-linuxmusl-arm64": "1.0.2"
|
||||
}
|
||||
},
|
||||
"@img/sharp-linuxmusl-x64": {
|
||||
"version": "0.33.4",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.4.tgz",
|
||||
"integrity": "sha512-uCPTku0zwqDmZEOi4ILyGdmW76tH7dm8kKlOIV1XC5cLyJ71ENAAqarOHQh0RLfpIpbV5KOpXzdU6XkJtS0daw==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"@img/sharp-libvips-linuxmusl-x64": "1.0.2"
|
||||
}
|
||||
},
|
||||
"@img/sharp-wasm32": {
|
||||
"version": "0.33.4",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.33.4.tgz",
|
||||
"integrity": "sha512-Bmmauh4sXUsUqkleQahpdNXKvo+wa1V9KhT2pDA4VJGKwnKMJXiSTGphn0gnJrlooda0QxCtXc6RX1XAU6hMnQ==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"@emnapi/runtime": "^1.1.1"
|
||||
}
|
||||
},
|
||||
"@img/sharp-win32-ia32": {
|
||||
"version": "0.33.4",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.4.tgz",
|
||||
"integrity": "sha512-99SJ91XzUhYHbx7uhK3+9Lf7+LjwMGQZMDlO/E/YVJ7Nc3lyDFZPGhjwiYdctoH2BOzW9+TnfqcaMKt0jHLdqw==",
|
||||
"optional": true
|
||||
},
|
||||
"@img/sharp-win32-x64": {
|
||||
"version": "0.33.4",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.4.tgz",
|
||||
"integrity": "sha512-3QLocdTRVIrFNye5YocZl+KKpYKP+fksi1QhmOArgx7GyhIbQp/WrJRu176jm8IxromS7RIkzMiMINVdBtC8Aw==",
|
||||
"optional": true
|
||||
},
|
||||
"@jest/create-cache-key-function": {
|
||||
"version": "29.6.3",
|
||||
"resolved": "https://registry.npmjs.org/@jest/create-cache-key-function/-/create-cache-key-function-29.6.3.tgz",
|
||||
@@ -20515,11 +21160,19 @@
|
||||
"shallow-clone": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"color": {
|
||||
"version": "4.2.3",
|
||||
"resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz",
|
||||
"integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==",
|
||||
"requires": {
|
||||
"color-convert": "^2.0.1",
|
||||
"color-string": "^1.9.0"
|
||||
}
|
||||
},
|
||||
"color-convert": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"color-name": "~1.1.4"
|
||||
}
|
||||
@@ -20527,8 +21180,16 @@
|
||||
"color-name": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
|
||||
"optional": true
|
||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
|
||||
},
|
||||
"color-string": {
|
||||
"version": "1.9.1",
|
||||
"resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz",
|
||||
"integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==",
|
||||
"requires": {
|
||||
"color-name": "^1.0.0",
|
||||
"simple-swizzle": "^0.2.2"
|
||||
}
|
||||
},
|
||||
"colorette": {
|
||||
"version": "1.4.0",
|
||||
@@ -25752,33 +26413,9 @@
|
||||
"integrity": "sha512-RjZyX3nVwJyCuTo5tGPx+PZWkDMCg7oOLpSlhjDdZfwUoNqG1mM8nyj31IGHyaPWXhjbP7cdK3qZ2bmkJ1GzRw=="
|
||||
},
|
||||
"semver": {
|
||||
"version": "7.5.4",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
|
||||
"integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"requires": {
|
||||
"lru-cache": "^6.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"lru-cache": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
|
||||
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"requires": {
|
||||
"yallist": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"yallist": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
|
||||
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
|
||||
"optional": true,
|
||||
"peer": true
|
||||
}
|
||||
}
|
||||
"version": "7.6.2",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz",
|
||||
"integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w=="
|
||||
},
|
||||
"send": {
|
||||
"version": "0.18.0",
|
||||
@@ -25907,6 +26544,42 @@
|
||||
"kind-of": "^6.0.2"
|
||||
}
|
||||
},
|
||||
"sharp": {
|
||||
"version": "0.33.4",
|
||||
"resolved": "https://registry.npmjs.org/sharp/-/sharp-0.33.4.tgz",
|
||||
"integrity": "sha512-7i/dt5kGl7qR4gwPRD2biwD2/SvBn3O04J77XKFgL2OnZtQw+AG9wnuS/csmu80nPRHLYE9E41fyEiG8nhH6/Q==",
|
||||
"requires": {
|
||||
"@img/sharp-darwin-arm64": "0.33.4",
|
||||
"@img/sharp-darwin-x64": "0.33.4",
|
||||
"@img/sharp-libvips-darwin-arm64": "1.0.2",
|
||||
"@img/sharp-libvips-darwin-x64": "1.0.2",
|
||||
"@img/sharp-libvips-linux-arm": "1.0.2",
|
||||
"@img/sharp-libvips-linux-arm64": "1.0.2",
|
||||
"@img/sharp-libvips-linux-s390x": "1.0.2",
|
||||
"@img/sharp-libvips-linux-x64": "1.0.2",
|
||||
"@img/sharp-libvips-linuxmusl-arm64": "1.0.2",
|
||||
"@img/sharp-libvips-linuxmusl-x64": "1.0.2",
|
||||
"@img/sharp-linux-arm": "0.33.4",
|
||||
"@img/sharp-linux-arm64": "0.33.4",
|
||||
"@img/sharp-linux-s390x": "0.33.4",
|
||||
"@img/sharp-linux-x64": "0.33.4",
|
||||
"@img/sharp-linuxmusl-arm64": "0.33.4",
|
||||
"@img/sharp-linuxmusl-x64": "0.33.4",
|
||||
"@img/sharp-wasm32": "0.33.4",
|
||||
"@img/sharp-win32-ia32": "0.33.4",
|
||||
"@img/sharp-win32-x64": "0.33.4",
|
||||
"color": "^4.2.3",
|
||||
"detect-libc": "^2.0.3",
|
||||
"semver": "^7.6.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"detect-libc": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz",
|
||||
"integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"shebang-command": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz",
|
||||
@@ -25974,6 +26647,21 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"simple-swizzle": {
|
||||
"version": "0.2.2",
|
||||
"resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz",
|
||||
"integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==",
|
||||
"requires": {
|
||||
"is-arrayish": "^0.3.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"is-arrayish": {
|
||||
"version": "0.3.2",
|
||||
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz",
|
||||
"integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"sisteransi": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz",
|
||||
|
||||
+7
-2
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "node-red-contrib-matrix-chat",
|
||||
"version": "0.7.1",
|
||||
"version": "0.8.0",
|
||||
"description": "Matrix chat server client for Node-RED",
|
||||
"dependencies": {
|
||||
"abort-controller": "^3.0.0",
|
||||
@@ -15,6 +15,7 @@
|
||||
"node-localstorage": "^2.2.1",
|
||||
"olm": "https://gitlab.matrix.org/matrix-org/olm/-/package_files/2572/download",
|
||||
"request": "^2.88.2",
|
||||
"sharp": "^0.33.4",
|
||||
"tmp": "^0.2.1",
|
||||
"utf8": "^3.0.0"
|
||||
},
|
||||
@@ -24,11 +25,15 @@
|
||||
"matrix-server-config": "src/matrix-server-config.js",
|
||||
"matrix-receive": "src/matrix-receive.js",
|
||||
"matrix-send-message": "src/matrix-send-message.js",
|
||||
"matrix-typing": "src/matrix-typing.js",
|
||||
"matrix-mark-read": "src/matrix-mark-read.js",
|
||||
"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-user-settings": "src/matrix-user-settings.js",
|
||||
"matrix-get-user": "src/matrix-get-user.js",
|
||||
"matrix-create-room": "src/matrix-create-room.js",
|
||||
"matrix-invite-room": "src/matrix-invite-room.js",
|
||||
"matrix-room-invite": "src/matrix-room-invite.js",
|
||||
@@ -45,7 +50,7 @@
|
||||
"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-typing": "src/matrix-typing.js"
|
||||
"matrix-paginate-room": "src/matrix-paginate-room.js"
|
||||
}
|
||||
},
|
||||
"engines": {
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 5.0 KiB |
@@ -0,0 +1,352 @@
|
||||
<script type="text/html" data-template-name="matrix-get-user">
|
||||
<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-user"><i class="fa fa-user"></i> User ID</label>
|
||||
<input type="text" id="node-input-user">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-property"><i class="fa fa-user"></i> Property</label>
|
||||
<input type="text" id="node-input-property">
|
||||
</div>
|
||||
<div class="form-row form-tips">
|
||||
This is the property the user data object will be set to
|
||||
</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>
|
||||
|
||||
<script type="text/html" data-help-name="matrix-get-user">
|
||||
<h3>Details</h3>
|
||||
<p>
|
||||
Get data for a user. Data includes display name, avatar URL, presence, last active, currently active, and latest user events.
|
||||
</p>
|
||||
|
||||
<h3>Inputs</h3>
|
||||
<dl class="message-properties">
|
||||
<dt class="optional">msg.userId | dynamic
|
||||
<span class="property-type">string</span>
|
||||
</dt>
|
||||
<dd> The user to get details for.</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.error <span class="property-type">undefined|object</span></dt>
|
||||
<dd>Returned if there was an error getting the user</dd>
|
||||
</dl>
|
||||
<dt class="optional">dynamic
|
||||
<span class="property-type">string|object</span>
|
||||
</dt>
|
||||
<dd> You configure what to return on the node.</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-get-user',{
|
||||
category: 'matrix',
|
||||
color: '#00b7ca',
|
||||
icon: "matrix.png",
|
||||
outputLabels: ["success", "error"],
|
||||
inputs:1,
|
||||
outputs:2,
|
||||
defaults: {
|
||||
name: { value: null },
|
||||
server: { type: "matrix-server-config" },
|
||||
userType: { value: "msg" },
|
||||
userValue: { value: "userId" },
|
||||
propertyType: { value: "msg" },
|
||||
propertyValue: { value: "user" },
|
||||
},
|
||||
oneditprepare: function() {
|
||||
$("#node-input-user").typedInput({
|
||||
type: this.roomType,
|
||||
types:['msg','flow','global','str'],
|
||||
})
|
||||
.typedInput('value', this.userValue)
|
||||
.typedInput('type', this.userType);
|
||||
|
||||
$("#node-input-property").typedInput({
|
||||
type: this.roomType,
|
||||
types:['msg','flow','global','str'],
|
||||
})
|
||||
.typedInput('value', this.propertyValue)
|
||||
.typedInput('type', this.propertyType);
|
||||
|
||||
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() {
|
||||
this.userType = $("#node-input-user").typedInput('type');
|
||||
this.userValue = $("#node-input-user").typedInput('value');
|
||||
this.propertyType = $("#node-input-property").typedInput('type');
|
||||
this.propertyValue = $("#node-input-property").typedInput('value');
|
||||
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 || "Get User";
|
||||
},
|
||||
paletteLabel: 'Get User'
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
@@ -0,0 +1,145 @@
|
||||
module.exports = function(RED) {
|
||||
function MatrixGetUser(n) {
|
||||
RED.nodes.createNode(this, n);
|
||||
|
||||
var node = this;
|
||||
|
||||
this.name = n.name;
|
||||
this.server = RED.nodes.getNode(n.server);
|
||||
this.userType = n.userType || "msg";
|
||||
this.userValue = n.userValue || "userId";
|
||||
this.propertyType = n.propertyType || "msg";
|
||||
this.propertyValue = n.propertyValue || "user";
|
||||
|
||||
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) {
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
function setToValue(value, type, property) {
|
||||
if(type === 'global' || type === 'flow') {
|
||||
var contextKey = RED.util.parseContextStore(property);
|
||||
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()[type];
|
||||
var callback = err => {
|
||||
if (err) {
|
||||
node.error(err, msg);
|
||||
getterErrors[rule.p] = err.message;
|
||||
}
|
||||
}
|
||||
target.set(contextKey.key, value, contextKey.store, callback);
|
||||
} else if(type === 'msg') {
|
||||
if (!RED.util.setMessageProperty(msg, property, value)) {
|
||||
node.warn(RED._("change.errors.no-override",{property:property}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
let userId = getToValue(msg, node.userType, node.userValue);
|
||||
if(!userId) {
|
||||
msg.error = "Missing userId";
|
||||
node.error(msg.error, msg);
|
||||
node.send([null, msg]);
|
||||
return;
|
||||
}
|
||||
|
||||
let user = null;
|
||||
try {
|
||||
user = node.server.matrixClient.getUser(userId);
|
||||
} catch(e) {
|
||||
msg.error = "Failed getting user: " + e.message;
|
||||
node.error(msg.error, msg);
|
||||
node.send([null, msg]);
|
||||
return;
|
||||
}
|
||||
|
||||
if(!user) {
|
||||
// failed to fetch from local storage, try to fetch data from server
|
||||
let user2 = {};
|
||||
|
||||
try {
|
||||
let profileInfo = node.server.matrixClient.getProfileInfo(userId);
|
||||
if(Object.keys(profileInfo).length > 0) {
|
||||
user2.displayName = profileInfo.displayname;
|
||||
user2.avatarUrl = profileInfo.avatar_url;
|
||||
}
|
||||
|
||||
let presence = node.server.matrixClient.getPresence(userId);
|
||||
if(Object.keys(presence).length > 0) {
|
||||
user2.currentlyActive = presence.currently_active;
|
||||
user2.lastActiveAgo = presence.last_active_ago;
|
||||
user2.presenceStatusMsg = presence.presence_status_msg;
|
||||
user2.presence = presence.presence;
|
||||
}
|
||||
|
||||
if(Object.keys(user2).length > 0) {
|
||||
setToValue(user2, node.propertyType, node.propertyValue);
|
||||
node.send([msg, null]);
|
||||
return;
|
||||
}
|
||||
} catch(e) {
|
||||
msg.error = "Failed getting user: " + e.message;
|
||||
node.error(msg.error, msg);
|
||||
node.send([null, msg]);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
setToValue(user, node.propertyType, node.propertyValue);
|
||||
node.send([msg, null]);
|
||||
});
|
||||
|
||||
node.on("close", function() {
|
||||
node.server.deregister(node);
|
||||
});
|
||||
}
|
||||
RED.nodes.registerType("matrix-get-user", MatrixGetUser);
|
||||
}
|
||||
@@ -0,0 +1,254 @@
|
||||
<script type="text/javascript">
|
||||
RED.nodes.registerType('matrix-mark-read',{
|
||||
category: 'matrix',
|
||||
color: '#00b7ca',
|
||||
icon: "matrix.png",
|
||||
outputLabels: ["message"],
|
||||
inputs: 1,
|
||||
outputs: 2,
|
||||
defaults: {
|
||||
name: { value: null },
|
||||
server: { type: "matrix-server-config" },
|
||||
roomType: { value: "msg" },
|
||||
roomValue: { value: "topic" },
|
||||
eventIdType: { value: "msg" },
|
||||
eventIdValue: { value: "eventId" }
|
||||
},
|
||||
label: function() {
|
||||
return this.name || "Mark Read";
|
||||
},
|
||||
paletteLabel: 'Mark Read',
|
||||
oneditprepare: function() {
|
||||
$("#node-input-room").typedInput({
|
||||
type: this.roomType,
|
||||
types:['msg','flow','global','str'],
|
||||
})
|
||||
.typedInput('value', this.roomValue)
|
||||
.typedInput('type', this.roomType);
|
||||
|
||||
$("#node-input-eventId").typedInput({
|
||||
types:['msg','flow','global','bool'],
|
||||
})
|
||||
.typedInput('value', this.eventIdValue)
|
||||
.typedInput('type', this.eventIdType);
|
||||
},
|
||||
oneditsave: function() {
|
||||
this.roomType = $("#node-input-room").typedInput('type');
|
||||
this.roomValue = $("#node-input-room").typedInput('value');
|
||||
this.eventIdType = $("#node-input-eventId").typedInput('type');
|
||||
this.eventIdValue = $("#node-input-eventId").typedInput('value');
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<script type="text/html" data-template-name="matrix-mark-read">
|
||||
<div class="form-row">
|
||||
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
|
||||
<input type="text" id="node-input-name" placeholder="Name">
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<label for="node-input-server"><i class="fa fa-server"></i> Matrix Server</label>
|
||||
<input type="text" id="node-input-server">
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<label for="node-input-room"><i class="fa fa-comments"></i> Room</label>
|
||||
<input type="text" id="node-input-room">
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<label for="node-input-eventId"><i class="fa fa-comments"></i> Event Id</label>
|
||||
<input type="text" id="node-input-eventId">
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script type="text/html" data-help-name="matrix-mark-read">
|
||||
<p>Mark an event in a room as read.</p>
|
||||
|
||||
<h3>Outputs</h3>
|
||||
<ul class="node-ports">
|
||||
<li>Always Returned
|
||||
<dl class="message-properties">
|
||||
<dt>msg.type <span class="property-type">string</span></dt>
|
||||
<dd>
|
||||
the message type. For example this will be either m.text, m.reaction, m.file, m.image, etc
|
||||
</dd>
|
||||
</dl>
|
||||
|
||||
<dl class="message-properties">
|
||||
<dt>msg.isDM <span class="property-type">bool</span></dt>
|
||||
<dd> returns true if message is from a direct message room.</dd>
|
||||
</dl>
|
||||
|
||||
<dl class="message-properties">
|
||||
<dt>msg.encrypted <span class="property-type">bool</span></dt>
|
||||
<dd> returns true if message was encrypted (e2ee).</dd>
|
||||
</dl>
|
||||
|
||||
<dl class="message-properties">
|
||||
<dt>msg.redacted <span class="property-type">bool</span></dt>
|
||||
<dd> returns true if the message was redacted (such as deleted by the user).</dd>
|
||||
</dl>
|
||||
|
||||
<dl class="message-properties">
|
||||
<dt>msg.payload <span class="property-type">string</span></dt>
|
||||
<dd>the body from the message's content.</dd>
|
||||
</dl>
|
||||
|
||||
<dl class="message-properties">
|
||||
<dt>msg.userId <span class="property-type">string</span></dt>
|
||||
<dd>the User ID of the message sender. Example: @john:matrix.org</dd>
|
||||
</dl>
|
||||
|
||||
<dl class="message-properties">
|
||||
<dt>msg.topic <span class="property-type">string</span></dt>
|
||||
<dd>the ID of the room. Example: !OGEhHVWSdvArJzumhm:matrix.org</dd>
|
||||
</dl>
|
||||
|
||||
<dl class="message-properties">
|
||||
<dt>msg.event <span class="property-type">object</span></dt>
|
||||
<dd>the event object returned by the Matrix server</dd>
|
||||
</dl>
|
||||
|
||||
<dl class="message-properties">
|
||||
<dt>msg.eventId <span class="property-type">object</span></dt>
|
||||
<dd>The event ID, e.g. $143350589368169JsLZx:localhost</dd>
|
||||
</dl>
|
||||
|
||||
<dl class="message-properties">
|
||||
<dt>msg.content <span class="property-type">object</span></dt>
|
||||
<dd>the message's content object</dd>
|
||||
</dl>
|
||||
</li>
|
||||
|
||||
<li><code>msg.type</code> == '<strong>m.text</strong>'
|
||||
<div class="form-tips" style="margin-bottom: 12px;">
|
||||
Doesn't return anything extra
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li><code>msg.type</code> == '<strong>m.reaction</strong>'
|
||||
<dl class="message-properties">
|
||||
<dt>msg.referenceEventId <span class="property-type">string</span></dt>
|
||||
<dd>the message that the reaction relates to</dd>
|
||||
</dl>
|
||||
|
||||
<dl class="message-properties">
|
||||
<dt>msg.payload <span class="property-type">string</span></dt>
|
||||
<dd>the key of the reaction's content</dd>
|
||||
</dl>
|
||||
</li>
|
||||
|
||||
<li><code>msg.type</code> == '<strong>m.emote</strong>'
|
||||
<div class="form-tips" style="margin-bottom: 12px;">
|
||||
Doesn't return anything extra
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li><code>msg.type</code> == '<strong>m.sticker</strong>'
|
||||
<dl class="message-properties">
|
||||
<dt>msg.url <span class="property-type">string</span></dt>
|
||||
<dd>URL to the sticker image</dd>
|
||||
</dl>
|
||||
|
||||
<dl class="message-properties">
|
||||
<dt>msg.mxc_url <span class="property-type">string</span></dt>
|
||||
<dd>Matrix URL to the sticker image</dd>
|
||||
</dl>
|
||||
|
||||
<dl class="message-properties">
|
||||
<dt>msg.thumbnail_url <span class="property-type">string</span></dt>
|
||||
<dd>URL to the thumbnail of the sticker</dd>
|
||||
</dl>
|
||||
|
||||
<dl class="message-properties">
|
||||
<dt>msg.thumbnail_mxc_url <span class="property-type">string</span></dt>
|
||||
<dd>Matrix URL to the thumbnail of the sticker</dd>
|
||||
</dl>
|
||||
</li>
|
||||
|
||||
<li><code>msg.type</code> == '<strong>m.file</strong>'
|
||||
<dl class="message-properties">
|
||||
<dt>msg.filename <span class="property-type">string</span></dt>
|
||||
<dd>the file's parsed filename</dd>
|
||||
</dl>
|
||||
|
||||
<dl class="message-properties">
|
||||
<dt>msg.url <span class="property-type">string</span></dt>
|
||||
<dd>the file's URL</dd>
|
||||
</dl>
|
||||
|
||||
<dl class="message-properties">
|
||||
<dt>msg.mxc_url <span class="property-type">string</span></dt>
|
||||
<dd>the file's Matrix URL</dd>
|
||||
</dl>
|
||||
</li>
|
||||
|
||||
<li><code>msg.type</code> == '<strong>m.audio</strong>'
|
||||
<dl class="message-properties">
|
||||
<dt>msg.filename <span class="property-type">string</span></dt>
|
||||
<dd>the image's parsed filename</dd>
|
||||
</dl>
|
||||
|
||||
<dl class="message-properties">
|
||||
<dt>msg.mimetype <span class="property-type">string</span></dt>
|
||||
<dd>audio file mimetype (ex: audio/ogg)</dd>
|
||||
</dl>
|
||||
|
||||
<dl class="message-properties">
|
||||
<dt>msg.url <span class="property-type">string</span></dt>
|
||||
<dd>the file's URL</dd>
|
||||
</dl>
|
||||
|
||||
<dl class="message-properties">
|
||||
<dt>msg.mxc_url <span class="property-type">string</span></dt>
|
||||
<dd>the file's Matrix URL</dd>
|
||||
</dl>
|
||||
|
||||
<dl class="message-properties">
|
||||
<dt>msg.duration <span class="property-type">integer</span></dt>
|
||||
<dd>duration of audio file in milliseconds</dd>
|
||||
</dl>
|
||||
|
||||
<dl class="message-properties">
|
||||
<dt>msg.waveform <span class="property-type">array[int]</span></dt>
|
||||
<dd>waveform of the audio clip</dd>
|
||||
</dl>
|
||||
</li>
|
||||
|
||||
<li><code>msg.type</code> == '<strong>m.image</strong>'
|
||||
<dl class="message-properties">
|
||||
<dt>msg.filename <span class="property-type">string</span></dt>
|
||||
<dd>the image's parsed filename</dd>
|
||||
</dl>
|
||||
|
||||
<dl class="message-properties">
|
||||
<dt>msg.url <span class="property-type">string</span></dt>
|
||||
<dd>the image's URL</dd>
|
||||
</dl>
|
||||
|
||||
<dl class="message-properties">
|
||||
<dt>msg.mxc_url <span class="property-type">string</span></dt>
|
||||
<dd>the image's Matrix URL</dd>
|
||||
</dl>
|
||||
|
||||
<dl class="message-properties">
|
||||
<dt>msg.thumbnail_url <span class="property-type">string</span></dt>
|
||||
<dd>the image's thumbnail URL</dd>
|
||||
</dl>
|
||||
|
||||
<dl class="message-properties">
|
||||
<dt>msg.thumbnail_mxc_url <span class="property-type">string</span></dt>
|
||||
<dd>the image's thumbnail Matrix URL</dd>
|
||||
</dl>
|
||||
</li>
|
||||
|
||||
<li><code>msg.type</code> == '<strong>m.location</strong>'
|
||||
<dl class="message-properties">
|
||||
<dt>msg.geo_uri <span class="property-type">string</span></dt>
|
||||
<dd>URI format of the geolocation</dd>
|
||||
</dl>
|
||||
</li>
|
||||
</ul>
|
||||
</script>
|
||||
@@ -0,0 +1,73 @@
|
||||
const {TimelineWindow, RelationType, Filter} = require("matrix-js-sdk");
|
||||
const crypto = require('crypto');
|
||||
module.exports = function(RED) {
|
||||
function MatrixReceiveMessage(n) {
|
||||
RED.nodes.createNode(this, n);
|
||||
|
||||
let node = this;
|
||||
this.name = n.name;
|
||||
this.server = RED.nodes.getNode(n.server);
|
||||
this.roomType = n.roomType;
|
||||
this.roomValue = n.roomValue;
|
||||
this.eventIdType = n.eventIdType;
|
||||
this.eventIdValue = n.eventIdValue;
|
||||
|
||||
node.status({ fill: "red", shape: "ring", text: "disconnected" });
|
||||
|
||||
if (!node.server) {
|
||||
node.error("No configuration node", {});
|
||||
return;
|
||||
}
|
||||
node.server.register(node);
|
||||
|
||||
node.server.on("disconnected", function(){
|
||||
node.status({ fill: "red", shape: "ring", text: "disconnected" });
|
||||
});
|
||||
|
||||
node.server.on("connected", function() {
|
||||
node.status({ fill: "green", shape: "ring", text: "connected" });
|
||||
});
|
||||
|
||||
node.on("input", async function (msg) {
|
||||
if (! node.server || ! node.server.matrixClient) {
|
||||
node.error("No matrix server selected", msg);
|
||||
return;
|
||||
}
|
||||
|
||||
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),
|
||||
eventId = getToValue(msg, node.eventIdType, node.eventIdValue);
|
||||
|
||||
msg.payload = await node.server.matrixClient.setRoomReadMarkers(roomId, eventId);
|
||||
node.send([msg, null]);
|
||||
} catch(e) {
|
||||
msg.error = `Room pagination error: ${e}`;
|
||||
node.error(msg.error, msg);
|
||||
node.send([null, msg]);
|
||||
}
|
||||
});
|
||||
|
||||
node.on("close", function() {
|
||||
node.server.deregister(node);
|
||||
});
|
||||
}
|
||||
RED.nodes.registerType("matrix-mark-read", MatrixReceiveMessage);
|
||||
}
|
||||
@@ -0,0 +1,284 @@
|
||||
<script type="text/javascript">
|
||||
RED.nodes.registerType('matrix-paginate-room',{
|
||||
category: 'matrix',
|
||||
color: '#00b7ca',
|
||||
icon: "matrix.png",
|
||||
outputLabels: ["message"],
|
||||
inputs: 1,
|
||||
outputs: 2,
|
||||
defaults: {
|
||||
name: { value: null },
|
||||
server: { type: "matrix-server-config" },
|
||||
roomType: { value: "msg" },
|
||||
roomValue: { value: "topic" },
|
||||
paginateKeyType: { value: "msg" },
|
||||
paginateKeyValue: { value: "paginationKey" },
|
||||
paginateBackwardsType: { value: "bool" },
|
||||
paginateBackwardsValue: { value: "true" },
|
||||
pageSizeType: { value: "num" },
|
||||
pageSizeValue: { value: "25" }
|
||||
},
|
||||
label: function() {
|
||||
return this.name || "Paginate Room";
|
||||
},
|
||||
paletteLabel: 'Paginate Room',
|
||||
oneditprepare: function() {
|
||||
$("#node-input-room").typedInput({
|
||||
type: this.roomType,
|
||||
types:['msg','flow','global','str'],
|
||||
})
|
||||
.typedInput('value', this.roomValue)
|
||||
.typedInput('type', this.roomType);
|
||||
|
||||
$("#node-input-paginateBackwards").typedInput({
|
||||
types:['msg','flow','global','bool'],
|
||||
})
|
||||
.typedInput('value', this.paginateBackwardsValue)
|
||||
.typedInput('type', this.paginateBackwardsType);
|
||||
|
||||
$("#node-input-paginateKey").typedInput({
|
||||
types:['msg','flow','global'],
|
||||
})
|
||||
.typedInput('value', this.paginateKeyValue)
|
||||
.typedInput('type', this.paginateKeyType);
|
||||
|
||||
$("#node-input-pageSize").typedInput({
|
||||
types:['msg','flow','global','num'],
|
||||
})
|
||||
.typedInput('value', this.pageSizeValue)
|
||||
.typedInput('type', this.pageSizeType);
|
||||
},
|
||||
oneditsave: function() {
|
||||
this.roomType = $("#node-input-room").typedInput('type');
|
||||
this.roomValue = $("#node-input-room").typedInput('value');
|
||||
this.paginateBackwardsType = $("#node-input-paginateBackwards").typedInput('type');
|
||||
this.paginateBackwardsValue = $("#node-input-paginateBackwards").typedInput('value');
|
||||
this.paginateKeyType = $("#node-input-paginateKey").typedInput('type');
|
||||
this.paginateKeyValue = $("#node-input-paginateKey").typedInput('value');
|
||||
this.pageSizeType = $("#node-input-pageSize").typedInput('type');
|
||||
this.pageSizeValue = $("#node-input-pageSize").typedInput('value');
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<script type="text/html" data-template-name="matrix-paginate-room">
|
||||
<div class="form-row">
|
||||
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
|
||||
<input type="text" id="node-input-name" placeholder="Name">
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<label for="node-input-server"><i class="fa fa-server"></i> Matrix Server</label>
|
||||
<input type="text" id="node-input-server">
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<label for="node-input-room"><i class="fa fa-comments"></i> Room</label>
|
||||
<input type="text" id="node-input-room">
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<label for="node-input-paginateKey"><i class="fa fa-comments"></i> Pagination Key</label>
|
||||
<input type="text" id="node-input-paginateKey">
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<label for="node-input-paginateBackwards"><i class="fa fa-commenting-o"></i> Paginate Backwards</label>
|
||||
<input type="text" id="node-input-paginateBackwards">
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<label for="node-input-pageSize"><i class="fa fa-commenting-o"></i> Page Size</label>
|
||||
<input type="text" id="node-input-pageSize">
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script type="text/html" data-help-name="matrix-paginate-room">
|
||||
<p>Receive events from Matrix.</p>
|
||||
|
||||
<h3>Outputs</h3>
|
||||
<ul class="node-ports">
|
||||
<li>Always Returned
|
||||
<dl class="message-properties">
|
||||
<dt>msg.type <span class="property-type">string</span></dt>
|
||||
<dd>
|
||||
the message type. For example this will be either m.text, m.reaction, m.file, m.image, etc
|
||||
</dd>
|
||||
</dl>
|
||||
|
||||
<dl class="message-properties">
|
||||
<dt>msg.isDM <span class="property-type">bool</span></dt>
|
||||
<dd> returns true if message is from a direct message room.</dd>
|
||||
</dl>
|
||||
|
||||
<dl class="message-properties">
|
||||
<dt>msg.encrypted <span class="property-type">bool</span></dt>
|
||||
<dd> returns true if message was encrypted (e2ee).</dd>
|
||||
</dl>
|
||||
|
||||
<dl class="message-properties">
|
||||
<dt>msg.redacted <span class="property-type">bool</span></dt>
|
||||
<dd> returns true if the message was redacted (such as deleted by the user).</dd>
|
||||
</dl>
|
||||
|
||||
<dl class="message-properties">
|
||||
<dt>msg.payload <span class="property-type">string</span></dt>
|
||||
<dd>the body from the message's content.</dd>
|
||||
</dl>
|
||||
|
||||
<dl class="message-properties">
|
||||
<dt>msg.userId <span class="property-type">string</span></dt>
|
||||
<dd>the User ID of the message sender. Example: @john:matrix.org</dd>
|
||||
</dl>
|
||||
|
||||
<dl class="message-properties">
|
||||
<dt>msg.topic <span class="property-type">string</span></dt>
|
||||
<dd>the ID of the room. Example: !OGEhHVWSdvArJzumhm:matrix.org</dd>
|
||||
</dl>
|
||||
|
||||
<dl class="message-properties">
|
||||
<dt>msg.event <span class="property-type">object</span></dt>
|
||||
<dd>the event object returned by the Matrix server</dd>
|
||||
</dl>
|
||||
|
||||
<dl class="message-properties">
|
||||
<dt>msg.eventId <span class="property-type">object</span></dt>
|
||||
<dd>The event ID, e.g. $143350589368169JsLZx:localhost</dd>
|
||||
</dl>
|
||||
|
||||
<dl class="message-properties">
|
||||
<dt>msg.content <span class="property-type">object</span></dt>
|
||||
<dd>the message's content object</dd>
|
||||
</dl>
|
||||
</li>
|
||||
|
||||
<li><code>msg.type</code> == '<strong>m.text</strong>'
|
||||
<div class="form-tips" style="margin-bottom: 12px;">
|
||||
Doesn't return anything extra
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li><code>msg.type</code> == '<strong>m.reaction</strong>'
|
||||
<dl class="message-properties">
|
||||
<dt>msg.referenceEventId <span class="property-type">string</span></dt>
|
||||
<dd>the message that the reaction relates to</dd>
|
||||
</dl>
|
||||
|
||||
<dl class="message-properties">
|
||||
<dt>msg.payload <span class="property-type">string</span></dt>
|
||||
<dd>the key of the reaction's content</dd>
|
||||
</dl>
|
||||
</li>
|
||||
|
||||
<li><code>msg.type</code> == '<strong>m.emote</strong>'
|
||||
<div class="form-tips" style="margin-bottom: 12px;">
|
||||
Doesn't return anything extra
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li><code>msg.type</code> == '<strong>m.sticker</strong>'
|
||||
<dl class="message-properties">
|
||||
<dt>msg.url <span class="property-type">string</span></dt>
|
||||
<dd>URL to the sticker image</dd>
|
||||
</dl>
|
||||
|
||||
<dl class="message-properties">
|
||||
<dt>msg.mxc_url <span class="property-type">string</span></dt>
|
||||
<dd>Matrix URL to the sticker image</dd>
|
||||
</dl>
|
||||
|
||||
<dl class="message-properties">
|
||||
<dt>msg.thumbnail_url <span class="property-type">string</span></dt>
|
||||
<dd>URL to the thumbnail of the sticker</dd>
|
||||
</dl>
|
||||
|
||||
<dl class="message-properties">
|
||||
<dt>msg.thumbnail_mxc_url <span class="property-type">string</span></dt>
|
||||
<dd>Matrix URL to the thumbnail of the sticker</dd>
|
||||
</dl>
|
||||
</li>
|
||||
|
||||
<li><code>msg.type</code> == '<strong>m.file</strong>'
|
||||
<dl class="message-properties">
|
||||
<dt>msg.filename <span class="property-type">string</span></dt>
|
||||
<dd>the file's parsed filename</dd>
|
||||
</dl>
|
||||
|
||||
<dl class="message-properties">
|
||||
<dt>msg.url <span class="property-type">string</span></dt>
|
||||
<dd>the file's URL</dd>
|
||||
</dl>
|
||||
|
||||
<dl class="message-properties">
|
||||
<dt>msg.mxc_url <span class="property-type">string</span></dt>
|
||||
<dd>the file's Matrix URL</dd>
|
||||
</dl>
|
||||
</li>
|
||||
|
||||
<li><code>msg.type</code> == '<strong>m.audio</strong>'
|
||||
<dl class="message-properties">
|
||||
<dt>msg.filename <span class="property-type">string</span></dt>
|
||||
<dd>the image's parsed filename</dd>
|
||||
</dl>
|
||||
|
||||
<dl class="message-properties">
|
||||
<dt>msg.mimetype <span class="property-type">string</span></dt>
|
||||
<dd>audio file mimetype (ex: audio/ogg)</dd>
|
||||
</dl>
|
||||
|
||||
<dl class="message-properties">
|
||||
<dt>msg.url <span class="property-type">string</span></dt>
|
||||
<dd>the file's URL</dd>
|
||||
</dl>
|
||||
|
||||
<dl class="message-properties">
|
||||
<dt>msg.mxc_url <span class="property-type">string</span></dt>
|
||||
<dd>the file's Matrix URL</dd>
|
||||
</dl>
|
||||
|
||||
<dl class="message-properties">
|
||||
<dt>msg.duration <span class="property-type">integer</span></dt>
|
||||
<dd>duration of audio file in milliseconds</dd>
|
||||
</dl>
|
||||
|
||||
<dl class="message-properties">
|
||||
<dt>msg.waveform <span class="property-type">array[int]</span></dt>
|
||||
<dd>waveform of the audio clip</dd>
|
||||
</dl>
|
||||
</li>
|
||||
|
||||
<li><code>msg.type</code> == '<strong>m.image</strong>'
|
||||
<dl class="message-properties">
|
||||
<dt>msg.filename <span class="property-type">string</span></dt>
|
||||
<dd>the image's parsed filename</dd>
|
||||
</dl>
|
||||
|
||||
<dl class="message-properties">
|
||||
<dt>msg.url <span class="property-type">string</span></dt>
|
||||
<dd>the image's URL</dd>
|
||||
</dl>
|
||||
|
||||
<dl class="message-properties">
|
||||
<dt>msg.mxc_url <span class="property-type">string</span></dt>
|
||||
<dd>the image's Matrix URL</dd>
|
||||
</dl>
|
||||
|
||||
<dl class="message-properties">
|
||||
<dt>msg.thumbnail_url <span class="property-type">string</span></dt>
|
||||
<dd>the image's thumbnail URL</dd>
|
||||
</dl>
|
||||
|
||||
<dl class="message-properties">
|
||||
<dt>msg.thumbnail_mxc_url <span class="property-type">string</span></dt>
|
||||
<dd>the image's thumbnail Matrix URL</dd>
|
||||
</dl>
|
||||
</li>
|
||||
|
||||
<li><code>msg.type</code> == '<strong>m.location</strong>'
|
||||
<dl class="message-properties">
|
||||
<dt>msg.geo_uri <span class="property-type">string</span></dt>
|
||||
<dd>URI format of the geolocation</dd>
|
||||
</dl>
|
||||
</li>
|
||||
</ul>
|
||||
</script>
|
||||
@@ -0,0 +1,155 @@
|
||||
const {TimelineWindow, RelationType, Filter} = require("matrix-js-sdk");
|
||||
const crypto = require('crypto');
|
||||
module.exports = function(RED) {
|
||||
function MatrixReceiveMessage(n) {
|
||||
RED.nodes.createNode(this, n);
|
||||
|
||||
let node = this;
|
||||
this.name = n.name;
|
||||
this.server = RED.nodes.getNode(n.server);
|
||||
this.roomType = n.roomType;
|
||||
this.roomValue = n.roomValue;
|
||||
this.paginateBackwardsType = n.paginateBackwardsType;
|
||||
this.paginateBackwardsValue = n.paginateBackwardsValue;
|
||||
this.paginateKeyType = n.paginateKeyType;
|
||||
this.paginateKeyValue = n.paginateKeyValue;
|
||||
this.pageSizeType = n.pageSizeType;
|
||||
this.pageSizeValue = n.pageSizeValue;
|
||||
this.timelineWindows = new Map();
|
||||
|
||||
node.status({ fill: "red", shape: "ring", text: "disconnected" });
|
||||
|
||||
if (!node.server) {
|
||||
node.error("No configuration node", {});
|
||||
return;
|
||||
}
|
||||
node.server.register(node);
|
||||
|
||||
node.server.on("disconnected", function(){
|
||||
node.status({ fill: "red", shape: "ring", text: "disconnected" });
|
||||
});
|
||||
|
||||
node.server.on("connected", function() {
|
||||
node.status({ fill: "green", shape: "ring", text: "connected" });
|
||||
});
|
||||
|
||||
node.on("input", async function (msg) {
|
||||
if (! node.server || ! node.server.matrixClient) {
|
||||
node.error("No matrix server selected", msg);
|
||||
return;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
function setToValue(value, type, property) {
|
||||
if(type === 'global' || type === 'flow') {
|
||||
var contextKey = RED.util.parseContextStore(property);
|
||||
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()[type];
|
||||
var callback = err => {
|
||||
if (err) {
|
||||
node.error(err, msg);
|
||||
getterErrors[rule.p] = err.message;
|
||||
}
|
||||
}
|
||||
target.set(contextKey.key, value, contextKey.store, callback);
|
||||
} else if(type === 'msg') {
|
||||
if (!RED.util.setMessageProperty(msg, property, value)) {
|
||||
node.warn(RED._("change.errors.no-override",{property:property}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
let roomId = getToValue(msg, node.roomType, node.roomValue),
|
||||
paginateBackwards = getToValue(msg, node.paginateBackwardsType, node.paginateBackwardsValue),
|
||||
pageSize = getToValue(msg, node.pageSizeType, node.pageSizeValue),
|
||||
pageKey = getToValue(msg, node.paginateKeyType, node.paginateKeyValue);
|
||||
|
||||
let room = node.server.matrixClient.getRoom(roomId);
|
||||
|
||||
if(!room) {
|
||||
throw new Error(`Room ${roomId} does not exist`);
|
||||
}
|
||||
if(pageSize > node.server.initialSyncLimit) {
|
||||
throw new Error(`Page size=${pageSize} cannot exceed initialSyncLimit=${node.server.initialSyncLimit}`);
|
||||
}
|
||||
if(!pageKey) {
|
||||
pageKey = crypto.randomUUID();
|
||||
setToValue(pageKey, node.paginateKeyType, node.paginateKeyValue);
|
||||
}
|
||||
let timelineWindow = node.timelineWindows.get(pageKey),
|
||||
moreMessages = true;
|
||||
if(!timelineWindow) {
|
||||
let timelineSet = room.getUnfilteredTimelineSet();
|
||||
node.debug(JSON.stringify(timelineSet.getFilter()));
|
||||
// MatrixClient's option initialSyncLimit gets set to the filter we are using
|
||||
// so override that value with our pageSize
|
||||
timelineWindow = new TimelineWindow(node.server.matrixClient, timelineSet);
|
||||
await timelineWindow.load(msg.eventId || null, pageSize);
|
||||
node.timelineWindows.set(pageKey, timelineWindow);
|
||||
} else {
|
||||
moreMessages = await timelineWindow.paginate(paginateBackwards ? 'b' : 'f', pageSize); // b for backwards f for forwards
|
||||
if(moreMessages) {
|
||||
await timelineWindow.unpaginate(pageSize, !paginateBackwards);
|
||||
}
|
||||
}
|
||||
|
||||
// MatrixEvent objects are massive so this throws an encode error for the string being too long
|
||||
// since msg objects convert to JSON
|
||||
// msg.payload = moreMessages ? timelineWindow.getEvents() : false;
|
||||
|
||||
msg.payload = false;
|
||||
msg.start = timelineWindow.getTimelineIndex('b')?.index;
|
||||
msg.end = timelineWindow.getTimelineIndex('f')?.index;
|
||||
if(moreMessages) {
|
||||
msg.payload = timelineWindow.getEvents().map(function(event) {
|
||||
return {
|
||||
encrypted : event.isEncrypted(),
|
||||
redacted : event.isRedacted(),
|
||||
content : event.getContent(),
|
||||
type : (event.getContent()['msgtype'] || event.getType()) || null,
|
||||
payload : (event.getContent()['body'] || event.getContent()) || null,
|
||||
isThread : event.getContent()?.['m.relates_to']?.rel_type === RelationType.Thread,
|
||||
mentions : event.getContent()["m.mentions"] || null,
|
||||
userId : event.getSender(),
|
||||
// user : node.matrixClient.getUser(event.getSender()),
|
||||
topic : event.getRoomId(),
|
||||
eventId : event.getId(),
|
||||
event : event,
|
||||
};
|
||||
});
|
||||
}
|
||||
node.send([msg, null]);
|
||||
} catch(e) {
|
||||
msg.error = `Room pagination error: ${e}`;
|
||||
node.error(msg.error, msg);
|
||||
node.send([null, msg]);
|
||||
}
|
||||
});
|
||||
|
||||
node.on("close", function() {
|
||||
node.server.deregister(node);
|
||||
});
|
||||
}
|
||||
RED.nodes.registerType("matrix-paginate-room", MatrixReceiveMessage);
|
||||
}
|
||||
@@ -10,6 +10,7 @@
|
||||
name: { value: null },
|
||||
server: { type: "matrix-server-config" },
|
||||
roomId: {"value": null},
|
||||
acceptOwnEvents: {"value": false},
|
||||
acceptText: {"value": true},
|
||||
acceptEmotes: {"value": true},
|
||||
acceptStickers: {"value": true},
|
||||
@@ -45,6 +46,16 @@
|
||||
<div class="form-row" style="margin-left: 100px;margin-top:10px;font-weight:bold;">
|
||||
Timeline event filters
|
||||
</div>
|
||||
<div class="form-row" style="margin-bottom:0;">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="node-input-acceptOwnEvents"
|
||||
style="width: auto; margin-left: 125px; vertical-align: top"
|
||||
/>
|
||||
<label for="node-input-acceptOwnEvents" style="width: auto">
|
||||
Receive own events
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-row" style="margin-bottom:0;">
|
||||
<input
|
||||
type="checkbox"
|
||||
|
||||
+45
-65
@@ -7,6 +7,7 @@ module.exports = function(RED) {
|
||||
|
||||
this.name = n.name;
|
||||
this.server = RED.nodes.getNode(n.server);
|
||||
this.acceptOwnEvents = n.acceptOwnEvents;
|
||||
this.acceptText = n.acceptText;
|
||||
this.acceptEmotes = n.acceptEmotes;
|
||||
this.acceptStickers = n.acceptStickers;
|
||||
@@ -17,7 +18,7 @@ module.exports = function(RED) {
|
||||
this.acceptVideos = n.acceptVideos;
|
||||
this.acceptLocations = n.acceptLocations;
|
||||
this.roomId = n.roomId;
|
||||
this.roomIds = this.roomId ? this.roomId.split(',') : [];
|
||||
this.roomIds = this.roomId ? this.roomId.split(',').map(s => s.trim()) : [];
|
||||
|
||||
node.status({ fill: "red", shape: "ring", text: "disconnected" });
|
||||
|
||||
@@ -37,115 +38,94 @@ module.exports = function(RED) {
|
||||
|
||||
node.server.on("Room.timeline", async function(event, room, toStartOfTimeline, removed, data, msg) {
|
||||
// if node has a room ID set we only listen on that room
|
||||
if(node.roomIds.length && node.roomIds.indexOf(room.roomId) === -1) {
|
||||
if (node.roomIds.length && !node.roomIds.includes(room.roomId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch(msg.type) {
|
||||
if (!node.acceptOwnEvents && (!event.getSender() || event.getSender().toLowerCase() === node.server.matrixClient.getUserId().toLowerCase())) {
|
||||
node.log(`Ignoring${msg.encrypted ? ' encrypted' : ''} timeline event [${msg.type}]: (${room.name}) ${event.getId()} for reason: own event`);
|
||||
return;
|
||||
}
|
||||
|
||||
const setUrls = (urlKey, encryptedKey) => {
|
||||
const url = msg.encrypted ? msg.content[encryptedKey]?.url : msg.content[urlKey];
|
||||
if (url) {
|
||||
msg.url = node.server.matrixClient.mxcUrlToHttp(url);
|
||||
msg.mxc_url = url;
|
||||
}
|
||||
};
|
||||
|
||||
const setThumbnailUrls = (infoKey) => {
|
||||
const thumbnailFile = msg.content.info?.[infoKey];
|
||||
const thumbnailUrl = thumbnailFile?.url;
|
||||
if (thumbnailUrl) {
|
||||
msg.thumbnail_url = node.server.matrixClient.mxcUrlToHttp(thumbnailUrl);
|
||||
msg.thumbnail_mxc_url = thumbnailUrl;
|
||||
}
|
||||
};
|
||||
|
||||
switch (msg.type) {
|
||||
case 'm.emote':
|
||||
if(!node.acceptEmotes) return;
|
||||
if (!node.acceptEmotes) return;
|
||||
break;
|
||||
|
||||
case 'm.text':
|
||||
if(!node.acceptText) return;
|
||||
if (!node.acceptText) return;
|
||||
break;
|
||||
|
||||
case 'm.sticker':
|
||||
if(!node.acceptStickers) return;
|
||||
if(msg.content.info) {
|
||||
if(msg.content.info.thumbnail_url) {
|
||||
msg.thumbnail_url = node.server.matrixClient.mxcUrlToHttp(msg.content.info.thumbnail_url);
|
||||
msg.thumbnail_mxc_url = msg.content.info.thumbnail_url;
|
||||
}
|
||||
|
||||
if(msg.content.url) {
|
||||
msg.url = node.server.matrixClient.mxcUrlToHttp(msg.content.url);
|
||||
msg.mxc_url = msg.content.url;
|
||||
}
|
||||
}
|
||||
if (!node.acceptStickers) return;
|
||||
setThumbnailUrls('thumbnail_url');
|
||||
setUrls('url', 'url');
|
||||
break;
|
||||
|
||||
case 'm.file':
|
||||
if(!node.acceptFiles) return;
|
||||
if (!node.acceptFiles) return;
|
||||
msg.filename = msg.content.filename || msg.content.body;
|
||||
if(msg.encrypted) {
|
||||
msg.url = node.server.matrixClient.mxcUrlToHttp(msg.content.file.url);
|
||||
msg.mxc_url = msg.content.file.url;
|
||||
} else {
|
||||
msg.url = node.server.matrixClient.mxcUrlToHttp(msg.content.url);
|
||||
msg.mxc_url = msg.content.url;
|
||||
}
|
||||
setUrls('url', 'file');
|
||||
break;
|
||||
|
||||
case 'm.audio':
|
||||
if(!node.acceptAudio) return;
|
||||
if(msg.encrypted) {
|
||||
msg.url = node.server.matrixClient.mxcUrlToHttp(msg.content.file.url);
|
||||
msg.mxc_url = msg.content.file.url;
|
||||
} else {
|
||||
msg.url = node.server.matrixClient.mxcUrlToHttp(msg.content.url);
|
||||
msg.mxc_url = msg.content.url;
|
||||
}
|
||||
|
||||
if('org.matrix.msc1767.file' in msg.content) {
|
||||
if (!node.acceptAudio) return;
|
||||
setUrls('url', 'file');
|
||||
if ('org.matrix.msc1767.file' in msg.content) {
|
||||
msg.filename = msg.content['org.matrix.msc1767.file'].name;
|
||||
msg.mimetype = msg.content['org.matrix.msc1767.file'].mimetype;
|
||||
}
|
||||
|
||||
if('org.matrix.msc1767.audio' in msg.content) {
|
||||
if ('org.matrix.msc1767.audio' in msg.content) {
|
||||
msg.duration = msg.content['org.matrix.msc1767.audio'].duration;
|
||||
msg.waveform = msg.content['org.matrix.msc1767.audio'].waveform;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'm.image':
|
||||
if(!node.acceptImages) return;
|
||||
if (!node.acceptImages) return;
|
||||
msg.filename = msg.content.filename || msg.content.body;
|
||||
if(msg.encrypted) {
|
||||
msg.url = node.server.matrixClient.mxcUrlToHttp(msg.content.file.url);
|
||||
msg.mxc_url = msg.content.file.url;
|
||||
msg.thumbnail_url = node.server.matrixClient.mxcUrlToHttp(msg.content.info.thumbnail_file.url);
|
||||
msg.thumbnail_mxc_url = msg.content.info.thumbnail_file.url;
|
||||
} else {
|
||||
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;
|
||||
}
|
||||
setUrls('url', 'file');
|
||||
setThumbnailUrls('thumbnail_file');
|
||||
break;
|
||||
|
||||
|
||||
case 'm.video':
|
||||
if(!node.acceptVideos) return;
|
||||
if (!node.acceptVideos) return;
|
||||
msg.filename = msg.content.filename || msg.content.body;
|
||||
if(msg.encrypted) {
|
||||
msg.url = node.server.matrixClient.mxcUrlToHttp(msg.content.file.url);
|
||||
msg.mxc_url = msg.content.file.url;
|
||||
msg.thumbnail_url = node.server.matrixClient.mxcUrlToHttp(msg.content.info.thumbnail_file.url);
|
||||
msg.thumbnail_mxc_url = msg.content.info.thumbnail_file.url;
|
||||
} else {
|
||||
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;
|
||||
}
|
||||
setUrls('url', 'file');
|
||||
setThumbnailUrls('thumbnail_file');
|
||||
break;
|
||||
|
||||
case 'm.location':
|
||||
if(!node.acceptLocations) return;
|
||||
if (!node.acceptLocations) return;
|
||||
msg.geo_uri = msg.content.geo_uri;
|
||||
msg.payload = msg.content.body;
|
||||
break;
|
||||
|
||||
case 'm.reaction':
|
||||
if(!node.acceptReactions) return;
|
||||
if (!node.acceptReactions) 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;
|
||||
|
||||
default:
|
||||
// node.warn("Unknown event type: " + msg.type);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -8,9 +8,8 @@
|
||||
<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>
|
||||
<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" style="margin-bottom:0;">
|
||||
<label><i class="fa fa-list"></i> <span data-i18n="change.label.rules"></span></label>
|
||||
@@ -18,18 +17,6 @@
|
||||
<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">
|
||||
@@ -129,8 +116,8 @@
|
||||
defaults: {
|
||||
name: { value: null },
|
||||
server: { type: "matrix-server-config" },
|
||||
roomId: { value: null },
|
||||
reason: { value: null },
|
||||
roomType: { value: "msg" },
|
||||
roomValue: { value: "topic" },
|
||||
rules: {
|
||||
value: defaultRules,
|
||||
validate: function(rules, opt) {
|
||||
@@ -154,6 +141,13 @@
|
||||
},
|
||||
},
|
||||
oneditprepare: function() {
|
||||
$("#node-input-room").typedInput({
|
||||
type: this.roomType,
|
||||
types:['msg','flow','global','str'],
|
||||
})
|
||||
.typedInput('value', this.roomValue)
|
||||
.typedInput('type', this.roomType);
|
||||
|
||||
var set = "Set";
|
||||
var to = "to the value";
|
||||
var toValueLabel = "to the property";
|
||||
@@ -333,6 +327,9 @@
|
||||
}
|
||||
},
|
||||
oneditsave: function() {
|
||||
this.roomType = $("#node-input-room").typedInput('type');
|
||||
this.roomValue = $("#node-input-room").typedInput('value');
|
||||
|
||||
var rules = $("#node-input-rule-container").editableList('items');
|
||||
var node = this;
|
||||
node.rules= [];
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
module.exports = function(RED) {
|
||||
function MatrixRoomSettings(n) {
|
||||
function MatrixRoomStateEvents(n) {
|
||||
RED.nodes.createNode(this, n);
|
||||
|
||||
var node = this;
|
||||
@@ -265,6 +265,7 @@ module.exports = function(RED) {
|
||||
}
|
||||
}
|
||||
setToValue(value, rule);
|
||||
cachedGetters[rule.p] = value;
|
||||
} catch(e) {
|
||||
getterErrors[rule.p] = e;
|
||||
}
|
||||
@@ -288,5 +289,5 @@ module.exports = function(RED) {
|
||||
node.server.deregister(node);
|
||||
});
|
||||
}
|
||||
RED.nodes.registerType("matrix-room-state-events", MatrixRoomSettings);
|
||||
RED.nodes.registerType("matrix-room-state-events", MatrixRoomStateEvents);
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
<script type="text/javascript">
|
||||
RED.nodes.registerType('matrix-send-file',{
|
||||
category: 'matrix',
|
||||
category: 'matrix (deprecated)',
|
||||
color: '#00b7ca',
|
||||
icon: "matrix.png",
|
||||
outputLabels: ["success", "error"],
|
||||
@@ -20,6 +20,26 @@
|
||||
</script>
|
||||
|
||||
<script type="text/html" data-template-name="matrix-send-file">
|
||||
<style>
|
||||
.matrix-chat-alert {
|
||||
color: #b94a48;
|
||||
background-color: #f2dede;
|
||||
border-color: #eed3d7;
|
||||
margin: 30px 0;
|
||||
padding: 8px 35px 8px 14px;
|
||||
}
|
||||
|
||||
.matrix-chat-alert a, .matrix-chat-alert a:active, .matrix-chat-alert a:hover {
|
||||
color: #b94a48;
|
||||
text-decoration: underline;
|
||||
font-weight: bold;
|
||||
}
|
||||
</style>
|
||||
<div class="matrix-chat-alert">
|
||||
<strong>Deprecation Warning!</strong> This node will be removed in a future release. We have consolidated the Send File & Send Image nodes into a single Upload File node (with more options). Read <a href="https://github.com/Skylar-Tech/node-red-contrib-matrix-chat/issues/102" target="_blank">here</a> for more info.<br>
|
||||
<br>
|
||||
Replace this node with a File Upload node attached to a Send Message node to accomplish the same behavior.
|
||||
</div>
|
||||
<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">
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script type="text/javascript">
|
||||
RED.nodes.registerType('matrix-send-image',{
|
||||
category: 'matrix',
|
||||
category: 'matrix (deprecated)',
|
||||
color: '#00b7ca',
|
||||
icon: "matrix.png",
|
||||
outputLabels: ["success", "error"],
|
||||
@@ -20,6 +20,26 @@
|
||||
</script>
|
||||
|
||||
<script type="text/html" data-template-name="matrix-send-image">
|
||||
<style>
|
||||
.matrix-chat-alert {
|
||||
color: #b94a48;
|
||||
background-color: #f2dede;
|
||||
border-color: #eed3d7;
|
||||
margin: 30px 0;
|
||||
padding: 8px 35px 8px 14px;
|
||||
}
|
||||
|
||||
.matrix-chat-alert a, .matrix-chat-alert a:active, .matrix-chat-alert a:hover {
|
||||
color: #b94a48;
|
||||
text-decoration: underline;
|
||||
font-weight: bold;
|
||||
}
|
||||
</style>
|
||||
<div class="matrix-chat-alert">
|
||||
<strong>Deprecation Warning!</strong> This node will be removed in a future release. We have consolidated the Send File & Send Image nodes into a single Upload File node (with more options). Read <a href="https://github.com/Skylar-Tech/node-red-contrib-matrix-chat/issues/102" target="_blank">here</a> for more info.<br>
|
||||
<br>
|
||||
Replace this node with a File Upload node attached to a Send Message node to accomplish the same behavior.
|
||||
</div>
|
||||
<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">
|
||||
|
||||
@@ -167,9 +167,6 @@ module.exports = function(RED) {
|
||||
} else if(threadReply) {
|
||||
// if incoming message is a reply to a thread we fetch the thread parent from the m.relates_to property
|
||||
// otherwise fallback to msg.eventId
|
||||
console.log(msg);
|
||||
console.log(msg?.content?.['m.relates_to']?.rel_type);
|
||||
console.log(msg?.content?.['m.relates_to']?.event_id);
|
||||
let threadParent = (msg?.content?.['m.relates_to']?.rel_type === RelationType.Thread ? msg?.content?.['m.relates_to']?.event_id : null) || msg.eventId;
|
||||
if(threadParent) {
|
||||
content["m.relates_to"] = {
|
||||
@@ -190,10 +187,11 @@ module.exports = function(RED) {
|
||||
.then(function(e) {
|
||||
node.log("Message sent: " + payload);
|
||||
msg.eventId = e.event_id;
|
||||
node.log(JSON.stringify(e));
|
||||
node.send([msg, null]);
|
||||
})
|
||||
.catch(function(e){
|
||||
node.warn("Error sending message " + e);
|
||||
node.error("Error sending message: " + e, {});
|
||||
msg.error = e;
|
||||
node.send([null, msg]);
|
||||
});
|
||||
|
||||
+28
-10
@@ -1,4 +1,4 @@
|
||||
const {RelationType} = require("matrix-js-sdk");
|
||||
const {RelationType, TimelineWindow} = require("matrix-js-sdk");
|
||||
|
||||
global.Olm = require('olm');
|
||||
const fs = require("fs-extra");
|
||||
@@ -55,9 +55,9 @@ module.exports = function(RED) {
|
||||
this.url = this.credentials.url;
|
||||
this.autoAcceptRoomInvites = n.autoAcceptRoomInvites;
|
||||
this.e2ee = n.enableE2ee || false;
|
||||
|
||||
this.globalAccess = n.global;
|
||||
this.initializedAt = new Date();
|
||||
node.initialSyncLimit = 25;
|
||||
|
||||
// Keep track of all consumers of this node to be able to catch errors
|
||||
node.register = function(consumerNode) {
|
||||
@@ -158,6 +158,8 @@ module.exports = function(RED) {
|
||||
// verificationMethods: ["m.sas.v1"]
|
||||
});
|
||||
|
||||
node.debug(`hasLazyLoadMembersEnabled=${node.matrixClient.hasLazyLoadMembersEnabled()}`);
|
||||
|
||||
// set globally if configured to do so
|
||||
if(this.globalAccess) {
|
||||
this.context().global.set('matrixClient["'+this.userId+'"]', node.matrixClient);
|
||||
@@ -178,7 +180,7 @@ module.exports = function(RED) {
|
||||
stopClient();
|
||||
if(node.globalAccess) {
|
||||
try {
|
||||
node.context().global.delete('matrixClient["'+node.userId+'"]');
|
||||
node.context().global.set('matrixClient["'+node.userId+'"]', undefined);
|
||||
} catch(e){
|
||||
node.error(e.message, {});
|
||||
}
|
||||
@@ -192,15 +194,15 @@ module.exports = function(RED) {
|
||||
|
||||
node.matrixClient.on(RoomEvent.Timeline, async function(event, room, toStartOfTimeline, removed, data) {
|
||||
if (toStartOfTimeline) {
|
||||
node.log("Ignoring" + (event.isEncrypted() ? ' encrypted' : '') +" timeline event [" + (event.getContent()['msgtype'] || event.getType()) + "]: (" + room.name + ") " + event.getId() + " for reason: paginated result");
|
||||
return; // ignore paginated results
|
||||
}
|
||||
if (!event.getSender() || event.getSender() === node.userId) {
|
||||
return; // ignore our own messages
|
||||
}
|
||||
if (!data || !data.liveEvent) {
|
||||
node.log("Ignoring" + (event.isEncrypted() ? ' encrypted' : '') +" timeline event [" + (event.getContent()['msgtype'] || event.getType()) + "]: (" + room.name + ") " + event.getId() + " for reason: old message");
|
||||
return; // ignore old message (we only want live events)
|
||||
}
|
||||
if(node.initializedAt > event.getDate()) {
|
||||
node.log("Ignoring" + (event.isEncrypted() ? ' encrypted' : '') +" timeline event [" + (event.getContent()['msgtype'] || event.getType()) + "]: (" + room.name + ") " + event.getId() + " for reason: old message before init");
|
||||
return; // skip events that occurred before our client initialized
|
||||
}
|
||||
|
||||
@@ -236,12 +238,20 @@ module.exports = function(RED) {
|
||||
isThread : event.getContent()?.['m.relates_to']?.rel_type === RelationType.Thread,
|
||||
mentions : event.getContent()["m.mentions"] || null,
|
||||
userId : event.getSender(),
|
||||
user : node.matrixClient.getUser(event.getSender()),
|
||||
topic : event.getRoomId(),
|
||||
eventId : event.getId(),
|
||||
event : event,
|
||||
};
|
||||
|
||||
node.log("Received" + (msg.encrypted ? ' encrypted' : '') +" timeline event [" + msg.type + "]: (" + room.name + ") " + event.getSender() + " :: " + msg.content.body + (toStartOfTimeline ? ' [PAGINATED]' : ''));
|
||||
// remove keys from user property that start with an underscore
|
||||
Object.keys(msg.user).forEach(function (key) {
|
||||
if (/^_/.test(key)) {
|
||||
delete msg.user[key];
|
||||
}
|
||||
});
|
||||
|
||||
node.log(`Received ${msg.encrypted ? 'encrypted ' : ''}timeline event [${msg.type}]: (${room.name}) ${event.getSender()} :: ${msg.content.body} ${toStartOfTimeline ? ' [PAGINATED]' : ''}`);
|
||||
node.emit("Room.timeline", event, room, toStartOfTimeline, removed, data, msg);
|
||||
});
|
||||
|
||||
@@ -394,7 +404,7 @@ module.exports = function(RED) {
|
||||
}
|
||||
node.log("Connecting to Matrix server...");
|
||||
await node.matrixClient.startClient({
|
||||
initialSyncLimit: 8
|
||||
initialSyncLimit: node.initialSyncLimit
|
||||
});
|
||||
} catch(error) {
|
||||
node.error(error, {});
|
||||
@@ -468,13 +478,21 @@ module.exports = function(RED) {
|
||||
const matrixClient = sdk.createClient({
|
||||
baseUrl: baseUrl,
|
||||
deviceId: deviceId,
|
||||
timelineSupport: true,
|
||||
localTimeoutMs: '30000',
|
||||
request
|
||||
});
|
||||
|
||||
new TimelineWindow()
|
||||
|
||||
matrixClient.timelineSupport = true;
|
||||
|
||||
matrixClient.login(
|
||||
'm.login.password', {
|
||||
user: userId,
|
||||
identifier: {
|
||||
type: 'm.id.user',
|
||||
user: userId,
|
||||
},
|
||||
password: password,
|
||||
initial_device_display_name: displayName
|
||||
})
|
||||
@@ -553,4 +571,4 @@ module.exports = function(RED) {
|
||||
localStorage.setItem('my_device_id', deviceId);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,9 @@
|
||||
$("#node-input-room").typedInput({
|
||||
type: this.roomType,
|
||||
types:['msg','flow','global','str'],
|
||||
}).typedInput('value', this.roomValue);
|
||||
})
|
||||
.typedInput('value', this.roomValue)
|
||||
.typedInput('type', this.roomType);
|
||||
|
||||
$("#node-input-typing").typedInput({
|
||||
types:['msg','flow','global','bool'],
|
||||
@@ -66,12 +68,12 @@
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<label for="node-input-room"><i class="fa fa-commenting-o"></i> Is Typing</label>
|
||||
<label for="node-input-typing"><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>
|
||||
<label for="node-input-timeoutMs"><i class="fa fa-clock-o"></i> Timeout Milliseconds</label>
|
||||
<input type="text" id="node-input-timeoutMs">
|
||||
</div>
|
||||
|
||||
|
||||
@@ -105,7 +105,7 @@
|
||||
|
||||
<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>
|
||||
<p>Upload a file to the matrix homeserver. Returns a payload that can then be chained to a Send Message node to post the file to a room, or you can use the mxc_url to update a user or room avatar.</p>
|
||||
|
||||
<h3>Inputs</h3>
|
||||
<dl class="message-properties">
|
||||
|
||||
+291
-228
@@ -1,5 +1,6 @@
|
||||
const crypto = require("isomorphic-webcrypto");
|
||||
const ffmpeg = require('fluent-ffmpeg');
|
||||
const sharp = require('sharp');
|
||||
const getImageSize = require('image-size');
|
||||
const tmp = require('tmp');
|
||||
const fs = require('fs');
|
||||
@@ -35,274 +36,336 @@ module.exports = function(RED) {
|
||||
node.status({ fill: "green", shape: "ring", text: "connected" });
|
||||
});
|
||||
|
||||
async function detectFileType(filename, bufferOrPath)
|
||||
{
|
||||
async function detectFileType(filename, bufferOrPath, msg) {
|
||||
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}
|
||||
try {
|
||||
if(file) {
|
||||
let type = Mime.getType(file);
|
||||
let ext = Mime.getExtension(file);
|
||||
if(type) {
|
||||
return {ext: ext, mime: type};
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
node.error(`Error detecting file type for ${filename}: ${error.message}`, msg);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function getFileBuffer(data)
|
||||
{
|
||||
if(Buffer.isBuffer(data)) {
|
||||
return data;
|
||||
}
|
||||
function getFileBuffer(data, msg) {
|
||||
try {
|
||||
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)));
|
||||
if (data && RED.settings.fileWorkingDirectory && !path.isAbsolute(data)) {
|
||||
return fs.readFileSync(path.resolve(path.join(RED.settings.fileWorkingDirectory, data)));
|
||||
}
|
||||
return fs.readFileSync(data);
|
||||
} catch (error) {
|
||||
node.error(`Error reading file buffer: ${error.message}`, msg);
|
||||
throw error;
|
||||
}
|
||||
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");
|
||||
try {
|
||||
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);
|
||||
}
|
||||
} else if(type === "bool") {
|
||||
value = (property === 'true');
|
||||
} else if(type === "num") {
|
||||
value = Number(property);
|
||||
} catch (error) {
|
||||
node.error(`Error evaluating value for type ${type}: ${error.message}`);
|
||||
throw new Error("Invalid value evaluation");
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
node.on("input", onInput);
|
||||
async function onInput(msg)
|
||||
{
|
||||
if (! node.server || ! node.server.matrixClient) {
|
||||
node.warn("No matrix server selected");
|
||||
return;
|
||||
}
|
||||
async function onInput(msg) {
|
||||
try {
|
||||
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';
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
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;
|
||||
}
|
||||
|
||||
if(tempFile) {
|
||||
return tempFile;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
msg.contentType = node.contentType || msg.contentType || null;
|
||||
let detectedFileType = await detectFileType(filename, bufferOrPath, msg);
|
||||
node.log("Detected file type " + JSON.stringify(detectedFileType) + " for " + (Buffer.isBuffer(bufferOrPath) ? 'buffer' : `file ${bufferOrPath}`), msg);
|
||||
|
||||
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);
|
||||
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);
|
||||
}
|
||||
|
||||
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 encryptedFile = null;
|
||||
if (msg.encrypted) {
|
||||
encryptedFile = await encryptAttachment(getFileBuffer(bufferOrPath, msg));
|
||||
}
|
||||
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?'
|
||||
node.log("Uploading file ", msg);
|
||||
let file;
|
||||
try {
|
||||
file = await node.server.matrixClient.uploadContent(
|
||||
encryptedFile?.data || getFileBuffer(bufferOrPath, msg),
|
||||
{
|
||||
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.
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
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);
|
||||
} catch (e) {
|
||||
node.error("Upload content error " + e, msg);
|
||||
msg.error = e;
|
||||
node.send([null, msg]);
|
||||
return;
|
||||
}
|
||||
|
||||
if(node.generateThumbnails) {
|
||||
// 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) {
|
||||
try {
|
||||
await _ffmpegVideoThumbnail(filepath);
|
||||
} catch(e) {
|
||||
node.error("Screenshot generation error: " + e);
|
||||
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;
|
||||
} catch (error) {
|
||||
node.error(`Error creating temp file: ${error.message}`, msg);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
deleteTempFile();
|
||||
}
|
||||
|
||||
node.send(msg, null);
|
||||
function deleteTempFile() {
|
||||
if (!tempFile) return null;
|
||||
try {
|
||||
fs.rmSync(tempFile);
|
||||
} catch (error) {
|
||||
node.error(`Error deleting temp file: ${error.message}`, msg);
|
||||
}
|
||||
}
|
||||
|
||||
// get size of a buffer or file in bytes
|
||||
function getFileSize(bufferOrPath) {
|
||||
try {
|
||||
if (Buffer.isBuffer(bufferOrPath)) {
|
||||
return Buffer.byteLength(bufferOrPath);
|
||||
}
|
||||
|
||||
return fs.statSync(bufferOrPath).size;
|
||||
} catch (error) {
|
||||
node.error(`Error getting file size: ${error.message}`, msg);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async function addThumbnail(buffer) {
|
||||
try {
|
||||
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;
|
||||
}
|
||||
} catch (error) {
|
||||
node.error(`Error adding thumbnail: ${error.message}`, msg);
|
||||
}
|
||||
}
|
||||
|
||||
async function createImageThumbnail(bufferOrPath) {
|
||||
try {
|
||||
if (Buffer.isBuffer(bufferOrPath)) {
|
||||
return sharp(bufferOrPath).resize({width: 320}).toBuffer();
|
||||
}
|
||||
return sharp(fs.readFileSync(bufferOrPath)).resize({width: 320}).toBuffer();
|
||||
} catch (error) {
|
||||
node.error(`Error creating image thumbnail: ${error.message}`);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
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, msg);
|
||||
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(bufferOrPath);
|
||||
msg.payload.info.h = imageSize.height;
|
||||
msg.payload.info.w = imageSize.width;
|
||||
|
||||
// Generate thumbnail for image
|
||||
if (node.generateThumbnails) {
|
||||
let thumbnailBuffer = await createImageThumbnail(bufferOrPath);
|
||||
let encryptedThumbnail = null;
|
||||
if (msg.encrypted) {
|
||||
encryptedThumbnail = await encryptAttachment(thumbnailBuffer);
|
||||
msg.payload.info.thumbnail_file = encryptedThumbnail.info;
|
||||
}
|
||||
await addThumbnail(encryptedThumbnail || thumbnailBuffer);
|
||||
}
|
||||
} catch (e) {
|
||||
node.error("thumbnail error: " + 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, msg);
|
||||
}
|
||||
|
||||
if (node.generateThumbnails) {
|
||||
try {
|
||||
await _ffmpegVideoThumbnail(filepath);
|
||||
} catch (e) {
|
||||
node.error("Screenshot generation error: " + e, msg);
|
||||
}
|
||||
}
|
||||
deleteTempFile();
|
||||
}
|
||||
|
||||
node.send(msg, null);
|
||||
} catch (error) {
|
||||
node.error(`Unhandled error: ${error.message}`, msg);
|
||||
node.log(error, msg);
|
||||
}
|
||||
}
|
||||
|
||||
node.on("close", function() {
|
||||
|
||||
@@ -0,0 +1,329 @@
|
||||
<script type="text/html" data-template-name="matrix-user-settings">
|
||||
<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" 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-user-settings">
|
||||
<h3>Details</h3>
|
||||
<p>
|
||||
Set and get the current user's display name or avatar.
|
||||
</p>
|
||||
|
||||
<h3>Inputs</h3>
|
||||
<dl class="message-properties">
|
||||
<dt class="optional">dynamic
|
||||
<span class="property-type">string|object</span>
|
||||
</dt>
|
||||
<dd> The properties used to set the avatar_url or display name are configured on the node.</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 saving a setting failed.</dd>
|
||||
</dl>
|
||||
<dl class="message-properties">
|
||||
<dt>msg.getter_errors <span class="property-type">undefined|object</span></dt>
|
||||
<dd>Returned if there is an error thrown getting a user setting</dd>
|
||||
</dl>
|
||||
<dt class="optional">dynamic
|
||||
<span class="property-type">string|object</span>
|
||||
</dt>
|
||||
<dd> You configure what user settings to output in the node configuration.</dd>
|
||||
</li>
|
||||
</ol>
|
||||
</script>
|
||||
|
||||
<script type="text/javascript">
|
||||
(function(){
|
||||
var userSettingOptions = [
|
||||
{ value: "display_name", label: "Display name" },
|
||||
{ value: "avatar_url", label: "Avatar URL" },
|
||||
];
|
||||
var defaultRules = [{
|
||||
t: "set",
|
||||
p: userSettingOptions[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-user-settings',{
|
||||
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 },
|
||||
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:userSettingOptions[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)
|
||||
.typedInput({type:"property", types:[{
|
||||
value: userSettingOptions[0]['value'],
|
||||
options: userSettingOptions
|
||||
}]})
|
||||
.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 || "User Settings";
|
||||
},
|
||||
paletteLabel: 'User Settings'
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
@@ -0,0 +1,184 @@
|
||||
module.exports = function(RED) {
|
||||
function MatrixUserSettings(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 "display_name":
|
||||
await node.server.matrixClient.setDisplayName(value);
|
||||
break;
|
||||
case "avatar_url":
|
||||
await node.server.matrixClient.setAvatarUrl(typeof value === "string" ? value : "");
|
||||
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 {
|
||||
// normalize some simpler events for easier access
|
||||
switch(rule.p) {
|
||||
case "display_name":
|
||||
value = (await node.server.matrixClient.getProfileInfo(node.server.matrixClient.getUserId(), 'displayname')).displayname || false;
|
||||
break;
|
||||
case "avatar_url":
|
||||
value = (await node.server.matrixClient.getProfileInfo(node.server.matrixClient.getUserId(), 'avatar_url')).avatar_url || false;
|
||||
break;
|
||||
}
|
||||
setToValue(value, rule);
|
||||
cachedGetters[rule.p] = value;
|
||||
} 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-user-settings", MatrixUserSettings);
|
||||
}
|
||||
Reference in New Issue
Block a user