matrix-python 1.4.0a0__tar.gz → 1.4.3a0__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.
Files changed (65) hide show
  1. matrix_python-1.4.3a0/.github/workflows/tests.yml +35 -0
  2. {matrix_python-1.4.0a0 → matrix_python-1.4.3a0}/PKG-INFO +18 -9
  3. {matrix_python-1.4.0a0 → matrix_python-1.4.3a0}/README.md +17 -8
  4. {matrix_python-1.4.0a0 → matrix_python-1.4.3a0}/examples/checks.py +2 -2
  5. {matrix_python-1.4.0a0 → matrix_python-1.4.3a0}/examples/cooldown.py +2 -2
  6. {matrix_python-1.4.0a0 → matrix_python-1.4.3a0}/examples/error_handling.py +2 -2
  7. {matrix_python-1.4.0a0 → matrix_python-1.4.3a0}/examples/extension.py +4 -2
  8. {matrix_python-1.4.0a0 → matrix_python-1.4.3a0}/examples/ping.py +2 -2
  9. matrix_python-1.4.3a0/examples/reaction.py +39 -0
  10. {matrix_python-1.4.0a0 → matrix_python-1.4.3a0}/examples/scheduler.py +5 -5
  11. {matrix_python-1.4.0a0 → matrix_python-1.4.3a0}/matrix/__init__.py +2 -0
  12. {matrix_python-1.4.0a0 → matrix_python-1.4.3a0}/matrix/_version.py +3 -3
  13. {matrix_python-1.4.0a0 → matrix_python-1.4.3a0}/matrix/bot.py +50 -22
  14. {matrix_python-1.4.0a0 → matrix_python-1.4.3a0}/matrix/extension.py +16 -3
  15. {matrix_python-1.4.0a0 → matrix_python-1.4.3a0}/matrix/message.py +61 -22
  16. matrix_python-1.4.3a0/matrix/protocols.py +9 -0
  17. {matrix_python-1.4.0a0 → matrix_python-1.4.3a0}/matrix/room.py +53 -19
  18. {matrix_python-1.4.0a0 → matrix_python-1.4.3a0}/matrix/types.py +6 -0
  19. {matrix_python-1.4.0a0 → matrix_python-1.4.3a0}/matrix_python.egg-info/PKG-INFO +18 -9
  20. {matrix_python-1.4.0a0 → matrix_python-1.4.3a0}/matrix_python.egg-info/SOURCES.txt +1 -0
  21. {matrix_python-1.4.0a0 → matrix_python-1.4.3a0}/tests/test_bot.py +30 -30
  22. {matrix_python-1.4.0a0 → matrix_python-1.4.3a0}/tests/test_extension.py +92 -6
  23. {matrix_python-1.4.0a0 → matrix_python-1.4.3a0}/tests/test_message.py +78 -9
  24. {matrix_python-1.4.0a0 → matrix_python-1.4.3a0}/tests/test_room.py +115 -82
  25. matrix_python-1.4.0a0/.github/workflows/tests.yml +0 -35
  26. matrix_python-1.4.0a0/examples/reaction.py +0 -43
  27. {matrix_python-1.4.0a0 → matrix_python-1.4.3a0}/.github/dependabot.yml +0 -0
  28. {matrix_python-1.4.0a0 → matrix_python-1.4.3a0}/.github/workflows/CODEOWNERS +0 -0
  29. {matrix_python-1.4.0a0 → matrix_python-1.4.3a0}/.github/workflows/codeql.yml +0 -0
  30. {matrix_python-1.4.0a0 → matrix_python-1.4.3a0}/.github/workflows/publish.yml +0 -0
  31. {matrix_python-1.4.0a0 → matrix_python-1.4.3a0}/.github/workflows/scorecard.yml +0 -0
  32. {matrix_python-1.4.0a0 → matrix_python-1.4.3a0}/.gitignore +0 -0
  33. {matrix_python-1.4.0a0 → matrix_python-1.4.3a0}/CODE_OF_CONDUCT.md +0 -0
  34. {matrix_python-1.4.0a0 → matrix_python-1.4.3a0}/CONTRIBUTING.md +0 -0
  35. {matrix_python-1.4.0a0 → matrix_python-1.4.3a0}/LICENSE +0 -0
  36. {matrix_python-1.4.0a0 → matrix_python-1.4.3a0}/examples/README.md +0 -0
  37. {matrix_python-1.4.0a0 → matrix_python-1.4.3a0}/examples/config.yaml +0 -0
  38. {matrix_python-1.4.0a0 → matrix_python-1.4.3a0}/matrix/checks.py +0 -0
  39. {matrix_python-1.4.0a0 → matrix_python-1.4.3a0}/matrix/command.py +0 -0
  40. {matrix_python-1.4.0a0 → matrix_python-1.4.3a0}/matrix/config.py +0 -0
  41. {matrix_python-1.4.0a0 → matrix_python-1.4.3a0}/matrix/content.py +0 -0
  42. {matrix_python-1.4.0a0 → matrix_python-1.4.3a0}/matrix/context.py +0 -0
  43. {matrix_python-1.4.0a0 → matrix_python-1.4.3a0}/matrix/errors.py +0 -0
  44. {matrix_python-1.4.0a0 → matrix_python-1.4.3a0}/matrix/group.py +0 -0
  45. {matrix_python-1.4.0a0 → matrix_python-1.4.3a0}/matrix/help/__init__.py +0 -0
  46. {matrix_python-1.4.0a0 → matrix_python-1.4.3a0}/matrix/help/help_command.py +0 -0
  47. {matrix_python-1.4.0a0 → matrix_python-1.4.3a0}/matrix/help/pagination.py +0 -0
  48. {matrix_python-1.4.0a0 → matrix_python-1.4.3a0}/matrix/registry.py +0 -0
  49. {matrix_python-1.4.0a0 → matrix_python-1.4.3a0}/matrix/scheduler.py +0 -0
  50. {matrix_python-1.4.0a0 → matrix_python-1.4.3a0}/matrix_python.egg-info/dependency_links.txt +0 -0
  51. {matrix_python-1.4.0a0 → matrix_python-1.4.3a0}/matrix_python.egg-info/requires.txt +0 -0
  52. {matrix_python-1.4.0a0 → matrix_python-1.4.3a0}/matrix_python.egg-info/top_level.txt +0 -0
  53. {matrix_python-1.4.0a0 → matrix_python-1.4.3a0}/mypy.ini +0 -0
  54. {matrix_python-1.4.0a0 → matrix_python-1.4.3a0}/pyproject.toml +0 -0
  55. {matrix_python-1.4.0a0 → matrix_python-1.4.3a0}/setup.cfg +0 -0
  56. {matrix_python-1.4.0a0 → matrix_python-1.4.3a0}/tests/config_fixture.yaml +0 -0
  57. {matrix_python-1.4.0a0 → matrix_python-1.4.3a0}/tests/config_fixture_token.yaml +0 -0
  58. {matrix_python-1.4.0a0 → matrix_python-1.4.3a0}/tests/help/test_default_help_command.py +0 -0
  59. {matrix_python-1.4.0a0 → matrix_python-1.4.3a0}/tests/help/test_help_command.py +0 -0
  60. {matrix_python-1.4.0a0 → matrix_python-1.4.3a0}/tests/help/test_pagination.py +0 -0
  61. {matrix_python-1.4.0a0 → matrix_python-1.4.3a0}/tests/test_command.py +0 -0
  62. {matrix_python-1.4.0a0 → matrix_python-1.4.3a0}/tests/test_config.py +0 -0
  63. {matrix_python-1.4.0a0 → matrix_python-1.4.3a0}/tests/test_context.py +0 -0
  64. {matrix_python-1.4.0a0 → matrix_python-1.4.3a0}/tests/test_group.py +0 -0
  65. {matrix_python-1.4.0a0 → matrix_python-1.4.3a0}/tests/test_registry.py +0 -0
