From d1313ca411b5d7c04b5bd1da510f9bdc6f7b10c4 Mon Sep 17 00:00:00 2001 From: miruka Date: Mon, 29 Mar 2021 18:26:33 -0400 Subject: [PATCH] Support giving a reason for all membership API Implement MSC2367 (https://github.com/matrix-org/matrix-doc/pull/2367), already supported by synapse: allow giving a reason when joining, leaving, inviting, and unbanning in addition to kicking and banning. --- mio/rooms/room.py | 27 ++++++++++++++++++--------- mio/rooms/rooms.py | 15 ++++++++++++--- mio/rooms/user.py | 9 +++++---- tests/test_room.py | 12 +++++++++--- tests/test_room_user.py | 4 ++-- tests/test_rooms.py | 6 ++++-- 6 files changed, 50 insertions(+), 23 deletions(-) diff --git a/mio/rooms/room.py b/mio/rooms/room.py index 7a3e64d..56711b5 100644 --- a/mio/rooms/room.py +++ b/mio/rooms/room.py @@ -1,10 +1,11 @@ from dataclasses import dataclass, field from pathlib import Path -from typing import TYPE_CHECKING, List, Set, Tuple +from typing import TYPE_CHECKING, List, Optional, Set, Tuple from ..core.callbacks import CallbackGroup, Callbacks, EventCallbacks from ..core.data import JSONFile, Parent, Runtime from ..core.types import RoomAlias, RoomId, UserId +from ..core.utils import remove_none from .state import RoomState from .timeline import Timeline @@ -75,23 +76,31 @@ async def create_alias(self, alias: RoomAlias) -> None: ) - async def invite(self, user_id: UserId) -> None: - url = self.client.api / "rooms" / self.id / "invite" - await self.client.send_json("POST", url, {"user_id": user_id}) + async def invite( + self, user_id: UserId, reason: Optional[str] = None, + ) -> None: + await self.client.send_json( + "POST", + self.client.api / "rooms" / self.id / "invite", + remove_none({"user_id": user_id, "reason": reason}), + ) - async def leave(self) -> None: - url = self.client.api / "rooms" / self.id / "leave" - await self.client.send_json("POST", url) + async def leave(self, reason: Optional[str] = None) -> None: + await self.client.send_json( + "POST", + self.client.api / "rooms" / self.id / "leave", + remove_none({"reason": reason}), + ) - async def forget(self) -> None: + async def forget(self, leave_reason: Optional[str] = None) -> None: # Prevent a sync from occuring AFTER leaving room, but BEFORE having # marked it as forgotten. We will get a "we left" event on next sync, # which we must ignore if we know we forgot the room. with self.client.sync.pause(): if not self.left: - await self.leave() + await self.leave(leave_reason) url = self.client.api / "rooms" / self.id / "forget" await self.client.send_json("POST", url) diff --git a/mio/rooms/rooms.py b/mio/rooms/rooms.py index 413d605..a39740b 100644 --- a/mio/rooms/rooms.py +++ b/mio/rooms/rooms.py @@ -139,9 +139,18 @@ async def create( return result["room_id"] - async def join(self, id_or_alias: Union[RoomId, RoomAlias]) -> RoomId: - url = self.client.api / "join" / id_or_alias - result = await self.client.send_json("POST", url) + async def join( + self, + id_or_alias: Union[RoomId, RoomAlias], + reason: Optional[str] = None, + ) -> RoomId: + + result = await self.client.send_json( + "POST", + self.client.api / "join" / id_or_alias, + remove_none({"reason": reason}), + ) + return result["room_id"] diff --git a/mio/rooms/user.py b/mio/rooms/user.py index 7159d45..a6a61b9 100644 --- a/mio/rooms/user.py +++ b/mio/rooms/user.py @@ -3,6 +3,7 @@ from ..core.data import Parent from ..core.types import MXC, UserId +from ..core.utils import remove_none from .contents.users import Member from .events import StateBase @@ -90,7 +91,7 @@ async def kick(self, reason: Optional[str] = None) -> None: await self.room.client.send_json( "POST", self.room.client.api / "rooms" / self.room.id / "kick", - {"user_id": self.user_id, "reason": reason}, + remove_none({"user_id": self.user_id, "reason": reason}), ) @@ -98,13 +99,13 @@ async def ban(self, reason: Optional[str] = None) -> None: await self.room.client.send_json( "POST", self.room.client.api / "rooms" / self.room.id / "ban", - {"user_id": self.user_id, "reason": reason}, + remove_none({"user_id": self.user_id, "reason": reason}), ) - async def unban(self) -> None: + async def unban(self, reason: Optional[str] = None) -> None: await self.room.client.send_json( "POST", self.room.client.api / "rooms" / self.room.id / "unban", - {"user_id": self.user_id}, + remove_none({"user_id": self.user_id, "reason": reason}), ) diff --git a/tests/test_room.py b/tests/test_room.py index 9f845ad..70fc0f4 100644 --- a/tests/test_room.py +++ b/tests/test_room.py @@ -35,19 +35,21 @@ async def test_create_alias(room: Room): async def test_invite(room: Room, bob: Client): assert room.id not in bob.rooms - await room.invite(bob.user_id) + await room.invite(bob.user_id, reason="test") await bob.sync.once() assert room.id in bob.rooms.invited + assert bob.rooms[room.id].state.me.membership_reason == "test" async def test_leave(e2e_room: Room): await e2e_room.timeline.send(Text("make a session")) assert e2e_room.id in e2e_room.client._e2e.out_group_sessions - await e2e_room.leave() + await e2e_room.leave(reason="bye") await e2e_room.client.sync.once() assert e2e_room.left assert e2e_room.id not in e2e_room.client._e2e.out_group_sessions + assert e2e_room.state.me.membership_reason == "bye" async def test_forget(room: Room, bob: Client): @@ -55,13 +57,17 @@ async def test_forget(room: Room, bob: Client): await bob.rooms.join(room.id) assert not room.left - await room.forget() + await room.forget(leave_reason="bye") await room.client.sync.once() assert room.left assert room.id not in room.client.rooms assert room.id in room.client.rooms.forgotten + await bob.sync.once() + bob_members = bob.rooms[room.id].state.leavers + assert bob_members[room.client.user_id].membership_reason == "bye" + # Make sure events are not ignored if we rejoin or get invited again await room.client.rooms.join(room.id) await room.client.sync.once() diff --git a/tests/test_room_user.py b/tests/test_room_user.py index 0972c70..b84fb82 100644 --- a/tests/test_room_user.py +++ b/tests/test_room_user.py @@ -94,9 +94,9 @@ async def test_ban_unban(alice: Client, bob: Client, room: Room): with raises(MatrixError): await bob.rooms.join(room.id) - await room.state.banned[bob.user_id].unban() + await room.state.banned[bob.user_id].unban("mistake") await alice.sync.once() assert room.state.leavers[bob.user_id].banned_by is None - assert room.state.leavers[bob.user_id].membership_reason is None + assert room.state.leavers[bob.user_id].membership_reason == "mistake" await bob.rooms.join(room.id) diff --git a/tests/test_rooms.py b/tests/test_rooms.py index 2a9f489..76ff89b 100644 --- a/tests/test_rooms.py +++ b/tests/test_rooms.py @@ -166,9 +166,10 @@ async def test_join_room_id(room: Room, bob: Client): await bob.sync.once() assert room.id not in bob.rooms - await bob.rooms.join(room.id) + await bob.rooms.join(room.id, reason="test") await bob.sync.once() assert room.id in bob.rooms + assert bob.rooms[room.id].state.me.membership_reason == "test" async def test_join_room_alias(room: Room, bob: Client): @@ -180,6 +181,7 @@ async def test_join_room_alias(room: Room, bob: Client): await bob.sync.once() assert room.id not in bob.rooms - await bob.rooms.join(alias) + await bob.rooms.join(alias, reason="test") await bob.sync.once() assert room.id in bob.rooms + assert bob.rooms[room.id].state.me.membership_reason == "test"