ErisPulse 2.4.3.dev0__tar.gz → 2.4.3.dev1__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.
- {erispulse-2.4.3.dev0 → erispulse-2.4.3.dev1}/.gitignore +1 -0
- {erispulse-2.4.3.dev0 → erispulse-2.4.3.dev1}/PKG-INFO +33 -18
- {erispulse-2.4.3.dev0 → erispulse-2.4.3.dev1}/README.md +31 -16
- {erispulse-2.4.3.dev0 → erispulse-2.4.3.dev1}/pyproject.toml +2 -2
- erispulse-2.4.3.dev1/src/ErisPulse/Core/Bases/__init__.py +17 -0
- {erispulse-2.4.3.dev0 → erispulse-2.4.3.dev1}/src/ErisPulse/Core/Bases/__init__.pyi +3 -2
- erispulse-2.4.3.dev1/src/ErisPulse/Core/Bases/storage.py +438 -0
- erispulse-2.4.3.dev1/src/ErisPulse/Core/Bases/storage.pyi +318 -0
- erispulse-2.4.3.dev1/src/ErisPulse/Core/__init__.py +49 -0
- {erispulse-2.4.3.dev0 → erispulse-2.4.3.dev1}/src/ErisPulse/Core/__init__.pyi +1 -1
- {erispulse-2.4.3.dev0 → erispulse-2.4.3.dev1}/src/ErisPulse/Core/adapter.py +8 -1
- {erispulse-2.4.3.dev0 → erispulse-2.4.3.dev1}/src/ErisPulse/Core/adapter.pyi +7 -0
- {erispulse-2.4.3.dev0 → erispulse-2.4.3.dev1}/src/ErisPulse/Core/config.py +13 -2
- {erispulse-2.4.3.dev0 → erispulse-2.4.3.dev1}/src/ErisPulse/Core/config.pyi +7 -33
- {erispulse-2.4.3.dev0 → erispulse-2.4.3.dev1}/src/ErisPulse/Core/router.py +6 -1
- {erispulse-2.4.3.dev0 → erispulse-2.4.3.dev1}/src/ErisPulse/Core/storage.py +445 -19
- erispulse-2.4.3.dev1/src/ErisPulse/Core/storage.pyi +404 -0
- {erispulse-2.4.3.dev0 → erispulse-2.4.3.dev1}/src/ErisPulse/finders/bases/finder.py +34 -0
- {erispulse-2.4.3.dev0 → erispulse-2.4.3.dev1}/src/ErisPulse/loaders/adapter.py +2 -2
- {erispulse-2.4.3.dev0 → erispulse-2.4.3.dev1}/src/ErisPulse/loaders/module.py +6 -2
- {erispulse-2.4.3.dev0 → erispulse-2.4.3.dev1}/src/ErisPulse/runtime/frame_config.py +17 -17
- {erispulse-2.4.3.dev0 → erispulse-2.4.3.dev1}/src/ErisPulse/sdk.py +90 -4
- {erispulse-2.4.3.dev0 → erispulse-2.4.3.dev1}/src/ErisPulse/sdk.pyi +6 -1
- erispulse-2.4.3.dev0/src/ErisPulse/Core/Bases/__init__.py +0 -14
- erispulse-2.4.3.dev0/src/ErisPulse/Core/__init__.py +0 -47
- erispulse-2.4.3.dev0/src/ErisPulse/Core/storage.pyi +0 -225
- {erispulse-2.4.3.dev0 → erispulse-2.4.3.dev1}/LICENSE +0 -0
- {erispulse-2.4.3.dev0 → erispulse-2.4.3.dev1}/src/ErisPulse/CLI/__init__.py +0 -0
- {erispulse-2.4.3.dev0 → erispulse-2.4.3.dev1}/src/ErisPulse/CLI/__init__.pyi +0 -0
- {erispulse-2.4.3.dev0 → erispulse-2.4.3.dev1}/src/ErisPulse/CLI/base.py +0 -0
- {erispulse-2.4.3.dev0 → erispulse-2.4.3.dev1}/src/ErisPulse/CLI/base.pyi +0 -0
- {erispulse-2.4.3.dev0 → erispulse-2.4.3.dev1}/src/ErisPulse/CLI/cli.py +0 -0
- {erispulse-2.4.3.dev0 → erispulse-2.4.3.dev1}/src/ErisPulse/CLI/cli.pyi +0 -0
- {erispulse-2.4.3.dev0 → erispulse-2.4.3.dev1}/src/ErisPulse/CLI/commands/__init__.py +0 -0
- {erispulse-2.4.3.dev0 → erispulse-2.4.3.dev1}/src/ErisPulse/CLI/commands/__init__.pyi +0 -0
- {erispulse-2.4.3.dev0 → erispulse-2.4.3.dev1}/src/ErisPulse/CLI/commands/init.py +0 -0
- {erispulse-2.4.3.dev0 → erispulse-2.4.3.dev1}/src/ErisPulse/CLI/commands/init.pyi +0 -0
- {erispulse-2.4.3.dev0 → erispulse-2.4.3.dev1}/src/ErisPulse/CLI/commands/install.py +0 -0
- {erispulse-2.4.3.dev0 → erispulse-2.4.3.dev1}/src/ErisPulse/CLI/commands/install.pyi +0 -0
- {erispulse-2.4.3.dev0 → erispulse-2.4.3.dev1}/src/ErisPulse/CLI/commands/list.py +0 -0
- {erispulse-2.4.3.dev0 → erispulse-2.4.3.dev1}/src/ErisPulse/CLI/commands/list.pyi +0 -0
- {erispulse-2.4.3.dev0 → erispulse-2.4.3.dev1}/src/ErisPulse/CLI/commands/list_remote.py +0 -0
- {erispulse-2.4.3.dev0 → erispulse-2.4.3.dev1}/src/ErisPulse/CLI/commands/list_remote.pyi +0 -0
- {erispulse-2.4.3.dev0 → erispulse-2.4.3.dev1}/src/ErisPulse/CLI/commands/run.py +0 -0
- {erispulse-2.4.3.dev0 → erispulse-2.4.3.dev1}/src/ErisPulse/CLI/commands/run.pyi +0 -0
- {erispulse-2.4.3.dev0 → erispulse-2.4.3.dev1}/src/ErisPulse/CLI/commands/self_update.py +0 -0
- {erispulse-2.4.3.dev0 → erispulse-2.4.3.dev1}/src/ErisPulse/CLI/commands/self_update.pyi +0 -0
- {erispulse-2.4.3.dev0 → erispulse-2.4.3.dev1}/src/ErisPulse/CLI/commands/uninstall.py +0 -0
- {erispulse-2.4.3.dev0 → erispulse-2.4.3.dev1}/src/ErisPulse/CLI/commands/uninstall.pyi +0 -0
- {erispulse-2.4.3.dev0 → erispulse-2.4.3.dev1}/src/ErisPulse/CLI/commands/upgrade.py +0 -0
- {erispulse-2.4.3.dev0 → erispulse-2.4.3.dev1}/src/ErisPulse/CLI/commands/upgrade.pyi +0 -0
- {erispulse-2.4.3.dev0 → erispulse-2.4.3.dev1}/src/ErisPulse/CLI/console.py +0 -0
- {erispulse-2.4.3.dev0 → erispulse-2.4.3.dev1}/src/ErisPulse/CLI/console.pyi +0 -0
- {erispulse-2.4.3.dev0 → erispulse-2.4.3.dev1}/src/ErisPulse/CLI/registry.py +0 -0
- {erispulse-2.4.3.dev0 → erispulse-2.4.3.dev1}/src/ErisPulse/CLI/registry.pyi +0 -0
- {erispulse-2.4.3.dev0 → erispulse-2.4.3.dev1}/src/ErisPulse/CLI/utils/__init__.py +0 -0
- {erispulse-2.4.3.dev0 → erispulse-2.4.3.dev1}/src/ErisPulse/CLI/utils/__init__.pyi +0 -0
- {erispulse-2.4.3.dev0 → erispulse-2.4.3.dev1}/src/ErisPulse/CLI/utils/package_manager.py +0 -0
- {erispulse-2.4.3.dev0 → erispulse-2.4.3.dev1}/src/ErisPulse/CLI/utils/package_manager.pyi +0 -0
- {erispulse-2.4.3.dev0 → erispulse-2.4.3.dev1}/src/ErisPulse/Core/Bases/adapter.py +0 -0
- {erispulse-2.4.3.dev0 → erispulse-2.4.3.dev1}/src/ErisPulse/Core/Bases/adapter.pyi +0 -0
- {erispulse-2.4.3.dev0 → erispulse-2.4.3.dev1}/src/ErisPulse/Core/Bases/manager.py +0 -0
- {erispulse-2.4.3.dev0 → erispulse-2.4.3.dev1}/src/ErisPulse/Core/Bases/manager.pyi +0 -0
- {erispulse-2.4.3.dev0 → erispulse-2.4.3.dev1}/src/ErisPulse/Core/Bases/module.py +0 -0
- {erispulse-2.4.3.dev0 → erispulse-2.4.3.dev1}/src/ErisPulse/Core/Bases/module.pyi +0 -0
- {erispulse-2.4.3.dev0 → erispulse-2.4.3.dev1}/src/ErisPulse/Core/Event/__init__.py +0 -0
- {erispulse-2.4.3.dev0 → erispulse-2.4.3.dev1}/src/ErisPulse/Core/Event/__init__.pyi +0 -0
- {erispulse-2.4.3.dev0 → erispulse-2.4.3.dev1}/src/ErisPulse/Core/Event/base.py +0 -0
- {erispulse-2.4.3.dev0 → erispulse-2.4.3.dev1}/src/ErisPulse/Core/Event/base.pyi +0 -0
- {erispulse-2.4.3.dev0 → erispulse-2.4.3.dev1}/src/ErisPulse/Core/Event/command.py +0 -0
- {erispulse-2.4.3.dev0 → erispulse-2.4.3.dev1}/src/ErisPulse/Core/Event/command.pyi +0 -0
- {erispulse-2.4.3.dev0 → erispulse-2.4.3.dev1}/src/ErisPulse/Core/Event/message.py +0 -0
- {erispulse-2.4.3.dev0 → erispulse-2.4.3.dev1}/src/ErisPulse/Core/Event/message.pyi +0 -0
- {erispulse-2.4.3.dev0 → erispulse-2.4.3.dev1}/src/ErisPulse/Core/Event/message_builder.py +0 -0
- {erispulse-2.4.3.dev0 → erispulse-2.4.3.dev1}/src/ErisPulse/Core/Event/message_builder.pyi +0 -0
- {erispulse-2.4.3.dev0 → erispulse-2.4.3.dev1}/src/ErisPulse/Core/Event/meta.py +0 -0
- {erispulse-2.4.3.dev0 → erispulse-2.4.3.dev1}/src/ErisPulse/Core/Event/meta.pyi +0 -0
- {erispulse-2.4.3.dev0 → erispulse-2.4.3.dev1}/src/ErisPulse/Core/Event/notice.py +0 -0
- {erispulse-2.4.3.dev0 → erispulse-2.4.3.dev1}/src/ErisPulse/Core/Event/notice.pyi +0 -0
- {erispulse-2.4.3.dev0 → erispulse-2.4.3.dev1}/src/ErisPulse/Core/Event/request.py +0 -0
- {erispulse-2.4.3.dev0 → erispulse-2.4.3.dev1}/src/ErisPulse/Core/Event/request.pyi +0 -0
- {erispulse-2.4.3.dev0 → erispulse-2.4.3.dev1}/src/ErisPulse/Core/Event/session_type.py +0 -0
- {erispulse-2.4.3.dev0 → erispulse-2.4.3.dev1}/src/ErisPulse/Core/Event/session_type.pyi +0 -0
- {erispulse-2.4.3.dev0 → erispulse-2.4.3.dev1}/src/ErisPulse/Core/Event/wrapper.py +0 -0
- {erispulse-2.4.3.dev0 → erispulse-2.4.3.dev1}/src/ErisPulse/Core/Event/wrapper.pyi +0 -0
- {erispulse-2.4.3.dev0 → erispulse-2.4.3.dev1}/src/ErisPulse/Core/lifecycle.py +0 -0
- {erispulse-2.4.3.dev0 → erispulse-2.4.3.dev1}/src/ErisPulse/Core/lifecycle.pyi +0 -0
- {erispulse-2.4.3.dev0 → erispulse-2.4.3.dev1}/src/ErisPulse/Core/logger.py +0 -0
- {erispulse-2.4.3.dev0 → erispulse-2.4.3.dev1}/src/ErisPulse/Core/logger.pyi +0 -0
- {erispulse-2.4.3.dev0 → erispulse-2.4.3.dev1}/src/ErisPulse/Core/module.py +0 -0
- {erispulse-2.4.3.dev0 → erispulse-2.4.3.dev1}/src/ErisPulse/Core/module.pyi +0 -0
- {erispulse-2.4.3.dev0 → erispulse-2.4.3.dev1}/src/ErisPulse/Core/router.pyi +0 -0
- {erispulse-2.4.3.dev0 → erispulse-2.4.3.dev1}/src/ErisPulse/__init__.py +0 -0
- {erispulse-2.4.3.dev0 → erispulse-2.4.3.dev1}/src/ErisPulse/__init__.pyi +0 -0
- {erispulse-2.4.3.dev0 → erispulse-2.4.3.dev1}/src/ErisPulse/__main__.py +0 -0
- {erispulse-2.4.3.dev0 → erispulse-2.4.3.dev1}/src/ErisPulse/__main__.pyi +0 -0
- {erispulse-2.4.3.dev0 → erispulse-2.4.3.dev1}/src/ErisPulse/finders/__init__.py +0 -0
- {erispulse-2.4.3.dev0 → erispulse-2.4.3.dev1}/src/ErisPulse/finders/__init__.pyi +0 -0
- {erispulse-2.4.3.dev0 → erispulse-2.4.3.dev1}/src/ErisPulse/finders/adapter.py +0 -0
- {erispulse-2.4.3.dev0 → erispulse-2.4.3.dev1}/src/ErisPulse/finders/adapter.pyi +0 -0
- {erispulse-2.4.3.dev0 → erispulse-2.4.3.dev1}/src/ErisPulse/finders/bases/__init__.py +0 -0
- {erispulse-2.4.3.dev0 → erispulse-2.4.3.dev1}/src/ErisPulse/finders/bases/__init__.pyi +0 -0
- {erispulse-2.4.3.dev0 → erispulse-2.4.3.dev1}/src/ErisPulse/finders/bases/finder.pyi +0 -0
- {erispulse-2.4.3.dev0 → erispulse-2.4.3.dev1}/src/ErisPulse/finders/module.py +0 -0
- {erispulse-2.4.3.dev0 → erispulse-2.4.3.dev1}/src/ErisPulse/finders/module.pyi +0 -0
- {erispulse-2.4.3.dev0 → erispulse-2.4.3.dev1}/src/ErisPulse/loaders/__init__.py +0 -0
- {erispulse-2.4.3.dev0 → erispulse-2.4.3.dev1}/src/ErisPulse/loaders/__init__.pyi +0 -0
- {erispulse-2.4.3.dev0 → erispulse-2.4.3.dev1}/src/ErisPulse/loaders/adapter.pyi +0 -0
- {erispulse-2.4.3.dev0 → erispulse-2.4.3.dev1}/src/ErisPulse/loaders/bases/__init__.py +0 -0
- {erispulse-2.4.3.dev0 → erispulse-2.4.3.dev1}/src/ErisPulse/loaders/bases/__init__.pyi +0 -0
- {erispulse-2.4.3.dev0 → erispulse-2.4.3.dev1}/src/ErisPulse/loaders/bases/loader.py +0 -0
- {erispulse-2.4.3.dev0 → erispulse-2.4.3.dev1}/src/ErisPulse/loaders/bases/loader.pyi +0 -0
- {erispulse-2.4.3.dev0 → erispulse-2.4.3.dev1}/src/ErisPulse/loaders/module.pyi +0 -0
- {erispulse-2.4.3.dev0 → erispulse-2.4.3.dev1}/src/ErisPulse/loaders/strategy.py +0 -0
- {erispulse-2.4.3.dev0 → erispulse-2.4.3.dev1}/src/ErisPulse/loaders/strategy.pyi +0 -0
- {erispulse-2.4.3.dev0 → erispulse-2.4.3.dev1}/src/ErisPulse/runtime/__init__.py +0 -0
- {erispulse-2.4.3.dev0 → erispulse-2.4.3.dev1}/src/ErisPulse/runtime/__init__.pyi +0 -0
- {erispulse-2.4.3.dev0 → erispulse-2.4.3.dev1}/src/ErisPulse/runtime/exceptions.py +0 -0
- {erispulse-2.4.3.dev0 → erispulse-2.4.3.dev1}/src/ErisPulse/runtime/exceptions.pyi +0 -0
- {erispulse-2.4.3.dev0 → erispulse-2.4.3.dev1}/src/ErisPulse/runtime/frame_config.pyi +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ErisPulse
|
|
3
|
-
Version: 2.4.3.
|
|
3
|
+
Version: 2.4.3.dev1
|
|
4
4
|
Summary: ErisPulse 是一个模块化、可扩展的异步 Python SDK 框架,主要用于构建高效、可维护的机器人应用程序。
|
|
5
5
|
Author-email: ErisDev <erisdev@88.com>
|
|
6
6
|
Maintainer-email: "艾莉丝·格雷拉特(WSu2059)" <wsu2059@qq.com>
|
|
@@ -48,7 +48,7 @@ Requires-Dist: packaging>=25.0
|
|
|
48
48
|
Requires-Dist: pip>=23.0
|
|
49
49
|
Requires-Dist: rich>=13.0.0
|
|
50
50
|
Requires-Dist: toml>=0.10.2
|
|
51
|
-
Requires-Dist: uvicorn>=0.30.0
|
|
51
|
+
Requires-Dist: uvicorn[standard]>=0.30.0
|
|
52
52
|
Requires-Dist: watchdog>=4.0.0
|
|
53
53
|
Provides-Extra: dev
|
|
54
54
|
Requires-Dist: httpx>=0.24.0; extra == 'dev'
|
|
@@ -88,11 +88,10 @@ Description-Content-Type: text/markdown
|
|
|
88
88
|
**事件驱动的多平台机器人开发框架**
|
|
89
89
|
|
|
90
90
|
[](https://pypi.org/project/ErisPulse/)
|
|
91
|
-
[](https://hub.docker.com/r/erispulse/erispulse)
|
|
94
|
-
[](https://hub.docker.com/r/erispulse/erispulse)
|
|
91
|
+
[](https://hub.docker.com/r/erispulse/erispulse)
|
|
92
|
+
[](https://pypi.org/project/ErisPulse/)
|
|
95
93
|
[](https://github.com/astral-sh/ruff)
|
|
94
|
+
[](https://socket.dev/pypi/package/ErisPulse)
|
|
96
95
|
|
|
97
96
|
</td>
|
|
98
97
|
</tr>
|
|
@@ -118,16 +117,16 @@ ErisPulse 是一个基于 Python 的事件驱动型多平台机器人开发框
|
|
|
118
117
|
|
|
119
118
|
| 适配器 | 说明 |
|
|
120
119
|
|--------|------|
|
|
121
|
-
|
|
|
122
|
-
|
|
|
123
|
-
|
|
|
124
|
-
|
|
|
125
|
-
|
|
|
126
|
-
|
|
|
127
|
-
|
|
|
128
|
-
|
|
|
129
|
-
|
|
|
130
|
-
|
|
|
120
|
+
| [Kook](https://github.com/shanfishapp/ErisPulse-KookAdapter) | Kook(开黑啦)即时通讯平台 |
|
|
121
|
+
| [Matrix](https://github.com/ErisPulse/ErisPulse-MatrixAdapter) | Matrix 去中心化通讯协议 |
|
|
122
|
+
| [OneBot11](https://github.com/ErisPulse/ErisPulse-OneBot11Adapter) | OneBot v11 通用机器人协议 |
|
|
123
|
+
| [OneBot12](https://github.com/ErisPulse/ErisPulse-OneBot12Adapter) | OneBot v12 标准协议 |
|
|
124
|
+
| [QQ](https://github.com/ErisPulse/ErisPulse-QQBotAdapter) | QQ 官方机器人平台 |
|
|
125
|
+
| [沙箱](https://github.com/ErisPulse/ErisPulse-SandboxAdapter) | 网页端调试,无需接入真实平台 |
|
|
126
|
+
| [Telegram](https://github.com/ErisPulse/ErisPulse-TelegramAdapter) | 全球性即时通讯平台 |
|
|
127
|
+
| [邮件](https://github.com/ErisPulse/ErisPulse-EmailAdapter) | 邮件协议收发适配器 |
|
|
128
|
+
| [云湖](https://github.com/ErisPulse/ErisPulse-YunhuAdapter) | 企业级即时通讯平台(机器人接入) |
|
|
129
|
+
| [云湖用户](https://github.com/wsu2059q/ErisPulse-YunhuUserAdapter) | 基于云湖用户协议的接入适配器 |
|
|
131
130
|
|
|
132
131
|
查看 [适配器详情介绍](docs/zh-CN/platform-guide/README.md)
|
|
133
132
|
|
|
@@ -139,6 +138,22 @@ ErisPulse 是一个基于 Python 的事件驱动型多平台机器人开发框
|
|
|
139
138
|
docker pull erispulse/erispulse:latest
|
|
140
139
|
```
|
|
141
140
|
|
|
141
|
+
<details>
|
|
142
|
+
<summary>Docker Hub不可用?</summary>
|
|
143
|
+
|
|
144
|
+
如果 Docker Hub 无法访问,可以使用 GitHub Container Registry:
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
docker pull ghcr.io/erispulse/erispulse:latest
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
使用 ghcr.io 镜像时,需要修改 `docker-compose.yml` 中的 image:
|
|
151
|
+
```yaml
|
|
152
|
+
image: ghcr.io/erispulse/erispulse:latest
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
</details>
|
|
156
|
+
|
|
142
157
|
<details>
|
|
143
158
|
<summary>快速启动</summary>
|
|
144
159
|
|
|
@@ -152,9 +167,9 @@ ERISPULSE_DASHBOARD_TOKEN=your-token docker compose up -d
|
|
|
152
167
|
|
|
153
168
|
> 镜像内置 ErisPulse 框架和 Dashboard 管理面板,支持 `linux/amd64` 和 `linux/arm64` 架构。
|
|
154
169
|
|
|
155
|
-
|
|
170
|
+
启动后访问 `http://<host>:<port>/Dashboard`,使用设置的令牌作为密码登录 Dashboard 管理面板。
|
|
156
171
|
|
|
157
|
-
|
|
172
|
+
</details>
|
|
158
173
|
|
|
159
174
|
### 使用 pip 安装
|
|
160
175
|
|
|
@@ -16,11 +16,10 @@
|
|
|
16
16
|
**事件驱动的多平台机器人开发框架**
|
|
17
17
|
|
|
18
18
|
[](https://pypi.org/project/ErisPulse/)
|
|
19
|
-
[](https://hub.docker.com/r/erispulse/erispulse)
|
|
22
|
-
[](https://hub.docker.com/r/erispulse/erispulse)
|
|
19
|
+
[](https://hub.docker.com/r/erispulse/erispulse)
|
|
20
|
+
[](https://pypi.org/project/ErisPulse/)
|
|
23
21
|
[](https://github.com/astral-sh/ruff)
|
|
22
|
+
[](https://socket.dev/pypi/package/ErisPulse)
|
|
24
23
|
|
|
25
24
|
</td>
|
|
26
25
|
</tr>
|
|
@@ -46,16 +45,16 @@ ErisPulse 是一个基于 Python 的事件驱动型多平台机器人开发框
|
|
|
46
45
|
|
|
47
46
|
| 适配器 | 说明 |
|
|
48
47
|
|--------|------|
|
|
49
|
-
|
|
|
50
|
-
|
|
|
51
|
-
|
|
|
52
|
-
|
|
|
53
|
-
|
|
|
54
|
-
|
|
|
55
|
-
|
|
|
56
|
-
|
|
|
57
|
-
|
|
|
58
|
-
|
|
|
48
|
+
| [Kook](https://github.com/shanfishapp/ErisPulse-KookAdapter) | Kook(开黑啦)即时通讯平台 |
|
|
49
|
+
| [Matrix](https://github.com/ErisPulse/ErisPulse-MatrixAdapter) | Matrix 去中心化通讯协议 |
|
|
50
|
+
| [OneBot11](https://github.com/ErisPulse/ErisPulse-OneBot11Adapter) | OneBot v11 通用机器人协议 |
|
|
51
|
+
| [OneBot12](https://github.com/ErisPulse/ErisPulse-OneBot12Adapter) | OneBot v12 标准协议 |
|
|
52
|
+
| [QQ](https://github.com/ErisPulse/ErisPulse-QQBotAdapter) | QQ 官方机器人平台 |
|
|
53
|
+
| [沙箱](https://github.com/ErisPulse/ErisPulse-SandboxAdapter) | 网页端调试,无需接入真实平台 |
|
|
54
|
+
| [Telegram](https://github.com/ErisPulse/ErisPulse-TelegramAdapter) | 全球性即时通讯平台 |
|
|
55
|
+
| [邮件](https://github.com/ErisPulse/ErisPulse-EmailAdapter) | 邮件协议收发适配器 |
|
|
56
|
+
| [云湖](https://github.com/ErisPulse/ErisPulse-YunhuAdapter) | 企业级即时通讯平台(机器人接入) |
|
|
57
|
+
| [云湖用户](https://github.com/wsu2059q/ErisPulse-YunhuUserAdapter) | 基于云湖用户协议的接入适配器 |
|
|
59
58
|
|
|
60
59
|
查看 [适配器详情介绍](docs/zh-CN/platform-guide/README.md)
|
|
61
60
|
|
|
@@ -67,6 +66,22 @@ ErisPulse 是一个基于 Python 的事件驱动型多平台机器人开发框
|
|
|
67
66
|
docker pull erispulse/erispulse:latest
|
|
68
67
|
```
|
|
69
68
|
|
|
69
|
+
<details>
|
|
70
|
+
<summary>Docker Hub不可用?</summary>
|
|
71
|
+
|
|
72
|
+
如果 Docker Hub 无法访问,可以使用 GitHub Container Registry:
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
docker pull ghcr.io/erispulse/erispulse:latest
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
使用 ghcr.io 镜像时,需要修改 `docker-compose.yml` 中的 image:
|
|
79
|
+
```yaml
|
|
80
|
+
image: ghcr.io/erispulse/erispulse:latest
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
</details>
|
|
84
|
+
|
|
70
85
|
<details>
|
|
71
86
|
<summary>快速启动</summary>
|
|
72
87
|
|
|
@@ -80,9 +95,9 @@ ERISPULSE_DASHBOARD_TOKEN=your-token docker compose up -d
|
|
|
80
95
|
|
|
81
96
|
> 镜像内置 ErisPulse 框架和 Dashboard 管理面板,支持 `linux/amd64` 和 `linux/arm64` 架构。
|
|
82
97
|
|
|
83
|
-
|
|
98
|
+
启动后访问 `http://<host>:<port>/Dashboard`,使用设置的令牌作为密码登录 Dashboard 管理面板。
|
|
84
99
|
|
|
85
|
-
|
|
100
|
+
</details>
|
|
86
101
|
|
|
87
102
|
### 使用 pip 安装
|
|
88
103
|
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "ErisPulse"
|
|
7
|
-
version = "2.4.3-dev.
|
|
7
|
+
version = "2.4.3-dev.1"
|
|
8
8
|
description = "ErisPulse 是一个模块化、可扩展的异步 Python SDK 框架,主要用于构建高效、可维护的机器人应用程序。"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -55,7 +55,7 @@ dependencies = [
|
|
|
55
55
|
"watchdog>=4.0.0",
|
|
56
56
|
"toml>=0.10.2",
|
|
57
57
|
"fastapi>=0.116.1",
|
|
58
|
-
"uvicorn>=0.30.0",
|
|
58
|
+
"uvicorn[standard]>=0.30.0",
|
|
59
59
|
"packaging>=25.0",
|
|
60
60
|
]
|
|
61
61
|
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""
|
|
2
|
+
ErisPulse 基础模块
|
|
3
|
+
|
|
4
|
+
提供平台适配器、模块和存储后端的基类
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from .adapter import SendDSL, BaseAdapter
|
|
8
|
+
from .module import BaseModule
|
|
9
|
+
from .storage import BaseStorage, BaseQueryBuilder
|
|
10
|
+
|
|
11
|
+
__all__ = [
|
|
12
|
+
"BaseAdapter",
|
|
13
|
+
"SendDSL",
|
|
14
|
+
"BaseModule",
|
|
15
|
+
"BaseStorage",
|
|
16
|
+
"BaseQueryBuilder",
|
|
17
|
+
]
|
|
@@ -0,0 +1,438 @@
|
|
|
1
|
+
"""
|
|
2
|
+
ErisPulse 存储基类
|
|
3
|
+
|
|
4
|
+
提供存储后端和查询构建器的抽象接口,支持不同存储介质的统一访问
|
|
5
|
+
|
|
6
|
+
{!--< tips >!--}
|
|
7
|
+
1. BaseStorage 定义了键值存储和表管理的标准接口
|
|
8
|
+
2. BaseQueryBuilder 定义了链式查询构建的标准接口
|
|
9
|
+
3. 具体存储后端(如 SQLite、Redis、MySQL)需继承并实现这些接口
|
|
10
|
+
{!--< /tips >!--}
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from abc import ABC, abstractmethod
|
|
14
|
+
from typing import Any
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class BaseQueryBuilder(ABC):
|
|
18
|
+
"""
|
|
19
|
+
查询构建器抽象基类
|
|
20
|
+
|
|
21
|
+
定义链式调用风格的查询构建接口,所有链式方法返回 self,
|
|
22
|
+
终止方法(Execute、ExecuteOne、Count、Exists)返回实际结果。
|
|
23
|
+
|
|
24
|
+
{!--< tips >!--}
|
|
25
|
+
使用方式:
|
|
26
|
+
1. storage.Table("users").Insert({"name": "Alice"}).Execute()
|
|
27
|
+
2. storage.Table("users").Select("name").Where("age > ?", 18).Limit(10).Execute()
|
|
28
|
+
{!--< /tips >!--}
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
def __init__(self, storage: "BaseStorage", table: str):
|
|
32
|
+
self._storage = storage
|
|
33
|
+
self._table = table
|
|
34
|
+
self._operation: str | None = None
|
|
35
|
+
self._columns: list[str] = []
|
|
36
|
+
self._data: dict[str, Any] | list[dict[str, Any]] | None = None
|
|
37
|
+
self._where_clauses: list[str] = []
|
|
38
|
+
self._where_params: list[Any] = []
|
|
39
|
+
self._order_by: list[tuple[str, bool]] = []
|
|
40
|
+
self._limit: int | None = None
|
|
41
|
+
self._offset: int | None = None
|
|
42
|
+
|
|
43
|
+
def Select(self, *columns: str) -> "BaseQueryBuilder":
|
|
44
|
+
"""
|
|
45
|
+
指定查询列
|
|
46
|
+
|
|
47
|
+
:param columns: 列名列表,为空时表示 SELECT *
|
|
48
|
+
:return: self
|
|
49
|
+
|
|
50
|
+
:example:
|
|
51
|
+
>>> storage.Table("users").Select("name", "age").Execute()
|
|
52
|
+
"""
|
|
53
|
+
self._operation = "select"
|
|
54
|
+
self._columns = list(columns)
|
|
55
|
+
return self
|
|
56
|
+
|
|
57
|
+
def Insert(self, data: dict[str, Any]) -> "BaseQueryBuilder":
|
|
58
|
+
"""
|
|
59
|
+
插入一行数据
|
|
60
|
+
|
|
61
|
+
:param data: 列名到值的映射
|
|
62
|
+
:return: self
|
|
63
|
+
|
|
64
|
+
:example:
|
|
65
|
+
>>> storage.Table("users").Insert({"name": "Alice", "age": 30}).Execute()
|
|
66
|
+
"""
|
|
67
|
+
self._operation = "insert"
|
|
68
|
+
self._data = data
|
|
69
|
+
return self
|
|
70
|
+
|
|
71
|
+
def InsertMulti(self, data: list[dict[str, Any]]) -> "BaseQueryBuilder":
|
|
72
|
+
"""
|
|
73
|
+
批量插入多行数据
|
|
74
|
+
|
|
75
|
+
:param data: 列名到值的映射列表
|
|
76
|
+
:return: self
|
|
77
|
+
|
|
78
|
+
:example:
|
|
79
|
+
>>> storage.Table("users").InsertMulti([
|
|
80
|
+
... {"name": "Alice", "age": 30},
|
|
81
|
+
... {"name": "Bob", "age": 25}
|
|
82
|
+
... ]).Execute()
|
|
83
|
+
"""
|
|
84
|
+
self._operation = "insert_multi"
|
|
85
|
+
self._data = data
|
|
86
|
+
return self
|
|
87
|
+
|
|
88
|
+
def Update(self, data: dict[str, Any]) -> "BaseQueryBuilder":
|
|
89
|
+
"""
|
|
90
|
+
更新数据
|
|
91
|
+
|
|
92
|
+
:param data: 列名到新值的映射
|
|
93
|
+
:return: self
|
|
94
|
+
|
|
95
|
+
:example:
|
|
96
|
+
>>> storage.Table("users").Update({"age": 31}).Where("name = ?", "Alice").Execute()
|
|
97
|
+
"""
|
|
98
|
+
self._operation = "update"
|
|
99
|
+
self._data = data
|
|
100
|
+
return self
|
|
101
|
+
|
|
102
|
+
def Delete(self) -> "BaseQueryBuilder":
|
|
103
|
+
"""
|
|
104
|
+
删除行
|
|
105
|
+
|
|
106
|
+
:return: self
|
|
107
|
+
|
|
108
|
+
:example:
|
|
109
|
+
>>> storage.Table("users").Delete().Where("name = ?", "Bob").Execute()
|
|
110
|
+
"""
|
|
111
|
+
self._operation = "delete"
|
|
112
|
+
return self
|
|
113
|
+
|
|
114
|
+
def Where(self, condition: str, *params: Any) -> "BaseQueryBuilder":
|
|
115
|
+
"""
|
|
116
|
+
添加 WHERE 条件
|
|
117
|
+
|
|
118
|
+
多次调用时条件之间以 AND 连接
|
|
119
|
+
|
|
120
|
+
:param condition: 条件表达式(使用占位符,如 "age > ?")
|
|
121
|
+
:param params: 占位符对应的参数值
|
|
122
|
+
:return: self
|
|
123
|
+
|
|
124
|
+
:example:
|
|
125
|
+
>>> storage.Table("users").Where("age > ?", 18).Where("name LIKE ?", "A%").Execute()
|
|
126
|
+
"""
|
|
127
|
+
self._where_clauses.append(condition)
|
|
128
|
+
self._where_params.extend(params)
|
|
129
|
+
return self
|
|
130
|
+
|
|
131
|
+
def OrderBy(self, column: str, desc: bool = False) -> "BaseQueryBuilder":
|
|
132
|
+
"""
|
|
133
|
+
添加排序规则
|
|
134
|
+
|
|
135
|
+
多次调用时按添加顺序组合 ORDER BY
|
|
136
|
+
|
|
137
|
+
:param column: 排序列名
|
|
138
|
+
:param desc: 是否降序(默认升序)
|
|
139
|
+
:return: self
|
|
140
|
+
|
|
141
|
+
:example:
|
|
142
|
+
>>> storage.Table("users").OrderBy("age", desc=True).OrderBy("name").Execute()
|
|
143
|
+
"""
|
|
144
|
+
self._order_by.append((column, desc))
|
|
145
|
+
return self
|
|
146
|
+
|
|
147
|
+
def Limit(self, count: int) -> "BaseQueryBuilder":
|
|
148
|
+
"""
|
|
149
|
+
限制返回条数
|
|
150
|
+
|
|
151
|
+
:param count: 最大返回条数
|
|
152
|
+
:return: self
|
|
153
|
+
|
|
154
|
+
:example:
|
|
155
|
+
>>> storage.Table("users").Limit(10).Execute()
|
|
156
|
+
"""
|
|
157
|
+
self._limit = count
|
|
158
|
+
return self
|
|
159
|
+
|
|
160
|
+
def Offset(self, count: int) -> "BaseQueryBuilder":
|
|
161
|
+
"""
|
|
162
|
+
设置偏移量
|
|
163
|
+
|
|
164
|
+
:param count: 跳过的条数
|
|
165
|
+
:return: self
|
|
166
|
+
|
|
167
|
+
:example:
|
|
168
|
+
>>> storage.Table("users").Limit(10).Offset(20).Execute()
|
|
169
|
+
"""
|
|
170
|
+
self._offset = count
|
|
171
|
+
return self
|
|
172
|
+
|
|
173
|
+
def copy(self) -> "BaseQueryBuilder":
|
|
174
|
+
"""
|
|
175
|
+
深拷贝当前构建器状态
|
|
176
|
+
|
|
177
|
+
:return: 新的构建器实例
|
|
178
|
+
"""
|
|
179
|
+
new = self.__class__(self._storage, self._table)
|
|
180
|
+
new._operation = self._operation
|
|
181
|
+
new._columns = list(self._columns)
|
|
182
|
+
new._data = (
|
|
183
|
+
dict(self._data) if isinstance(self._data, dict)
|
|
184
|
+
else [dict(d) for d in self._data] if isinstance(self._data, list)
|
|
185
|
+
else None
|
|
186
|
+
)
|
|
187
|
+
new._where_clauses = list(self._where_clauses)
|
|
188
|
+
new._where_params = list(self._where_params)
|
|
189
|
+
new._order_by = list(self._order_by)
|
|
190
|
+
new._limit = self._limit
|
|
191
|
+
new._offset = self._offset
|
|
192
|
+
return new
|
|
193
|
+
|
|
194
|
+
def clear(self) -> "BaseQueryBuilder":
|
|
195
|
+
"""
|
|
196
|
+
重置构建器状态
|
|
197
|
+
|
|
198
|
+
:return: self
|
|
199
|
+
"""
|
|
200
|
+
self._operation = None
|
|
201
|
+
self._columns = []
|
|
202
|
+
self._data = None
|
|
203
|
+
self._where_clauses = []
|
|
204
|
+
self._where_params = []
|
|
205
|
+
self._order_by = []
|
|
206
|
+
self._limit = None
|
|
207
|
+
self._offset = None
|
|
208
|
+
return self
|
|
209
|
+
|
|
210
|
+
@abstractmethod
|
|
211
|
+
def Execute(self) -> list[tuple] | int:
|
|
212
|
+
"""
|
|
213
|
+
执行构建的查询
|
|
214
|
+
|
|
215
|
+
- SELECT 返回 list[tuple]
|
|
216
|
+
- INSERT/UPDATE/DELETE 返回受影响行数 int
|
|
217
|
+
|
|
218
|
+
:return: 查询结果或受影响行数
|
|
219
|
+
"""
|
|
220
|
+
...
|
|
221
|
+
|
|
222
|
+
@abstractmethod
|
|
223
|
+
def ExecuteOne(self) -> tuple | None:
|
|
224
|
+
"""
|
|
225
|
+
执行查询并返回单条结果
|
|
226
|
+
|
|
227
|
+
:return: 单行元组或 None
|
|
228
|
+
"""
|
|
229
|
+
...
|
|
230
|
+
|
|
231
|
+
@abstractmethod
|
|
232
|
+
def Count(self) -> int:
|
|
233
|
+
"""
|
|
234
|
+
执行 COUNT 查询
|
|
235
|
+
|
|
236
|
+
:return: 匹配的行数
|
|
237
|
+
"""
|
|
238
|
+
...
|
|
239
|
+
|
|
240
|
+
@abstractmethod
|
|
241
|
+
def Exists(self) -> bool:
|
|
242
|
+
"""
|
|
243
|
+
检查是否存在匹配的记录
|
|
244
|
+
|
|
245
|
+
:return: 是否存在
|
|
246
|
+
"""
|
|
247
|
+
...
|
|
248
|
+
|
|
249
|
+
def __repr__(self) -> str:
|
|
250
|
+
return (
|
|
251
|
+
f"{self.__class__.__name__}"
|
|
252
|
+
f"(table={self._table!r}, op={self._operation!r})"
|
|
253
|
+
)
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
class BaseStorage(ABC):
|
|
257
|
+
"""
|
|
258
|
+
存储后端抽象基类
|
|
259
|
+
|
|
260
|
+
定义键值存储和表管理的统一接口,所有存储后端必须继承并实现此基类。
|
|
261
|
+
|
|
262
|
+
{!--< tips >!--}
|
|
263
|
+
1. 键值操作(get/set/delete)用于简单数据存取
|
|
264
|
+
2. Table/CreateTable/DropTable 用于结构化数据操作
|
|
265
|
+
3. transaction 提供事务支持
|
|
266
|
+
{!--< /tips >!--}
|
|
267
|
+
"""
|
|
268
|
+
|
|
269
|
+
@abstractmethod
|
|
270
|
+
def get(self, key: str, default: Any = None) -> Any:
|
|
271
|
+
"""
|
|
272
|
+
获取存储项的值
|
|
273
|
+
|
|
274
|
+
:param key: 存储项键名
|
|
275
|
+
:param default: 默认值
|
|
276
|
+
:return: 存储项的值
|
|
277
|
+
"""
|
|
278
|
+
...
|
|
279
|
+
|
|
280
|
+
@abstractmethod
|
|
281
|
+
def set(self, key: str, value: Any) -> bool:
|
|
282
|
+
"""
|
|
283
|
+
设置存储项的值
|
|
284
|
+
|
|
285
|
+
:param key: 存储项键名
|
|
286
|
+
:param value: 存储项的值
|
|
287
|
+
:return: 操作是否成功
|
|
288
|
+
"""
|
|
289
|
+
...
|
|
290
|
+
|
|
291
|
+
@abstractmethod
|
|
292
|
+
def delete(self, key: str) -> bool:
|
|
293
|
+
"""
|
|
294
|
+
删除存储项
|
|
295
|
+
|
|
296
|
+
:param key: 存储项键名
|
|
297
|
+
:return: 操作是否成功
|
|
298
|
+
"""
|
|
299
|
+
...
|
|
300
|
+
|
|
301
|
+
@abstractmethod
|
|
302
|
+
def get_all_keys(self) -> list[str]:
|
|
303
|
+
"""
|
|
304
|
+
获取所有存储项的键名
|
|
305
|
+
|
|
306
|
+
:return: 键名列表
|
|
307
|
+
"""
|
|
308
|
+
...
|
|
309
|
+
|
|
310
|
+
@abstractmethod
|
|
311
|
+
def clear(self) -> bool:
|
|
312
|
+
"""
|
|
313
|
+
清空所有存储项
|
|
314
|
+
|
|
315
|
+
:return: 操作是否成功
|
|
316
|
+
"""
|
|
317
|
+
...
|
|
318
|
+
|
|
319
|
+
@abstractmethod
|
|
320
|
+
def transaction(self) -> Any:
|
|
321
|
+
"""
|
|
322
|
+
创建事务上下文
|
|
323
|
+
|
|
324
|
+
:return: 事务上下文管理器
|
|
325
|
+
"""
|
|
326
|
+
...
|
|
327
|
+
|
|
328
|
+
@abstractmethod
|
|
329
|
+
def Table(self, table_name: str) -> BaseQueryBuilder:
|
|
330
|
+
"""
|
|
331
|
+
获取指定表的查询构建器
|
|
332
|
+
|
|
333
|
+
:param table_name: 表名
|
|
334
|
+
:return: 查询构建器实例
|
|
335
|
+
"""
|
|
336
|
+
...
|
|
337
|
+
|
|
338
|
+
@abstractmethod
|
|
339
|
+
def CreateTable(self, table_name: str, columns: dict[str, str]) -> bool:
|
|
340
|
+
"""
|
|
341
|
+
创建表
|
|
342
|
+
|
|
343
|
+
:param table_name: 表名
|
|
344
|
+
:param columns: 列名到类型的映射(如 {"id": "INTEGER PRIMARY KEY", "name": "TEXT"})
|
|
345
|
+
:return: 操作是否成功
|
|
346
|
+
"""
|
|
347
|
+
...
|
|
348
|
+
|
|
349
|
+
@abstractmethod
|
|
350
|
+
def DropTable(self, table_name: str) -> bool:
|
|
351
|
+
"""
|
|
352
|
+
删除表
|
|
353
|
+
|
|
354
|
+
:param table_name: 表名
|
|
355
|
+
:return: 操作是否成功
|
|
356
|
+
"""
|
|
357
|
+
...
|
|
358
|
+
|
|
359
|
+
@abstractmethod
|
|
360
|
+
def HasTable(self, table_name: str) -> bool:
|
|
361
|
+
"""
|
|
362
|
+
检查表是否存在
|
|
363
|
+
|
|
364
|
+
:param table_name: 表名
|
|
365
|
+
:return: 是否存在
|
|
366
|
+
"""
|
|
367
|
+
...
|
|
368
|
+
|
|
369
|
+
def get_multi(self, keys: list[str]) -> dict[str, Any]:
|
|
370
|
+
"""
|
|
371
|
+
批量获取多个存储项的值
|
|
372
|
+
|
|
373
|
+
:param keys: 键名列表
|
|
374
|
+
:return: 键值对字典
|
|
375
|
+
"""
|
|
376
|
+
results = {}
|
|
377
|
+
for key in keys:
|
|
378
|
+
value = self.get(key)
|
|
379
|
+
if value is not None:
|
|
380
|
+
results[key] = value
|
|
381
|
+
return results
|
|
382
|
+
|
|
383
|
+
def set_multi(self, items: dict[str, Any]) -> bool:
|
|
384
|
+
"""
|
|
385
|
+
批量设置多个存储项
|
|
386
|
+
|
|
387
|
+
:param items: 键值对字典
|
|
388
|
+
:return: 操作是否成功
|
|
389
|
+
"""
|
|
390
|
+
for key, value in items.items():
|
|
391
|
+
if not self.set(key, value):
|
|
392
|
+
return False
|
|
393
|
+
return True
|
|
394
|
+
|
|
395
|
+
def delete_multi(self, keys: list[str]) -> bool:
|
|
396
|
+
"""
|
|
397
|
+
批量删除多个存储项
|
|
398
|
+
|
|
399
|
+
:param keys: 键名列表
|
|
400
|
+
:return: 操作是否成功
|
|
401
|
+
"""
|
|
402
|
+
for key in keys:
|
|
403
|
+
if not self.delete(key):
|
|
404
|
+
return False
|
|
405
|
+
return True
|
|
406
|
+
|
|
407
|
+
def keys(self) -> list[str]:
|
|
408
|
+
"""
|
|
409
|
+
获取所有存储项的键名(代理到 get_all_keys)
|
|
410
|
+
|
|
411
|
+
:return: 键名列表
|
|
412
|
+
"""
|
|
413
|
+
return self.get_all_keys()
|
|
414
|
+
|
|
415
|
+
def __getattr__(self, key: str) -> Any:
|
|
416
|
+
if key.startswith("_"):
|
|
417
|
+
raise AttributeError(
|
|
418
|
+
f"'{self.__class__.__name__}' object has no attribute '{key}'"
|
|
419
|
+
)
|
|
420
|
+
value = self.get(key)
|
|
421
|
+
if value is None:
|
|
422
|
+
raise AttributeError(f"存储项 {key} 不存在")
|
|
423
|
+
return value
|
|
424
|
+
|
|
425
|
+
def __setattr__(self, key: str, value: Any) -> None:
|
|
426
|
+
if key.startswith("_"):
|
|
427
|
+
super().__setattr__(key, value)
|
|
428
|
+
return
|
|
429
|
+
try:
|
|
430
|
+
self.set(key, value)
|
|
431
|
+
except Exception:
|
|
432
|
+
super().__setattr__(key, value)
|
|
433
|
+
|
|
434
|
+
|
|
435
|
+
__all__ = [
|
|
436
|
+
"BaseStorage",
|
|
437
|
+
"BaseQueryBuilder",
|
|
438
|
+
]
|