@@ -0,0 +1,35 @@
1
+ name: Tests
2
+
3
+ on:
4
+ push:
5
+ branches: [ "main" ]
6
+ pull_request:
7
+ branches: [ "main" ]
8
+
9
+ permissions:
10
+ contents: read
11
+
12
+ jobs:
13
+ build:
14
+
15
+ runs-on: ubuntu-latest
16
+
17
+ steps:
18
+ - uses: actions/checkout@v4
19
+ - name: Set up Python 3.12
20
+ uses: actions/setup-python@v3
21
+ with:
22
+ python-version: "3.12"
23
+ - name: Install dependencies
24
+ run: |
25
+ python -m pip install --upgrade pip
26
+ pip install .[dev]
27
+ - name: Lint with Black
28
+ run: |
29
+ black --check matrix/ tests/ examples/
30
+ - name: Check typing with mypy
31
+ run: |
32
+ mypy matrix
33
+ - name: Test with pytest
34
+ run: |
35
+ pytest -v
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: matrix-python
3
- Version: 1.4.0a0
3
+ Version: 1.4.3a0
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>
@@ -718,6 +718,7 @@ decorator-based API similar to popular event-driven frameworks, allowing
718
718
  developers to focus on behavior rather than boilerplate.
719
719
 
720
720
  #### Key Features
