From 65be63fdd251b82733c1d95906c077bb22a79ef2 Mon Sep 17 00:00:00 2001 From: jkhsjdhjs Date: Sat, 24 Aug 2024 17:47:24 +0200 Subject: [PATCH 01/30] Fix PluginWebApp base path handling (#240) Previously, the webapp handler would match without respect to the trailing slash, e.g. matching "foo" for "foo2". This behavior is changed to respect the trailing slash. Fixes #239 --- maubot/server.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/maubot/server.py b/maubot/server.py index 097fe5b..dd1101e 100644 --- a/maubot/server.py +++ b/maubot/server.py @@ -64,14 +64,14 @@ class MaubotServer: if request.path.startswith(path): request = request.clone( rel_url=request.rel_url.with_path( - request.rel_url.path[len(path) :] + request.rel_url.path[len(path) - 1 :] ).with_query(request.query_string) ) return await app.handle(request) return web.Response(status=404) def get_instance_subapp(self, instance_id: str) -> tuple[PluginWebApp, str]: - subpath = self.config["server.plugin_base_path"] + instance_id + subpath = self.config["server.plugin_base_path"] + instance_id + "/" url = self.config["server.public_url"] + subpath try: return self.plugin_routes[subpath], url @@ -82,7 +82,7 @@ class MaubotServer: def remove_instance_webapp(self, instance_id: str) -> None: try: - subpath = self.config["server.plugin_base_path"] + instance_id + subpath = self.config["server.plugin_base_path"] + instance_id + "/" self.plugin_routes.pop(subpath).clear() except KeyError: return From 472fb9f6acbb0e93c2d819d17077c25548aca284 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Sun, 8 Sep 2024 00:58:36 +0300 Subject: [PATCH 02/30] Remove outdated comment [skip ci] --- maubot/example-config.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/maubot/example-config.yaml b/maubot/example-config.yaml index a16ea19..f21ac1e 100644 --- a/maubot/example-config.yaml +++ b/maubot/example-config.yaml @@ -1,5 +1,4 @@ # The full URI to the database. SQLite and Postgres are fully supported. -# Other DBMSes supported by SQLAlchemy may or may not work. # Format examples: # SQLite: sqlite:filename.db # Postgres: postgresql://username:password@hostname/dbname From dd58135c94d1a312e7ebad82815c41b638395e17 Mon Sep 17 00:00:00 2001 From: Dominik Rimpf Date: Thu, 3 Oct 2024 23:59:34 +0200 Subject: [PATCH 03/30] Update media endpoints in management frontend (#253) --- maubot/management/frontend/src/api.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/maubot/management/frontend/src/api.js b/maubot/management/frontend/src/api.js index 5c1fd55..b173e4c 100644 --- a/maubot/management/frontend/src/api.js +++ b/maubot/management/frontend/src/api.js @@ -205,7 +205,7 @@ export const getClients = () => defaultGet("/clients") export const getClient = id => defaultGet(`/clients/${id}`) export async function uploadAvatar(id, data, mime) { - const resp = await fetch(`${BASE_PATH}/proxy/${id}/_matrix/media/r0/upload`, { + const resp = await fetch(`${BASE_PATH}/proxy/${id}/_matrix/media/v3/upload`, { headers: getHeaders(mime), body: data, method: "POST", @@ -217,8 +217,8 @@ export function getAvatarURL({ id, avatar_url }) { if (!avatar_url?.startsWith("mxc://")) { return null } - avatar_url = avatar_url.substr("mxc://".length) - return `${BASE_PATH}/proxy/${id}/_matrix/media/r0/download/${avatar_url}?access_token=${ + avatar_url = avatar_url.substring("mxc://".length) + return `${BASE_PATH}/proxy/${id}/_matrix/client/v1/media/download/${avatar_url}?access_token=${ localStorage.accessToken}` } From bceacb97a0a862fb9a626959a83442c616c0c65f Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Fri, 4 Oct 2024 00:58:15 +0300 Subject: [PATCH 04/30] Cut off plaintext body if the event is too long --- maubot/matrix.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/maubot/matrix.py b/maubot/matrix.py index 0f3e36b..87c7b70 100644 --- a/maubot/matrix.py +++ b/maubot/matrix.py @@ -62,7 +62,10 @@ async def parse_formatted( html = message else: return message, escape(message) - return (await MaubotHTMLParser().parse(html)).text, html + text = (await MaubotHTMLParser().parse(html)).text + if len(text) + len(html) > 60000: + text = text[:100] + "[long message cut off]" + return text, html class MaubotMessageEvent(MessageEvent): From 48cc00f5910a43a09ef09e4393f638331294c11b Mon Sep 17 00:00:00 2001 From: nexy7574 Date: Thu, 2 Jan 2025 09:18:06 +0000 Subject: [PATCH 05/30] Update asyncpg dependency to fix python 3.13 support (#256) --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 7de02dd..5a4312d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,7 @@ mautrix>=0.20.6,<0.21 aiohttp>=3,<4 yarl>=1,<2 -asyncpg>=0.20,<0.30 +asyncpg>=0.20,<0.31 aiosqlite>=0.16,<0.21 commonmark>=0.9,<1 ruamel.yaml>=0.15.35,<0.19 From 46aed7e1d2bbf1e7a161c8ce4cb2c8a50356c229 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Fri, 3 Jan 2025 12:26:25 +0200 Subject: [PATCH 06/30] Relax asyncpg and aiosqlite version requirement --- requirements.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/requirements.txt b/requirements.txt index 5a4312d..e1df001 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,8 +1,8 @@ -mautrix>=0.20.6,<0.21 +mautrix>=0.20.7,<0.21 aiohttp>=3,<4 yarl>=1,<2 -asyncpg>=0.20,<0.31 -aiosqlite>=0.16,<0.21 +asyncpg>=0.20,<1 +aiosqlite>=0.16,<1 commonmark>=0.9,<1 ruamel.yaml>=0.15.35,<0.19 attrs>=18.1.0 From 813fee7a2ca8e945d75e10887e65a4451b9c076d Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Fri, 3 Jan 2025 12:26:51 +0200 Subject: [PATCH 07/30] Update linters --- .github/workflows/python-lint.yml | 4 ++-- .pre-commit-config.yaml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/python-lint.yml b/.github/workflows/python-lint.yml index b2fe29f..28d6df2 100644 --- a/.github/workflows/python-lint.yml +++ b/.github/workflows/python-lint.yml @@ -9,14 +9,14 @@ jobs: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: - python-version: "3.12" + python-version: "3.13" - uses: isort/isort-action@master with: sortPaths: "./maubot" - uses: psf/black@stable with: src: "./maubot" - version: "24.2.0" + version: "24.10.0" - name: pre-commit run: | pip install pre-commit diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e3c5bb8..4a6328e 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.5.0 + rev: v5.0.0 hooks: - id: trailing-whitespace exclude_types: [markdown] @@ -8,7 +8,7 @@ repos: - id: check-yaml - id: check-added-large-files - repo: https://github.com/psf/black - rev: 24.2.0 + rev: 24.10.0 hooks: - id: black language_version: python3 From 01b5f53d906c917db2bf1de06d757468cc75b68d Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Fri, 3 Jan 2025 12:31:01 +0200 Subject: [PATCH 08/30] Update Alpine and Node --- .gitlab-ci.yml | 2 +- Dockerfile | 4 ++-- Dockerfile.ci | 2 +- maubot/standalone/Dockerfile | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 8c8c6db..50d0c15 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -10,7 +10,7 @@ default: - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY build frontend: - image: node:20-alpine + image: node:22-alpine stage: build frontend before_script: [] variables: diff --git a/Dockerfile b/Dockerfile index b4ee106..2c6bad4 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,9 +1,9 @@ -FROM node:20 AS frontend-builder +FROM node:22 AS frontend-builder COPY ./maubot/management/frontend /frontend RUN cd /frontend && yarn --prod && yarn build -FROM alpine:3.20 +FROM alpine:3.21 RUN apk add --no-cache \ python3 py3-pip py3-setuptools py3-wheel \ diff --git a/Dockerfile.ci b/Dockerfile.ci index 3f83a1c..9712a16 100644 --- a/Dockerfile.ci +++ b/Dockerfile.ci @@ -1,4 +1,4 @@ -FROM alpine:3.20 +FROM alpine:3.21 RUN apk add --no-cache \ python3 py3-pip py3-setuptools py3-wheel \ diff --git a/maubot/standalone/Dockerfile b/maubot/standalone/Dockerfile index 8cfa8cd..54623f2 100644 --- a/maubot/standalone/Dockerfile +++ b/maubot/standalone/Dockerfile @@ -1,4 +1,4 @@ -FROM docker.io/alpine:3.20 +FROM docker.io/alpine:3.21 RUN apk add --no-cache \ python3 py3-pip py3-setuptools py3-wheel \ From 6c7d0754f80bead267bbcf126d5b9e58460febcb Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Fri, 3 Jan 2025 12:32:04 +0200 Subject: [PATCH 09/30] Add Python 3.13 to classifiers --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index cf16f7e..838196f 100644 --- a/setup.py +++ b/setup.py @@ -53,6 +53,7 @@ setuptools.setup( "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", ], entry_points=""" [console_scripts] From c3458eab5808ab1fa911eff902750064fdfedcce Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Fri, 3 Jan 2025 12:40:46 +0200 Subject: [PATCH 10/30] Bump version to 0.5.1 --- CHANGELOG.md | 16 ++++++++++++++++ maubot/__meta__.py | 2 +- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b112eb2..3a04252 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,19 @@ +# v0.5.1 (2025-01-03) + +* Updated Docker image to Alpine 3.21. +* Updated media upload/download endpoints in management frontend + (thanks to [@domrim] in [#253]). +* Fixed plugin web app base path not including a trailing slash + (thanks to [@jkhsjdhjs] in [#240]). +* Changed markdown parsing to cut off plaintext body if necessary to allow + longer formatted messages. +* Updated dependencies to fix Python 3.13 compatibility. + +[@domrim]: https://github.com/domrim +[@jkhsjdhjs]: https://github.com/jkhsjdhjs +[#253]: https://github.com/maubot/maubot/pull/253 +[#240]: https://github.com/maubot/maubot/pull/240 + # v0.5.0 (2024-08-24) * Dropped Python 3.9 support. diff --git a/maubot/__meta__.py b/maubot/__meta__.py index 3d18726..dd9b22c 100644 --- a/maubot/__meta__.py +++ b/maubot/__meta__.py @@ -1 +1 @@ -__version__ = "0.5.0" +__version__ = "0.5.1" From 094e1eca35fd7d859bdf03db0555925986265996 Mon Sep 17 00:00:00 2001 From: Binesh Bannerjee Date: Wed, 22 Jan 2025 13:10:39 -0500 Subject: [PATCH 11/30] Fix autojoin and online flags not being applied if set during client creation (#258) --- maubot/management/api/client.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/maubot/management/api/client.py b/maubot/management/api/client.py index 2a8964c..d2ad35d 100644 --- a/maubot/management/api/client.py +++ b/maubot/management/api/client.py @@ -78,8 +78,8 @@ async def _create_client(user_id: UserID | None, data: dict) -> web.Response: ) client.enabled = data.get("enabled", True) client.sync = data.get("sync", True) - client.autojoin = data.get("autojoin", True) - client.online = data.get("online", True) + await client.update_autojoin(data.get("autojoin", True), save=False) + await client.update_online(data.get("online", True), save=False) client.displayname = data.get("displayname", "disable") client.avatar_url = data.get("avatar_url", "disable") await client.update() From c09eb195f87a022e0530b4bd8974cd4cfb1eca01 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Tue, 28 Jan 2025 16:55:18 +0200 Subject: [PATCH 12/30] Add comment --- maubot/management/frontend/src/api.js | 1 + 1 file changed, 1 insertion(+) diff --git a/maubot/management/frontend/src/api.js b/maubot/management/frontend/src/api.js index b173e4c..d1a51b8 100644 --- a/maubot/management/frontend/src/api.js +++ b/maubot/management/frontend/src/api.js @@ -218,6 +218,7 @@ export function getAvatarURL({ id, avatar_url }) { return null } avatar_url = avatar_url.substring("mxc://".length) + // Note: the maubot backend will replace the query param with an authorization header return `${BASE_PATH}/proxy/${id}/_matrix/client/v1/media/download/${avatar_url}?access_token=${ localStorage.accessToken}` } From fe4d2f02bb8ad8ee37a46777a968fb5988bc7737 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Tue, 28 Jan 2025 16:55:42 +0200 Subject: [PATCH 13/30] Fix clearing PluginWebApp Fixes #233 --- maubot/plugin_server.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/maubot/plugin_server.py b/maubot/plugin_server.py index 9dd2df4..e5c246c 100644 --- a/maubot/plugin_server.py +++ b/maubot/plugin_server.py @@ -40,6 +40,8 @@ class PluginWebApp(web.UrlDispatcher): self._resources = [] self._named_resources = {} self._middleware = [] + self._resource_index = {} + self._matched_sub_app_resources = [] async def handle(self, request: web.Request) -> web.StreamResponse: match_info = await self.resolve(request) From f0ade0a04333fe8b347a4781474d0cb397c5c39f Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Sun, 4 May 2025 00:43:35 +0300 Subject: [PATCH 14/30] Clarify type of admins map [skip ci] --- maubot/example-config.yaml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/maubot/example-config.yaml b/maubot/example-config.yaml index f21ac1e..0a6c8ac 100644 --- a/maubot/example-config.yaml +++ b/maubot/example-config.yaml @@ -78,8 +78,9 @@ homeservers: # When this is empty, `mbc auth --register` won't work, but `mbc auth` (login) will. secret: null -# List of administrator users. Plaintext passwords will be bcrypted on startup. Set empty password -# to prevent normal login. Root is a special user that can't have a password and will always exist. +# List of administrator users. Each key is a username and the value is the password. +# Plaintext passwords will be bcrypted on startup. Set empty password to prevent normal login. +# Root is a special user that can't have a password and will always exist. admins: root: "" From 80b65d6a2f69fda8500c570e3836e2ac2e8815ad Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Mon, 5 May 2025 00:59:20 +0300 Subject: [PATCH 15/30] Improve tombstone handling --- maubot/client.py | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/maubot/client.py b/maubot/client.py index bdb76fc..f06af83 100644 --- a/maubot/client.py +++ b/maubot/client.py @@ -353,8 +353,33 @@ class Client(DBClient): if not evt.content.replacement_room: self.log.info(f"{evt.room_id} tombstoned with no replacement, ignoring") return + is_joined = await self.client.state_store.is_joined( + evt.content.replacement_room, + self.client.mxid, + ) + if is_joined: + self.log.debug( + f"Ignoring tombstone from {evt.room_id} to {evt.content.replacement_room} " + f"sent by {evt.sender}: already joined to replacement room" + ) + return + self.log.debug( + f"Following tombstone from {evt.room_id} to {evt.content.replacement_room} " + f"sent by {evt.sender}" + ) _, server = self.client.parse_user_id(evt.sender) - await self.client.join_room(evt.content.replacement_room, servers=[server]) + room_id = await self.client.join_room(evt.content.replacement_room, servers=[server]) + power_levels = await self.client.get_state_event(room_id, EventType.ROOM_POWER_LEVELS) + if power_levels.get_user_level(evt.sender) < power_levels.invite: + self.log.warning( + f"{evt.room_id} was tombstoned into {room_id}," + " but the sender doesn't have invite power levels, leaving..." + ) + await self.client.leave_room( + room_id, + f"Followed tombstone from {evt.room_id} by {evt.sender}," + " but sender doesn't have sufficient power level for invites", + ) async def _handle_invite(self, evt: StrippedStateEvent) -> None: if evt.state_key == self.id and evt.content.membership == Membership.INVITE: From 59cfff99f152aa92c2ec564de32218930b1baf78 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Mon, 5 May 2025 01:29:43 +0300 Subject: [PATCH 16/30] Adjust log --- maubot/client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/maubot/client.py b/maubot/client.py index f06af83..56efbbb 100644 --- a/maubot/client.py +++ b/maubot/client.py @@ -372,7 +372,7 @@ class Client(DBClient): power_levels = await self.client.get_state_event(room_id, EventType.ROOM_POWER_LEVELS) if power_levels.get_user_level(evt.sender) < power_levels.invite: self.log.warning( - f"{evt.room_id} was tombstoned into {room_id}," + f"{evt.room_id} was tombstoned into {room_id} by {evt.sender}," " but the sender doesn't have invite power levels, leaving..." ) await self.client.leave_room( From 9109047ef25bc556d96715e10cac2e45599e2ffa Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Tue, 6 May 2025 00:10:47 +0300 Subject: [PATCH 17/30] Bump version to 0.5.2 --- CHANGELOG.md | 11 +++++++++++ maubot/__meta__.py | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3a04252..d9de2b7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,14 @@ +# v0.5.2 (2025-05-05) + +* Improved tombstone handling to ensure that the tombstone sender has + permissions to invite users to the target room. +* Fixed autojoin and online flags not being applied if set during client + creation (thanks to [@bnsh] in [#258]). +* Fixed plugin web apps not being cleared properly when unloading plugins. + +[@bnsh]: https://github.com/bnsh +[#258]: https://github.com/maubot/maubot/pull/258 + # v0.5.1 (2025-01-03) * Updated Docker image to Alpine 3.21. diff --git a/maubot/__meta__.py b/maubot/__meta__.py index dd9b22c..7225152 100644 --- a/maubot/__meta__.py +++ b/maubot/__meta__.py @@ -1 +1 @@ -__version__ = "0.5.1" +__version__ = "0.5.2" From ac3f0c34ccbe40da91bc4955ab63d3a2b4d22f52 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Wed, 14 May 2025 17:18:02 +0300 Subject: [PATCH 18/30] Reduce limit when plaintext body is cut off --- maubot/matrix.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/maubot/matrix.py b/maubot/matrix.py index 87c7b70..a99c1a4 100644 --- a/maubot/matrix.py +++ b/maubot/matrix.py @@ -63,7 +63,7 @@ async def parse_formatted( else: return message, escape(message) text = (await MaubotHTMLParser().parse(html)).text - if len(text) + len(html) > 60000: + if len(text) + len(html) > 40000: text = text[:100] + "[long message cut off]" return text, html From 10383d526f84f8d56c7e24833d510f36953006b1 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Wed, 14 May 2025 17:18:18 +0300 Subject: [PATCH 19/30] Ignore tombstones with non-empty state key --- maubot/client.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/maubot/client.py b/maubot/client.py index 56efbbb..b0fde73 100644 --- a/maubot/client.py +++ b/maubot/client.py @@ -350,6 +350,8 @@ class Client(DBClient): } async def _handle_tombstone(self, evt: StateEvent) -> None: + if evt.state_key != "": + return if not evt.content.replacement_room: self.log.info(f"{evt.room_id} tombstoned with no replacement, ignoring") return From a4a39b7f90e8fbad33d7ed656f346fb3f35442a1 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Fri, 8 Aug 2025 19:52:28 +0300 Subject: [PATCH 20/30] Clarify what ui_base_path does [skip ci] --- maubot/example-config.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/maubot/example-config.yaml b/maubot/example-config.yaml index 0a6c8ac..7991461 100644 --- a/maubot/example-config.yaml +++ b/maubot/example-config.yaml @@ -54,7 +54,8 @@ server: port: 29316 # Public base URL where the server is visible. public_url: https://example.com - # The base path for the UI. + # The base path for the UI. Note that this does not change the API path. + # Add a path prefix to public_url if you want everything on a subpath. ui_base_path: /_matrix/maubot # The base path for plugin endpoints. The instance ID will be appended directly. plugin_base_path: /_matrix/maubot/plugin/ From 4c01a49310e22010b053704a3dd7a81425ce1da9 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Sat, 9 Aug 2025 22:00:29 +0300 Subject: [PATCH 21/30] Don't cut off body if there's nothing there --- maubot/matrix.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/maubot/matrix.py b/maubot/matrix.py index a99c1a4..7f1ec0f 100644 --- a/maubot/matrix.py +++ b/maubot/matrix.py @@ -63,7 +63,7 @@ async def parse_formatted( else: return message, escape(message) text = (await MaubotHTMLParser().parse(html)).text - if len(text) + len(html) > 40000: + if len(text) > 100 and len(text) + len(html) > 40000: text = text[:100] + "[long message cut off]" return text, html From 97dc989394a6563d3a044c5dfc767a1b2b9e7a20 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Mon, 11 Aug 2025 22:28:30 +0300 Subject: [PATCH 22/30] Check creator power when following tombstones --- maubot/client.py | 5 ++++- requirements.txt | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/maubot/client.py b/maubot/client.py index b0fde73..5c98498 100644 --- a/maubot/client.py +++ b/maubot/client.py @@ -372,7 +372,10 @@ class Client(DBClient): _, server = self.client.parse_user_id(evt.sender) room_id = await self.client.join_room(evt.content.replacement_room, servers=[server]) power_levels = await self.client.get_state_event(room_id, EventType.ROOM_POWER_LEVELS) - if power_levels.get_user_level(evt.sender) < power_levels.invite: + create_event = await self.client.get_state_event( + room_id, EventType.ROOM_CREATE, format="event" + ) + if power_levels.get_user_level(evt.sender, create_event) < power_levels.invite: self.log.warning( f"{evt.room_id} was tombstoned into {room_id} by {evt.sender}," " but the sender doesn't have invite power levels, leaving..." diff --git a/requirements.txt b/requirements.txt index e1df001..5a165c4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -mautrix>=0.20.7,<0.21 +mautrix>=0.20.9rc1,<0.21 aiohttp>=3,<4 yarl>=1,<2 asyncpg>=0.20,<1 From 43a14513f0a3e5c05cb60c439e970b58a60d9495 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Mon, 11 Aug 2025 22:50:25 +0300 Subject: [PATCH 23/30] Update mautrix-python again --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 5a165c4..ff09981 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -mautrix>=0.20.9rc1,<0.21 +mautrix>=0.20.9rc2,<0.21 aiohttp>=3,<4 yarl>=1,<2 asyncpg>=0.20,<1 From ff19278d24d68b31ec9d43ffe7af9b4b2ab12f0d Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Tue, 12 Aug 2025 10:12:00 +0300 Subject: [PATCH 24/30] Update mautrix-python --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index ff09981..169d053 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -mautrix>=0.20.9rc2,<0.21 +mautrix>=0.20.9rc3,<0.21 aiohttp>=3,<4 yarl>=1,<2 asyncpg>=0.20,<1 From 93e0ebd24ed20c88beed54e7f71875d05d4eb571 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Tue, 19 Aug 2025 12:25:03 +0300 Subject: [PATCH 25/30] Catch errors when updating profile from server --- maubot/client.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/maubot/client.py b/maubot/client.py index 5c98498..972f0de 100644 --- a/maubot/client.py +++ b/maubot/client.py @@ -504,8 +504,14 @@ class Client(DBClient): self.start_sync() async def _update_remote_profile(self) -> None: - profile = await self.client.get_profile(self.id) - self.remote_displayname, self.remote_avatar_url = profile.displayname, profile.avatar_url + try: + profile = await self.client.get_profile(self.id) + self.remote_displayname, self.remote_avatar_url = ( + profile.displayname, + profile.avatar_url, + ) + except Exception: + self.log.warning("Failed to update own profile from server", exc_info=True) async def delete(self) -> None: try: From 905c91c285d12202f6f20e1c3af4a4cf12836c2a Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Tue, 19 Aug 2025 12:28:11 +0300 Subject: [PATCH 26/30] Fix default value for profile when creating client --- maubot/management/frontend/src/pages/dashboard/Client.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/maubot/management/frontend/src/pages/dashboard/Client.js b/maubot/management/frontend/src/pages/dashboard/Client.js index c60129c..e9d7541 100644 --- a/maubot/management/frontend/src/pages/dashboard/Client.js +++ b/maubot/management/frontend/src/pages/dashboard/Client.js @@ -70,9 +70,9 @@ class Client extends BaseMainView { get initialState() { return { id: "", - displayname: "", + displayname: "disable", homeserver: "", - avatar_url: "", + avatar_url: "disable", access_token: "", device_id: "", fingerprint: null, From beaba079ca97984ff9c948a05b58f6c00cfd1c69 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Wed, 20 Aug 2025 10:26:10 +0300 Subject: [PATCH 27/30] Update Alpine and Node --- .gitlab-ci.yml | 2 +- Dockerfile | 4 ++-- Dockerfile.ci | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 50d0c15..8c8a44e 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -10,7 +10,7 @@ default: - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY build frontend: - image: node:22-alpine + image: node:24-alpine stage: build frontend before_script: [] variables: diff --git a/Dockerfile b/Dockerfile index 2c6bad4..ae35fbb 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,9 +1,9 @@ -FROM node:22 AS frontend-builder +FROM node:24 AS frontend-builder COPY ./maubot/management/frontend /frontend RUN cd /frontend && yarn --prod && yarn build -FROM alpine:3.21 +FROM alpine:3.22 RUN apk add --no-cache \ python3 py3-pip py3-setuptools py3-wheel \ diff --git a/Dockerfile.ci b/Dockerfile.ci index 9712a16..fb76f50 100644 --- a/Dockerfile.ci +++ b/Dockerfile.ci @@ -1,4 +1,4 @@ -FROM alpine:3.21 +FROM alpine:3.22 RUN apk add --no-cache \ python3 py3-pip py3-setuptools py3-wheel \ From 94c4e5aaafa16f242e093dbffe3f48833b71e50f Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Tue, 26 Aug 2025 11:59:22 +0300 Subject: [PATCH 28/30] Allow specifying extra content in reply/respond --- maubot/matrix.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/maubot/matrix.py b/maubot/matrix.py index 7f1ec0f..3763533 100644 --- a/maubot/matrix.py +++ b/maubot/matrix.py @@ -15,7 +15,7 @@ # along with this program. If not, see . from __future__ import annotations -from typing import Awaitable +from typing import Any, Awaitable from html import escape import asyncio @@ -88,6 +88,7 @@ class MaubotMessageEvent(MessageEvent): reply: bool | str = False, in_thread: bool | None = None, edits: EventID | MessageEvent | None = None, + extra_content: dict[str, Any] | None = None, ) -> EventID: """ Respond to the message. @@ -107,6 +108,7 @@ class MaubotMessageEvent(MessageEvent): the root if necessary. edits: An event ID or MessageEvent to edit. If set, the reply and in_thread parameters are ignored, as edits can't change the reply or thread status. + extra_content: Extra content to add to the event. Returns: The ID of the response event. @@ -143,6 +145,9 @@ class MaubotMessageEvent(MessageEvent): ) else: content.set_reply(self) + if extra_content: + for k, v in extra_content.items(): + content[k] = v return await self.client.send_message_event(self.room_id, event_type, content) def reply( @@ -152,6 +157,7 @@ class MaubotMessageEvent(MessageEvent): markdown: bool = True, allow_html: bool = False, in_thread: bool | None = None, + extra_content: dict[str, Any] | None = None, ) -> Awaitable[EventID]: """ Reply to the message. The parameters are the same as :meth:`respond`, @@ -169,6 +175,7 @@ class MaubotMessageEvent(MessageEvent): thread. If set to ``False``, the response will never be in a thread. If set to ``True``, the response will always be in a thread, creating one with this event as the root if necessary. + extra_content: Extra content to add to the event. Returns: The ID of the response event. @@ -180,6 +187,7 @@ class MaubotMessageEvent(MessageEvent): reply=True, in_thread=in_thread, allow_html=allow_html, + extra_content=extra_content, ) def mark_read(self) -> Awaitable[None]: From 87bb7fc854374eed8bff7e364edf44241bc6bde3 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Tue, 26 Aug 2025 12:01:36 +0300 Subject: [PATCH 29/30] Also allow setting extra content in send_markdown --- maubot/matrix.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/maubot/matrix.py b/maubot/matrix.py index 3763533..8a811af 100644 --- a/maubot/matrix.py +++ b/maubot/matrix.py @@ -264,14 +264,18 @@ class MaubotMatrixClient(MatrixClient): markdown: str, *, allow_html: bool = False, + render_markdown: bool = True, msgtype: MessageType = MessageType.TEXT, edits: EventID | MessageEvent | None = None, relates_to: RelatesTo | None = None, + extra_content: dict[str, Any] = None, **kwargs, ) -> EventID: content = TextMessageEventContent(msgtype=msgtype, format=Format.HTML) content.body, content.formatted_body = await parse_formatted( - markdown, allow_html=allow_html + markdown, + allow_html=allow_html, + render_markdown=render_markdown, ) if relates_to: if edits: @@ -279,6 +283,9 @@ class MaubotMatrixClient(MatrixClient): content.relates_to = relates_to elif edits: content.set_edit(edits) + if extra_content: + for k, v in extra_content.items(): + content[k] = v return await self.send_message(room_id, content, **kwargs) def dispatch_event(self, event: Event, source: SyncStream) -> list[asyncio.Task]: From b771b79b9175c48e90424255a8c51cda4ca12215 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Wed, 27 Aug 2025 20:45:13 +0300 Subject: [PATCH 30/30] Install aiodns and brotli in Docker image to speed up aiohttp --- Dockerfile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Dockerfile b/Dockerfile index ae35fbb..204a78a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -11,6 +11,8 @@ RUN apk add --no-cache \ su-exec \ yq \ py3-aiohttp \ + py3-aiodns \ + py3-brotli \ py3-attrs \ py3-bcrypt \ py3-cffi \