Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions matrix_client/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -744,3 +744,29 @@ def get_room_members(self, room_id):
room_id (str): The room to get the member events for.
"""
return self._send("GET", "/rooms/{}/members".format(quote(room_id)))

def set_join_rule(self, room_id, join_rule):
"""Set the rule for users wishing to join the room.

Args:
room_id(str): The room to set the rules for.
join_rule(str): The chosen rule. One of: ["public", "knock",
"invite", "private"]
"""
content = {
"join_rule": join_rule
}
return self.send_state_event(room_id, "m.room.join_rules", content)

def set_guest_access(self, room_id, guest_access):
"""Set the guest access policy of the room.

Args:
room_id(str): The room to set the rules for.
guest_access(str): Wether guests can join. One of: ["can_join",
"forbidden"]
"""
content = {
"guest_access": guest_access
}
return self.send_state_event(room_id, "m.room.guest_access", content)
19 changes: 12 additions & 7 deletions matrix_client/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -495,26 +495,31 @@ def _process_state_event(self, state_event, current_room):
if "type" not in state_event:
return # Ignore event
etype = state_event["type"]
econtent = state_event["content"]

# Don't keep track of room state if caching turned off
if self._cache_level.value >= 0:
if etype == "m.room.name":
current_room.name = state_event["content"].get("name", None)
current_room.name = econtent.get("name")
elif etype == "m.room.canonical_alias":
current_room.canonical_alias = state_event["content"].get("alias")
current_room.canonical_alias = econtent.get("alias")
elif etype == "m.room.topic":
current_room.topic = state_event["content"].get("topic", None)
current_room.topic = econtent.get("topic")
elif etype == "m.room.aliases":
current_room.aliases = state_event["content"].get("aliases", None)
current_room.aliases = econtent.get("aliases")
elif etype == "m.room.join_rules":
current_room.invite_only = econtent["join_rule"] == "invite"
elif etype == "m.room.guest_access":
current_room.guest_access = econtent["guest_access"] == "can_join"
elif etype == "m.room.member" and self._cache_level == CACHE.ALL:
# tracking room members can be large e.g. #matrix:matrix.org
if state_event["content"]["membership"] == "join":
if econtent["membership"] == "join":
current_room._mkmembers(
User(self.api,
state_event["state_key"],
state_event["content"].get("displayname", None))
econtent.get("displayname"))
)
elif state_event["content"]["membership"] in ("leave", "kick", "invite"):
elif econtent["membership"] in ("leave", "kick", "invite"):
current_room._rmmembers(state_event["state_key"])

for listener in current_room.state_listeners:
Expand Down
37 changes: 37 additions & 0 deletions matrix_client/room.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ def __init__(self, client, room_id):
self.canonical_alias = None
self.aliases = []
self.topic = None
self.invite_only = None
self.guest_access = None
self._prev_batch = None
self._members = []

Expand Down Expand Up @@ -624,6 +626,41 @@ def modify_required_power_levels(self, events=None, **kwargs):
except MatrixRequestError:
return False

def set_invite_only(self, invite_only):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I really think we should make this take a (fake) enum rather than a boolean
(change method name also obviously). That way if in the future knock or
private are used, we can accommodate that without changing the API for this
class. Thoughts?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wanted to have something intuitive. As I understood it, the goal of those higher-level methods is to ease the use of the CS API, hence it didn't make sense to me to have basically the same method in room.py and api.py.
Also, I'm thinking that if knock or private are used, they will necessitate changes to this method anyway, so it may be good to wait to include them. And depending on what behavior they introduce, maybe it will make sense to have two different setters for the same underlying api call!

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You convinced me. Thanks again!

"""Set how the room can be joined.

Args:
invite_only(bool): If True, users will have to be invited to join
the room. If False, anyone who knows the room link can join.

Returns:
True if successful, False if not
"""
join_rule = "invite" if invite_only else "public"
try:
self.client.api.set_join_rule(self.room_id, join_rule)
self.invite_only = invite_only
return True
except MatrixRequestError:
return False

def set_guest_access(self, allow_guests):
"""Set whether guests can join the room.

Args:
allow_guests(bool): If True, guests can join.

Returns:
True if successful, False if not
"""
guest_access = "can_join" if allow_guests else "forbidden"
try:
self.client.api.set_guest_access(self.room_id, guest_access)
self.guest_access = allow_guests
return True
except MatrixRequestError:
return False

@property
def prev_batch(self):
return self._prev_batch
Expand Down
46 changes: 46 additions & 0 deletions test/client_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,20 @@ def test_state_event():
client._process_state_event(ev, room)
assert len(room._members) == 0

# test join_rules
room.invite_only = False
ev["type"] = "m.room.join_rules"
ev["content"] = {"join_rule": "invite"}
client._process_state_event(ev, room)
assert room.invite_only

# test guest_access
room.guest_access = False
ev["type"] = "m.room.guest_access"
ev["content"] = {"guest_access": "can_join"}
client._process_state_event(ev, room)
assert room.guest_access


def test_get_user():
client = MatrixClient("http://example.com")
Expand Down Expand Up @@ -406,3 +420,35 @@ def test_cache():
assert m_none.rooms[room_id]._members == m_some.rooms[room_id]._members == []
assert len(m_all.rooms[room_id]._members) == 1
assert m_all.rooms[room_id]._members[0].user_id == "@alice:example.com"


@responses.activate
def test_room_join_rules():
client = MatrixClient(HOSTNAME)
room_id = "!UcYsUzyxTGDxLBEvLz:matrix.org"
room = client._mkroom(room_id)
assert room.invite_only is None
join_rules_state_path = HOSTNAME + MATRIX_V2_API_PATH + \
"/rooms/" + quote(room_id) + "/state/m.room.join_rules"

responses.add(responses.PUT, join_rules_state_path,
json=response_examples.example_event_response)

assert room.set_invite_only(True)
assert room.invite_only


@responses.activate
def test_room_guest_access():
client = MatrixClient(HOSTNAME)
room_id = "!UcYsUzyxTGDxLBEvLz:matrix.org"
room = client._mkroom(room_id)
assert room.guest_access is None
guest_access_state_path = HOSTNAME + MATRIX_V2_API_PATH + \
"/rooms/" + quote(room_id) + "/state/m.room.guest_access"

responses.add(responses.PUT, guest_access_state_path,
json=response_examples.example_event_response)

assert room.set_guest_access(True)
assert room.guest_access