matrix-python 0.1.2a0__tar.gz → 1.0.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.
- matrix_python-1.0.3a0/.github/workflows/codeql.yml +99 -0
- matrix_python-1.0.3a0/.github/workflows/scorecard.yml +52 -0
- {matrix_python-0.1.2a0 → matrix_python-1.0.3a0}/.gitignore +1 -1
- {matrix_python-0.1.2a0 → matrix_python-1.0.3a0}/PKG-INFO +3 -1
- {matrix_python-0.1.2a0 → matrix_python-1.0.3a0}/README.md +2 -0
- {matrix_python-0.1.2a0 → matrix_python-1.0.3a0}/examples/scheduler.py +1 -2
- {matrix_python-0.1.2a0 → matrix_python-1.0.3a0}/matrix/__init__.py +3 -1
- {matrix_python-0.1.2a0 → matrix_python-1.0.3a0}/matrix/bot.py +41 -14
- {matrix_python-0.1.2a0 → matrix_python-1.0.3a0}/matrix/checks.py +5 -2
- {matrix_python-0.1.2a0 → matrix_python-1.0.3a0}/matrix/command.py +57 -38
- {matrix_python-0.1.2a0 → matrix_python-1.0.3a0}/matrix/config.py +22 -12
- {matrix_python-0.1.2a0 → matrix_python-1.0.3a0}/matrix/context.py +14 -3
- matrix_python-1.0.3a0/matrix/errors.py +57 -0
- matrix_python-1.0.3a0/matrix/group.py +66 -0
- matrix_python-1.0.3a0/matrix/help/__init__.py +4 -0
- matrix_python-1.0.3a0/matrix/help/help_command.py +376 -0
- matrix_python-1.0.3a0/matrix/help/pagination.py +92 -0
- {matrix_python-0.1.2a0 → matrix_python-1.0.3a0}/matrix/message.py +5 -2
- {matrix_python-0.1.2a0 → matrix_python-1.0.3a0}/matrix_python.egg-info/PKG-INFO +3 -1
- {matrix_python-0.1.2a0 → matrix_python-1.0.3a0}/matrix_python.egg-info/SOURCES.txt +13 -3
- {matrix_python-0.1.2a0 → matrix_python-1.0.3a0}/mypy.ini +2 -0
- {matrix_python-0.1.2a0 → matrix_python-1.0.3a0}/pyproject.toml +1 -1
- matrix_python-1.0.3a0/tests/config_fixture.yaml +4 -0
- matrix_python-1.0.3a0/tests/config_fixture_token.yaml +1 -0
- matrix_python-1.0.3a0/tests/help/test_default_help_command.py +84 -0
- matrix_python-1.0.3a0/tests/help/test_help_command.py +174 -0
- matrix_python-1.0.3a0/tests/help/test_pagination.py +83 -0
- {matrix_python-0.1.2a0 → matrix_python-1.0.3a0}/tests/test_bot.py +4 -24
- {matrix_python-0.1.2a0 → matrix_python-1.0.3a0}/tests/test_context.py +1 -1
- matrix_python-1.0.3a0/tests/test_group.py +127 -0
- matrix_python-1.0.3a0/tests/test_help.py +0 -0
- {matrix_python-0.1.2a0 → matrix_python-1.0.3a0}/tests/test_message.py +1 -1
- matrix_python-0.1.2a0/matrix/errors.py +0 -37
- matrix_python-0.1.2a0/matrix/help.py +0 -231
- matrix_python-0.1.2a0/tests/config_mixture.yaml +0 -4
- matrix_python-0.1.2a0/tests/test_help.py +0 -127
- {matrix_python-0.1.2a0 → matrix_python-1.0.3a0}/.github/workflows/publish.yml +0 -0
- {matrix_python-0.1.2a0 → matrix_python-1.0.3a0}/.github/workflows/tests.yml +0 -0
- {matrix_python-0.1.2a0 → matrix_python-1.0.3a0}/CODE_OF_CONDUCT.md +0 -0
- {matrix_python-0.1.2a0 → matrix_python-1.0.3a0}/CONTRIBUTING.md +0 -0
- {matrix_python-0.1.2a0 → matrix_python-1.0.3a0}/LICENSE +0 -0
- {matrix_python-0.1.2a0 → matrix_python-1.0.3a0}/examples/checks.py +0 -0
- {matrix_python-0.1.2a0 → matrix_python-1.0.3a0}/examples/config.yaml +0 -0
- {matrix_python-0.1.2a0 → matrix_python-1.0.3a0}/examples/cooldown.py +0 -0
- {matrix_python-0.1.2a0 → matrix_python-1.0.3a0}/examples/error.py +0 -0
- {matrix_python-0.1.2a0 → matrix_python-1.0.3a0}/examples/ping.py +0 -0
- {matrix_python-0.1.2a0 → matrix_python-1.0.3a0}/examples/reaction.py +0 -0
- {matrix_python-0.1.2a0 → matrix_python-1.0.3a0}/matrix/room.py +0 -0
- {matrix_python-0.1.2a0 → matrix_python-1.0.3a0}/matrix/scheduler.py +0 -0
- {matrix_python-0.1.2a0 → matrix_python-1.0.3a0}/matrix_python.egg-info/dependency_links.txt +0 -0
- {matrix_python-0.1.2a0 → matrix_python-1.0.3a0}/matrix_python.egg-info/requires.txt +0 -0
- {matrix_python-0.1.2a0 → matrix_python-1.0.3a0}/matrix_python.egg-info/top_level.txt +0 -0
- {matrix_python-0.1.2a0 → matrix_python-1.0.3a0}/setup.cfg +0 -0
- {matrix_python-0.1.2a0 → matrix_python-1.0.3a0}/tests/test_command.py +1 -1
- {matrix_python-0.1.2a0 → matrix_python-1.0.3a0}/tests/test_config.py +0 -0
- {matrix_python-0.1.2a0 → matrix_python-1.0.3a0}/tests/test_room.py +0 -0
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# For most projects, this workflow file will not need changing; you simply need
|
|
2
|
+
# to commit it to your repository.
|
|
3
|
+
#
|
|
4
|
+
# You may wish to alter this file to override the set of languages analyzed,
|
|
5
|
+
# or to provide custom queries or build logic.
|
|
6
|
+
#
|
|
7
|
+
# ******** NOTE ********
|
|
8
|
+
# We have attempted to detect the languages in your repository. Please check
|
|
9
|
+
# the `language` matrix defined below to confirm you have the correct set of
|
|
10
|
+
# supported CodeQL languages.
|
|
11
|
+
#
|
|
12
|
+
name: "CodeQL Advanced"
|
|
13
|
+
|
|
14
|
+
on:
|
|
15
|
+
push:
|
|
16
|
+
branches: [ "main" ]
|
|
17
|
+
pull_request:
|
|
18
|
+
branches: [ "main" ]
|
|
19
|
+
schedule:
|
|
20
|
+
- cron: '24 17 * * 4'
|
|
21
|
+
|
|
22
|
+
jobs:
|
|
23
|
+
analyze:
|
|
24
|
+
name: Analyze (${{ matrix.language }})
|
|
25
|
+
# Runner size impacts CodeQL analysis time. To learn more, please see:
|
|
26
|
+
# - https://gh.io/recommended-hardware-resources-for-running-codeql
|
|
27
|
+
# - https://gh.io/supported-runners-and-hardware-resources
|
|
28
|
+
# - https://gh.io/using-larger-runners (GitHub.com only)
|
|
29
|
+
# Consider using larger runners or machines with greater resources for possible analysis time improvements.
|
|
30
|
+
runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }}
|
|
31
|
+
permissions:
|
|
32
|
+
# required for all workflows
|
|
33
|
+
security-events: write
|
|
34
|
+
|
|
35
|
+
# required to fetch internal or private CodeQL packs
|
|
36
|
+
packages: read
|
|
37
|
+
|
|
38
|
+
# only required for workflows in private repositories
|
|
39
|
+
actions: read
|
|
40
|
+
contents: read
|
|
41
|
+
|
|
42
|
+
strategy:
|
|
43
|
+
fail-fast: false
|
|
44
|
+
matrix:
|
|
45
|
+
include:
|
|
46
|
+
- language: python
|
|
47
|
+
build-mode: none
|
|
48
|
+
# CodeQL supports the following values keywords for 'language': 'actions', 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'rust', 'swift'
|
|
49
|
+
# Use `c-cpp` to analyze code written in C, C++ or both
|
|
50
|
+
# Use 'java-kotlin' to analyze code written in Java, Kotlin or both
|
|
51
|
+
# Use 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both
|
|
52
|
+
# To learn more about changing the languages that are analyzed or customizing the build mode for your analysis,
|
|
53
|
+
# see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning.
|
|
54
|
+
# If you are analyzing a compiled language, you can modify the 'build-mode' for that language to customize how
|
|
55
|
+
# your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages
|
|
56
|
+
steps:
|
|
57
|
+
- name: Checkout repository
|
|
58
|
+
uses: actions/checkout@v4
|
|
59
|
+
|
|
60
|
+
# Add any setup steps before running the `github/codeql-action/init` action.
|
|
61
|
+
# This includes steps like installing compilers or runtimes (`actions/setup-node`
|
|
62
|
+
# or others). This is typically only required for manual builds.
|
|
63
|
+
# - name: Setup runtime (example)
|
|
64
|
+
# uses: actions/setup-example@v1
|
|
65
|
+
|
|
66
|
+
# Initializes the CodeQL tools for scanning.
|
|
67
|
+
- name: Initialize CodeQL
|
|
68
|
+
uses: github/codeql-action/init@v4
|
|
69
|
+
with:
|
|
70
|
+
languages: ${{ matrix.language }}
|
|
71
|
+
build-mode: ${{ matrix.build-mode }}
|
|
72
|
+
# If you wish to specify custom queries, you can do so here or in a config file.
|
|
73
|
+
# By default, queries listed here will override any specified in a config file.
|
|
74
|
+
# Prefix the list here with "+" to use these queries and those in the config file.
|
|
75
|
+
|
|
76
|
+
# For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
|
|
77
|
+
# queries: security-extended,security-and-quality
|
|
78
|
+
|
|
79
|
+
# If the analyze step fails for one of the languages you are analyzing with
|
|
80
|
+
# "We were unable to automatically build your code", modify the matrix above
|
|
81
|
+
# to set the build mode to "manual" for that language. Then modify this step
|
|
82
|
+
# to build your code.
|
|
83
|
+
# ℹ️ Command-line programs to run using the OS shell.
|
|
84
|
+
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
|
|
85
|
+
- name: Run manual build steps
|
|
86
|
+
if: matrix.build-mode == 'manual'
|
|
87
|
+
shell: bash
|
|
88
|
+
run: |
|
|
89
|
+
echo 'If you are using a "manual" build mode for one or more of the' \
|
|
90
|
+
'languages you are analyzing, replace this with the commands to build' \
|
|
91
|
+
'your code, for example:'
|
|
92
|
+
echo ' make bootstrap'
|
|
93
|
+
echo ' make release'
|
|
94
|
+
exit 1
|
|
95
|
+
|
|
96
|
+
- name: Perform CodeQL Analysis
|
|
97
|
+
uses: github/codeql-action/analyze@v4
|
|
98
|
+
with:
|
|
99
|
+
category: "/language:${{matrix.language}}"
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
name: Scorecard supply-chain security
|
|
2
|
+
on:
|
|
3
|
+
branch_protection_rule:
|
|
4
|
+
schedule:
|
|
5
|
+
- cron: '16 0 * * 4'
|
|
6
|
+
push:
|
|
7
|
+
branches: ["main"]
|
|
8
|
+
workflow_dispatch:
|
|
9
|
+
|
|
10
|
+
permissions: read-all
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
analysis:
|
|
14
|
+
name: Scorecard analysis
|
|
15
|
+
runs-on: ubuntu-latest
|
|
16
|
+
permissions:
|
|
17
|
+
# Needed to upload the results to code-scanning dashboard.
|
|
18
|
+
security-events: write
|
|
19
|
+
# Needed to publish results and get a badge (see publish_results below).
|
|
20
|
+
id-token: write
|
|
21
|
+
contents: read
|
|
22
|
+
actions: read
|
|
23
|
+
|
|
24
|
+
steps:
|
|
25
|
+
- name: Harden Runner
|
|
26
|
+
uses: step-security/harden-runner@ec9f2d5744a09debf3a187a3f4f675c53b671911 # v2.13.0
|
|
27
|
+
with:
|
|
28
|
+
egress-policy: audit
|
|
29
|
+
|
|
30
|
+
- name: "Checkout code"
|
|
31
|
+
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
|
32
|
+
with:
|
|
33
|
+
persist-credentials: false
|
|
34
|
+
|
|
35
|
+
- name: "Run analysis"
|
|
36
|
+
uses: ossf/scorecard-action@05b42c624433fc40578a4040d5cf5e36ddca8cde # v2.4.2
|
|
37
|
+
with:
|
|
38
|
+
results_file: results.sarif
|
|
39
|
+
results_format: sarif
|
|
40
|
+
publish_results: true
|
|
41
|
+
|
|
42
|
+
- name: "Upload artifact"
|
|
43
|
+
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
|
44
|
+
with:
|
|
45
|
+
name: SARIF file
|
|
46
|
+
path: results.sarif
|
|
47
|
+
retention-days: 5
|
|
48
|
+
|
|
49
|
+
- name: "Upload to code-scanning"
|
|
50
|
+
uses: github/codeql-action/upload-sarif@3599b3baa15b485a2e49ef411a7a4bb2452e7f93 # v3.29.5
|
|
51
|
+
with:
|
|
52
|
+
sarif_file: results.sarif
|
|
@@ -165,7 +165,7 @@ cython_debug/
|
|
|
165
165
|
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
|
166
166
|
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
|
167
167
|
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
|
168
|
-
|
|
168
|
+
.idea/
|
|
169
169
|
|
|
170
170
|
# Ruff stuff:
|
|
171
171
|
.ruff_cache/
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: matrix-python
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 1.0.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>
|
|
@@ -710,6 +710,8 @@ Requires-Dist: types-Markdown; extra == "dev"
|
|
|
710
710
|
[](https://github.com/Code-Society-Lab/matrixpy/wiki)
|
|
711
711
|
[](https://discord.gg/code-society-823178343943897088)
|
|
712
712
|
[](https://github.com/Code-Society-Lab/matrixpy/actions/workflows/tests.yml)
|
|
713
|
+
[](https://github.com/Code-Society-Lab/matrixpy/actions/workflows/codeql.yml)
|
|
714
|
+
[](https://securityscorecards.dev/viewer/?uri=github.com/Code-Society-Lab/matrixpy)
|
|
713
715
|
|
|
714
716
|
Matrix.py is a lightweight and intuitive Python library to build bots on
|
|
715
717
|
the [Matrix protocol]([Matrix](https://matrix.org)). It provides a clean,
|
|
@@ -11,6 +11,8 @@
|
|
|
11
11
|
[](https://github.com/Code-Society-Lab/matrixpy/wiki)
|
|
12
12
|
[](https://discord.gg/code-society-823178343943897088)
|
|
13
13
|
[](https://github.com/Code-Society-Lab/matrixpy/actions/workflows/tests.yml)
|
|
14
|
+
[](https://github.com/Code-Society-Lab/matrixpy/actions/workflows/codeql.yml)
|
|
15
|
+
[](https://securityscorecards.dev/viewer/?uri=github.com/Code-Society-Lab/matrixpy)
|
|
14
16
|
|
|
15
17
|
Matrix.py is a lightweight and intuitive Python library to build bots on
|
|
16
18
|
the [Matrix protocol]([Matrix](https://matrix.org)). It provides a clean,
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
"""A simple, developer-friendly library to create powerful Matrix bots."""
|
|
2
2
|
|
|
3
|
-
__version__ = "0.
|
|
3
|
+
__version__ = "1.0.3-alpha"
|
|
4
4
|
|
|
5
5
|
from .bot import Bot
|
|
6
|
+
from .group import Group
|
|
6
7
|
from .config import Config
|
|
7
8
|
from .context import Context
|
|
8
9
|
from .command import Command
|
|
@@ -11,6 +12,7 @@ from .checks import cooldown
|
|
|
11
12
|
|
|
12
13
|
__all__ = [
|
|
13
14
|
"Bot",
|
|
15
|
+
"Group",
|
|
14
16
|
"Config",
|
|
15
17
|
"Command",
|
|
16
18
|
"Context",
|
|
@@ -25,15 +25,22 @@ from nio import (
|
|
|
25
25
|
)
|
|
26
26
|
|
|
27
27
|
from .room import Room
|
|
28
|
+
from .group import Group
|
|
28
29
|
from .config import Config
|
|
29
30
|
from .context import Context
|
|
30
31
|
from .command import Command
|
|
31
|
-
from .help import HelpCommand
|
|
32
|
-
from .errors import AlreadyRegisteredError, CommandNotFoundError, CheckError
|
|
32
|
+
from .help import HelpCommand, DefaultHelpCommand
|
|
33
33
|
from .scheduler import Scheduler
|
|
34
34
|
|
|
35
|
+
from .errors import (
|
|
36
|
+
AlreadyRegisteredError,
|
|
37
|
+
CommandNotFoundError,
|
|
38
|
+
CheckError,
|
|
39
|
+
)
|
|
40
|
+
|
|
35
41
|
|
|
36
42
|
Callback = Callable[..., Coroutine[Any, Any, Any]]
|
|
43
|
+
GroupCallable = Callable[[Callable[..., Coroutine[Any, Any, Any]]], Group]
|
|
37
44
|
ErrorCallback = Callable[[Exception], Coroutine]
|
|
38
45
|
CommandErrorCallback = Callable[["Context", Exception], Coroutine[Any, Any, Any]]
|
|
39
46
|
|
|
@@ -66,13 +73,15 @@ class Bot:
|
|
|
66
73
|
"on_member_change": RoomMemberEvent,
|
|
67
74
|
}
|
|
68
75
|
|
|
69
|
-
def __init__(
|
|
76
|
+
def __init__(
|
|
77
|
+
self, *, config: Union[Config, str], help: Optional[HelpCommand] = None
|
|
78
|
+
) -> None:
|
|
70
79
|
if isinstance(config, Config):
|
|
71
80
|
self.config = config
|
|
72
81
|
elif isinstance(config, str):
|
|
73
82
|
self.config = Config(config_path=config)
|
|
74
83
|
else:
|
|
75
|
-
|
|
84
|
+
raise TypeError("config must be a Config instance or a config file path")
|
|
76
85
|
|
|
77
86
|
self.client: AsyncClient = AsyncClient(self.config.homeserver)
|
|
78
87
|
self.log: logging.Logger = logging.getLogger(__name__)
|
|
@@ -89,7 +98,7 @@ class Bot:
|
|
|
89
98
|
self._error_handlers: dict[type[Exception], ErrorCallback] = {}
|
|
90
99
|
self._command_error_handlers: dict[type[Exception], CommandErrorCallback] = {}
|
|
91
100
|
|
|
92
|
-
self.help: HelpCommand =
|
|
101
|
+
self.help: HelpCommand = help or DefaultHelpCommand(prefix=self.prefix)
|
|
93
102
|
self.register_command(self.help)
|
|
94
103
|
|
|
95
104
|
self.client.add_event_callback(self._on_event, Event)
|
|
@@ -179,7 +188,14 @@ class Bot:
|
|
|
179
188
|
return wrapper(func)
|
|
180
189
|
|
|
181
190
|
def command(
|
|
182
|
-
self,
|
|
191
|
+
self,
|
|
192
|
+
name: Optional[str] = None,
|
|
193
|
+
*,
|
|
194
|
+
description: Optional[str] = None,
|
|
195
|
+
prefix: Optional[str] = None,
|
|
196
|
+
parent: Optional[str] = None,
|
|
197
|
+
usage: Optional[str] = None,
|
|
198
|
+
cooldown: Optional[tuple[int, float]] = None,
|
|
183
199
|
) -> Callable[[Callback], Command]:
|
|
184
200
|
"""
|
|
185
201
|
Decorator to register a coroutine function as a command handler.
|
|
@@ -187,9 +203,12 @@ class Bot:
|
|
|
187
203
|
The command name defaults to the function name unless
|
|
188
204
|
explicitly provided.
|
|
189
205
|
|
|
190
|
-
:param name: The name of the command. If omitted, the function
|
|
191
|
-
|
|
192
|
-
:
|
|
206
|
+
:param name: The name of the command. If omitted, the function name is used.
|
|
207
|
+
:param description: A brief description of the command.
|
|
208
|
+
:param prefix: The command prefix. If omitted, the bot's default prefix is used.
|
|
209
|
+
:param parent: The parent command name for subcommands.
|
|
210
|
+
:param usage: A usage string describing command arguments.
|
|
211
|
+
:param cooldown: A tuple defining (max_calls, per_seconds) for rate limiting.
|
|
193
212
|
:raises TypeError: If the decorated function is not a coroutine.
|
|
194
213
|
:raises ValueError: If a command with the same name is registered.
|
|
195
214
|
:return: Decorator that registers the command handler.
|
|
@@ -197,12 +216,20 @@ class Bot:
|
|
|
197
216
|
"""
|
|
198
217
|
|
|
199
218
|
def wrapper(func: Callback) -> Command:
|
|
200
|
-
cmd = Command(
|
|
219
|
+
cmd = Command(
|
|
220
|
+
func,
|
|
221
|
+
name=name,
|
|
222
|
+
description=description,
|
|
223
|
+
prefix=prefix,
|
|
224
|
+
parent=parent,
|
|
225
|
+
usage=usage,
|
|
226
|
+
cooldown=cooldown,
|
|
227
|
+
)
|
|
201
228
|
return self.register_command(cmd)
|
|
202
229
|
|
|
203
230
|
return wrapper
|
|
204
231
|
|
|
205
|
-
def schedule(self, cron: str):
|
|
232
|
+
def schedule(self, cron: str) -> Callable[..., Callback]:
|
|
206
233
|
"""
|
|
207
234
|
Decorator to register a coroutine function as a scheduled task.
|
|
208
235
|
|
|
@@ -225,12 +252,12 @@ class Bot:
|
|
|
225
252
|
|
|
226
253
|
return wrapper
|
|
227
254
|
|
|
228
|
-
def register_command(self, cmd: Command):
|
|
255
|
+
def register_command(self, cmd: Command) -> Command:
|
|
229
256
|
if cmd in self.commands:
|
|
230
257
|
raise AlreadyRegisteredError(cmd)
|
|
231
258
|
|
|
232
259
|
self.commands[cmd.name] = cmd
|
|
233
|
-
self.log.debug("command %s registered", cmd)
|
|
260
|
+
self.log.debug("command '%s' registered", cmd)
|
|
234
261
|
|
|
235
262
|
return cmd
|
|
236
263
|
|
|
@@ -312,7 +339,7 @@ class Bot:
|
|
|
312
339
|
|
|
313
340
|
await ctx.command(ctx)
|
|
314
341
|
|
|
315
|
-
async def _build_context(self, room: MatrixRoom, event: Event):
|
|
342
|
+
async def _build_context(self, room: MatrixRoom, event: Event) -> Context:
|
|
316
343
|
"""Builds the base context and extracts the command from the event"""
|
|
317
344
|
ctx = Context(bot=self, room=room, event=event)
|
|
318
345
|
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
from typing import Callable
|
|
1
|
+
from typing import TYPE_CHECKING, Callable
|
|
2
|
+
|
|
3
|
+
if TYPE_CHECKING:
|
|
4
|
+
from .command import Command
|
|
2
5
|
|
|
3
6
|
|
|
4
7
|
def cooldown(rate: int, period: float) -> Callable:
|
|
@@ -11,7 +14,7 @@ def cooldown(rate: int, period: float) -> Callable:
|
|
|
11
14
|
:type period: float
|
|
12
15
|
"""
|
|
13
16
|
|
|
14
|
-
def wrapper(cmd):
|
|
17
|
+
def wrapper(cmd: Command) -> Command:
|
|
15
18
|
cmd.set_cooldown(rate, period)
|
|
16
19
|
return cmd
|
|
17
20
|
|
|
@@ -31,32 +31,43 @@ class Command:
|
|
|
31
31
|
:param func: The coroutine that is executed when the command is invoked.
|
|
32
32
|
:type func: Callable[..., Coroutine[Any, Any, Any]]
|
|
33
33
|
|
|
34
|
-
:
|
|
35
|
-
:
|
|
36
|
-
:
|
|
37
|
-
:
|
|
34
|
+
:param name: Optional name. Defaults to the function's name.
|
|
35
|
+
:param description: Optional description of what the command does.
|
|
36
|
+
:param prefix: Optional prefix for the command.
|
|
37
|
+
:param parent: Optional parent command name for subcommands.
|
|
38
|
+
:param usage: Optional usage string for the command.
|
|
39
|
+
:param cooldown: Optional cooldown settings as a tuple of (rate, period).
|
|
38
40
|
|
|
39
41
|
:raises TypeError: If the provided name is not a string.
|
|
40
42
|
:raises TypeError: If the provided callback is not a coroutine.
|
|
41
43
|
"""
|
|
42
44
|
|
|
43
|
-
def __init__(
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
45
|
+
def __init__(
|
|
46
|
+
self,
|
|
47
|
+
func: Callback,
|
|
48
|
+
*,
|
|
49
|
+
name: Optional[str] = None,
|
|
50
|
+
description: Optional[str] = None,
|
|
51
|
+
prefix: Optional[str] = None,
|
|
52
|
+
parent: Optional[str] = None,
|
|
53
|
+
usage: Optional[str] = None,
|
|
54
|
+
cooldown: Optional[tuple[int, float]] = None,
|
|
55
|
+
):
|
|
56
|
+
if name is not None and not isinstance(name, str):
|
|
47
57
|
raise TypeError("Name must be a string.")
|
|
48
58
|
|
|
49
|
-
self.name: str = name
|
|
59
|
+
self.name: str = name or func.__name__
|
|
50
60
|
self.callback = func
|
|
51
61
|
self.checks: List[Callback] = []
|
|
52
62
|
|
|
53
|
-
self.description: str =
|
|
54
|
-
self.prefix: str =
|
|
55
|
-
self.
|
|
63
|
+
self.description: str = description or ""
|
|
64
|
+
self.prefix: str = prefix or ""
|
|
65
|
+
self.parent: str = parent or ""
|
|
66
|
+
self.usage: str = usage or self._build_usage()
|
|
56
67
|
self.help: str = self._build_help()
|
|
57
68
|
|
|
58
|
-
self.
|
|
59
|
-
self.
|
|
69
|
+
self._before_invoke_callback: Optional[Callback] = None
|
|
70
|
+
self._after_invoke_callback: Optional[Callback] = None
|
|
60
71
|
self._on_error: Optional[ErrorCallback] = None
|
|
61
72
|
self._error_handlers: dict[type[Exception], ErrorCallback] = {}
|
|
62
73
|
|
|
@@ -64,7 +75,7 @@ class Command:
|
|
|
64
75
|
self.cooldown_period: Optional[float] = None
|
|
65
76
|
self.cooldown_calls: DefaultDict[str, deque[float]] = defaultdict(deque)
|
|
66
77
|
|
|
67
|
-
if cooldown
|
|
78
|
+
if cooldown:
|
|
68
79
|
self.set_cooldown(*cooldown)
|
|
69
80
|
|
|
70
81
|
@property
|
|
@@ -115,7 +126,12 @@ class Command:
|
|
|
115
126
|
:rtype: str
|
|
116
127
|
"""
|
|
117
128
|
params = " ".join(f"[{p.name}]" for p in self.params)
|
|
118
|
-
|
|
129
|
+
command_name = self.name
|
|
130
|
+
|
|
131
|
+
if self.parent:
|
|
132
|
+
command_name = f"{self.parent} {self.name}"
|
|
133
|
+
|
|
134
|
+
return f"{self.prefix}{command_name} {params}"
|
|
119
135
|
|
|
120
136
|
def _parse_arguments(self, ctx: "Context") -> list[Any]:
|
|
121
137
|
parsed_args = []
|
|
@@ -153,10 +169,13 @@ class Command:
|
|
|
153
169
|
self.cooldown_rate = rate
|
|
154
170
|
self.cooldown_period = period
|
|
155
171
|
|
|
156
|
-
async def cooldown_function(ctx):
|
|
172
|
+
async def cooldown_function(ctx: "Context") -> bool:
|
|
157
173
|
if ctx is None or not hasattr(ctx, "sender"):
|
|
158
174
|
return False
|
|
159
175
|
|
|
176
|
+
if self.cooldown_period is None or self.cooldown_rate is None:
|
|
177
|
+
return False
|
|
178
|
+
|
|
160
179
|
now = monotonic()
|
|
161
180
|
user_id = ctx.sender
|
|
162
181
|
calls = self.cooldown_calls[user_id]
|
|
@@ -186,7 +205,7 @@ class Command:
|
|
|
186
205
|
if not asyncio.iscoroutinefunction(func):
|
|
187
206
|
raise TypeError("The hook must be a coroutine.")
|
|
188
207
|
|
|
189
|
-
self.
|
|
208
|
+
self._before_invoke_callback = func
|
|
190
209
|
|
|
191
210
|
def after_invoke(self, func: Callback) -> None:
|
|
192
211
|
"""
|
|
@@ -201,7 +220,7 @@ class Command:
|
|
|
201
220
|
if not asyncio.iscoroutinefunction(func):
|
|
202
221
|
raise TypeError("The hook must be a coroutine.")
|
|
203
222
|
|
|
204
|
-
self.
|
|
223
|
+
self._after_invoke_callback = func
|
|
205
224
|
|
|
206
225
|
def error(self, exception: Optional[type[Exception]] = None) -> Callable:
|
|
207
226
|
"""
|
|
@@ -250,17 +269,25 @@ class Command:
|
|
|
250
269
|
ctx.logger.exception("error while executing command '%s'", self)
|
|
251
270
|
raise error
|
|
252
271
|
|
|
253
|
-
async def
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
272
|
+
async def invoke(self, ctx: "Context") -> None:
|
|
273
|
+
parsed_args = self._parse_arguments(ctx)
|
|
274
|
+
await self.callback(ctx, *parsed_args)
|
|
275
|
+
|
|
276
|
+
async def _invoke(self, ctx: "Context") -> None:
|
|
277
|
+
try:
|
|
278
|
+
for check in self.checks:
|
|
279
|
+
if not await check(ctx):
|
|
280
|
+
raise CheckError(self, check)
|
|
257
281
|
|
|
258
|
-
|
|
259
|
-
|
|
282
|
+
if self._before_invoke_callback:
|
|
283
|
+
await self._before_invoke_callback(ctx)
|
|
260
284
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
285
|
+
await self.invoke(ctx)
|
|
286
|
+
|
|
287
|
+
if self._after_invoke_callback:
|
|
288
|
+
await self._after_invoke_callback(ctx)
|
|
289
|
+
except Exception as error:
|
|
290
|
+
await self.on_error(ctx, error)
|
|
264
291
|
|
|
265
292
|
async def __call__(self, ctx: "Context") -> None:
|
|
266
293
|
"""
|
|
@@ -269,17 +296,9 @@ class Command:
|
|
|
269
296
|
:param ctx: The command execution context.
|
|
270
297
|
:type ctx: Context
|
|
271
298
|
"""
|
|
272
|
-
|
|
273
|
-
await self.__before_invoke(ctx)
|
|
274
|
-
|
|
275
|
-
parsed_args = self._parse_arguments(ctx)
|
|
276
|
-
await self.callback(ctx, *parsed_args)
|
|
277
|
-
|
|
278
|
-
await self.__after_invoke(ctx)
|
|
279
|
-
except Exception as error:
|
|
280
|
-
await self.on_error(ctx, error)
|
|
299
|
+
await self._invoke(ctx)
|
|
281
300
|
|
|
282
|
-
def __eq__(self, other) -> bool:
|
|
301
|
+
def __eq__(self, other: object) -> bool:
|
|
283
302
|
return self.name == other
|
|
284
303
|
|
|
285
304
|
def __hash__(self) -> int:
|
|
@@ -13,30 +13,40 @@ class Config:
|
|
|
13
13
|
token: (Optional) One of the password or token must be provided.
|
|
14
14
|
prefix: Defaults to '!' if not specified in the config file.
|
|
15
15
|
|
|
16
|
-
:param config_path:
|
|
17
|
-
:
|
|
18
|
-
|
|
19
|
-
:param
|
|
20
|
-
:
|
|
16
|
+
:param config_path: Path to the YAML configuration file.
|
|
17
|
+
:param homeserver: The Matrix homeserver URL.
|
|
18
|
+
:param username: The Matrix user ID (username).
|
|
19
|
+
:param password: The password for the Matrix user.
|
|
20
|
+
:param token: The access token for the Matrix user.
|
|
21
|
+
:param prefix: The command prefix.
|
|
21
22
|
|
|
22
23
|
:raises FileNotFoundError: If the configuration file does not exist.
|
|
23
24
|
:raises yaml.YAMLError: If the configuration file cannot be parsed.
|
|
24
25
|
:raises ConfigError: If neither password or token has been provided.
|
|
25
26
|
"""
|
|
26
27
|
|
|
27
|
-
def __init__(
|
|
28
|
-
self
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
28
|
+
def __init__(
|
|
29
|
+
self,
|
|
30
|
+
config_path: Optional[str] = None,
|
|
31
|
+
*,
|
|
32
|
+
homeserver: Optional[str] = None,
|
|
33
|
+
username: Optional[str] = None,
|
|
34
|
+
password: Optional[str] = None,
|
|
35
|
+
token: Optional[str] = None,
|
|
36
|
+
prefix: Optional[str] = None,
|
|
37
|
+
) -> None:
|
|
38
|
+
self.homeserver: str = homeserver or "https://matrix.org"
|
|
39
|
+
self.user_id: Optional[str] = username
|
|
40
|
+
self.password: Optional[str] = password
|
|
41
|
+
self.token: Optional[str] = token
|
|
42
|
+
self.prefix: str = prefix or "!"
|
|
33
43
|
|
|
34
44
|
if config_path:
|
|
35
45
|
self.load_from_file(config_path)
|
|
36
46
|
elif not (self.password or self.token):
|
|
37
47
|
raise ConfigError("username and password or token")
|
|
38
48
|
|
|
39
|
-
def load_from_file(self, config_path: str):
|
|
49
|
+
def load_from_file(self, config_path: str) -> None:
|
|
40
50
|
"""Load Matrix client settings via YAML config file."""
|
|
41
51
|
with open(config_path, "r") as f:
|
|
42
52
|
config = yaml.safe_load(f)
|
|
@@ -42,6 +42,7 @@ class Context:
|
|
|
42
42
|
# Command metdata
|
|
43
43
|
self.prefix: str = bot.prefix
|
|
44
44
|
self.command: Optional[Command] = None
|
|
45
|
+
self.subcommand: Optional[Command] = None
|
|
45
46
|
self._args: List[str] = shlex.split(self.body)
|
|
46
47
|
|
|
47
48
|
@property
|
|
@@ -54,8 +55,12 @@ class Context:
|
|
|
54
55
|
:return: The list of arguments.
|
|
55
56
|
:rtype: List[str]
|
|
56
57
|
"""
|
|
58
|
+
if self.subcommand:
|
|
59
|
+
return self._args[2:]
|
|
60
|
+
|
|
57
61
|
if self.command:
|
|
58
62
|
return self._args[1:]
|
|
63
|
+
|
|
59
64
|
return self._args
|
|
60
65
|
|
|
61
66
|
@property
|
|
@@ -80,6 +85,12 @@ class Context:
|
|
|
80
85
|
raise MatrixError(f"Failed to send message: {e}")
|
|
81
86
|
|
|
82
87
|
async def send_help(self) -> None:
|
|
83
|
-
if
|
|
84
|
-
|
|
85
|
-
|
|
88
|
+
if self.subcommand:
|
|
89
|
+
await self.reply(self.subcommand.help)
|
|
90
|
+
return
|
|
91
|
+
|
|
92
|
+
if self.command:
|
|
93
|
+
await self.reply(self.command.help)
|
|
94
|
+
return
|
|
95
|
+
|
|
96
|
+
await self.bot.help.execute(self)
|