matrix-python 1.4.12a0__tar.gz → 1.5.0a0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- matrix_python-1.5.0a0/.github/ISSUE_TEMPLATE/bug_report.md +28 -0
- matrix_python-1.5.0a0/.github/ISSUE_TEMPLATE/feature--good-first.md +29 -0
- matrix_python-1.5.0a0/.github/ISSUE_TEMPLATE/feature_request.md +21 -0
- {matrix_python-1.4.12a0 → matrix_python-1.5.0a0}/LICENSE +2 -2
- {matrix_python-1.4.12a0/matrix_python.egg-info → matrix_python-1.5.0a0}/PKG-INFO +10 -9
- matrix_python-1.5.0a0/docs/docs/reference/space.md +15 -0
- {matrix_python-1.4.12a0 → matrix_python-1.5.0a0}/docs/mkdocs.yml +1 -0
- {matrix_python-1.4.12a0 → matrix_python-1.5.0a0}/matrix/__init__.py +2 -0
- {matrix_python-1.4.12a0 → matrix_python-1.5.0a0}/matrix/_version.py +3 -3
- matrix_python-1.5.0a0/matrix/api.py +35 -0
- {matrix_python-1.4.12a0 → matrix_python-1.5.0a0}/matrix/bot.py +68 -3
- {matrix_python-1.4.12a0 → matrix_python-1.5.0a0}/matrix/message.py +24 -17
- {matrix_python-1.4.12a0 → matrix_python-1.5.0a0}/matrix/room.py +91 -40
- matrix_python-1.5.0a0/matrix/space.py +45 -0
- {matrix_python-1.4.12a0 → matrix_python-1.5.0a0/matrix_python.egg-info}/PKG-INFO +10 -9
- {matrix_python-1.4.12a0 → matrix_python-1.5.0a0}/matrix_python.egg-info/SOURCES.txt +10 -0
- {matrix_python-1.4.12a0 → matrix_python-1.5.0a0}/matrix_python.egg-info/requires.txt +7 -7
- matrix_python-1.5.0a0/matrix_python.egg-info/scm_file_list.json +105 -0
- matrix_python-1.5.0a0/matrix_python.egg-info/scm_version.json +8 -0
- {matrix_python-1.4.12a0 → matrix_python-1.5.0a0}/pyproject.toml +7 -7
- matrix_python-1.5.0a0/tests/test_api.py +43 -0
- {matrix_python-1.4.12a0 → matrix_python-1.5.0a0}/tests/test_bot.py +98 -2
- {matrix_python-1.4.12a0 → matrix_python-1.5.0a0}/tests/test_message.py +53 -2
- {matrix_python-1.4.12a0 → matrix_python-1.5.0a0}/tests/test_room.py +183 -2
- matrix_python-1.5.0a0/tests/test_space.py +151 -0
- {matrix_python-1.4.12a0 → matrix_python-1.5.0a0}/.github/dependabot.yml +0 -0
- {matrix_python-1.4.12a0 → matrix_python-1.5.0a0}/.github/workflows/CODEOWNERS +0 -0
- {matrix_python-1.4.12a0 → matrix_python-1.5.0a0}/.github/workflows/codeql.yml +0 -0
- {matrix_python-1.4.12a0 → matrix_python-1.5.0a0}/.github/workflows/docs.yml +0 -0
- {matrix_python-1.4.12a0 → matrix_python-1.5.0a0}/.github/workflows/publish.yml +0 -0
- {matrix_python-1.4.12a0 → matrix_python-1.5.0a0}/.github/workflows/scorecard.yml +0 -0
- {matrix_python-1.4.12a0 → matrix_python-1.5.0a0}/.github/workflows/tests.yml +0 -0
- {matrix_python-1.4.12a0 → matrix_python-1.5.0a0}/.gitignore +0 -0
- {matrix_python-1.4.12a0 → matrix_python-1.5.0a0}/CODE_OF_CONDUCT.md +0 -0
- {matrix_python-1.4.12a0 → matrix_python-1.5.0a0}/CONTRIBUTING.md +0 -0
- {matrix_python-1.4.12a0 → matrix_python-1.5.0a0}/README.md +0 -0
- {matrix_python-1.4.12a0 → matrix_python-1.5.0a0}/docs/docs/examples/checks.md +0 -0
- {matrix_python-1.4.12a0 → matrix_python-1.5.0a0}/docs/docs/examples/cooldown.md +0 -0
- {matrix_python-1.4.12a0 → matrix_python-1.5.0a0}/docs/docs/examples/error-handling.md +0 -0
- {matrix_python-1.4.12a0 → matrix_python-1.5.0a0}/docs/docs/examples/extension.md +0 -0
- {matrix_python-1.4.12a0 → matrix_python-1.5.0a0}/docs/docs/examples/index.md +0 -0
- {matrix_python-1.4.12a0 → matrix_python-1.5.0a0}/docs/docs/examples/ping.md +0 -0
- {matrix_python-1.4.12a0 → matrix_python-1.5.0a0}/docs/docs/examples/reaction.md +0 -0
- {matrix_python-1.4.12a0 → matrix_python-1.5.0a0}/docs/docs/examples/scheduler.md +0 -0
- {matrix_python-1.4.12a0 → matrix_python-1.5.0a0}/docs/docs/guides/bigger-bot.md +0 -0
- {matrix_python-1.4.12a0 → matrix_python-1.5.0a0}/docs/docs/guides/checks.md +0 -0
- {matrix_python-1.4.12a0 → matrix_python-1.5.0a0}/docs/docs/guides/commands.md +0 -0
- {matrix_python-1.4.12a0 → matrix_python-1.5.0a0}/docs/docs/guides/configuration.md +0 -0
- {matrix_python-1.4.12a0 → matrix_python-1.5.0a0}/docs/docs/guides/error-handling.md +0 -0
- {matrix_python-1.4.12a0 → matrix_python-1.5.0a0}/docs/docs/guides/events.md +0 -0
- {matrix_python-1.4.12a0 → matrix_python-1.5.0a0}/docs/docs/guides/groups.md +0 -0
- {matrix_python-1.4.12a0 → matrix_python-1.5.0a0}/docs/docs/guides/index.md +0 -0
- {matrix_python-1.4.12a0 → matrix_python-1.5.0a0}/docs/docs/guides/introduction.md +0 -0
- {matrix_python-1.4.12a0 → matrix_python-1.5.0a0}/docs/docs/img/favicon.svg +0 -0
- {matrix_python-1.4.12a0 → matrix_python-1.5.0a0}/docs/docs/img/matrixpy-black.svg +0 -0
- {matrix_python-1.4.12a0 → matrix_python-1.5.0a0}/docs/docs/img/matrixpy-white.svg +0 -0
- {matrix_python-1.4.12a0 → matrix_python-1.5.0a0}/docs/docs/index.md +0 -0
- {matrix_python-1.4.12a0 → matrix_python-1.5.0a0}/docs/docs/reference/bot.md +0 -0
- {matrix_python-1.4.12a0 → matrix_python-1.5.0a0}/docs/docs/reference/checks.md +0 -0
- {matrix_python-1.4.12a0 → matrix_python-1.5.0a0}/docs/docs/reference/command.md +0 -0
- {matrix_python-1.4.12a0 → matrix_python-1.5.0a0}/docs/docs/reference/config.md +0 -0
- {matrix_python-1.4.12a0 → matrix_python-1.5.0a0}/docs/docs/reference/content.md +0 -0
- {matrix_python-1.4.12a0 → matrix_python-1.5.0a0}/docs/docs/reference/context.md +0 -0
- {matrix_python-1.4.12a0 → matrix_python-1.5.0a0}/docs/docs/reference/errors.md +0 -0
- {matrix_python-1.4.12a0 → matrix_python-1.5.0a0}/docs/docs/reference/extension.md +0 -0
- {matrix_python-1.4.12a0 → matrix_python-1.5.0a0}/docs/docs/reference/group.md +0 -0
- {matrix_python-1.4.12a0 → matrix_python-1.5.0a0}/docs/docs/reference/message.md +0 -0
- {matrix_python-1.4.12a0 → matrix_python-1.5.0a0}/docs/docs/reference/protocols.md +0 -0
- {matrix_python-1.4.12a0 → matrix_python-1.5.0a0}/docs/docs/reference/registry.md +0 -0
- {matrix_python-1.4.12a0 → matrix_python-1.5.0a0}/docs/docs/reference/room.md +0 -0
- {matrix_python-1.4.12a0 → matrix_python-1.5.0a0}/docs/docs/reference/scheduler.md +0 -0
- {matrix_python-1.4.12a0 → matrix_python-1.5.0a0}/docs/docs/reference/types.md +0 -0
- {matrix_python-1.4.12a0 → matrix_python-1.5.0a0}/docs/docs/stylesheets/extra.css +0 -0
- {matrix_python-1.4.12a0 → matrix_python-1.5.0a0}/examples/README.md +0 -0
- {matrix_python-1.4.12a0 → matrix_python-1.5.0a0}/examples/checks.py +0 -0
- {matrix_python-1.4.12a0 → matrix_python-1.5.0a0}/examples/config.yaml +0 -0
- {matrix_python-1.4.12a0 → matrix_python-1.5.0a0}/examples/cooldown.py +0 -0
- {matrix_python-1.4.12a0 → matrix_python-1.5.0a0}/examples/error_handling.py +0 -0
- {matrix_python-1.4.12a0 → matrix_python-1.5.0a0}/examples/extension.py +0 -0
- {matrix_python-1.4.12a0 → matrix_python-1.5.0a0}/examples/ping.py +0 -0
- {matrix_python-1.4.12a0 → matrix_python-1.5.0a0}/examples/reaction.py +0 -0
- {matrix_python-1.4.12a0 → matrix_python-1.5.0a0}/examples/scheduler.py +0 -0
- {matrix_python-1.4.12a0 → matrix_python-1.5.0a0}/matrix/checks.py +0 -0
- {matrix_python-1.4.12a0 → matrix_python-1.5.0a0}/matrix/command.py +0 -0
- {matrix_python-1.4.12a0 → matrix_python-1.5.0a0}/matrix/config.py +0 -0
- {matrix_python-1.4.12a0 → matrix_python-1.5.0a0}/matrix/content.py +0 -0
- {matrix_python-1.4.12a0 → matrix_python-1.5.0a0}/matrix/context.py +0 -0
- {matrix_python-1.4.12a0 → matrix_python-1.5.0a0}/matrix/errors.py +0 -0
- {matrix_python-1.4.12a0 → matrix_python-1.5.0a0}/matrix/extension.py +0 -0
- {matrix_python-1.4.12a0 → matrix_python-1.5.0a0}/matrix/group.py +0 -0
- {matrix_python-1.4.12a0 → matrix_python-1.5.0a0}/matrix/help/__init__.py +0 -0
- {matrix_python-1.4.12a0 → matrix_python-1.5.0a0}/matrix/help/help_command.py +0 -0
- {matrix_python-1.4.12a0 → matrix_python-1.5.0a0}/matrix/help/pagination.py +0 -0
- {matrix_python-1.4.12a0 → matrix_python-1.5.0a0}/matrix/protocols.py +0 -0
- {matrix_python-1.4.12a0 → matrix_python-1.5.0a0}/matrix/py.typed +0 -0
- {matrix_python-1.4.12a0 → matrix_python-1.5.0a0}/matrix/registry.py +0 -0
- {matrix_python-1.4.12a0 → matrix_python-1.5.0a0}/matrix/scheduler.py +0 -0
- {matrix_python-1.4.12a0 → matrix_python-1.5.0a0}/matrix/types.py +0 -0
- {matrix_python-1.4.12a0 → matrix_python-1.5.0a0}/matrix_python.egg-info/dependency_links.txt +0 -0
- {matrix_python-1.4.12a0 → matrix_python-1.5.0a0}/matrix_python.egg-info/top_level.txt +0 -0
- {matrix_python-1.4.12a0 → matrix_python-1.5.0a0}/mypy.ini +0 -0
- {matrix_python-1.4.12a0 → matrix_python-1.5.0a0}/setup.cfg +0 -0
- {matrix_python-1.4.12a0 → matrix_python-1.5.0a0}/tests/help/test_default_help_command.py +0 -0
- {matrix_python-1.4.12a0 → matrix_python-1.5.0a0}/tests/help/test_help_command.py +0 -0
- {matrix_python-1.4.12a0 → matrix_python-1.5.0a0}/tests/help/test_pagination.py +0 -0
- {matrix_python-1.4.12a0 → matrix_python-1.5.0a0}/tests/test_command.py +0 -0
- {matrix_python-1.4.12a0 → matrix_python-1.5.0a0}/tests/test_config.py +0 -0
- {matrix_python-1.4.12a0 → matrix_python-1.5.0a0}/tests/test_context.py +0 -0
- {matrix_python-1.4.12a0 → matrix_python-1.5.0a0}/tests/test_extension.py +0 -0
- {matrix_python-1.4.12a0 → matrix_python-1.5.0a0}/tests/test_group.py +0 -0
- {matrix_python-1.4.12a0 → matrix_python-1.5.0a0}/tests/test_registry.py +0 -0
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: Bug report
|
|
3
|
+
about: Create a report to help us improve
|
|
4
|
+
title: ''
|
|
5
|
+
labels: ''
|
|
6
|
+
assignees: PenguinBoi12
|
|
7
|
+
type: Bug
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
**Describe the bug**
|
|
12
|
+
A clear and concise description of what the bug is.
|
|
13
|
+
|
|
14
|
+
**To Reproduce**
|
|
15
|
+
Steps to reproduce the behavior:
|
|
16
|
+
1. Go to '...'
|
|
17
|
+
2. Click on '....'
|
|
18
|
+
3. Scroll down to '....'
|
|
19
|
+
4. See error
|
|
20
|
+
|
|
21
|
+
**Expected behavior**
|
|
22
|
+
A clear and concise description of what you expected to happen.
|
|
23
|
+
|
|
24
|
+
**Screenshots**
|
|
25
|
+
If applicable, add screenshots to help explain your problem.
|
|
26
|
+
|
|
27
|
+
**Additional context**
|
|
28
|
+
Add any other context about the problem here.
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: 'Feature: Good First'
|
|
3
|
+
about: 'This template is for good first issues '
|
|
4
|
+
title: ''
|
|
5
|
+
labels: good first issue
|
|
6
|
+
assignees: ''
|
|
7
|
+
type: Feature
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
Explain clearly what we want to add and why it's useful to add it. Give context when necessary.
|
|
12
|
+
|
|
13
|
+
### Proposed API
|
|
14
|
+
This is a proposed API. It does not necessarily mean it's the final API but it's a good place to start.
|
|
15
|
+
|
|
16
|
+
```python
|
|
17
|
+
Some code here
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
**Example**
|
|
21
|
+
```python
|
|
22
|
+
An example of usage of the feature
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
### Before opening a PR
|
|
26
|
+
- Write unit tests
|
|
27
|
+
- Run `mypy matrix/`
|
|
28
|
+
- Run `pytest tests/`
|
|
29
|
+
- Run `black .`
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: Feature request
|
|
3
|
+
about: Suggest an idea for this project
|
|
4
|
+
title: ''
|
|
5
|
+
labels: ''
|
|
6
|
+
assignees: ''
|
|
7
|
+
type: Feature
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
**Is your feature request related to a problem? Please describe.**
|
|
12
|
+
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
|
13
|
+
|
|
14
|
+
**Describe the solution you'd like**
|
|
15
|
+
A clear and concise description of what you want to happen.
|
|
16
|
+
|
|
17
|
+
**Describe alternatives you've considered**
|
|
18
|
+
A clear and concise description of any alternative solutions or features you've considered.
|
|
19
|
+
|
|
20
|
+
**Additional context**
|
|
21
|
+
Add any other context or screenshots about the feature request here.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
The MIT License (MIT)
|
|
2
2
|
|
|
3
|
-
Copyright (c)
|
|
3
|
+
Copyright (c) 2025 Code Society Lab
|
|
4
4
|
|
|
5
5
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
6
|
of this software and associated documentation files (the "Software"), to deal
|
|
@@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
|
18
18
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
19
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
20
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
-
THE SOFTWARE.
|
|
21
|
+
THE SOFTWARE.
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: matrix-python
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.5.0a0
|
|
4
4
|
Summary: An easy-to-use Matrix bot framework designed for quick development and minimal setup
|
|
5
5
|
Author: Simon Roy, Chris Dedman Rollet
|
|
6
6
|
Maintainer-email: Code Society Lab <admin@codesociety.xyz>
|
|
7
7
|
License: The MIT License (MIT)
|
|
8
8
|
|
|
9
|
-
Copyright (c)
|
|
9
|
+
Copyright (c) 2025 Code Society Lab
|
|
10
10
|
|
|
11
11
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
12
12
|
of this software and associated documentation files (the "Software"), to deal
|
|
@@ -25,6 +25,7 @@ License: The MIT License (MIT)
|
|
|
25
25
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
26
26
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
27
27
|
THE SOFTWARE.
|
|
28
|
+
|
|
28
29
|
Project-URL: Homepage, https://codesociety.xyz
|
|
29
30
|
Project-URL: Source, https://github.com/Code-Society-Lab/matrixpy
|
|
30
31
|
Project-URL: Issues, https://github.com/Code-Society-Lab/matrixpy/issues
|
|
@@ -34,15 +35,15 @@ Requires-Dist: matrix-nio==0.25.2
|
|
|
34
35
|
Requires-Dist: logger
|
|
35
36
|
Requires-Dist: PyYAML==6.0.3
|
|
36
37
|
Requires-Dist: markdown==3.10.2
|
|
37
|
-
Requires-Dist: APScheduler==3.11.
|
|
38
|
+
Requires-Dist: APScheduler==3.11.3
|
|
38
39
|
Requires-Dist: envyaml==1.10.211231
|
|
39
40
|
Provides-Extra: dev
|
|
40
|
-
Requires-Dist: pytest==9.
|
|
41
|
-
Requires-Dist: pytest-asyncio==1.
|
|
42
|
-
Requires-Dist: black==26.
|
|
43
|
-
Requires-Dist: mypy==1.
|
|
44
|
-
Requires-Dist: types-PyYAML==6.0.12.
|
|
45
|
-
Requires-Dist: types-Markdown==3.10.2.
|
|
41
|
+
Requires-Dist: pytest==9.1.1; extra == "dev"
|
|
42
|
+
Requires-Dist: pytest-asyncio==1.4.0; extra == "dev"
|
|
43
|
+
Requires-Dist: black==26.5.1; extra == "dev"
|
|
44
|
+
Requires-Dist: mypy==2.1.0; extra == "dev"
|
|
45
|
+
Requires-Dist: types-PyYAML==6.0.12.20260518; extra == "dev"
|
|
46
|
+
Requires-Dist: types-Markdown==3.10.2.20260518; extra == "dev"
|
|
46
47
|
Provides-Extra: doc
|
|
47
48
|
Requires-Dist: mkdocs==1.6.1; extra == "doc"
|
|
48
49
|
Requires-Dist: mkdocs-material==9.7.6; extra == "doc"
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Space
|
|
2
|
+
|
|
3
|
+
`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`.
|
|
4
|
+
|
|
5
|
+
```python
|
|
6
|
+
from matrix import Bot
|
|
7
|
+
|
|
8
|
+
bot = Bot()
|
|
9
|
+
|
|
10
|
+
space = bot.get_space("!space123:matrix.org")
|
|
11
|
+
if space:
|
|
12
|
+
print(space.name)
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
::: matrix.space.Space
|
|
@@ -15,6 +15,7 @@ from .command import Command
|
|
|
15
15
|
from .help import HelpCommand
|
|
16
16
|
from .checks import cooldown
|
|
17
17
|
from .room import Room
|
|
18
|
+
from .space import Space
|
|
18
19
|
from .message import Message
|
|
19
20
|
from .extension import Extension
|
|
20
21
|
|
|
@@ -28,6 +29,7 @@ __all__ = [
|
|
|
28
29
|
"HelpCommand",
|
|
29
30
|
"cooldown",
|
|
30
31
|
"Room",
|
|
32
|
+
"Space",
|
|
31
33
|
"Message",
|
|
32
34
|
"Extension",
|
|
33
35
|
]
|
|
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
|
|
|
18
18
|
commit_id: str | None
|
|
19
19
|
__commit_id__: str | None
|
|
20
20
|
|
|
21
|
-
__version__ = version = '1.
|
|
22
|
-
__version_tuple__ = version_tuple = (1,
|
|
21
|
+
__version__ = version = '1.5.0a0'
|
|
22
|
+
__version_tuple__ = version_tuple = (1, 5, 0, 'a0')
|
|
23
23
|
|
|
24
|
-
__commit_id__ = commit_id = '
|
|
24
|
+
__commit_id__ = commit_id = 'g91a8b536b'
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
from typing import Awaitable, TypeVar
|
|
2
|
+
|
|
3
|
+
from nio import ErrorResponse, Response
|
|
4
|
+
|
|
5
|
+
from matrix.errors import MatrixError
|
|
6
|
+
|
|
7
|
+
T = TypeVar("T", bound=Response)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
async def matrix_call(coro: Awaitable[T], /, *, error_message: str) -> T:
|
|
11
|
+
"""Await `coro`, translating any failure into a `MatrixError`.
|
|
12
|
+
|
|
13
|
+
matrix-nio's `AsyncClient` methods don't raise on API-level errors; they
|
|
14
|
+
return an `ErrorResponse` instead of raising. This wraps a single call so
|
|
15
|
+
both transport-level exceptions and nio `ErrorResponse` results become a
|
|
16
|
+
`MatrixError` carrying `error_message`.
|
|
17
|
+
|
|
18
|
+
## Example
|
|
19
|
+
|
|
20
|
+
```python
|
|
21
|
+
response = await matrix_call(
|
|
22
|
+
self.client.room_kick(room_id=self.room_id, user_id=user_id),
|
|
23
|
+
error_message="Failed to kick user",
|
|
24
|
+
)
|
|
25
|
+
```
|
|
26
|
+
"""
|
|
27
|
+
try:
|
|
28
|
+
response = await coro
|
|
29
|
+
except Exception as e:
|
|
30
|
+
raise MatrixError(f"{error_message}: {e}") from e
|
|
31
|
+
|
|
32
|
+
if isinstance(response, ErrorResponse):
|
|
33
|
+
raise MatrixError(f"{error_message}: {response}")
|
|
34
|
+
|
|
35
|
+
return response
|
|
@@ -7,7 +7,8 @@ from typing import Optional, Any
|
|
|
7
7
|
|
|
8
8
|
from nio import AsyncClient, Event, MatrixRoom
|
|
9
9
|
|
|
10
|
-
from .room import Room
|
|
10
|
+
from .room import Room, make_room
|
|
11
|
+
from .space import Space
|
|
11
12
|
from .group import Group
|
|
12
13
|
from .config import Config
|
|
13
14
|
from .context import Context
|
|
@@ -21,6 +22,7 @@ from .errors import (
|
|
|
21
22
|
CheckError,
|
|
22
23
|
RoomNotFoundError,
|
|
23
24
|
)
|
|
25
|
+
from .api import matrix_call
|
|
24
26
|
|
|
25
27
|
|
|
26
28
|
class Bot(Registry):
|
|
@@ -87,19 +89,79 @@ class Bot(Registry):
|
|
|
87
89
|
|
|
88
90
|
Returns the `Room` object corresponding to `room_id` if it exists in
|
|
89
91
|
the client's known rooms. Returns `None` if the room cannot be found.
|
|
92
|
+
Returns a typed subclass if the room type is registered (e.g. `Space` for m.space rooms).
|
|
90
93
|
|
|
91
94
|
## Example
|
|
92
95
|
|
|
93
96
|
```python
|
|
94
97
|
room = bot.get_room("!abc123:matrix.org")
|
|
98
|
+
|
|
95
99
|
if room:
|
|
96
100
|
print(room.name)
|
|
97
101
|
```
|
|
98
102
|
"""
|
|
99
103
|
if matrix_room := self.client.rooms.get(room_id):
|
|
100
|
-
return
|
|
104
|
+
return make_room(matrix_room, self.client)
|
|
101
105
|
return None
|
|
102
106
|
|
|
107
|
+
def get_rooms(self) -> list[Room]:
|
|
108
|
+
"""Retrieve a list of all rooms the bot is aware of.
|
|
109
|
+
|
|
110
|
+
This method returns a list of `Room` objects for all rooms currently
|
|
111
|
+
known to the client. This includes both regular rooms and spaces;
|
|
112
|
+
spaces are returned as `Space` instances.
|
|
113
|
+
|
|
114
|
+
## Example
|
|
115
|
+
|
|
116
|
+
```python
|
|
117
|
+
rooms = bot.get_rooms()
|
|
118
|
+
|
|
119
|
+
for room in rooms:
|
|
120
|
+
print(room.name)
|
|
121
|
+
```
|
|
122
|
+
"""
|
|
123
|
+
rooms = []
|
|
124
|
+
|
|
125
|
+
for matrix_room in self.client.rooms.values():
|
|
126
|
+
rooms.append(make_room(matrix_room, self.client))
|
|
127
|
+
|
|
128
|
+
return rooms
|
|
129
|
+
|
|
130
|
+
def get_space(self, space_id: str) -> Space | None:
|
|
131
|
+
"""Retrieve a `Space` instance by its Matrix room ID.
|
|
132
|
+
|
|
133
|
+
Returns the `Space` object corresponding to `space_id` if it exists in
|
|
134
|
+
the client's known rooms and is a space. Returns `None` otherwise.
|
|
135
|
+
|
|
136
|
+
## Example
|
|
137
|
+
|
|
138
|
+
```python
|
|
139
|
+
space = bot.get_space("!space123:matrix.org")
|
|
140
|
+
|
|
141
|
+
if space:
|
|
142
|
+
print(space.name)
|
|
143
|
+
```
|
|
144
|
+
"""
|
|
145
|
+
room = self.get_room(space_id)
|
|
146
|
+
return room if isinstance(room, Space) else None
|
|
147
|
+
|
|
148
|
+
def get_spaces(self) -> list[Space]:
|
|
149
|
+
"""Retrieve a list of all spaces the bot is aware of.
|
|
150
|
+
|
|
151
|
+
This method returns a list of `Space` objects for all rooms currently
|
|
152
|
+
known to the client that are identified as spaces.
|
|
153
|
+
|
|
154
|
+
## Example
|
|
155
|
+
|
|
156
|
+
```python
|
|
157
|
+
spaces = bot.get_spaces()
|
|
158
|
+
|
|
159
|
+
for space in spaces:
|
|
160
|
+
print(space.name)
|
|
161
|
+
```
|
|
162
|
+
"""
|
|
163
|
+
return [room for room in self.get_rooms() if isinstance(room, Space)]
|
|
164
|
+
|
|
103
165
|
def load_extension(self, extension: Extension) -> None:
|
|
104
166
|
self.log.debug(f"Loading extension: '{extension.name}'")
|
|
105
167
|
|
|
@@ -267,7 +329,10 @@ class Bot(Registry):
|
|
|
267
329
|
if self.config.token:
|
|
268
330
|
self.client.access_token = self.config.token
|
|
269
331
|
else:
|
|
270
|
-
login_resp = await
|
|
332
|
+
login_resp = await matrix_call(
|
|
333
|
+
self.client.login(self.config.password),
|
|
334
|
+
error_message="Failed to log in",
|
|
335
|
+
)
|
|
271
336
|
self.log.info("logged in: %s", login_resp)
|
|
272
337
|
|
|
273
338
|
sync_task = asyncio.create_task(self.client.sync_forever(timeout=30_000))
|
|
@@ -5,6 +5,7 @@ from nio import AsyncClient, Event
|
|
|
5
5
|
from matrix.types import Reaction
|
|
6
6
|
from matrix.content import ReactionContent, EditContent
|
|
7
7
|
from matrix.errors import MatrixError
|
|
8
|
+
from matrix.api import matrix_call
|
|
8
9
|
|
|
9
10
|
if TYPE_CHECKING:
|
|
10
11
|
from .room import Room # pragma: no cover
|
|
@@ -116,14 +117,14 @@ class Message:
|
|
|
116
117
|
"""
|
|
117
118
|
content = ReactionContent(event_id=self.event_id, emoji=emoji)
|
|
118
119
|
|
|
119
|
-
|
|
120
|
-
|
|
120
|
+
await matrix_call(
|
|
121
|
+
self.client.room_send(
|
|
121
122
|
room_id=self.room.room_id,
|
|
122
123
|
message_type="m.reaction",
|
|
123
124
|
content=content.build(),
|
|
124
|
-
)
|
|
125
|
-
|
|
126
|
-
|
|
125
|
+
),
|
|
126
|
+
error_message="Failed to add reaction",
|
|
127
|
+
)
|
|
127
128
|
|
|
128
129
|
async def edit(self, new_body: str) -> None:
|
|
129
130
|
"""Updates the message content to the new text.
|
|
@@ -139,19 +140,21 @@ class Message:
|
|
|
139
140
|
"""
|
|
140
141
|
content = EditContent(new_body, original_event_id=self.event_id)
|
|
141
142
|
|
|
142
|
-
|
|
143
|
-
|
|
143
|
+
await matrix_call(
|
|
144
|
+
self.client.room_send(
|
|
144
145
|
room_id=self.room.room_id,
|
|
145
146
|
message_type="m.room.message",
|
|
146
147
|
content=content.build(),
|
|
147
|
-
)
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
148
|
+
),
|
|
149
|
+
error_message="Failed to edit message",
|
|
150
|
+
)
|
|
151
|
+
self._body = new_body
|
|
151
152
|
|
|
152
|
-
async def delete(self) -> None:
|
|
153
|
+
async def delete(self, reason: str | None = None) -> None:
|
|
153
154
|
"""Removes the message content from the room. This action cannot be undone.
|
|
154
155
|
|
|
156
|
+
Optionally provide a reason that will be visible to room moderators.
|
|
157
|
+
|
|
155
158
|
## Example
|
|
156
159
|
|
|
157
160
|
```python
|
|
@@ -159,12 +162,16 @@ class Message:
|
|
|
159
162
|
async def oops(ctx: Context):
|
|
160
163
|
msg = await ctx.reply("Secret info!")
|
|
161
164
|
await msg.delete()
|
|
165
|
+
|
|
166
|
+
# Delete with a reason
|
|
167
|
+
await message.delete(reason="Violated room rules")
|
|
162
168
|
```
|
|
163
169
|
"""
|
|
164
|
-
|
|
165
|
-
|
|
170
|
+
await matrix_call(
|
|
171
|
+
self.client.room_redact(
|
|
166
172
|
room_id=self.room.room_id,
|
|
167
173
|
event_id=self.event_id,
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
174
|
+
reason=reason,
|
|
175
|
+
),
|
|
176
|
+
error_message="Failed to delete message",
|
|
177
|
+
)
|
|
@@ -2,7 +2,7 @@ from typing import Any
|
|
|
2
2
|
|
|
3
3
|
from nio import AsyncClient, MatrixRoom, Event
|
|
4
4
|
|
|
5
|
-
from matrix.
|
|
5
|
+
from matrix.api import matrix_call
|
|
6
6
|
from matrix.message import Message
|
|
7
7
|
from matrix.content import (
|
|
8
8
|
BaseMessageContent,
|
|
@@ -17,10 +17,26 @@ from matrix.content import (
|
|
|
17
17
|
)
|
|
18
18
|
from matrix.types import File, Image, Audio, Video
|
|
19
19
|
|
|
20
|
+
_registry: dict[str, type["Room"]] = {}
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def make_room(matrix_room: MatrixRoom, client: AsyncClient) -> "Room":
|
|
24
|
+
room_cls = _registry.get(str(matrix_room.room_type), Room)
|
|
25
|
+
return room_cls(matrix_room, client)
|
|
26
|
+
|
|
20
27
|
|
|
21
28
|
class Room:
|
|
22
29
|
"""Represents a Matrix room and provides methods to interact with it."""
|
|
23
30
|
|
|
31
|
+
def __init_subclass__(cls, room_type: str | None = None, **kwargs: Any) -> None:
|
|
32
|
+
super().__init_subclass__(**kwargs)
|
|
33
|
+
if room_type:
|
|
34
|
+
if room_type in _registry:
|
|
35
|
+
raise ValueError(
|
|
36
|
+
f"Room type '{room_type}' is already registered by {_registry[room_type].__name__}"
|
|
37
|
+
)
|
|
38
|
+
_registry[room_type] = cls
|
|
39
|
+
|
|
24
40
|
def __init__(self, matrix_room: MatrixRoom, client: AsyncClient) -> None:
|
|
25
41
|
self._matrix_room: MatrixRoom = matrix_room
|
|
26
42
|
self._client: AsyncClient = client
|
|
@@ -313,21 +329,21 @@ class Room:
|
|
|
313
329
|
|
|
314
330
|
async def _send_payload(self, payload: BaseMessageContent) -> Message:
|
|
315
331
|
"""Send a BaseMessageContent payload and return a Message object."""
|
|
316
|
-
|
|
317
|
-
|
|
332
|
+
resp = await matrix_call(
|
|
333
|
+
self.client.room_send(
|
|
318
334
|
room_id=self.room_id,
|
|
319
335
|
message_type="m.room.message",
|
|
320
336
|
content=payload.build(),
|
|
321
|
-
)
|
|
322
|
-
|
|
337
|
+
),
|
|
338
|
+
error_message="Failed to send message",
|
|
339
|
+
)
|
|
340
|
+
event = await self.fetch_event(resp.event_id)
|
|
323
341
|
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
except Exception as e:
|
|
330
|
-
raise MatrixError(f"Failed to send message: {e}")
|
|
342
|
+
return Message(
|
|
343
|
+
room=self,
|
|
344
|
+
event=event,
|
|
345
|
+
client=self.client,
|
|
346
|
+
)
|
|
331
347
|
|
|
332
348
|
async def fetch_event(self, event_id: str) -> Event:
|
|
333
349
|
"""Fetch a Matrix event by its ID.
|
|
@@ -338,14 +354,11 @@ class Room:
|
|
|
338
354
|
print(event.sender)
|
|
339
355
|
```
|
|
340
356
|
"""
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
return response.event
|
|
347
|
-
except Exception as e:
|
|
348
|
-
raise MatrixError(f"Failed to get event: {e}")
|
|
357
|
+
response = await matrix_call(
|
|
358
|
+
self.client.room_get_event(room_id=self.room_id, event_id=event_id),
|
|
359
|
+
error_message="Failed to get event",
|
|
360
|
+
)
|
|
361
|
+
return response.event
|
|
349
362
|
|
|
350
363
|
async def fetch_message(self, event_id: str) -> Message:
|
|
351
364
|
"""Fetch a Message by its event ID.
|
|
@@ -363,6 +376,29 @@ class Room:
|
|
|
363
376
|
client=self.client,
|
|
364
377
|
)
|
|
365
378
|
|
|
379
|
+
async def mark_as_read(self, event_id: str) -> None:
|
|
380
|
+
"""Send a read receipt for the given event.
|
|
381
|
+
|
|
382
|
+
Signals to other clients that the bot has read up to this event. Useful
|
|
383
|
+
for bots that process messages silently without sending a reply.
|
|
384
|
+
|
|
385
|
+
## Example
|
|
386
|
+
|
|
387
|
+
```python
|
|
388
|
+
@bot.event
|
|
389
|
+
async def on_message(room: Room, event: Event):
|
|
390
|
+
await room.mark_as_read(event.event_id)
|
|
391
|
+
```
|
|
392
|
+
"""
|
|
393
|
+
await matrix_call(
|
|
394
|
+
self.client.room_read_markers(
|
|
395
|
+
room_id=self.room_id,
|
|
396
|
+
fully_read_event=event_id,
|
|
397
|
+
read_event=event_id,
|
|
398
|
+
),
|
|
399
|
+
error_message="Failed to mark as read",
|
|
400
|
+
)
|
|
401
|
+
|
|
366
402
|
async def invite_user(self, user_id: str) -> None:
|
|
367
403
|
"""Invite a user to the room.
|
|
368
404
|
|
|
@@ -376,10 +412,10 @@ class Room:
|
|
|
376
412
|
await room.invite_user("@alice:example.com")
|
|
377
413
|
```
|
|
378
414
|
"""
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
415
|
+
await matrix_call(
|
|
416
|
+
self.client.room_invite(room_id=self.room_id, user_id=user_id),
|
|
417
|
+
error_message="Failed to invite user",
|
|
418
|
+
)
|
|
383
419
|
|
|
384
420
|
async def ban_user(self, user_id: str, reason: str | None = None) -> None:
|
|
385
421
|
"""Ban a user from the room.
|
|
@@ -397,12 +433,10 @@ class Room:
|
|
|
397
433
|
await room.ban_user("@spammer:example.com", reason="Spam and harassment")
|
|
398
434
|
```
|
|
399
435
|
"""
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
except Exception as e:
|
|
405
|
-
raise MatrixError(f"Failed to ban user: {e}")
|
|
436
|
+
await matrix_call(
|
|
437
|
+
self.client.room_ban(room_id=self.room_id, user_id=user_id, reason=reason),
|
|
438
|
+
error_message="Failed to ban user",
|
|
439
|
+
)
|
|
406
440
|
|
|
407
441
|
async def unban_user(self, user_id: str) -> None:
|
|
408
442
|
"""Unban a user from the room.
|
|
@@ -417,10 +451,10 @@ class Room:
|
|
|
417
451
|
await room.unban_user("@alice:example.com")
|
|
418
452
|
```
|
|
419
453
|
"""
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
454
|
+
await matrix_call(
|
|
455
|
+
self.client.room_unban(room_id=self.room_id, user_id=user_id),
|
|
456
|
+
error_message="Failed to unban user",
|
|
457
|
+
)
|
|
424
458
|
|
|
425
459
|
async def kick_user(self, user_id: str, reason: str | None = None) -> None:
|
|
426
460
|
"""Kick a user from the room.
|
|
@@ -439,9 +473,26 @@ class Room:
|
|
|
439
473
|
await room.kick_user("@troublemaker:example.com", reason="Violating room rules")
|
|
440
474
|
```
|
|
441
475
|
"""
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
476
|
+
await matrix_call(
|
|
477
|
+
self.client.room_kick(room_id=self.room_id, user_id=user_id, reason=reason),
|
|
478
|
+
error_message="Failed to kick user",
|
|
479
|
+
)
|
|
480
|
+
|
|
481
|
+
async def get_members(self) -> list[str]:
|
|
482
|
+
"""Fetch the list of user IDs currently joined to the room.
|
|
483
|
+
|
|
484
|
+
This queries the Matrix server directly for the current membership,
|
|
485
|
+
which may include members not yet reflected in local room state.
|
|
486
|
+
|
|
487
|
+
## Example
|
|
488
|
+
|
|
489
|
+
```python
|
|
490
|
+
members = await room.get_members()
|
|
491
|
+
print(f"{len(members)} members: {', '.join(members)}")
|
|
492
|
+
```
|
|
493
|
+
"""
|
|
494
|
+
response = await matrix_call(
|
|
495
|
+
self.client.joined_members(self.room_id),
|
|
496
|
+
error_message="Failed to get members",
|
|
497
|
+
)
|
|
498
|
+
return [member.user_id for member in response.members]
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
from typing import Self
|
|
2
|
+
from matrix.room import Room, make_room
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class Space(Room, room_type="m.space"):
|
|
6
|
+
def get_children(self, depth: int = 1) -> list[Room | Self]:
|
|
7
|
+
"""Return the child rooms and spaces of this space that the bot has joined.
|
|
8
|
+
|
|
9
|
+
Children the bot has not joined are silently omitted. Use `depth` to
|
|
10
|
+
recursively collect children of sub-spaces. `depth=1` returns direct
|
|
11
|
+
children only (default).
|
|
12
|
+
|
|
13
|
+
## Example
|
|
14
|
+
|
|
15
|
+
```python
|
|
16
|
+
space = bot.get_space("!space123:matrix.org")
|
|
17
|
+
|
|
18
|
+
for child in space.get_children():
|
|
19
|
+
print(child.name)
|
|
20
|
+
|
|
21
|
+
for child in space.get_children(depth=3):
|
|
22
|
+
print(child.name)
|
|
23
|
+
```
|
|
24
|
+
"""
|
|
25
|
+
children: list[Room | Self] = []
|
|
26
|
+
|
|
27
|
+
if depth < 0:
|
|
28
|
+
raise ValueError(f"depth must be a non-negative integer, got {depth}")
|
|
29
|
+
|
|
30
|
+
if depth == 0:
|
|
31
|
+
return []
|
|
32
|
+
|
|
33
|
+
for room_id in self.children:
|
|
34
|
+
matrix_room = self._client.rooms.get(room_id)
|
|
35
|
+
|
|
36
|
+
if not matrix_room:
|
|
37
|
+
continue
|
|
38
|
+
|
|
39
|
+
child = make_room(matrix_room, self._client)
|
|
40
|
+
children.append(child)
|
|
41
|
+
|
|
42
|
+
if isinstance(child, Space) and depth > 1:
|
|
43
|
+
children.extend(child.get_children(depth - 1))
|
|
44
|
+
|
|
45
|
+
return children
|