Skip to content

Commit

Permalink
Add ability to Knock on a Room and enable Knocking for a Room (#418)
Browse files Browse the repository at this point in the history
* Adds working knock

* Adds basic tests for client/type Knock

* Adds comments on the RoomKnock classes

* Adds ability to enable knocking for a room

* Use suggested commit. Ran linters
  • Loading branch information
justin-russell committed Jul 14, 2023
1 parent 17e34e1 commit f3a3cca
Show file tree
Hide file tree
Showing 6 changed files with 113 additions and 1 deletion.
30 changes: 30 additions & 0 deletions nio/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -777,6 +777,36 @@ def room_unban(
Api.to_json(body),
)

@staticmethod
def room_knock(
access_token: str,
room_id: str,
reason: Optional[str] = None,
) -> Tuple[str, str, str]:
"""Knocks on a room for the user.
Returns the HTTP method, HTTP path and data for the request.
Args:
access_token (str): The access token to be used with the request.
room_id (str): The room id of the room that the user will be
knocking on.
reason (str, optional): The reason the user is knocking.
"""

path = ["knock", room_id]
query_parameters = {"access_token": access_token}
body = {}

if reason:
body["reason"] = reason

return (
"POST",
Api._build_path(path, query_parameters),
Api.to_json(body),
)

@staticmethod
def room_invite(access_token, room_id, user_id):
# type (str, str, str) -> Tuple[str, str, str]
Expand Down
46 changes: 46 additions & 0 deletions nio/client/async_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,8 @@
RoomKeyRequestResponse,
RoomKickError,
RoomKickResponse,
RoomKnockError,
RoomKnockResponse,
RoomLeaveError,
RoomLeaveResponse,
RoomMessagesError,
Expand Down Expand Up @@ -2234,6 +2236,50 @@ async def join(self, room_id: str) -> Union[JoinResponse, JoinError]:
method, path, data = Api.join(self.access_token, room_id)
return await self._send(JoinResponse, method, path, data)

@logged_in_async
async def room_knock(
self,
room_id: str,
reason: Optional[str] = None,
) -> Union[RoomKnockResponse, RoomKnockError]:
"""Knock on a room.
Calls receive_response() to update the client state if necessary.
Returns either a `RoomKnockResponse` if the request was successful or
a `RoomKnockError` if there was an error with the request.
Args:
room_id (str): The room id of the room that the user is
knocking on.
reason (str, optional): The reason for the knock.
"""
method, path, data = Api.room_knock(
self.access_token,
room_id,
reason,
)
return await self._send(RoomKnockResponse, method, path, data)

@logged_in_async
async def room_enable_knocking(
self,
room_id: str,
) -> Union[RoomPutStateResponse, RoomPutStateError]:
"""Enables knocking for a room.
Returns either a `RoomPutStateResponse` if the request was successful
or a `RoomPutStateError` if there was an error with the request.
Args:
room_id (str): The room id of the room to enable knocking for.
"""
return await self.room_put_state(
room_id,
event_type="m.room.join_rules",
content={"join_rule": "knock"},
)

@logged_in_async
async def room_invite(
self,
Expand Down
2 changes: 1 addition & 1 deletion nio/events/room_events.py
Original file line number Diff line number Diff line change
Expand Up @@ -1483,7 +1483,7 @@ class RoomMemberEvent(Event):
cases except for when membership is join, the user ID in the sender
attribute does not need to match the user ID in the state_key.
membership (str): The membership state of the user. One of "invite",
"join", "leave", "ban".
"join", "leave", "ban", "knock".
prev_membership (str, optional): The previous membership state that
this one is overwriting. Can be None in which case the membership
state is assumed to have been "leave".
Expand Down
14 changes: 14 additions & 0 deletions nio/responses.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,8 @@
"RoomInviteError",
"RoomKickResponse",
"RoomKickError",
"RoomKnockResponse",
"RoomKnockError",
"RoomLeaveResponse",
"RoomLeaveError",
"RoomForgetResponse",
Expand Down Expand Up @@ -505,6 +507,12 @@ class JoinError(ErrorResponse):
pass


class RoomKnockError(ErrorResponse):
"""A response representing a unsuccessful room knock request."""

pass


class RoomLeaveError(ErrorResponse):
pass

Expand Down Expand Up @@ -1263,6 +1271,12 @@ def create_error(parsed_dict):
return JoinError.from_dict(parsed_dict)


class RoomKnockResponse(RoomIdResponse):
@staticmethod
def create_error(parsed_dict):
return RoomKnockError.from_dict(parsed_dict)


class RoomLeaveResponse(EmptyResponse):
@staticmethod
def create_error(parsed_dict):
Expand Down
16 changes: 16 additions & 0 deletions tests/async_client_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@
RoomInviteResponse,
RoomKeyRequest,
RoomKickResponse,
RoomKnockResponse,
RoomLeaveResponse,
RoomMemberEvent,
RoomMessagesResponse,
Expand Down Expand Up @@ -1431,6 +1432,21 @@ async def test_room_invite(self, async_client, aioresponse):
resp = await async_client.room_invite(TEST_ROOM_ID, ALICE_ID)
assert isinstance(resp, RoomInviteResponse)

async def test_room_knock(self, async_client, aioresponse):
await async_client.receive_response(
LoginResponse.from_dict(self.login_response)
)
assert async_client.logged_in

aioresponse.post(
f"https://example.org/_matrix/client/r0/knock/{TEST_ROOM_ID}?access_token=abc123",
status=200,
payload=self.room_id_response(TEST_ROOM_ID),
)

resp = await async_client.room_knock(TEST_ROOM_ID, reason="test")
assert isinstance(resp, RoomKnockResponse)

async def test_room_leave(self, async_client, aioresponse):
await async_client.receive_response(
LoginResponse.from_dict(self.login_response)
Expand Down
6 changes: 6 additions & 0 deletions tests/responses_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
RoomForgetResponse,
RoomKeyRequestError,
RoomKeyRequestResponse,
RoomKnockResponse,
RoomLeaveResponse,
RoomMessagesResponse,
RoomTypingResponse,
Expand Down Expand Up @@ -251,6 +252,11 @@ def test_join(self):
response = JoinResponse.from_dict(parsed_dict)
assert isinstance(response, JoinResponse)

def test_knock(self):
parsed_dict = TestClass._load_response("tests/data/room_id.json")
response = RoomKnockResponse.from_dict(parsed_dict)
assert isinstance(response, RoomKnockResponse)

def test_room_leave(self):
response = RoomLeaveResponse.from_dict({})
assert isinstance(response, RoomLeaveResponse)
Expand Down

0 comments on commit f3a3cca

Please sign in to comment.