721
+
721
722
  - Minimal setup, easy to extend
722
723
  - Event-driven API using async/await
723
724
  - Clean command registration
@@ -727,6 +728,7 @@ developers to focus on behavior rather than boilerplate.
727
728
  # Quickstart
728
729
 
729
730
  **Requirements**
731
+
730
732
  - Python 3.10+
731
733
 
732
734
  ```
@@ -734,38 +736,45 @@ pip install matrix-python
734
736
  ```
735
737
 
736
738
  If you plan on contributing to matrix.py, we recommend to install the development libraries:
739
+
737
740
  ```
738
741
  pip install -e .[dev]
739
742
  ```
740
743
 
741
- *Note*: It is recommended to use a [virtual environment](https://packaging.python.org/en/latest/guides/installing-using-pip-and-virtual-environments/) when installing python packages.
742
-
744
+ *Note*: It is recommended to use
745
+ a [virtual environment](https://packaging.python.org/en/latest/guides/installing-using-pip-and-virtual-environments/)
746
+ when installing python packages.
743
747
 
744
748
  ```python
745
749
  from matrix import Bot, Context
746
750
 
747
- bot = Bot(config="config.yml")
751
+ bot = Bot()
748
752
 
749
753
 
750
754
  @bot.command("ping")
751
755
  async def ping(ctx: Context):
752
- await ctx.send("Pong!")
756
+ await ctx.reply("Pong!")
753
757
 
754
758
 
755
- bot.start()
759
+ bot.start(config="config.yml")
756
760
  ```
757
761
 
758
762
  [Documentation](https://github.com/Code-Society-Lab/matrixpy/wiki) - [Examples](https://github.com/Code-Society-Lab/matrixpy/tree/main/examples)
759
763
 
760
764
  # Contributing
761
- We welcome everyone to contribute!
765
+
766
+ We welcome everyone to contribute!
762
767
 
763
768
  Whether it's fixing bugs, suggesting features, or improving the docs - every bit helps.
769
+
764
770
  - Submit an issue
765
771
  - Open a pull request
766
- - Or just hop into our [Matrix](https://matrix.to/#/%23codesociety:matrix.org) or [Discord](https://discord.gg/code-society-823178343943897088) server and say hi!
772
+ - Or just hop into our [Matrix](https://matrix.to/#/%23codesociety:matrix.org)
773
+ or [Discord](https://discord.gg/code-society-823178343943897088) server and say hi!
767
774
 
768
- If you intend to contribute, please read the [CONTRIBUTING.md](./CONTRIBUTING.md) first. Additionally, **every contributor** is expected to follow the [code of conduct](./CODE_OF_CONDUCT.md).
775
+ If you intend to contribute, please read the [CONTRIBUTING.md](./CONTRIBUTING.md) first. Additionally, **every
776
+ contributor** is expected to follow the [code of conduct](./CODE_OF_CONDUCT.md).
769
777
 
770
778
  # License
779
+
771
780
  matrix.py is released under [GPL-3.0](https://opensource.org/license/gpl-3-0)
@@ -19,6 +19,7 @@ decorator-based API similar to popular event-driven frameworks, allowing
19
19
  developers to focus on behavior rather than boilerplate.
20
20
 
21
21
  #### Key Features
22
+
22
23
  - Minimal setup, easy to extend
23
24
  - Event-driven API using async/await
24
25
  - Clean command registration
@@ -28,6 +29,7 @@ developers to focus on behavior rather than boilerplate.
28
29
  # Quickstart
29
30
 
30
31
  **Requirements**
32
+
31
33
  - Python 3.10+
32
34
 
33
35
  ```
@@ -35,38 +37,45 @@ pip install matrix-python
35
37
  ```
36
38
 
37
39
  If you plan on contributing to matrix.py, we recommend to install the development libraries:
40
+
38
41
  ```
39
42
  pip install -e .[dev]
40
43
  ```
41
44
 
42
- *Note*: It is recommended to use a [virtual environment](https://packaging.python.org/en/latest/guides/installing-using-pip-and-virtual-environments/) when installing python packages.
43
-
45
+ *Note*: It is recommended to use
46
+ a [virtual environment](https://packaging.python.org/en/latest/guides/installing-using-pip-and-virtual-environments/)
47
+ when installing python packages.
44
48
 
45
49
  ```python
46
50
  from matrix import Bot, Context
47
51
 
48
- bot = Bot(config="config.yml")
52
+ bot = Bot()
49
53
 
50
54
 
51
55
  @bot.command("ping")
52
56
  async def ping(ctx: Context):
53
- await ctx.send("Pong!")
57
+ await ctx.reply("Pong!")
54
58
 
55
59
 
56
- bot.start()
60
+ bot.start(config="config.yml")
57
61
  ```
58
62
 
59
63
  [Documentation](https://github.com/Code-Society-Lab/matrixpy/wiki) - [Examples](https://github.com/Code-Society-Lab/matrixpy/tree/main/examples)
60
64
 
61
65
  # Contributing
62
- We welcome everyone to contribute!
66
+
67
+ We welcome everyone to contribute!
63
68
 
64
69
  Whether it's fixing bugs, suggesting features, or improving the docs - every bit helps.
70
+
65
71
  - Submit an issue
66
72
  - Open a pull request
67
- - Or just hop into our [Matrix](https://matrix.to/#/%23codesociety:matrix.org) or [Discord](https://discord.gg/code-society-823178343943897088) server and say hi!
73
+ - Or just hop into our [Matrix](https://matrix.to/#/%23codesociety:matrix.org)
74
+ or [Discord](https://discord.gg/code-society-823178343943897088) server and say hi!
68
75
 
69
- If you intend to contribute, please read the [CONTRIBUTING.md](./CONTRIBUTING.md) first. Additionally, **every contributor** is expected to follow the [code of conduct](./CODE_OF_CONDUCT.md).
76
+ If you intend to contribute, please read the [CONTRIBUTING.md](./CONTRIBUTING.md) first. Additionally, **every
77
+ contributor** is expected to follow the [code of conduct](./CODE_OF_CONDUCT.md).
70
78
 
71
79
  # License
80
+
72
81
  matrix.py is released under [GPL-3.0](https://opensource.org/license/gpl-3-0)
@@ -1,7 +1,7 @@
1
1
  from matrix import Bot, Context
2
2
  from matrix.errors import CheckError
3
3
 
4
- bot = Bot(config="config.yaml")
4
+ bot = Bot()
5
5
 
6
6
  allowed_users = {"@alice:matrix.org", "@bob:matrix.org"}
7
7
 
@@ -23,4 +23,4 @@ async def permission_error_handler(ctx: Context, error: CheckError) -> None:
23
23
  await ctx.reply(f"Access denied: {error}")
24
24
 
25
25
 
26
- bot.start()
26
+ bot.start(config="config.yaml")
@@ -1,7 +1,7 @@
1
1
  from matrix import Bot, Context, cooldown
2
2
  from matrix.errors import CooldownError
3
3
 
4
- bot = Bot(config="config.yaml")
4
+ bot = Bot()
5
5
 
6
6
 
7
7
  # Invoke by using !hello
@@ -27,4 +27,4 @@ async def cooldown_function(ctx: Context, error: CooldownError) -> None:
27
27
  await ctx.reply(f"⏳ Try again in {error.retry:.1f}s")
28
28
 
29
29
 
30
- bot.start()
30
+ bot.start(config="config.yaml")
@@ -1,7 +1,7 @@
1
1
  from matrix import Bot, Context
2
2
  from matrix.errors import CommandNotFoundError, MissingArgumentError
3
3
 
4
- bot = Bot(config="config.yaml")
4
+ bot = Bot()
5
5
 
6
6
 
7
7
  @bot.error(CommandNotFoundError)
@@ -30,4 +30,4 @@ async def command_error(ctx: Context, error: MissingArgumentError) -> None:
30
30
  await ctx.reply(f"{error}")
31
31
 
32
32
 
33
- bot.start()
33
+ bot.start(config="config.yaml")
@@ -34,12 +34,14 @@ async def divide_error(ctx: Context, error):
34
34
 
35
35
 
36
36
  """
37
+ # bot.py
38
+
37
39
  from matrix import Bot
38
40
  from math_extension import extension as math_extension
39
41
 
40
- bot = Bot(config="config.yaml")
42
+ bot = Bot()
41
43
 
42
44
 
43
45
  bot.load_extension(math_extension)
44
- bot.start()
46
+ bot.start(config="config.yaml")
45
47
  """
@@ -1,6 +1,6 @@
1
1
  from matrix import Bot, Context
2
2
 
3
- bot = Bot(config="config.yaml")
3
+ bot = Bot()
4
4
 
5
5
 
6
6
  @bot.command("ping")
@@ -8,4 +8,4 @@ async def ping(ctx: Context) -> None:
8
8
  await ctx.reply("Pong!")
9
9
 
10
10
 
11
- bot.start()
11
+ bot.start(config="config.yaml")
@@ -0,0 +1,39 @@
1
+ from asyncio import Event
2
+ from nio import MatrixRoom, RoomMessageText, ReactionEvent
3
+ from matrix import Bot, Room, Message
4
+
5
+ bot = Bot()
6
+
7
+
8
+ @bot.event
9
+ async def on_message(room: Room, event: RoomMessageText) -> None:
10
+ """
11
+ This function listens for new messages in a room and reacts based
12
+ on the message content.
13
+ """
14
+ message = await room.fetch_message(event.event_id)
15
+
16
+ if message.body.lower().startswith("thanks"):
17
+ await message.react("🙏")
18
+
19
+ if message.body.lower().startswith("hello"):
20
+ # Can also react with a text message instead of emoji
21
+ await message.react("hi")
22
+
23
+ if message.body.lower().startswith("❤️"):
24
+ await message.react("❤️")
25
+
26
+
27
+ @bot.event
28
+ async def on_react(room: Room, event: ReactionEvent) -> None:
29
+ """
30
+ This function listens for new member reaction to messages in a room,
31
+ and reacts based on the reaction emoji.
32
+ """
33
+ message = await room.fetch_message(event.reacts_to)
34
+
35
+ if message.key == "🙏":
36
+ await message.react("❤️")
37
+
38
+
39
+ bot.start(config="config.yaml")
@@ -1,6 +1,6 @@
1
1
  from matrix import Bot, Context
2
2
 
3
- bot = Bot(config="config.yaml")
3
+ bot = Bot()
4
4
 
5
5
  room_id = "!your_room_id:matrix.org" # Replace with your room ID
6
6
 
@@ -15,19 +15,19 @@ async def ping(ctx: Context) -> None:
15
15
  @bot.schedule("* * * * *")
16
16
  async def scheduled_task() -> None:
17
17
  # This task runs every minute.
18
- await room.send(message="Scheduled ping!")
18
+ await room.send("Scheduled ping!")
19
19
 
20
20
 
21
21
  @bot.schedule("0 * * * *")
22
22
  async def hourly_task() -> None:
23
23
  # This task runs every hour.
24
- await room.send(message="This is your hourly update!")
24
+ await room.send("This is your hourly update!")
25
25
 
26
26
 
27
27
  @bot.schedule("0 9 * * 1-5")
28
28
  async def weekday_morning_task() -> None:
29
29
  # This task runs every weekday at 9 AM.
30
- await room.send(message="Good morning! Here's your weekday update!")
30
+ await room.send("Good morning! Here's your weekday update!")
31
31
 
32
32
 
33
- bot.start()
33
+ bot.start(config="config.yaml")
@@ -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 .message import Message
18
19
  from .extension import Extension
19
20
 
20
21
  __all__ = [
@@ -27,5 +28,6 @@ __all__ = [
27
28
  "HelpCommand",
28
29
  "cooldown",
29
30
  "Room",
31
+ "Message",
30
32
  "Extension",
31
33
  ]
@@ -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.4.0a0'
22
- __version_tuple__ = version_tuple = (1, 4, 0, 'a0')
21
+ __version__ = version = '1.4.3a0'
22
+ __version_tuple__ = version_tuple = (1, 4, 3, 'a0')
23
23
 
24
- __commit_id__ = commit_id = 'g9a7d62be9'
24
+ __commit_id__ = commit_id = 'gba1ba5d3c'
@@ -28,29 +28,34 @@ class Bot(Registry):
28
28
  """
29
29
 
30
30
  def __init__(
31
- self, *, config: Union[Config, str], help: Optional[HelpCommand] = None
31
+ self,
32
+ *,
33
+ help_: Optional[HelpCommand] = None,
32
34
  ) -> None:
33
- if isinstance(config, Config):
34
- self.config = config
35
- elif isinstance(config, str):
36
- self.config = Config(config_path=config)
37
- else:
38
- raise TypeError("config must be a Config instance or a config file path")
35
+ super().__init__(self.__class__.__name__)
39
36
 
40
- super().__init__(self.__class__.__name__, prefix=self.config.prefix)
41
-
42
- self.client: AsyncClient = AsyncClient(self.config.homeserver)
37
+ self._config: Config | None = None
38
+ self._client: AsyncClient | None = None
39
+ self._help: HelpCommand | None = help_
43
40
  self.extensions: dict[str, Extension] = {}
44
41
  self.scheduler: Scheduler = Scheduler()
45
42
  self.log: logging.Logger = logging.getLogger(__name__)
43
+ self.start_at: float | None = None
46
44
 
47
- self.start_at: float | None = None # unix timestamp
45
+ @property
46
+ def client(self) -> AsyncClient:
47
+ assert self._client is not None, "Bot has not been started."
48
+ return self._client
48
49
 
49
- self.help: HelpCommand = help or DefaultHelpCommand(prefix=self.prefix)
50
- self.register_command(self.help)
50
+ @property
51
+ def config(self) -> Config:
52
+ assert self._config is not None, "Bot has not been started."
53
+ return self._config
51
54
 
52
- self.client.add_event_callback(self._on_matrix_event, Event)
53
- self._auto_register_events()
55
+ @property
56
+ def help(self) -> HelpCommand:
57
+ assert self._help is not None, "Bot has not been started."
58
+ return self._help
54
59
 
55
60
  def _auto_register_events(self) -> None:
56
61
  for attr in dir(self):
@@ -105,7 +110,7 @@ class Bot(Registry):
105
110
  )
106
111
 
107
112
  self.extensions[extension.name] = extension
108
- extension.load()
113
+ extension.load(self)
109
114
  self.log.debug("loaded extension '%s'", extension.name)
110
115
 
111
116
  def unload_extension(self, ext_name: str) -> None:
@@ -192,7 +197,26 @@ class Bot(Registry):
192
197
 
193
198
  # ENTRYPOINT
194
199
 
195
- def start(self) -> None:
200
+ def _load_config(self, config: Config | str) -> None:
201
+ if self._config is not None:
202
+ raise RuntimeError("Config is already loaded.")
203
+
204
+ if isinstance(config, str):
205
+ config = Config(config_path=config)
206
+ elif not isinstance(config, Config):
207
+ raise TypeError("config must be a Config instance or a config file path")
208
+
209
+ self._config = config
210
+ self._client = AsyncClient(config.homeserver)
211
+ self._help = self._help or DefaultHelpCommand()
212
+
213
+ self.prefix = config.prefix
214
+ self.register_command(self.help)
215
+
216
+ self.client.add_event_callback(self._on_matrix_event, Event)
217
+ self._auto_register_events()
218
+
219
+ def start(self, *, config: Config | str) -> None:
196
220
  """
197
221
  Synchronous entry point for running the bot.
198
222
 
@@ -201,6 +225,9 @@ class Bot(Registry):
201
225
  :func:`asyncio.run`, and ensures the client is closed gracefully
202
226
  on interruption.
203
227
  """
228
+ if config is not None:
229
+ self._load_config(config)
230
+
204
231
  try:
205
232
  asyncio.run(self.run())
206
233
  except KeyboardInterrupt:
@@ -235,10 +262,10 @@ class Bot(Registry):
235
262
 
236
263
  # MATRIX EVENTS
237
264
 
238
- async def on_message(self, room: MatrixRoom, event: Event) -> None:
265
+ async def on_message(self, room: Room, event: Event) -> None:
239
266
  await self._process_commands(room, event)
240
267
 
241
- async def _on_matrix_event(self, room: MatrixRoom, event: Event) -> None:
268
+ async def _on_matrix_event(self, matrix_room: MatrixRoom, event: Event) -> None:
242
269
  # ignore bot events
243
270
  if event.sender == self.client.user:
244
271
  return
@@ -248,6 +275,7 @@ class Bot(Registry):
248
275
  return
249
276
 
250
277
  try:
278
+ room = self.get_room(matrix_room.room_id)
251
279
  await self._dispatch_matrix_event(room, event)
252
280
  except Exception as error:
253
281
  await self._on_error(error)
@@ -257,14 +285,14 @@ class Bot(Registry):
257
285
  for handler in self._hook_handlers.get(event_name, []):
258
286
  await handler(*args, **kwargs)
259
287
 
260
- async def _dispatch_matrix_event(self, room: MatrixRoom, event: Event) -> None:
288
+ async def _dispatch_matrix_event(self, room: Room, event: Event) -> None:
261
289
  """Fire all listeners registered for a named matrix event."""
262
290
  for event_type, funcs in self._event_handlers.items():
263
291
  if isinstance(event, event_type):
264
292
  for func in funcs:
265
293
  await func(room, event)
266
294
 
267
- async def _process_commands(self, room: MatrixRoom, event: Event) -> None:
295
+ async def _process_commands(self, room: Room, event: Event) -> None:
268
296
  """Parse and execute commands"""
269
297
  ctx = await self._build_context(room, event)
270
298
 
@@ -276,7 +304,7 @@ class Bot(Registry):
276
304
  await self._on_command(ctx)
277
305
  await ctx.command(ctx)
278
306
 
279
- async def _build_context(self, matrix_room: MatrixRoom, event: Event) -> Context:
307
+ async def _build_context(self, matrix_room: Room, event: Event) -> Context:
280
308
  room = self.get_room(matrix_room.room_id)
281
309
  ctx = Context(bot=self, room=room, event=event)
282
310
  prefix = self.prefix or self.config.prefix
@@ -1,8 +1,10 @@
1
- import logging
2
1
  import inspect
2
+ import logging
3
+ from typing import Callable, Optional
3
4
 
4
- from typing import Any, Callable, Coroutine, Optional
5
+ from matrix.protocols import BotLike
5
6
  from matrix.registry import Registry
7
+ from matrix.room import Room
6
8
 
7
9
  logger = logging.getLogger(__name__)
8
10
 
@@ -10,10 +12,19 @@ logger = logging.getLogger(__name__)
10
12
  class Extension(Registry):
11
13
  def __init__(self, name: str, prefix: Optional[str] = None) -> None:
12
14
  super().__init__(name, prefix=prefix)
15
+
16
+ self.bot: Optional[BotLike] = None
13
17
  self._on_load: Optional[Callable] = None
14
18
  self._on_unload: Optional[Callable] = None
15
19
 
16
- def load(self) -> None:
20
+ def get_room(self, room_id: str) -> Room:
21
+ if self.bot is None:
22
+ raise RuntimeError("Extension is not loaded")
23
+ return self.bot.get_room(room_id)
24
+
25
+ def load(self, bot: BotLike) -> None:
26
+ self.bot = bot
27
+
17
28
  if self._on_load:
18
29
  self._on_load()
19
30
 
@@ -35,6 +46,8 @@ class Extension(Registry):
35
46
  return func
36
47
 
37
48
  def unload(self) -> None:
49
+ self.bot = None
50
+
38
51
  if self._on_unload:
39
52
  self._on_unload()
40
53