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
15 changes: 15 additions & 0 deletions docs/docs/reference/space.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Space

`Space` extends `Room` to represent a Matrix Space. It is returned by `Bot.get_space()` and `Bot.get_spaces()` instead of a plain `Room` whenever the room type is `m.space`.

```python
from matrix import Bot

bot = Bot()

space = bot.get_space("!space123:matrix.org")
if space:
print(space.name)
```

::: matrix.space.Space
1 change: 1 addition & 0 deletions docs/mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ nav:
- Protocols: reference/protocols.md
- Registry: reference/registry.md
- Room: reference/room.md
- Space: reference/space.md
- Scheduler: reference/scheduler.md
- Types: reference/types.md
- Examples:
Expand Down
65 changes: 64 additions & 1 deletion matrix/bot.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from nio import AsyncClient, Event, MatrixRoom

from .room import Room
from .space import Space
from .group import Group
from .config import Config
from .context import Context
Expand Down Expand Up @@ -87,19 +88,81 @@ def get_room(self, room_id: str) -> Room | None:

Returns the `Room` object corresponding to `room_id` if it exists in
the client's known rooms. Returns `None` if the room cannot be found.
If the room is a space, a `Space` instance is returned instead.

## Example

```python
room = bot.get_room("!abc123:matrix.org")

if room:
print(room.name)
```
"""
if matrix_room := self.client.rooms.get(room_id):
return Room(matrix_room=matrix_room, client=self.client)
room_cls = Space if matrix_room.room_type == "m.space" else Room
return room_cls(matrix_room=matrix_room, client=self.client)
return None

def get_rooms(self) -> list[Room]:
"""Retrieve a list of all rooms the bot is aware of.

This method returns a list of `Room` objects for all rooms currently
known to the client. This includes both regular rooms and spaces;
spaces are returned as `Space` instances.

## Example

```python
rooms = bot.get_rooms()

for room in rooms:
print(room.name)
```
"""
rooms = []

for matrix_room in self.client.rooms.values():
room_cls = Space if matrix_room.room_type == "m.space" else Room
rooms.append(room_cls(matrix_room=matrix_room, client=self.client))

return rooms

def get_space(self, space_id: str) -> Space | None:
"""Retrieve a `Space` instance by its Matrix room ID.

Returns the `Space` object corresponding to `space_id` if it exists in
the client's known rooms and is a space. Returns `None` otherwise.

## Example

```python
space = bot.get_space("!space123:matrix.org")

if space:
print(space.name)
```
"""
room = self.get_room(space_id)
return room if isinstance(room, Space) else None

def get_spaces(self) -> list[Space]:
"""Retrieve a list of all spaces the bot is aware of.

This method returns a list of `Space` objects for all rooms currently
known to the client that are identified as spaces.

## Example

```python
spaces = bot.get_spaces()

for space in spaces:
print(space.name)
```
"""
return [room for room in self.get_rooms() if isinstance(room, Space)]

def load_extension(self, extension: Extension) -> None:
self.log.debug(f"Loading extension: '{extension.name}'")

Expand Down
5 changes: 5 additions & 0 deletions matrix/space.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from .room import Room


class Space(Room):
pass

@PenguinBoi12 PenguinBoi12 Jun 17, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This is normal, additional features will be added in other PR

82 changes: 81 additions & 1 deletion tests/test_bot.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from unittest.mock import AsyncMock, MagicMock, patch
from nio import MatrixRoom, RoomMessageText

from matrix.bot import Bot, Config, Extension, Room
from matrix.bot import Bot, Config, Extension, Room, Space
from matrix.errors import (
CheckError,
CommandNotFoundError,
Expand Down Expand Up @@ -67,6 +67,86 @@ def event():
)


@pytest.fixture
def space_room():
space = MatrixRoom(room_id="!space:id", own_user_id="grace")
space.name = "Test Space"
space.room_type = "m.space"
return space


def test_get_room__with_known_room_id__expect_room(bot, room):
bot._client.rooms = {room.room_id: room}

result = bot.get_room(room.room_id)

assert type(result) is Room
assert result.matrix_room is room


def test_get_room__with_space_room_id__expect_space(bot, space_room):
bot._client.rooms = {space_room.room_id: space_room}

result = bot.get_room(space_room.room_id)

assert isinstance(result, Space)
assert result.matrix_room is space_room


def test_get_room__with_unknown_room_id__expect_none(bot):
bot._client.rooms = {}

assert bot.get_room("!missing:id") is None


def test_get_rooms__expect_all_known_rooms(bot, room, space_room):
bot._client.rooms = {room.room_id: room, space_room.room_id: space_room}

rooms = bot.get_rooms()
by_id = {r.room_id: r for r in rooms}

assert len(rooms) == 2
assert type(by_id[room.room_id]) is Room
assert isinstance(by_id[space_room.room_id], Space)


def test_get_space__with_space_room_id__expect_space(bot, space_room):
bot._client.rooms = {space_room.room_id: space_room}

result = bot.get_space(space_room.room_id)

assert isinstance(result, Space)
assert result.matrix_room is space_room


def test_get_space__with_non_space_room_id__expect_none(bot, room):
bot._client.rooms = {room.room_id: room}

assert bot.get_space(room.room_id) is None


def test_get_space__with_unknown_room_id__expect_none(bot):
bot._client.rooms = {}

assert bot.get_space("!missing:id") is None


def test_get_spaces__with_mixed_rooms__expect_only_spaces(bot, room, space_room):
bot._client.rooms = {room.room_id: room, space_room.room_id: space_room}

spaces = bot.get_spaces()

assert len(spaces) == 1
assert isinstance(spaces[0], Space)
assert spaces[0].matrix_room is space_room


def test_get_spaces__with_no_spaces__expect_empty_list(bot, room):
bot._client.rooms = {room.room_id: room}

assert bot.get_spaces() == []


def test_bot_init_with_config():
bot = Bot()
bot._load_config(Config(username="grace", password="grace1234"))
Expand Down
Loading