790 lines
33 KiB
Python
790 lines
33 KiB
Python
# Copyright (c) 2022 Tulir Asokan
|
|
#
|
|
# This Source Code Form is subject to the terms of the Mozilla Public
|
|
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
from __future__ import annotations
|
|
|
|
from typing import Any, Awaitable, Callable
|
|
import asyncio
|
|
|
|
from multidict import CIMultiDict
|
|
|
|
from mautrix.api import Method, Path
|
|
from mautrix.errors import (
|
|
MatrixRequestError,
|
|
MatrixResponseError,
|
|
MNotFound,
|
|
MNotJoined,
|
|
MRoomInUse,
|
|
)
|
|
from mautrix.types import (
|
|
JSON,
|
|
DirectoryPaginationToken,
|
|
EventID,
|
|
EventType,
|
|
Membership,
|
|
MemberStateEventContent,
|
|
PowerLevelStateEventContent,
|
|
RoomAlias,
|
|
RoomAliasInfo,
|
|
RoomCreatePreset,
|
|
RoomCreateStateEventContent,
|
|
RoomDirectoryResponse,
|
|
RoomDirectoryVisibility,
|
|
RoomID,
|
|
Serializable,
|
|
StateEvent,
|
|
StrippedStateEvent,
|
|
UserID,
|
|
)
|
|
|
|
from .base import BaseClientAPI
|
|
from .events import EventMethods
|
|
|
|
|
|
class RoomMethods(EventMethods, BaseClientAPI):
|
|
"""
|
|
Methods in section 8 Rooms of the spec. These methods are used for creating rooms, interacting
|
|
with the room directory and using the easy room metadata editing endpoints. Generic state
|
|
setting and sending events are in the :class:`EventMethods` (section 7) module.
|
|
|
|
See also: `API reference <https://spec.matrix.org/v1.1/client-server-api/#rooms-1>`__
|
|
"""
|
|
|
|
# region 8.1 Creation
|
|
# API reference: https://spec.matrix.org/v1.1/client-server-api/#creation
|
|
|
|
async def create_room(
|
|
self,
|
|
alias_localpart: str | None = None,
|
|
visibility: RoomDirectoryVisibility = RoomDirectoryVisibility.PRIVATE,
|
|
preset: RoomCreatePreset = RoomCreatePreset.PRIVATE,
|
|
name: str | None = None,
|
|
topic: str | None = None,
|
|
is_direct: bool = False,
|
|
invitees: list[UserID] | None = None,
|
|
initial_state: list[StateEvent | StrippedStateEvent | dict[str, JSON]] | None = None,
|
|
room_version: str = None,
|
|
creation_content: RoomCreateStateEventContent | dict[str, JSON] | None = None,
|
|
power_level_override: PowerLevelStateEventContent | dict[str, JSON] | None = None,
|
|
beeper_auto_join_invites: bool = False,
|
|
custom_request_fields: dict[str, Any] | None = None,
|
|
) -> RoomID:
|
|
"""
|
|
Create a new room with various configuration options.
|
|
|
|
See also: `API reference <https://spec.matrix.org/v1.1/client-server-api/#post_matrixclientv3createroom>`__
|
|
|
|
Args:
|
|
alias_localpart: The desired room alias **local part**. If this is included, a room
|
|
alias will be created and mapped to the newly created room. The alias will belong
|
|
on the same homeserver which created the room. For example, if this was set to
|
|
"foo" and sent to the homeserver "example.com" the complete room alias would be
|
|
``#foo:example.com``.
|
|
visibility: A ``public`` visibility indicates that the room will be shown in the
|
|
published room list. A ``private`` visibility will hide the room from the published
|
|
room list. Defaults to ``private``. **NB:** This should not be confused with
|
|
``join_rules`` which also uses the word ``public``.
|
|
preset: Convenience parameter for setting various default state events based on a
|
|
preset. Defaults to private (invite-only).
|
|
name: If this is included, an ``m.room.name`` event will be sent into the room to
|
|
indicate the name of the room. See `Room Events`_ for more information on
|
|
``m.room.name``.
|
|
topic: If this is included, an ``m.room.topic`` event will be sent into the room to
|
|
indicate the topic for the room. See `Room Events`_ for more information on
|
|
``m.room.topic``.
|
|
is_direct: This flag makes the server set the ``is_direct`` flag on the
|
|
`m.room.member`_ events sent to the users in ``invite`` and ``invite_3pid``. See
|
|
`Direct Messaging`_ for more information.
|
|
invitees: A list of user IDs to invite to the room. This will tell the server to invite
|
|
everyone in the list to the newly created room.
|
|
initial_state: A list of state events to set in the new room. This allows the user to
|
|
override the default state events set in the new room. The expected format of the
|
|
state events are an object with type, state_key and content keys set.
|
|
|
|
Takes precedence over events set by ``is_public``, but gets overriden by ``name``
|
|
and ``topic keys``.
|
|
room_version: The room version to set for the room. If not provided, the homeserver
|
|
will use its configured default.
|
|
creation_content: Extra keys, such as ``m.federate``, to be added to the
|
|
``m.room.create`` event. The server will ignore ``creator`` and ``room_version``.
|
|
Future versions of the specification may allow the server to ignore other keys.
|
|
power_level_override: The power level content to override in the default power level
|
|
event. This object is applied on top of the generated ``m.room.power_levels`` event
|
|
content prior to it being sent to the room. Defaults to overriding nothing.
|
|
beeper_auto_join_invites: A Beeper-specific extension which auto-joins all members in
|
|
the invite array instead of sending invites.
|
|
custom_request_fields: Additional fields to put in the top-level /createRoom content.
|
|
Non-custom fields take precedence over fields here.
|
|
|
|
Returns:
|
|
The ID of the newly created room.
|
|
|
|
Raises:
|
|
MatrixResponseError: If the response does not contain a ``room_id`` field.
|
|
|
|
.. _Room Events: https://spec.matrix.org/v1.1/client-server-api/#room-events
|
|
.. _Direct Messaging: https://spec.matrix.org/v1.1/client-server-api/#direct-messaging
|
|
.. _m.room.create: https://spec.matrix.org/v1.1/client-server-api/#mroomcreate
|
|
.. _m.room.member: https://spec.matrix.org/v1.1/client-server-api/#mroommember
|
|
"""
|
|
content = {
|
|
**(custom_request_fields or {}),
|
|
"visibility": visibility.value,
|
|
"is_direct": is_direct,
|
|
"preset": preset.value,
|
|
}
|
|
if alias_localpart:
|
|
content["room_alias_name"] = alias_localpart
|
|
if invitees:
|
|
content["invite"] = invitees
|
|
if beeper_auto_join_invites:
|
|
content["com.beeper.auto_join_invites"] = True
|
|
if name:
|
|
content["name"] = name
|
|
if topic:
|
|
content["topic"] = topic
|
|
if initial_state:
|
|
content["initial_state"] = [
|
|
event.serialize() if isinstance(event, Serializable) else event
|
|
for event in initial_state
|
|
]
|
|
if room_version:
|
|
content["room_version"] = room_version
|
|
if creation_content:
|
|
content["creation_content"] = (
|
|
creation_content.serialize()
|
|
if isinstance(creation_content, Serializable)
|
|
else creation_content
|
|
)
|
|
# Remove keys that the server will ignore anyway
|
|
content["creation_content"].pop("room_version", None)
|
|
if power_level_override:
|
|
content["power_level_content_override"] = (
|
|
power_level_override.serialize()
|
|
if isinstance(power_level_override, Serializable)
|
|
else power_level_override
|
|
)
|
|
|
|
resp = await self.api.request(Method.POST, Path.v3.createRoom, content)
|
|
try:
|
|
return resp["room_id"]
|
|
except KeyError:
|
|
raise MatrixResponseError("`room_id` not in response.")
|
|
|
|
# endregion
|
|
# region 8.2 Room aliases
|
|
# API reference: https://spec.matrix.org/v1.1/client-server-api/#room-aliases
|
|
|
|
async def add_room_alias(
|
|
self, room_id: RoomID, alias_localpart: str, override: bool = False
|
|
) -> None:
|
|
"""
|
|
Create a new mapping from room alias to room ID.
|
|
|
|
See also: `API reference <https://matrix.org/docs/spec/client_server/r0.4.0.html#put-matrix-client-r0-directory-room-roomalias>`__
|
|
|
|
Args:
|
|
room_id: The room ID to set.
|
|
alias_localpart: The localpart of the room alias to set.
|
|
override: Whether or not the alias should be removed and the request retried if the
|
|
server responds with HTTP 409 Conflict
|
|
"""
|
|
room_alias = f"#{alias_localpart}:{self.domain}"
|
|
content = {"room_id": room_id}
|
|
try:
|
|
await self.api.request(Method.PUT, Path.v3.directory.room[room_alias], content)
|
|
except MatrixRequestError as e:
|
|
if e.http_status == 409:
|
|
if override:
|
|
await self.remove_room_alias(alias_localpart)
|
|
await self.api.request(Method.PUT, Path.v3.directory.room[room_alias], content)
|
|
else:
|
|
raise MRoomInUse(e.http_status, e.message) from e
|
|
else:
|
|
raise
|
|
|
|
async def remove_room_alias(self, alias_localpart: str, raise_404: bool = False) -> None:
|
|
"""
|
|
Remove a mapping of room alias to room ID.
|
|
|
|
Servers may choose to implement additional access control checks here, for instance that
|
|
room aliases can only be deleted by their creator or server administrator.
|
|
|
|
See also: `API reference <https://matrix.org/docs/spec/client_server/r0.4.0.html#delete-matrix-client-r0-directory-room-roomalias>`__
|
|
|
|
Args:
|
|
alias_localpart: The room alias to remove.
|
|
raise_404: Whether 404 errors should be raised as exceptions instead of ignored.
|
|
"""
|
|
room_alias = f"#{alias_localpart}:{self.domain}"
|
|
try:
|
|
await self.api.request(Method.DELETE, Path.v3.directory.room[room_alias])
|
|
except MNotFound:
|
|
if raise_404:
|
|
raise
|
|
# else: ignore
|
|
|
|
async def resolve_room_alias(self, room_alias: RoomAlias) -> RoomAliasInfo:
|
|
"""
|
|
Request the server to resolve a room alias to a room ID.
|
|
|
|
The server will use the federation API to resolve the alias if the domain part of the alias
|
|
does not correspond to the server's own domain.
|
|
|
|
See also: `API reference <https://spec.matrix.org/v1.1/client-server-api/#get_matrixclientv3directoryroomroomalias>`__
|
|
|
|
Args:
|
|
room_alias: The room alias.
|
|
|
|
Returns:
|
|
The room ID and a list of servers that are aware of the room.
|
|
"""
|
|
content = await self.api.request(Method.GET, Path.v3.directory.room[room_alias])
|
|
return RoomAliasInfo.deserialize(content)
|
|
|
|
# endregion
|
|
# region 8.4 Room membership
|
|
# API reference: https://spec.matrix.org/v1.1/client-server-api/#room-membership
|
|
|
|
async def get_joined_rooms(self) -> list[RoomID]:
|
|
"""Get the list of rooms the user is in."""
|
|
content = await self.api.request(Method.GET, Path.v3.joined_rooms)
|
|
try:
|
|
return content["joined_rooms"]
|
|
except KeyError:
|
|
raise MatrixResponseError("`joined_rooms` not in response.")
|
|
|
|
# region 8.4.1 Joining rooms
|
|
# API reference: https://spec.matrix.org/v1.1/client-server-api/#joining-rooms
|
|
|
|
async def join_room_by_id(
|
|
self,
|
|
room_id: RoomID,
|
|
third_party_signed: JSON = None,
|
|
extra_content: dict[str, Any] | None = None,
|
|
) -> RoomID:
|
|
"""
|
|
Start participating in a room, i.e. join it by its ID.
|
|
|
|
See also: `API reference <https://spec.matrix.org/v1.1/client-server-api/#post_matrixclientv3roomsroomidjoin>`__
|
|
|
|
Args:
|
|
room_id: The ID of the room to join.
|
|
third_party_signed: A signature of an ``m.third_party_invite`` token to prove that this
|
|
user owns a third party identity which has been invited to the room.
|
|
extra_content: Additional properties for the join event content.
|
|
If a non-empty dict is passed, the join event will be created using
|
|
the ``PUT /state/m.room.member/...`` endpoint instead of ``POST /join``.
|
|
|
|
Returns:
|
|
The ID of the room the user joined.
|
|
"""
|
|
if extra_content:
|
|
await self.send_member_event(
|
|
room_id, self.mxid, Membership.JOIN, extra_content=extra_content
|
|
)
|
|
return room_id
|
|
content = await self.api.request(
|
|
Method.POST,
|
|
Path.v3.rooms[room_id].join,
|
|
{"third_party_signed": third_party_signed} if third_party_signed is not None else None,
|
|
)
|
|
try:
|
|
return content["room_id"]
|
|
except KeyError:
|
|
raise MatrixResponseError("`room_id` not in response.")
|
|
|
|
async def join_room(
|
|
self,
|
|
room_id_or_alias: RoomID | RoomAlias,
|
|
servers: list[str] | None = None,
|
|
third_party_signed: JSON = None,
|
|
max_retries: int = 4,
|
|
) -> RoomID:
|
|
"""
|
|
Start participating in a room, i.e. join it by its ID or alias, with an optional list of
|
|
servers to ask about the ID from.
|
|
|
|
See also: `API reference <https://spec.matrix.org/v1.1/client-server-api/#post_matrixclientv3joinroomidoralias>`__
|
|
|
|
Args:
|
|
room_id_or_alias: The ID of the room to join, or an alias pointing to the room.
|
|
servers: A list of servers to ask about the room ID to join. Not applicable for aliases,
|
|
as aliases already contain the necessary server information.
|
|
third_party_signed: A signature of an ``m.third_party_invite`` token to prove that this
|
|
user owns a third party identity which has been invited to the room.
|
|
max_retries: The maximum number of retries. Used to circumvent a Synapse bug with
|
|
accepting invites over federation. 0 means only one join call will be attempted.
|
|
See: `matrix-org/synapse#2807 <https://github.com/matrix-org/synapse/issues/2807>`__
|
|
|
|
Returns:
|
|
The ID of the room the user joined.
|
|
"""
|
|
max_retries = max(0, max_retries)
|
|
tries = 0
|
|
content = (
|
|
{"third_party_signed": third_party_signed} if third_party_signed is not None else None
|
|
)
|
|
query_params = CIMultiDict()
|
|
for server_name in servers or []:
|
|
query_params.add("server_name", server_name)
|
|
while tries <= max_retries:
|
|
try:
|
|
content = await self.api.request(
|
|
Method.POST,
|
|
Path.v3.join[room_id_or_alias],
|
|
content=content,
|
|
query_params=query_params,
|
|
)
|
|
break
|
|
except MatrixRequestError:
|
|
tries += 1
|
|
if tries <= max_retries:
|
|
wait = tries * 10
|
|
self.log.exception(
|
|
f"Failed to join room {room_id_or_alias}, retrying in {wait} seconds..."
|
|
)
|
|
await asyncio.sleep(wait)
|
|
else:
|
|
raise
|
|
try:
|
|
return content["room_id"]
|
|
except KeyError:
|
|
raise MatrixResponseError("`room_id` not in response.")
|
|
|
|
fill_member_event_callback: (
|
|
Callable[
|
|
[RoomID, UserID, MemberStateEventContent], Awaitable[MemberStateEventContent | None]
|
|
]
|
|
| None
|
|
)
|
|
|
|
async def fill_member_event(
|
|
self, room_id: RoomID, user_id: UserID, content: MemberStateEventContent
|
|
) -> MemberStateEventContent | None:
|
|
"""
|
|
Fill a membership event content that is going to be sent in :meth:`send_member_event`.
|
|
|
|
This is used to set default fields like the displayname and avatar, which are usually set
|
|
by the server in the sugar membership endpoints like /join and /invite, but are not set
|
|
automatically when sending member events manually.
|
|
|
|
This default implementation only calls :attr:`fill_member_event_callback`.
|
|
|
|
Args:
|
|
room_id: The room where the member event is going to be sent.
|
|
user_id: The user whose membership is changing.
|
|
content: The new member event content.
|
|
|
|
Returns:
|
|
The filled member event content.
|
|
"""
|
|
if self.fill_member_event_callback is not None:
|
|
return await self.fill_member_event_callback(room_id, user_id, content)
|
|
return None
|
|
|
|
async def send_member_event(
|
|
self,
|
|
room_id: RoomID,
|
|
user_id: UserID,
|
|
membership: Membership,
|
|
extra_content: dict[str, JSON] | None = None,
|
|
) -> EventID:
|
|
"""
|
|
Send a membership event manually.
|
|
|
|
Args:
|
|
room_id: The room to send the event to.
|
|
user_id: The user whose membership to change.
|
|
membership: The membership status.
|
|
extra_content: Additional content to put in the member event.
|
|
|
|
Returns:
|
|
The event ID of the new member event.
|
|
"""
|
|
content = MemberStateEventContent(membership=membership)
|
|
for key, value in extra_content.items():
|
|
content[key] = value
|
|
content = await self.fill_member_event(room_id, user_id, content) or content
|
|
return await self.send_state_event(
|
|
room_id, EventType.ROOM_MEMBER, content=content, state_key=user_id, ensure_joined=False
|
|
)
|
|
|
|
async def invite_user(
|
|
self,
|
|
room_id: RoomID,
|
|
user_id: UserID,
|
|
reason: str | None = None,
|
|
extra_content: dict[str, JSON] | None = None,
|
|
) -> None:
|
|
"""
|
|
Invite a user to participate in a particular room. They do not start participating in the
|
|
room until they actually join the room.
|
|
|
|
Only users currently in the room can invite other users to join that room.
|
|
|
|
If the user was invited to the room, the homeserver will add a `m.room.member`_ event to
|
|
the room.
|
|
|
|
See also: `API reference <https://spec.matrix.org/v1.1/client-server-api/#post_matrixclientv3roomsroomidinvite>`__
|
|
|
|
.. _m.room.member: https://spec.matrix.org/v1.1/client-server-api/#mroommember
|
|
|
|
Args:
|
|
room_id: The ID of the room to which to invite the user.
|
|
user_id: The fully qualified user ID of the invitee.
|
|
reason: The reason the user was invited. This will be supplied as the ``reason`` on
|
|
the `m.room.member`_ event.
|
|
extra_content: Additional properties for the invite event content.
|
|
If a non-empty dict is passed, the invite event will be created using
|
|
the ``PUT /state/m.room.member/...`` endpoint instead of ``POST /invite``.
|
|
"""
|
|
if extra_content:
|
|
await self.send_member_event(
|
|
room_id, user_id, Membership.INVITE, extra_content=extra_content
|
|
)
|
|
else:
|
|
data = {"user_id": user_id}
|
|
if reason:
|
|
data["reason"] = reason
|
|
await self.api.request(Method.POST, Path.v3.rooms[room_id].invite, content=data)
|
|
|
|
# endregion
|
|
# region 8.4.2 Leaving rooms
|
|
# API reference: https://spec.matrix.org/v1.1/client-server-api/#leaving-rooms
|
|
|
|
async def leave_room(
|
|
self,
|
|
room_id: RoomID,
|
|
reason: str | None = None,
|
|
extra_content: dict[str, JSON] | None = None,
|
|
raise_not_in_room: bool = False,
|
|
) -> None:
|
|
"""
|
|
Stop participating in a particular room, i.e. leave the room.
|
|
|
|
If the user was already in the room, they will no longer be able to see new events in the
|
|
room. If the room requires an invite to join, they will need to be re-invited before they
|
|
can re-join.
|
|
|
|
If the user was invited to the room, but had not joined, this call serves to reject the
|
|
invite.
|
|
|
|
The user will still be allowed to retrieve history from the room which they were previously
|
|
allowed to see.
|
|
|
|
See also: `API reference <https://spec.matrix.org/v1.1/client-server-api/#post_matrixclientv3roomsroomidleave>`__
|
|
|
|
Args:
|
|
room_id: The ID of the room to leave.
|
|
reason: The reason for leaving the room. This will be supplied as the ``reason`` on
|
|
the updated `m.room.member`_ event.
|
|
extra_content: Additional properties for the leave event content.
|
|
If a non-empty dict is passed, the leave event will be created using
|
|
the ``PUT /state/m.room.member/...`` endpoint instead of ``POST /leave``.
|
|
raise_not_in_room: Should errors about the user not being in the room be raised?
|
|
"""
|
|
try:
|
|
if extra_content:
|
|
await self.send_member_event(
|
|
room_id, self.mxid, Membership.LEAVE, extra_content=extra_content
|
|
)
|
|
else:
|
|
data = {}
|
|
if reason:
|
|
data["reason"] = reason
|
|
await self.api.request(Method.POST, Path.v3.rooms[room_id].leave, content=data)
|
|
except MNotJoined:
|
|
if raise_not_in_room:
|
|
raise
|
|
except MatrixRequestError as e:
|
|
# TODO remove this once MSC3848 is released and minimum spec version is bumped
|
|
if "not in room" not in e.message or raise_not_in_room:
|
|
raise
|
|
|
|
async def knock_room(
|
|
self,
|
|
room_id_or_alias: RoomID | RoomAlias,
|
|
reason: str | None = None,
|
|
servers: list[str] | None = None,
|
|
) -> RoomID:
|
|
"""
|
|
Knock on a room, i.e. request to join it by its ID or alias, with an optional list of
|
|
servers to ask about the ID from.
|
|
|
|
See also: `API reference <https://spec.matrix.org/v1.3/client-server-api/#post_matrixclientv3knockroomidoralias>`__
|
|
|
|
Args:
|
|
room_id_or_alias: The ID of the room to knock on, or an alias pointing to the room.
|
|
reason: The reason for knocking on the room. This will be supplied as the ``reason`` on
|
|
the updated `m.room.member`_ event.
|
|
servers: A list of servers to ask about the room ID to knock. Not applicable for aliases,
|
|
as aliases already contain the necessary server information.
|
|
|
|
Returns:
|
|
The ID of the room the user knocked on.
|
|
"""
|
|
data = {}
|
|
if reason:
|
|
data["reason"] = reason
|
|
query_params = CIMultiDict()
|
|
for server_name in servers or []:
|
|
query_params.add("server_name", server_name)
|
|
content = await self.api.request(
|
|
Method.POST,
|
|
Path.v3.knock[room_id_or_alias],
|
|
content=data,
|
|
query_params=query_params,
|
|
)
|
|
try:
|
|
return content["room_id"]
|
|
except KeyError:
|
|
raise MatrixResponseError("`room_id` not in response.")
|
|
|
|
async def forget_room(self, room_id: RoomID) -> None:
|
|
"""
|
|
Stop remembering a particular room, i.e. forget it.
|
|
|
|
In general, history is a first class citizen in Matrix. After this API is called, however,
|
|
a user will no longer be able to retrieve history for this room. If all users on a
|
|
homeserver forget a room, the room is eligible for deletion from that homeserver.
|
|
|
|
If the user is currently joined to the room, they must leave the room before calling this
|
|
API.
|
|
|
|
See also: `API reference <https://spec.matrix.org/v1.1/client-server-api/#post_matrixclientv3roomsroomidforget>`__
|
|
|
|
Args:
|
|
room_id: The ID of the room to forget.
|
|
"""
|
|
await self.api.request(Method.POST, Path.v3.rooms[room_id].forget)
|
|
|
|
async def kick_user(
|
|
self,
|
|
room_id: RoomID,
|
|
user_id: UserID,
|
|
reason: str = "",
|
|
extra_content: dict[str, JSON] | None = None,
|
|
) -> None:
|
|
"""
|
|
Kick a user from the room.
|
|
|
|
The caller must have the required power level in order to perform this operation.
|
|
|
|
Kicking a user adjusts the target member's membership state to be ``leave`` with an optional
|
|
``reason``. Like with other membership changes, a user can directly adjust the target
|
|
member's state by calling :meth:`EventMethods.send_state_event` with
|
|
:attr:`EventType.ROOM_MEMBER` as the event type and the ``user_id`` as the state key.
|
|
|
|
See also: `API reference <https://spec.matrix.org/v1.1/client-server-api/#post_matrixclientv3roomsroomidkick>`__
|
|
|
|
Args:
|
|
room_id: The ID of the room from which the user should be kicked.
|
|
user_id: The fully qualified user ID of the user being kicked.
|
|
reason: The reason the user has been kicked. This will be supplied as the ``reason`` on
|
|
the target's updated `m.room.member`_ event.
|
|
extra_content: Additional properties for the kick event content.
|
|
If a non-empty dict is passed, the kick event will be created using
|
|
the ``PUT /state/m.room.member/...`` endpoint instead of ``POST /kick``.
|
|
|
|
.. _m.room.member: https://spec.matrix.org/v1.1/client-server-api/#mroommember
|
|
"""
|
|
if extra_content:
|
|
if reason and "reason" not in extra_content:
|
|
extra_content["reason"] = reason
|
|
await self.send_member_event(
|
|
room_id, user_id, Membership.LEAVE, extra_content=extra_content
|
|
)
|
|
return
|
|
await self.api.request(
|
|
Method.POST, Path.v3.rooms[room_id].kick, {"user_id": user_id, "reason": reason}
|
|
)
|
|
|
|
# endregion
|
|
# region 8.4.2.1 Banning users in a room
|
|
# API reference: https://spec.matrix.org/v1.1/client-server-api/#banning-users-in-a-room
|
|
|
|
async def ban_user(
|
|
self,
|
|
room_id: RoomID,
|
|
user_id: UserID,
|
|
reason: str = "",
|
|
extra_content: dict[str, JSON] | None = None,
|
|
) -> None:
|
|
"""
|
|
Ban a user in the room. If the user is currently in the room, also kick them. When a user is
|
|
banned from a room, they may not join it or be invited to it until they are unbanned. The
|
|
caller must have the required power level in order to perform this operation.
|
|
|
|
See also: `API reference <https://spec.matrix.org/v1.1/client-server-api/#post_matrixclientv3roomsroomidban>`__
|
|
|
|
Args:
|
|
room_id: The ID of the room from which the user should be banned.
|
|
user_id: The fully qualified user ID of the user being banned.
|
|
reason: The reason the user has been kicked. This will be supplied as the ``reason`` on
|
|
the target's updated `m.room.member`_ event.
|
|
extra_content: Additional properties for the ban event content.
|
|
If a non-empty dict is passed, the ban will be created using
|
|
the ``PUT /state/m.room.member/...`` endpoint instead of ``POST /ban``.
|
|
|
|
.. _m.room.member: https://spec.matrix.org/v1.1/client-server-api/#mroommember
|
|
"""
|
|
if extra_content:
|
|
if reason and "reason" not in extra_content:
|
|
extra_content["reason"] = reason
|
|
await self.send_member_event(
|
|
room_id, user_id, Membership.BAN, extra_content=extra_content
|
|
)
|
|
return
|
|
await self.api.request(
|
|
Method.POST, Path.v3.rooms[room_id].ban, {"user_id": user_id, "reason": reason}
|
|
)
|
|
|
|
async def unban_user(
|
|
self,
|
|
room_id: RoomID,
|
|
user_id: UserID,
|
|
reason: str = "",
|
|
extra_content: dict[str, JSON] | None = None,
|
|
) -> None:
|
|
"""
|
|
Unban a user from the room. This allows them to be invited to the room, and join if they
|
|
would otherwise be allowed to join according to its join rules. The caller must have the
|
|
required power level in order to perform this operation.
|
|
|
|
See also: `API reference <https://spec.matrix.org/v1.1/client-server-api/#post_matrixclientv3roomsroomidunban>`__
|
|
|
|
Args:
|
|
room_id: The ID of the room from which the user should be unbanned.
|
|
user_id: The fully qualified user ID of the user being banned.
|
|
reason: The reason the user has been unbanned. This will be supplied as the ``reason`` on
|
|
the target's updated `m.room.member`_ event.
|
|
extra_content: Additional properties for the unban (leave) event content.
|
|
If a non-empty dict is passed, the unban will be created using
|
|
the ``PUT /state/m.room.member/...`` endpoint instead of ``POST /unban``.
|
|
"""
|
|
if extra_content:
|
|
if reason and "reason" not in extra_content:
|
|
extra_content["reason"] = reason
|
|
await self.send_member_event(
|
|
room_id, user_id, Membership.LEAVE, extra_content=extra_content
|
|
)
|
|
return
|
|
await self.api.request(
|
|
Method.POST, Path.v3.rooms[room_id].unban, {"user_id": user_id, "reason": reason}
|
|
)
|
|
|
|
# endregion
|
|
|
|
# endregion
|
|
# region 8.5 Listing rooms
|
|
# API reference: https://spec.matrix.org/v1.1/client-server-api/#listing-rooms
|
|
|
|
async def get_room_directory_visibility(self, room_id: RoomID) -> RoomDirectoryVisibility:
|
|
"""
|
|
Get the visibility of the room on the server's public room directory.
|
|
|
|
See also: `API reference <https://spec.matrix.org/v1.1/client-server-api/#get_matrixclientv3directorylistroomroomid>`__
|
|
|
|
Args:
|
|
room_id: The ID of the room.
|
|
|
|
Returns:
|
|
The visibility of the room in the directory.
|
|
"""
|
|
resp = await self.api.request(Method.GET, Path.v3.directory.list.room[room_id])
|
|
try:
|
|
return RoomDirectoryVisibility(resp["visibility"])
|
|
except KeyError:
|
|
raise MatrixResponseError("`visibility` not in response.")
|
|
except ValueError:
|
|
raise MatrixResponseError(
|
|
f"Invalid value for `visibility` in response: {resp['visibility']}"
|
|
)
|
|
|
|
async def set_room_directory_visibility(
|
|
self, room_id: RoomID, visibility: RoomDirectoryVisibility
|
|
) -> None:
|
|
"""
|
|
Set the visibility of the room in the server's public room directory.
|
|
|
|
Servers may choose to implement additional access control checks here, for instance that
|
|
room visibility can only be changed by the room creator or a server administrator.
|
|
|
|
Args:
|
|
room_id: The ID of the room.
|
|
visibility: The new visibility setting for the room.
|
|
|
|
.. _API reference: https://spec.matrix.org/v1.1/client-server-api/#put_matrixclientv3directorylistroomroomid
|
|
"""
|
|
await self.api.request(
|
|
Method.PUT,
|
|
Path.v3.directory.list.room[room_id],
|
|
{
|
|
"visibility": visibility.value,
|
|
},
|
|
)
|
|
|
|
async def get_room_directory(
|
|
self,
|
|
limit: int | None = None,
|
|
server: str | None = None,
|
|
since: DirectoryPaginationToken | None = None,
|
|
search_query: str | None = None,
|
|
include_all_networks: bool | None = None,
|
|
third_party_instance_id: str | None = None,
|
|
) -> RoomDirectoryResponse:
|
|
"""
|
|
Get a list of public rooms from the server's room directory.
|
|
|
|
See also: `API reference <https://spec.matrix.org/v1.1/client-server-api/#get_matrixclientv3publicrooms>`__
|
|
|
|
Args:
|
|
limit: The maximum number of results to return.
|
|
server: The server to fetch the room directory from. Defaults to the user's server.
|
|
since: A pagination token from a previous request, allowing clients to get the next (or
|
|
previous) batch of rooms. The direction of pagination is specified solely by which
|
|
token is supplied, rather than via an explicit flag.
|
|
search_query: A string to search for in the room metadata, e.g. name, topic, canonical
|
|
alias etc.
|
|
include_all_networks: Whether or not to include rooms from all known networks/protocols
|
|
from application services on the homeserver. Defaults to false.
|
|
third_party_instance_id: The specific third party network/protocol to request from the
|
|
homeserver. Can only be used if ``include_all_networks`` is false.
|
|
|
|
Returns:
|
|
The relevant pagination tokens, an estimate of the total number of public rooms and the
|
|
paginated chunk of public rooms.
|
|
"""
|
|
method = (
|
|
Method.GET
|
|
if (
|
|
search_query is None
|
|
and include_all_networks is None
|
|
and third_party_instance_id is None
|
|
)
|
|
else Method.POST
|
|
)
|
|
content = {}
|
|
if limit is not None:
|
|
content["limit"] = limit
|
|
if since is not None:
|
|
content["since"] = since
|
|
if search_query is not None:
|
|
content["filter"] = {"generic_search_term": search_query}
|
|
if include_all_networks is not None:
|
|
content["include_all_networks"] = include_all_networks
|
|
if third_party_instance_id is not None:
|
|
content["third_party_instance_id"] = third_party_instance_id
|
|
query_params = {"server": server} if server is not None else None
|
|
|
|
content = await self.api.request(
|
|
method, Path.v3.publicRooms, content, query_params=query_params
|
|
)
|
|
|
|
return RoomDirectoryResponse.deserialize(content)
|
|
|
|
# endregion
|