moemoe 0.0.1__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.
- moemoe-0.0.1/.gitignore +25 -0
- moemoe-0.0.1/LICENSE +21 -0
- moemoe-0.0.1/PKG-INFO +378 -0
- moemoe-0.0.1/README.md +351 -0
- moemoe-0.0.1/RULES.md +118 -0
- moemoe-0.0.1/docs/bench_dispatch.py +121 -0
- moemoe-0.0.1/docs/design.md +217 -0
- moemoe-0.0.1/examples/echo.py +144 -0
- moemoe-0.0.1/moemoe/__init__.py +60 -0
- moemoe-0.0.1/moemoe/app.py +412 -0
- moemoe-0.0.1/moemoe/bot.py +180 -0
- moemoe-0.0.1/moemoe/command.py +199 -0
- moemoe-0.0.1/moemoe/errors.py +41 -0
- moemoe-0.0.1/moemoe/event.py +248 -0
- moemoe-0.0.1/moemoe/loader.py +75 -0
- moemoe-0.0.1/moemoe/message.py +205 -0
- moemoe-0.0.1/moemoe/permission.py +62 -0
- moemoe-0.0.1/moemoe/py.typed +0 -0
- moemoe-0.0.1/moemoe/scheduler.py +110 -0
- moemoe-0.0.1/moemoe/session.py +205 -0
- moemoe-0.0.1/pyproject.toml +59 -0
- moemoe-0.0.1/tests/__init__.py +10 -0
- moemoe-0.0.1/tests/helpers.py +76 -0
- moemoe-0.0.1/tests/test_app.py +256 -0
- moemoe-0.0.1/tests/test_command.py +174 -0
- moemoe-0.0.1/tests/test_event.py +229 -0
- moemoe-0.0.1/tests/test_hooks.py +208 -0
- moemoe-0.0.1/tests/test_message.py +122 -0
- moemoe-0.0.1/tests/test_session.py +343 -0
moemoe-0.0.1/.gitignore
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# 打包产物
|
|
2
|
+
build/
|
|
3
|
+
dist/
|
|
4
|
+
*.egg-info/
|
|
5
|
+
*.egg
|
|
6
|
+
|
|
7
|
+
# Python
|
|
8
|
+
__pycache__/
|
|
9
|
+
*.py[cod]
|
|
10
|
+
.pytest_cache/
|
|
11
|
+
.ruff_cache/
|
|
12
|
+
.mypy_cache/
|
|
13
|
+
|
|
14
|
+
# 虚拟环境
|
|
15
|
+
.venv/
|
|
16
|
+
venv/
|
|
17
|
+
|
|
18
|
+
# 你的配置和日志
|
|
19
|
+
config.py
|
|
20
|
+
logs/
|
|
21
|
+
*.log
|
|
22
|
+
|
|
23
|
+
# 工具
|
|
24
|
+
.idea/
|
|
25
|
+
.vscode/
|
moemoe-0.0.1/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 nicemoe
|
|
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 OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
moemoe-0.0.1/PKG-INFO
ADDED
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: moemoe
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: QQ 官方机器人应用端框架,配合 robot 协议端使用
|
|
5
|
+
Project-URL: Homepage, https://github.com/nicemoe/moemoe
|
|
6
|
+
Project-URL: Source, https://github.com/nicemoe/moemoe
|
|
7
|
+
Project-URL: Issues, https://github.com/nicemoe/moemoe/issues
|
|
8
|
+
Author-email: nicemoe <22282320@qq.com>
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: bot,qq,qqbot,robot
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Framework :: AsyncIO
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
17
|
+
Classifier: Topic :: Communications :: Chat
|
|
18
|
+
Classifier: Typing :: Typed
|
|
19
|
+
Requires-Python: >=3.11
|
|
20
|
+
Requires-Dist: loguru>=0.7
|
|
21
|
+
Requires-Dist: starlette>=0.37
|
|
22
|
+
Provides-Extra: scheduler
|
|
23
|
+
Requires-Dist: apscheduler<4,>=3.10; extra == 'scheduler'
|
|
24
|
+
Provides-Extra: uvicorn
|
|
25
|
+
Requires-Dist: uvicorn>=0.30; extra == 'uvicorn'
|
|
26
|
+
Description-Content-Type: text/markdown
|
|
27
|
+
|
|
28
|
+
# moemoe
|
|
29
|
+
|
|
30
|
+
QQ 官方机器人的应用端框架,配合 [robot](https://github.com/nicemoe/robot) 协议端使用。
|
|
31
|
+
|
|
32
|
+
```
|
|
33
|
+
QQ 官方 ←──WSS──→ robot(协议端,Go)──反向 WS──→ moemoe(应用端,你的代码)
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
**robot 主动拨号,所以 moemoe 是服务端。** 一个 moemoe 同时接多个 robot,每个 robot
|
|
37
|
+
一个 `Bot` 实例。
|
|
38
|
+
|
|
39
|
+
核心依赖只有 `starlette` 和 `loguru`:连接、凭证续期、断线重连都在 robot 那侧。
|
|
40
|
+
|
|
41
|
+
## 装
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
pip install moemoe[uvicorn]
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
挂进已有的 ASGI 应用就不装 `[uvicorn]`,把 `Moemoe.asgi` 交给你的服务器。
|
|
48
|
+
|
|
49
|
+
需要 Python 3.11+。
|
|
50
|
+
|
|
51
|
+
## 三分钟上手
|
|
52
|
+
|
|
53
|
+
```python
|
|
54
|
+
from moemoe import Moemoe
|
|
55
|
+
|
|
56
|
+
app = Moemoe(access_token='') # 和 robot 配的一致,留空表示不校验
|
|
57
|
+
|
|
58
|
+
@app.command('ping')
|
|
59
|
+
async def _(event, args):
|
|
60
|
+
return 'pong' # 返回非 None 会当消息发出去
|
|
61
|
+
|
|
62
|
+
app.run(host='127.0.0.1', port=8000)
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
robot 那侧把 `backend.url` 指到 `ws://127.0.0.1:8000/robot` 就通了。
|
|
66
|
+
|
|
67
|
+
## 多机器人
|
|
68
|
+
|
|
69
|
+
几十个 robot 连进来是常态。`Event.bot` 指回收到这条消息的那个,所以「回哪条、从哪个
|
|
70
|
+
机器人回」永远是确定的:
|
|
71
|
+
|
|
72
|
+
```python
|
|
73
|
+
@app.on_message()
|
|
74
|
+
async def _(event):
|
|
75
|
+
await event.send(f'我是 {event.bot.name}')
|
|
76
|
+
|
|
77
|
+
# 主动推送要指名机器人
|
|
78
|
+
await app.bot('102134274').push(group_openid, '早安')
|
|
79
|
+
|
|
80
|
+
# 只连了一个时可以省略
|
|
81
|
+
await app.bot().push(group_openid, '早安')
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
`app.bots` 是 `{app_id: Bot}`。想广播就自己遍历。
|
|
85
|
+
|
|
86
|
+
## 三个发消息的方法
|
|
87
|
+
|
|
88
|
+
QQ 有两套互不相干的机制:`msg_id` 决定这条免不免费,`message_reference` 决定界面上
|
|
89
|
+
出不出引用气泡。
|
|
90
|
+
|
|
91
|
+
```python
|
|
92
|
+
await event.send('收到') # 回应这次事件。免费,无气泡
|
|
93
|
+
await event.reply('收到') # 同上,加引用气泡
|
|
94
|
+
await event.reply('看这条', target='REFIDX_x') # 引用别的消息
|
|
95
|
+
await event.push('推送') # 主动消息:两个都不带
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
| | `msg_id` | `message_reference` | 额度 |
|
|
99
|
+
|---|---|---|---|
|
|
100
|
+
| `send` | ✓ | — | 免费 |
|
|
101
|
+
| `reply` | ✓ | ✓ | 免费 |
|
|
102
|
+
| `push` | — | — | 烧主动消息额度,走审核 |
|
|
103
|
+
|
|
104
|
+
平台的规矩(moemoe 不代管,超了会拿到 QQ 的错误码):
|
|
105
|
+
|
|
106
|
+
- 凭据群聊 **5 分钟**、单聊 **60 分钟**内有效
|
|
107
|
+
- 同一条 `msg_id` 最多回 **5 次**(单聊 4 次)
|
|
108
|
+
- 引用的值是引用索引 `REFIDX_xxx`,不是 `message_id`,两者不通用
|
|
109
|
+
- QQ 不是每条消息都给引用索引,拿不到时 `reply` 的效果等同 `send`
|
|
110
|
+
|
|
111
|
+
### 推送时蹭一下还没过期的额度
|
|
112
|
+
|
|
113
|
+
```python
|
|
114
|
+
await app.bot().push(gid, '早安', borrow=True)
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
robot 会找一条这个会话还没过期的凭据,找到就免费。凭据来自这个会话最近那条消息,
|
|
118
|
+
跟你要推的内容未必有关系——本质上是借别人那条的额度,所以是显式选择。
|
|
119
|
+
|
|
120
|
+
`event.push` 没有这个开关:本会话要蹭就该用 `event.send`,那是精确的。
|
|
121
|
+
|
|
122
|
+
先问清楚再决定:
|
|
123
|
+
|
|
124
|
+
```python
|
|
125
|
+
out = await app.bot().call('GET', '/robot/passive', {'scene': gid})
|
|
126
|
+
if out['left']:
|
|
127
|
+
...
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
## 命令
|
|
131
|
+
|
|
132
|
+
```python
|
|
133
|
+
@app.command('weather', alias=('天气', 'w'), patterns=r'今天.*天气')
|
|
134
|
+
async def _(event, args):
|
|
135
|
+
"""查天气""" # docstring 进 command.doc
|
|
136
|
+
city = args.get(0, '北京')
|
|
137
|
+
return f'{city}今天晴'
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
`Moemoe(prefixes=('/', ''))` 表示带不带斜杠都认,`nicknames=('萌萌',)` 让「萌萌,天气」
|
|
141
|
+
也能触发。
|
|
142
|
+
|
|
143
|
+
`args` 认引号:`/say "a b" c` 拆成 `['a b', 'c']`。`args.rest()` 拿原文,
|
|
144
|
+
`args.int(0)` 按整数取。
|
|
145
|
+
|
|
146
|
+
### to_me:群里默认要求 @ 机器人
|
|
147
|
+
|
|
148
|
+
命令默认 `to_me=True`,群里必须 @ 机器人才触发。订了全量群消息时,别人聊天里打个
|
|
149
|
+
`/ping` 不会让机器人抢答。
|
|
150
|
+
|
|
151
|
+
**`to_me=True` 就是严格要求 @,昵称不算。** 两者的关系取决于你在 robot 那侧订了
|
|
152
|
+
哪些事件:
|
|
153
|
+
|
|
154
|
+
| robot 订的事件 | `to_me=True`(默认) | `to_me=False` |
|
|
155
|
+
|---|---|---|
|
|
156
|
+
| 只订 `GROUP_AT_MESSAGE_CREATE` | 每条都是 @ 来的,命令照常触发,昵称也能用 | 一样 |
|
|
157
|
+
| 订了 `GROUP_MESSAGE_CREATE` | **必须 @**,昵称触发不了 | 昵称、裸命令都能触发 |
|
|
158
|
+
|
|
159
|
+
也就是说,订了全量群消息又想用昵称触发的话,那几条命令要写 `to_me=False`:
|
|
160
|
+
|
|
161
|
+
```python
|
|
162
|
+
@app.command('ping', to_me=False) # 「萌萌,ping」和裸的「/ping」都能触发
|
|
163
|
+
async def _(event, args):
|
|
164
|
+
return 'pong'
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
单聊一律算冲机器人来的,不受影响。
|
|
168
|
+
|
|
169
|
+
权限谓词可以用 `&` `|` `~` 组合:
|
|
170
|
+
|
|
171
|
+
```python
|
|
172
|
+
from moemoe import GROUP_ADMIN, SUPERUSER
|
|
173
|
+
|
|
174
|
+
@app.command('kick', permission=GROUP_ADMIN | SUPERUSER, denied='你不行')
|
|
175
|
+
async def _(event, args): ...
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
`SUPERUSER` 读的是 `Moemoe(superusers=[...])`。
|
|
179
|
+
|
|
180
|
+
## 多轮问答
|
|
181
|
+
|
|
182
|
+
处理函数可以停下来等后续消息,状态留在局部变量里,不用维护一张「谁在玩」的表。
|
|
183
|
+
|
|
184
|
+
```python
|
|
185
|
+
from moemoe import SessionTimeout
|
|
186
|
+
|
|
187
|
+
@app.command('改名')
|
|
188
|
+
async def _(event, args):
|
|
189
|
+
try:
|
|
190
|
+
answer = await event.prompt('叫什么?', timeout=30)
|
|
191
|
+
except SessionTimeout:
|
|
192
|
+
return '等太久了'
|
|
193
|
+
return f'好的,以后叫你{answer.text}'
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
群级抢答用 `capture`,它既是异步迭代器也是异步上下文管理器:
|
|
197
|
+
|
|
198
|
+
```python
|
|
199
|
+
@app.command('猜数')
|
|
200
|
+
async def _(event, args):
|
|
201
|
+
answer = random.randint(1, 10)
|
|
202
|
+
async for guess in event.capture(scope='scene', timeout=20,
|
|
203
|
+
match=lambda one: one.text.isdigit()):
|
|
204
|
+
if int(guess.text) == answer:
|
|
205
|
+
return f'{guess.username} 答对了'
|
|
206
|
+
await guess.send('不对,再猜')
|
|
207
|
+
return f'没人猜出来,答案是 {answer}'
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
要点:
|
|
211
|
+
|
|
212
|
+
- `scope='user'`(默认)只等这个人,`scope='scene'` 等这个群/单聊里的任何人
|
|
213
|
+
- **`timeout` 是每条消息的等待上限**,收到一条就重新计时
|
|
214
|
+
- `async for` 在超时或关闭时正常结束,不用自己接异常;`prompt` 超时抛 `SessionTimeout`
|
|
215
|
+
- `match` 挡掉不相干的消息,被挡的照常走命令和事件处理函数
|
|
216
|
+
- **被会话接住的消息不再走命令**,否则一句「42」会既当答案又当命令
|
|
217
|
+
- 会话键带机器人 id,同一个人对不同机器人说话是不同的会话
|
|
218
|
+
- 挂着的会话数有上限,`Moemoe(sessions=1000)`,超了抛 `TooManySessions`
|
|
219
|
+
|
|
220
|
+
## 消息段
|
|
221
|
+
|
|
222
|
+
```python
|
|
223
|
+
from moemoe import At, Image, Keyboard, Markdown, Text
|
|
224
|
+
|
|
225
|
+
await event.send(Text('看图') + Image('https://...'))
|
|
226
|
+
await event.send(Markdown('## 标题\n正文 **加粗**') + Keyboard(rows=[...]))
|
|
227
|
+
await event.send(At('U9') + ' 在吗')
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
段帮你兜住 QQ 那三条互斥规矩:markdown 和纯文本互斥(文本段会并进 markdown 正文)、
|
|
231
|
+
一条消息只挂一个富媒体(多图拆成多条)、按钮挂在 markdown 上(只给按钮时正文包一层)。
|
|
232
|
+
|
|
233
|
+
`Keyboard(rows=...)` 原样透传给 QQ,字段照官方[消息按钮](https://bot.q.qq.com/wiki/develop/api-v2/server-inter/message/trans/msg-btn.html)文档填。
|
|
234
|
+
|
|
235
|
+
不想用糖就直接给 dict 当请求体:
|
|
236
|
+
|
|
237
|
+
```python
|
|
238
|
+
await event.send({'msg_type': 2, 'markdown': {'custom_template_id': 'tpl'}})
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
## 直接调官方接口
|
|
242
|
+
|
|
243
|
+
moemoe 不重新建模 QQ 的载荷。路径和字段照[腾讯的文档](https://bot.q.qq.com/wiki/develop/api-v2/)写:
|
|
244
|
+
|
|
245
|
+
```python
|
|
246
|
+
await event.call('GET', f'/v2/groups/{gid}/members')
|
|
247
|
+
await event.bot.call('POST', f'/v2/groups/{gid}/batch_remove_members',
|
|
248
|
+
{'member_openids': ['xxx']})
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
事件也一样,类型化访问只是取值方便,**`event.raw` 是 QQ 的原文**:
|
|
252
|
+
|
|
253
|
+
```python
|
|
254
|
+
event.group_openid # 好写
|
|
255
|
+
event.raw # QQ 给什么这里就有什么
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
4xx / 5xx 抛 `CallError`,里面带着 QQ 的原始错误体和 trace:
|
|
259
|
+
|
|
260
|
+
```python
|
|
261
|
+
from moemoe import CallError
|
|
262
|
+
|
|
263
|
+
try:
|
|
264
|
+
await event.call('GET', f'/v2/groups/{gid}/members')
|
|
265
|
+
except CallError as error:
|
|
266
|
+
error.status # HTTP 状态码,robot 自己出错时是 0
|
|
267
|
+
error.body # QQ 的响应体,err_code 在里面
|
|
268
|
+
error.trace # 报障要附的链路号
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
## 钩子
|
|
272
|
+
|
|
273
|
+
```python
|
|
274
|
+
@app.on_startup
|
|
275
|
+
async def _():
|
|
276
|
+
... # 在事件循环里跑
|
|
277
|
+
|
|
278
|
+
@app.on_shutdown
|
|
279
|
+
async def _():
|
|
280
|
+
... # 关服前收尾,会话会先被关掉
|
|
281
|
+
|
|
282
|
+
@app.on_bot_connect
|
|
283
|
+
async def _(bot):
|
|
284
|
+
logger.info('{} 上线', bot) # 钩子里可以调接口
|
|
285
|
+
|
|
286
|
+
@app.on_bot_disconnect
|
|
287
|
+
async def _(bot):
|
|
288
|
+
...
|
|
289
|
+
|
|
290
|
+
@app.on_error
|
|
291
|
+
async def _(failure):
|
|
292
|
+
failure.error # 抛出来的异常
|
|
293
|
+
failure.event # 出错时手上那条事件,没有就是 None
|
|
294
|
+
failure.handler # 哪个处理函数
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
钩子出错只记一行,不会影响别的钩子,也不会打断连接。
|
|
298
|
+
|
|
299
|
+
## HTTP 路由
|
|
300
|
+
|
|
301
|
+
跟 WebSocket 那条路共用端口和进程:
|
|
302
|
+
|
|
303
|
+
```python
|
|
304
|
+
from starlette.responses import JSONResponse
|
|
305
|
+
|
|
306
|
+
@app.route('/healthz')
|
|
307
|
+
async def _(request):
|
|
308
|
+
return JSONResponse({'bots': len(app.bots)})
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
不用装额外的东西,starlette 本来就是核心依赖。
|
|
312
|
+
|
|
313
|
+
## 定时任务
|
|
314
|
+
|
|
315
|
+
可选模块,先装:
|
|
316
|
+
|
|
317
|
+
```bash
|
|
318
|
+
pip install "moemoe[scheduler]"
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
```python
|
|
322
|
+
from moemoe.scheduler import Scheduler
|
|
323
|
+
|
|
324
|
+
scheduler = Scheduler(app) # 自动挂 startup / shutdown
|
|
325
|
+
|
|
326
|
+
@scheduler.scheduled_job('cron', hour=7, minute=15, id='daily')
|
|
327
|
+
async def _():
|
|
328
|
+
await app.bot().push(group_openid, '早上好')
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
调度器不是 moemoe 写的——cron 解析、时区、夏令时、错过补发,APScheduler 已经写对
|
|
332
|
+
十几年了。这个模块只解决接线:什么时候 `start()`、什么时候 `shutdown()`、
|
|
333
|
+
没装时报一句人话。认不出的属性转给底层的 `AsyncIOScheduler`,`add_job` /
|
|
334
|
+
`get_jobs` / `pause` 照 APScheduler 的文档写。
|
|
335
|
+
|
|
336
|
+
只支持 APScheduler 3.x。
|
|
337
|
+
|
|
338
|
+
## 插件
|
|
339
|
+
|
|
340
|
+
应用对象单独放一个模块,插件从那儿 import:
|
|
341
|
+
|
|
342
|
+
```
|
|
343
|
+
myrobot/
|
|
344
|
+
app.py app = Moemoe(...)
|
|
345
|
+
main.py from app import app; load_plugins('plugins'); app.run()
|
|
346
|
+
plugins/
|
|
347
|
+
__init__.py
|
|
348
|
+
ping.py from app import app → @app.command('ping')
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
```python
|
|
352
|
+
from moemoe import load_plugins
|
|
353
|
+
load_plugins('plugins')
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
## 日志
|
|
357
|
+
|
|
358
|
+
`from moemoe import logger` 拿到的就是 loguru 的全局 logger 原样再导出。
|
|
359
|
+
`logger.disable('moemoe')` 让库彻底闭嘴。
|
|
360
|
+
|
|
361
|
+
## 跑测试
|
|
362
|
+
|
|
363
|
+
不需要装任何东西:
|
|
364
|
+
|
|
365
|
+
```bash
|
|
366
|
+
python -m unittest discover -s tests -t .
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
装了 pytest 的话 `pytest` 也直接认。
|
|
370
|
+
|
|
371
|
+
## 状态
|
|
372
|
+
|
|
373
|
+
**0.0.1,第一个版本,API 还会动。** 整条链路实连验证过:QQ 收消息 → robot 转发
|
|
374
|
+
→ moemoe 触发命令 → 回复发出去。
|
|
375
|
+
|
|
376
|
+
没做的:命令中间件。
|
|
377
|
+
|
|
378
|
+
铁律见 [RULES.md](https://github.com/nicemoe/moemoe/blob/main/RULES.md),设计理由见 [docs/design.md](https://github.com/nicemoe/moemoe/blob/main/docs/design.md)。
|