ErisPulse 1.2.9__py3-none-any.whl → 2.1.0__py3-none-any.whl
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/Core/__init__.py +19 -0
- ErisPulse/Core/adapter.py +619 -0
- ErisPulse/Core/env.py +614 -0
- ErisPulse/{logger.py → Core/logger.py} +1 -118
- ErisPulse/Core/mods.py +226 -0
- ErisPulse/Core/raiserr.py +152 -0
- ErisPulse/Core/server.py +276 -0
- ErisPulse/Core/shellprint.py +165 -0
- ErisPulse/Core/util.py +126 -0
- ErisPulse/__init__.py +654 -243
- ErisPulse/__main__.py +322 -1187
- {erispulse-1.2.9.dist-info → erispulse-2.1.0.dist-info}/METADATA +16 -41
- erispulse-2.1.0.dist-info/RECORD +16 -0
- {erispulse-1.2.9.dist-info → erispulse-2.1.0.dist-info}/entry_points.txt +1 -0
- {erispulse-1.2.9.dist-info → erispulse-2.1.0.dist-info}/licenses/LICENSE +4 -3
- ErisPulse/adapter.py +0 -465
- ErisPulse/db.py +0 -769
- ErisPulse/mods.py +0 -345
- ErisPulse/raiserr.py +0 -141
- ErisPulse/util.py +0 -144
- erispulse-1.2.9.dist-info/RECORD +0 -13
- {erispulse-1.2.9.dist-info → erispulse-2.1.0.dist-info}/WHEEL +0 -0
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ErisPulse
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 2.1.0
|
|
4
4
|
Summary: ErisPulse 是一个模块化、可扩展的异步 Python SDK 框架,主要用于构建高效、可维护的机器人应用程序。
|
|
5
5
|
Author-email: "艾莉丝·格雷拉特(WSu2059)" <wsu2059@qq.com>, runoneall <runoobsteve@gmail.com>
|
|
6
6
|
License: MIT License
|
|
7
7
|
|
|
8
8
|
Copyright (c) 2025 ErisPulse
|
|
9
9
|
|
|
10
|
-
Portions of this software are based on https://github.com/
|
|
11
|
-
(Copyright (c) 2025 runoneall).
|
|
10
|
+
Portions of this software are based on https://github.com/runoneall/sdkFrame.
|
|
12
11
|
|
|
13
12
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
14
13
|
of this software and associated documentation files (the "Software"), to deal
|
|
@@ -31,6 +30,8 @@ License: MIT License
|
|
|
31
30
|
The documentation portion of this project references content from https://codeberg.org/ybr/yhwiki,
|
|
32
31
|
licensed under the Creative Commons Attribution-NonCommercial-NoDerivatives 4.0
|
|
33
32
|
International License (CC BY-NC-ND 4.0).
|
|
33
|
+
|
|
34
|
+
Note: This license file has been updated to correct the upstream project reference.
|
|
34
35
|
License-File: LICENSE
|
|
35
36
|
Classifier: Development Status :: 5 - Production/Stable
|
|
36
37
|
Classifier: Intended Audience :: Developers
|
|
@@ -46,7 +47,12 @@ Classifier: Programming Language :: Python :: 3.12
|
|
|
46
47
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
47
48
|
Requires-Python: >=3.8
|
|
48
49
|
Requires-Dist: aiohttp
|
|
50
|
+
Requires-Dist: fastapi>=0.116.1
|
|
51
|
+
Requires-Dist: hypercorn>=0.14.0
|
|
49
52
|
Requires-Dist: pip
|
|
53
|
+
Requires-Dist: pydantic>=2.10.6
|
|
54
|
+
Requires-Dist: python-multipart>=0.0.20
|
|
55
|
+
Requires-Dist: toml
|
|
50
56
|
Requires-Dist: watchdog
|
|
51
57
|
Description-Content-Type: text/markdown
|
|
52
58
|
|
|
@@ -78,50 +84,19 @@ Description-Content-Type: text/markdown
|
|
|
78
84
|
|
|
79
85
|
## 快速开始
|
|
80
86
|
|
|
81
|
-
###
|
|
82
|
-
|
|
83
|
-
| 需求 | 推荐框架 | 理由 |
|
|
84
|
-
|------|---------|------|
|
|
85
|
-
| 轻量化/底层模块化 | [Framer](https://github.com/FramerOrg/Framer) | 高度解耦的模块化设计 |
|
|
86
|
-
| 全功能机器人开发 | ErisPulse | 开箱即用的完整解决方案 |
|
|
87
|
-
|
|
88
|
-
---
|
|
89
|
-
|
|
90
|
-
## 安装指南
|
|
91
|
-
|
|
92
|
-
我们全面采用 [`uv`](https://github.com/astral-sh/uv) 作为 Python 工具链,提供更快速可靠的安装体验。
|
|
93
|
-
|
|
94
|
-
> ℹ️ **uv** 是由 Astral 开发的新一代 Python 包管理工具,比传统 pip 快 10-100 倍,并具有更好的依赖解析能力。
|
|
95
|
-
|
|
96
|
-
### 1. 安装 uv
|
|
97
|
-
|
|
98
|
-
#### 通用方法 (pip):
|
|
99
|
-
```bash
|
|
100
|
-
pip install uv
|
|
101
|
-
```
|
|
87
|
+
### 一键安装脚本
|
|
102
88
|
|
|
103
|
-
|
|
104
|
-
```bash
|
|
105
|
-
curl -LsSf https://astral.sh/uv/install.sh | sh
|
|
106
|
-
```
|
|
89
|
+
我们提供了一键安装脚本,支持所有主流平台:
|
|
107
90
|
|
|
108
91
|
#### Windows (PowerShell):
|
|
109
|
-
```powershell
|
|
110
|
-
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
|
|
111
|
-
```
|
|
112
92
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
uv --version
|
|
93
|
+
```powershell
|
|
94
|
+
irm https://get.erisdev.com/install.ps1 -OutFile install.ps1; powershell -ExecutionPolicy Bypass -File install.ps1
|
|
116
95
|
```
|
|
117
96
|
|
|
118
|
-
|
|
119
|
-
|
|
97
|
+
#### macOS/Linux:
|
|
120
98
|
```bash
|
|
121
|
-
|
|
122
|
-
uv venv # 创建虚拟环境
|
|
123
|
-
source .venv/bin/activate # 激活环境 (Windows: .venv\Scripts\activate)
|
|
124
|
-
uv pip install ErisPulse --upgrade # 安装框架
|
|
99
|
+
curl -sSL https://get.erisdev.com/install.sh | tee install.sh >/dev/null && chmod +x install.sh && ./install.sh
|
|
125
100
|
```
|
|
126
101
|
|
|
127
102
|
---
|
|
@@ -131,7 +106,7 @@ uv pip install ErisPulse --upgrade # 安装框架
|
|
|
131
106
|
### 克隆项目并进入目录
|
|
132
107
|
|
|
133
108
|
```bash
|
|
134
|
-
git clone https://github.com/ErisPulse/ErisPulse.git
|
|
109
|
+
git clone -b Develop/v2 https://github.com/ErisPulse/ErisPulse.git
|
|
135
110
|
cd ErisPulse
|
|
136
111
|
```
|
|
137
112
|
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
ErisPulse/__init__.py,sha256=4jcAocMDQt4xdE9B3di8lGCtrU47Y4DY2ipW_9diYbU,26210
|
|
2
|
+
ErisPulse/__main__.py,sha256=DeMBevz0KyLywnd5HccgZDwVfv-EKQOpo1fsVypKwiE,18525
|
|
3
|
+
ErisPulse/Core/__init__.py,sha256=CIxWFdB6-0D8YJz7IdW052A4YllDsklEONCRj7mOpkQ,362
|
|
4
|
+
ErisPulse/Core/adapter.py,sha256=gzryZjrOy0uowE5oU4MaVTtGPW7HP4StfvP5ECC4rUk,20510
|
|
5
|
+
ErisPulse/Core/env.py,sha256=9WYNadD9h2jP_2wxOVBJEhH1uDzbctW7eB4Ba9RSjA4,20409
|
|
6
|
+
ErisPulse/Core/logger.py,sha256=035II2YvRmcAvbbCEsmMxjvAJhdLvQlUqaDXAufWk_E,5782
|
|
7
|
+
ErisPulse/Core/mods.py,sha256=5SPutuzbMrA-VZwiXeNxYWfrdbpLRdYfQ0RvEkFuQgg,7308
|
|
8
|
+
ErisPulse/Core/raiserr.py,sha256=QLQ3r7p4iFP86XBLq9mtf1wv1xSlgny35i8t5-l4DXo,4620
|
|
9
|
+
ErisPulse/Core/server.py,sha256=H8dUUj8mxBLEd3ick7btbWEE_m58Y6277Zo5FY7Ild4,9217
|
|
10
|
+
ErisPulse/Core/shellprint.py,sha256=-BFoyFho_D3XEhxIoKt6x5gO4C62LKwmJWKDUGiPjNY,5908
|
|
11
|
+
ErisPulse/Core/util.py,sha256=kyydBAJHHG9I7rMRzKWtLAQMZoJyBqHiBAweqcraFkU,4001
|
|
12
|
+
erispulse-2.1.0.dist-info/METADATA,sha256=Ip9gteaaYMG0Qkzxq_DbFnmvCYNC8ULMPxCZ6jYimeA,6161
|
|
13
|
+
erispulse-2.1.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
14
|
+
erispulse-2.1.0.dist-info/entry_points.txt,sha256=Jss71M6nEha0TA-DyVZugPYdcL14s9QpiOeIlgWxzOc,182
|
|
15
|
+
erispulse-2.1.0.dist-info/licenses/LICENSE,sha256=lBYj7Nk4urLvByj4HvQFxu8j9hThhFF6OGfyxAZBP9Q,1451
|
|
16
|
+
erispulse-2.1.0.dist-info/RECORD,,
|
|
@@ -2,8 +2,7 @@ MIT License
|
|
|
2
2
|
|
|
3
3
|
Copyright (c) 2025 ErisPulse
|
|
4
4
|
|
|
5
|
-
Portions of this software are based on https://github.com/
|
|
6
|
-
(Copyright (c) 2025 runoneall).
|
|
5
|
+
Portions of this software are based on https://github.com/runoneall/sdkFrame.
|
|
7
6
|
|
|
8
7
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
9
8
|
of this software and associated documentation files (the "Software"), to deal
|
|
@@ -25,4 +24,6 @@ SOFTWARE.
|
|
|
25
24
|
|
|
26
25
|
The documentation portion of this project references content from https://codeberg.org/ybr/yhwiki,
|
|
27
26
|
licensed under the Creative Commons Attribution-NonCommercial-NoDerivatives 4.0
|
|
28
|
-
International License (CC BY-NC-ND 4.0).
|
|
27
|
+
International License (CC BY-NC-ND 4.0).
|
|
28
|
+
|
|
29
|
+
Note: This license file has been updated to correct the upstream project reference.
|
ErisPulse/adapter.py
DELETED
|
@@ -1,465 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
# 适配器系统
|
|
3
|
-
|
|
4
|
-
提供平台适配器基类、消息发送DSL和适配器管理功能。支持多平台消息处理、事件驱动和生命周期管理。
|
|
5
|
-
|
|
6
|
-
## API 文档
|
|
7
|
-
|
|
8
|
-
### 适配器基类 (BaseAdapter)
|
|
9
|
-
适配器基类提供了与外部平台交互的标准接口。
|
|
10
|
-
|
|
11
|
-
#### call_api(endpoint: str, **params: Any) -> Any
|
|
12
|
-
调用平台API的抽象方法。
|
|
13
|
-
- 参数:
|
|
14
|
-
- endpoint: API端点
|
|
15
|
-
- **params: API参数
|
|
16
|
-
- 返回:
|
|
17
|
-
- Any: API调用结果
|
|
18
|
-
- 说明:
|
|
19
|
-
- 必须由子类实现
|
|
20
|
-
- 处理与平台的实际通信
|
|
21
|
-
- 示例:
|
|
22
|
-
```python
|
|
23
|
-
class MyPlatformAdapter(BaseAdapter):
|
|
24
|
-
async def call_api(self, endpoint: str, **params: Any) -> Any:
|
|
25
|
-
if endpoint == "/send":
|
|
26
|
-
return await self._send_message(params)
|
|
27
|
-
elif endpoint == "/upload":
|
|
28
|
-
return await self._upload_file(params)
|
|
29
|
-
raise NotImplementedError(f"未实现的端点: {endpoint}")
|
|
30
|
-
```
|
|
31
|
-
|
|
32
|
-
#### start() -> None
|
|
33
|
-
启动适配器的抽象方法。
|
|
34
|
-
- 参数: 无
|
|
35
|
-
- 返回:
|
|
36
|
-
- None
|
|
37
|
-
- 说明:
|
|
38
|
-
- 必须由子类实现
|
|
39
|
-
- 处理适配器的初始化和启动逻辑
|
|
40
|
-
- 示例:
|
|
41
|
-
```python
|
|
42
|
-
class MyPlatformAdapter(BaseAdapter):
|
|
43
|
-
async def start(self) -> None:
|
|
44
|
-
self.client = await self._create_client()
|
|
45
|
-
self.ws = await self.client.create_websocket()
|
|
46
|
-
self._start_heartbeat()
|
|
47
|
-
```
|
|
48
|
-
|
|
49
|
-
#### shutdown() -> None
|
|
50
|
-
关闭适配器的抽象方法。
|
|
51
|
-
- 参数: 无
|
|
52
|
-
- 返回:
|
|
53
|
-
- None
|
|
54
|
-
- 说明:
|
|
55
|
-
- 必须由子类实现
|
|
56
|
-
- 处理资源清理和关闭逻辑
|
|
57
|
-
- 示例:
|
|
58
|
-
```python
|
|
59
|
-
class MyPlatformAdapter(BaseAdapter):
|
|
60
|
-
async def shutdown(self) -> None:
|
|
61
|
-
if self.ws:
|
|
62
|
-
await self.ws.close()
|
|
63
|
-
if self.client:
|
|
64
|
-
await self.client.close()
|
|
65
|
-
```
|
|
66
|
-
|
|
67
|
-
#### on(event_type: str = "*") -> Callable[[Callable[..., Any]], Callable[..., Any]]
|
|
68
|
-
事件监听装饰器。
|
|
69
|
-
- 参数:
|
|
70
|
-
- event_type: 事件类型,默认"*"表示所有事件
|
|
71
|
-
- 返回:
|
|
72
|
-
- Callable[[Callable[..., Any]], Callable[..., Any]]: 装饰器函数
|
|
73
|
-
- 示例:
|
|
74
|
-
```python
|
|
75
|
-
adapter = MyPlatformAdapter()
|
|
76
|
-
|
|
77
|
-
@adapter.on("message")
|
|
78
|
-
async def handle_message(data: Any) -> None:
|
|
79
|
-
print(f"收到消息: {data}")
|
|
80
|
-
|
|
81
|
-
@adapter.on("error")
|
|
82
|
-
async def handle_error(error: Exception) -> None:
|
|
83
|
-
print(f"发生错误: {error}")
|
|
84
|
-
|
|
85
|
-
# 处理所有事件
|
|
86
|
-
@adapter.on()
|
|
87
|
-
async def handle_all(event: Any) -> None:
|
|
88
|
-
print(f"事件: {event}")
|
|
89
|
-
```
|
|
90
|
-
|
|
91
|
-
#### emit(event_type: str, data: Any) -> None
|
|
92
|
-
触发事件。
|
|
93
|
-
- 参数:
|
|
94
|
-
- event_type: 事件类型
|
|
95
|
-
- data: 事件数据
|
|
96
|
-
- 返回:
|
|
97
|
-
- None
|
|
98
|
-
- 示例:
|
|
99
|
-
```python
|
|
100
|
-
class MyPlatformAdapter(BaseAdapter):
|
|
101
|
-
async def _handle_websocket_message(self, message: Any) -> None:
|
|
102
|
-
# 处理消息并触发相应事件
|
|
103
|
-
if message.type == "chat":
|
|
104
|
-
await self.emit("message", {
|
|
105
|
-
"type": "chat",
|
|
106
|
-
"content": message.content,
|
|
107
|
-
"sender": message.sender
|
|
108
|
-
})
|
|
109
|
-
```
|
|
110
|
-
|
|
111
|
-
#### middleware(func: Callable[..., Any]) -> Callable[..., Any]
|
|
112
|
-
添加中间件处理器。
|
|
113
|
-
- 参数:
|
|
114
|
-
- func: 中间件函数
|
|
115
|
-
- 返回:
|
|
116
|
-
- Callable[..., Any]: 中间件函数
|
|
117
|
-
- 示例:
|
|
118
|
-
```python
|
|
119
|
-
adapter = MyPlatformAdapter()
|
|
120
|
-
|
|
121
|
-
@adapter.middleware
|
|
122
|
-
async def log_middleware(data: Any) -> Any:
|
|
123
|
-
print(f"处理数据: {data}")
|
|
124
|
-
return data
|
|
125
|
-
|
|
126
|
-
@adapter.middleware
|
|
127
|
-
async def filter_middleware(data: Any) -> Optional[Any]:
|
|
128
|
-
if "spam" in data.get("content", ""):
|
|
129
|
-
return None
|
|
130
|
-
return data
|
|
131
|
-
```
|
|
132
|
-
|
|
133
|
-
### 消息发送DSL (SendDSL)
|
|
134
|
-
提供链式调用风格的消息发送接口。
|
|
135
|
-
|
|
136
|
-
#### To(target_type: Optional[str] = None, target_id: Optional[str] = None) -> 'SendDSL'
|
|
137
|
-
设置消息目标。
|
|
138
|
-
- 参数:
|
|
139
|
-
- target_type: 目标类型(可选)
|
|
140
|
-
- target_id: 目标ID
|
|
141
|
-
- 返回:
|
|
142
|
-
- SendDSL: 发送器实例
|
|
143
|
-
- 示例:
|
|
144
|
-
```python
|
|
145
|
-
# 发送到用户
|
|
146
|
-
sdk.adapter.Platform.Send.To("user", "123").Text("Hello")
|
|
147
|
-
|
|
148
|
-
# 发送到群组
|
|
149
|
-
sdk.adapter.Platform.Send.To("group", "456").Text("Hello Group")
|
|
150
|
-
|
|
151
|
-
# 简化形式(只有ID)
|
|
152
|
-
sdk.adapter.Platform.Send.To("123").Text("Hello")
|
|
153
|
-
```
|
|
154
|
-
|
|
155
|
-
#### Text(text: str) -> asyncio.Task
|
|
156
|
-
发送文本消息。
|
|
157
|
-
- 参数:
|
|
158
|
-
- text: 文本内容
|
|
159
|
-
- 返回:
|
|
160
|
-
- asyncio.Task: 异步任务
|
|
161
|
-
- 示例:
|
|
162
|
-
```python
|
|
163
|
-
# 发送简单文本
|
|
164
|
-
await sdk.adapter.Platform.Send.To("user", "123").Text("Hello")
|
|
165
|
-
|
|
166
|
-
# 发送格式化文本
|
|
167
|
-
name = "Alice"
|
|
168
|
-
await sdk.adapter.Platform.Send.To("123").Text(f"Hello {name}")
|
|
169
|
-
```
|
|
170
|
-
|
|
171
|
-
### 适配器管理 (AdapterManager)
|
|
172
|
-
管理多个平台适配器的注册、启动和关闭。
|
|
173
|
-
|
|
174
|
-
#### register(platform: str, adapter_class: Type[BaseAdapter]) -> bool
|
|
175
|
-
注册新的适配器类。
|
|
176
|
-
- 参数:
|
|
177
|
-
- platform: 平台名称
|
|
178
|
-
- adapter_class: 适配器类
|
|
179
|
-
- 返回:
|
|
180
|
-
- bool: 注册是否成功
|
|
181
|
-
- 示例:
|
|
182
|
-
```python
|
|
183
|
-
# 注册适配器
|
|
184
|
-
sdk.adapter.register("MyPlatform", MyPlatformAdapter)
|
|
185
|
-
|
|
186
|
-
# 注册多个适配器
|
|
187
|
-
adapters = {
|
|
188
|
-
"Platform1": Platform1Adapter,
|
|
189
|
-
"Platform2": Platform2Adapter
|
|
190
|
-
}
|
|
191
|
-
for name, adapter in adapters.items():
|
|
192
|
-
sdk.adapter.register(name, adapter)
|
|
193
|
-
```
|
|
194
|
-
|
|
195
|
-
#### startup(platforms: Optional[List[str]] = None) -> None
|
|
196
|
-
启动指定的适配器。
|
|
197
|
-
- 参数:
|
|
198
|
-
- platforms: 要启动的平台列表,None表示所有平台
|
|
199
|
-
- 返回:
|
|
200
|
-
- None
|
|
201
|
-
- 示例:
|
|
202
|
-
```python
|
|
203
|
-
# 启动所有适配器
|
|
204
|
-
await sdk.adapter.startup()
|
|
205
|
-
|
|
206
|
-
# 启动指定适配器
|
|
207
|
-
await sdk.adapter.startup(["Platform1", "Platform2"])
|
|
208
|
-
```
|
|
209
|
-
|
|
210
|
-
#### shutdown() -> None
|
|
211
|
-
关闭所有适配器。
|
|
212
|
-
- 参数: 无
|
|
213
|
-
- 返回:
|
|
214
|
-
- None
|
|
215
|
-
- 示例:
|
|
216
|
-
```python
|
|
217
|
-
# 关闭所有适配器
|
|
218
|
-
await sdk.adapter.shutdown()
|
|
219
|
-
|
|
220
|
-
# 在程序退出时关闭
|
|
221
|
-
import atexit
|
|
222
|
-
atexit.register(lambda: asyncio.run(sdk.adapter.shutdown()))
|
|
223
|
-
```
|
|
224
|
-
|
|
225
|
-
"""
|
|
226
|
-
|
|
227
|
-
import functools
|
|
228
|
-
import asyncio
|
|
229
|
-
from typing import (
|
|
230
|
-
Callable, Any, Dict, List, Type, Optional, Set,
|
|
231
|
-
Union, Awaitable, TypeVar, Generic, Tuple, Coroutine, FrozenSet
|
|
232
|
-
)
|
|
233
|
-
from collections import defaultdict
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
# DSL 基类,用于实现 Send.To(...).Func(...) 风格
|
|
237
|
-
class SendDSLBase:
|
|
238
|
-
def __init__(self, adapter: 'BaseAdapter', target_type: Optional[str] = None, target_id: Optional[str] = None):
|
|
239
|
-
self._adapter = adapter
|
|
240
|
-
self._target_type = target_type
|
|
241
|
-
self._target_id = target_id
|
|
242
|
-
self._target_to = target_id
|
|
243
|
-
|
|
244
|
-
def To(self, target_type: str = None, target_id: str = None) -> 'SendDSL':
|
|
245
|
-
if target_id is None and target_type is not None:
|
|
246
|
-
target_id = target_type
|
|
247
|
-
target_type = None
|
|
248
|
-
|
|
249
|
-
return self.__class__(self._adapter, target_type, target_id)
|
|
250
|
-
|
|
251
|
-
def __getattr__(self, name: str):
|
|
252
|
-
def wrapper(*args, **kwargs):
|
|
253
|
-
return asyncio.create_task(
|
|
254
|
-
self._adapter._real_send(
|
|
255
|
-
target_type=self._target_type,
|
|
256
|
-
target_id=self._target_id,
|
|
257
|
-
action=name,
|
|
258
|
-
data={
|
|
259
|
-
"args": args,
|
|
260
|
-
"kwargs": kwargs
|
|
261
|
-
}
|
|
262
|
-
)
|
|
263
|
-
)
|
|
264
|
-
return wrapper
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
class BaseAdapter:
|
|
268
|
-
class Send(SendDSLBase):
|
|
269
|
-
def Text(self, text: str):
|
|
270
|
-
"""基础文本消息发送方法,子类应该重写此方法"""
|
|
271
|
-
return asyncio.create_task(
|
|
272
|
-
self._adapter.call_api(
|
|
273
|
-
endpoint="/send",
|
|
274
|
-
content=text,
|
|
275
|
-
recvId=self._target_id,
|
|
276
|
-
recvType=self._target_type
|
|
277
|
-
)
|
|
278
|
-
)
|
|
279
|
-
|
|
280
|
-
def __init__(self):
|
|
281
|
-
self._handlers = defaultdict(list)
|
|
282
|
-
self._middlewares = []
|
|
283
|
-
# 绑定当前适配器的 Send 实例
|
|
284
|
-
self.Send = self.__class__.Send(self)
|
|
285
|
-
|
|
286
|
-
def on(self, event_type: str = "*"):
|
|
287
|
-
def decorator(func: Callable):
|
|
288
|
-
@functools.wraps(func)
|
|
289
|
-
async def wrapper(*args, **kwargs):
|
|
290
|
-
return await func(*args, **kwargs)
|
|
291
|
-
self._handlers[event_type].append(wrapper)
|
|
292
|
-
return wrapper
|
|
293
|
-
return decorator
|
|
294
|
-
|
|
295
|
-
def middleware(self, func: Callable):
|
|
296
|
-
self._middlewares.append(func)
|
|
297
|
-
return func
|
|
298
|
-
|
|
299
|
-
async def call_api(self, endpoint: str, **params):
|
|
300
|
-
raise NotImplementedError
|
|
301
|
-
|
|
302
|
-
async def start(self):
|
|
303
|
-
raise NotImplementedError
|
|
304
|
-
|
|
305
|
-
async def shutdown(self):
|
|
306
|
-
raise NotImplementedError
|
|
307
|
-
|
|
308
|
-
def add_handler(self, *args):
|
|
309
|
-
if len(args) == 1:
|
|
310
|
-
event_type = "*"
|
|
311
|
-
handler = args[0]
|
|
312
|
-
elif len(args) == 2:
|
|
313
|
-
event_type, handler = args
|
|
314
|
-
else:
|
|
315
|
-
raise TypeError("add_handler() 接受 1 个(监听所有事件)或 2 个参数(指定事件类型)")
|
|
316
|
-
|
|
317
|
-
@functools.wraps(handler)
|
|
318
|
-
async def wrapper(*handler_args, **handler_kwargs):
|
|
319
|
-
return await handler(*handler_args, **handler_kwargs)
|
|
320
|
-
|
|
321
|
-
self._handlers[event_type].append(wrapper)
|
|
322
|
-
async def emit(self, event_type: str, data: Any):
|
|
323
|
-
# 先执行中间件
|
|
324
|
-
for middleware in self._middlewares:
|
|
325
|
-
data = await middleware(data)
|
|
326
|
-
|
|
327
|
-
# 触发具体事件类型的处理器
|
|
328
|
-
if event_type in self._handlers:
|
|
329
|
-
for handler in self._handlers[event_type]:
|
|
330
|
-
await handler(data)
|
|
331
|
-
|
|
332
|
-
# 触发通配符 "*" 的处理器
|
|
333
|
-
for handler in self._handlers.get("*", []):
|
|
334
|
-
await handler(data)
|
|
335
|
-
|
|
336
|
-
async def send(self, target_type: str, target_id: str, message: Any, **kwargs):
|
|
337
|
-
method_name = kwargs.pop("method", "Text")
|
|
338
|
-
method = getattr(self.Send.To(target_type, target_id), method_name, None)
|
|
339
|
-
if not method:
|
|
340
|
-
raise AttributeError(f"未找到 {method_name} 方法,请确保已在 Send 类中定义")
|
|
341
|
-
return await method(text=message, **kwargs)
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
class AdapterManager:
|
|
345
|
-
def __init__(self):
|
|
346
|
-
self._adapters: Dict[str, BaseAdapter] = {}
|
|
347
|
-
self._adapter_instances: Dict[Type[BaseAdapter], BaseAdapter] = {}
|
|
348
|
-
self._platform_to_instance: Dict[str, BaseAdapter] = {}
|
|
349
|
-
self._started_instances: Set[BaseAdapter] = set()
|
|
350
|
-
|
|
351
|
-
def register(self, platform: str, adapter_class: Type[BaseAdapter]) -> bool:
|
|
352
|
-
if not issubclass(adapter_class, BaseAdapter):
|
|
353
|
-
raise TypeError("适配器必须继承自BaseAdapter")
|
|
354
|
-
from . import sdk
|
|
355
|
-
|
|
356
|
-
# 如果该类已经创建过实例,复用
|
|
357
|
-
if adapter_class in self._adapter_instances:
|
|
358
|
-
instance = self._adapter_instances[adapter_class]
|
|
359
|
-
else:
|
|
360
|
-
instance = adapter_class(sdk)
|
|
361
|
-
self._adapter_instances[adapter_class] = instance
|
|
362
|
-
|
|
363
|
-
# 注册平台名,并统一映射到该实例
|
|
364
|
-
self._adapters[platform] = instance
|
|
365
|
-
self._platform_to_instance[platform] = instance
|
|
366
|
-
|
|
367
|
-
if len(platform) <= 10:
|
|
368
|
-
from itertools import product
|
|
369
|
-
combinations = [''.join(c) for c in product(*[(ch.lower(), ch.upper()) for ch in platform])]
|
|
370
|
-
for name in set(combinations):
|
|
371
|
-
setattr(self, name, instance)
|
|
372
|
-
else:
|
|
373
|
-
self.logger.warning(f"平台名 {platform} 过长,如果您是开发者,请考虑使用更短的名称")
|
|
374
|
-
setattr(self, platform.lower(), instance)
|
|
375
|
-
setattr(self, platform.upper(), instance)
|
|
376
|
-
setattr(self, platform.capitalize(), instance)
|
|
377
|
-
|
|
378
|
-
return True
|
|
379
|
-
|
|
380
|
-
async def startup(self, platforms: List[str] = None):
|
|
381
|
-
if platforms is None:
|
|
382
|
-
platforms = list(self._adapters.keys())
|
|
383
|
-
|
|
384
|
-
# 已经被调度过的 adapter 实例集合(防止重复调度)
|
|
385
|
-
scheduled_adapters = set()
|
|
386
|
-
|
|
387
|
-
for platform in platforms:
|
|
388
|
-
if platform not in self._adapters:
|
|
389
|
-
raise ValueError(f"平台 {platform} 未注册")
|
|
390
|
-
adapter = self._adapters[platform]
|
|
391
|
-
|
|
392
|
-
# 如果该实例已经被启动或已调度,跳过
|
|
393
|
-
if adapter in self._started_instances or adapter in scheduled_adapters:
|
|
394
|
-
continue
|
|
395
|
-
|
|
396
|
-
# 加入调度队列
|
|
397
|
-
scheduled_adapters.add(adapter)
|
|
398
|
-
asyncio.create_task(self._run_adapter(adapter, platform))
|
|
399
|
-
|
|
400
|
-
async def _run_adapter(self, adapter: BaseAdapter, platform: str):
|
|
401
|
-
from . import sdk
|
|
402
|
-
|
|
403
|
-
# 加锁防止并发启动
|
|
404
|
-
if not getattr(adapter, "_starting_lock", None):
|
|
405
|
-
adapter._starting_lock = asyncio.Lock()
|
|
406
|
-
|
|
407
|
-
async with adapter._starting_lock:
|
|
408
|
-
# 再次确认是否已经被启动
|
|
409
|
-
if adapter in self._started_instances:
|
|
410
|
-
sdk.logger.info(f"适配器 {platform}(实例ID: {id(adapter)})已被其他协程启动,跳过")
|
|
411
|
-
return
|
|
412
|
-
|
|
413
|
-
retry_count = 0
|
|
414
|
-
fixed_delay = 3 * 60 * 60
|
|
415
|
-
backoff_intervals = [60, 10 * 60, 30 * 60, 60 * 60]
|
|
416
|
-
|
|
417
|
-
while True:
|
|
418
|
-
try:
|
|
419
|
-
await adapter.start()
|
|
420
|
-
self._started_instances.add(adapter)
|
|
421
|
-
sdk.logger.info(f"适配器 {platform}(实例ID: {id(adapter)})已启动")
|
|
422
|
-
return
|
|
423
|
-
except Exception as e:
|
|
424
|
-
retry_count += 1
|
|
425
|
-
sdk.logger.error(f"平台 {platform} 启动失败(第{retry_count}次重试): {e}")
|
|
426
|
-
|
|
427
|
-
try:
|
|
428
|
-
await adapter.shutdown()
|
|
429
|
-
except Exception as stop_err:
|
|
430
|
-
sdk.logger.warning(f"停止适配器失败: {stop_err}")
|
|
431
|
-
|
|
432
|
-
# 计算等待时间
|
|
433
|
-
if retry_count <= len(backoff_intervals):
|
|
434
|
-
wait_time = backoff_intervals[retry_count - 1]
|
|
435
|
-
else:
|
|
436
|
-
wait_time = fixed_delay
|
|
437
|
-
|
|
438
|
-
sdk.logger.info(f"将在 {wait_time // 60} 分钟后再次尝试重启 {platform}")
|
|
439
|
-
await asyncio.sleep(wait_time)
|
|
440
|
-
|
|
441
|
-
async def shutdown(self):
|
|
442
|
-
for adapter in self._adapters.values():
|
|
443
|
-
await adapter.shutdown()
|
|
444
|
-
|
|
445
|
-
def get(self, platform: str) -> BaseAdapter:
|
|
446
|
-
platform_lower = platform.lower()
|
|
447
|
-
for registered, instance in self._adapters.items():
|
|
448
|
-
if registered.lower() == platform_lower:
|
|
449
|
-
return instance
|
|
450
|
-
return None
|
|
451
|
-
|
|
452
|
-
def __getattr__(self, platform: str) -> BaseAdapter:
|
|
453
|
-
platform_lower = platform.lower()
|
|
454
|
-
for registered, instance in self._adapters.items():
|
|
455
|
-
if registered.lower() == platform_lower:
|
|
456
|
-
return instance
|
|
457
|
-
raise AttributeError(f"平台 {platform} 的适配器未注册")
|
|
458
|
-
|
|
459
|
-
@property
|
|
460
|
-
def platforms(self) -> list:
|
|
461
|
-
return list(self._adapters.keys())
|
|
462
|
-
|
|
463
|
-
AdapterFather = BaseAdapter
|
|
464
|
-
adapter = AdapterManager()
|
|
465
|
-
SendDSL = SendDSLBase
|