Compare commits

..

4 Commits

Author SHA1 Message Date
timedout 93b9007e1d fix: Unreliability in kicks & bans 2026-02-14 15:23:22 +00:00
timedout 2919c8e636 chore: Add double-lock flag 2026-02-14 14:24:13 +00:00
timedout 5ad653ab86 chore: Revert leaked changes to leave.rs 2026-02-14 14:17:23 +00:00
nexy7574 c9d9ed0a90 feat: Port room takeover and shutdown commands from continuwuity.rocks 2026-02-14 14:12:52 +00:00
15 changed files with 748 additions and 286 deletions
Generated
+15 -15
View File
@@ -1343,7 +1343,7 @@ dependencies = [
[[package]]
name = "continuwuity-admin-api"
version = "0.1.0"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=b496b7f38d517149361a882e75d3fd4faf210441#b496b7f38d517149361a882e75d3fd4faf210441"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=458d52bdc7f9a07c497be94a1420ebd3d87d7b2b#458d52bdc7f9a07c497be94a1420ebd3d87d7b2b"
dependencies = [
"ruma-common",
"serde",
@@ -1758,7 +1758,7 @@ dependencies = [
[[package]]
name = "draupnir-antispam"
version = "0.1.0"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=b496b7f38d517149361a882e75d3fd4faf210441#b496b7f38d517149361a882e75d3fd4faf210441"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=458d52bdc7f9a07c497be94a1420ebd3d87d7b2b#458d52bdc7f9a07c497be94a1420ebd3d87d7b2b"
dependencies = [
"ruma-common",
"serde",
@@ -3116,7 +3116,7 @@ checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273"
[[package]]
name = "meowlnir-antispam"
version = "0.1.0"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=b496b7f38d517149361a882e75d3fd4faf210441#b496b7f38d517149361a882e75d3fd4faf210441"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=458d52bdc7f9a07c497be94a1420ebd3d87d7b2b#458d52bdc7f9a07c497be94a1420ebd3d87d7b2b"
dependencies = [
"ruma-common",
"serde",
@@ -4389,7 +4389,7 @@ dependencies = [
[[package]]
name = "ruma"
version = "0.10.1"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=b496b7f38d517149361a882e75d3fd4faf210441#b496b7f38d517149361a882e75d3fd4faf210441"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=458d52bdc7f9a07c497be94a1420ebd3d87d7b2b#458d52bdc7f9a07c497be94a1420ebd3d87d7b2b"
dependencies = [
"assign",
"continuwuity-admin-api",
@@ -4412,7 +4412,7 @@ dependencies = [
[[package]]
name = "ruma-appservice-api"
version = "0.10.0"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=b496b7f38d517149361a882e75d3fd4faf210441#b496b7f38d517149361a882e75d3fd4faf210441"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=458d52bdc7f9a07c497be94a1420ebd3d87d7b2b#458d52bdc7f9a07c497be94a1420ebd3d87d7b2b"
dependencies = [
"js_int",
"ruma-common",
@@ -4424,7 +4424,7 @@ dependencies = [
[[package]]
name = "ruma-client-api"
version = "0.18.0"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=b496b7f38d517149361a882e75d3fd4faf210441#b496b7f38d517149361a882e75d3fd4faf210441"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=458d52bdc7f9a07c497be94a1420ebd3d87d7b2b#458d52bdc7f9a07c497be94a1420ebd3d87d7b2b"
dependencies = [
"as_variant",
"assign",
@@ -4447,7 +4447,7 @@ dependencies = [
[[package]]
name = "ruma-common"
version = "0.13.0"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=b496b7f38d517149361a882e75d3fd4faf210441#b496b7f38d517149361a882e75d3fd4faf210441"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=458d52bdc7f9a07c497be94a1420ebd3d87d7b2b#458d52bdc7f9a07c497be94a1420ebd3d87d7b2b"
dependencies = [
"as_variant",
"base64 0.22.1",
@@ -4479,7 +4479,7 @@ dependencies = [
[[package]]
name = "ruma-events"
version = "0.28.1"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=b496b7f38d517149361a882e75d3fd4faf210441#b496b7f38d517149361a882e75d3fd4faf210441"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=458d52bdc7f9a07c497be94a1420ebd3d87d7b2b#458d52bdc7f9a07c497be94a1420ebd3d87d7b2b"
dependencies = [
"as_variant",
"indexmap",
@@ -4504,7 +4504,7 @@ dependencies = [
[[package]]
name = "ruma-federation-api"
version = "0.9.0"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=b496b7f38d517149361a882e75d3fd4faf210441#b496b7f38d517149361a882e75d3fd4faf210441"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=458d52bdc7f9a07c497be94a1420ebd3d87d7b2b#458d52bdc7f9a07c497be94a1420ebd3d87d7b2b"
dependencies = [
"bytes",
"headers",
@@ -4526,7 +4526,7 @@ dependencies = [
[[package]]
name = "ruma-identifiers-validation"
version = "0.9.5"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=b496b7f38d517149361a882e75d3fd4faf210441#b496b7f38d517149361a882e75d3fd4faf210441"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=458d52bdc7f9a07c497be94a1420ebd3d87d7b2b#458d52bdc7f9a07c497be94a1420ebd3d87d7b2b"
dependencies = [
"js_int",
"thiserror 2.0.18",
@@ -4535,7 +4535,7 @@ dependencies = [
[[package]]
name = "ruma-identity-service-api"
version = "0.9.0"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=b496b7f38d517149361a882e75d3fd4faf210441#b496b7f38d517149361a882e75d3fd4faf210441"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=458d52bdc7f9a07c497be94a1420ebd3d87d7b2b#458d52bdc7f9a07c497be94a1420ebd3d87d7b2b"
dependencies = [
"js_int",
"ruma-common",
@@ -4545,7 +4545,7 @@ dependencies = [
[[package]]
name = "ruma-macros"
version = "0.13.0"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=b496b7f38d517149361a882e75d3fd4faf210441#b496b7f38d517149361a882e75d3fd4faf210441"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=458d52bdc7f9a07c497be94a1420ebd3d87d7b2b#458d52bdc7f9a07c497be94a1420ebd3d87d7b2b"
dependencies = [
"cfg-if",
"proc-macro-crate",
@@ -4560,7 +4560,7 @@ dependencies = [
[[package]]
name = "ruma-push-gateway-api"
version = "0.9.0"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=b496b7f38d517149361a882e75d3fd4faf210441#b496b7f38d517149361a882e75d3fd4faf210441"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=458d52bdc7f9a07c497be94a1420ebd3d87d7b2b#458d52bdc7f9a07c497be94a1420ebd3d87d7b2b"
dependencies = [
"js_int",
"ruma-common",
@@ -4572,7 +4572,7 @@ dependencies = [
[[package]]
name = "ruma-signatures"
version = "0.15.0"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=b496b7f38d517149361a882e75d3fd4faf210441#b496b7f38d517149361a882e75d3fd4faf210441"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=458d52bdc7f9a07c497be94a1420ebd3d87d7b2b#458d52bdc7f9a07c497be94a1420ebd3d87d7b2b"
dependencies = [
"base64 0.22.1",
"ed25519-dalek",
@@ -4969,7 +4969,7 @@ checksum = "bc14a55107113a16346915d7e3d78acc539a923458385db89670e22cac106d7a"
dependencies = [
"ahash",
"annotate-snippets",
"base64 0.21.7",
"base64 0.22.1",
"encoding_rs_io",
"figment",
"garde",
+2 -3
View File
@@ -342,7 +342,7 @@ version = "0.1.2"
# Used for matrix spec type definitions and helpers
[workspace.dependencies.ruma]
git = "https://forgejo.ellis.link/continuwuation/ruwuma"
rev = "b496b7f38d517149361a882e75d3fd4faf210441"
rev = "458d52bdc7f9a07c497be94a1420ebd3d87d7b2b"
features = [
"compat",
"rand",
@@ -378,8 +378,7 @@ features = [
"unstable-msc4210", # remove legacy mentions
"unstable-extensible-events",
"unstable-pdu",
"unstable-msc4155",
"unstable-msc4143", # livekit well_known response
"unstable-msc4155"
]
[workspace.dependencies.rust-rocksdb]
-1
View File
@@ -1 +0,0 @@
Fixed sliding sync not resolving wildcard state key requests, enabling Video/Audio calls in Element X.
-11
View File
@@ -1820,17 +1820,6 @@
#
#support_mxid =
# A list of MatrixRTC foci URLs which will be served as part of the
# MSC4143 client endpoint at /.well-known/matrix/client. If you're
# setting up livekit, you'd want something like:
# rtc_focus_server_urls = [
# { type = "livekit", livekit_service_url = "https://livekit.example.com" },
# ]
#
# To disable, set this to be an empty vector (`[]`).
#
#rtc_focus_server_urls = []
[global.blurhashing]
# blurhashing x component, 4 is recommended by https://blurha.sh/
+1 -1
View File
@@ -48,7 +48,7 @@ EOF
# Developer tool versions
# renovate: datasource=github-releases depName=cargo-bins/cargo-binstall
ENV BINSTALL_VERSION=1.17.5
ENV BINSTALL_VERSION=1.17.4
# renovate: datasource=github-releases depName=psastras/sbom-rs
ENV CARGO_SBOM_VERSION=0.9.1
# renovate: datasource=crate depName=lddtree
+1 -1
View File
@@ -18,7 +18,7 @@ RUN --mount=type=cache,target=/etc/apk/cache apk add \
# Developer tool versions
# renovate: datasource=github-releases depName=cargo-bins/cargo-binstall
ENV BINSTALL_VERSION=1.17.5
ENV BINSTALL_VERSION=1.17.4
# renovate: datasource=github-releases depName=psastras/sbom-rs
ENV CARGO_SBOM_VERSION=0.9.1
# renovate: datasource=crate depName=lddtree
-10
View File
@@ -19,16 +19,6 @@ hero:
src: /assets/logo.svg
alt: continuwuity logo
beforeFeatures:
- title: Matrix for Discord users
details: New to Matrix? Learn how Matrix compares to Discord
link: https://joinmatrix.org/guide/matrix-vs-discord/
buttonText: Find Out the Difference
- title: How Matrix Works
details: Learn how Matrix works under the hood, and what that means
link: https://matrix.org/docs/matrix-concepts/elements-of-matrix/
buttonText: Read the Guide
features:
- title: 🚀 High Performance
details: Built with Rust for exceptional speed and efficiency. Designed to run smoothly even on modest hardware.
+5 -20
View File
@@ -1,28 +1,13 @@
# Troubleshooting Continuwuity
:::warning{title="Docker users:"}
Docker can be difficult to use and debug. It's common for Docker
misconfigurations to cause issues, particularly with networking and permissions.
Please check that your issues are not due to problems with your Docker setup.
:::
> **Docker users ⚠️**
>
> Docker can be difficult to use and debug. It's common for Docker
> misconfigurations to cause issues, particularly with networking and permissions.
> Please check that your issues are not due to problems with your Docker setup.
## Continuwuity and Matrix issues
### Slow joins to rooms
Some slowness is to be expected if you're the first person on your homserver to join a room (which will
always be the case for single-user homeservers). In this situation, your homeserver has to verify the signatures of
all of the state events sent by other servers before your join. To make this process as fast as possible, make sure you have
multiple fast, trusted servers listed in `trusted_servers` in your configuration, and ensure
`query_trusted_key_servers_first_on_join` is set to true (the default).
If you need suggestions for trusted servers, ask in the Continuwuity main room.
However, _very_ slow joins, especially to rooms with only a few users in them or rooms created by another user
on your homeserver, may be caused by [issue !779](https://forgejo.ellis.link/continuwuation/continuwuity/issues/779),
which is a longstanding bug with synchronizing room joins to clients. In this situation, you did succeed in joining the room, but
the bug caused your homeserver to forget to tell your client. **To fix this, clear your client's cache.** Both Element and Cinny
have a button to clear their cache in the "About" section of their settings.
### Lost access to admin room
You can reinvite yourself to the admin room through the following methods:
+85 -78
View File
@@ -119,13 +119,14 @@
}
},
"node_modules/@rsbuild/core": {
"version": "2.0.0-beta.3",
"resolved": "https://registry.npmjs.org/@rsbuild/core/-/core-2.0.0-beta.3.tgz",
"integrity": "sha512-dfH+Pt2GuF3rWOWGsf5XOhn3Zarvr4DoHwoI1arAsCGvpzoeud3DNGmWPy13tngj0r/YvQRcPTRBCRV4RP5CMw==",
"version": "2.0.0-beta.1",
"resolved": "https://registry.npmjs.org/@rsbuild/core/-/core-2.0.0-beta.1.tgz",
"integrity": "sha512-m7L3oi4evTDODcY+Qk3cmY/p7GCaauSRe00D0AkXVohNvxFBt7F49uPwBSThS24I9d31zFuAED2jFqBeBlDqWw==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@rspack/core": "2.0.0-beta.0",
"@rspack/core": "2.0.0-alpha.1",
"@swc/helpers": "^0.5.18",
"jiti": "^2.6.1"
},
@@ -159,28 +160,28 @@
}
},
"node_modules/@rspack/binding": {
"version": "2.0.0-beta.0",
"resolved": "https://registry.npmjs.org/@rspack/binding/-/binding-2.0.0-beta.0.tgz",
"integrity": "sha512-L6PPqhwZWC2vzwdhBItNPXw+7V4sq+MBDRXLdd8NMqaJSCB5iKdJIbpbEQucST9Nn7V28IYoQTXs6+ol5vWUBA==",
"version": "2.0.0-alpha.1",
"resolved": "https://registry.npmjs.org/@rspack/binding/-/binding-2.0.0-alpha.1.tgz",
"integrity": "sha512-Glz0SNFYPtNVM+ExJ4ocSzW+oQhb1iHTmxqVEAILbL17Hq3N/nwZpo1cWEs6hJjn8cosJIb1VKbbgb/1goEtCQ==",
"dev": true,
"license": "MIT",
"optionalDependencies": {
"@rspack/binding-darwin-arm64": "2.0.0-beta.0",
"@rspack/binding-darwin-x64": "2.0.0-beta.0",
"@rspack/binding-linux-arm64-gnu": "2.0.0-beta.0",
"@rspack/binding-linux-arm64-musl": "2.0.0-beta.0",
"@rspack/binding-linux-x64-gnu": "2.0.0-beta.0",
"@rspack/binding-linux-x64-musl": "2.0.0-beta.0",
"@rspack/binding-wasm32-wasi": "2.0.0-beta.0",
"@rspack/binding-win32-arm64-msvc": "2.0.0-beta.0",
"@rspack/binding-win32-ia32-msvc": "2.0.0-beta.0",
"@rspack/binding-win32-x64-msvc": "2.0.0-beta.0"
"@rspack/binding-darwin-arm64": "2.0.0-alpha.1",
"@rspack/binding-darwin-x64": "2.0.0-alpha.1",
"@rspack/binding-linux-arm64-gnu": "2.0.0-alpha.1",
"@rspack/binding-linux-arm64-musl": "2.0.0-alpha.1",
"@rspack/binding-linux-x64-gnu": "2.0.0-alpha.1",
"@rspack/binding-linux-x64-musl": "2.0.0-alpha.1",
"@rspack/binding-wasm32-wasi": "2.0.0-alpha.1",
"@rspack/binding-win32-arm64-msvc": "2.0.0-alpha.1",
"@rspack/binding-win32-ia32-msvc": "2.0.0-alpha.1",
"@rspack/binding-win32-x64-msvc": "2.0.0-alpha.1"
}
},
"node_modules/@rspack/binding-darwin-arm64": {
"version": "2.0.0-beta.0",
"resolved": "https://registry.npmjs.org/@rspack/binding-darwin-arm64/-/binding-darwin-arm64-2.0.0-beta.0.tgz",
"integrity": "sha512-PPx1+SPEROSvDKmBuCbsE7W9tk07ajPosyvyuafv2wbBI6PW2rNcz62uzpIFS+FTgwwZ5u/06WXRtlD2xW9bKg==",
"version": "2.0.0-alpha.1",
"resolved": "https://registry.npmjs.org/@rspack/binding-darwin-arm64/-/binding-darwin-arm64-2.0.0-alpha.1.tgz",
"integrity": "sha512-+6E6pYgpKvs41cyOlqRjpCT3djjL9hnntF61JumM/TNo1aTYXMNNG4b8ZsLMpBq5ZwCy9Dg8oEDe8AZ84rfM7A==",
"cpu": [
"arm64"
],
@@ -192,9 +193,9 @@
]
},
"node_modules/@rspack/binding-darwin-x64": {
"version": "2.0.0-beta.0",
"resolved": "https://registry.npmjs.org/@rspack/binding-darwin-x64/-/binding-darwin-x64-2.0.0-beta.0.tgz",
"integrity": "sha512-GucsfjrSKBZ9cuOTXmHWxeY2wPmaNyvGNxTyzttjRcfwqOWz8r+ku6PCsMSXUqxZRYWW1L9mvtTdlDrzTYJZ0w==",
"version": "2.0.0-alpha.1",
"resolved": "https://registry.npmjs.org/@rspack/binding-darwin-x64/-/binding-darwin-x64-2.0.0-alpha.1.tgz",
"integrity": "sha512-Ccf9NNupVe67vlaS9zKQJ+BvsAn385uBC1vXnYaUxxHoY/tEwNJf6t+XyDARt7mCtT7+Bu4L/iJ/JEF/MsO5zg==",
"cpu": [
"x64"
],
@@ -206,9 +207,9 @@
]
},
"node_modules/@rspack/binding-linux-arm64-gnu": {
"version": "2.0.0-beta.0",
"resolved": "https://registry.npmjs.org/@rspack/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-2.0.0-beta.0.tgz",
"integrity": "sha512-nTtYtklRZD4sb2RIFCF9YS8tZ/MjpqIBKVS3YIvdXcfHUdVfmQHTZGtwEuZGg6AxTC5L1hcvkYmTXCG0ok7auw==",
"version": "2.0.0-alpha.1",
"resolved": "https://registry.npmjs.org/@rspack/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-2.0.0-alpha.1.tgz",
"integrity": "sha512-B7omNsPSsinOq2VRD4d4VFrLgHceMQobqlLg0txFUZ7PDjE307gpTcGViWQlUhNCbkZXMPzDeXBFa5ZlEmxgnA==",
"cpu": [
"arm64"
],
@@ -220,9 +221,9 @@
]
},
"node_modules/@rspack/binding-linux-arm64-musl": {
"version": "2.0.0-beta.0",
"resolved": "https://registry.npmjs.org/@rspack/binding-linux-arm64-musl/-/binding-linux-arm64-musl-2.0.0-beta.0.tgz",
"integrity": "sha512-S2fshx0Rf7/XYwoMLaqFsVg4y+VAfHzubrczy8AW5xIs6UNC3eRLVTgShLerUPtF6SG+v6NQxQ9JI3vOo2qPOA==",
"version": "2.0.0-alpha.1",
"resolved": "https://registry.npmjs.org/@rspack/binding-linux-arm64-musl/-/binding-linux-arm64-musl-2.0.0-alpha.1.tgz",
"integrity": "sha512-NCG401ofZcDKlTWD8VHv76Y+02Stmd9Nu5MRbVUBOCTVgXMj8Mgrm5XsGBWUjzd5J/Mvo2hstCKIZxNzmPd8uQ==",
"cpu": [
"arm64"
],
@@ -234,9 +235,9 @@
]
},
"node_modules/@rspack/binding-linux-x64-gnu": {
"version": "2.0.0-beta.0",
"resolved": "https://registry.npmjs.org/@rspack/binding-linux-x64-gnu/-/binding-linux-x64-gnu-2.0.0-beta.0.tgz",
"integrity": "sha512-yx5Fk1gl7lfkvqcjolNLCNeduIs6C2alMsQ/kZ1pLeP5MPquVOYNqs6EcDPIp+fUjo3lZYtnJBiZKK+QosbzYg==",
"version": "2.0.0-alpha.1",
"resolved": "https://registry.npmjs.org/@rspack/binding-linux-x64-gnu/-/binding-linux-x64-gnu-2.0.0-alpha.1.tgz",
"integrity": "sha512-Xgp8wJ5gjpPG8I3VMEsVAesfckWryQVUhJkHcxPfNi72QTv8UkMER7Jl+JrlQk7K7nMO5ltokx/VGl1c3tMx+w==",
"cpu": [
"x64"
],
@@ -248,9 +249,9 @@
]
},
"node_modules/@rspack/binding-linux-x64-musl": {
"version": "2.0.0-beta.0",
"resolved": "https://registry.npmjs.org/@rspack/binding-linux-x64-musl/-/binding-linux-x64-musl-2.0.0-beta.0.tgz",
"integrity": "sha512-sBX4b2W0PgehlAVT224k0Q6GaH6t9HP+hBNDrbX/g6d0hfxZN56gm5NfOTOD1Rien4v7OBEejJ3/uFbm1WjwYQ==",
"version": "2.0.0-alpha.1",
"resolved": "https://registry.npmjs.org/@rspack/binding-linux-x64-musl/-/binding-linux-x64-musl-2.0.0-alpha.1.tgz",
"integrity": "sha512-lrYKcOgsPA1UMswxzFAV37ofkznbtTLCcEas6lxtlT3Dr28P6VRzC8TgVbIiprkm10I0BlThQWDJ3aGzzLj9Kg==",
"cpu": [
"x64"
],
@@ -262,9 +263,9 @@
]
},
"node_modules/@rspack/binding-wasm32-wasi": {
"version": "2.0.0-beta.0",
"resolved": "https://registry.npmjs.org/@rspack/binding-wasm32-wasi/-/binding-wasm32-wasi-2.0.0-beta.0.tgz",
"integrity": "sha512-o6OatnNvb4kCzXbCaomhENGaCsO3naIyAqqErew90HeAwa1lfY3NhRfDLeIyuANQ+xqFl34/R7n8q3ZDx3nd4Q==",
"version": "2.0.0-alpha.1",
"resolved": "https://registry.npmjs.org/@rspack/binding-wasm32-wasi/-/binding-wasm32-wasi-2.0.0-alpha.1.tgz",
"integrity": "sha512-rppGiT7CtXlM8st+IgzBDqb7V//1xx5Oe0SY1sxxw0cfOGMpIQCwhJqx/uI6ioqJLZLGX/obt359+hPXyqGl4w==",
"cpu": [
"wasm32"
],
@@ -276,9 +277,9 @@
}
},
"node_modules/@rspack/binding-win32-arm64-msvc": {
"version": "2.0.0-beta.0",
"resolved": "https://registry.npmjs.org/@rspack/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-2.0.0-beta.0.tgz",
"integrity": "sha512-neCzVllXzIqM8p8qKb89qV7wyk233gC/V9VrHIKbGeQjAEzpBsk5GOWlFbq5DDL6tivQ+uzYaTrZWm9tb2qxXg==",
"version": "2.0.0-alpha.1",
"resolved": "https://registry.npmjs.org/@rspack/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-2.0.0-alpha.1.tgz",
"integrity": "sha512-yD2g1JmnCxrix/344r7lBn+RH+Nv8uWP0UDP8kwv4kQGCWr4U7IP8PKFpoyulVOgOUjvJpgImeyrDJ7R8he+5w==",
"cpu": [
"arm64"
],
@@ -290,9 +291,9 @@
]
},
"node_modules/@rspack/binding-win32-ia32-msvc": {
"version": "2.0.0-beta.0",
"resolved": "https://registry.npmjs.org/@rspack/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-2.0.0-beta.0.tgz",
"integrity": "sha512-/f0n2eO+DxMKQm9IebeMQJITx8M/+RvY/i8d3sAQZBgR53izn8y7EcDlidXpr24/2DvkLbiub8IyCKPlhLB+1A==",
"version": "2.0.0-alpha.1",
"resolved": "https://registry.npmjs.org/@rspack/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-2.0.0-alpha.1.tgz",
"integrity": "sha512-5qpQL5Qz3uYb56pwffEGzznXSX9TNkLpigQbIObfnUwX7WkdjgTT7oTHpjn2sRSLLNiJ/jCp2r4ZHvjmnNRsRA==",
"cpu": [
"ia32"
],
@@ -304,9 +305,9 @@
]
},
"node_modules/@rspack/binding-win32-x64-msvc": {
"version": "2.0.0-beta.0",
"resolved": "https://registry.npmjs.org/@rspack/binding-win32-x64-msvc/-/binding-win32-x64-msvc-2.0.0-beta.0.tgz",
"integrity": "sha512-dx4zgiAT88EQE7kEUpr7Z9EZAwLnO5FhzWzvd/cDK4bkqYsx+rTklgf/c0EYPBeroXCxlGiMsuC9wHAFNK7sFw==",
"version": "2.0.0-alpha.1",
"resolved": "https://registry.npmjs.org/@rspack/binding-win32-x64-msvc/-/binding-win32-x64-msvc-2.0.0-alpha.1.tgz",
"integrity": "sha512-dZ76NN9tXLaF2gnB/pU+PcK4Adf9tj8dY06KcWk5F81ur2V4UbrMfkWJkQprur8cgL/F49YtFMRWa4yp/qNbpQ==",
"cpu": [
"x64"
],
@@ -318,13 +319,13 @@
]
},
"node_modules/@rspack/core": {
"version": "2.0.0-beta.0",
"resolved": "https://registry.npmjs.org/@rspack/core/-/core-2.0.0-beta.0.tgz",
"integrity": "sha512-aEqlQQjiXixT5i9S4DFtiAap8ZjF6pOgfY2ALHOizins/QqWyB8dyLxSoXdzt7JixmKcFmHkbL9XahO28BlVUA==",
"version": "2.0.0-alpha.1",
"resolved": "https://registry.npmjs.org/@rspack/core/-/core-2.0.0-alpha.1.tgz",
"integrity": "sha512-2KK3hbxrRqzxtzg+ka7LsiEKIWIGIQz317k9HHC2U4IC5yLJ31K8y/vQfA1aIT2QcFls9gW7GyRjp8A4X5cvLA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@rspack/binding": "2.0.0-beta.0",
"@rspack/binding": "2.0.0-alpha.1",
"@rspack/lite-tapable": "1.1.0"
},
"engines": {
@@ -371,20 +372,21 @@
}
},
"node_modules/@rspress/core": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/@rspress/core/-/core-2.0.3.tgz",
"integrity": "sha512-a+JJFiALqMxGJBqR38/lkN6tas42UF4jRIhu6RilC/3DdqpfqR8j6jjQFOmqoNKo6ZGXW2W+i1Pscn6drvoG3w==",
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@rspress/core/-/core-2.0.2.tgz",
"integrity": "sha512-tU8rUVaPyC8o8k4ezgigRVQuZhBAC41KWdwZZ0BldN6o+QXSEIb722RnxCTpa9FGK2riqcwJgM+OqqcqXsFpmw==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@mdx-js/mdx": "^3.1.1",
"@mdx-js/react": "^3.1.1",
"@rsbuild/core": "2.0.0-beta.3",
"@rsbuild/core": "2.0.0-beta.1",
"@rsbuild/plugin-react": "~1.4.5",
"@rspress/shared": "2.0.3",
"@rspress/shared": "2.0.2",
"@shikijs/rehype": "^3.21.0",
"@types/unist": "^3.0.3",
"@unhead/react": "^2.1.4",
"@unhead/react": "^2.1.2",
"body-scroll-lock": "4.0.0-beta.0",
"cac": "^6.7.14",
"chokidar": "^3.6.0",
@@ -428,39 +430,39 @@
}
},
"node_modules/@rspress/plugin-client-redirects": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/@rspress/plugin-client-redirects/-/plugin-client-redirects-2.0.3.tgz",
"integrity": "sha512-9+SoAbfoxM6OCRWx8jWHHi2zwJDcNaej/URx0CWZk8tvQ618yJW5mXJydknlac62399eYh/F7C3w8TZM3ORGVA==",
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@rspress/plugin-client-redirects/-/plugin-client-redirects-2.0.2.tgz",
"integrity": "sha512-FOxUBDOGP06+1hL4jgbIxUe0XoEduXIQ0rSjWjzpo2mC+qTdhZUGJ0xYE2laQIfJXYv/up5zk25zjxUBnxsejw==",
"dev": true,
"license": "MIT",
"engines": {
"node": "^20.19.0 || >=22.12.0"
},
"peerDependencies": {
"@rspress/core": "^2.0.3"
"@rspress/core": "^2.0.2"
}
},
"node_modules/@rspress/plugin-sitemap": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/@rspress/plugin-sitemap/-/plugin-sitemap-2.0.3.tgz",
"integrity": "sha512-SKa7YEAdkUqya2YjMKbakg3kcYMkXgXhTQdDsHd+QlJWN8j8cDPiCcctMZu8iIPeKZlb+hTJkTWvh27LSIKdOA==",
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@rspress/plugin-sitemap/-/plugin-sitemap-2.0.2.tgz",
"integrity": "sha512-3E0yEif4Pj3RX+QVOsyWXW6IIjuhwh93bhVSmhShmTKi8opH5vnHcRVZZ1z7X/P3MHXFTrC925F8383Sl2qOEg==",
"dev": true,
"license": "MIT",
"engines": {
"node": "^20.19.0 || >=22.12.0"
},
"peerDependencies": {
"@rspress/core": "^2.0.3"
"@rspress/core": "^2.0.2"
}
},
"node_modules/@rspress/shared": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/@rspress/shared/-/shared-2.0.3.tgz",
"integrity": "sha512-yI9G4P165fSsmm6QoYTUrdgUis1aFnDh04GcM4SQIpL3itvEZhGtItgoeGkX9EWbnEjhriwI8mTqDDJIp+vrGA==",
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@rspress/shared/-/shared-2.0.2.tgz",
"integrity": "sha512-9+QC8UL1gV2KpRZx4n55vAl6bE38y7eDnGJhdFSHdJkpFbUCiJDk9ZcR6jD/Rrtq7vlT0gfumUk640pxpi3IDQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@rsbuild/core": "2.0.0-beta.3",
"@rsbuild/core": "2.0.0-beta.1",
"@shikijs/rehype": "^3.21.0",
"gray-matter": "4.0.3",
"lodash-es": "^4.17.23",
@@ -562,6 +564,7 @@
"integrity": "sha512-TXTnIcNJQEKwThMMqBXsZ4VGAza6bvN4pa41Rkqoio6QBKMvo+5lexeTMScGCIxtzgQJzElcvIltani+adC5PQ==",
"dev": true,
"license": "Apache-2.0",
"peer": true,
"dependencies": {
"tslib": "^2.8.0"
}
@@ -664,13 +667,13 @@
"license": "ISC"
},
"node_modules/@unhead/react": {
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/@unhead/react/-/react-2.1.4.tgz",
"integrity": "sha512-3DzMi5nJkUyLVfQF/q78smCvcSy84TTYgTwXVz5s3AjUcLyHro5Z7bLWriwk1dn5+YRfEsec8aPkLCMi5VjMZg==",
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/@unhead/react/-/react-2.1.2.tgz",
"integrity": "sha512-VNKa0JJZq5Jp28VuiOMfjAA7CTLHI0SdW/Hs1ZPq2PsNV/cgxGv8quFBGXWx4gfoHB52pejO929RKjIpYX5+iQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"unhead": "2.1.4"
"unhead": "2.1.2"
},
"funding": {
"url": "https://github.com/sponsors/harlan-zw"
@@ -685,6 +688,7 @@
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
"dev": true,
"license": "MIT",
"peer": true,
"bin": {
"acorn": "bin/acorn"
},
@@ -938,8 +942,7 @@
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
"dev": true,
"license": "MIT",
"peer": true
"license": "MIT"
},
"node_modules/debug": {
"version": "4.4.3",
@@ -2969,6 +2972,7 @@
"integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==",
"dev": true,
"license": "MIT",
"peer": true,
"engines": {
"node": ">=0.10.0"
}
@@ -2979,6 +2983,7 @@
"integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"scheduler": "^0.27.0"
},
@@ -3015,6 +3020,7 @@
"integrity": "sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==",
"dev": true,
"license": "MIT",
"peer": true,
"engines": {
"node": ">=0.10.0"
}
@@ -3482,6 +3488,7 @@
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"dev": true,
"license": "MIT",
"peer": true,
"engines": {
"node": ">=12"
},
@@ -3563,9 +3570,9 @@
}
},
"node_modules/unhead": {
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/unhead/-/unhead-2.1.4.tgz",
"integrity": "sha512-+5091sJqtNNmgfQ07zJOgUnMIMKzVKAWjeMlSrTdSGPB6JSozhpjUKuMfWEoLxlMAfhIvgOU8Me0XJvmMA/0fA==",
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/unhead/-/unhead-2.1.2.tgz",
"integrity": "sha512-vSihrxyb+zsEUfEbraZBCjdE0p/WSoc2NGDrpwwSNAwuPxhYK1nH3eegf02IENLpn1sUhL8IoO84JWmRQ6tILA==",
"dev": true,
"license": "MIT",
"dependencies": {
+622 -2
View File
@@ -1,12 +1,29 @@
use std::collections::BTreeMap;
use api::client::leave_room;
use clap::Subcommand;
use conduwuit::{
Err, Result, debug, info,
Err, Result, RoomVersion, debug, info,
utils::{IterStream, ReadyExt},
warn,
};
use futures::{FutureExt, StreamExt};
use ruma::{OwnedRoomId, OwnedRoomOrAliasId, RoomAliasId, RoomId, RoomOrAliasId};
use ruma::{
Int, OwnedRoomId, OwnedRoomOrAliasId, RoomAliasId, RoomId, RoomOrAliasId,
events::{
StateEventType,
room::{
create::RoomCreateEventContent,
history_visibility::{HistoryVisibility, RoomHistoryVisibilityEventContent},
join_rules::{JoinRule, RoomJoinRulesEventContent},
member::{MembershipState, RoomMemberEventContent},
power_levels::RoomPowerLevelsEventContent,
tombstone::RoomTombstoneEventContent,
},
},
exports::serde::Deserialize,
};
use serde_json::json;
use crate::{admin_command, admin_command_dispatch, get_room_info};
@@ -43,6 +60,59 @@ pub enum RoomModerationCommand {
/// information
no_details: bool,
},
/// - Take over a room by puppeting a local user into giving you a higher
/// power level
Takeover {
/// Whether to force joining the room if no local users are in the room
#[arg(long)]
force: bool,
/// The room in the format of `!roomid:example.com` or a room alias in
/// the format of `#roomalias:example.com`
room: OwnedRoomOrAliasId,
},
/// - Shut down a room, as much is possible. **This is immediate and
/// irreversible**.
///
/// This command requires that you have a local user in the room with at
/// least a moderator power level. It will first attempt to raise power
/// levels so that nobody can use the room further, then remove the
/// canonical alias event, sets the history visibility to `joined`,
/// sets the join rules to `org.continuwuity.shutdown` (preventing anyone
/// from joining even with an invite), and then bans or kicks all users,
/// setting the MSC4293 "redact events" flag on those users if possible.
/// Finally, it will send a room tombstone event, which will effectively
/// make the room unusable on most clients even if the room state resets.
///
/// This effectively will make the room unusable, unjoinable, and removes
/// everyone from it. This is as close to a "shutdown" as you can get with
/// federation.
ShutdownRoom {
/// If no local users with a power level are joined to the room, setting
/// this flag will attempt one, and will join the user with the
/// highest power level to the room to perform the shutdown.
///
/// If this flag is not set, and no local users can perform the
/// shutdown, no further attempt will be made.
#[arg(long)]
force: bool,
/// Whether to use MSC4293 fields to indicate that all messages in the
/// room should be redacted. This will make it more difficult for
/// clients that implement MSC4293 (like Element) to render the room
/// in the event users manage to rejoin.
#[arg(long)]
redact: bool,
///
#[arg(long)]
yes_i_am_sure_i_want_to_irreversibly_shutdown_this_room_destroying_it_in_the_process:
bool,
/// The room in the format of `!roomid:example.com` or a room alias in
/// the format of `#roomalias:example.com`
room: OwnedRoomOrAliasId,
},
}
#[admin_command]
@@ -468,3 +538,553 @@ async fn list_banned_rooms(&self, no_details: bool) -> Result {
self.write_str(&format!("Rooms Banned ({num}):\n```\n{body}\n```",))
.await
}
#[admin_command]
async fn takeover(&self, force: bool, room: OwnedRoomOrAliasId) -> Result {
let room_id = if room.is_room_id() {
let room_id = match RoomId::parse(&room) {
| Ok(room_id) => room_id,
| Err(e) => {
return Err!(
"Failed to parse room ID {room}. Please note that this requires a full room \
ID (`!awIh6gGInaS5wLQJwa:example.com`) or a room alias \
(`#roomalias:example.com`): {e}"
);
},
};
room_id.to_owned()
} else if room.is_room_alias_id() {
let room_alias = match RoomAliasId::parse(&room) {
| Ok(room_alias) => room_alias,
| Err(e) => {
return Err!(
"Failed to parse room ID {room}. Please note that this requires a full room \
ID (`!awIh6gGInaS5wLQJwa:example.com`) or a room alias \
(`#roomalias:example.com`): {e}"
);
},
};
match self
.services
.rooms
.alias
.resolve_alias(room_alias, None)
.await
{
| Ok((room_id, servers)) => {
debug!(
?room_id,
?servers,
"Got federation response fetching room ID for room {room}"
);
room_id
},
| Err(e) => {
return Err!("Failed to resolve room alias {room} to a room ID: {e}");
},
}
} else {
return Err!(
"Room specified is not a room ID or room alias. Please note that this requires a \
full room ID (`!awIh6gGInaS5wLQJwa:example.com`) or a room alias \
(`#roomalias:example.com`)",
);
};
let room_version =
RoomVersion::new(&self.services.rooms.state.get_room_version(&room_id).await?)?;
let Ok(create_content) = self
.services
.rooms
.state_accessor
.room_state_get_content::<RoomCreateEventContent>(
&room_id,
&StateEventType::RoomCreate,
"",
)
.await
else {
return Err!("Failed to get room create event");
};
let mut power_levels = match self
.services
.rooms
.state_accessor
.room_state_get_content::<RoomPowerLevelsEventContent>(
&room_id,
&StateEventType::RoomPowerLevels,
"",
)
.await
{
| Ok(content) => content,
| Err(e) => {
return Err!("Failed to get power levels for room {room_id}: {e}");
},
};
let local_creators = if room_version.explicitly_privilege_room_creators
&& create_content.additional_creators.is_some()
{
create_content
.additional_creators
.clone()
.unwrap()
.into_iter()
.filter(|user_id| self.services.globals.user_is_local(user_id))
.collect::<Vec<_>>()
} else {
vec![]
};
let local_users = power_levels
.users
.iter()
.filter(|(user_id, _)| self.services.globals.user_is_local(user_id))
.map(|(user_id, level)| (user_id.clone(), *level))
.collect::<BTreeMap<_, _>>();
let min_pl = power_levels
.events
.get(&StateEventType::RoomPowerLevels.into())
.copied()
.unwrap_or(power_levels.state_default);
let mut ordered_users = local_users
.iter()
.chain(local_creators.iter().map(|user_id| (user_id, &Int::MAX)))
.map(|(user_id, level)| {
if local_creators.contains(user_id) {
(user_id, Int::MAX)
} else {
(user_id, *level)
}
})
.filter(|(user_id, level)| *level >= min_pl || local_creators.contains(*user_id))
.collect::<Vec<_>>();
ordered_users.sort_by_key(|(_, level)| level.saturating_mul(Int::from(-1)));
for (user_id, powerlevel) in ordered_users {
if !self
.services
.rooms
.state_cache
.is_joined(user_id.as_ref(), &room_id)
.await
{
if !force {
continue;
}
info!("Joining {user_id} to room {room_id} to perform takeover");
let lock = self.services.rooms.state.mutex.lock(&room_id).await;
if let Err(e) = self
.services
.rooms
.timeline
.build_and_append_pdu(
conduwuit::pdu::Builder::state(
String::from(user_id.as_str()),
&RoomMemberEventContent::new(MembershipState::Join),
),
user_id,
Some(&room_id),
&lock,
)
.await
{
warn!("Failed to join {user_id} to room {room_id} to perform takeover: {e}");
drop(lock);
continue;
}
drop(lock);
}
info!("Promoting you to power level {powerlevel} in room {room_id} via {user_id}");
let lock = self.services.rooms.state.mutex.lock(&room_id).await;
power_levels
.users
.insert(self.sender.expect("you should exist").to_owned(), powerlevel);
if let Err(e) = self
.services
.rooms
.timeline
.build_and_append_pdu(
conduwuit::pdu::Builder::state(String::new(), &power_levels),
user_id,
Some(&room_id),
&lock,
)
.await
{
warn!(
"Failed to promote you to power level {powerlevel} in room {room_id} via \
{user_id}: {e}"
);
drop(lock);
continue;
}
return self
.write_str(&format!(
"Successfully promoted you to power level {powerlevel} in room {room_id} via \
{user_id}"
))
.await;
}
self.write_str("Failed to promote you, no local users with sufficient power level found.")
.await
}
#[admin_command]
async fn shutdown_room(
&self,
force: bool,
redact: bool,
yes_i_am_sure_i_want_to_irreversibly_shutdown_this_room_destroying_it_in_the_process: bool,
room: OwnedRoomOrAliasId,
) -> Result {
let room_id = if room.is_room_id() {
let room_id = match RoomId::parse(&room) {
| Ok(room_id) => room_id,
| Err(e) => {
return Err!(
"Failed to parse room ID {room}. Please note that this requires a full room \
ID (`!awIh6gGInaS5wLQJwa:example.com`) or a room alias \
(`#roomalias:example.com`): {e}"
);
},
};
room_id.to_owned()
} else if room.is_room_alias_id() {
let room_alias = match RoomAliasId::parse(&room) {
| Ok(room_alias) => room_alias,
| Err(e) => {
return Err!(
"Failed to parse room ID {room}. Please note that this requires a full room \
ID (`!awIh6gGInaS5wLQJwa:example.com`) or a room alias \
(`#roomalias:example.com`): {e}"
);
},
};
match self
.services
.rooms
.alias
.resolve_alias(room_alias, None)
.await
{
| Ok((room_id, servers)) => {
debug!(
?room_id,
?servers,
"Got federation response fetching room ID for room {room}"
);
room_id
},
| Err(e) => {
return Err!("Failed to resolve room alias {room} to a room ID: {e}");
},
}
} else {
return Err!(
"Room specified is not a room ID or room alias. Please note that this requires a \
full room ID (`!awIh6gGInaS5wLQJwa:example.com`) or a room alias \
(`#roomalias:example.com`)",
);
};
if !yes_i_am_sure_i_want_to_irreversibly_shutdown_this_room_destroying_it_in_the_process {
return Err!(
"This command is irreversible and will immediately shutdown the room, making it \
completely unusable if successful. If you are sure you want to do this, add the \
flag --yes-i-am-sure-i-want-to-irreversibly-shutdown-this-room-destroying-it-in-the-process \
to your command."
);
}
let mut power_levels: RoomPowerLevelsEventContent = match self
.services
.rooms
.state_accessor
.room_state_get_content(&room_id, &StateEventType::RoomPowerLevels, "")
.await
.map_err(|e| Err!("Failed to get power levels for room {room_id}: {e}"))
{
| Ok(content) => content,
| Err(e) => {
return e;
},
};
let mut joined_users = self
.services
.rooms
.state_cache
.room_members(&room_id)
.map(ToOwned::to_owned)
.collect::<Vec<_>>()
.await;
let room_version =
RoomVersion::new(&self.services.rooms.state.get_room_version(&room_id).await?)?;
let Ok(create_content) = self
.services
.rooms
.state_accessor
.room_state_get_content::<RoomCreateEventContent>(
&room_id,
&StateEventType::RoomCreate,
"",
)
.await
else {
return Err!("Failed to get room create event");
};
let local_creators = if room_version.explicitly_privilege_room_creators
&& create_content.additional_creators.is_some()
{
create_content
.additional_creators
.unwrap()
.into_iter()
.filter(|user_id| self.services.globals.user_is_local(user_id))
.collect::<Vec<_>>()
} else {
vec![]
};
let local_users = power_levels
.users
.iter()
.filter(|(user_id, _)| self.services.globals.user_is_local(user_id))
.map(|(user_id, level)| (user_id.clone(), *level))
.collect::<BTreeMap<_, _>>();
let mut ordered_users = local_users
.iter()
.chain(local_creators.iter().map(|user_id| (user_id, &Int::MAX)))
.map(|(user_id, level)| {
if local_creators.contains(user_id) {
(user_id, Int::MAX)
} else {
(user_id, *level)
}
})
.collect::<Vec<_>>();
ordered_users.sort_by_key(|(_, level)| level.saturating_mul(Int::from(-1)));
let mut changed_join_rules = false;
let mut changed_history_visibility = false;
let mut changed_power_levels = false;
let mut sent_tombstone = false;
let mut removed_ok: u32 = 0;
for (user_id, powerlevel) in ordered_users {
if !self
.services
.rooms
.state_cache
.is_joined(user_id.as_ref(), &room_id)
.await
{
if !force {
continue;
}
info!("Joining {user_id} to room {room_id} to perform shutdown");
let lock = self.services.rooms.state.mutex.lock(&room_id).await;
if let Err(e) = self
.services
.rooms
.timeline
.build_and_append_pdu(
conduwuit::pdu::Builder::state(
String::from(user_id.as_str()),
&RoomMemberEventContent::new(MembershipState::Join),
),
user_id,
Some(&room_id),
&lock,
)
.await
{
warn!("Failed to join {user_id} to room {room_id} to perform shutdown: {e}");
drop(lock);
continue;
}
drop(lock);
}
if !changed_power_levels {
info!("Raising minimum power levels to {powerlevel} via {user_id}");
power_levels.events_default = power_levels.events_default.max(powerlevel);
power_levels.state_default = power_levels.state_default.max(powerlevel);
if power_levels.users_default < powerlevel {
power_levels.users_default = Int::MIN;
}
power_levels.kick = power_levels.kick.max(powerlevel);
power_levels.ban = power_levels.ban.max(powerlevel);
for (event_type, event_pl) in power_levels.events.clone() {
power_levels
.events
.insert(event_type, event_pl.max(powerlevel));
}
for (user, user_pl) in power_levels.users.clone() {
if user_pl < powerlevel {
power_levels.users.remove(&user);
}
}
let lock = self.services.rooms.state.mutex.lock(&room_id).await;
if let Err(e) = self
.services
.rooms
.timeline
.build_and_append_pdu(
conduwuit::pdu::Builder::state(String::new(), &power_levels.clone()),
user_id,
Some(&room_id),
&lock,
)
.await
{
warn!(
"Failed to raise power levels to {powerlevel} in room {room_id} via \
{user_id}: {e}"
);
} else {
changed_power_levels = true;
}
drop(lock);
}
if !changed_join_rules {
info!("Setting room to private via {user_id}");
// NOTE: Setting the room to `private` soft-bricks it, as new joins with this
// join rule can actually be authorised.
let lock = self.services.rooms.state.mutex.lock(&room_id).await;
if let Err(e) = self
.services
.rooms
.timeline
.build_and_append_pdu(
conduwuit::pdu::Builder::state(
String::new(),
&RoomJoinRulesEventContent::new(
JoinRule::deserialize(json!("\"org.continuwuity.shutdown\""))
.expect("valid fixed json"),
),
),
user_id,
Some(&room_id),
&lock,
)
.await
{
warn!("Failed to set room to private in room {room_id} via {user_id}: {e}");
} else {
changed_join_rules = true;
}
drop(lock);
}
if !changed_history_visibility {
info!("Setting history visibility to joined via {user_id}");
let lock = self.services.rooms.state.mutex.lock(&room_id).await;
if let Err(e) = self
.services
.rooms
.timeline
.build_and_append_pdu(
conduwuit::pdu::Builder::state(
String::new(),
&RoomHistoryVisibilityEventContent::new(HistoryVisibility::Joined),
),
user_id,
Some(&room_id),
&lock,
)
.await
{
warn!(
"Failed to set history visibility to joined in room {room_id} via \
{user_id}: {e}"
);
} else {
changed_history_visibility = true;
}
drop(lock);
}
info!("Removing {} users in {room_id} via {user_id}", joined_users.len());
let lock = self.services.rooms.state.mutex.lock(&room_id).await;
for remove_user in &joined_users.clone() {
if remove_user == user_id || self.services.admin.user_is_admin(user_id).await {
continue;
}
let user_pl = power_levels
.users
.get(remove_user)
.copied()
.unwrap_or(power_levels.users_default);
let new_membership = if power_levels.ban <= powerlevel && user_pl < powerlevel {
MembershipState::Ban
} else {
MembershipState::Leave
};
debug!("Removing {remove_user} via {user_id}");
if let Err(e) = self
.services
.rooms
.timeline
.build_and_append_pdu(
conduwuit::pdu::Builder::state(
String::from(remove_user.as_str()),
&RoomMemberEventContent {
membership: new_membership.clone(),
redact_events: if redact { Some(true) } else { None },
..RoomMemberEventContent::new(new_membership.clone())
},
),
user_id,
Some(&room_id),
&lock,
)
.await
{
warn!("Failed to remove {remove_user} via {user_id}: {e}");
continue;
}
removed_ok = removed_ok.saturating_add(1);
if self.services.globals.user_is_local(remove_user) {
self.services
.rooms
.state_cache
.forget(&room_id, remove_user);
}
joined_users.retain(|u| u != remove_user);
}
if !sent_tombstone {
info!("Sending tombstone event for {room_id} via {user_id}");
if let Err(e) = self
.services
.rooms
.timeline
.build_and_append_pdu(
conduwuit::pdu::Builder::state(
String::new(),
&RoomTombstoneEventContent::new(
format!("Room {room_id} has been shut down"),
room_id.clone(),
),
),
user_id,
Some(&room_id),
&lock,
)
.await
{
warn!("Failed to send tombstone event for {room_id} via {user_id}: {e}");
} else {
sent_tombstone = true;
}
}
}
self.write_str(&format!(
"Room shutdown complete, removed {removed_ok} users, changed join rules: \
{changed_join_rules}.\nConsider banning the room with `ban-room`.",
))
.await
}
+16 -44
View File
@@ -30,8 +30,7 @@ use ruma::{
api::client::sync::sync_events::{self, DeviceLists, UnreadNotificationsCount},
directory::RoomTypeFilter,
events::{
AnyRawAccountDataEvent, AnySyncEphemeralRoomEvent, AnySyncStateEvent, StateEventType,
TimelineEventType,
AnyRawAccountDataEvent, AnySyncEphemeralRoomEvent, StateEventType, TimelineEventType,
room::member::{MembershipState, RoomMemberEventContent},
typing::TypingEventContent,
},
@@ -534,9 +533,6 @@ where
}
});
let required_state =
collect_required_state(services, room_id, required_state_request).await;
let room_events: Vec<_> = timeline_pdus
.iter()
.stream()
@@ -555,6 +551,21 @@ where
}
}
let required_state = required_state_request
.iter()
.stream()
.filter_map(|state| async move {
services
.rooms
.state_accessor
.room_state_get(room_id, &state.0, &state.1)
.await
.map(Event::into_format)
.ok()
})
.collect()
.await;
// Heroes
let heroes: Vec<_> = services
.rooms
@@ -678,45 +689,6 @@ where
Ok(rooms)
}
/// Collect the required state events for a room
async fn collect_required_state(
services: &Services,
room_id: &RoomId,
required_state_request: &BTreeSet<TypeStateKey>,
) -> Vec<Raw<AnySyncStateEvent>> {
let mut required_state = Vec::new();
for (event_type, state_key) in required_state_request {
if state_key.as_str() == "*" {
if let Ok(keys) = services
.rooms
.state_accessor
.room_state_keys(room_id, event_type)
.await
{
for key in keys {
if let Ok(event) = services
.rooms
.state_accessor
.room_state_get(room_id, event_type, &key)
.await
{
required_state.push(Event::into_format(event));
}
}
break;
}
} else if let Ok(event) = services
.rooms
.state_accessor
.room_state_get(room_id, event_type, state_key)
.await
{
required_state.push(Event::into_format(event));
}
}
required_state
}
async fn collect_typing_events(
services: &Services,
sender_user: &UserId,
-1
View File
@@ -27,7 +27,6 @@ pub(crate) async fn well_known_client(
identity_server: None,
sliding_sync_proxy: Some(SlidingSyncProxyInfo { url: client_url }),
tile_server: None,
rtc_foci: services.config.well_known.rtc_focus_server_urls.clone(),
})
}
+1 -17
View File
@@ -19,7 +19,7 @@ pub use figment::{Figment, value::Value as FigmentValue};
use regex::RegexSet;
use ruma::{
OwnedRoomId, OwnedRoomOrAliasId, OwnedServerName, OwnedUserId, RoomVersionId,
api::client::discovery::{discover_homeserver::RtcFocusInfo, discover_support::ContactRole},
api::client::discovery::discover_support::ContactRole,
};
use serde::{Deserialize, de::IgnoredAny};
use url::Url;
@@ -2111,19 +2111,6 @@ pub struct WellKnownConfig {
/// If no email or mxid is specified, all of the server's admins will be
/// listed.
pub support_mxid: Option<OwnedUserId>,
/// A list of MatrixRTC foci URLs which will be served as part of the
/// MSC4143 client endpoint at /.well-known/matrix/client. If you're
/// setting up livekit, you'd want something like:
/// rtc_focus_server_urls = [
/// { type = "livekit", livekit_service_url = "https://livekit.example.com" },
/// ]
///
/// To disable, set this to be an empty vector (`[]`).
///
/// default: []
#[serde(default = "default_rtc_focus_urls")]
pub rtc_focus_server_urls: Vec<RtcFocusInfo>,
}
#[derive(Clone, Copy, Debug, Deserialize, Default)]
@@ -2621,9 +2608,6 @@ fn default_rocksdb_stats_level() -> u8 { 1 }
#[inline]
pub fn default_default_room_version() -> RoomVersionId { RoomVersionId::V11 }
#[must_use]
pub fn default_rtc_focus_urls() -> Vec<RtcFocusInfo> { vec![] }
fn default_ip_range_denylist() -> Vec<String> {
vec![
"127.0.0.0/8".to_owned(),
-65
View File
@@ -105,68 +105,3 @@ body:not(.notTopArrived) header.rp-nav {
.rspress-logo {
height: 32px;
}
/* pre-hero */
.custom-section {
padding: 4rem 1.5rem;
background: var(--rp-c-bg);
}
.custom-cards {
display: flex;
gap: 2rem;
max-width: 800px;
margin: 0 auto;
justify-content: center;
flex-wrap: wrap;
}
.custom-card {
padding: 2rem;
border: 1px solid var(--rp-c-divider-light);
border-radius: 12px;
background: var(--rp-c-bg-soft);
text-decoration: none;
color: var(--rp-c-text-1);
transition: all 0.3s ease;
display: flex;
flex-direction: column;
flex: 1;
min-width: 280px;
max-width: 350px;
}
.custom-card:hover {
border-color: var(--rp-c-brand);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
transform: translateY(-2px);
}
.custom-card h3 {
margin: 0 0 1rem 0;
font-size: 1.25rem;
font-weight: 600;
color: var(--rp-c-text-0);
}
.custom-card p {
margin: 0 0 1.5rem 0;
color: var(--rp-c-text-2);
line-height: 1.6;
flex: 1;
}
.custom-card-button {
display: inline-block;
padding: 0.5rem 1.5rem;
background: var(--rp-c-brand);
color: white;
border-radius: 6px;
font-weight: 500;
text-align: center;
transition: background 0.2s ease;
}
.custom-card:hover .custom-card-button {
background: var(--rp-c-brand-light);
}
-17
View File
@@ -12,23 +12,6 @@ function HomeLayout(props: HomeLayoutProps) {
return (
<BasicHomeLayout
beforeFeatures={
frontmatter.beforeFeatures ? (
<section className="custom-section">
<div className="rp-container">
<div className="custom-cards">
{frontmatter.beforeFeatures.map((item: any, index: number) => (
<a key={index} href={item.link} className="custom-card" target="_blank" rel="noopener noreferrer">
<h3>{item.title}</h3>
<p>{item.details}</p>
<span className="custom-card-button">{item.buttonText || 'Learn More'} </span>
</a>
))}
</div>
</div>
</section>
) : <></>
}
afterFeatures={
(frontmatter.doc) ?
<main className="rp-doc-layout__doc-container">