mirror of
https://github.com/maubot/maubot
synced 2025-08-29 06:40:37 +00:00
Compare commits
30 commits
Author | SHA1 | Date | |
---|---|---|---|
|
b771b79b91 | ||
|
87bb7fc854 | ||
|
94c4e5aaaf | ||
|
beaba079ca | ||
|
905c91c285 | ||
|
93e0ebd24e | ||
|
ff19278d24 | ||
|
43a14513f0 | ||
|
97dc989394 | ||
|
4c01a49310 | ||
|
a4a39b7f90 | ||
|
10383d526f | ||
|
ac3f0c34cc | ||
|
9109047ef2 | ||
|
59cfff99f1 | ||
|
80b65d6a2f | ||
|
f0ade0a043 | ||
|
fe4d2f02bb | ||
|
c09eb195f8 | ||
|
094e1eca35 | ||
|
c3458eab58 | ||
|
6c7d0754f8 | ||
|
01b5f53d90 | ||
|
813fee7a2c | ||
|
46aed7e1d2 | ||
|
48cc00f591 | ||
|
bceacb97a0 | ||
|
dd58135c94 | ||
|
472fb9f6ac | ||
|
65be63fdd2 |
18 changed files with 121 additions and 33 deletions
4
.github/workflows/python-lint.yml
vendored
4
.github/workflows/python-lint.yml
vendored
|
@ -9,14 +9,14 @@ jobs:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: actions/setup-python@v5
|
- uses: actions/setup-python@v5
|
||||||
with:
|
with:
|
||||||
python-version: "3.12"
|
python-version: "3.13"
|
||||||
- uses: isort/isort-action@master
|
- uses: isort/isort-action@master
|
||||||
with:
|
with:
|
||||||
sortPaths: "./maubot"
|
sortPaths: "./maubot"
|
||||||
- uses: psf/black@stable
|
- uses: psf/black@stable
|
||||||
with:
|
with:
|
||||||
src: "./maubot"
|
src: "./maubot"
|
||||||
version: "24.2.0"
|
version: "24.10.0"
|
||||||
- name: pre-commit
|
- name: pre-commit
|
||||||
run: |
|
run: |
|
||||||
pip install pre-commit
|
pip install pre-commit
|
||||||
|
|
|
@ -10,7 +10,7 @@ default:
|
||||||
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
|
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
|
||||||
|
|
||||||
build frontend:
|
build frontend:
|
||||||
image: node:20-alpine
|
image: node:24-alpine
|
||||||
stage: build frontend
|
stage: build frontend
|
||||||
before_script: []
|
before_script: []
|
||||||
variables:
|
variables:
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
repos:
|
repos:
|
||||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||||
rev: v4.5.0
|
rev: v5.0.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: trailing-whitespace
|
- id: trailing-whitespace
|
||||||
exclude_types: [markdown]
|
exclude_types: [markdown]
|
||||||
|
@ -8,7 +8,7 @@ repos:
|
||||||
- id: check-yaml
|
- id: check-yaml
|
||||||
- id: check-added-large-files
|
- id: check-added-large-files
|
||||||
- repo: https://github.com/psf/black
|
- repo: https://github.com/psf/black
|
||||||
rev: 24.2.0
|
rev: 24.10.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: black
|
- id: black
|
||||||
language_version: python3
|
language_version: python3
|
||||||
|
|
27
CHANGELOG.md
27
CHANGELOG.md
|
@ -1,3 +1,30 @@
|
||||||
|
# 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.
|
||||||
|
* 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)
|
# v0.5.0 (2024-08-24)
|
||||||
|
|
||||||
* Dropped Python 3.9 support.
|
* Dropped Python 3.9 support.
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
FROM node:20 AS frontend-builder
|
FROM node:24 AS frontend-builder
|
||||||
|
|
||||||
COPY ./maubot/management/frontend /frontend
|
COPY ./maubot/management/frontend /frontend
|
||||||
RUN cd /frontend && yarn --prod && yarn build
|
RUN cd /frontend && yarn --prod && yarn build
|
||||||
|
|
||||||
FROM alpine:3.20
|
FROM alpine:3.22
|
||||||
|
|
||||||
RUN apk add --no-cache \
|
RUN apk add --no-cache \
|
||||||
python3 py3-pip py3-setuptools py3-wheel \
|
python3 py3-pip py3-setuptools py3-wheel \
|
||||||
|
@ -11,6 +11,8 @@ RUN apk add --no-cache \
|
||||||
su-exec \
|
su-exec \
|
||||||
yq \
|
yq \
|
||||||
py3-aiohttp \
|
py3-aiohttp \
|
||||||
|
py3-aiodns \
|
||||||
|
py3-brotli \
|
||||||
py3-attrs \
|
py3-attrs \
|
||||||
py3-bcrypt \
|
py3-bcrypt \
|
||||||
py3-cffi \
|
py3-cffi \
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
FROM alpine:3.20
|
FROM alpine:3.22
|
||||||
|
|
||||||
RUN apk add --no-cache \
|
RUN apk add --no-cache \
|
||||||
python3 py3-pip py3-setuptools py3-wheel \
|
python3 py3-pip py3-setuptools py3-wheel \
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
__version__ = "0.5.0"
|
__version__ = "0.5.2"
|
||||||
|
|
|
@ -350,11 +350,41 @@ class Client(DBClient):
|
||||||
}
|
}
|
||||||
|
|
||||||
async def _handle_tombstone(self, evt: StateEvent) -> None:
|
async def _handle_tombstone(self, evt: StateEvent) -> None:
|
||||||
|
if evt.state_key != "":
|
||||||
|
return
|
||||||
if not evt.content.replacement_room:
|
if not evt.content.replacement_room:
|
||||||
self.log.info(f"{evt.room_id} tombstoned with no replacement, ignoring")
|
self.log.info(f"{evt.room_id} tombstoned with no replacement, ignoring")
|
||||||
return
|
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)
|
_, 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)
|
||||||
|
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..."
|
||||||
|
)
|
||||||
|
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:
|
async def _handle_invite(self, evt: StrippedStateEvent) -> None:
|
||||||
if evt.state_key == self.id and evt.content.membership == Membership.INVITE:
|
if evt.state_key == self.id and evt.content.membership == Membership.INVITE:
|
||||||
|
@ -474,8 +504,14 @@ class Client(DBClient):
|
||||||
self.start_sync()
|
self.start_sync()
|
||||||
|
|
||||||
async def _update_remote_profile(self) -> None:
|
async def _update_remote_profile(self) -> None:
|
||||||
profile = await self.client.get_profile(self.id)
|
try:
|
||||||
self.remote_displayname, self.remote_avatar_url = profile.displayname, profile.avatar_url
|
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:
|
async def delete(self) -> None:
|
||||||
try:
|
try:
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
# The full URI to the database. SQLite and Postgres are fully supported.
|
# The full URI to the database. SQLite and Postgres are fully supported.
|
||||||
# Other DBMSes supported by SQLAlchemy may or may not work.
|
|
||||||
# Format examples:
|
# Format examples:
|
||||||
# SQLite: sqlite:filename.db
|
# SQLite: sqlite:filename.db
|
||||||
# Postgres: postgresql://username:password@hostname/dbname
|
# Postgres: postgresql://username:password@hostname/dbname
|
||||||
|
@ -55,7 +54,8 @@ server:
|
||||||
port: 29316
|
port: 29316
|
||||||
# Public base URL where the server is visible.
|
# Public base URL where the server is visible.
|
||||||
public_url: https://example.com
|
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
|
ui_base_path: /_matrix/maubot
|
||||||
# The base path for plugin endpoints. The instance ID will be appended directly.
|
# The base path for plugin endpoints. The instance ID will be appended directly.
|
||||||
plugin_base_path: /_matrix/maubot/plugin/
|
plugin_base_path: /_matrix/maubot/plugin/
|
||||||
|
@ -79,8 +79,9 @@ homeservers:
|
||||||
# When this is empty, `mbc auth --register` won't work, but `mbc auth` (login) will.
|
# When this is empty, `mbc auth --register` won't work, but `mbc auth` (login) will.
|
||||||
secret: null
|
secret: null
|
||||||
|
|
||||||
# List of administrator users. Plaintext passwords will be bcrypted on startup. Set empty password
|
# List of administrator users. Each key is a username and the value is the password.
|
||||||
# to prevent normal login. Root is a special user that can't have a password and will always exist.
|
# 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:
|
admins:
|
||||||
root: ""
|
root: ""
|
||||||
|
|
||||||
|
|
|
@ -78,8 +78,8 @@ async def _create_client(user_id: UserID | None, data: dict) -> web.Response:
|
||||||
)
|
)
|
||||||
client.enabled = data.get("enabled", True)
|
client.enabled = data.get("enabled", True)
|
||||||
client.sync = data.get("sync", True)
|
client.sync = data.get("sync", True)
|
||||||
client.autojoin = data.get("autojoin", True)
|
await client.update_autojoin(data.get("autojoin", True), save=False)
|
||||||
client.online = data.get("online", True)
|
await client.update_online(data.get("online", True), save=False)
|
||||||
client.displayname = data.get("displayname", "disable")
|
client.displayname = data.get("displayname", "disable")
|
||||||
client.avatar_url = data.get("avatar_url", "disable")
|
client.avatar_url = data.get("avatar_url", "disable")
|
||||||
await client.update()
|
await client.update()
|
||||||
|
|
|
@ -205,7 +205,7 @@ export const getClients = () => defaultGet("/clients")
|
||||||
export const getClient = id => defaultGet(`/clients/${id}`)
|
export const getClient = id => defaultGet(`/clients/${id}`)
|
||||||
|
|
||||||
export async function uploadAvatar(id, data, mime) {
|
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),
|
headers: getHeaders(mime),
|
||||||
body: data,
|
body: data,
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
@ -217,8 +217,9 @@ export function getAvatarURL({ id, avatar_url }) {
|
||||||
if (!avatar_url?.startsWith("mxc://")) {
|
if (!avatar_url?.startsWith("mxc://")) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
avatar_url = avatar_url.substr("mxc://".length)
|
avatar_url = avatar_url.substring("mxc://".length)
|
||||||
return `${BASE_PATH}/proxy/${id}/_matrix/media/r0/download/${avatar_url}?access_token=${
|
// 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}`
|
localStorage.accessToken}`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -70,9 +70,9 @@ class Client extends BaseMainView {
|
||||||
get initialState() {
|
get initialState() {
|
||||||
return {
|
return {
|
||||||
id: "",
|
id: "",
|
||||||
displayname: "",
|
displayname: "disable",
|
||||||
homeserver: "",
|
homeserver: "",
|
||||||
avatar_url: "",
|
avatar_url: "disable",
|
||||||
access_token: "",
|
access_token: "",
|
||||||
device_id: "",
|
device_id: "",
|
||||||
fingerprint: null,
|
fingerprint: null,
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import Awaitable
|
from typing import Any, Awaitable
|
||||||
from html import escape
|
from html import escape
|
||||||
import asyncio
|
import asyncio
|
||||||
|
|
||||||
|
@ -62,7 +62,10 @@ async def parse_formatted(
|
||||||
html = message
|
html = message
|
||||||
else:
|
else:
|
||||||
return message, escape(message)
|
return message, escape(message)
|
||||||
return (await MaubotHTMLParser().parse(html)).text, html
|
text = (await MaubotHTMLParser().parse(html)).text
|
||||||
|
if len(text) > 100 and len(text) + len(html) > 40000:
|
||||||
|
text = text[:100] + "[long message cut off]"
|
||||||
|
return text, html
|
||||||
|
|
||||||
|
|
||||||
class MaubotMessageEvent(MessageEvent):
|
class MaubotMessageEvent(MessageEvent):
|
||||||
|
@ -85,6 +88,7 @@ class MaubotMessageEvent(MessageEvent):
|
||||||
reply: bool | str = False,
|
reply: bool | str = False,
|
||||||
in_thread: bool | None = None,
|
in_thread: bool | None = None,
|
||||||
edits: EventID | MessageEvent | None = None,
|
edits: EventID | MessageEvent | None = None,
|
||||||
|
extra_content: dict[str, Any] | None = None,
|
||||||
) -> EventID:
|
) -> EventID:
|
||||||
"""
|
"""
|
||||||
Respond to the message.
|
Respond to the message.
|
||||||
|
@ -104,6 +108,7 @@ class MaubotMessageEvent(MessageEvent):
|
||||||
the root if necessary.
|
the root if necessary.
|
||||||
edits: An event ID or MessageEvent to edit. If set, the reply and in_thread parameters
|
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.
|
are ignored, as edits can't change the reply or thread status.
|
||||||
|
extra_content: Extra content to add to the event.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
The ID of the response event.
|
The ID of the response event.
|
||||||
|
@ -140,6 +145,9 @@ class MaubotMessageEvent(MessageEvent):
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
content.set_reply(self)
|
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)
|
return await self.client.send_message_event(self.room_id, event_type, content)
|
||||||
|
|
||||||
def reply(
|
def reply(
|
||||||
|
@ -149,6 +157,7 @@ class MaubotMessageEvent(MessageEvent):
|
||||||
markdown: bool = True,
|
markdown: bool = True,
|
||||||
allow_html: bool = False,
|
allow_html: bool = False,
|
||||||
in_thread: bool | None = None,
|
in_thread: bool | None = None,
|
||||||
|
extra_content: dict[str, Any] | None = None,
|
||||||
) -> Awaitable[EventID]:
|
) -> Awaitable[EventID]:
|
||||||
"""
|
"""
|
||||||
Reply to the message. The parameters are the same as :meth:`respond`,
|
Reply to the message. The parameters are the same as :meth:`respond`,
|
||||||
|
@ -166,6 +175,7 @@ class MaubotMessageEvent(MessageEvent):
|
||||||
thread. If set to ``False``, the response will never be in a thread. If set to
|
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
|
``True``, the response will always be in a thread, creating one with this event as
|
||||||
the root if necessary.
|
the root if necessary.
|
||||||
|
extra_content: Extra content to add to the event.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
The ID of the response event.
|
The ID of the response event.
|
||||||
|
@ -177,6 +187,7 @@ class MaubotMessageEvent(MessageEvent):
|
||||||
reply=True,
|
reply=True,
|
||||||
in_thread=in_thread,
|
in_thread=in_thread,
|
||||||
allow_html=allow_html,
|
allow_html=allow_html,
|
||||||
|
extra_content=extra_content,
|
||||||
)
|
)
|
||||||
|
|
||||||
def mark_read(self) -> Awaitable[None]:
|
def mark_read(self) -> Awaitable[None]:
|
||||||
|
@ -253,14 +264,18 @@ class MaubotMatrixClient(MatrixClient):
|
||||||
markdown: str,
|
markdown: str,
|
||||||
*,
|
*,
|
||||||
allow_html: bool = False,
|
allow_html: bool = False,
|
||||||
|
render_markdown: bool = True,
|
||||||
msgtype: MessageType = MessageType.TEXT,
|
msgtype: MessageType = MessageType.TEXT,
|
||||||
edits: EventID | MessageEvent | None = None,
|
edits: EventID | MessageEvent | None = None,
|
||||||
relates_to: RelatesTo | None = None,
|
relates_to: RelatesTo | None = None,
|
||||||
|
extra_content: dict[str, Any] = None,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
) -> EventID:
|
) -> EventID:
|
||||||
content = TextMessageEventContent(msgtype=msgtype, format=Format.HTML)
|
content = TextMessageEventContent(msgtype=msgtype, format=Format.HTML)
|
||||||
content.body, content.formatted_body = await parse_formatted(
|
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 relates_to:
|
||||||
if edits:
|
if edits:
|
||||||
|
@ -268,6 +283,9 @@ class MaubotMatrixClient(MatrixClient):
|
||||||
content.relates_to = relates_to
|
content.relates_to = relates_to
|
||||||
elif edits:
|
elif edits:
|
||||||
content.set_edit(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)
|
return await self.send_message(room_id, content, **kwargs)
|
||||||
|
|
||||||
def dispatch_event(self, event: Event, source: SyncStream) -> list[asyncio.Task]:
|
def dispatch_event(self, event: Event, source: SyncStream) -> list[asyncio.Task]:
|
||||||
|
|
|
@ -40,6 +40,8 @@ class PluginWebApp(web.UrlDispatcher):
|
||||||
self._resources = []
|
self._resources = []
|
||||||
self._named_resources = {}
|
self._named_resources = {}
|
||||||
self._middleware = []
|
self._middleware = []
|
||||||
|
self._resource_index = {}
|
||||||
|
self._matched_sub_app_resources = []
|
||||||
|
|
||||||
async def handle(self, request: web.Request) -> web.StreamResponse:
|
async def handle(self, request: web.Request) -> web.StreamResponse:
|
||||||
match_info = await self.resolve(request)
|
match_info = await self.resolve(request)
|
||||||
|
|
|
@ -64,14 +64,14 @@ class MaubotServer:
|
||||||
if request.path.startswith(path):
|
if request.path.startswith(path):
|
||||||
request = request.clone(
|
request = request.clone(
|
||||||
rel_url=request.rel_url.with_path(
|
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)
|
).with_query(request.query_string)
|
||||||
)
|
)
|
||||||
return await app.handle(request)
|
return await app.handle(request)
|
||||||
return web.Response(status=404)
|
return web.Response(status=404)
|
||||||
|
|
||||||
def get_instance_subapp(self, instance_id: str) -> tuple[PluginWebApp, str]:
|
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
|
url = self.config["server.public_url"] + subpath
|
||||||
try:
|
try:
|
||||||
return self.plugin_routes[subpath], url
|
return self.plugin_routes[subpath], url
|
||||||
|
@ -82,7 +82,7 @@ class MaubotServer:
|
||||||
|
|
||||||
def remove_instance_webapp(self, instance_id: str) -> None:
|
def remove_instance_webapp(self, instance_id: str) -> None:
|
||||||
try:
|
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()
|
self.plugin_routes.pop(subpath).clear()
|
||||||
except KeyError:
|
except KeyError:
|
||||||
return
|
return
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
FROM docker.io/alpine:3.20
|
FROM docker.io/alpine:3.21
|
||||||
|
|
||||||
RUN apk add --no-cache \
|
RUN apk add --no-cache \
|
||||||
python3 py3-pip py3-setuptools py3-wheel \
|
python3 py3-pip py3-setuptools py3-wheel \
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
mautrix>=0.20.6,<0.21
|
mautrix>=0.20.9rc3,<0.21
|
||||||
aiohttp>=3,<4
|
aiohttp>=3,<4
|
||||||
yarl>=1,<2
|
yarl>=1,<2
|
||||||
asyncpg>=0.20,<0.30
|
asyncpg>=0.20,<1
|
||||||
aiosqlite>=0.16,<0.21
|
aiosqlite>=0.16,<1
|
||||||
commonmark>=0.9,<1
|
commonmark>=0.9,<1
|
||||||
ruamel.yaml>=0.15.35,<0.19
|
ruamel.yaml>=0.15.35,<0.19
|
||||||
attrs>=18.1.0
|
attrs>=18.1.0
|
||||||
|
|
1
setup.py
1
setup.py
|
@ -53,6 +53,7 @@ setuptools.setup(
|
||||||
"Programming Language :: Python :: 3.10",
|
"Programming Language :: Python :: 3.10",
|
||||||
"Programming Language :: Python :: 3.11",
|
"Programming Language :: Python :: 3.11",
|
||||||
"Programming Language :: Python :: 3.12",
|
"Programming Language :: Python :: 3.12",
|
||||||
|
"Programming Language :: Python :: 3.13",
|
||||||
],
|
],
|
||||||
entry_points="""
|
entry_points="""
|
||||||
[console_scripts]
|
[console_scripts]
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue