clovers 0.1.0__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.
- clovers-0.1.0/LICENSE +21 -0
- clovers-0.1.0/PKG-INFO +382 -0
- clovers-0.1.0/README.md +370 -0
- clovers-0.1.0/clovers/__init__.py +0 -0
- clovers-0.1.0/clovers/core/adapter.py +99 -0
- clovers-0.1.0/clovers/core/config.py +25 -0
- clovers-0.1.0/clovers/core/plugin.py +196 -0
- clovers-0.1.0/clovers/utils/library.py +85 -0
- clovers-0.1.0/clovers/utils/linecard.py +331 -0
- clovers-0.1.0/clovers/utils/tools.py +76 -0
- clovers-0.1.0/pyproject.toml +15 -0
clovers-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 KarisAya
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
clovers-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: clovers
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary:
|
|
5
|
+
Author: KarisAya
|
|
6
|
+
Author-email: 1048827424@qq.com
|
|
7
|
+
Requires-Python: >=3.12,<4.0
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Requires-Dist: toml (>=0.10.2,<0.11.0)
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
|
|
12
|
+
# CLOVERS
|
|
13
|
+
|
|
14
|
+
_✨ 自定义的聊天平台异步机器人指令-响应插件框架 ✨_
|
|
15
|
+
|
|
16
|
+
<div align="center">
|
|
17
|
+
<img src="https://img.shields.io/badge/python-3.12+-blue.svg" alt="python">
|
|
18
|
+
<a href="./LICENSE">
|
|
19
|
+
<img src="https://img.shields.io/github/license/KarisAya/clovers.svg" alt="license">
|
|
20
|
+
</a>
|
|
21
|
+
<a href="https://pypi.python.org/pypi/clovers">
|
|
22
|
+
<img src="https://img.shields.io/pypi/v/clovers.svg" alt="pypi">
|
|
23
|
+
</a>
|
|
24
|
+
<a href="https://pypi.python.org/pypi/clovers">
|
|
25
|
+
<img src="https://img.shields.io/pypi/dm/clovers" alt="pypi download">
|
|
26
|
+
</a>
|
|
27
|
+
</div>
|
|
28
|
+
|
|
29
|
+
## 💿 安装
|
|
30
|
+
|
|
31
|
+
<details open>
|
|
32
|
+
<summary>pip</summary>
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
pip install clovers
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
</details>
|
|
39
|
+
|
|
40
|
+
<details>
|
|
41
|
+
<summary>poetry</summary>
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
poetry add clovers
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
</details>
|
|
48
|
+
|
|
49
|
+
## 插件获取配置
|
|
50
|
+
|
|
51
|
+
配置文件存放在一个 toml 文件里,文件由你指定
|
|
52
|
+
|
|
53
|
+
下面是配置一个例子
|
|
54
|
+
|
|
55
|
+
clovers.toml
|
|
56
|
+
|
|
57
|
+
```toml
|
|
58
|
+
[nonebot_plugin_clovers]
|
|
59
|
+
plugins_path = "./clovers/plugins"
|
|
60
|
+
plugins_list = ["clovers_apscheduler"]
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
意味着 clovers 会加载`./clovers/plugins`文件夹下的文件或文件夹作为插件(排除`_`开头的文件)
|
|
64
|
+
|
|
65
|
+
插件获取的配置会是一个字典。
|
|
66
|
+
|
|
67
|
+
为便于插件间的配置互相获取,建议在插件中使用类似下面的代码加载配置
|
|
68
|
+
|
|
69
|
+
```python
|
|
70
|
+
from clovers.core.config import config as clovers_config
|
|
71
|
+
config_key = __package__ # 或者你自定义的任何key
|
|
72
|
+
default_config = {"some_config_name":"some_config_value"}
|
|
73
|
+
# 各种方法获取配置
|
|
74
|
+
config_data = clovers_config.get(config_key, {})
|
|
75
|
+
default_config.update(config_data)
|
|
76
|
+
# 把配置存回总配置
|
|
77
|
+
clovers_config[config_key] = config_data
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
当然你也可以不这么做
|
|
81
|
+
|
|
82
|
+
## 关于插件
|
|
83
|
+
|
|
84
|
+
下面是一个模板
|
|
85
|
+
|
|
86
|
+
```python
|
|
87
|
+
from clovers.core.config import config as clovers_config
|
|
88
|
+
from clovers.core.plugin import Plugin
|
|
89
|
+
from .config import Config
|
|
90
|
+
|
|
91
|
+
# 获取你的配置
|
|
92
|
+
config_key = __package__
|
|
93
|
+
config_data = Config.parse_obj(clovers_config.get(config_key, {}))
|
|
94
|
+
clovers_config[config_key] = config_data.dict()
|
|
95
|
+
|
|
96
|
+
plugin = Plugin()
|
|
97
|
+
|
|
98
|
+
# 启动时的任务
|
|
99
|
+
@plugin.startup
|
|
100
|
+
async def _():
|
|
101
|
+
pass
|
|
102
|
+
|
|
103
|
+
# 关闭时的任务
|
|
104
|
+
@plugin.shutdown
|
|
105
|
+
async def _():
|
|
106
|
+
pass
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
# 指令-响应任务
|
|
110
|
+
@plugin.handle({"测试"})
|
|
111
|
+
async def _(event: Event):
|
|
112
|
+
pass
|
|
113
|
+
|
|
114
|
+
__plugin__ = plugin
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
插件加载器会尝试获取你的模块的`__plugin__`属性,并作为插件放进适配器的插件列表里
|
|
118
|
+
|
|
119
|
+
如果你想编写插件的插件,也可以不定义`__plugin__`属性,但是一般你需要使用其他插件的`__plugin__`属性
|
|
120
|
+
|
|
121
|
+
```python
|
|
122
|
+
from some_plugin import __plugin__ as plugin
|
|
123
|
+
|
|
124
|
+
# do something
|
|
125
|
+
@plugin.handle({"其他测试"})
|
|
126
|
+
async def _(event: Event):
|
|
127
|
+
pass
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### 指令-响应任务获取平台参数
|
|
131
|
+
|
|
132
|
+
如果你在插件中需要获取一些平台参数,那么需要在注册 plugin.handle 时事先声明需要的参数
|
|
133
|
+
|
|
134
|
+
```python
|
|
135
|
+
@plugin.handle({"测试"},{"user_id","others"})
|
|
136
|
+
async def _(event: Event):
|
|
137
|
+
print(event.kwargs["user_id"])
|
|
138
|
+
print(event.kwargs["others"])
|
|
139
|
+
print(event.kwargs["extra"]) # KeyError
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
适配器方法会根据你需要的参数构建 event.kwargs
|
|
143
|
+
|
|
144
|
+
### 指令-响应任务中的 event
|
|
145
|
+
|
|
146
|
+
event 是你在指令-响应任务的函数中唯一获得的参数,你需要的所有东西都在 event 里
|
|
147
|
+
|
|
148
|
+
`raw_command` 触发本次响应的原始字符串
|
|
149
|
+
`args` 解析的参数列表
|
|
150
|
+
|
|
151
|
+
```python
|
|
152
|
+
#使用 "你好世界" 触发响应
|
|
153
|
+
@plugin.handle({"你好"})
|
|
154
|
+
async def _(event: Event):
|
|
155
|
+
print(event.raw_command) # "你好世界"
|
|
156
|
+
print(event.args) # ["世界"]
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
如果你不想使用原始的 event,你也可以自建 event 类,然后在创建 plugin 实例时注入 build_event 方法。
|
|
160
|
+
|
|
161
|
+
```python
|
|
162
|
+
from clovers.core.plugin import Plugin
|
|
163
|
+
class Event:
|
|
164
|
+
def __init__(self, event: CloversEvent):
|
|
165
|
+
self.event: CloversEvent = event
|
|
166
|
+
|
|
167
|
+
@property
|
|
168
|
+
def raw_command(self):
|
|
169
|
+
return self.event.raw_command
|
|
170
|
+
|
|
171
|
+
@property
|
|
172
|
+
def args(self):
|
|
173
|
+
return self.event.args
|
|
174
|
+
|
|
175
|
+
@property
|
|
176
|
+
def user_id(self) -> str:
|
|
177
|
+
return self.event.kwargs["user_id"]
|
|
178
|
+
|
|
179
|
+
plugin = Plugin(build_event=lambda event: Event(event))
|
|
180
|
+
|
|
181
|
+
@plugin.handle({"测试"},{"user_id"})
|
|
182
|
+
async def _(event: Event):
|
|
183
|
+
print(event.user_id) # "123456"
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
### 插件的响应
|
|
187
|
+
|
|
188
|
+
响应的格式应该是 clovers.core.plugin.Result 类
|
|
189
|
+
|
|
190
|
+
`send_method` 控制适配器方法用什么方式发送你的数据
|
|
191
|
+
|
|
192
|
+
`data` 是要发送的原始数据
|
|
193
|
+
|
|
194
|
+
接下来的示例是指令为 "测试" 回应 "你好" 的 插件指令-响应任务
|
|
195
|
+
|
|
196
|
+
```python
|
|
197
|
+
@plugin.handle({"测试"})
|
|
198
|
+
async def _(event: Event):
|
|
199
|
+
return Result("text", "你好")
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
当然如果你认为这样太过繁琐,你也可以使用 build_result 方法
|
|
203
|
+
|
|
204
|
+
```python
|
|
205
|
+
from clovers.core.plugin import Plugin
|
|
206
|
+
def build_result(result):
|
|
207
|
+
if isinstance(result, str):
|
|
208
|
+
return Result("text", result)
|
|
209
|
+
if isinstance(result, BytesIO):
|
|
210
|
+
return Result("image", result)
|
|
211
|
+
if isinstance(result, AnyTypeYouNeed):
|
|
212
|
+
return Result("any_method_you_want", result)
|
|
213
|
+
return result
|
|
214
|
+
|
|
215
|
+
plugin = Plugin(build_result=build_result)
|
|
216
|
+
|
|
217
|
+
@plugin.handle({"测试"},{"user_id"})
|
|
218
|
+
async def _(event: Event):
|
|
219
|
+
return "你好"
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
### 关于插件的其他功能
|
|
223
|
+
|
|
224
|
+
**临时任意触发任务**
|
|
225
|
+
|
|
226
|
+
```python
|
|
227
|
+
@plugin.temp_handle("temp_handle1", {"user_id", "group_id"}, 30)
|
|
228
|
+
async def _(event: Event, finish):
|
|
229
|
+
if i_should_finish:
|
|
230
|
+
finish()
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
需要的三个参数
|
|
234
|
+
|
|
235
|
+
`key` 临时任务 key 如果这个 key 被注册过,并且没有超时也没有结束,那么之前的任务会被下面的任务覆盖
|
|
236
|
+
|
|
237
|
+
`extra_args` 需要的平台参数
|
|
238
|
+
|
|
239
|
+
`timeout` 任务超时时间(秒)
|
|
240
|
+
|
|
241
|
+
temp_handle 会被任意消息触发,请在任务内自定义检查规则。
|
|
242
|
+
|
|
243
|
+
temp_handle 任务除了 event,你还会获得一个 Callable 参数 finish,它的功能是结束本任务。如果你不结束,在临时任务超时前每次消息都会触发。
|
|
244
|
+
|
|
245
|
+
**关于 handle 任务的指令格式和参数列表**
|
|
246
|
+
|
|
247
|
+
set 格式:合集内的指令都会触发插件
|
|
248
|
+
|
|
249
|
+
```python
|
|
250
|
+
#触发指令为"你好 世界"时,输出 ["世界"]
|
|
251
|
+
#触发指令为"hello1 world with extra args"时,输出 ["1","world","with","extra","args"]
|
|
252
|
+
@plugin.handle({"你好","hello"})
|
|
253
|
+
async def _(event: Event):
|
|
254
|
+
print(event.args)
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
字符串格式:正则匹配
|
|
258
|
+
|
|
259
|
+
如果 handle 的指令参数是字符串那么它会进行正则匹配,args 会是正则字符串中的 group 列表
|
|
260
|
+
|
|
261
|
+
```python
|
|
262
|
+
#触发指令为"i love you"时,输出 ["i "," you"] 使用时注意去掉参数里的空格
|
|
263
|
+
#触发指令为"you love me"时,输出 ["you "," me"]
|
|
264
|
+
#触发指令为"make love"时,输出 ["make ", None]
|
|
265
|
+
@plugin.handle(r"^(.+)love(.*)")
|
|
266
|
+
async def _(event: Event):
|
|
267
|
+
print(event.args)
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
## 关于适配器
|
|
271
|
+
|
|
272
|
+
创建一个适配器
|
|
273
|
+
|
|
274
|
+
```python
|
|
275
|
+
adapter = Adapter()
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
~~创建好了~~
|
|
279
|
+
|
|
280
|
+
一个适配器可以有多个适配器方法
|
|
281
|
+
|
|
282
|
+
适配器的所有方法都需要自己写
|
|
283
|
+
|
|
284
|
+
如果你想使用 clovers 框架,需要使用你接收到的纯文本消息触发适配器响应
|
|
285
|
+
|
|
286
|
+
像这样
|
|
287
|
+
|
|
288
|
+
```python
|
|
289
|
+
#假设你在一个循环里不断轮询收发消息端是否有新消息
|
|
290
|
+
while True:
|
|
291
|
+
command = received_plain_text()
|
|
292
|
+
if command:
|
|
293
|
+
await adapter.response(adapter_key, command, **kwargs)
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
`adapter_key` 适配器方法指定的 key
|
|
297
|
+
`kwargs` 适配器方法需要的所有参数
|
|
298
|
+
|
|
299
|
+
### 适配器方法
|
|
300
|
+
|
|
301
|
+
获取参数,发送信息的方法。里面所有的方法都需要自己写
|
|
302
|
+
|
|
303
|
+
发送信息,获取参数
|
|
304
|
+
|
|
305
|
+
```python
|
|
306
|
+
# 假如收发信息框架提供了如下方法
|
|
307
|
+
|
|
308
|
+
# send_plain_text(text:str)发送纯文本
|
|
309
|
+
|
|
310
|
+
method = AdapterMethod()
|
|
311
|
+
@method.send("text")
|
|
312
|
+
async def _(message: str):
|
|
313
|
+
send_plain_text(message)
|
|
314
|
+
|
|
315
|
+
# send_image(image:bytes) 发送图片,但是需要response的参数
|
|
316
|
+
|
|
317
|
+
@method.send("image")
|
|
318
|
+
async def _(message: bytes,send_image):
|
|
319
|
+
send_image(message)
|
|
320
|
+
|
|
321
|
+
# sender发送消息的用户信息,通过response的参数传入
|
|
322
|
+
# 假设有 sender.user_id 属性为该用户uid
|
|
323
|
+
@method.kwarg("user_id")
|
|
324
|
+
async def _(sender):
|
|
325
|
+
return sender.user_id
|
|
326
|
+
|
|
327
|
+
# 注入适配器方法
|
|
328
|
+
adapter.methods["my_adapter_method"] = method
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
使用上述适配器
|
|
332
|
+
|
|
333
|
+
你的 `Result("text", "你好")` 会使用 send_plain_text 发送
|
|
334
|
+
|
|
335
|
+
你的指令响应任务获取平台参数的 `"user_id"` 就是 sender.user_id
|
|
336
|
+
|
|
337
|
+
### 使用插件加载器 PluginLoader 向适配器注入插件
|
|
338
|
+
|
|
339
|
+
```python
|
|
340
|
+
loader = PluginLoader(plugins_path, plugins_list)
|
|
341
|
+
adapter.plugins = loader.plugins
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
`plugins_list` 插件名列表,例如["plugin1","plugin2"]。从 python lib 路径下的包名加载插件
|
|
345
|
+
|
|
346
|
+
`plugins_path` 插件文件夹,加载改路径下的文件或文件夹作为插件(排除`_`开头的文件)
|
|
347
|
+
|
|
348
|
+
或者
|
|
349
|
+
|
|
350
|
+
```python
|
|
351
|
+
plugin = PluginLoader.load("plugin1")
|
|
352
|
+
if not plugin is None:
|
|
353
|
+
adapter.plugins.append(plugin)
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
### 开始,结束任务
|
|
357
|
+
|
|
358
|
+
一些插件会注册一些开始时,结束时运行的任务
|
|
359
|
+
|
|
360
|
+
所以你需要在开始时,或结束时执行
|
|
361
|
+
|
|
362
|
+
```python
|
|
363
|
+
asyncio.create_task(adapter.startup)
|
|
364
|
+
asyncio.create_task(adapter.shutdown)
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
或类似作用的代码
|
|
368
|
+
|
|
369
|
+
## 📞 联系
|
|
370
|
+
|
|
371
|
+
如有建议,bug 反馈等可以加群
|
|
372
|
+
|
|
373
|
+
机器人 bug 研究中心(闲聊群) 744751179
|
|
374
|
+
|
|
375
|
+
永恒之城(测试群) 724024810
|
|
376
|
+
|
|
377
|
+

|
|
378
|
+
|
|
379
|
+
## 💡 鸣谢
|
|
380
|
+
|
|
381
|
+
- [nonebot2](https://github.com/nonebot/nonebot2) 跨平台 Python 异步聊天机器人框架 ~~需求都是基于这个写的~~
|
|
382
|
+
|