ErisPulse 1.1.12__py3-none-any.whl → 1.1.14__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/__init__.py +201 -112
- ErisPulse/__main__.py +117 -39
- ErisPulse/adapter.py +351 -0
- ErisPulse/db.py +689 -8
- ErisPulse/logger.py +325 -1
- ErisPulse/mods.py +331 -0
- ErisPulse/raiserr.py +159 -1
- ErisPulse/util.py +421 -0
- erispulse-1.1.14.dist-info/METADATA +165 -0
- erispulse-1.1.14.dist-info/RECORD +13 -0
- {erispulse-1.1.12.dist-info → erispulse-1.1.14.dist-info}/WHEEL +1 -2
- erispulse-1.1.12.dist-info/METADATA +0 -79
- erispulse-1.1.12.dist-info/RECORD +0 -14
- erispulse-1.1.12.dist-info/top_level.txt +0 -1
- {erispulse-1.1.12.dist-info → erispulse-1.1.14.dist-info}/entry_points.txt +0 -0
- {erispulse-1.1.12.dist-info → erispulse-1.1.14.dist-info}/licenses/LICENSE +0 -0
ErisPulse/__main__.py
CHANGED
|
@@ -1,3 +1,48 @@
|
|
|
1
|
+
"""
|
|
2
|
+
# CLI 入口
|
|
3
|
+
|
|
4
|
+
提供命令行界面(CLI)用于模块管理、源管理和开发调试。
|
|
5
|
+
|
|
6
|
+
## 主要功能
|
|
7
|
+
- 模块管理: 安装/卸载/启用/禁用
|
|
8
|
+
- 源管理: 添加/删除/更新源
|
|
9
|
+
- 热重载: 开发时自动重启
|
|
10
|
+
- 彩色终端输出
|
|
11
|
+
|
|
12
|
+
## 主要命令
|
|
13
|
+
### 模块管理:
|
|
14
|
+
install: 安装模块
|
|
15
|
+
uninstall: 卸载模块
|
|
16
|
+
enable: 启用模块
|
|
17
|
+
disable: 禁用模块
|
|
18
|
+
list: 列出模块
|
|
19
|
+
update: 更新模块列表
|
|
20
|
+
upgrade: 升级模块
|
|
21
|
+
|
|
22
|
+
### 源管理:
|
|
23
|
+
origin add: 添加源
|
|
24
|
+
origin del: 删除源
|
|
25
|
+
origin list: 列出源
|
|
26
|
+
|
|
27
|
+
### 开发调试:
|
|
28
|
+
run: 运行脚本
|
|
29
|
+
--reload: 启用热重载
|
|
30
|
+
|
|
31
|
+
### 示例用法:
|
|
32
|
+
|
|
33
|
+
```
|
|
34
|
+
# 安装模块
|
|
35
|
+
epsdk install MyModule
|
|
36
|
+
|
|
37
|
+
# 启用热重载
|
|
38
|
+
epsdk run main.py --reload
|
|
39
|
+
|
|
40
|
+
# 管理源
|
|
41
|
+
epsdk origin add https://example.com/map.json
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
"""
|
|
45
|
+
|
|
1
46
|
import argparse
|
|
2
47
|
import os
|
|
3
48
|
import sys
|
|
@@ -9,6 +54,7 @@ import fnmatch
|
|
|
9
54
|
import asyncio
|
|
10
55
|
import subprocess
|
|
11
56
|
import json
|
|
57
|
+
import json
|
|
12
58
|
from .db import env
|
|
13
59
|
from .mods import mods
|
|
14
60
|
from watchdog.observers import Observer
|
|
@@ -37,7 +83,6 @@ class Shell_Printer:
|
|
|
37
83
|
|
|
38
84
|
@classmethod
|
|
39
85
|
def _get_color(cls, level):
|
|
40
|
-
"""根据消息级别返回颜色"""
|
|
41
86
|
return {
|
|
42
87
|
"info": cls.CYAN,
|
|
43
88
|
"success": cls.GREEN,
|
|
@@ -49,77 +94,87 @@ class Shell_Printer:
|
|
|
49
94
|
|
|
50
95
|
@classmethod
|
|
51
96
|
def panel(cls, msg: str, title: str = None, level: str = "info") -> None:
|
|
52
|
-
"""带标题和边框的面板,支持颜色编码"""
|
|
53
97
|
color = cls._get_color(level)
|
|
54
|
-
|
|
98
|
+
width = 70
|
|
99
|
+
border_char = "─" * width
|
|
100
|
+
|
|
101
|
+
if level == "error":
|
|
102
|
+
border_char = "═" * width
|
|
103
|
+
msg = f"{cls.RED}✗ {msg}{cls.RESET}"
|
|
104
|
+
elif level == "warning":
|
|
105
|
+
border_char = "─" * width
|
|
106
|
+
msg = f"{cls.YELLOW}⚠ {msg}{cls.RESET}"
|
|
55
107
|
|
|
56
|
-
# 标题行
|
|
57
108
|
title_line = ""
|
|
58
109
|
if title:
|
|
59
110
|
title = f" {title.upper()} "
|
|
60
|
-
title_padding = (
|
|
111
|
+
title_padding = (width - len(title)) // 2
|
|
61
112
|
left_pad = " " * title_padding
|
|
62
|
-
right_pad = " " * (
|
|
63
|
-
title_line = f"{cls.DIM}
|
|
113
|
+
right_pad = " " * (width - len(title) - title_padding)
|
|
114
|
+
title_line = f"{cls.DIM}┌{left_pad}{cls.BOLD}{color}{title}{cls.RESET}{cls.DIM}{right_pad}┐{cls.RESET}\n"
|
|
64
115
|
|
|
65
|
-
# 内容行
|
|
66
116
|
lines = []
|
|
67
117
|
for line in msg.split("\n"):
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
118
|
+
if len(line) > width - 4:
|
|
119
|
+
words = line.split()
|
|
120
|
+
current_line = ""
|
|
121
|
+
for word in words:
|
|
122
|
+
if len(current_line) + len(word) + 1 > width - 4:
|
|
123
|
+
lines.append(f"{cls.DIM}│{cls.RESET} {current_line.ljust(width-4)} {cls.DIM}│{cls.RESET}")
|
|
124
|
+
current_line = word
|
|
125
|
+
else:
|
|
126
|
+
current_line += (" " + word) if current_line else word
|
|
127
|
+
if current_line:
|
|
128
|
+
lines.append(f"{cls.DIM}│{cls.RESET} {current_line.ljust(width-4)} {cls.DIM}│{cls.RESET}")
|
|
129
|
+
else:
|
|
130
|
+
lines.append(f"{cls.DIM}│{cls.RESET} {line.ljust(width-4)} {cls.DIM}│{cls.RESET}")
|
|
72
131
|
|
|
73
|
-
|
|
74
|
-
|
|
132
|
+
if level == "error":
|
|
133
|
+
border_style = "╘"
|
|
134
|
+
elif level == "warning":
|
|
135
|
+
border_style = "╧"
|
|
136
|
+
else:
|
|
137
|
+
border_style = "└"
|
|
138
|
+
bottom_border = f"{cls.DIM}{border_style}{border_char}┘{cls.RESET}"
|
|
75
139
|
|
|
76
|
-
|
|
77
|
-
panel
|
|
140
|
+
panel = f"{title_line}"
|
|
141
|
+
panel += f"{cls.DIM}├{border_char}┤{cls.RESET}\n"
|
|
78
142
|
panel += "\n".join(lines) + "\n"
|
|
79
|
-
panel += f"{
|
|
143
|
+
panel += f"{bottom_border}\n"
|
|
80
144
|
|
|
81
145
|
print(panel)
|
|
82
146
|
|
|
83
147
|
@classmethod
|
|
84
148
|
def table(cls, headers, rows, title=None, level="info") -> None:
|
|
85
|
-
"""改进的表格输出,带有颜色和分隔线"""
|
|
86
149
|
color = cls._get_color(level)
|
|
87
150
|
if title:
|
|
88
151
|
print(f"{cls.BOLD}{color}== {title} =={cls.RESET}")
|
|
89
152
|
|
|
90
|
-
# 计算列宽
|
|
91
153
|
col_widths = [len(h) for h in headers]
|
|
92
154
|
for row in rows:
|
|
93
155
|
for i, cell in enumerate(row):
|
|
94
156
|
col_widths[i] = max(col_widths[i], len(str(cell)))
|
|
95
157
|
|
|
96
|
-
# 构建标题格式
|
|
97
158
|
fmt = "│".join(f" {{:<{w}}} " for w in col_widths)
|
|
98
159
|
|
|
99
|
-
# 顶部边框
|
|
100
160
|
top_border = "┌" + "┬".join("─" * (w+2) for w in col_widths) + "┐"
|
|
101
161
|
print(f"{cls.DIM}{top_border}{cls.RESET}")
|
|
102
162
|
|
|
103
|
-
# 表头
|
|
104
163
|
header_line = fmt.format(*headers)
|
|
105
164
|
print(f"{cls.BOLD}{color}│{header_line}│{cls.RESET}")
|
|
106
165
|
|
|
107
|
-
# 表头分隔线
|
|
108
166
|
separator = "├" + "┼".join("─" * (w+2) for w in col_widths) + "┤"
|
|
109
167
|
print(f"{cls.DIM}{separator}{cls.RESET}")
|
|
110
168
|
|
|
111
|
-
# 表格内容
|
|
112
169
|
for row in rows:
|
|
113
170
|
row_line = fmt.format(*row)
|
|
114
171
|
print(f"│{row_line}│")
|
|
115
172
|
|
|
116
|
-
# 底部边框
|
|
117
173
|
bottom_border = "└" + "┴".join("─" * (w+2) for w in col_widths) + "┘"
|
|
118
174
|
print(f"{cls.DIM}{bottom_border}{cls.RESET}")
|
|
119
175
|
|
|
120
176
|
@classmethod
|
|
121
177
|
def progress_bar(cls, current, total, prefix="", suffix="", length=50):
|
|
122
|
-
"""显示进度条"""
|
|
123
178
|
filled_length = int(length * current // total)
|
|
124
179
|
percent = min(100.0, 100 * (current / float(total)))
|
|
125
180
|
bar = f"{cls.GREEN}{'█' * filled_length}{cls.WHITE}{'░' * (length - filled_length)}{cls.RESET}"
|
|
@@ -130,7 +185,6 @@ class Shell_Printer:
|
|
|
130
185
|
|
|
131
186
|
@classmethod
|
|
132
187
|
def confirm(cls, msg, default=False) -> bool:
|
|
133
|
-
"""带颜色和默认选择的确认对话框"""
|
|
134
188
|
yes_options = {'y', 'yes'}
|
|
135
189
|
no_options = {'n', 'no'}
|
|
136
190
|
default_str = "Y/n" if default else "y/N"
|
|
@@ -148,7 +202,6 @@ class Shell_Printer:
|
|
|
148
202
|
|
|
149
203
|
@classmethod
|
|
150
204
|
def ask(cls, msg, choices=None, default=None) -> str | None:
|
|
151
|
-
"""带颜色和选择的提问"""
|
|
152
205
|
prompt = f"{cls.BOLD}{msg}{cls.RESET}"
|
|
153
206
|
if choices:
|
|
154
207
|
prompt += f" ({cls.CYAN}{'/'.join(choices)}{cls.RESET})"
|
|
@@ -166,7 +219,6 @@ class Shell_Printer:
|
|
|
166
219
|
|
|
167
220
|
@classmethod
|
|
168
221
|
def status(cls, msg, success=True):
|
|
169
|
-
"""显示状态指示器"""
|
|
170
222
|
symbol = f"{cls.GREEN}✓" if success else f"{cls.RED}✗"
|
|
171
223
|
print(f"\r{symbol}{cls.RESET} {msg}")
|
|
172
224
|
|
|
@@ -179,10 +231,29 @@ class SourceManager:
|
|
|
179
231
|
def _init_sources(self):
|
|
180
232
|
if not env.get('origins'):
|
|
181
233
|
env.set('origins', [])
|
|
234
|
+
|
|
235
|
+
primary_source = "https://erisdev.com/map.json"
|
|
236
|
+
secondary_source = "https://raw.githubusercontent.com/ErisPulse/ErisPulse-ModuleRepo/refs/heads/main/map.json"
|
|
237
|
+
|
|
238
|
+
shellprint.status("正在验证主源...")
|
|
239
|
+
validated_url = asyncio.run(self._validate_url(primary_source))
|
|
240
|
+
|
|
241
|
+
if validated_url:
|
|
242
|
+
env.set('origins', [validated_url])
|
|
243
|
+
shellprint.status(f"主源 {validated_url} 已成功添加")
|
|
244
|
+
else:
|
|
245
|
+
if secondary_source not in env.get('origins', []):
|
|
246
|
+
env.set('origins', [secondary_source])
|
|
247
|
+
shellprint.panel(
|
|
248
|
+
f"主源不可用,已添加备用源 {secondary_source}\n\n"
|
|
249
|
+
f"{Shell_Printer.YELLOW}提示:{Shell_Printer.RESET} 建议尽快升级 ErisPulse SDK 版本",
|
|
250
|
+
"源初始化",
|
|
251
|
+
"warning"
|
|
252
|
+
)
|
|
182
253
|
|
|
183
254
|
async def _validate_url(self, url):
|
|
184
255
|
if not url.startswith(('http://', 'https://')):
|
|
185
|
-
protocol = shellprint.
|
|
256
|
+
protocol = shellprint.ask("未指定协议,请输入使用的协议", choices=['http', 'https'], default="https")
|
|
186
257
|
url = f"{protocol}://{url}"
|
|
187
258
|
if not url.endswith('.json'):
|
|
188
259
|
url = f"{url}/map.json"
|
|
@@ -190,10 +261,12 @@ class SourceManager:
|
|
|
190
261
|
async with aiohttp.ClientSession() as session:
|
|
191
262
|
async with session.get(url) as response:
|
|
192
263
|
response.raise_for_status()
|
|
193
|
-
|
|
264
|
+
try:
|
|
265
|
+
content = await response.text()
|
|
266
|
+
json.loads(content)
|
|
194
267
|
return url
|
|
195
|
-
|
|
196
|
-
shellprint.panel(f"源 {url} 返回的内容不是有效的 JSON
|
|
268
|
+
except (ValueError, json.JSONDecodeError) as e:
|
|
269
|
+
shellprint.panel(f"源 {url} 返回的内容不是有效的 JSON 格式: {e}", "错误", "error")
|
|
197
270
|
return None
|
|
198
271
|
except Exception as e:
|
|
199
272
|
shellprint.panel(f"访问源 {url} 失败: {e}", "错误", "error")
|
|
@@ -230,8 +303,9 @@ class SourceManager:
|
|
|
230
303
|
async with aiohttp.ClientSession() as session:
|
|
231
304
|
async with session.get(origin) as response:
|
|
232
305
|
response.raise_for_status()
|
|
233
|
-
|
|
234
|
-
|
|
306
|
+
try:
|
|
307
|
+
text = await response.text()
|
|
308
|
+
content = json.loads(text)
|
|
235
309
|
providers[content["name"]] = content["base"]
|
|
236
310
|
for module in content["modules"].keys():
|
|
237
311
|
module_content = content["modules"][module]
|
|
@@ -244,8 +318,8 @@ class SourceManager:
|
|
|
244
318
|
module,
|
|
245
319
|
f"{providers[content['name']]}{module_origin_name}"
|
|
246
320
|
])
|
|
247
|
-
|
|
248
|
-
shellprint.panel(f"源 {origin} 返回的内容不是有效的 JSON
|
|
321
|
+
except (ValueError, json.JSONDecodeError) as e:
|
|
322
|
+
shellprint.panel(f"源 {origin} 返回的内容不是有效的 JSON 格式: {e}", "错误", "error")
|
|
249
323
|
except Exception as e:
|
|
250
324
|
shellprint.panel(f"获取 {origin} 时出错: {e}", "错误", "error")
|
|
251
325
|
|
|
@@ -1073,7 +1147,12 @@ def main():
|
|
|
1073
1147
|
else:
|
|
1074
1148
|
shellprint.panel(f"运行脚本: {Shell_Printer.BOLD}{script_path}{Shell_Printer.RESET}", "执行", "info")
|
|
1075
1149
|
import runpy
|
|
1076
|
-
|
|
1150
|
+
|
|
1151
|
+
# 添加KeyboardInterrupt异常捕捉
|
|
1152
|
+
try:
|
|
1153
|
+
runpy.run_path(script_path, run_name="__main__")
|
|
1154
|
+
except KeyboardInterrupt:
|
|
1155
|
+
shellprint.panel("脚本执行已中断", "中断", "info")
|
|
1077
1156
|
|
|
1078
1157
|
elif args.command == 'origin':
|
|
1079
1158
|
if args.origin_command == 'add':
|
|
@@ -1090,7 +1169,6 @@ def main():
|
|
|
1090
1169
|
else:
|
|
1091
1170
|
origin_parser.print_help()
|
|
1092
1171
|
else:
|
|
1093
|
-
# 如果没有提供命令,显示帮助信息
|
|
1094
1172
|
parser.print_help()
|
|
1095
1173
|
|
|
1096
1174
|
if __name__ == "__main__":
|
ErisPulse/adapter.py
CHANGED
|
@@ -1,3 +1,354 @@
|
|
|
1
|
+
"""
|
|
2
|
+
# 适配器系统
|
|
3
|
+
|
|
4
|
+
提供平台适配器基类、消息发送DSL和适配器管理功能。支持多平台消息处理、事件驱动和生命周期管理。
|
|
5
|
+
|
|
6
|
+
## 核心功能
|
|
7
|
+
1. 适配器基类定义
|
|
8
|
+
2. 链式消息发送DSL
|
|
9
|
+
3. 适配器注册和管理
|
|
10
|
+
4. 事件处理系统
|
|
11
|
+
5. 中间件支持
|
|
12
|
+
|
|
13
|
+
## API 文档
|
|
14
|
+
|
|
15
|
+
### 适配器基类 (BaseAdapter)
|
|
16
|
+
适配器基类提供了与外部平台交互的标准接口。
|
|
17
|
+
|
|
18
|
+
#### call_api(endpoint: str, **params) -> Any
|
|
19
|
+
调用平台API的抽象方法。
|
|
20
|
+
- 参数:
|
|
21
|
+
- endpoint: API端点
|
|
22
|
+
- **params: API参数
|
|
23
|
+
- 返回:
|
|
24
|
+
- Any: API调用结果
|
|
25
|
+
- 说明:
|
|
26
|
+
- 必须由子类实现
|
|
27
|
+
- 处理与平台的实际通信
|
|
28
|
+
- 示例:
|
|
29
|
+
```python
|
|
30
|
+
class MyPlatformAdapter(BaseAdapter):
|
|
31
|
+
async def call_api(self, endpoint: str, **params):
|
|
32
|
+
if endpoint == "/send":
|
|
33
|
+
return await self._send_message(params)
|
|
34
|
+
elif endpoint == "/upload":
|
|
35
|
+
return await self._upload_file(params)
|
|
36
|
+
raise NotImplementedError(f"未实现的端点: {endpoint}")
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
#### start() -> None
|
|
40
|
+
启动适配器的抽象方法。
|
|
41
|
+
- 参数: 无
|
|
42
|
+
- 返回:
|
|
43
|
+
- None
|
|
44
|
+
- 说明:
|
|
45
|
+
- 必须由子类实现
|
|
46
|
+
- 处理适配器的初始化和启动逻辑
|
|
47
|
+
- 示例:
|
|
48
|
+
```python
|
|
49
|
+
class MyPlatformAdapter(BaseAdapter):
|
|
50
|
+
async def start(self):
|
|
51
|
+
self.client = await self._create_client()
|
|
52
|
+
self.ws = await self.client.create_websocket()
|
|
53
|
+
self._start_heartbeat()
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
#### shutdown() -> None
|
|
57
|
+
关闭适配器的抽象方法。
|
|
58
|
+
- 参数: 无
|
|
59
|
+
- 返回:
|
|
60
|
+
- None
|
|
61
|
+
- 说明:
|
|
62
|
+
- 必须由子类实现
|
|
63
|
+
- 处理资源清理和关闭逻辑
|
|
64
|
+
- 示例:
|
|
65
|
+
```python
|
|
66
|
+
class MyPlatformAdapter(BaseAdapter):
|
|
67
|
+
async def shutdown(self):
|
|
68
|
+
if self.ws:
|
|
69
|
+
await self.ws.close()
|
|
70
|
+
if self.client:
|
|
71
|
+
await self.client.close()
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
#### on(event_type: str = "*") -> Callable
|
|
75
|
+
事件监听装饰器。
|
|
76
|
+
- 参数:
|
|
77
|
+
- event_type: 事件类型,默认"*"表示所有事件
|
|
78
|
+
- 返回:
|
|
79
|
+
- Callable: 装饰器函数
|
|
80
|
+
- 示例:
|
|
81
|
+
```python
|
|
82
|
+
adapter = MyPlatformAdapter()
|
|
83
|
+
|
|
84
|
+
@adapter.on("message")
|
|
85
|
+
async def handle_message(data):
|
|
86
|
+
print(f"收到消息: {data}")
|
|
87
|
+
|
|
88
|
+
@adapter.on("error")
|
|
89
|
+
async def handle_error(error):
|
|
90
|
+
print(f"发生错误: {error}")
|
|
91
|
+
|
|
92
|
+
# 处理所有事件
|
|
93
|
+
@adapter.on()
|
|
94
|
+
async def handle_all(event):
|
|
95
|
+
print(f"事件: {event}")
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
#### emit(event_type: str, data: Any) -> None
|
|
99
|
+
触发事件。
|
|
100
|
+
- 参数:
|
|
101
|
+
- event_type: 事件类型
|
|
102
|
+
- data: 事件数据
|
|
103
|
+
- 返回:
|
|
104
|
+
- None
|
|
105
|
+
- 示例:
|
|
106
|
+
```python
|
|
107
|
+
class MyPlatformAdapter(BaseAdapter):
|
|
108
|
+
async def _handle_websocket_message(self, message):
|
|
109
|
+
# 处理消息并触发相应事件
|
|
110
|
+
if message.type == "chat":
|
|
111
|
+
await self.emit("message", {
|
|
112
|
+
"type": "chat",
|
|
113
|
+
"content": message.content,
|
|
114
|
+
"sender": message.sender
|
|
115
|
+
})
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
#### middleware(func: Callable) -> Callable
|
|
119
|
+
添加中间件处理器。
|
|
120
|
+
- 参数:
|
|
121
|
+
- func: 中间件函数
|
|
122
|
+
- 返回:
|
|
123
|
+
- Callable: 中间件函数
|
|
124
|
+
- 示例:
|
|
125
|
+
```python
|
|
126
|
+
adapter = MyPlatformAdapter()
|
|
127
|
+
|
|
128
|
+
@adapter.middleware
|
|
129
|
+
async def log_middleware(data):
|
|
130
|
+
print(f"处理数据: {data}")
|
|
131
|
+
return data
|
|
132
|
+
|
|
133
|
+
@adapter.middleware
|
|
134
|
+
async def filter_middleware(data):
|
|
135
|
+
if "spam" in data.get("content", ""):
|
|
136
|
+
return None
|
|
137
|
+
return data
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### 消息发送DSL (SendDSL)
|
|
141
|
+
提供链式调用风格的消息发送接口。
|
|
142
|
+
|
|
143
|
+
#### To(target_type: str = None, target_id: str = None) -> 'SendDSL'
|
|
144
|
+
设置消息目标。
|
|
145
|
+
- 参数:
|
|
146
|
+
- target_type: 目标类型(可选)
|
|
147
|
+
- target_id: 目标ID
|
|
148
|
+
- 返回:
|
|
149
|
+
- SendDSL: 发送器实例
|
|
150
|
+
- 示例:
|
|
151
|
+
```python
|
|
152
|
+
# 发送到用户
|
|
153
|
+
sdk.adapter.Platform.Send.To("user", "123").Text("Hello")
|
|
154
|
+
|
|
155
|
+
# 发送到群组
|
|
156
|
+
sdk.adapter.Platform.Send.To("group", "456").Text("Hello Group")
|
|
157
|
+
|
|
158
|
+
# 简化形式(只有ID)
|
|
159
|
+
sdk.adapter.Platform.Send.To("123").Text("Hello")
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
#### Text(text: str) -> Task
|
|
163
|
+
发送文本消息。
|
|
164
|
+
- 参数:
|
|
165
|
+
- text: 文本内容
|
|
166
|
+
- 返回:
|
|
167
|
+
- Task: 异步任务
|
|
168
|
+
- 示例:
|
|
169
|
+
```python
|
|
170
|
+
# 发送简单文本
|
|
171
|
+
await sdk.adapter.Platform.Send.To("user", "123").Text("Hello")
|
|
172
|
+
|
|
173
|
+
# 发送格式化文本
|
|
174
|
+
name = "Alice"
|
|
175
|
+
await sdk.adapter.Platform.Send.To("123").Text(f"Hello {name}")
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
### 适配器管理 (AdapterManager)
|
|
179
|
+
管理多个平台适配器的注册、启动和关闭。
|
|
180
|
+
|
|
181
|
+
#### register(platform: str, adapter_class: Type[BaseAdapter]) -> bool
|
|
182
|
+
注册新的适配器类。
|
|
183
|
+
- 参数:
|
|
184
|
+
- platform: 平台名称
|
|
185
|
+
- adapter_class: 适配器类
|
|
186
|
+
- 返回:
|
|
187
|
+
- bool: 注册是否成功
|
|
188
|
+
- 示例:
|
|
189
|
+
```python
|
|
190
|
+
# 注册适配器
|
|
191
|
+
sdk.adapter.register("MyPlatform", MyPlatformAdapter)
|
|
192
|
+
|
|
193
|
+
# 注册多个适配器
|
|
194
|
+
adapters = {
|
|
195
|
+
"Platform1": Platform1Adapter,
|
|
196
|
+
"Platform2": Platform2Adapter
|
|
197
|
+
}
|
|
198
|
+
for name, adapter in adapters.items():
|
|
199
|
+
sdk.adapter.register(name, adapter)
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
#### startup(platforms: List[str] = None) -> None
|
|
203
|
+
启动指定的适配器。
|
|
204
|
+
- 参数:
|
|
205
|
+
- platforms: 要启动的平台列表,None表示所有平台
|
|
206
|
+
- 返回:
|
|
207
|
+
- None
|
|
208
|
+
- 示例:
|
|
209
|
+
```python
|
|
210
|
+
# 启动所有适配器
|
|
211
|
+
await sdk.adapter.startup()
|
|
212
|
+
|
|
213
|
+
# 启动指定适配器
|
|
214
|
+
await sdk.adapter.startup(["Platform1", "Platform2"])
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
#### shutdown() -> None
|
|
218
|
+
关闭所有适配器。
|
|
219
|
+
- 参数: 无
|
|
220
|
+
- 返回:
|
|
221
|
+
- None
|
|
222
|
+
- 示例:
|
|
223
|
+
```python
|
|
224
|
+
# 关闭所有适配器
|
|
225
|
+
await sdk.adapter.shutdown()
|
|
226
|
+
|
|
227
|
+
# 在程序退出时关闭
|
|
228
|
+
import atexit
|
|
229
|
+
atexit.register(lambda: asyncio.run(sdk.adapter.shutdown()))
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
## 最佳实践
|
|
233
|
+
|
|
234
|
+
1. 适配器实现
|
|
235
|
+
```python
|
|
236
|
+
class MyPlatformAdapter(sdk.BaseAdapter):
|
|
237
|
+
class Send(sdk.BaseAdapter.Send):
|
|
238
|
+
# 实现基本消息类型
|
|
239
|
+
def Text(self, text: str):
|
|
240
|
+
return asyncio.create_task(
|
|
241
|
+
self._adapter.call_api(
|
|
242
|
+
endpoint="/send",
|
|
243
|
+
content=text,
|
|
244
|
+
recvId=self._target_id,
|
|
245
|
+
recvType=self._target_type
|
|
246
|
+
)
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
# 添加自定义消息类型
|
|
250
|
+
def Image(self, file: bytes):
|
|
251
|
+
return asyncio.create_task(
|
|
252
|
+
self._adapter.call_api(
|
|
253
|
+
endpoint="/send_image",
|
|
254
|
+
file=file,
|
|
255
|
+
recvId=self._target_id,
|
|
256
|
+
recvType=self._target_type
|
|
257
|
+
)
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
async def call_api(self, endpoint: str, **params):
|
|
261
|
+
# 实现API调用逻辑
|
|
262
|
+
async with aiohttp.ClientSession() as session:
|
|
263
|
+
async with session.post(
|
|
264
|
+
f"{self.api_base}{endpoint}",
|
|
265
|
+
json=params
|
|
266
|
+
) as response:
|
|
267
|
+
return await response.json()
|
|
268
|
+
|
|
269
|
+
async def start(self):
|
|
270
|
+
# 初始化连接
|
|
271
|
+
self.client = await self._create_client()
|
|
272
|
+
# 启动事件监听
|
|
273
|
+
asyncio.create_task(self._listen_events())
|
|
274
|
+
|
|
275
|
+
async def shutdown(self):
|
|
276
|
+
# 清理资源
|
|
277
|
+
if self.client:
|
|
278
|
+
await self.client.close()
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
2. 事件处理
|
|
282
|
+
```python
|
|
283
|
+
# 注册事件处理器
|
|
284
|
+
adapter = MyPlatformAdapter()
|
|
285
|
+
|
|
286
|
+
@adapter.on("message")
|
|
287
|
+
async def handle_message(data):
|
|
288
|
+
# 消息处理逻辑
|
|
289
|
+
if data["type"] == "text":
|
|
290
|
+
await process_text_message(data)
|
|
291
|
+
elif data["type"] == "image":
|
|
292
|
+
await process_image_message(data)
|
|
293
|
+
|
|
294
|
+
# 使用中间件
|
|
295
|
+
@adapter.middleware
|
|
296
|
+
async def auth_middleware(data):
|
|
297
|
+
if not verify_token(data.get("token")):
|
|
298
|
+
return None
|
|
299
|
+
return data
|
|
300
|
+
|
|
301
|
+
@adapter.middleware
|
|
302
|
+
async def log_middleware(data):
|
|
303
|
+
sdk.logger.info(f"处理事件: {data}")
|
|
304
|
+
return data
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
3. 消息发送
|
|
308
|
+
```python
|
|
309
|
+
# 基本消息发送
|
|
310
|
+
async def send_welcome(user_id: str):
|
|
311
|
+
await sdk.adapter.Platform.Send.To("user", user_id).Text("欢迎!")
|
|
312
|
+
|
|
313
|
+
# 复杂消息处理
|
|
314
|
+
async def process_group_notification(group_id: str, event: dict):
|
|
315
|
+
# 发送格式化消息
|
|
316
|
+
message = format_notification(event)
|
|
317
|
+
await sdk.adapter.Platform.Send.To("group", group_id).Text(message)
|
|
318
|
+
|
|
319
|
+
# 发送附加文件
|
|
320
|
+
if event.get("has_attachment"):
|
|
321
|
+
file_data = await get_attachment(event["attachment_id"])
|
|
322
|
+
await sdk.adapter.Platform.Send.To("group", group_id).File(file_data)
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
## 注意事项
|
|
326
|
+
|
|
327
|
+
1. 适配器实现
|
|
328
|
+
- 确保正确实现所有抽象方法
|
|
329
|
+
- 处理所有可能的异常情况
|
|
330
|
+
- 实现适当的重试机制
|
|
331
|
+
- 注意资源的正确释放
|
|
332
|
+
|
|
333
|
+
2. 事件处理
|
|
334
|
+
- 避免在事件处理器中执行长时间操作
|
|
335
|
+
- 使用适当的错误处理
|
|
336
|
+
- 考虑事件处理的顺序性
|
|
337
|
+
- 合理使用中间件过滤机制
|
|
338
|
+
|
|
339
|
+
3. 消息发送
|
|
340
|
+
- 实现消息发送的限流机制
|
|
341
|
+
- 处理发送失败的情况
|
|
342
|
+
- 注意消息格式的平台兼容性
|
|
343
|
+
- 大文件传输时考虑分片
|
|
344
|
+
|
|
345
|
+
4. 生命周期管理
|
|
346
|
+
- 确保适配器正确启动和关闭
|
|
347
|
+
- 处理意外断开的情况
|
|
348
|
+
- 实现自动重连机制
|
|
349
|
+
- 注意资源泄漏问题
|
|
350
|
+
"""
|
|
351
|
+
|
|
1
352
|
import functools
|
|
2
353
|
import asyncio
|
|
3
354
|
from typing import Callable, Any, Dict, List, Type, Optional, Set
|