Compare commits

...

1690 Commits

Author SHA1 Message Date
noneflow[bot]
f2b0b1752b 🔖 Release 2.0.1 2023-07-23 08:24:19 +00:00
Ju4tCode
81dcc65f99 🔖 bump version 2.0.1 (#2209) 2023-07-23 16:21:58 +08:00
noneflow[bot]
ac90df929e 📝 Update changelog 2023-07-21 14:38:47 +00:00
Tarrailt
555268239f 📝 Docs: 移动 Alconna 文档至最佳实践 (#2208)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Ju4tCode <42488585+yanyongyu@users.noreply.github.com>
2023-07-21 22:37:34 +08:00
noneflow[bot]
7009c8e8c1 📝 Update changelog 2023-07-21 12:53:17 +00:00
Jigsaw
2f3cc84f82 📝 Docs: 移除商店中不符合现规范的 tag (#2205) 2023-07-21 20:51:48 +08:00
noneflow[bot]
9444e01f0f 📝 Update changelog 2023-07-21 08:57:14 +00:00
NCBM
23b7a94b9a 🍻 publish plugin 方寸狭间 (#2206) 2023-07-21 08:56:06 +00:00
noneflow[bot]
70ece41b66 📝 Update changelog 2023-07-19 05:23:59 +00:00
Rockytkg
a5bb6e4220 🍻 publish plugin DALL-E绘图 (#2200) 2023-07-19 05:22:34 +00:00
noneflow[bot]
4fc99771c5 📝 Update changelog 2023-07-19 02:49:00 +00:00
canxin121
6601def5f7 🍻 publish plugin 指定戳一戳 (#2201) 2023-07-19 02:47:51 +00:00
noneflow[bot]
b2edea141e 📝 Update changelog 2023-07-19 02:15:58 +00:00
Ju4tCode
38886b9651 📝 Docs: 添加 scoped 插件配置指南 (#2198) 2023-07-19 10:14:36 +08:00
noneflow[bot]
1b225cbbca 📝 Update changelog 2023-07-18 05:36:22 +00:00
canxin121
b4f004c500 🍻 publish plugin templates_render (#2196) 2023-07-18 05:34:54 +00:00
noneflow[bot]
7a345714aa 📝 Update changelog 2023-07-17 12:43:28 +00:00
Ju4tCode
cb9fcae64c 🧑‍💻 Develop: 添加 Pyright 检查 (#2194) 2023-07-17 20:42:15 +08:00
noneflow[bot]
6ebeefed79 📝 Update changelog 2023-07-17 07:57:36 +00:00
Ju4tCode
6dc87a9455 use typing.override instead (#2193) 2023-07-17 15:56:27 +08:00
noneflow[bot]
7dd7c927bf 📝 Update changelog 2023-07-17 07:02:34 +00:00
Ju4tCode
e167865686 🐛 fix quart context error (#2192) 2023-07-17 15:01:21 +08:00
noneflow[bot]
29364679c4 📝 Update changelog 2023-07-16 13:43:30 +00:00
LambdaYH
ebbe8beec0 🍻 publish bot 米缸 (#2190) 2023-07-16 13:42:16 +00:00
noneflow[bot]
a04580e79e 📝 Update changelog 2023-07-14 16:28:15 +00:00
Well2333
bfe9e7e253 🍻 publish plugin MongoDB (#2188) 2023-07-14 16:26:50 +00:00
noneflow[bot]
720398198f 📝 Update changelog 2023-07-12 14:19:44 +00:00
Agnes4m
5ebf349886 🍻 publish plugin pjsk表情 (#2186) 2023-07-12 14:18:32 +00:00
noneflow[bot]
f8f5750c3b 📝 Update changelog 2023-07-11 15:16:11 +00:00
Q1351998764
8d9be61406 🍻 publish plugin nonebot-plugin-wenan (#2183) 2023-07-11 15:15:01 +00:00
noneflow[bot]
42ea650509 📝 Update changelog 2023-07-11 12:15:30 +00:00
mute23-code
a941a0f292 🍻 publish bot 林汐 (#2181) 2023-07-11 12:14:17 +00:00
noneflow[bot]
89f8745425 📝 Update changelog 2023-07-11 11:19:00 +00:00
Q1351998764
cc476528d8 🍻 publish plugin nonebot-plugin-picture-api (#2179) 2023-07-11 11:17:50 +00:00
noneflow[bot]
64f6c2dd4c 📝 Update changelog 2023-07-11 05:23:32 +00:00
MerCuJerry
81d9531b42 🍻 publish plugin Blocker (#2177) 2023-07-11 05:22:13 +00:00
noneflow[bot]
3512b0ab98 📝 Update changelog 2023-07-10 15:48:44 +00:00
Lptr-byte
ab3e916770 🍻 publish plugin nonebot-plugin-nobahpicture (#2175) 2023-07-10 15:47:23 +00:00
noneflow[bot]
21376a5bfa 📝 Update changelog 2023-07-08 07:35:00 +00:00
Akirami
5046b2a86e 📝 Docs: 钩子函数代码片段补充 (#2173) 2023-07-08 15:33:45 +08:00
noneflow[bot]
910c768910 📝 Update changelog 2023-07-08 07:27:52 +00:00
Akirami
5a526ddb40 📝 Docs: 格式化钩子函数中的代码片段 (#2172) 2023-07-08 15:26:30 +08:00
noneflow[bot]
4c5c97dca6 📝 Update changelog 2023-07-08 07:04:46 +00:00
Akirami
b3e0fb4830 ✏️ Plugin: 黑白名单添加标签 (#2170) 2023-07-08 15:03:35 +08:00
noneflow[bot]
258aa7d2d7 📝 Update changelog 2023-07-08 05:43:41 +00:00
A-kirami
5c72fd5ba7 🍻 publish plugin 过期事件过滤器 (#2168) 2023-07-08 05:42:28 +00:00
noneflow[bot]
26e4f23a67 📝 Update changelog 2023-07-08 03:41:39 +00:00
HuParry
28fc6c35f0 🍻 publish plugin 猫猫虫咖波图片发送 (#2166) 2023-07-08 03:40:25 +00:00
noneflow[bot]
3ef1d7d5d7 📝 Update changelog 2023-07-08 02:23:24 +00:00
Cypas
8474d8987e 🍻 publish plugin nonebot-plugin-splatoon3 (#2163) 2023-07-08 02:22:08 +00:00
noneflow[bot]
13ddfa1bdd 📝 Update changelog 2023-07-07 17:33:46 +00:00
coyude
ec8be10f26 🍻 publish plugin nonebot-plugin-cfassistant (#2162) 2023-07-07 17:32:16 +00:00
noneflow[bot]
511c521a68 📝 Update changelog 2023-07-07 13:28:22 +00:00
HuParry
0ef5940d0f 🍻 publish plugin 算法竞赛比赛查询 (#2158) 2023-07-07 13:26:51 +00:00
noneflow[bot]
eecc881cd8 📝 Update changelog 2023-07-07 03:29:19 +00:00
eya46
770141cf0a 📝 Docs: 补充 Message.only 文档 (#2155) 2023-07-07 11:28:08 +08:00
noneflow[bot]
b2b20ffc4a 📝 Update changelog 2023-07-06 07:29:30 +00:00
eya46
94a6067a4b Feature: 补充响应器组属性 (#2154)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2023-07-06 15:18:08 +08:00
noneflow[bot]
77220d9d1f 📝 Update changelog 2023-07-05 15:57:33 +00:00
djkcyl
647ad9ff8f 🍻 publish plugin nonebot-plugin-update (#2152) 2023-07-05 15:56:15 +00:00
pre-commit-ci[bot]
04182eefba ⬆️ auto update by pre-commit hooks (#2149)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2023-07-04 16:39:12 +08:00
noneflow[bot]
7b4aa08c54 📝 Update changelog 2023-07-04 02:47:02 +00:00
eya46
0033d7c686 🐛 Fix: 修复 dotenv 配置项为 None 将会跳过赋值 (#2143)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Ju4tCode <42488585+yanyongyu@users.noreply.github.com>
2023-07-04 10:45:55 +08:00
noneflow[bot]
c40b95f3e9 📝 Update changelog 2023-07-03 02:29:35 +00:00
Fireinsect
1fa44ca5c1 ✏️ Plugin: 修改 nonebot-plugin-ocgbot-v2 插件名称 (#2147) 2023-07-03 10:28:27 +08:00
noneflow[bot]
381f6633f6 📝 Update changelog 2023-07-03 02:18:44 +00:00
Agnes4m
d617508e32 🍻 publish plugin 远程同意好友 (#2145) 2023-07-03 02:17:36 +00:00
noneflow[bot]
8248e88686 📝 Update changelog 2023-07-02 14:28:26 +00:00
canxin
25649373a6 ✏️ Plugin: 更新 SparkGPT 插件描述 (#2144)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2023-07-02 22:27:13 +08:00
noneflow[bot]
3bee189598 📝 Update changelog 2023-07-01 07:41:39 +00:00
eya46
c1b1742b20 Feature: CommandGroup 支持命令别名添加前缀选项 (#2134)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2023-07-01 15:40:30 +08:00
noneflow[bot]
3e826cab72 📝 Update changelog 2023-07-01 05:55:19 +00:00
Fireinsect
4ef4bb0042 ✏️ Plugin: 修改 nonebot-plugin-ocgbot-v2 插件名称 (#2141) 2023-07-01 13:54:09 +08:00
Agnes4m
25ac653623 🍻 publish plugin 戳一戳事件 (#2138) 2023-06-30 12:45:13 +00:00
noneflow[bot]
b35bdfe6dc 📝 Update changelog 2023-06-30 12:44:37 +00:00
17TheWord
f06efca8cc 📝 Docs: 修复日志自定义文档 typo (#2140) 2023-06-30 20:43:26 +08:00
noneflow[bot]
a899523607 📝 Update changelog 2023-06-29 02:27:59 +00:00
lgc2333
2c162335cb 🍻 publish plugin EitherChoice (#2136) 2023-06-29 02:26:32 +00:00
noneflow[bot]
3a12984d4b 📝 Update changelog 2023-06-28 12:23:53 +00:00
MeetWq
7211f24a7d 🍻 publish plugin 用户信息 (#2132) 2023-06-28 12:22:35 +00:00
noneflow[bot]
649624ed80 📝 Update changelog 2023-06-27 08:26:35 +00:00
worldmozara
c03ff4e676 Feature: 添加用于动态继承支持适配器数据的方法 (#2127) 2023-06-27 16:25:27 +08:00
noneflow[bot]
0b5a18cb63 📝 Update changelog 2023-06-27 02:13:28 +00:00
wsdtl
518bf16082 🍻 publish bot web_bot (#2129) 2023-06-27 02:12:19 +00:00
noneflow[bot]
b625a5d19a 📝 Update changelog 2023-06-26 05:27:55 +00:00
kexue
acca22e179 ✏️ Plugin: 删除 nonebot-plugin-phlogo (#2128) 2023-06-26 13:26:26 +08:00
noneflow[bot]
a3009d45dc 📝 Update changelog 2023-06-25 15:02:27 +00:00
QBkira
fd3d1bb115 🍻 publish plugin Diablo4地狱狂潮boss提醒小助手 (#2121) 2023-06-25 15:01:22 +00:00
noneflow[bot]
7282da8b04 📝 Update changelog 2023-06-25 03:30:28 +00:00
eya46
7a3c7476fb 📝 Docs: 修复依赖注入文档 ArgStr 3.9+ 和 3.8+ 版本代码写反 (#2126)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2023-06-25 11:29:10 +08:00
noneflow[bot]
f1046cfb11 📝 Update changelog 2023-06-24 11:19:31 +00:00
eya46
8de25447b3 🐛 Fix: 修复 ArgParam 不支持 Annotated (#2124)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2023-06-24 19:18:24 +08:00
noneflow[bot]
3cdbf35dc6 📝 Update changelog 2023-06-24 09:06:04 +00:00
Agnes Digital
0228e255e1 ✏️ Plugin: 修改 nonebot-plugin-gw2 模块名 (#2123)
Co-authored-by: Agnes Digital <Z735803792@163.com>
2023-06-24 17:04:50 +08:00
noneflow[bot]
353d16ebfd 📝 Update changelog 2023-06-24 06:48:39 +00:00
Ju4tCode
3d5dd5969c 🚨 Develop: 添加 ruff linter (#2114) 2023-06-24 14:47:35 +08:00
noneflow[bot]
fe21cbfa1d 📝 Update changelog 2023-06-23 14:00:26 +00:00
fireinsect
c20f65636f 🍻 publish plugin nonbot-plugin-ocgbot-v2 (#2118) 2023-06-23 13:59:13 +00:00
noneflow[bot]
eade8face6 📝 Update changelog 2023-06-23 12:28:20 +00:00
worldmozara
ab75133e9d ✏️ Plugin: 更新 nonebot-plugin-msgbuf 插件的名称等信息 (#2119) 2023-06-23 20:27:08 +08:00
noneflow[bot]
89596fb708 📝 Update changelog 2023-06-22 02:42:23 +00:00
ssttkkl
eedcf0779d 🍻 publish plugin 错误告警 (#2116) 2023-06-22 02:41:06 +00:00
noneflow[bot]
05333260b7 📝 Update changelog 2023-06-21 05:28:42 +00:00
Agnes Digital
55fd447230 ✏️ Plugin: 修改插件信息和仓库地址 (#2115)
Co-authored-by: Agnes Digital <Z735803792@163.com>
2023-06-21 13:27:37 +08:00
noneflow[bot]
263e6b25e2 📝 Update changelog 2023-06-20 05:51:13 +00:00
Ju4tCode
e00890033e add plugin metadata to builtin plugins (#2113) 2023-06-20 13:50:05 +08:00
noneflow[bot]
20d3d62bd5 📝 Update changelog 2023-06-19 09:50:07 +00:00
Ju4tCode
080b876d93 👷 Test: 移除 httpbin 并整理测试 (#2110) 2023-06-19 17:48:59 +08:00
noneflow[bot]
27a3d1f0bb 📝 Update changelog 2023-06-19 08:48:59 +00:00
CMHopeSunshine
7a47985c2b 🍻 publish plugin follow_withdraw (#2111) 2023-06-19 08:47:51 +00:00
noneflow[bot]
8d97081948 📝 Update changelog 2023-06-18 13:17:46 +00:00
ThirdBlood
f4ffa07c8b 🍻 publish bot ReimeiBot-黎明机器人 (#2106) 2023-06-18 13:16:43 +00:00
noneflow[bot]
1b1ddc5c0f 📝 Update changelog 2023-06-14 09:07:00 +00:00
uy/sun
30dbd270a6 👷 CI: 缓存 NoneFlow 所需的 pre-commit hooks (#2104) 2023-06-14 17:05:52 +08:00
noneflow[bot]
7d3c7c4933 📝 Update changelog 2023-06-14 05:05:52 +00:00
0Neptune0
8c8436a94f 🍻 publish plugin 战雷查水表 (#2102) 2023-06-14 05:04:44 +00:00
noneflow[bot]
8601942ed3 📝 Update changelog 2023-06-13 13:46:01 +00:00
pre-commit-ci[bot]
4cc958ca17 🚨 auto fix by pre-commit hooks 2023-06-13 13:44:48 +00:00
SuperGuGuGu
472a2c7866 🍻 publish plugin bili_push (#2100) 2023-06-13 13:44:48 +00:00
noneflow[bot]
222609182e 📝 Update changelog 2023-06-12 13:19:50 +00:00
forchannot
dccf2f3ca8 🔥 Docs: 删除商店插件发布多余模块 (#2095)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2023-06-12 21:18:25 +08:00
noneflow[bot]
156807c365 📝 Update changelog 2023-06-12 12:40:57 +00:00
worldmozara
50941f5259 📝 Docs: 微调插件元数据的部分描述 (#2096) 2023-06-12 20:39:28 +08:00
noneflow[bot]
2de1524a89 📝 Update changelog 2023-06-11 15:49:49 +00:00
uy/sun
bdd17b62cc Feature: 插件商店适配最新的插件元数据 (#2094)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2023-06-11 23:48:37 +08:00
noneflow[bot]
3a9e800a58 📝 Update changelog 2023-06-11 15:42:26 +00:00
worldmozara
cb8d48c362 📝 Docs: 完成发布插件教程 (#2078)
Co-authored-by: Ju4tCode <42488585+yanyongyu@users.noreply.github.com>
Co-authored-by: uy/sun <hmy0119@hotmail.com>
2023-06-11 23:41:16 +08:00
noneflow[bot]
a5981c05d5 📝 Update changelog 2023-06-11 13:06:02 +00:00
worldmozara
4cb87e596d 📝 Docs: 更新插件元数据的相关描述 (#2087) 2023-06-11 21:04:58 +08:00
noneflow[bot]
2725a0a324 📝 Update changelog 2023-06-11 07:34:45 +00:00
Ju4tCode
f6b0809e5f Feature: 依赖注入支持 Generic TypeVar 和 Matcher 重载 (#2089) 2023-06-11 15:33:33 +08:00
noneflow[bot]
6181c1760f 📝 Update changelog 2023-06-11 07:00:08 +00:00
Jigsaw
324277091c 🐛 Fix: aiohttp 请求时 data 和 file 不能同时存在 (#2088) 2023-06-11 14:59:05 +08:00
noneflow[bot]
6eef863b70 📝 Update changelog 2023-06-11 06:50:18 +00:00
Alpaca4610
7d52f5af4d 🍻 publish plugin AI作曲 (#2092) 2023-06-11 06:48:53 +00:00
noneflow[bot]
0a70721ec0 📝 Update changelog 2023-06-11 04:47:10 +00:00
reine-ishyanami
f430f061ec 🍻 publish plugin pcrjjc (#2090) 2023-06-11 04:46:06 +00:00
noneflow[bot]
572be1eb47 📝 Update changelog 2023-06-07 09:33:36 +00:00
惜月
29cf7de1a6 📝 add Villa adapter to README (#2086) 2023-06-07 17:32:31 +08:00
noneflow[bot]
c61e3cab90 📝 Update changelog 2023-06-06 16:23:08 +00:00
CMHopeSunshine
77bdc5ecba 🍻 publish adapter 大别野 (#2084) 2023-06-06 16:22:06 +00:00
pre-commit-ci[bot]
16054d18c6 ⬆️ auto update by pre-commit hooks (#2083)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2023-06-06 17:22:45 +08:00
noneflow[bot]
f0361295c3 📝 Update changelog 2023-06-05 09:18:27 +00:00
nek0us
9bd1964ae2 🍻 publish plugin twitter订阅 (#2081) 2023-06-05 09:17:09 +00:00
noneflow[bot]
9141c88f77 📝 Update changelog 2023-06-03 14:46:48 +00:00
DiheChen
491855876b 🐛 Fix: 修复因 loguru 更新导致的启动和关闭日志 name 不正常 (#2080)
Co-authored-by: Ju4tCode <42488585+yanyongyu@users.noreply.github.com>
2023-06-03 22:45:46 +08:00
noneflow[bot]
6df28dd2a8 📝 Update changelog 2023-06-03 03:28:06 +00:00
ssttkkl
142d0f4d95 🍻 publish plugin 链接防夹 (#2073) 2023-06-03 03:27:04 +00:00
noneflow[bot]
0127d765ae 📝 Update changelog 2023-06-02 10:14:15 +00:00
Bill Chen
207c6b3c15 ✏️ Plugin: 移除过时未更新的插件&Bot (#2072) 2023-06-02 18:13:04 +08:00
noneflow[bot]
d2e699a13a 📝 Update changelog 2023-06-02 10:10:24 +00:00
Agnes4m
ce9ba7dd9b 🍻 publish plugin 碧蓝航线攻略 (#2075) 2023-06-02 10:09:13 +00:00
noneflow[bot]
2af23c9d89 📝 Update changelog 2023-06-01 15:55:58 +00:00
BalconyJH
8ee0f5efc4 ✏️ Plugin: 删除插件 nonebot_plugin_r6s (#2071) 2023-06-01 23:54:46 +08:00
noneflow[bot]
8dcfe92f13 🔖 Release 2.0.0 2023-06-01 06:26:07 +00:00
Ju4tCode
f3d5c1f226 🔖 Release: v2.0.0 (#2070) 2023-06-01 14:18:16 +08:00
noneflow[bot]
8af21f6e76 📝 Update changelog 2023-05-31 14:35:34 +00:00
Tarrailt
9bf3dc4274 📝 Docs: 添加 Alconna 响应器介绍 (#2069)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2023-05-31 22:34:10 +08:00
noneflow[bot]
40d2f975cb 📝 Update changelog 2023-05-30 08:18:36 +00:00
Ju4tCode
784ba287aa 📝 Docs: 更新 README 适配器链接 (#2068) 2023-05-30 16:17:24 +08:00
noneflow[bot]
3b9cf6cc51 📝 Update changelog 2023-05-30 07:21:42 +00:00
Ju4tCode
f52abc8314 Feature: 优化事件分发方法 (#2067) 2023-05-30 15:20:31 +08:00
noneflow[bot]
3199fc454a 📝 Update changelog 2023-05-28 14:00:27 +00:00
DiaoDaiaChan
738f8cae3b 🍻 publish plugin stablediffusion绘画插件 (#2065) 2023-05-28 13:59:10 +00:00
noneflow[bot]
9406c117a6 📝 Update changelog 2023-05-28 13:20:29 +00:00
Ikaros-521
7b0e62c128 🍻 publish plugin 随机抽取自定义内容 (#2063) 2023-05-28 13:19:05 +00:00
noneflow[bot]
5d0d91b87b 📝 Update changelog 2023-05-28 11:34:55 +00:00
ssttkkl
ed687d8ff6 🍻 publish plugin NAGA公交车 (#2061) 2023-05-28 11:33:36 +00:00
noneflow[bot]
cc214c320d 📝 Update changelog 2023-05-24 16:10:20 +00:00
Xie-Tiao
0897f3b0f7 🍻 publish plugin 本子标题关键词提取 (#2055) 2023-05-24 16:09:01 +00:00
noneflow[bot]
7986c45b3f 📝 Update changelog 2023-05-24 09:54:54 +00:00
Akirami
8ea0241aa2 ✏️ Plugin: Hello World 添加 tag (#2056) 2023-05-24 17:53:28 +08:00
noneflow[bot]
fabc2faa4c 📝 Update changelog 2023-05-24 09:39:52 +00:00
Akirami
3216479530 ✏️ 修改 nonebot-plugin-logpile 的名称和描述 (#2057) 2023-05-24 17:38:06 +08:00
noneflow[bot]
6c66a54223 📝 Update changelog 2023-05-24 03:01:50 +00:00
initialencounter
e760290beb 🍻 publish plugin puzzle (#2053) 2023-05-24 03:00:26 +00:00
noneflow[bot]
3beefdff72 📝 Update changelog 2023-05-24 02:53:49 +00:00
Special-Week
104f610ea7 🍻 publish plugin homo_mathematician (#2051) 2023-05-24 02:51:54 +00:00
noneflow[bot]
2b337e1310 📝 Update changelog 2023-05-23 16:06:23 +00:00
initialencounter
b78455f910 🍻 publish plugin cuber (#2045) 2023-05-23 16:05:13 +00:00
noneflow[bot]
c3d6e20120 📝 Update changelog 2023-05-23 15:56:26 +00:00
synodriver
b32af0f6ba 🍻 publish plugin nonebot-plugin-lua (#2047) 2023-05-23 15:55:09 +00:00
noneflow[bot]
3469b0dbb7 📝 Update changelog 2023-05-23 02:26:54 +00:00
ElainaFanBoy
0fd7396665 🍻 publish plugin Github仓库卡片 (#2041) 2023-05-23 02:25:40 +00:00
noneflow[bot]
c6f41e1975 📝 Update changelog 2023-05-21 16:01:58 +00:00
Ju4tCode
2cfc20c143 🐛 fix require new plugin context error (#2040) 2023-05-22 00:00:50 +08:00
noneflow[bot]
99197f30f6 📝 Update changelog 2023-05-21 08:03:01 +00:00
Ju4tCode
aa48299d5d improve dependency injection params (#2034) 2023-05-21 16:01:55 +08:00
noneflow[bot]
dd80191761 📝 Update changelog 2023-05-20 09:32:41 +00:00
canxin
34c1c33996 ✏️ Plugin: 移除 nonebot_paddle_ocrnonebot_poe_chat (#2039) 2023-05-20 17:31:20 +08:00
noneflow[bot]
2dcbce9cd7 📝 Update changelog 2023-05-20 06:02:19 +00:00
MingxuanGame
c4fbd1cac3 🔥 remove plugin nonebot-plugin-rtfm (#2037) 2023-05-20 14:01:16 +08:00
noneflow[bot]
575e3fb920 📝 Update changelog 2023-05-20 03:12:19 +00:00
Calanosay
50fd4acccb 🍻 publish plugin 股票看盘助手 (#2031) 2023-05-20 03:11:15 +00:00
noneflow[bot]
f9e214de93 📝 Update changelog 2023-05-19 09:42:59 +00:00
xi-yue-233
f28d354875 🍻 publish plugin 便携插件安装器 (#2026) 2023-05-19 09:41:47 +00:00
noneflow[bot]
648b838a75 📝 Update changelog 2023-05-19 09:10:44 +00:00
worldmozara
157fe31051 ✏️ Plugin: 移除 extrautils 工具拓展插件(暂停维护) (#2033) 2023-05-19 17:09:33 +08:00
noneflow[bot]
170fb94896 📝 Update changelog 2023-05-18 08:01:35 +00:00
Ju4tCode
9616b4c0ca Feature: 添加插件元数据字段 type homepage supported_adapters (#2012) 2023-05-18 16:00:10 +08:00
noneflow[bot]
0c4a040394 📝 Update changelog 2023-05-16 14:55:41 +00:00
MeetWq
8592e77e15 🍻 publish plugin 会话 id (#2024) 2023-05-16 14:54:33 +00:00
noneflow[bot]
fc8850496e 📝 Update changelog 2023-05-16 08:44:08 +00:00
evan-gyy
227afbfd8d 🍻 publish plugin SD绘画插件 (#2022) 2023-05-16 08:42:50 +00:00
noneflow[bot]
4672af12fe 📝 Update changelog 2023-05-16 08:38:57 +00:00
xi-yue-233
079996d936 🍻 publish plugin 《女神异闻录5》预告信生成器 (#2016) 2023-05-16 08:37:36 +00:00
noneflow[bot]
bd9b05b990 📝 Update changelog 2023-05-15 07:01:11 +00:00
chaichaisi
f2f3f7ab8e 🍻 publish plugin 小小的WEBAPI调用插件 (#2019) 2023-05-15 06:59:55 +00:00
noneflow[bot]
22222e79b6 📝 Update changelog 2023-05-15 04:42:14 +00:00
lgc2333
55164e8ece 🍻 publish plugin MultiNCM (#2017) 2023-05-15 04:41:11 +00:00
noneflow[bot]
336822ff5c 📝 Update changelog 2023-05-14 14:44:54 +00:00
zhulinyv
82e8417b9a 🍻 publish plugin 签到 (#2013) 2023-05-14 14:43:48 +00:00
noneflow[bot]
9c0ecb441f 📝 Update changelog 2023-05-13 10:10:24 +00:00
mute23-code
0c0fabcb89 🍻 publish plugin 链接解析 (#1959) 2023-05-13 10:09:14 +00:00
noneflow[bot]
202f437aea 📝 Update changelog 2023-05-13 07:33:31 +00:00
Ju4tCode
a8b06aa7c7 publish to store using issue form (#2010) 2023-05-13 15:32:28 +08:00
noneflow[bot]
a5e634319a 📝 Update changelog 2023-05-13 03:27:01 +00:00
bingqiu456
6d1262f402 🍻 publish bot 狐尾 (#2007) 2023-05-13 03:25:46 +00:00
noneflow[bot]
a5fd182bd0 📝 Update changelog 2023-05-13 02:31:25 +00:00
NCBM
771cf8bdcf 🍻 publish plugin 信鸽巴夫 (#2006) 2023-05-13 02:29:55 +00:00
noneflow[bot]
56304aea8d 📝 Update changelog 2023-05-12 15:22:56 +00:00
RF-Tar-Railt
c6e69ddc17 🍻 publish plugin 明日方舟抽卡模拟 (#2004) 2023-05-12 15:21:31 +00:00
noneflow[bot]
ae55ec3e1b 📝 Update changelog 2023-05-11 14:56:56 +00:00
Well2333
f72243304f 🍻 publish plugin 雷神工业 (#2002) 2023-05-11 14:55:42 +00:00
noneflow[bot]
5425180aec 📝 Update changelog 2023-05-10 10:34:12 +00:00
A-kirami
a496db4ddf 🍻 publish plugin nonebot-plugin-logpile (#1998) 2023-05-10 10:33:02 +00:00
noneflow[bot]
26bad8eb4b 📝 Update changelog 2023-05-10 10:10:30 +00:00
canxin121
f526080611 🍻 publish plugin Spark-GPT (#1996) 2023-05-10 10:09:04 +00:00
noneflow[bot]
6c269825c9 📝 Update changelog 2023-05-09 03:41:33 +00:00
AzideCupric
17959b7056 🍻 publish plugin 企鹅物流统计数据查询 (#1994) 2023-05-09 03:40:32 +00:00
noneflow[bot]
17a8ed379a 📝 Update changelog 2023-05-07 12:33:29 +00:00
863109569
163e5001d3 🍻 publish bot ay机器人 (#1992) 2023-05-07 12:32:26 +00:00
noneflow[bot]
5d27646ef9 📝 Update changelog 2023-05-07 03:21:34 +00:00
lgc2333
38be147e8a 🍻 publish plugin CallAPI (#1989) 2023-05-07 03:20:30 +00:00
noneflow[bot]
93829aeb80 📝 Update changelog 2023-05-07 03:11:18 +00:00
ZM25XC
9098dbae9a 🍻 publish plugin 群聊人数锁定 (#1987) 2023-05-07 03:10:13 +00:00
noneflow[bot]
9edc51c2a4 📝 Update changelog 2023-05-07 03:06:01 +00:00
roiiiu
f1aa2b1cb2 🍻 publish plugin CSGO开箱模拟器 (#1985) 2023-05-07 03:04:51 +00:00
noneflow[bot]
bbb2cb3a2c 📝 Update changelog 2023-05-06 17:04:48 +00:00
Lptr-byte
7a0a32398b 📝 Docs: 修复获取事件信息文档代码范例中的高亮行 (#1983) 2023-05-07 01:03:43 +08:00
noneflow[bot]
272ed8e85c 📝 Update changelog 2023-05-06 16:57:35 +00:00
Lptr-byte
e308d4cfac Docs: 修复事件处理函数文档代码范例中缺失的 import (#1982) 2023-05-07 00:56:22 +08:00
noneflow[bot]
0162360cfe 📝 Update changelog 2023-05-06 16:14:46 +00:00
Lptr-byte
4ba4c0bebc 📝 Docs: 修复获取事件信息文档代码范例中缺失的 import (#1980)
Co-authored-by: Ju4tCode <42488585+yanyongyu@users.noreply.github.com>
2023-05-07 00:13:44 +08:00
noneflow[bot]
4b0b1e69a4 📝 Update changelog 2023-05-05 13:05:45 +00:00
mobyw
22c0f81054 🍻 publish bot March7th (#1977) 2023-05-05 13:04:32 +00:00
noneflow[bot]
2494e615fd 📝 Update changelog 2023-05-05 11:34:56 +00:00
Special-Week
ab637d217b 🍻 publish plugin wordle_help (#1973) 2023-05-05 11:33:34 +00:00
noneflow[bot]
9d8f16f940 📝 Update changelog 2023-05-04 06:26:26 +00:00
Ju4tCode
dc2c5e3c80 🐛 fix command whitespace if no arg (#1975) 2023-05-04 14:25:09 +08:00
noneflow[bot]
6cfdbbe597 📝 Update changelog 2023-05-03 07:52:31 +00:00
17TheWord
487867a967 ✏️ Adapter: 更新 Minecraft 适配器 (#1972)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2023-05-03 15:51:21 +08:00
noneflow[bot]
f3a692d294 📝 Update changelog 2023-05-03 07:39:16 +00:00
synodriver
72b798f7ae 🐛 Fix: run_sync 上下文 (#1968) 2023-05-03 15:37:53 +08:00
pre-commit-ci[bot]
50237fb778 ⬆️ auto update by pre-commit hooks (#1971)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2023-05-02 14:11:59 +08:00
noneflow[bot]
fa5b8853af 📝 Update changelog 2023-05-02 04:51:39 +00:00
nicklly
0cd2282640 🍻 publish plugin 星穹铁道活动日历 (#1969) 2023-05-02 04:50:35 +00:00
noneflow[bot]
c2cea75bb7 📝 Update changelog 2023-05-02 04:13:18 +00:00
X-Skirt-X
b832ae742b 🍻 publish plugin 水印大师 (#1962) 2023-05-02 04:12:07 +00:00
noneflow[bot]
e98d28f3b4 📝 Update changelog 2023-04-30 14:28:36 +00:00
Akirami
b2e26cd6bd ✏️ Docs: 更正 issue 表单部分内容 (#1961)
Co-authored-by: Ju4tCode <42488585+yanyongyu@users.noreply.github.com>
2023-04-30 22:27:32 +08:00
noneflow[bot]
cfe182f452 📝 Update changelog 2023-04-29 04:15:20 +00:00
maoxig
729a894a04 🍻 publish plugin 图片/漫画翻译 (#1954) 2023-04-29 04:14:09 +00:00
noneflow[bot]
7231d493d0 📝 Update changelog 2023-04-29 03:29:31 +00:00
youlanan
abf1d52168 🍻 publish plugin 为美好群聊献上爆炎 (#1952) 2023-04-29 03:28:28 +00:00
noneflow[bot]
a5618f163f 📝 Update changelog 2023-04-29 00:45:55 +00:00
djkcyl
7d6c512f27 🍻 publish plugin 公共画板插件 (#1956) 2023-04-29 00:44:41 +00:00
noneflow[bot]
f00c0ae71c 📝 Update changelog 2023-04-27 14:00:11 +00:00
Ju4tCode
93b79ddcb3 Feature: 支持 re.Match 依赖注入 (#1950) 2023-04-27 21:58:56 +08:00
Ju4tCode
6691f6ef70 support exit none driver (#1951) 2023-04-27 17:26:59 +08:00
noneflow[bot]
6173836bdb 📝 Update changelog 2023-04-24 08:15:02 +00:00
student_2333
a2a88c1414 ✏️ Plugin: 更新 AutoReply 插件描述 (#1949) 2023-04-24 16:13:57 +08:00
noneflow[bot]
6114867e34 📝 Update changelog 2023-04-24 06:50:20 +00:00
Yincmewy
3c5cd6046d 🍻 publish plugin 运行代码 (#1941) 2023-04-24 06:49:10 +00:00
noneflow[bot]
7dc3702db5 📝 Update changelog 2023-04-24 02:46:05 +00:00
17TheWord
791f75c13e ✏️ Plugin: 移除 MC_QQ_MCRcon (#1948) 2023-04-24 10:44:53 +08:00
noneflow[bot]
4cfc8fcb44 📝 Update changelog 2023-04-24 02:36:03 +00:00
campanulamediuml
57fc04e4aa 🍻 publish plugin brainfuck (#1943) 2023-04-24 02:34:51 +00:00
noneflow[bot]
1e74c4eacf 📝 Update changelog 2023-04-24 02:34:33 +00:00
Well404
e55052ecfd 📝 Docs: 新增插件跨平台指南 (#1938)
Co-authored-by: Ju4tCode <42488585+yanyongyu@users.noreply.github.com>
2023-04-24 10:33:23 +08:00
noneflow[bot]
dc0aea9e3e 📝 Update changelog 2023-04-23 12:31:49 +00:00
NCBM
295b55da44 🍻 publish plugin Mixin (#1946) 2023-04-23 12:30:45 +00:00
noneflow[bot]
4b9ae5fd68 📝 Update changelog 2023-04-23 07:08:13 +00:00
Ju4tCode
cc8b6fa7a2 ✏️ enable blank issues (#1945) 2023-04-23 15:06:52 +08:00
noneflow[bot]
f28de96ea9 📝 Update changelog 2023-04-23 03:59:29 +00:00
Akirami
5e225b2898 📝 Docs: 使用 issue 表单替换 issue 模板 (#1928)
Co-authored-by: StarHeart <starheart233@gmail.com>
Co-authored-by: Ju4tCode <42488585+yanyongyu@users.noreply.github.com>
2023-04-23 11:58:25 +08:00
noneflow[bot]
392502dd68 📝 Update changelog 2023-04-22 07:35:22 +00:00
XZhouQD
ba329e5ef2 🍻 publish plugin AppInsights日志监控 (#1939) 2023-04-22 07:34:17 +00:00
noneflow[bot]
68b64a6004 📝 Update changelog 2023-04-22 03:18:34 +00:00
student_2333
0f694aa157 ✏️ Plugin: 更新 lgc2333 插件仓库地址 (#1935) 2023-04-22 11:17:24 +08:00
noneflow[bot]
0d7c399094 📝 Update changelog 2023-04-22 01:56:49 +00:00
canxin121
2f6685ab45 🍻 publish plugin nonebot_poe_chat (#1936) 2023-04-22 01:55:36 +00:00
noneflow[bot]
2061887276 📝 Update changelog 2023-04-20 02:05:46 +00:00
forchannot
2f40024edb 🍻 publish plugin 更改BOT群名片 (#1933) 2023-04-20 02:04:55 +00:00
noneflow[bot]
9799809ebd 📝 Update changelog 2023-04-18 10:57:48 +00:00
This-is-XiaoDeng
2a08bc5a14 🍻 publish bot XDbot2 (#1931) 2023-04-18 10:57:00 +00:00
noneflow[bot]
ff1ace7a04 📝 Update changelog 2023-04-16 10:47:18 +00:00
Well404
96f0daf535 📝 Docs: 修正教程中部分 import 缺失的问题 (#1927)
Co-authored-by: Ju4tCode <42488585+yanyongyu@users.noreply.github.com>
2023-04-16 18:46:29 +08:00
noneflow[bot]
1b2f560ad7 📝 Update changelog 2023-04-15 01:54:44 +00:00
lgc2333
1c033d3a53 🍻 publish plugin Akinator (#1924) 2023-04-15 01:53:52 +00:00
noneflow[bot]
99b26fccb0 📝 Update changelog 2023-04-14 08:27:59 +00:00
Ju4tCode
565aba61dc 🐛 Fix: shell command 包含富文本时报错信息出错 (#1923) 2023-04-14 16:26:54 +08:00
noneflow[bot]
21eb289411 📝 Update changelog 2023-04-13 16:01:18 +00:00
Agnes4m
441e772f48 🍻 publish plugin Bilifan (#1920) 2023-04-13 16:00:22 +00:00
noneflow[bot]
f360e439e9 📝 Update changelog 2023-04-13 14:10:16 +00:00
mas-alone
98b3affe91 🍻 publish plugin osu!入群审批 (#1918) 2023-04-13 14:09:19 +00:00
noneflow[bot]
04be5cb8e8 📝 Update changelog 2023-04-11 17:00:26 +00:00
nikissXI
7f925536b5 🍻 publish plugin 与ChatGpt聊天 (#1916) 2023-04-11 16:59:39 +00:00
noneflow[bot]
73dfcc53e1 📝 Update changelog 2023-04-11 16:43:02 +00:00
aaron-lii
3e543977c9 🍻 publish plugin TataruBot2 (#1914) 2023-04-11 16:42:18 +00:00
noneflow[bot]
97829aa122 📝 Update changelog 2023-04-10 16:38:02 +00:00
IllusiveBull
e42dd109f9 🍻 publish plugin 宝可梦融合 (#1911) 2023-04-10 16:37:16 +00:00
noneflow[bot]
98a6dfc514 📝 Update changelog 2023-04-10 16:17:08 +00:00
lgc2333
d6fd1b8614 🍻 publish plugin FuckYou (#1909) 2023-04-10 16:16:20 +00:00
noneflow[bot]
dd57aabfdc 📝 Update changelog 2023-04-10 08:02:28 +00:00
A60
44c8b4c29d ✏️ Plugin: 更新多功能哔哩哔哩解析工具 (#1913) 2023-04-10 16:01:38 +08:00
noneflow[bot]
1bc3a8eb02 📝 Update changelog 2023-04-09 15:03:56 +00:00
thx114
7371d3e7bb 🍻 publish plugin SDGPT (#1907) 2023-04-09 15:02:56 +00:00
noneflow[bot]
139eeac6ce 📝 Update changelog 2023-04-09 07:27:13 +00:00
Zeta-qixi
d33d2653cf 🍻 publish plugin nonebot clock 群闹钟 (#1861) 2023-04-09 07:26:28 +00:00
noneflow[bot]
ba3fc6abc4 📝 Update changelog 2023-04-08 14:30:44 +00:00
uy/sun
1d60714054 👷 跳过 PR 仓库为 fork 的情况 (#1905) 2023-04-08 22:29:58 +08:00
noneflow[bot]
020705bd4b 📝 Update changelog 2023-04-08 03:29:28 +00:00
Wuyi无疑
1495b34e39 ✏️ Plugin: 移除旧版本的 GenshinUID (#1904) 2023-04-08 11:28:42 +08:00
noneflow[bot]
e0d11226db 📝 Update changelog 2023-04-08 03:16:58 +00:00
zangxx66
8749bc9dc5 🍻 publish plugin B站直播间路灯 (#1900) 2023-04-08 03:16:08 +00:00
noneflow[bot]
716a047aba 📝 Update changelog 2023-04-08 01:04:49 +00:00
KimigaiiWuyi
ed6d436a50 🍻 publish plugin GenshinUID (#1902) 2023-04-08 01:03:55 +00:00
noneflow[bot]
028b51facf 📝 Update changelog 2023-04-07 16:08:37 +00:00
uy/sun
8f28124237 👷 CI: 使用最新的 NoneFlow (#1899) 2023-04-08 00:07:39 +08:00
noneflow[bot]
f3d7a30c66 📝 Update changelog 2023-04-06 09:53:35 +00:00
noneflow[bot]
8f3e9f87cb 🍻 publish plugin 多功能哔哩哔哩解析工具 (#1897)
Co-authored-by: djkcyl <djkcyl@users.noreply.github.com>
2023-04-06 17:52:24 +08:00
noneflow[bot]
36e4c02699 📝 Update changelog 2023-04-04 13:43:12 +00:00
Ju4tCode
1817102a7c Feature: 为消息类添加 has join include exclude 方法 (#1895) 2023-04-04 21:42:01 +08:00
pre-commit-ci[bot]
20820e72ad ⬆️ auto update by pre-commit hooks (#1896)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2023-04-04 17:33:00 +08:00
noneflow[bot]
fb4d957025 📝 Update changelog 2023-04-04 03:23:10 +00:00
he0119
fe5635db62 🍻 publish bot CoolQBot (#1893) 2023-04-04 11:22:06 +08:00
noneflow[bot]
30a8230eea 📝 Update changelog 2023-04-04 02:27:57 +00:00
Ju4tCode
908622cf61 👷 use noneflow app (#1892) 2023-04-04 09:48:31 +08:00
github-actions[bot]
8e5ec5c4e7 📝 Update changelog 2023-04-03 17:34:29 +00:00
nek0us
ca071bfc48 🍻 publish plugin Steam游戏状态播报 (#1886) 2023-04-04 01:33:15 +08:00
github-actions[bot]
4e22252c3e 📝 Update changelog 2023-04-03 17:26:30 +00:00
Alpaca4610
c0eee74968 🍻 publish plugin AI生成PPT (#1883) 2023-04-04 01:25:21 +08:00
github-actions[bot]
1e054a4370 📝 Update changelog 2023-04-03 17:16:39 +00:00
canxin121
76ccd241fc 🍻 publish plugin nonebot_paddle_ocr (#1881) 2023-04-04 01:15:30 +08:00
github-actions[bot]
e984b64fe3 📝 Update changelog 2023-04-03 17:01:58 +00:00
canxin121
3256cf7fce 🍻 publish plugin nonebot_api_paddle (#1879) 2023-04-04 01:00:55 +08:00
github-actions[bot]
d9eeb690ac 📝 Update changelog 2023-04-03 13:59:17 +00:00
Ju4tCode
b66f4436bf 📝 add walle-q to readme (#1891) 2023-04-03 21:57:56 +08:00
github-actions[bot]
c11bc7b78f 📝 Update changelog 2023-04-03 13:33:23 +00:00
Ju4tCode
3bbb48dd25 📝 update deploy docs (#1890) 2023-04-03 21:32:12 +08:00
github-actions[bot]
73b92be1e4 📝 Update changelog 2023-04-03 13:27:57 +00:00
abrahum
e977d79ebd 🍻 publish adapter Walle-Q (#1888) 2023-04-03 21:26:50 +08:00
github-actions[bot]
d02896065e 📝 Update changelog 2023-04-02 07:14:23 +00:00
mas-alone
f468aa992d 🍻 publish plugin 来份睡眠套餐 (#1875) 2023-04-02 15:13:21 +08:00
github-actions[bot]
3bfbbcf111 📝 Update changelog 2023-04-02 06:47:44 +00:00
glamorgan9826
e2e8b0a8cd 🍻 publish plugin 今日老婆 (#1873) 2023-04-02 14:46:45 +08:00
github-actions[bot]
c8c5f17fd1 📝 Update changelog 2023-04-01 11:48:37 +00:00
Ju4tCode
7f6fc56bd8 👷 unlock poetry version (#1872) 2023-04-01 19:47:33 +08:00
github-actions[bot]
40855ade01 📝 Update changelog 2023-04-01 09:47:08 +00:00
Umamusume-Agnes-Digital
d116563958 🍻 publish plugin 激战2!!! (#1869) 2023-04-01 17:45:44 +08:00
github-actions[bot]
8f603d3112 📝 Update changelog 2023-04-01 09:06:57 +00:00
mas-alone
998752926f 🍻 publish plugin ROLL (#1867) 2023-04-01 17:05:26 +08:00
github-actions[bot]
890b2ee22f 🔖 Release 2.0.0rc4 2023-04-01 03:59:10 +00:00
Ju4tCode
408292d679 🔖 bump version 2.0.0rc4 (#1870) 2023-04-01 11:52:43 +08:00
github-actions[bot]
ec4761c3a9 📝 Update changelog 2023-03-31 06:02:26 +00:00
AkashiCoin
0091a03653 🍻 publish plugin ChatGPT网页端API (#1864) 2023-03-31 14:01:18 +08:00
github-actions[bot]
e1a63f980f 📝 Update changelog 2023-03-31 04:48:41 +00:00
Ju4tCode
d982e14793 📝 update nonebug flow action arg (#1866) 2023-03-31 12:47:12 +08:00
Ju4tCode
43933920ed 📝 add editor config field name (#1863) 2023-03-29 23:26:27 +08:00
github-actions[bot]
fc03c58c70 📝 Update changelog 2023-03-29 15:10:44 +00:00
Akirami
283560daa7 Feature: 公开自定义 on 函数所需的函数 (#1856)
Co-authored-by: Ju4tCode <42488585+yanyongyu@users.noreply.github.com>
2023-03-29 23:09:33 +08:00
github-actions[bot]
efc4f5a0d5 📝 Update changelog 2023-03-29 11:01:45 +00:00
Ju4tCode
9f707469da fix test coverage condition (#1862) 2023-03-29 19:00:25 +08:00
github-actions[bot]
6396a7558a 📝 Update changelog 2023-03-29 08:01:17 +00:00
Ju4tCode
a8a76393a5 Feature: 重构驱动器 lifespan 方法 (#1860) 2023-03-29 15:59:54 +08:00
github-actions[bot]
0d0bc656c8 📝 Update changelog 2023-03-29 04:24:13 +00:00
Ju4tCode
2a2f7b6dce 🐛 detect runtime plugin (#1857) 2023-03-29 12:22:50 +08:00
github-actions[bot]
17c86f7da2 📝 Update changelog 2023-03-29 03:59:10 +00:00
Ju4tCode
1213e89bf5 add coverage condition annotation (#1858) 2023-03-29 11:57:33 +08:00
github-actions[bot]
ae08568daf 📝 Update changelog 2023-03-29 02:40:00 +00:00
Ju4tCode
8fbc85cf50 🐛 fix matcher create missing block (#1859) 2023-03-29 10:38:39 +08:00
github-actions[bot]
315dcb329e 📝 Update changelog 2023-03-28 15:22:44 +00:00
Cvandia
438e4f57e3 🍻 publish plugin 原神cos (#1854) 2023-03-28 23:21:19 +08:00
github-actions[bot]
a346efd684 📝 Update changelog 2023-03-28 15:13:45 +00:00
Ju4tCode
e3151c5f5e 📝 add warning to template (#1853) 2023-03-28 23:12:23 +08:00
github-actions[bot]
47536e6554 📝 Update changelog 2023-03-28 13:59:04 +00:00
NumberSir
79ac7f024f 🍻 publish plugin 颠倒问号 (#1848) 2023-03-28 21:57:34 +08:00
github-actions[bot]
3709e0ba4f 📝 Update changelog 2023-03-27 05:02:04 +00:00
CMHopeSunshine
c8ffafc1e8 🍻 publish plugin nonebot-plugin-miao (#1850) 2023-03-27 13:00:58 +08:00
github-actions[bot]
fedb67d4ae 📝 Update changelog 2023-03-27 04:53:42 +00:00
NCBM
076611166a 🍻 publish plugin 通括膨胀 (#1846) 2023-03-27 12:52:30 +08:00
github-actions[bot]
d5234e44f5 📝 Update changelog 2023-03-26 15:14:52 +00:00
A-kirami
64f78c279a 🍻 publish plugin Hello World (#1844) 2023-03-26 23:13:04 +08:00
github-actions[bot]
744443ab18 📝 Update changelog 2023-03-25 04:26:31 +00:00
nikissXI
9e5cde490e 🍻 publish plugin 喵喵点歌 (#1837) 2023-03-25 12:25:04 +08:00
github-actions[bot]
080c0db64b 📝 Update changelog 2023-03-25 03:11:14 +00:00
StarHeart
ec41b5f57f 📝 Docs: 移除 Messenger 移动端预期外的蓝色遮罩 (#1842) 2023-03-25 11:09:49 +08:00
github-actions[bot]
c441ec7080 📝 Update changelog 2023-03-25 02:59:11 +00:00
uy/sun
8bb92309d5 📝 Docs: 更新指向文档的链接 (#1841) 2023-03-25 10:58:03 +08:00
github-actions[bot]
9ab7666b6d 📝 Update changelog 2023-03-24 09:37:06 +00:00
Ju4tCode
add1f1473d update setup svg (#1840) 2023-03-24 17:32:24 +08:00
github-actions[bot]
cba38c399b 📝 Update changelog 2023-03-24 08:35:40 +00:00
Ju4tCode
18beb63d55 📝 Docs: 重写教程与进阶指南 (#1604)
Co-authored-by: Johnny Hsieh <32300164+mnixry@users.noreply.github.com>
2023-03-24 16:34:21 +08:00
github-actions[bot]
8977be2985 📝 Update changelog 2023-03-24 03:48:29 +00:00
uy/sun
00686380b8 Feature: 在 Windows 上处理 SIGBREAK 信号 (#1836) 2023-03-24 11:47:02 +08:00
github-actions[bot]
d4da953ad8 📝 Update changelog 2023-03-23 02:51:53 +00:00
QNLanYang
e5de8c8053 🍻 publish plugin ChatGLM-6B API版 (#1833) 2023-03-23 10:50:37 +08:00
github-actions[bot]
c2a2f8d420 📝 Update changelog 2023-03-23 02:28:08 +00:00
DaoMingze
af2ea7b83a 🍻 publish plugin ChatGLM (#1827) 2023-03-23 10:26:56 +08:00
github-actions[bot]
909a335106 📝 Update changelog 2023-03-22 12:55:55 +00:00
Johnny Hsieh
3e92fccd4e Feature: 为子依赖添加 PEP593 Annotated 支持 (#1832)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2023-03-22 20:54:46 +08:00
github-actions[bot]
9afaf3d516 📝 Update changelog 2023-03-21 06:01:34 +00:00
Alpaca4610
8ca56f24e3 🍻 publish plugin 基于OpenAI的AI模拟面试官 (#1828) 2023-03-21 14:00:19 +08:00
github-actions[bot]
8d09dd97f5 📝 Update changelog 2023-03-20 14:39:30 +00:00
Ju4tCode
78bbf9e623 🐛 Fix: 修复 bot hook 缺少依赖缓存和上下文管理 (#1826) 2023-03-20 22:37:57 +08:00
github-actions[bot]
05a6af46b9 📝 Update changelog 2023-03-20 05:52:12 +00:00
Astolfocat
243ad3f896 🍻 publish plugin 多平台热搜获取插件 (#1822) 2023-03-20 13:51:05 +08:00
github-actions[bot]
709c36bf5f 📝 Update changelog 2023-03-20 04:40:26 +00:00
Ju4tCode
ba808c85d5 improve user permission accessibility (#1825) 2023-03-20 12:39:17 +08:00
github-actions[bot]
c5444799f5 📝 Update changelog 2023-03-19 07:46:49 +00:00
Ju4tCode
36e99bc3ea Feature: 移除内置响应规则事件类型限制 (#1824) 2023-03-19 15:45:32 +08:00
github-actions[bot]
f65127e655 📝 Update changelog 2023-03-19 07:37:11 +00:00
QingMuCat
600c4f3268 🍻 publish plugin 随机点名 (#1818) 2023-03-19 15:36:02 +08:00
github-actions[bot]
53898dfb51 📝 Update changelog 2023-03-19 03:07:53 +00:00
MeetWq
95e3650c51 🍻 publish plugin 表情包制作(调用API版) (#1820) 2023-03-19 11:06:35 +08:00
github-actions[bot]
9f1b9ce2f3 📝 Update changelog 2023-03-18 06:41:17 +00:00
RongRongJi
551963c6c3 🍻 publish plugin 群聊语录库 (#1816) 2023-03-18 14:40:06 +08:00
github-actions[bot]
d59c999554 📝 Update changelog 2023-03-17 07:49:58 +00:00
Ju4tCode
8f44df371a allow using matcher subclass (#1815) 2023-03-17 15:48:48 +08:00
github-actions[bot]
7822cabe32 📝 Update changelog 2023-03-17 05:09:31 +00:00
NanakoOfficial
ca0b17b46a 🍻 publish plugin 随机狗妈 (#1812) 2023-03-17 13:08:17 +08:00
github-actions[bot]
d1404f6004 📝 Update changelog 2023-03-17 03:43:17 +00:00
Windylh
a294f0fbe0 🍻 publish plugin apex信息查询 (#1810) 2023-03-17 11:42:03 +08:00
github-actions[bot]
3cd0066715 📝 Update changelog 2023-03-17 03:10:19 +00:00
Zeta-qixi
faaef1a387 🍻 publish plugin unoconv文件转换 (#1808) 2023-03-17 11:08:56 +08:00
github-actions[bot]
ad4b244701 📝 Update changelog 2023-03-16 09:03:05 +00:00
forchannot
51e7bae8f2 🍻 publish plugin 原神历史卡池 (#1805) 2023-03-16 17:01:51 +08:00
github-actions[bot]
f18b6f609e 📝 Update changelog 2023-03-16 08:22:18 +00:00
MeetWq
dfbb32937e 🍻 publish plugin 括号补全 (#1803) 2023-03-16 16:21:07 +08:00
github-actions[bot]
cf9788ec99 📝 Update changelog 2023-03-16 08:01:14 +00:00
luoyefufeng
6b83d03094 🍻 publish plugin 修仙模拟器 (#1799) 2023-03-16 16:00:08 +08:00
github-actions[bot]
5508c1a4ee 📝 Update changelog 2023-03-16 07:53:03 +00:00
Ju4tCode
3462295562 🐛 fix missing cache in session updater (#1807) 2023-03-16 15:51:48 +08:00
github-actions[bot]
fee16082e0 📝 Update changelog 2023-03-15 11:32:24 +00:00
tkgs0
926b257065 🍻 publish bot 桃桃酱 (#1800) 2023-03-15 19:31:19 +08:00
github-actions[bot]
fca2d074e0 📝 Update changelog 2023-03-14 03:44:27 +00:00
iidamie
1a473f171c 🍻 publish plugin 发6 (#1797) 2023-03-14 11:43:12 +08:00
github-actions[bot]
97eee5a2f7 📝 Update changelog 2023-03-12 03:11:18 +00:00
DMCSWCG
8f1fbd9b36 🍻 publish plugin 群聊自定义表情包 (#1794) 2023-03-12 11:10:13 +08:00
github-actions[bot]
856f0b981f 📝 Update changelog 2023-03-11 12:50:31 +00:00
student_2333
f629fc9309 ✏️ Plugin: 删除 bnhhsh (#1792) 2023-03-11 20:49:17 +08:00
github-actions[bot]
e617bf2762 📝 Update changelog 2023-03-11 12:26:08 +00:00
lgc2333
072c2a2a41 🍻 publish plugin RimoFun (#1790) 2023-03-11 20:24:59 +08:00
github-actions[bot]
3a142033a1 📝 Update changelog 2023-03-10 11:38:15 +00:00
Alpaca4610
2832514f49 🍻 publish plugin ChatPDF文章分析 (#1787) 2023-03-10 19:37:06 +08:00
github-actions[bot]
e7887056b9 📝 Update changelog 2023-03-10 09:38:39 +00:00
TheLZY
500b59905d 🍻 publish plugin 和团子聊天! (#1784) 2023-03-10 17:37:26 +08:00
github-actions[bot]
4d4074ca24 📝 Update changelog 2023-03-10 09:28:30 +00:00
Suxmx
8391de52d9 🍻 publish plugin 多功能的ChatGPT机器人 (#1780) 2023-03-10 17:27:04 +08:00
github-actions[bot]
dd5f3bdea1 📝 Update changelog 2023-03-10 09:12:01 +00:00
Alpaca4610
c4bfe3a823 🍻 publish plugin ChatGPT官方接口版 (#1766) 2023-03-10 17:10:44 +08:00
github-actions[bot]
3ec76454e3 📝 Update changelog 2023-03-09 05:28:00 +00:00
zheuziihau
0e481d96a6 🍻 publish plugin 明日方舟抽卡记录分析 (#1746) 2023-03-09 13:26:51 +08:00
github-actions[bot]
c2b8bbee5f 📝 Update changelog 2023-03-09 05:13:33 +00:00
HCskia
61ad0733de 🍻 publish bot fubot (#1782) 2023-03-09 13:12:14 +08:00
github-actions[bot]
06fa0fb860 📝 Update changelog 2023-03-08 10:44:53 +00:00
allureluoli
e9b1692124 🍻 publish bot LOVE酱 (#1778) 2023-03-08 18:43:35 +08:00
pre-commit-ci[bot]
653902c6a2 ⬆️ auto update by pre-commit hooks (#1777)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2023-03-07 13:14:28 +08:00
github-actions[bot]
db0fcc0ceb 📝 Update changelog 2023-03-07 03:34:23 +00:00
Ju4tCode
f0021af6d4 👷 temporarily fix poetry install (#1776) 2023-03-07 11:33:13 +08:00
github-actions[bot]
7e614bb2b7 📝 Update changelog 2023-03-06 11:43:28 +00:00
Hoshinonyaruko
adba6c1890 🍻 publish plugin Sanae (#1774) 2023-03-06 19:42:13 +08:00
github-actions[bot]
6116d394e5 📝 Update changelog 2023-03-05 06:42:16 +00:00
maoxig
31ea5fa306 🍻 publish plugin 小爱课程表 (#1772) 2023-03-05 14:40:57 +08:00
github-actions[bot]
b209b77235 📝 Update changelog 2023-03-05 03:16:05 +00:00
DMCSWCG
81870e0d64 🍻 publish plugin AutoRepeater (#1768) 2023-03-05 11:14:56 +08:00
github-actions[bot]
40bccbc585 📝 Update changelog 2023-03-04 02:46:41 +00:00
zhulinyv
46c2817bba 🍻 publish bot 脑积水 (#1770) 2023-03-04 10:45:35 +08:00
github-actions[bot]
a5302a1872 📝 Update changelog 2023-03-03 03:12:35 +00:00
techotaku39
6642500c1c 🍻 publish plugin 60s日历 (#1751) 2023-03-03 11:11:22 +08:00
github-actions[bot]
f9464171fd 📝 Update changelog 2023-03-03 03:07:19 +00:00
ZM25XC
34d307b881 🍻 publish plugin 青年大学习提交(基础版) (#1763) 2023-03-03 11:06:05 +08:00
github-actions[bot]
8dc36aa630 📝 Update changelog 2023-03-01 13:21:39 +00:00
ZM25XC
f324b62eb2 🍻 publish plugin 青年大学习提交(Web UI) (#1761) 2023-03-01 21:14:05 +08:00
github-actions[bot]
a21b511568 📝 Update changelog 2023-03-01 10:35:31 +00:00
techotaku39
df1c13accd 🍻 publish plugin 网抑云 (#1759) 2023-03-01 18:34:19 +08:00
github-actions[bot]
c141b3eae7 📝 Update changelog 2023-03-01 10:25:48 +00:00
PadorFelice
a2a5af9b5e 🍻 publish plugin nonebot_plugin_eventdone (#1754) 2023-03-01 18:24:31 +08:00
github-actions[bot]
86a4f4043e 📝 Update changelog 2023-03-01 10:23:19 +00:00
Ju4tCode
f3aa8c6aa5 🐛 assert bot when disconnect (#1757) 2023-03-01 18:22:07 +08:00
github-actions[bot]
be81d094b4 📝 Update changelog 2023-02-28 06:47:15 +00:00
Ju4tCode
d0f832c4cd Feature: 添加 get_adapter 类型 overload (#1755)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2023-02-28 14:46:09 +08:00
github-actions[bot]
317a2b8c9b 📝 Update changelog 2023-02-26 16:12:34 +00:00
Ju4tCode
433c672130 Feature: 命令匹配支持强制指定空白符 (#1748) 2023-02-27 00:11:24 +08:00
github-actions[bot]
f8c67ebdf6 📝 Update changelog 2023-02-26 13:57:50 +00:00
17TheWord
2a95588421 🍻 publish plugin 爱发电审核 (#1749) 2023-02-26 21:56:35 +08:00
github-actions[bot]
a5fc40f2dc 📝 Update changelog 2023-02-26 13:39:57 +00:00
zzcqie666
e4ccb683cc 🍻 publish plugin 战地一入群审批 (#1744) 2023-02-26 21:38:54 +08:00
github-actions[bot]
34223d6b37 📝 Update changelog 2023-02-26 06:16:24 +00:00
Ju4tCode
04a7c3bc13 add get adapter (#1747) 2023-02-26 14:15:10 +08:00
github-actions[bot]
dd04190ca2 📝 Update changelog 2023-02-25 07:16:56 +00:00
mmxd12
5fd5b2f5b3 🍻 publish plugin wf的wm市场 (#1741) 2023-02-25 15:15:51 +08:00
github-actions[bot]
e8ad79aaf3 📝 Update changelog 2023-02-25 07:10:23 +00:00
Gin2O
ec8bc0424e 🍻 publish plugin 呆呆兽都会用的chatbot接api (#1737) 2023-02-25 15:09:15 +08:00
github-actions[bot]
6063714093 📝 Update changelog 2023-02-25 06:57:00 +00:00
Gin2O
1a0976e834 🍻 publish plugin 呆呆兽都会起来锻炼 H2E (#1736) 2023-02-25 14:55:53 +08:00
Ju4tCode
74743e6176 Develop: 升级 NoneBug 版本 (#1725) 2023-02-22 23:32:48 +08:00
github-actions[bot]
1befd9ffc6 📝 Update changelog 2023-02-22 15:30:13 +00:00
QingMuCat
c688450690 🍻 publish plugin 修仙_2.0 (#1729) 2023-02-22 23:28:44 +08:00
github-actions[bot]
d9e7986f5c 📝 Update changelog 2023-02-22 03:09:12 +00:00
Ikaros-521
d9bdf38a4e 🍻 publish plugin 发病语录 (#1727) 2023-02-22 11:07:55 +08:00
github-actions[bot]
1bd1f15c49 📝 Update changelog 2023-02-21 15:50:13 +00:00
felinae98
b4083ff9f9 🍻 publish plugin 峯驰物流 (#1722) 2023-02-21 23:49:03 +08:00
github-actions[bot]
2e766a86c2 📝 Update changelog 2023-02-21 15:26:52 +00:00
_3yude
93976a4162 📝 Docs: pip 安装指令添加引号 (#1724) 2023-02-21 23:25:43 +08:00
github-actions[bot]
4dc92ffc0b 📝 Update changelog 2023-02-20 16:45:00 +00:00
_3yude
5747397790 📝 Docs: 修正交互模式命令 (#1719)
Co-authored-by: Ju4tCode <42488585+yanyongyu@users.noreply.github.com>
2023-02-21 00:43:54 +08:00
github-actions[bot]
17fb3f92eb 📝 Update changelog 2023-02-20 14:26:30 +00:00
Ju4tCode
8f79ba1ccd Feature: 使用 tomllib 读取 toml 配置 (#1720) 2023-02-20 22:25:14 +08:00
github-actions[bot]
e11298d15b 📝 Update changelog 2023-02-20 05:20:25 +00:00
Ju4tCode
728902bfcf 🔊 Feature: 优化插件加载日志 (#1716) 2023-02-20 13:19:05 +08:00
github-actions[bot]
97723a0838 📝 Update changelog 2023-02-19 09:06:25 +00:00
Zhiyu
e42111f31f ✏️ Plugin: 修改链接分享解析器插件名称 (#1715) 2023-02-19 17:05:05 +08:00
github-actions[bot]
8d4eb7faf8 📝 Update changelog 2023-02-18 15:26:34 +00:00
Harry-Jing
9b19bf63b2 🍻 publish plugin Bing Chat (#1713) 2023-02-18 23:25:14 +08:00
github-actions[bot]
6685d88b44 📝 Update changelog 2023-02-18 15:01:46 +00:00
zhiyu1998
694db6c278 🍻 publish plugin 视频、图片解析器 (#1709) 2023-02-18 23:00:39 +08:00
github-actions[bot]
1dc02bfe8e 📝 Update changelog 2023-02-15 13:36:54 +00:00
bridgeL
045ab60699 🍻 publish plugin 你画我猜组队 (#1704) 2023-02-15 21:35:44 +08:00
github-actions[bot]
dc5ebd7a90 📝 Update changelog 2023-02-13 03:13:42 +00:00
student_2333
676e729df8 🔥 Bot: 移除 ShigureBot (#1699) 2023-02-13 11:12:40 +08:00
github-actions[bot]
dda6b97ef8 📝 Update changelog 2023-02-13 02:52:11 +00:00
NumberSir
b3d22ea8c4 🍻 publish plugin 明日方舟工具箱 (#1697) 2023-02-13 10:51:04 +08:00
github-actions[bot]
eac72d2d48 📝 Update changelog 2023-02-12 10:05:26 +00:00
monsterxcn
955ada47ed 🍻 publish plugin 原神深境螺旋数据查询 (#1695) 2023-02-12 18:04:23 +08:00
github-actions[bot]
4c31683231 📝 Update changelog 2023-02-12 03:48:39 +00:00
NCBM
a8f3d83947 🍻 publish plugin 工具拓展 (#1693) 2023-02-12 11:47:39 +08:00
github-actions[bot]
87e1866cf4 📝 Update changelog 2023-02-12 03:33:31 +00:00
j1g5awi
0d39b788bb 🍻 publish plugin OneBot 实现 (#1691) 2023-02-12 11:32:21 +08:00
github-actions[bot]
635668e6d4 📝 Update changelog 2023-02-10 02:19:47 +00:00
uy/sun
6358d07fbd 👷 发布机器人使用 latest 标签 (#1690) 2023-02-10 10:18:38 +08:00
github-actions[bot]
c03e4161c7 📝 Update changelog 2023-02-09 02:25:35 +00:00
scdhh
4c0d4065c5 Use raise from e when load driver error (#1689) 2023-02-09 10:24:27 +08:00
Umamusume-Agnes-Digital
720c736343 🍻 publish plugin 舞萌maimai插件版 (#1684) 2023-02-08 23:09:07 +08:00
github-actions[bot]
6d7435bf36 📝 Update changelog 2023-02-08 06:26:11 +00:00
BigOrangeQWQ
423e055ecd 🍻 publish plugin ACMReminder (#1685) 2023-02-08 14:25:05 +08:00
github-actions[bot]
b952f325d2 📝 Update changelog 2023-02-08 06:18:34 +00:00
KarisAya
3c3331a1ef 🍻 publish plugin 通用指令阻断 (#1682) 2023-02-08 14:17:27 +08:00
github-actions[bot]
4679e7d9cb 📝 Update changelog 2023-02-08 06:07:44 +00:00
CupidsBow
cbcd3987d2 🍻 publish bot koishi (#1680) 2023-02-08 14:06:25 +08:00
github-actions[bot]
d8cc1bd644 📝 Update changelog 2023-02-07 13:28:18 +00:00
Cvandia
09b0b2084d 🍻 publish plugin 今天吃喝什么(图片版) (#1677) 2023-02-07 21:27:03 +08:00
github-actions[bot]
6bc2870ea5 📝 Update changelog 2023-02-07 13:07:03 +00:00
Ju4tCode
f14580e688 ✏️ fix module path error in bilibili live (#1679) 2023-02-07 21:05:53 +08:00
github-actions[bot]
5ae24313f7 📝 Update changelog 2023-02-06 13:09:16 +00:00
cnchens
14088c6c51 🍻 publish bot ChensQBOTv2 (#1675) 2023-02-06 21:08:10 +08:00
Ju4tCode
73126535ef ⬆️ upgrade pre-commit config (#1674)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2023-02-05 12:57:31 +08:00
github-actions[bot]
22e27cce5f 📝 Update changelog 2023-02-05 03:46:41 +00:00
Reversedeer
8abab79cfc 🍻 publish plugin Q群消息事件监控 (#1666) 2023-02-05 11:45:45 +08:00
github-actions[bot]
6b419dc929 📝 Update changelog 2023-02-05 03:31:01 +00:00
tkgs0
8bf8b4760b 🍻 publish plugin DickyPK (#1668) 2023-02-05 11:29:54 +08:00
github-actions[bot]
678c1e1532 📝 Update changelog 2023-02-05 03:04:05 +00:00
Rene8028
325d28fbd4 🍻 publish plugin 每日人品2 (#1667) 2023-02-05 11:03:03 +08:00
github-actions[bot]
6bff6e9ad3 📝 Update changelog 2023-02-04 09:51:09 +00:00
KarisAya
d58c1407b5 🍻 publish plugin 娶群友 (#1664) 2023-02-04 17:49:53 +08:00
github-actions[bot]
e8760b6e4a 📝 Update changelog 2023-02-03 02:52:22 +00:00
KarisAya
4c92890265 🍻 publish plugin 我要一张xx涩图 (#1662) 2023-02-03 10:51:10 +08:00
Anh71me
d2f000bb16 Docs: Bump nb-autodoc from 0.x to 1.0.0a5 (#1661) 2023-02-02 16:10:22 +08:00
github-actions[bot]
b534b3b03f 📝 Update changelog 2023-02-02 02:58:33 +00:00
lgc2333
485d6e94b4 🍻 publish plugin AutoReply (#1659) 2023-02-02 10:57:34 +08:00
github-actions[bot]
2f5ba409ec 📝 Update changelog 2023-02-02 02:51:04 +00:00
eya46
e2e9bcc260 🍻 publish plugin B站热搜 (#1657) 2023-02-02 10:49:48 +08:00
github-actions[bot]
6e214efde8 📝 Update changelog 2023-02-01 08:17:45 +00:00
17TheWord
da72d20d0e 🍻 publish plugin MC Ping (#1655) 2023-02-01 16:16:37 +08:00
github-actions[bot]
646ab1002f 📝 Update changelog 2023-01-31 05:54:18 +00:00
Special-Week
1b4e5d05ab 🍻 publish plugin impact淫趴 (#1652) 2023-01-31 13:53:12 +08:00
github-actions[bot]
f323feac1b 📝 Update changelog 2023-01-31 02:31:43 +00:00
KroMiose
cb715c132f 🍻 publish plugin 更人性化的GPT-Ai聊天插件 (#1650) 2023-01-31 10:30:38 +08:00
github-actions[bot]
9e1b439128 📝 Update changelog 2023-01-30 02:55:54 +00:00
ZombieFly
f07e9bf699 🍻 publish plugin uuid生成器 (#1648) 2023-01-30 10:54:53 +08:00
github-actions[bot]
92e410ce0a 📝 Update changelog 2023-01-30 02:45:21 +00:00
Reversedeer
e14709c7f0 🍻 publish plugin 舔狗日记 (#1645) 2023-01-30 10:44:12 +08:00
github-actions[bot]
0533058dc2 📝 Update changelog 2023-01-29 02:36:06 +00:00
ZYKsslm
896e988f0e 🍻 publish plugin 查找轻小说 (#1643) 2023-01-29 10:34:59 +08:00
github-actions[bot]
b39fe9f1a9 📝 Update changelog 2023-01-29 02:23:19 +00:00
longchengguxiao
02301f6098 🍻 publish plugin XDU校园服务 (#1641) 2023-01-29 10:22:19 +08:00
github-actions[bot]
c5a0f0bd6d 📝 Update changelog 2023-01-28 08:40:54 +00:00
Proviasw
f59d5f0826 🍻 publish plugin nonebot-plugin-mcport (#1633) 2023-01-28 16:39:47 +08:00
github-actions[bot]
c369475603 📝 Update changelog 2023-01-27 03:07:16 +00:00
RF-Tar-Railt
18e698f2a0 🍻 publish plugin Alconna 命令工具 (#1638) 2023-01-27 11:06:03 +08:00
github-actions[bot]
33bab04a5a 📝 Update changelog 2023-01-27 03:01:58 +00:00
17TheWord
3cf31998b9 🍻 publish plugin Group_Link_Guild (#1636) 2023-01-27 11:00:57 +08:00
github-actions[bot]
c6d85ac9b0 📝 Update changelog 2023-01-27 02:51:16 +00:00
zhulinyv
27286fdc57 🍻 publish plugin 简易群管女生自用99新 (#1634) 2023-01-27 10:50:15 +08:00
github-actions[bot]
735cf9db6b 📝 Update changelog 2023-01-25 05:00:44 +00:00
17TheWord
d457bece3d 🍻 publish bot 青岚 (#1629) 2023-01-25 12:59:46 +08:00
github-actions[bot]
37b2a100d0 📝 Update changelog 2023-01-25 04:46:17 +00:00
17TheWord
cd4037c4fe 🍻 publish plugin 青岚 (#1628) 2023-01-25 12:45:08 +08:00
github-actions[bot]
13a490de0d 📝 Update changelog 2023-01-25 04:33:48 +00:00
Hiroshi12138
cec09397cc 🍻 publish plugin 对话超管 (#1626) 2023-01-25 12:32:45 +08:00
StarHeart
a11ac82a91 🚸 add constraint for port (#1632) 2023-01-25 12:31:26 +08:00
github-actions[bot]
6410af19ba 📝 Update changelog 2023-01-23 02:59:13 +00:00
kifuan
8af08c6417 🍻 publish plugin 摩尔质量计算器 (#1624) 2023-01-23 10:58:15 +08:00
github-actions[bot]
801d29f66c 📝 Update changelog 2023-01-23 02:51:13 +00:00
longchengguxiao
c518f768d1 🍻 publish plugin 植物大战僵尸小游戏 (#1621) 2023-01-23 10:50:02 +08:00
github-actions[bot]
27149108d9 📝 Update changelog 2023-01-22 10:17:41 +00:00
Jigsaw
5d1582566a 🔥 Docs: 移除商店中的过期插件 2023 (#1610) 2023-01-22 18:16:26 +08:00
github-actions[bot]
eceef1ebec 🔖 Release 2.0.0rc3 2023-01-22 08:17:26 +00:00
Ju4tCode
50aa8c53e0 🔖 bump version 2.0.0rc3 (#1620) 2023-01-22 16:10:57 +08:00
github-actions[bot]
558f740c13 📝 Update changelog 2023-01-22 07:31:16 +00:00
wwweww
8bffba7efd 🍻 publish adapter BilibiliLive (#1616) 2023-01-22 15:29:55 +08:00
github-actions[bot]
ce93ea13e7 📝 Update changelog 2023-01-22 07:26:24 +00:00
Limnium
174182d62a 🍻 publish plugin 反向词典 (#1618) 2023-01-22 15:25:20 +08:00
github-actions[bot]
36f047be7f 📝 Update changelog 2023-01-22 07:21:13 +00:00
lgc2333
9d73af0513 🍻 publish plugin PicMCStat (#1613) 2023-01-22 15:20:03 +08:00
github-actions[bot]
ecb0d78011 📝 Update changelog 2023-01-22 07:19:32 +00:00
Ju4tCode
5920efb6c5 📝 Docs: 修改更新部分文档 (#1615) 2023-01-22 15:18:26 +08:00
github-actions[bot]
5893fbe57d 📝 Update changelog 2023-01-22 03:41:58 +00:00
17TheWord
27557af636 🍻 publish adapter Spigot (#1611) 2023-01-22 11:40:49 +08:00
github-actions[bot]
b37c7995cb 📝 Update changelog 2023-01-22 03:32:38 +00:00
StarHeart
f46addbb85 Docs: 商店搜索大小写不敏感 (#1609) 2023-01-22 11:31:32 +08:00
github-actions[bot]
6f57a290d7 📝 Update changelog 2023-01-21 06:12:32 +00:00
bridgeL
ae66e45287 🍻 publish plugin 犯人在跳舞 (#1607) 2023-01-21 14:11:18 +08:00
github-actions[bot]
03cf7f290a 📝 Update changelog 2023-01-20 04:02:45 +00:00
Jigsaw
f203aaf4ca ✏️ Plugin: 移除 nonebot-plugin-puppet (#1605) 2023-01-20 12:01:35 +08:00
github-actions[bot]
9a2edbbeb1 📝 Update changelog 2023-01-18 07:26:26 +00:00
Rinfair-CSP-A016
bd9ca99f63 🍻 publish bot SuzunoBot (#1600) 2023-01-18 15:25:12 +08:00
github-actions[bot]
8be262d305 📝 Update changelog 2023-01-17 02:30:56 +00:00
nikissXI
b92d47b362 🍻 publish plugin 喵喵自记菜谱 (#1598) 2023-01-17 10:29:45 +08:00
github-actions[bot]
bdf8cb0d57 📝 Update changelog 2023-01-16 07:06:21 +00:00
itsevin
0cb65214c6 🍻 publish plugin 语音功能 (#1596) 2023-01-16 15:05:12 +08:00
github-actions[bot]
ccc2c5676a 📝 Update changelog 2023-01-16 06:54:42 +00:00
BigOrangeQWQ
6daec67ebd 🍻 publish plugin OrangeDice! (#1594) 2023-01-16 14:53:41 +08:00
github-actions[bot]
051851faed 📝 Update changelog 2023-01-16 06:43:25 +00:00
nikissXI
8d2fca3e12 🍻 publish plugin 简易谷歌翻译插件 (#1592) 2023-01-16 14:42:17 +08:00
github-actions[bot]
76f37c485c 📝 Update changelog 2023-01-16 03:54:28 +00:00
mengxinyuan638
0c7af0873f 🍻 publish plugin 哔哩哔哩q群登录 (#1590) 2023-01-16 11:53:03 +08:00
github-actions[bot]
31fa4ec5f4 📝 Update changelog 2023-01-16 03:06:47 +00:00
nikissXI
fda490d252 ✏️ Plugin: 更新 MC 的插件信息 (#1589) 2023-01-16 11:05:43 +08:00
github-actions[bot]
40e443fd1a 📝 Update changelog 2023-01-16 03:04:37 +00:00
Akirami
4a17e581d2 🔥 移除 nonebot-plugin-aidraw (#1588) 2023-01-16 11:03:24 +08:00
github-actions[bot]
081d212487 📝 Update changelog 2023-01-13 09:11:38 +00:00
mengxinyuan638
3d6774136f 🍻 publish plugin 原神实时公告 (#1584) 2023-01-13 17:10:28 +08:00
github-actions[bot]
fa934a156a 📝 Update changelog 2023-01-12 09:09:47 +00:00
bridgeL
bac5356a90 ✏️ Plugins: 更新 ayaka_games 插件名和描述 (#1586)
Co-authored-by: Su <wxlxy316@163.com>
2023-01-12 17:08:28 +08:00
github-actions[bot]
b289065f71 📝 Update changelog 2023-01-12 02:38:13 +00:00
dpm12345
09cf0f29ba ✏️ Plugin: 更新 tts_gal 插件名和描述 (#1581) 2023-01-12 10:36:58 +08:00
github-actions[bot]
244837dd7c 📝 Update changelog 2023-01-12 02:35:30 +00:00
mengxinyuan638
a0bc113912 🍻 publish bot 辞辞(cici)Bot (#1582) 2023-01-12 10:34:19 +08:00
github-actions[bot]
6f6a296105 📝 Update changelog 2023-01-12 02:29:19 +00:00
Monarchdos
a0d316127f 🍻 publish plugin 心灵鸡汤 (#1579) 2023-01-12 10:27:57 +08:00
github-actions[bot]
f0c0d7788f 📝 Update changelog 2023-01-11 08:52:35 +00:00
Akirami
3f7e2604f1 🔊 Feature: 添加事件响应器检查完成日志 (#1578)
Co-authored-by: Ju4tCode <42488585+yanyongyu@users.noreply.github.com>
2023-01-11 16:51:20 +08:00
github-actions[bot]
f43c0087f7 📝 Update changelog 2023-01-10 08:47:48 +00:00
ericzhang-debug
e71d841045 🍻 publish plugin Bing每日图片获取 (#1576) 2023-01-10 16:46:32 +08:00
github-actions[bot]
a3af8da331 📝 Update changelog 2023-01-09 06:25:42 +00:00
Ju4tCode
8bdfdaef91 ⬆️ Fix: 屏蔽 fastapi 0.89.0 (#1574) 2023-01-09 14:24:30 +08:00
github-actions[bot]
afd13ed65d 📝 Update changelog 2023-01-09 02:39:02 +00:00
mengxinyuan638
d83751d0ca 🍻 publish plugin 星座运势 (#1571) 2023-01-09 10:37:45 +08:00
github-actions[bot]
63dd3b8fa7 📝 Update changelog 2023-01-09 02:32:39 +00:00
hmzz804
ae689605a5 🍻 publish plugin 回声洞 (#1570) 2023-01-09 10:31:25 +08:00
github-actions[bot]
bbaba1c955 📝 Update changelog 2023-01-08 08:51:53 +00:00
Cvandia
f1ee54e5c9 🍻 publish plugin 整点报时 (#1568) 2023-01-08 16:50:38 +08:00
github-actions[bot]
6f8e532afe 📝 Update changelog 2023-01-08 07:22:21 +00:00
Akirami
6f68ff61e5 🔥 移除 nonebot_plugin_super_resolution (#1561) 2023-01-08 15:21:05 +08:00
github-actions[bot]
a930fc0997 📝 Update changelog 2023-01-07 09:54:17 +00:00
Jigsaw
65da0947fe ✏️ update OlivOS.nb2's module_name (#1560) 2023-01-07 17:52:56 +08:00
github-actions[bot]
1b64a54421 📝 Update changelog 2023-01-07 08:03:05 +00:00
Akirami
d4e1bb7bf3 🐛 修复子插件加载失败时没有从父插件中移除的问题 (#1559) 2023-01-07 16:01:56 +08:00
github-actions[bot]
d737679ccd 📝 Update changelog 2023-01-07 07:59:53 +00:00
SkyDynamic
4cef5512ee 🍻 publish plugin Hypixel数据查询 (#1555) 2023-01-07 15:58:47 +08:00
github-actions[bot]
1d5d1602f0 📝 Update changelog 2023-01-06 12:08:47 +00:00
Ju4tCode
87e767fa25 remove default fastapi installation (#1557) 2023-01-06 20:07:28 +08:00
github-actions[bot]
c38437a22f 📝 Update changelog 2023-01-06 04:43:30 +00:00
Ju4tCode
cafb7bedb4 add pyright config (#1554) 2023-01-06 12:42:20 +08:00
github-actions[bot]
ace053f387 📝 Update changelog 2023-01-06 03:51:04 +00:00
cpuopt
d6e176d03b 🍻 publish plugin 查找图片出处 (#1552) 2023-01-06 11:49:51 +08:00
github-actions[bot]
2fca5b9664 📝 Update changelog 2023-01-06 03:44:58 +00:00
Monarchdos
cd93ace0dd 🍻 publish plugin 云签到 (#1549) 2023-01-06 11:43:47 +08:00
github-actions[bot]
b118cb6f22 📝 Update changelog 2023-01-06 03:00:44 +00:00
istrashguy
a69ccb4e6c 🍻 publish plugin 图像标注 (#1546) 2023-01-06 10:59:15 +08:00
github-actions[bot]
d5ec31d0a0 📝 Update changelog 2023-01-04 06:55:42 +00:00
CMHopeSunshine
62560635b2 🍻 publish plugin 对对联 (#1541) 2023-01-04 14:54:29 +08:00
github-actions[bot]
c00430c53f 📝 Update changelog 2023-01-04 06:34:51 +00:00
CMHopeSunshine
1dcda4bd77 🍻 publish plugin 群聊学习 (#1539) 2023-01-04 14:33:25 +08:00
github-actions[bot]
b60035f0e6 📝 Update changelog 2023-01-04 05:51:54 +00:00
Umamusume-Agnes-Digital
8551b13eab 🍻 publish plugin 求生之路2——服务器操作 (#1531) 2023-01-04 13:50:41 +08:00
github-actions[bot]
b448a6e083 📝 Update changelog 2023-01-04 05:10:19 +00:00
nikissXI
956b202087 🍻 publish plugin setu_customization (#1533) 2023-01-04 13:08:54 +08:00
github-actions[bot]
b95d49cd9c 📝 Update changelog 2023-01-04 04:59:45 +00:00
Akirami
006b9dd816 Feature: 支持给 FastAPIQuart 传递额外的参数 (#1543) 2023-01-04 12:58:26 +08:00
github-actions[bot]
a9125cd696 📝 Update changelog 2023-01-04 04:49:29 +00:00
uy/sun
ee5dcf0d42 👷 CI: 优化触发条件减少无效运行 (#1545)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2023-01-04 12:48:10 +08:00
github-actions[bot]
f13c1cc980 📝 Update changelog 2023-01-03 16:23:05 +00:00
ssttkkl
16c0a87929 🍻 publish plugin 主动消息撤回 (#1535) 2023-01-04 00:21:51 +08:00
github-actions[bot]
39d1554905 📝 Update changelog 2023-01-03 16:14:15 +00:00
ANGJustinl
37067229b0 🍻 publish plugin HttpCat🐱猫猫http状态码 (#1528) 2023-01-04 00:13:05 +08:00
pre-commit-ci[bot]
5ca708d3f4 ⬆️ auto update by pre-commit hooks (#1530)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2023-01-03 13:54:27 +08:00
github-actions[bot]
53dded52a7 📝 Update changelog 2023-01-01 07:23:20 +00:00
Akirami
f8cee790e7 Feature: 添加 logger 重导出 (#1526)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2023-01-01 15:22:07 +08:00
bridgeL
10447ff3c4 🍻 publish plugin 命令探查 (#1523) 2023-01-01 15:20:53 +08:00
github-actions[bot]
f08aec7894 📝 Update changelog 2023-01-01 07:09:08 +00:00
uy/sun
69edb98835 Feature: 将 block driver 转正为 none 驱动器 (#1522) 2023-01-01 15:08:00 +08:00
github-actions[bot]
c73ca2b43f 📝 Update changelog 2023-01-01 07:05:09 +00:00
bridgeL
848c6c5061 ✏️ Plugin: 删除 ayaka_who_is_suspect 插件 (#1525)
Co-authored-by: Su <wxlxy316@163.com>
2023-01-01 15:03:41 +08:00
github-actions[bot]
58f82bf881 📝 Update changelog 2022-12-31 12:43:05 +00:00
uy/sun
9b3e670cee 🐛 修复异常在 traceback 中无法正常显示信息 (#1521) 2022-12-31 20:41:53 +08:00
github-actions[bot]
f0bebb65b4 📝 Update changelog 2022-12-30 15:09:51 +00:00
ANGJustinl
bc845c94e2 🍻 publish plugin AnimalVoice_Convert (#1517) 2022-12-30 23:08:41 +08:00
github-actions[bot]
f4668bf0bc 📝 Update changelog 2022-12-30 03:32:03 +00:00
uy/sun
3493d69fcd 👷 CI: 添加插件加载测试 (#1519)
* 👷 添加插件加载测试

* 调整命名格式

* 添加 issue_comment 的触发条件
2022-12-30 11:30:47 +08:00
github-actions[bot]
125bcb943f 📝 Update changelog 2022-12-29 12:27:17 +00:00
OREOCODEDEV
e65eb3fb18 🍻 publish plugin 服务状态查询 (#1512) 2022-12-29 20:26:12 +08:00
github-actions[bot]
8045420f97 📝 Update changelog 2022-12-29 11:28:33 +00:00
ANGJustinl
03a90006e5 🍻 publish plugin 腾讯云图像变换 (#1514) 2022-12-29 19:27:09 +08:00
github-actions[bot]
adbe341076 📝 Update changelog 2022-12-27 10:54:55 +00:00
Johnny Hsieh
2a623b1c81 🔥 Plugin: 移除停止维护的 nonebot-plugin-filehost (#1516) 2022-12-27 18:53:46 +08:00
github-actions[bot]
c91926aea6 📝 Update changelog 2022-12-27 03:08:47 +00:00
IAXRetailer
63329257de 🍻 publish bot RanBot (#1510) 2022-12-27 11:07:31 +08:00
github-actions[bot]
c9dc6e648e 📝 Update changelog 2022-12-26 03:13:47 +00:00
Ikaros
78a818547e ✏️ Plugin: 更新 abstain_diary 插件名和描述 (#1509) 2022-12-26 11:12:40 +08:00
github-actions[bot]
41eed9d0e9 📝 Update changelog 2022-12-26 02:57:23 +00:00
StarHeart
e32019f15d 📝 Docs: 更新测试文档中的连接方式&细化插件发布描述 (#1504) 2022-12-26 10:56:16 +08:00
github-actions[bot]
59c033e2dd 📝 Update changelog 2022-12-26 02:55:30 +00:00
zhulinyv
49cf1ec5d3 🍻 publish plugin Ping (#1507) 2022-12-26 10:54:07 +08:00
github-actions[bot]
e5ad15d6d6 📝 Update changelog 2022-12-25 16:18:00 +00:00
zhulinyv
4cf9790a95 🍻 publish plugin 群友召唤术 (#1502) 2022-12-26 00:16:32 +08:00
github-actions[bot]
7467e66dab 📝 Update changelog 2022-12-25 16:06:28 +00:00
050644zf
516c1c220c 🍻 publish plugin 战地群聊天插件 (#1505) 2022-12-26 00:05:05 +08:00
github-actions[bot]
136778ae5b 📝 Update changelog 2022-12-24 02:58:56 +00:00
chrisyy2003
17538f7a66 ✏️ Plugin: 更新 gpt3 插件模块名 (#1501) 2022-12-24 10:57:34 +08:00
github-actions[bot]
d1da0be0da 📝 Update changelog 2022-12-23 15:07:19 +00:00
bridgeL
e92bc24631 🍻 publish plugin 不要复读 (#1499) 2022-12-23 23:06:04 +08:00
github-actions[bot]
73d1e5dd88 📝 Update changelog 2022-12-21 06:53:36 +00:00
nikissXI
ea83ba78ec 🍻 publish plugin JAVA MC服务器信息查询 (#1496) 2022-12-21 14:52:23 +08:00
github-actions[bot]
712f80a307 📝 Update changelog 2022-12-21 06:40:17 +00:00
Ju4tCode
5c4ef8fc00 🐛 Fix: 修复客户端请求未处理 cookies (#1491) 2022-12-21 14:39:05 +08:00
github-actions[bot]
2b0973c9f5 📝 Update changelog 2022-12-21 05:59:28 +00:00
Ikaros
767a8ecb08 ✏️ Plugin: 更新 随机禁言 插件功能描述 (#1495) 2022-12-21 13:58:11 +08:00
github-actions[bot]
b342940b69 📝 Update changelog 2022-12-21 04:46:10 +00:00
Jerry080801
7880f07db4 🍻 publish plugin 防撤回 (#1488) 2022-12-21 12:44:42 +08:00
github-actions[bot]
ac702d7eb7 📝 Update changelog 2022-12-21 03:32:39 +00:00
chrisyy2003
9f3b3b2c32 ✏️ Plugin: 更新 multi chatgpt 插件仓库地址 (#1487) 2022-12-21 11:31:35 +08:00
Ju4tCode
2d08465426 add cookies support for forward driver 2022-12-20 10:13:45 +00:00
github-actions[bot]
827d8fbc0e 📝 Update changelog 2022-12-20 07:22:48 +00:00
Ikaros-521
0320be1947 🍻 publish plugin 随机禁言 (#1485) 2022-12-20 15:21:42 +08:00
github-actions[bot]
aca65954bd 📝 Update changelog 2022-12-20 07:04:06 +00:00
RShock
1da9376fc8 🍻 publish plugin 只因进化录 (#1483) 2022-12-20 15:02:47 +08:00
github-actions[bot]
909b811f68 📝 Update changelog 2022-12-18 07:54:38 +00:00
Ju4tCode
ceecf9c692 🐛 fix on_type typing error (#1482) 2022-12-18 15:53:25 +08:00
github-actions[bot]
3de2922773 📝 Update changelog 2022-12-17 09:05:55 +00:00
Ju4tCode
a5d26b7747 add pycln to auto remove unused imports (#1481) 2022-12-17 17:04:38 +08:00
github-actions[bot]
932f50d1fb 📝 Update changelog 2022-12-17 06:25:10 +00:00
chrisyy2003
c69619f142 🍻 publish plugin GPT3 (#1479) 2022-12-17 14:24:08 +08:00
github-actions[bot]
5a49d1e0e2 📝 Update changelog 2022-12-16 05:17:35 +00:00
ReiiNoki
4f9f3f449c 🍻 publish plugin 熊老板 (#1471) 2022-12-16 13:16:23 +08:00
github-actions[bot]
60733c97be 📝 Update changelog 2022-12-15 05:39:28 +00:00
Yuelioi
7a1aa0c204 🍻 publish plugin QQ群文件备份 (#1477) 2022-12-15 13:38:27 +08:00
github-actions[bot]
6d1383a10c 📝 Update changelog 2022-12-15 05:31:38 +00:00
Ikaros-521
86006fafdc 🍻 publish plugin 戒色打卡日记 (#1474) 2022-12-15 13:30:24 +08:00
github-actions[bot]
44ca4f729a 📝 Update changelog 2022-12-14 05:01:36 +00:00
wsdtl
cf29209a55 🍻 publish plugin nonebot_plugin_idiom (#1468) 2022-12-14 13:00:22 +08:00
github-actions[bot]
5e78e2bb5d 📝 Update changelog 2022-12-13 09:49:05 +00:00
幼稚园园长
440e15e204 📝 Docs: 修复文档中部分超链接跳转到 /store.html 的问题 (#1470) 2022-12-13 17:47:45 +08:00
github-actions[bot]
6711a84cab 📝 Update changelog 2022-12-12 14:39:08 +00:00
Ikaros-521
5c2d2141e3 🍻 publish plugin 随机配色方案 (#1465) 2022-12-12 22:37:49 +08:00
github-actions[bot]
8ec1552fd6 📝 Update changelog 2022-12-11 17:09:38 +00:00
chrisyy2003
c1dca723ae 🍻 publish plugin multi-ChatGPT (#1461) 2022-12-12 01:08:28 +08:00
github-actions[bot]
b6cd0424fa 📝 Update changelog 2022-12-10 09:28:58 +00:00
ssttkkl
1beee94c1d 🍻 publish plugin 权限控制 (#1463) 2022-12-10 17:27:48 +08:00
github-actions[bot]
f2a618f663 📝 Update changelog 2022-12-09 16:13:43 +00:00
Akirami
c4286f1f39 💡 补充 params 模块的类型注解 (#1458) 2022-12-10 00:12:28 +08:00
github-actions[bot]
cd9e30bd52 📝 Update changelog 2022-12-09 06:49:49 +00:00
A-kirami
24ae0dfa15 🍻 publish plugin 汇率换算 (#1451) 2022-12-09 14:48:44 +08:00
github-actions[bot]
19a20a3407 📝 Update changelog 2022-12-09 06:44:08 +00:00
Akirami
36d7b44741 Feature: 添加正则匹配文本注入 (#1457)
Co-authored-by: Ju4tCode <42488585+yanyongyu@users.noreply.github.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2022-12-09 14:42:54 +08:00
dependabot[bot]
8176cd189c ⬆️ Bump nwtgck/actions-netlify from 1 to 2 (#1456)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-12-09 12:19:22 +08:00
github-actions[bot]
b25de93eb1 📝 Update changelog 2022-12-07 17:06:48 +00:00
A-kirami
79e636ad89 🍻 publish plugin 全群广播 (#1449) 2022-12-08 01:05:31 +08:00
github-actions[bot]
9c9973d8d8 📝 Update changelog 2022-12-07 15:49:53 +00:00
Akirami
04d4954d50 📝 Docs: 移除文档 自定义日志 中多余的符号 (#1448) 2022-12-07 23:48:48 +08:00
github-actions[bot]
2c81dc1975 📝 Update changelog 2022-12-07 13:44:39 +00:00
Akirami
850096ceaa 📝 Docs: 完善 调用平台 API 部分 (#1447)
Co-authored-by: StarHeart <starheart233@gmail.com>
2022-12-07 21:43:27 +08:00
github-actions[bot]
6bb15f6533 📝 Update changelog 2022-12-06 15:12:57 +00:00
Ikaros-521
92c6a8dd6e 🍻 publish plugin 图片背景消除 (#1445) 2022-12-06 23:11:44 +08:00
github-actions[bot]
723eef10bb 📝 Update changelog 2022-12-06 06:20:53 +00:00
Akirami
06c33ad6ea Feature: 支持主动销毁事件响应器 (#1444)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2022-12-06 14:19:48 +08:00
github-actions[bot]
9826bc29ca 📝 Update changelog 2022-12-05 15:22:06 +00:00
ssttkkl
f35b5b57b7 🍻 publish plugin 雀魂信息查询 (#1442) 2022-12-05 23:20:54 +08:00
github-actions[bot]
433b50c79c 📝 Update changelog 2022-12-05 08:44:04 +00:00
A-kirami
89f3496a2a 🍻 publish plugin ChatGPT (#1438) 2022-12-05 16:42:59 +08:00
github-actions[bot]
df7e8f6d83 📝 Update changelog 2022-12-04 05:23:34 +00:00
ZYKsslm
b57f17d447 🍻 publish plugin 免费快捷点歌插件 (#1435) 2022-12-04 13:22:09 +08:00
github-actions[bot]
665de1da3e 📝 Update changelog 2022-12-04 02:44:19 +00:00
Ikaros-521
091232e996 🍻 publish plugin 动画截图追溯来源 (#1433) 2022-12-04 10:43:09 +08:00
github-actions[bot]
9ee2d94f3c 📝 Update changelog 2022-12-03 06:35:43 +00:00
MingxuanGame
78bdfe65ba ✏️ Docs: 修正文档中部分配置文件示例的符号误用 (#1432)
* :typo: 使用方括号替换大括号

* ✏️ 修改 2.0.0rc2 的文档
2022-12-03 14:34:25 +08:00
github-actions[bot]
5006cf7be6 📝 Update changelog 2022-12-02 06:42:02 +00:00
jcjrobert
a818e0056e 🍻 publish plugin b站图片下载 (#1429) 2022-12-02 14:40:59 +08:00
github-actions[bot]
3efae8bfbc 📝 Update changelog 2022-12-02 05:57:28 +00:00
bridgeL
024d97b997 🍻 Plugin: 更新 ayaka_games 介绍 (#1431)
Co-authored-by: Su <wxlxy316@163.com>
2022-12-02 13:56:15 +08:00
github-actions[bot]
c90ab949d2 📝 Update changelog 2022-11-29 04:01:31 +00:00
Ju4tCode
e8ffa63b78 🐛 fix argument parser message (#1426) 2022-11-29 12:00:09 +08:00
github-actions[bot]
6c27ec7357 📝 Update changelog 2022-11-27 11:38:28 +00:00
Sena
9bf08593d7 ✏️ Plugin: 修改 novelai send magiadice 插件模块名 (#1423) 2022-11-27 19:37:15 +08:00
github-actions[bot]
b016a59a38 📝 Update changelog 2022-11-27 11:22:19 +00:00
Passerby-D
794395e737 🍻 publish plugin 记事本 (#1419) 2022-11-27 19:21:17 +08:00
github-actions[bot]
a758e6f06e 📝 Update changelog 2022-11-26 02:33:35 +00:00
monsterxcn
11feb2c0d0 🍻 publish plugin 原神前瞻直播兑换码查询 (#1421) 2022-11-26 10:32:16 +08:00
Ju4tCode
59a2ed7c2e 🎨 format code 2022-11-25 16:12:23 +00:00
github-actions[bot]
d83866f03b 🔖 Release 2.0.0rc2 2022-11-24 03:55:37 +00:00
Ju4tCode
cb83e76e16 📝 remove old doc version (#1417) 2022-11-24 11:50:20 +08:00
Ju4tCode
1644615462 🔖 bump version 2.0.0rc2 2022-11-24 03:35:31 +00:00
github-actions[bot]
89d8abf863 📝 Update changelog 2022-11-24 02:59:33 +00:00
p0ise
f8cf7c94ae 🍻 publish plugin 谁在窥屏 (#1415) 2022-11-24 10:58:29 +08:00
github-actions[bot]
bef494615f 📝 Update changelog 2022-11-24 02:58:06 +00:00
那个小白白白
6e110e725e 📝 Docs: 添加 ntchat 社区适配器 (#1414)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2022-11-24 10:56:53 +08:00
Ju4tCode
85390a14b6 ⬆️ upgrade dependencies (#1413) 2022-11-21 19:59:39 +08:00
github-actions[bot]
276041e314 📝 Update changelog 2022-11-21 10:46:01 +00:00
Ju4tCode
2922da7b2f Feature: 支持自定义 matchers 存储管理 (#1395) 2022-11-21 18:44:55 +08:00
github-actions[bot]
c783ab5e9b 📝 Update changelog 2022-11-20 09:42:50 +00:00
ZYKsslm
139190bff7 🍻 publish plugin 免费版NovelAI生图插件 (#1407) 2022-11-20 17:41:05 +08:00
github-actions[bot]
1524434444 📝 Update changelog 2022-11-19 16:05:11 +00:00
Ikaros
b6857d59b8 ✏️ 更新 nonebot_plugin_searchBiliInfo 插件标题和描述 (#1410) 2022-11-20 00:03:54 +08:00
github-actions[bot]
a7b0eb10a0 📝 Update changelog 2022-11-19 08:15:27 +00:00
synodriver
0eadb44e20 🐛 Fix: Bot __getattr__ 不再对 __xxx__ 方法返回 (#1398) 2022-11-19 16:14:03 +08:00
github-actions[bot]
6b43209d37 📝 Update changelog 2022-11-15 02:51:59 +00:00
Ju4tCode
a50990bef2 🐛 fix run pre/post hook not in context (#1391) 2022-11-15 10:50:52 +08:00
github-actions[bot]
f1525c1ecd 📝 Update changelog 2022-11-14 17:11:56 +00:00
Kaguya233qwq
7df9756205 🍻 publish plugin sky光遇 (#1393) 2022-11-15 01:10:50 +08:00
github-actions[bot]
376a720881 📝 Update changelog 2022-11-14 11:06:42 +00:00
Ju4tCode
c7377647fa Feature: 升级 devcontainer 配置 (#1392)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2022-11-14 19:05:34 +08:00
github-actions[bot]
cfbd6f1e4d 📝 Update changelog 2022-11-13 10:02:16 +00:00
EtherLeaF
cecc853f25 🍻 publish plugin Colab-NovelAI (#1389) 2022-11-13 18:01:08 +08:00
github-actions[bot]
fe5f85517e 📝 Update changelog 2022-11-13 09:38:21 +00:00
Ikaros-521
66040a7e44 🍻 publish plugin b站用户直播号、粉丝、舰团数查询 (#1384) 2022-11-13 17:37:22 +08:00
github-actions[bot]
81d2f017f6 📝 Update changelog 2022-11-12 10:23:32 +00:00
Akirami
4355025f87 Feature: 使用 importlib.metadata 替换 pkg_resources (#1388)
Co-authored-by: yanyongyu <42488585+yanyongyu@users.noreply.github.com>
2022-11-12 18:22:16 +08:00
github-actions[bot]
0bc8a39578 📝 Update changelog 2022-11-10 17:23:35 +00:00
Aziteee
7308f57776 🍻 publish plugin 投胎模拟器 (#1381) 2022-11-11 01:22:12 +08:00
github-actions[bot]
7de8912edb 📝 Update changelog 2022-11-09 15:55:14 +00:00
H-xiaoH
42a4edc3ee 🍻 publish plugin Apex API Query (#1374) 2022-11-09 23:54:00 +08:00
github-actions[bot]
4a12429a38 📝 Update changelog 2022-11-09 14:38:12 +00:00
Qianyiovo
092f6d05f5 🍻 publish bot Bread Dog Bot (#1379) 2022-11-09 22:37:07 +08:00
github-actions[bot]
2c0c05dca1 📝 Update changelog 2022-11-09 14:34:19 +00:00
Sena
31bafc832f 🍻 Plugin: Sena-nana项目拆分,地址更改 (#1378) 2022-11-09 22:33:08 +08:00
github-actions[bot]
e1720d8ea0 📝 Update changelog 2022-11-06 16:04:17 +00:00
jcjrobert
08be5724b9 🍻 publish plugin 随个人 (#1372) 2022-11-07 00:03:00 +08:00
github-actions[bot]
828714a4e3 📝 Update changelog 2022-11-06 06:58:29 +00:00
Melodyknit
a79eeb73a6 🍻 publish plugin 动漫资源获取 (#1370) 2022-11-06 14:57:18 +08:00
github-actions[bot]
c1aec637d5 📝 Update changelog 2022-11-04 07:19:58 +00:00
ssttkkl
12cc08efbd 🍻 publish plugin 日麻小工具 (#1364) 2022-11-04 15:18:35 +08:00
github-actions[bot]
f410af72dc 📝 Update changelog 2022-11-04 01:30:33 +00:00
StarHeart
113021cdf4 👷 CI: 测试环境添加 Python 3.11 (#1366)
Co-authored-by: yanyongyu <42488585+yanyongyu@users.noreply.github.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2022-11-04 09:29:13 +08:00
github-actions[bot]
fcc23f98f8 📝 Update changelog 2022-11-03 11:45:38 +00:00
gzy02
0b693c419e 🍻 publish bot hsbot (#1368) 2022-11-03 19:44:34 +08:00
github-actions[bot]
36e5b81510 📝 Update changelog 2022-11-02 17:05:59 +00:00
A-kirami
71f17bebaa 🍻 publish plugin 图像超分辨率增强 (#1361) 2022-11-03 01:04:38 +08:00
github-actions[bot]
2f45f25d13 📝 Update changelog 2022-11-02 16:57:54 +00:00
A-kirami
17d52446c3 🍻 publish plugin 二次元化图像 (#1359) 2022-11-03 00:56:36 +08:00
pre-commit-ci[bot]
2304aaf22b ⬆️ auto update by pre-commit hooks (#1358)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2022-11-01 14:12:53 +08:00
github-actions[bot]
a70c37de69 📝 Update changelog 2022-10-30 17:26:28 +00:00
ssttkkl
1cabc18277 🍻 publish plugin 日麻寄分器 (#1356) 2022-10-31 01:25:22 +08:00
github-actions[bot]
ed1235ed11 📝 Update changelog 2022-10-30 17:02:55 +00:00
MeetWq
9952b4e838 🍻 publish plugin 文本生成器 (#1354) 2022-10-31 01:01:33 +08:00
github-actions[bot]
e81390a104 📝 Update changelog 2022-10-30 16:16:02 +00:00
tkgs0
7bc22289b4 🍻 publish plugin 反嘴臭插件 (#1349) 2022-10-31 00:14:51 +08:00
github-actions[bot]
bfa4a079bf 📝 Update changelog 2022-10-28 07:06:09 +00:00
tkgs0
403850262b 🍻 publish plugin 用户&群聊黑名单 (#1347) 2022-10-28 15:05:08 +08:00
github-actions[bot]
ed25e7aa39 📝 Update changelog 2022-10-27 03:00:53 +00:00
ssttkkl
7ab480f044 🍻 publish plugin NoneBot SQLAlchemy封装 (#1344) 2022-10-27 10:59:36 +08:00
github-actions[bot]
d7d6152094 📝 Update changelog 2022-10-27 02:57:35 +00:00
bridgeL
2954e58c77 Plugin: 更新 ayaka 插件的主页链接 (#1346)
Co-authored-by: Su <wxlxy316@163.com>
2022-10-27 10:56:23 +08:00
github-actions[bot]
f684b96433 📝 Update changelog 2022-10-24 02:25:32 +00:00
jcjrobert
4388d1c9e6 🍻 publish plugin 通用抽图/语音 (#1340) 2022-10-24 10:24:15 +08:00
github-actions[bot]
876acf3e88 📝 Update changelog 2022-10-20 13:19:39 +00:00
Kaguya233qwq
963e73f517 🍻 publish plugin kfcrazy (#1338) 2022-10-20 21:18:27 +08:00
github-actions[bot]
bdc9e44142 📝 Update changelog 2022-10-20 03:14:13 +00:00
A-kirami
4f2efb7304 🍻 publish plugin 二次元图像鉴赏 (#1336) 2022-10-20 11:12:51 +08:00
github-actions[bot]
6ae891124f 📝 Update changelog 2022-10-19 11:27:42 +00:00
bridgeL
145bd4d4e1 🍻 publish plugin ayaka衍生插件 - 坏词撤回 (#1334) 2022-10-19 19:26:39 +08:00
github-actions[bot]
ab54049909 📝 Update changelog 2022-10-18 14:07:59 +00:00
Sena
7aa554f5a2 ✏️ Plugin: 补充 novelai 插件信息 (#1333) 2022-10-18 22:06:47 +08:00
github-actions[bot]
5566777374 📝 Update changelog 2022-10-18 09:57:33 +00:00
bridgeL
b2594f61de 🍻 publish plugin ayaka衍生插件 - 时区助手 (#1331) 2022-10-18 17:56:25 +08:00
github-actions[bot]
1ad1e0606c 📝 Update changelog 2022-10-18 07:07:05 +00:00
bridgeL
583d5060db 🍻 publish plugin ayaka衍生插件 - 谁是卧底 (#1329) 2022-10-18 15:05:45 +08:00
github-actions[bot]
eaa3dbdfa8 📝 Update changelog 2022-10-17 03:00:47 +00:00
bridgeL
03f378690a 🍻 publish plugin ayaka衍生插件 - 小游戏合集 (#1327) 2022-10-17 10:59:43 +08:00
github-actions[bot]
512c66ccc0 📝 Update changelog 2022-10-15 07:07:21 +00:00
lgc2333
9d20c7510a 🍻 publish plugin bnhhsh -「不能好好说话!」 (#1325) 2022-10-15 15:06:04 +08:00
github-actions[bot]
29ad8a6686 📝 Update changelog 2022-10-14 01:59:51 +00:00
Ju4tCode
db534b8824 Feature: 新增 dotenv 嵌套配置项支持 (#1324)
Co-authored-by: hemengyang <hmy0119@hotmail.com>
2022-10-14 09:58:44 +08:00
github-actions[bot]
67b96528af 📝 Update changelog 2022-10-13 04:47:18 +00:00
A-kirami
a5929f80f7 🍻 publish plugin AI绘图 (#1322) 2022-10-13 12:45:56 +08:00
github-actions[bot]
9619477a27 📝 Update changelog 2022-10-12 05:42:41 +00:00
Akirami
8377680fd7 Feature: 添加 State 响应器触发消息注入 (#1315) 2022-10-12 13:41:28 +08:00
github-actions[bot]
3e3d6f91a5 📝 Update changelog 2022-10-12 03:05:31 +00:00
sena-nana
1d3d886004 🍻 publish plugin novelai (#1318) 2022-10-12 11:04:34 +08:00
github-actions[bot]
ebc5a3cc9e 📝 Update changelog 2022-10-12 02:53:57 +00:00
Kaguyaya
1092767a51 🍻 publish plugin 游戏王小程序查价 (#1316) 2022-10-12 10:52:56 +08:00
github-actions[bot]
ab227ee64b 📝 Update changelog 2022-10-11 11:53:01 +00:00
cjladmin
00b37fb3d2 🍻 publish plugin 监测群事件 (#900) 2022-10-11 19:51:56 +08:00
github-actions[bot]
e6494dc98e 📝 Update changelog 2022-10-10 14:06:29 +00:00
JustUndertaker
c80869f952 🍻 publish adapter Ntchat (#1313) 2022-10-10 22:05:27 +08:00
github-actions[bot]
2be72eac5e 📝 Update changelog 2022-10-10 12:36:47 +00:00
HornCopper
830c4f8c6a 🍻 Bot: 修改 Inkar Suki 描述 (#1312) 2022-10-10 20:35:45 +08:00
github-actions[bot]
138fb458b7 📝 Update changelog 2022-10-08 08:18:40 +00:00
KarisAya
2c93f82ef3 🍻 publish plugin 轮盘禁言小游戏 (#1310) 2022-10-08 16:17:42 +08:00
github-actions[bot]
77aa16c2fc 📝 Update changelog 2022-10-07 02:33:18 +00:00
Ankhyty
945da7151e 🍻 publish plugin 真白萌自动签到 (#1307) 2022-10-07 10:32:15 +08:00
github-actions[bot]
41ea0df0a5 📝 Update changelog 2022-10-06 03:51:25 +00:00
Ju4tCode
cec45cf89c ⚰️ remove dead namespace (#1306) 2022-10-06 11:50:18 +08:00
Ju4tCode
2de8c66c70 ⬆️ upgrade pydantic dependency (#1305) 2022-10-05 15:13:54 +08:00
github-actions[bot]
0bcc4277e5 📝 Update changelog 2022-10-05 02:54:10 +00:00
Shadow403
d3dd93b36c 🍻 publish plugin BiliRequestAll (#1301) 2022-10-05 10:53:00 +08:00
github-actions[bot]
e9bd81d9bb 📝 Update changelog 2022-10-03 11:45:36 +00:00
17TheWord
997d4f5042 ✏️ 修改插件 MCQQ 主页地址 (#1303) 2022-10-03 19:44:21 +08:00
github-actions[bot]
3bb321c519 📝 Update changelog 2022-10-03 11:42:16 +00:00
AbCooly
fe92d29322 🍻 publish plugin 监听者 (#1298) 2022-10-03 19:41:11 +08:00
github-actions[bot]
3234871b53 🔖 Release 2.0.0-rc.1 2022-10-02 08:05:19 +00:00
Ju4tCode
03543f01f2 🔖 bump version 2.0.0rc1 (#1300) 2022-10-02 15:49:31 +08:00
github-actions[bot]
ba5c0303c7 📝 Update changelog 2022-09-29 09:21:16 +00:00
Ju4tCode
e56fdd04ad 🍻 publish adapter GitHub (#1297) 2022-09-29 17:20:05 +08:00
github-actions[bot]
9f10bb70db 📝 Update changelog 2022-09-29 08:57:20 +00:00
Ju4tCode
71aad502d1 🐛 Fix: 内置规则和权限没有捕获错误 (#1291) 2022-09-29 16:56:06 +08:00
github-actions[bot]
ab85b8651e 📝 Update changelog 2022-09-29 08:43:25 +00:00
NewYearPrism
4fe8929441 🍻 publish plugin 文字识别 (#1294) 2022-09-29 16:42:06 +08:00
github-actions[bot]
5c303710f6 📝 Update changelog 2022-09-29 08:17:33 +00:00
RandomEnch
68d2ada94b 🍻 publish plugin 在线编曲 (#1292) 2022-09-29 16:16:07 +08:00
github-actions[bot]
75470fe157 📝 Update changelog 2022-09-25 15:24:53 +00:00
koking0
47b3fc516a 🍻 publish plugin 图灵机器人 (#1288) 2022-09-25 23:23:49 +08:00
github-actions[bot]
84c24b014f 📝 Update changelog 2022-09-23 12:26:33 +00:00
lgc2333
756cde6525 🍻 publish plugin PicStatus (#1286) 2022-09-23 20:25:22 +08:00
github-actions[bot]
57ef19af94 📝 Update changelog 2022-09-21 08:58:18 +00:00
Kaguya233qwq
5927b517e2 🍻 publish plugin 阿里云盘福利码自动兑换 (#1282) 2022-09-21 16:57:01 +08:00
github-actions[bot]
132205bfcc 📝 Update changelog 2022-09-21 08:49:44 +00:00
dpm12345
b31dfa9ab0 🍻 publish plugin gal角色语音生成 (#1280) 2022-09-21 16:48:17 +08:00
github-actions[bot]
b249802c38 📝 Update changelog 2022-09-19 09:56:45 +00:00
Todysheep
9df705aaa7 🍻 publish plugin 漂流瓶 (#1278) 2022-09-19 17:55:38 +08:00
github-actions[bot]
a0df535f0c 📝 Update changelog 2022-09-18 14:36:17 +00:00
AkiraXie
31022a653d Feature: SUPERUSER 权限匹配任意超管事件 (#1275)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2022-09-18 22:33:36 +08:00
github-actions[bot]
ba77443dde 📝 Update changelog 2022-09-18 14:30:09 +00:00
XZhouQD
984f743097 🍻 publish plugin BWIKI助手移植版 (#1273) 2022-09-18 22:29:03 +08:00
github-actions[bot]
638a9c94af 📝 Update changelog 2022-09-16 11:52:43 +00:00
su226
92f1d5a4d7 🍻 publish bot IdhagnBot (#1266) 2022-09-16 19:51:35 +08:00
github-actions[bot]
248af2ae1a 📝 Update changelog 2022-09-15 13:58:15 +00:00
littlebutt
4c37be7312 🍻 publish plugin nonebot物联网插件 (#1264) 2022-09-15 21:56:57 +08:00
github-actions[bot]
2cb8eafa81 📝 Update changelog 2022-09-13 02:47:25 +00:00
CMHopeSunshine
05bff5ec17 🍻 publish bot LittlePaimon (#1255) 2022-09-13 10:46:18 +08:00
github-actions[bot]
13245cb58f 📝 Update changelog 2022-09-12 13:27:14 +00:00
AbCooly
37bc7326b5 🍻 publish plugin 狼人杀插件 (#1251) 2022-09-12 21:25:50 +08:00
github-actions[bot]
f6d189d8c5 📝 Update changelog 2022-09-12 04:30:37 +00:00
bridgeL
600ef7031f 🍻 publish plugin ayaka - 文字游戏开发辅助插件 (#1253) 2022-09-12 12:29:30 +08:00
github-actions[bot]
7bedf7c8d0 📝 Update changelog 2022-09-11 12:59:20 +00:00
ppxxxg22
f62ee5893c 🍻 publish plugin 图像超分辨率重建 (#1249) 2022-09-11 20:57:55 +08:00
github-actions[bot]
71234e9a68 📝 Update changelog 2022-09-10 12:56:12 +00:00
Akirami
3bbca0fa70 Feature: 改进 CommandGroupMatcherGroup 的结构 (#1240) 2022-09-10 20:54:49 +08:00
github-actions[bot]
20f144ba93 📝 Update changelog 2022-09-09 10:53:37 +00:00
Akirami
4c8bc9f0cb 🍻 Fix: 修正 GenshinUID 的发布类型 (#1243) 2022-09-09 18:52:12 +08:00
github-actions[bot]
064509f26b 📝 Update changelog 2022-09-09 03:54:19 +00:00
Ju4tCode
8c42490a7e 🔇 Feature: 调整日志输出格式与等级 (#1233) 2022-09-09 11:52:57 +08:00
github-actions[bot]
179d7105c9 📝 Update changelog 2022-09-09 02:16:34 +00:00
KarisAya
1c14e638c8 🍻 publish plugin Minecraft Server 聊天同步 (#1244) 2022-09-09 10:15:26 +08:00
github-actions[bot]
c6eef06b55 📝 Update changelog 2022-09-08 02:38:39 +00:00
Akirami
beef564a22 🔥 remove unused imports (#1236) 2022-09-08 10:37:16 +08:00
github-actions[bot]
672f2ceecc 📝 Update changelog 2022-09-07 02:25:49 +00:00
Sclock
28142402d7 🍻 publish plugin 查询ETH合并日期 (#1231) 2022-09-07 10:24:39 +08:00
github-actions[bot]
b886329fb8 📝 Update changelog 2022-09-07 02:00:34 +00:00
Ju4tCode
a0b186aff3 ♻️ improve dependent structure (#1227) 2022-09-07 09:59:05 +08:00
pre-commit-ci[bot]
595c64e760 ⬆️ auto update by pre-commit hooks (#1229)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2022-09-06 09:54:57 +08:00
github-actions[bot]
5114749073 📝 Update changelog 2022-09-05 06:23:20 +00:00
84227871
af2d7b5797 🍻 publish bot GenshinUID (#1225) 2022-09-05 14:21:53 +08:00
github-actions[bot]
56943c0908 📝 Update changelog 2022-09-05 05:52:33 +00:00
SDIJF1521
45478deb95 🍻 publish bot 小白机器人 (#1223) 2022-09-05 13:51:16 +08:00
github-actions[bot]
d281ec5bf9 📝 Update changelog 2022-09-05 02:02:23 +00:00
17TheWord
291a7cbb8b 🍻 publish plugin 星际战甲事件查询 (#1219) 2022-09-05 10:01:20 +08:00
github-actions[bot]
41259546bd 📝 Update changelog 2022-09-04 11:31:42 +00:00
Ljzd-PRO
f96038241f ✏️ 更新插件米游社辅助工具 tag (#1221) 2022-09-04 19:30:32 +08:00
github-actions[bot]
b051320d78 📝 Update changelog 2022-09-04 03:18:37 +00:00
Ljzd-PRO
d12efac9f4 🍻 publish plugin 米游社辅助工具 (#1217) 2022-09-04 11:17:20 +08:00
github-actions[bot]
f87a38a30a 📝 Update changelog 2022-09-03 02:27:15 +00:00
monsterxcn
bf016b3f69 🍻 publish plugin 原神每日材料查询 (#1215) 2022-09-03 10:26:07 +08:00
github-actions[bot]
373f5255f1 📝 Update changelog 2022-09-02 05:41:40 +00:00
Ju4tCode
5a35015195 📝 update documentation 2022-09-02 13:40:24 +08:00
Melodyknit
d3a2f1dc08 🍻 publish adapter Console (#1212) 2022-09-02 13:40:24 +08:00
github-actions[bot]
f1aec4eb10 📝 Update changelog 2022-09-01 02:42:53 +00:00
Ju4tCode
cd30be21ba 🐛 fix nested user permission update (#1208) 2022-09-01 10:41:43 +08:00
17TheWord
f150a9ee89 🍻 publish plugin MC_QQ_MCRcon (#1210) 2022-09-01 10:41:08 +08:00
github-actions[bot]
e68281f60f 📝 Update changelog 2022-09-01 02:05:08 +00:00
monsterxcn
32be64485a 🍻 publish plugin 原神角色展柜查询 (#1207) 2022-09-01 10:03:55 +08:00
github-actions[bot]
c76f492305 📝 Update changelog 2022-08-31 07:46:34 +00:00
s52047qwas
29b0351644 🍻 publish plugin 修仙模拟器 (#1195) 2022-08-31 15:45:27 +08:00
github-actions[bot]
ef3350fd9c 📝 Update changelog 2022-08-31 06:57:46 +00:00
Raidenneox
459699de5c 🍻 publish plugin 赛博浅草寺 (#1205) 2022-08-31 14:56:25 +08:00
github-actions[bot]
31c3eb8fd6 📝 Update changelog 2022-08-31 02:08:36 +00:00
Lan
1cfdee2645 Featue: load_plugin 支持 pathlib.Path (#1194)
Co-authored-by: Ju4tCode <42488585+yanyongyu@users.noreply.github.com>
2022-08-31 10:07:14 +08:00
github-actions[bot]
4e76518a58 📝 Update changelog 2022-08-31 02:05:28 +00:00
GC-ZF
b53e029df1 🍻 publish plugin 不背单词 (#1203) 2022-08-31 10:04:01 +08:00
github-actions[bot]
c1faf68806 📝 Update changelog 2022-08-30 11:39:33 +00:00
CofinCup
fe64b904ff 🍻 publish plugin 自识别todo (#1192) 2022-08-30 19:38:24 +08:00
github-actions[bot]
a621ade449 📝 Update changelog 2022-08-30 01:55:21 +00:00
Ju4tCode
3fda978064 Feature: 新增事件类型过滤 rule (#1183) 2022-08-30 09:54:09 +08:00
github-actions[bot]
60ab93164c 📝 Update changelog 2022-08-28 12:12:28 +00:00
sena-nana
2b22d5abda 🍻 publish plugin 雨课堂自动签到 (#1188) 2022-08-28 20:11:22 +08:00
github-actions[bot]
e3a4834383 📝 Update changelog 2022-08-27 14:30:10 +00:00
sena-nana
21087036af 🍻 publish plugin 反馈及通知 (#1186) 2022-08-27 22:29:05 +08:00
github-actions[bot]
94d336ef4d 📝 Update changelog 2022-08-27 14:16:48 +00:00
sena-nana
07707213a5 🍻 publish plugin MagiaDice骰娘及TRPGLOG (#1184) 2022-08-27 22:15:41 +08:00
github-actions[bot]
7579878fb4 📝 Update changelog 2022-08-27 13:51:18 +00:00
Nranphy
6599b6420e 🍻 publish plugin 面麻小助手 (#1190) 2022-08-27 21:50:04 +08:00
github-actions[bot]
135c6e8168 📝 Update changelog 2022-08-26 02:16:15 +00:00
X-Skirt-X
743e7363ea 🍻 publish plugin 话痨排行榜 (#1181) 2022-08-26 10:15:07 +08:00
github-actions[bot]
a101428c81 📝 Update changelog 2022-08-25 02:27:25 +00:00
YunJin
0d2b1f693e ✏️ Plugin: 修改插件多功能简易群管信息 (#1180) 2022-08-25 10:26:06 +08:00
github-actions[bot]
e5a53dfd5c 📝 Update changelog 2022-08-25 02:00:45 +00:00
KarisAya
915c2b3e43 🍻 publish plugin 保存群聊闪照 (#1178) 2022-08-25 09:59:40 +08:00
github-actions[bot]
767b6a9913 📝 Update changelog 2022-08-24 01:55:17 +00:00
Ju4tCode
3f8af04803 add rich text support for shell command (#1171) 2022-08-24 09:54:08 +08:00
github-actions[bot]
00af815b8a 📝 Update changelog 2022-08-23 07:01:42 +00:00
InariInDream
24df594b97 🍻 publish plugin 课表查询 (#1167) 2022-08-23 15:00:34 +08:00
github-actions[bot]
d6567f9288 📝 Update changelog 2022-08-23 06:35:43 +00:00
yzyyz1387
4eb158245e 🍻 publish plugin 业余无线电助手 (#1172) 2022-08-23 14:34:27 +08:00
github-actions[bot]
ef35266d3e 📝 Update changelog 2022-08-23 04:04:55 +00:00
he0119
1d1beb100a 🍻 publish plugin NoneBot 树形帮助插件 (#1176) 2022-08-23 12:03:41 +08:00
github-actions[bot]
06ab6093b7 📝 Update changelog 2022-08-23 03:54:04 +00:00
yzyyz1387
c1ce7fb940 🍻 publish plugin 工作性价比 (#1174) 2022-08-23 11:53:01 +08:00
github-actions[bot]
6e03ddbf12 📝 Update changelog 2022-08-23 03:48:13 +00:00
KarisAya
40c8787828 🍻 publish plugin 娶群友 (#1169) 2022-08-23 11:47:03 +08:00
github-actions[bot]
be459e0bbb 📝 Update changelog 2022-08-22 10:12:31 +00:00
ssttkkl
1056828f90 🍻 publish plugin PixivBot (#1164) 2022-08-22 18:11:28 +08:00
github-actions[bot]
ef18e8943d 📝 Update changelog 2022-08-22 06:40:04 +00:00
Mix
92ff1df419 🐛 修复当消息与不支持的类型相加时抛出的异常类型错误 (#1166) 2022-08-22 14:39:00 +08:00
github-actions[bot]
be5ac88a18 📝 Update changelog 2022-08-21 03:25:26 +00:00
Yiyuiii
fac647370a 🍻 publish plugin 日韩中 VITS 模型原神拟声 (#1161) 2022-08-21 11:24:11 +08:00
github-actions[bot]
05a3891903 📝 Update changelog 2022-08-20 02:19:01 +00:00
Ju4tCode
4deae8f00c 🔥 remove deprecated State param (#1160) 2022-08-20 10:17:52 +08:00
github-actions[bot]
0f70e975b0 📝 Update changelog 2022-08-19 01:10:13 +00:00
SkyDynamic
982680be91 🍻 publish plugin 每日人品 (#1155) 2022-08-19 09:09:10 +08:00
github-actions[bot]
96b0a863e6 📝 Update changelog 2022-08-18 01:43:06 +00:00
CrazyBoyM
d64bb37c6d 🍻 publish plugin nonebot-plugin-drawer (#1145) 2022-08-18 09:41:48 +08:00
github-actions[bot]
51d7f1783d 📝 Update changelog 2022-08-16 02:08:08 +00:00
YunJin
64c18379c9 ✏️ change plugin admin hello info (#1159) 2022-08-16 10:06:52 +08:00
GC_XiaoZhang
2735f0cba9 ✏️ change plugin fireN info (#1158) 2022-08-16 10:06:02 +08:00
github-actions[bot]
660dbaf3b8 📝 Update changelog 2022-08-16 02:04:40 +00:00
Ju4tCode
898c29d7ee 💥 remove deprecated nonebot.plugins toml table (#1151)
Feature: 移除过时的 `nonebot.plugins` toml 配置
2022-08-16 10:03:37 +08:00
github-actions[bot]
cdc507bab9 📝 Update changelog 2022-08-15 13:34:28 +00:00
YunJin
f32bcdc1fc ✏️ update plugin 多功能简易群管 (#1154) 2022-08-15 21:33:13 +08:00
github-actions[bot]
013602da21 📝 Update changelog 2022-08-14 11:42:11 +00:00
Ju4tCode
4974c596ec 💥 remove Python 3.7 support (#1148) 2022-08-14 19:41:00 +08:00
github-actions[bot]
0620bec51f 📝 Update changelog 2022-08-14 09:11:29 +00:00
KarisAya
8870e6a26e 🍻 publish plugin 小游戏合集 (#1149) 2022-08-14 17:10:21 +08:00
github-actions[bot]
0e3ed0e7ab 📝 Update changelog 2022-08-12 07:02:00 +00:00
HuYihe2008
6cc3b68447 🍻 publish plugin 简易群管(带入群欢迎) (#1141) 2022-08-12 15:00:52 +08:00
github-actions[bot]
549a37b172 📝 Update changelog 2022-08-11 05:52:38 +00:00
ZombieFly
16394ad68b 🍻 publish plugin wiki条目搜索、获取简介 (#1132) 2022-08-11 13:51:20 +08:00
github-actions[bot]
57e580c255 📝 Update changelog 2022-08-11 05:51:05 +00:00
Ankhyty
6c23d89494 🍻 publish plugin bangumi搜索 (#1136) 2022-08-11 13:50:01 +08:00
github-actions[bot]
675e70f579 📝 Update changelog 2022-08-09 11:01:23 +00:00
bingqiu456
7a098b96f8 🍻 publish plugin 疫情小助手-频道版 (#1130) 2022-08-09 19:00:15 +08:00
github-actions[bot]
c9794bf91d 📝 Update changelog 2022-08-08 13:08:47 +00:00
Ju4tCode
1766d4da69 💥 remove deprecated export (#1125) 2022-08-08 21:07:36 +08:00
github-actions[bot]
6583bc8c61 📝 Update changelog 2022-08-08 04:19:01 +00:00
17TheWord
179f16346a 🍻 publish plugin MC_QQ通信 (#1126) 2022-08-08 12:17:47 +08:00
github-actions[bot]
badb0c9ff4 📝 Update changelog 2022-08-08 02:26:00 +00:00
lgc2333
ee0ea85e40 🍻 publish plugin BAWiki (#1128) 2022-08-08 10:24:48 +08:00
github-actions[bot]
e5e69c2726 🔖 Release 2.0.0-beta.5 2022-08-04 06:27:40 +00:00
github-actions[bot]
7c7ea613e9 📝 Update changelog 2022-08-04 06:18:34 +00:00
Ju4tCode
bb1b94e5e3 🔖 bump version 2.0.0-beta.5 (#1122) 2022-08-04 14:14:50 +08:00
github-actions[bot]
8420add975 📝 Update changelog 2022-08-04 05:40:36 +00:00
Ju4tCode
2192e8cb6d 🐛 fix parent detect error after require (#1121) 2022-08-04 13:39:20 +08:00
github-actions[bot]
48ccef2f06 📝 Update changelog 2022-08-02 02:31:11 +00:00
AkiraXie
c6bc24efc2 🐛 run_postprecessors handle matcher.state now (#1119) 2022-08-02 10:29:48 +08:00
github-actions[bot]
63f8d78d20 📝 Update changelog 2022-08-01 06:19:28 +00:00
ArgonarioD
db36c262db 🍻 publish plugin 「能不能好好说话?」缩写翻译 (#1117) 2022-08-01 14:18:15 +08:00
github-actions[bot]
732a13b692 📝 Update changelog 2022-08-01 02:53:25 +00:00
Ju4tCode
71bf1d1147 🐛 fix import error if setuptools not installed (#1116)
Fix: 修复 setuptools 未安装导致 ImportError
2022-08-01 10:52:15 +08:00
github-actions[bot]
6e98ac031c 📝 Update changelog 2022-07-31 02:33:58 +00:00
syrinka
9a49354ddd 🍻 publish plugin 推送钩子 (#1114) 2022-07-31 10:32:36 +08:00
github-actions[bot]
455752bd92 📝 Update changelog 2022-07-28 04:42:49 +00:00
yuyuziYYZ
2a51b07229 🍻 publish bot SkadiBot (#1112) 2022-07-28 12:41:42 +08:00
github-actions[bot]
732b5b0b1b 📝 Update changelog 2022-07-25 02:01:16 +00:00
ziru-w
a0dcc7753c 🍻 publish plugin 易命令 (#1110) 2022-07-25 09:59:58 +08:00
github-actions[bot]
12942f2d50 📝 Update changelog 2022-07-23 02:25:34 +00:00
bingqiu456
192d094f54 🍻 publish plugin 群昵称时间 (#1108) 2022-07-23 10:24:17 +08:00
github-actions[bot]
bb02d50837 📝 Update changelog 2022-07-22 06:23:48 +00:00
那个小白白白
bc8c65d0d8 Bot: 修改剑网三 bot 信息 (#1107) 2022-07-22 14:22:36 +08:00
github-actions[bot]
9447b1f462 📝 Update changelog 2022-07-20 02:23:17 +00:00
Dobiichi-Origami
c03b0c73cb Feature: on_x 支持 expire_time 参数 (#1106)
Co-authored-by: Dobiichi-Origami <454470535@qq.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: yanyongyu <42488585+yanyongyu@users.noreply.github.com>
2022-07-20 10:21:31 +08:00
github-actions[bot]
19f4c01ad3 📝 Update changelog 2022-07-15 02:12:35 +00:00
synodriver
9bd07b9ced add block driver startup/shutdown sync support (#1104)
Feature: 正向驱动器 startup/shutdown hook 支持同步函数
2022-07-15 10:11:19 +08:00
github-actions[bot]
fe5cf5624c 📝 Update changelog 2022-07-14 03:31:37 +00:00
Shine-Light
a14c38300e 🍻 publish bot 真宵Bot (#1102) 2022-07-14 11:30:34 +08:00
github-actions[bot]
9e908d5b3f 📝 Update changelog 2022-07-12 07:22:59 +00:00
ziru-w
f1ab95489c 🍻 publish plugin 处理好友添加和群邀请 (#1098) 2022-07-12 15:21:30 +08:00
github-actions[bot]
3c42e26e27 📝 Update changelog 2022-07-12 06:59:52 +00:00
zheuziihau
c248b8c354 🍻 publish plugin 明日方舟寻访记录分析 (#1096) 2022-07-12 14:58:45 +08:00
github-actions[bot]
0ecea50778 📝 Update changelog 2022-07-11 08:05:27 +00:00
ziru-w
33d4d01d51 🍻 publish plugin b站视频每日推送 (#1094) 2022-07-11 16:04:06 +08:00
github-actions[bot]
1667440c64 📝 Update changelog 2022-07-10 06:38:53 +00:00
KarisAya
141527238c 🍻 publish plugin 自动回复(文i)插件 (#1089) 2022-07-10 14:37:54 +08:00
github-actions[bot]
e2d0453741 📝 Update changelog 2022-07-10 02:48:38 +00:00
10-24
0849df1c76 🍻 publish plugin ACC计算工具 (#1092) 2022-07-10 10:47:35 +08:00
github-actions[bot]
c4d45c087a 📝 Update changelog 2022-07-08 04:26:23 +00:00
Ju4tCode
be15cfabcc 📝 Docs: 添加 nonemoji 并更新开发指南 (#1088)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2022-07-08 12:24:48 +08:00
github-actions[bot]
dddbeb389f 📝 Update changelog 2022-07-06 03:02:29 +00:00
yaowan233
bdfaf4840f 🍻 publish plugin OSU查分插件 (#1081) 2022-07-06 11:00:35 +08:00
GC-ZF
b37b1380a3 🍻 publish plugin 战地1、5战绩查询工具 (#1086) 2022-07-06 10:59:34 +08:00
github-actions[bot]
d8ed5c2e80 📝 Update changelog 2022-07-06 02:38:40 +00:00
GC-ZF
4bc391c066 🍻 publish plugin 一起燚xN吧 (#1084) 2022-07-06 10:37:18 +08:00
pre-commit-ci[bot]
5aa6138bf3 ⬆️ auto update by pre-commit hooks (#1080)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2022-07-05 10:17:39 +08:00
github-actions[bot]
fc78b9c547 📝 Update changelog 2022-07-05 02:17:00 +00:00
Ju4tCode
118874080d ✏️ fix docs event message type error (#1079) 2022-07-05 10:15:55 +08:00
github-actions[bot]
cf2137a1a9 📝 Update changelog 2022-07-02 02:21:11 +00:00
StarHeart
14b145b58d 📝 Docs: 修复旧 Vuepress 文档缓存问题 (#1077)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2022-07-02 10:19:51 +08:00
github-actions[bot]
1aba737cbd 📝 Update changelog 2022-07-01 02:58:18 +00:00
Ju4tCode
fe38b1f17f 📝 Docs: 更新 Readme 贡献图片 (#1074) 2022-07-01 10:57:05 +08:00
github-actions[bot]
776651284f 📝 Update changelog 2022-07-01 02:39:14 +00:00
CMHopeSunshine
068cc3a7ea 🍻 publish plugin 米游币商品自动兑换 (#1075) 2022-07-01 10:38:16 +08:00
github-actions[bot]
0e7e88cfa2 📝 Update changelog 2022-06-30 06:31:31 +00:00
StarHeart
c120f9be70 📝 unregister sw loaded from vuepress (#1073) 2022-06-30 14:30:15 +08:00
github-actions[bot]
563436b38e 📝 Update changelog 2022-06-30 06:29:47 +00:00
shinianj
386be6cbb6 🍻 publish plugin 赛马 (#1068) 2022-06-30 14:28:42 +08:00
github-actions[bot]
b5e29533d8 📝 Update changelog 2022-06-30 04:20:11 +00:00
MingxuanGame
beb19adad5 📝 fix docs call Permission wrong (#1072) 2022-06-30 12:19:10 +08:00
github-actions[bot]
e2289c78b0 📝 Update changelog 2022-06-29 02:56:19 +00:00
hamo-reid
d3f261eb34 🍻 publish plugin PicMenu (#1070) 2022-06-29 10:55:16 +08:00
github-actions[bot]
d54c2e6bf4 📝 Update changelog 2022-06-28 02:26:03 +00:00
Mai-icy
6fdebc4912 🍻 publish plugin 面包店小游戏 (#1063) 2022-06-28 10:25:02 +08:00
github-actions[bot]
f1ffac5ca7 📝 Update changelog 2022-06-25 08:20:06 +00:00
A-kirami
12716ee79a 🍻 publish plugin 黑白名单 (#1060) 2022-06-25 16:19:08 +08:00
github-actions[bot]
42413281bb 📝 Update changelog 2022-06-25 04:34:24 +00:00
github-actions[bot]
a67eda4c80 📝 Update changelog 2022-06-24 02:52:05 +00:00
Akirami
9dbea871b8 ✏️ fix type T_RunPostProcessor incorrect description (#1057)
Bug: 修复 typing 中 T_RunPostProcessor 类型的注释描述不正确
2022-06-24 10:51:06 +08:00
github-actions[bot]
4181f4ca77 📝 Update changelog 2022-06-24 02:49:36 +00:00
Special-Week
fe4a33d19b 🍻 publish plugin BitTorrent (#1058) 2022-06-24 10:48:29 +08:00
github-actions[bot]
58d8815f39 🔖 Release 2.0.0-beta.4 2022-06-20 11:40:59 +00:00
Ju4tCode
b80083fed5 🔖 bump version 2.0.0-beta.4 (#1056) 2022-06-20 19:29:56 +08:00
github-actions[bot]
4ba17d900a 📝 Update changelog 2022-06-20 07:53:30 +00:00
Ju4tCode
f11970132c Fix: 修复 MessageSegment 在有额外数据时报错 (#1055) 2022-06-20 15:52:12 +08:00
github-actions[bot]
c91c9380a7 📝 Update changelog 2022-06-20 07:51:00 +00:00
Ju4tCode
06ee47edcd Feature: 添加插件元信息定义 (#1046) 2022-06-20 15:49:53 +08:00
github-actions[bot]
a82ce00a4b 📝 Update changelog 2022-06-19 01:25:55 +00:00
AquamarineCyan
e0902eeb58 🍻 publish plugin 历史上的今天 (#1048) 2022-06-19 09:24:36 +08:00
github-actions[bot]
b754ac2fbc 📝 Update changelog 2022-06-18 07:59:15 +00:00
Special-Week
1ae0a654bf 🍻 publish plugin 智能回复 (#1053) 2022-06-18 15:58:10 +08:00
github-actions[bot]
b85348f648 📝 Update changelog 2022-06-18 06:49:01 +00:00
Ju4tCode
7b06469a30 🐛 fix env var not override dotenv file (#1052) 2022-06-18 14:47:42 +08:00
github-actions[bot]
a62a49d477 📝 Update changelog 2022-06-18 03:13:55 +00:00
Special-Week
e2f96a0b5c 🍻 publish plugin nonebot_plugin_setu4 (#1050) 2022-06-18 11:12:40 +08:00
github-actions[bot]
28c22b7511 📝 Update changelog 2022-06-16 03:43:18 +00:00
nikissXI
c027e4d2ce 🍻 publish bot nya_bot (#1044) 2022-06-16 11:42:09 +08:00
github-actions[bot]
e38bb2b530 📝 Update changelog 2022-06-06 02:26:03 +00:00
18870
19a9a3c3c5 🍻 publish plugin 命令重启机器人 (#1037) 2022-06-06 10:24:55 +08:00
github-actions[bot]
4c8f5059db 📝 Update changelog 2022-06-04 11:34:00 +00:00
ZMXC01
9ab1acf1e7 🍻 publish plugin 青年大学习自动提交 (#1035) 2022-06-04 19:32:59 +08:00
github-actions[bot]
175acd38eb 📝 Update changelog 2022-06-03 13:00:11 +00:00
BlueGlassBlock
4241eb538c 🎨 Feature: 日志记录自动检测终端是否支持彩色 (#1034)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2022-06-03 20:59:04 +08:00
github-actions[bot]
c55b32b9f9 📝 Update changelog 2022-06-03 02:42:41 +00:00
bingqiu456
2082e5f5c2 🍻 publish plugin 疫情小助手 (#1032) 2022-06-03 10:41:18 +08:00
SEAFHMC
8485a356e7 🍻 publish plugin 谁艾特我了 (#1030) 2022-06-02 14:48:41 +08:00
dependabot[bot]
f6f70fb435 ⬆️ Bump httpx from 0.22.0 to 0.23.0 (#1029)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-06-02 11:21:28 +08:00
github-actions[bot]
c3ce4f76d1 📝 Update changelog 2022-06-01 16:37:04 +00:00
benx1n
db90a9ebab 🍻 publish plugin Hikari-战舰世界水表查询 (#1024) 2022-06-02 00:35:51 +08:00
github-actions[bot]
3962a98743 📝 Update changelog 2022-06-01 10:36:51 +00:00
Ju4tCode
3ce23bc593 🍻 publish adapter OneBot V12 (#1026)
Co-authored-by: yanyongyu <yanyongyu@users.noreply.github.com>
2022-06-01 18:35:40 +08:00
github-actions[bot]
3238a82042 📝 Update changelog 2022-05-31 04:36:51 +00:00
axStar
ed1f920088 🍻 publish plugin Warframe时间查询 (#1022) 2022-05-31 12:35:52 +08:00
github-actions[bot]
c34b3439fa 📝 Update changelog 2022-05-30 12:41:17 +00:00
Ju4tCode
bd2225c43c 🍻 publish plugin imagetools (#1020) (#1021)
Co-authored-by: MeetWq <MeetWq@users.noreply.github.com>
2022-05-30 20:40:10 +08:00
github-actions[bot]
381234725e 📝 Update changelog 2022-05-29 02:57:28 +00:00
NumberSir
a5ee6ac401 🍻 publish plugin 明日方舟工具箱 (#1018) 2022-05-29 10:56:14 +08:00
github-actions[bot]
79fd12768d 📝 Update changelog 2022-05-27 09:29:27 +00:00
ASTWY
c0ab86f91b 🍻 publish plugin B站视频伪分享卡片 (#1013) 2022-05-27 17:28:20 +08:00
Ju4tCode
ae21bfdd0e 👷 fix python ci cache path error (#1012)
* 👷 fix python ci cache path error

* 🐛 fix path error

* ✏️ fix typo
2022-05-27 11:25:51 +08:00
github-actions[bot]
6cfa21b15c 📝 Update changelog 2022-05-26 08:36:55 +00:00
Ju4tCode
fa3ed2b58c improve plugin system (#1011) 2022-05-26 16:35:47 +08:00
github-actions[bot]
579839f2a4 📝 Update changelog 2022-05-24 11:34:00 +00:00
shoucandanghehe
daa95d02ac 🍻 publish plugin TETRIS Stats (#1008) 2022-05-24 19:32:52 +08:00
github-actions[bot]
33c261c802 📝 Update changelog 2022-05-24 04:37:58 +00:00
Jigsaw
558e073cfa 📝 Add style guides for document (#1005) 2022-05-24 12:36:59 +08:00
github-actions[bot]
c52637a3b8 📝 Update changelog 2022-05-24 02:43:21 +00:00
kexue-z
e3d1f572ed 🍻 publish plugin 签到插件 (#1006) 2022-05-24 10:42:16 +08:00
github-actions[bot]
af86b96974 📝 Update changelog 2022-05-22 17:16:30 +00:00
snowyfirefly
ddc42f7be8 🍻 publish bot LiteyukiBot-轻雪机器人 (#1002) 2022-05-23 01:15:18 +08:00
github-actions[bot]
41d50641ad 📝 Update changelog 2022-05-22 11:43:31 +00:00
Ju4tCode
6feed0610b 🐛 fix union validation error (#1001) 2022-05-22 19:42:30 +08:00
github-actions[bot]
fe43cc92a5 📝 Update changelog 2022-05-21 14:04:12 +00:00
Ju4tCode
f6fb3b3970 📝 Docs: 更新 require 样例 (#996) 2022-05-21 22:03:19 +08:00
github-actions[bot]
d8ea7f1e6f 📝 Update changelog 2022-05-21 11:42:46 +00:00
Mix
dd55650f0a 📝 Update QQ Channel icon in README.md (#997)
* 📝 Update QQ Channel icon

* 📝  Minimize QQ Channel icon SVG
2022-05-21 19:41:50 +08:00
github-actions[bot]
20f414d0de 📝 Update changelog 2022-05-21 03:18:43 +00:00
AkiraXie
5924f1e7ac 📝 Docs: 调整跨插件访问文档 (#993)
Co-authored-by: Ju4tCode <42488585+yanyongyu@users.noreply.github.com>
2022-05-21 11:17:27 +08:00
github-actions[bot]
2fbd44eef9 📝 Update changelog 2022-05-21 03:15:19 +00:00
kexue-z
0adf4d6934 🍻 publish plugin 数据库连接插件 (#994) 2022-05-21 11:14:19 +08:00
github-actions[bot]
2c91a4c7f1 📝 Update changelog 2022-05-21 02:54:46 +00:00
NumberSir
625c72ab0d 🍻 publish plugin 百度翻译 (#991) 2022-05-21 10:53:47 +08:00
github-actions[bot]
449a2c5f96 📝 Update changelog 2022-05-21 01:15:31 +00:00
AkashiCoin
9e64f3f8ab 🍻 publish plugin MockingBird语音 2022-05-21 09:14:27 +08:00
github-actions[bot]
9b45b77894 🔖 Release 2.0.0-beta.3 2022-05-20 10:21:32 +00:00
Ju4tCode
e890453870 🔖 bump version 2.0.0-beta.3 (#990) 2022-05-20 18:13:50 +08:00
github-actions[bot]
abcea78fcc 📝 Update changelog 2022-05-20 09:35:28 +00:00
Ju4tCode
80594cffb6 🔊 add export deprecation warning (#983) 2022-05-20 17:34:15 +08:00
github-actions[bot]
6d4c5cbc2d 📝 Update changelog 2022-05-20 00:45:25 +00:00
Ju4tCode
d295e9ef6b 🍻 publish plugin imageutils (#985)
Co-authored-by: MeetWq <MeetWq@users.noreply.github.com>
2022-05-20 08:44:16 +08:00
Ju4tCode
2ad46bf97a 🍻 publish bot 屑岛风Bot (#987)
Co-authored-by: kexue-z <kexue-z@users.noreply.github.com>
2022-05-20 08:43:36 +08:00
Ju4tCode
70ddc634f6 Feat: 添加 devcontainer 支持 (#981) 2022-05-19 11:55:56 +08:00
github-actions[bot]
540629aa7c 📝 Update changelog 2022-05-18 23:38:35 +00:00
黯星座
4f4369c712 ✏️ Fix typo in scheduler document (#982) 2022-05-19 07:37:23 +08:00
github-actions[bot]
c6633bc9af 📝 Update changelog 2022-05-18 08:54:30 +00:00
Ju4tCode
1c099b4d13 🍻 publish plugin 摸鱼日历 (#980)
Co-authored-by: A-kirami <A-kirami@users.noreply.github.com>
2022-05-18 16:53:13 +08:00
github-actions[bot]
abe1e29fd9 📝 Update changelog 2022-05-15 05:51:01 +00:00
Ju4tCode
e8b9963ef3 🐛 Fix: 商店搜索失效 (#978) 2022-05-15 13:49:56 +08:00
github-actions[bot]
90f7c153cb 📝 Update changelog 2022-05-15 03:23:24 +00:00
Ju4tCode
983a5930c6 🍻 publish plugin 走迷宫 (#977)
Co-authored-by: EtherLeaF <EtherLeaF@users.noreply.github.com>
2022-05-15 11:22:16 +08:00
github-actions[bot]
ff65f10da9 📝 Update changelog 2022-05-15 02:36:29 +00:00
Ju4tCode
1710d009bb 🍻 publish plugin 语录娱乐 (#973)
Co-authored-by: bingqiu456 <bingqiu456@users.noreply.github.com>
2022-05-15 10:35:20 +08:00
github-actions[bot]
049d988574 📝 Update changelog 2022-05-14 15:16:43 +00:00
Ju4tCode
97fa0b4fe9 🍻 publish plugin 国内新冠疫情数据查询 (#975)
Co-authored-by: nicklly <nicklly@users.noreply.github.com>
2022-05-14 23:15:27 +08:00
github-actions[bot]
cd42385a43 📝 Update changelog 2022-05-14 13:08:07 +00:00
Ju4tCode
56f99b7f0b Feat: 支持 WebSocket 连接同时获取 str 或 bytes (#962) 2022-05-14 21:06:57 +08:00
github-actions[bot]
91c5056c97 📝 Update changelog 2022-05-14 08:55:45 +00:00
Ju4tCode
5e970a291f 🐛 fix di default param eq override (#971) 2022-05-14 16:54:41 +08:00
github-actions[bot]
42a49a20aa 📝 Update changelog 2022-05-13 15:05:36 +00:00
Ju4tCode
a4a329cf87 🍻 publish plugin nonebot_plugin_eventdone (#966)
Co-authored-by: PadorFelice <PadorFelice@users.noreply.github.com>
2022-05-13 23:04:19 +08:00
github-actions[bot]
dc074f35d5 📝 Update changelog 2022-05-13 07:36:59 +00:00
Ju4tCode
591107870e 🍻 publish plugin 幻影坦克图片合成 (#968)
Co-authored-by: RafuiiChan <RafuiiChan@users.noreply.github.com>
2022-05-13 15:35:53 +08:00
github-actions[bot]
94b19b4833 📝 Update changelog 2022-05-13 02:34:52 +00:00
Ju4tCode
1a91371410 🍻 publish plugin 合成字符画(GIF) (#964)
Co-authored-by: RafuiiChan <RafuiiChan@users.noreply.github.com>
2022-05-13 10:33:48 +08:00
github-actions[bot]
a77664297d 📝 Update changelog 2022-05-07 05:17:59 +00:00
StarHeart
b889d2352e 📝 Docs: 添加 QQ 频道链接 (#961)
* 📝 add qq channel badge

* 📝 update qq channel link

* Update README.md

Co-authored-by: Ju4tCode <42488585+yanyongyu@users.noreply.github.com>
2022-05-07 13:16:43 +08:00
github-actions[bot]
17e09267e0 📝 Update changelog 2022-05-05 02:20:00 +00:00
Ju4tCode
bbf734b2d1 🍻 publish bot ShigureBot (#959)
Co-authored-by: lgc2333 <lgc2333@users.noreply.github.com>
2022-05-05 10:18:41 +08:00
github-actions[bot]
7ab9e85dc0 📝 Update changelog 2022-05-02 11:51:05 +00:00
Ju4tCode
71bfb42fe0 🍻 publish plugin 国际象棋 (#957)
Co-authored-by: MeetWq <MeetWq@users.noreply.github.com>
2022-05-02 19:49:50 +08:00
github-actions[bot]
aeaea54ac1 📝 Update changelog 2022-05-02 05:12:14 +00:00
Ju4tCode
49437daf10 🍻 publish bot Inkar Suki (#955)
Co-authored-by: HornCopper <HornCopper@users.noreply.github.com>
2022-05-02 13:10:56 +08:00
github-actions[bot]
a93401e3b4 📝 Update changelog 2022-05-02 03:01:43 +00:00
MeetWq
e87861983b 🍻 rename nonebot-plugin-chess (#953)
Plugin: nonebot-plugin-chess 改名为 nonebot-plugin-boardgame
2022-05-02 11:00:27 +08:00
github-actions[bot]
2d81d54d93 📝 Update changelog 2022-05-01 02:30:47 +00:00
Ju4tCode
e145d99335 🍻 publish plugin NoneBot2 文档搜索 (#952)
Co-authored-by: MingxuanGame <MingxuanGame@users.noreply.github.com>
2022-05-01 10:29:40 +08:00
github-actions[bot]
7e3a58a0e8 📝 Update changelog 2022-04-30 08:05:57 +00:00
MingxuanGame
11b6e1ba98 📝 Docs: 添加 nonebug 单元测试文档 (#929)
Co-authored-by: Ju4tCode <42488585+yanyongyu@users.noreply.github.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: StarHeart <starheart233@gmail.com>
2022-04-30 16:04:41 +08:00
github-actions[bot]
34186830ab 📝 Update changelog 2022-04-30 06:45:08 +00:00
Ju4tCode
b98be416e4 🍻 publish plugin 中国象棋 (#949)
Co-authored-by: MeetWq <MeetWq@users.noreply.github.com>
2022-04-30 14:44:04 +08:00
github-actions[bot]
aaae928026 📝 Update changelog 2022-04-30 06:41:36 +00:00
@刘作鱼
1e43b4df10 📝 Add deployment document using PM2 (#853)
* Update deployment.md

重新提交 PM2 部署文档

* ♻️ 📝 Refactor PM2 deplotyment document

* ✏️ Fix typo in PM2 deployment document

* Update deployment.md

fix 表述歧义

* 🚚 copy docs to next version

Co-authored-by: Mix <32300164+mnixry@users.noreply.github.com>
Co-authored-by: yanyongyu <42488585+yanyongyu@users.noreply.github.com>
2022-04-30 14:40:25 +08:00
github-actions[bot]
505b4d46d0 📝 Update changelog 2022-04-30 02:00:37 +00:00
Mix
95331bbb22 🐛 Fix MessageTemplate improper behavior when no format spec (#947)
* 🧪 Add a test to figure out bug in #938

* ♻️ 🐛 Refactor rich message template formatting, fix #938
2022-04-30 09:59:23 +08:00
github-actions[bot]
f028575f2f 📝 Update changelog 2022-04-28 10:16:51 +00:00
Ju4tCode
252e3de459 🍻 publish plugin B站视频封面提取 (#946)
Co-authored-by: A-kirami <A-kirami@users.noreply.github.com>
2022-04-28 18:15:34 +08:00
github-actions[bot]
6449b1e9fd 📝 Update changelog 2022-04-28 09:59:12 +00:00
Ju4tCode
5334f11902 🍻 publish plugin 一言 (#944)
Co-authored-by: A-kirami <A-kirami@users.noreply.github.com>
2022-04-28 17:57:50 +08:00
github-actions[bot]
76ffcf14e8 📝 Update changelog 2022-04-28 09:49:41 +00:00
Ju4tCode
c8f25db6f6 🍻 publish plugin 答案之书 (#942)
Co-authored-by: A-kirami <A-kirami@users.noreply.github.com>
2022-04-28 17:48:22 +08:00
github-actions[bot]
4845ca10a4 📝 Update changelog 2022-04-28 09:43:10 +00:00
Ju4tCode
eec27a267a 🍻 publish plugin 支付宝到账语音 (#940)
Co-authored-by: A-kirami <A-kirami@users.noreply.github.com>
2022-04-28 17:41:13 +08:00
github-actions[bot]
3870f0084d 📝 Update changelog 2022-04-24 10:08:06 +00:00
kexue
06b36ec278 📝 Docs: 更新 GitHub Action 部署文档 (#937)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Ju4tCode <42488585+yanyongyu@users.noreply.github.com>
2022-04-24 18:06:57 +08:00
github-actions[bot]
dcfa25c486 📝 Update changelog 2022-04-20 09:03:15 +00:00
Ju4tCode
14953f5161 🍻 publish plugin nonebot-plugin-dida (#934)
Co-authored-by: TDK1969 <TDK1969@users.noreply.github.com>
2022-04-20 17:02:06 +08:00
github-actions[bot]
16f69b045b 📝 Update changelog 2022-04-20 06:44:40 +00:00
MeetWq
533e99418c Feat: 添加 CommandStart 依赖注入参数 (#915)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: yanyongyu <42488585+yanyongyu@users.noreply.github.com>
2022-04-20 14:43:29 +08:00
github-actions[bot]
f989710cd6 📝 Update changelog 2022-04-18 10:24:27 +00:00
Ju4tCode
e1534f2205 🍻 publish plugin 随机唐可可 (#931)
Co-authored-by: KafCoppelia <KafCoppelia@users.noreply.github.com>
2022-04-18 18:23:13 +08:00
github-actions[bot]
532aee5e71 📝 Update changelog 2022-04-16 15:47:46 +00:00
Nacho
fb047a4987 🍻 change ncm info (#924) 2022-04-16 23:46:37 +08:00
github-actions[bot]
af799aa846 📝 Update changelog 2022-04-16 02:21:11 +00:00
Ju4tCode
91f4daa722 📝 add custom rule guide (#914)
Co-authored-by: StarHeart <starheart233@gmail.com>
2022-04-16 10:20:01 +08:00
Ju4tCode
42fa47263a 🍻 publish plugin splatoon2新闻 (#917)
Co-authored-by: DrinkOolongTea <DrinkOolongTea@users.noreply.github.com>
2022-04-16 10:18:56 +08:00
dependabot[bot]
39fd544651 ⬆️ Bump codecov/codecov-action from 2 to 3 (#911)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-04-06 20:18:44 +08:00
github-actions[bot]
e5bb30e2b5 📝 Update changelog 2022-04-05 14:21:41 +00:00
Ju4tCode
a6d8f18cf0 🍻 publish plugin nonebot_plugin_draw (#910)
Co-authored-by: bingganhe123 <bingganhe123@users.noreply.github.com>
2022-04-05 22:20:27 +08:00
github-actions[bot]
47d843ddca 📝 Update changelog 2022-04-05 13:54:00 +00:00
Jigsaw
74542d30e0 📝 remove outdated plugins (#902) 2022-04-05 21:52:42 +08:00
pre-commit-ci[bot]
e12445be2f ⬆️ auto update by pre-commit hooks (#908)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2022-04-05 11:05:37 +08:00
github-actions[bot]
d8eb7d311b 📝 Update changelog 2022-04-05 02:53:11 +00:00
Ju4tCode
7ac14bab03 🍻 publish plugin 扫雷游戏 (#907)
Co-authored-by: MeetWq <MeetWq@users.noreply.github.com>
2022-04-05 10:52:04 +08:00
github-actions[bot]
e2621b4448 📝 Update changelog 2022-04-04 02:36:23 +00:00
Ju4tCode
2f3324ce0c 🐛 Fix: Bot Hook 没有捕获跳过异常 (#905) 2022-04-04 10:35:14 +08:00
github-actions[bot]
494b9c625d 📝 Update changelog 2022-04-01 08:30:51 +00:00
Akirami
f20cf785ce 🏷️ fix some matcher's redundant optional (#904)
Fix: 修复部分事件响应器参数类型中冗余的 Optional
2022-04-01 16:29:44 +08:00
github-actions[bot]
82803ff90f 📝 Update changelog 2022-03-29 02:33:41 +00:00
Ju4tCode
d38b5602a6 🍻 publish plugin 汉兜 Handle (#899)
Co-authored-by: MeetWq <MeetWq@users.noreply.github.com>
2022-03-29 10:32:52 +08:00
github-actions[bot]
adb5fd8ca0 📝 Update changelog 2022-03-28 07:36:06 +00:00
Ju4tCode
977e1de077 🍻 publish plugin 多适配器帮助函数 (#897)
Co-authored-by: iyume <iyume@users.noreply.github.com>
2022-03-28 15:35:01 +08:00
github-actions[bot]
537866db95 📝 Update changelog 2022-03-25 13:19:16 +00:00
Ju4tCode
9ffd78dda3 🍻 publish plugin 语句抽象化 (#894)
Co-authored-by: CherryCherries <CherryCherries@users.noreply.github.com>
2022-03-25 21:18:28 +08:00
github-actions[bot]
599da5158e 📝 Update changelog 2022-03-25 10:25:27 +00:00
hemengyang
2b64e8266c 👷 CI: 修复发布机器人的意外错误 (#892)
* 🐛 fix permission error

* 👷 remove unnecessary push trigger
2022-03-25 18:24:35 +08:00
github-actions[bot]
8ccf10954a 📝 Update changelog 2022-03-25 03:44:05 +00:00
Ju4tCode
09b9a626e6 🍻 publish plugin 快速搜索 (#889)
Co-authored-by: KoishiStudio <KoishiStudio@users.noreply.github.com>
2022-03-25 11:43:06 +08:00
github-actions[bot]
1d221fddab 📝 Update changelog 2022-03-25 03:11:00 +00:00
Ju4tCode
524ed419c2 🍻 publish plugin wordle猜单词 (#891)
Co-authored-by: MeetWq <MeetWq@users.noreply.github.com>
2022-03-25 11:10:10 +08:00
github-actions[bot]
536e75f994 📝 Update changelog 2022-03-25 03:03:58 +00:00
Ju4tCode
130c2ed5c0 🍻 publish plugin MediaWiki查询 (#886)
Co-authored-by: KoishiStudio <KoishiStudio@users.noreply.github.com>
2022-03-25 11:02:54 +08:00
github-actions[bot]
3c8e705bb0 📝 Update changelog 2022-03-24 02:47:44 +00:00
Ju4tCode
87e5e15b52 🍻 publish plugin HikariSearch (#884)
Co-authored-by: MeetWq <MeetWq@users.noreply.github.com>
2022-03-24 10:46:45 +08:00
github-actions[bot]
a2abc5a714 📝 Update changelog 2022-03-23 02:43:18 +00:00
Ju4tCode
de434b3072 🍻 publish plugin 第二个leetcode查询插件 (#882)
Co-authored-by: Nranphy <Nranphy@users.noreply.github.com>
2022-03-23 10:42:15 +08:00
github-actions[bot]
614f005373 📝 Update changelog 2022-03-21 15:05:52 +00:00
MeetWq
36efa3f441 📝 remove or replace some invalid plugins (#879)
* 替代SimpleMusic插件

* 移除失效插件
2022-03-21 23:04:48 +08:00
github-actions[bot]
78a90ef7aa 📝 Update changelog 2022-03-20 11:43:57 +00:00
Ju4tCode
2e5df56d38 🍻 publish plugin 成分姬 (#878)
Co-authored-by: MeetWq <MeetWq@users.noreply.github.com>
2022-03-20 19:42:55 +08:00
github-actions[bot]
a230e98052 📝 Update changelog 2022-03-20 11:41:34 +00:00
Ju4tCode
45e2e6c280 🐛 fix event maybe converted when checking type (#876)
Fix: 修复 event 类型检查会对类型进行自动转换
2022-03-20 19:40:43 +08:00
github-actions[bot]
fcdb05a7e2 📝 Update changelog 2022-03-18 14:49:11 +00:00
Ju4tCode
8c6d5a2d1f 🍻 publish plugin Arcaea查分插件 (#875)
Co-authored-by: SEAFHMC <SEAFHMC@users.noreply.github.com>
2022-03-18 22:48:09 +08:00
github-actions[bot]
8735b61a8d 📝 Update changelog 2022-03-17 13:12:30 +00:00
Ju4tCode
02de6fd266 add rule permission reflected operation support (#872)
Feature: 添加 Rule, Permission 反向位运算支持
2022-03-17 21:11:37 +08:00
github-actions[bot]
06f8dde33c 📝 Update changelog 2022-03-17 02:57:02 +00:00
Ju4tCode
0fe3e4fb16 🍻 publish plugin QQ自动同意好友申请 (#871)
Co-authored-by: ZakiuC <ZakiuC@users.noreply.github.com>
2022-03-17 10:56:17 +08:00
github-actions[bot]
56c6f6a471 📝 Update changelog 2022-03-11 15:41:47 +00:00
Ju4tCode
58e69f7884 🍻 publish plugin 21点游戏插件 (#865)
Co-authored-by: yaowan233 <yaowan233@users.noreply.github.com>
2022-03-11 23:40:54 +08:00
github-actions[bot]
4ebbf7638c 📝 Update changelog 2022-03-11 08:43:18 +00:00
Ju4tCode
8d7507c8f2 🍻 publish plugin 色图生成 (#863)
Co-authored-by: monsterxcn <monsterxcn@users.noreply.github.com>
2022-03-11 16:42:29 +08:00
github-actions[bot]
c1c720756b 📝 Update changelog 2022-03-11 07:48:09 +00:00
Ju4tCode
a1be18f7f4 📝 fix abs link in register adapter (#861)
Docs: 修复适配器文档内商店链接
2022-03-11 15:47:22 +08:00
github-actions[bot]
e2fcfa902e 📝 Update changelog 2022-03-11 04:13:18 +00:00
StarHeart
b54f4c8d4c 📝 Docs: tips for finding adapters' document link (#860)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: yanyongyu <42488585+yanyongyu@users.noreply.github.com>
2022-03-11 12:12:25 +08:00
github-actions[bot]
4cf07ca2e0 📝 Update changelog 2022-03-10 09:21:58 +00:00
Ju4tCode
5b3dd8f020 🍻 Plugin: bilibili通知插件 (#859)
Co-authored-by: TDK1969 <TDK1969@users.noreply.github.com>
2022-03-10 17:20:46 +08:00
Ju4tCode
9bb291e95b ⬆️ upgrade fastapi to 0.75 (#857) 2022-03-09 11:59:35 +08:00
github-actions[bot]
492c0947c3 📝 Update changelog 2022-03-05 15:36:03 +00:00
Ju4tCode
547d50ad76 🍻 Plugin: 订阅推送管理 (#855)
Co-authored-by: mwbimh <mwbimh@users.noreply.github.com>
2022-03-05 23:35:14 +08:00
github-actions[bot]
f7600a8a62 📝 Update changelog 2022-03-03 09:44:57 +00:00
Ju4tCode
9bd380a3bb 🍻 Plugin: 动漫新闻 (#852)
Co-authored-by: 5656565566 <5656565566@users.noreply.github.com>
2022-03-03 17:43:53 +08:00
github-actions[bot]
8600687f7d 📝 Update changelog 2022-03-02 09:48:28 +00:00
Ju4tCode
1d98ea1961 Plugin: 游戏王卡查 (#846)
Co-authored-by: anlen123 <anlen123@users.noreply.github.com>
2022-03-02 17:47:18 +08:00
dependabot[bot]
998db949da ⬆️ Bump actions/checkout from 2 to 3 (#844)
Bumps [actions/checkout](https://github.com/actions/checkout) from 2 to 3.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v2...v3)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-03-02 13:46:22 +08:00
github-actions[bot]
dea1b8c6fa 📝 Update changelog 2022-03-01 04:28:59 +00:00
Ju4tCode
d348f544b1 🔀 Merge pull request #843
Plugin: 二维码识别与发送
2022-03-01 12:28:08 +08:00
kexue-z
b1559eee42 🍻 publish plugin 二维码识别与发送 2022-03-01 04:02:57 +00:00
github-actions[bot]
edc7183c22 📝 Update changelog 2022-02-28 03:56:30 +00:00
Ju4tCode
ea539345cc 🔀 Merge pull request #841
Plugin: mockingbird
2022-02-28 11:55:41 +08:00
Diaosi1111
03d60dd0be 🍻 publish plugin mockingbird 2022-02-28 02:56:03 +00:00
Ju4tCode
48003a779f ⬆️ update fastapi and tomlkit (#837)
Co-authored-by: iyume <iyumelive@gmail.com>
2022-02-27 18:10:20 +08:00
github-actions[bot]
2203b82b09 📝 Update changelog 2022-02-27 07:36:59 +00:00
Ju4tCode
9fc2f7c02e 🔀 Merge pull request #839
Plugin: QQ自动续火花
2022-02-27 15:36:09 +08:00
25252www
6ee295bfd7 🍻 publish plugin QQ自动续火花 2022-02-27 07:09:39 +00:00
mobyw
6b067f0865 🔥 Plugin: 移除 nonebot-general-rss (#836) 2022-02-25 23:18:26 +08:00
Ju4tCode
339c25638b 👷 fix ci error when no changlog performed (#835) 2022-02-25 12:58:38 +08:00
Ju4tCode
e6cd3e57f5 🔀 Merge pull request #834
Plugin: update nonebot-bison
2022-02-25 12:10:19 +08:00
felinae98
69fcda5658 update nonebot-bison
支持beta
2022-02-24 22:55:30 +08:00
github-actions[bot]
88f8614cfc 📝 Update changelog 2022-02-23 07:38:38 +00:00
Ju4tCode
273b302ef2 🔀 Merge pull request #832
Plugin: 每日一句
2022-02-23 15:37:44 +08:00
MelodyYuuka
e86bab74d3 🍻 publish plugin 每日一句 2022-02-23 06:56:46 +00:00
github-actions[bot]
b1df360900 📝 Update changelog 2022-02-22 15:11:23 +00:00
CherryGS
2c271da965 📝 Add note for fastapi_reload option on Windows @CherryGS (#830)
* 添加 `fastapi_reload` 在 win 的额外影响

* 🚨 auto fix by pre-commit hooks

* Update choose-driver.md

* 🚨 auto fix by pre-commit hooks

* 调整格式

* 🚨 auto fix by pre-commit hooks

* Update website/versioned_docs/version-2.0.0-beta.2/tutorial/choose-driver.md

Co-authored-by: Mix <32300164+mnixry@users.noreply.github.com>

* Update website/versioned_docs/version-2.0.0-beta.2/tutorial/choose-driver.md

Co-authored-by: Mix <32300164+mnixry@users.noreply.github.com>

* Update website/versioned_docs/version-2.0.0-beta.2/tutorial/choose-driver.md

Co-authored-by: Mix <32300164+mnixry@users.noreply.github.com>

* Update website/versioned_docs/version-2.0.0-beta.2/tutorial/choose-driver.md

Co-authored-by: Mix <32300164+mnixry@users.noreply.github.com>

* Update website/versioned_docs/version-2.0.0-beta.2/tutorial/choose-driver.md

Co-authored-by: Mix <32300164+mnixry@users.noreply.github.com>

* Update choose-driver.md

* Update choose-driver.md

* 📝 update reload warning

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Mix <32300164+mnixry@users.noreply.github.com>
Co-authored-by: yanyongyu <42488585+yanyongyu@users.noreply.github.com>
2022-02-22 23:10:18 +08:00
github-actions[bot]
5db9c1e232 📝 Update changelog 2022-02-20 19:17:42 +00:00
Ju4tCode
b05ce41b1d 🔀 Merge pull request #829
Plugin: 原神抽卡记录分析
2022-02-21 03:16:57 +08:00
monsterxcn
bda27c78b9 🍻 publish plugin 原神抽卡记录分析 2022-02-20 19:09:12 +00:00
github-actions[bot]
6f0e27ee6d 📝 Update changelog 2022-02-20 15:18:56 +00:00
Ju4tCode
2c89409667 🔀 Merge pull request #825
Plugin: YetAnotherPicSearch
2022-02-20 23:18:01 +08:00
Ju4tCode
5adc5ce1cd 🔀 Merge pull request #826
Chore(deps): bump dependencies
2022-02-20 23:16:28 +08:00
StarHeartHunt
cad2f90b8a ⬆️ bump dependencies 2022-02-20 14:58:53 +08:00
NekoAria
b3d246cfb1 🍻 publish plugin YetAnotherPicSearch 2022-02-19 15:52:45 +00:00
github-actions[bot]
a8a6eb8c93 📝 Update changelog 2022-02-19 10:45:12 +00:00
Ju4tCode
86a73011b1 🔀 Merge pull request #822
CI: 添加更新日志忽略 label 选项
2022-02-19 18:44:23 +08:00
yanyongyu
9fb089bf08 👷 exclude label for changelog 2022-02-19 16:56:41 +08:00
github-actions[bot]
72d993921f 📝 Update changelog 2022-02-19 05:33:28 +00:00
Ju4tCode
db764f7e9e 🔀 Merge pull request #820
CI: 添加 dependabot 配置文件
2022-02-19 13:32:46 +08:00
Mix
1767f7a388 🔧 Add dependabot config file to update GitHub Actions 2022-02-19 11:51:07 +08:00
github-actions[bot]
fc45c67d97 📝 Update changelog 2022-02-19 03:22:02 +00:00
Ju4tCode
87885bd878 🔀 Merge pull request #819
Docs: 修复 ci/cd action 中错误的版本号
2022-02-19 11:21:20 +08:00
Bubbleioa
d75a04b31a fix bobheadxi/deployments version
在 [github action](https://github.com/Bubbleioa/ioa-bot/runs/5250225969) 上出错 
Error: Unable to resolve action `bobheadxi/deployments@v0.6`, unable to find version `v0.6`

最新版本为 v0.6.2
2022-02-19 00:40:01 +08:00
github-actions[bot]
12313204e1 📝 Update changelog 2022-02-18 08:00:02 +00:00
Ju4tCode
4573235583 🔀 Merge pull request #816
Docs: 添加 netlify 标签
2022-02-18 15:59:21 +08:00
yanyongyu
4293bdf21f 🎨 add dark theme support 2022-02-18 15:46:59 +08:00
yanyongyu
baae3e48de 🍻 add netlify badge 2022-02-18 14:53:24 +08:00
github-actions[bot]
24df95ae4a 📝 Update changelog 2022-02-18 06:08:33 +00:00
Ju4tCode
6b50a57348 🔀 Merge pull request #815
Fix: 修复 on_fullmatch 返回类型错误
2022-02-18 14:07:41 +08:00
yanyongyu
6920ec3a11 🏷️ fix fullmatch return type error 2022-02-18 11:12:19 +08:00
github-actions[bot]
6586f28f6a 📝 Update changelog 2022-02-18 03:05:38 +00:00
Ju4tCode
192c8da09c 🔀 Merge pull request #797
Feature: 新增文本完整匹配规则
2022-02-18 11:04:49 +08:00
Mix
1fba27d9b8 Fix failed full match test 2022-02-17 23:50:00 +08:00
Mix
0f0dc0a818 improve full match performance with frozenset 2022-02-17 23:49:47 +08:00
github-actions[bot]
3c3a250180 📝 Update changelog 2022-02-17 08:45:14 +00:00
Ju4tCode
03d33f3bdc 🔀 Merge pull request #814
CI: 分离 pr 预览 action
2022-02-17 16:44:31 +08:00
yanyongyu
1147d67f1a 👷 separate website ci for pr checking 2022-02-17 16:30:01 +08:00
github-actions[bot]
98e5956d44 📝 Update changelog 2022-02-17 08:00:39 +00:00
Ju4tCode
999a6a0e10 🔀 Merge pull request #813
Docs: 减小更新日志 toc 最大显示等级
2022-02-17 15:59:43 +08:00
github-actions[bot]
ff2675b527 📝 Update changelog 2022-02-17 07:56:39 +00:00
Ju4tCode
daa026cfd7 🔀 Merge pull request #812
Fix: 修复 DataclassEncoder 嵌套 encode 的问题
2022-02-17 15:55:56 +08:00
yanyongyu
d4d3962177 🎨 reduce changelog toc level 2022-02-17 15:44:49 +08:00
yanyongyu
1d6a333b49 update dataclass encoder tests 2022-02-17 15:35:39 +08:00
pre-commit-ci[bot]
9c0e05c615 🚨 auto fix by pre-commit hooks 2022-02-17 07:12:29 +00:00
github-actions[bot]
258bdbe403 📝 Update changelog 2022-02-17 07:11:16 +00:00
AkiraXie
c48ddaf0a2 🐛 fix DataclassEncoder bug and add test case 2022-02-17 15:06:26 +08:00
github-actions[bot]
9dd989c627 📝 Update changelog 2022-02-16 15:19:06 +00:00
Ju4tCode
3d84844a58 🔀 Merge pull request #810
Plugin: 60s读世界小插件
2022-02-16 23:18:20 +08:00
bingganhe123
d63d434e0f 🍻 publish plugin 60s读世界小插件 2022-02-16 15:03:00 +00:00
github-actions[bot]
5216a5b8f2 📝 Update changelog 2022-02-16 12:29:53 +00:00
hemengyang
4107affb9b Docs: 修改议题模板中的错误链接 (#807) 2022-02-16 20:29:04 +08:00
github-actions[bot]
b898303e4d 📝 Update changelog 2022-02-16 12:25:05 +00:00
Mix
43aebd9c93 Docs: 修改消息模板文档中错误的样例 (#806)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Ju4tCode <42488585+yanyongyu@users.noreply.github.com>
2022-02-16 20:24:00 +08:00
github-actions[bot]
fcda5c37d7 📝 Update changelog 2022-02-16 11:45:11 +00:00
Lan
1412385e51 Fix: 修改错误的插件 PyPI 项目名称 (#804)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Ju4tCode <42488585+yanyongyu@users.noreply.github.com>
2022-02-16 19:44:29 +08:00
github-actions[bot]
00fab9143e 📝 Update changelog 2022-02-16 11:36:28 +00:00
Ju4tCode
5e17d4c2f9 🔀 Merge pull request #798
Docs: 更新贡献指南
2022-02-16 19:35:42 +08:00
github-actions[bot]
d7de928f22 📝 Update changelog 2022-02-16 10:00:23 +00:00
Ju4tCode
83ff7a3a6c 🔀 Merge pull request #805
CI: 减少 action 冗余运行
2022-02-16 17:59:33 +08:00
yanyongyu
6f9c9eb740 🔧 change latest changes to chinese 2022-02-16 17:51:17 +08:00
yanyongyu
6272dfd46a 🔥 remove unused condition 2022-02-16 17:04:12 +08:00
yanyongyu
ab78769822 🐛 fix concurrency missing matrix 2022-02-16 14:41:39 +08:00
yanyongyu
004a308765 👷 disable auto push 2022-02-16 14:28:44 +08:00
yanyongyu
98e0ec27ee ✏️ fix changelog typo 2022-02-16 14:19:26 +08:00
yanyongyu
a491d842db 👷 reduce ci redundant run 2022-02-16 14:17:33 +08:00
github-actions[bot]
722fc6c6e1 📝 Update changelog 2022-02-16 04:12:35 +00:00
Ju4tCode
a5fffe2a4f 🔀 Merge pull request #803
Plugin: pixiv.net p站查询图片
2022-02-16 12:12:10 +08:00
github-actions[bot]
5f1f84327d 📝 Update changelog 2022-02-16 04:05:34 +00:00
Ju4tCode
3d8ac3e789 🔀 Merge pull request #802
CI: fix ci permission error
2022-02-16 12:05:13 +08:00
anlen123
c05eea2b67 🍻 publish plugin pixiv.net p站查询图片 2022-02-16 04:03:22 +00:00
yanyongyu
a6299bec8f 💚 fix ci permission error 2022-02-16 11:49:45 +08:00
Ju4tCode
513c14ee78 🔀 Merge pull request #799
CI: 添加更新日志自动更新 action
2022-02-16 11:30:56 +08:00
yanyongyu
987e44e1d0 👷 update ci config 2022-02-16 11:10:54 +08:00
jigsaw
e7937e5a06 📝 update contributing guide 2022-02-16 10:33:21 +08:00
yanyongyu
962c71ea4e 👷 update changelog ci 2022-02-15 23:39:04 +08:00
Ju4tCode
04e9a50bc1 📝 update contributing guide
Co-authored-by: Mix <32300164+mnixry@users.noreply.github.com>
2022-02-15 23:03:56 +08:00
yanyongyu
4bd1b92e9f 📝 update contributing guide 2022-02-15 21:10:07 +08:00
pre-commit-ci[bot]
f737bb899c 🚨 auto fix by pre-commit hooks 2022-02-15 00:27:43 +00:00
Akirami
9f12404338 add full match Matcher 2022-02-15 08:20:29 +08:00
github-actions
cee96d8ab6 🔖 Release 2.0.0-beta.2 2022-02-14 16:27:11 +00:00
Ju4tCode
14703fce8d 🔀 Merge pull request #796
⬆️ bump version 2.0.0-beta.2
2022-02-15 00:17:33 +08:00
hemengyang
6d0c782fcc ⬆️ bump version 2.0.0-beta.2 2022-02-15 00:11:19 +08:00
hemengyang
0cceeaec0b 👷 fix release-drafter 2022-02-15 00:05:20 +08:00
Ju4tCode
803223f31c 🔀 Merge pull request #790
Release: 2.0.0-beta.2
2022-02-14 23:55:05 +08:00
yanyongyu
6ceaf51af7 👷 prepare for changelog automation 2022-02-14 21:54:38 +08:00
yanyongyu
8f38fc5795 👷 update ci workflow 2022-02-14 21:01:39 +08:00
yanyongyu
257c49466f ⚰️ remove beta1 docs 2022-02-14 19:53:27 +08:00
Mix
85d86dfa96 📝 Add project code of conduct (#794)
* 📝 add project code of conduct

* 🚨 auto fix by pre-commit hooks

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2022-02-14 17:40:36 +08:00
AkiraXie
925886534c 📝 refactor dependency-injection documents (#791)
* 📝 update dependency-injection docs

* 🚨 auto fix by pre-commit hooks

* 📝 fix some indent

* 📝 fix description

* 📝 add create callable in DI docs

* 🚨 auto fix by pre-commit hooks

* 📝 delete unused params in docs

* 📝 update di docs

* 🚨 auto fix by pre-commit hooks

* 📝 update di doc

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: yanyongyu <42488585+yanyongyu@users.noreply.github.com>
2022-02-14 17:26:55 +08:00
yanyongyu
9a53b415d9 ✏️ fix plugin structure typo 2022-02-14 10:53:56 +08:00
yanyongyu
a884869ae2 ⬆️ update test dependencies 2022-02-13 21:56:12 +08:00
Mix
04fe654d74 📝 Add document for message template (#789)
* 📝 Add document for message template

* 📝 🎨 Optimize example reading experience for message template

* 🚨 auto fix by pre-commit hooks

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2022-02-12 16:15:06 +08:00
yanyongyu
ff887481ee 📝 add custom api doc 2022-02-12 14:07:42 +08:00
yanyongyu
0bfe1ce433 ⬆️ upgrade dependencies 2022-02-11 17:22:46 +08:00
Ju4tCode
80bf73e0e9 🔀 Merge pull request #788
Plugin: おはよう!
2022-02-11 17:16:11 +08:00
KafCoppelia
a8694aee23 🍻 publish plugin おはよう! 2022-02-11 06:24:02 +00:00
yanyongyu
943de58826 🏷️ fix typing error 2022-02-11 11:25:31 +08:00
yanyongyu
c0af89a6ad ⬆️ upgrade dependencies 2022-02-11 11:24:34 +08:00
Ju4tCode
b076497823 🔀 Merge pull request #786
Bot: 琪露诺Bot
2022-02-11 10:49:54 +08:00
summerkirakira
a416b4315a 🍻 publish bot 琪露诺Bot 2022-02-10 19:11:57 +00:00
Ju4tCode
4f91e63759 🔀 Merge pull request #782
Bugfix: Potential message body injection vulnerability in MessageTemplate
2022-02-10 15:44:41 +08:00
Mix
dc982fe5eb Fix update cause failed test 2022-02-10 15:12:27 +08:00
Ju4tCode
1b17039fe1 🔀 Merge pull request #784
Plugin: 轻量文字转图片插件
2022-02-10 15:01:33 +08:00
mobyw
b4fc9f9539 🍻 publish plugin 轻量文字转图片插件 2022-02-10 06:11:55 +00:00
Mix
b7762b9176 🔒 🐛 Add initial value to vformat results list, fix #781 2022-02-10 13:17:11 +08:00
Mix
455c599b06 🧪 Add a fail test to reproduce #781 2022-02-10 13:15:59 +08:00
yanyongyu
e9908bcbc4 🔧 change docusaurus prism config 2022-02-08 14:59:54 +08:00
yanyongyu
fba9471fe6 📝 add deploy guide 2022-02-08 14:46:43 +08:00
Ju4tCode
58bceff175 🔀 Merge pull request #769
Docs: Fix copywriting
2022-02-08 10:50:59 +08:00
yanyongyu
c45843e32d ⬆️ upgrade dependencies 2022-02-07 19:56:39 +08:00
yanyongyu
aefe16f2c7 📝 update onebot homepage 2022-02-07 17:46:02 +08:00
yanyongyu
460efe436d 📝 add message document 2022-02-07 17:40:26 +08:00
Satoshi Jek
aacfc9b90c Plugin: 修改 nonebot-plugin-strman 错误的 module_name (#780) 2022-02-07 14:59:05 +08:00
yanyongyu
7d190213b7 📝 update readme badge 2022-02-07 14:12:08 +08:00
yanyongyu
ae37f31e38 🔀 Merge branch master 2022-02-07 11:38:52 +08:00
yanyongyu
6f90dcb12f 🚚 move call api doc 2022-02-07 11:30:24 +08:00
Ju4tCode
5ce72655e2 🔀 Merge pull request #772
Fix Message.template format spec does not support static method
2022-02-07 10:59:43 +08:00
Ju4tCode
9ed5d915d5 🔀 Merge pull request #779
Plugin: Fgo从者推理
2022-02-07 10:54:45 +08:00
suhexia
c4d82aa21b 🍻 publish plugin Fgo从者推理 2022-02-06 16:48:48 +00:00
Ju4tCode
319b3ff20e 🔀 Merge pull request #777
Plugin: 无数据库的问答插件
2022-02-06 22:53:51 +08:00
kexue-z
5e19ad672e 🍻 publish plugin 无数据库的问答插件 2022-02-06 14:26:00 +00:00
Ju4tCode
2d276a6b7a 🔀 Merge pull request #775
Plugin: random_cat_gif
2022-02-06 22:25:34 +08:00
applenana
e98e784fe7 🍻 publish plugin random_cat_gif 2022-02-06 13:59:58 +00:00
Satoshi Jek
f4120d91e0 🔀 Plugin: 风格化字符串管理 商店信息更新 (#773)
* chore(plugins): 插件仓库重定向

* chore(plugins): 补充修改作者信息
2022-02-06 21:37:27 +08:00
Jigsaw
2f51afc007 🔀 Merge branch 'dev' into docs/fix-copywriting 2022-02-06 19:50:50 +08:00
jigsaw
b9fd4b1ac8 📝 Fix copywriting 2022-02-06 19:46:14 +08:00
Mix
f4395d77d7 Add test to prove the fix is valid 2022-02-06 18:55:19 +08:00
Mix
28cfa45d95 🐛 Fix Message.template format spec does not support static method
Fixes #770
2022-02-06 18:40:30 +08:00
Ju4tCode
b90054e61b 🔀 Merge pull request #771
Feature: refactor and support bot connection hook
2022-02-06 18:16:18 +08:00
yanyongyu
1ee7f73d59 🔧 remove duplicated isort black config 2022-02-06 17:24:06 +08:00
yanyongyu
118519e15d ♻️ reorganize internal tree 2022-02-06 17:08:11 +08:00
yanyongyu
65dc9a908b 📝 update doc link 2022-02-06 15:37:36 +08:00
yanyongyu
924d9b6536 ✏️ fix changelog and typing 2022-02-06 15:24:41 +08:00
yanyongyu
fd11e2696b ♻️ reorganize class and add bot hook di 2022-02-06 14:52:50 +08:00
yanyongyu
b8456b12ad ✏️ fix matcher operation typo 2022-02-05 16:51:57 +08:00
yanyongyu
dcaf5cedcf 💡 update matcher docstrings 2022-02-05 16:49:21 +08:00
Ju4tCode
2cd2ea6ca4 🔀 Merge pull request #765
Docs: update some advanced docs
2022-02-05 12:38:48 +08:00
Ju4tCode
6e8978710a 🔀 Merge pull request #767
Plugin: 群聊反闪照
2022-02-05 12:35:55 +08:00
KafCoppelia
7e5c179641 🍻 publish plugin 群聊反闪照 2022-02-05 03:33:04 +00:00
AkiraXie
86aa3ef2e8 📝 update export-and-require and permission docs 2022-02-04 21:52:00 +08:00
AkiraXie
994b99e462 📝 fix indent 2022-02-04 15:45:21 +08:00
pre-commit-ci[bot]
b0b2b1f681 🚨 auto fix by pre-commit hooks 2022-02-04 07:15:37 +00:00
AkiraXie
4e1e0e98b4 Merge branch 'nonebot:dev' into dev 2022-02-04 15:14:06 +08:00
AkiraXie
a68a6e2659 ✏️ fix typo in fastapi 2022-02-04 15:13:31 +08:00
AkiraXie
e8bb66ca48 📝 update some advanced docs 2022-02-04 15:12:02 +08:00
yanyongyu
e720584044 change hook parse typing 2022-02-04 11:12:17 +08:00
yanyongyu
ec9e8511b7 reduce the hook typing limit 2022-02-04 11:06:38 +08:00
Ju4tCode
3a314fc7ae 🔀 Merge pull request #763
update plugin info
2022-02-03 21:27:11 +08:00
kexue
fe5e5428dd 🍻 update plugin info 2022-02-03 18:05:43 +08:00
Ju4tCode
e37e6d0714 🔀 Merge pull request #762
Plugin: random-cat
2022-02-02 17:54:26 +08:00
alphaAE
7cdcfd02ab 🍻 publish plugin random-cat 2022-02-02 08:47:52 +00:00
Ju4tCode
578f0fb619 🔀 Merge pull request #760
Plugin: nonebot-plugin-setu2
2022-02-02 16:46:54 +08:00
alphaAE
3408f27a77 🍻 publish plugin nonebot-plugin-setu2 2022-02-02 08:35:14 +00:00
Ju4tCode
0fd7c77640 🔀 Merge pull request #758
Plugin: 聊天记录插件
2022-01-31 23:14:18 +08:00
MeetWq
613fc09382 🍻 publish plugin 聊天记录插件 2022-01-31 14:59:33 +00:00
yanyongyu
4d5cc03454 📝 add handler running doc 2022-01-31 14:29:26 +08:00
yanyongyu
09b438485a ✏️ fix typo 2022-01-31 12:44:33 +08:00
yanyongyu
c7883863c7 🐛 fix asciinema ssr failed 2022-01-30 18:26:26 +08:00
yanyongyu
60ccdf8a7a 🐛 fix typing error for logger wrapper 2022-01-30 15:03:10 +08:00
yanyongyu
ea188cb948 📝 add custom log guide 2022-01-30 14:11:27 +08:00
yanyongyu
244c7266b3 📝 update changelog 2022-01-30 13:21:51 +08:00
yanyongyu
1eaacde745 ⬆️ upgrade dependencies 2022-01-30 13:18:50 +08:00
yanyongyu
c33e7eca46 ⬆️ upgrade dependencies 2022-01-30 13:13:55 +08:00
Ju4tCode
bdf2f4771b 🔀 Merge pull request #752
Fix: rewrite message typing and construct
2022-01-30 13:06:54 +08:00
yanyongyu
2cd6867bd1 add more tests 2022-01-30 11:04:02 +08:00
yanyongyu
f3cc93c699 add more tests 2022-01-30 00:05:01 +08:00
Ju4tCode
244b3c02a8 🔀 Merge pull request #756
Plugin: FG(Fifth Generation)
2022-01-29 23:56:57 +08:00
yanyongyu
2ec5917709 🐛 fix missing self instance validate 2022-01-29 23:55:14 +08:00
mgsky1
a0d9c4e1a0 🍻 publish plugin FG(Fifth Generation) 2022-01-29 15:43:24 +00:00
yanyongyu
5abf55d095 add message tests 2022-01-29 23:39:13 +08:00
Ju4tCode
34e2535390 🔀 Merge pull request #754
Plugin: 词云
2022-01-29 22:20:45 +08:00
yanyongyu
5fa7806a2f improve pydantic validate for message 2022-01-29 18:20:30 +08:00
he0119
1b674496ed 🍻 publish plugin 词云 2022-01-29 09:56:21 +00:00
yanyongyu
b43dfb983d ⚰️ remove unused import 2022-01-29 15:36:25 +08:00
yanyongyu
e887c39998 🏷️ update message typing 2022-01-29 13:56:54 +08:00
jigsaw
4c2269f789 ✏️ Fix typo 2022-01-28 20:46:27 +08:00
Ju4tCode
ac0dd63ca4 🔀 Merge pull request #749
CI: avoid duplicate CI runs
2022-01-28 16:48:06 +08:00
Ju4tCode
a8a68d89a2 🔀 Merge pull request #751
Plugin: 棋类游戏
2022-01-28 16:42:38 +08:00
MeetWq
942bb8761e 🍻 publish plugin 棋类游戏 2022-01-28 07:20:26 +00:00
yanyongyu
ad712c59b3 reuse type check code for dependent 2022-01-28 14:49:04 +08:00
yanyongyu
1271a757c9 👽 update type check due to py3.10 UnionType 2022-01-28 14:27:54 +08:00
Ju4tCode
ea8a700f86 🔀 Merge pull request #745
Docs: Add CONTRIBUTING
2022-01-28 14:05:36 +08:00
pre-commit-ci[bot]
fd8fd233b6 🚨 auto fix by pre-commit hooks 2022-01-28 06:04:57 +00:00
StarHeartHunt
d06e06eba2 👷 avoid duplicate CI runs 2022-01-28 14:03:19 +08:00
jigsaw
392def7936 📝 Update CONTRIBUTING
Co-authored-by: Ju4tCode <42488585+yanyongyu@users.noreply.github.com>
2022-01-28 13:21:45 +08:00
Jigsaw
c4d08d13cb ✏️ Fix typo
Co-authored-by: Ju4tCode <42488585+yanyongyu@users.noreply.github.com>
2022-01-28 13:21:45 +08:00
jigsaw
ffab125f02 📝 Update CONTRIBUTING
Co-authored-by: Mix <32300164+mnixry@users.noreply.github.com>
Co-authored-by: StarHeartHunt <starheart233@gmail.com>
2022-01-28 13:17:41 +08:00
yanyongyu
0b150d35f7 ✏️ fix isort line length typo 2022-01-28 12:32:19 +08:00
yanyongyu
15d68706b3 📝 add asciinema svg to install doc 2022-01-27 21:24:56 +08:00
jigsaw
2924c74333 📝 Add CONTRIBUTING 2022-01-27 18:58:10 +08:00
yanyongyu
afea004421 📝 update readme 2022-01-27 16:05:30 +08:00
Ju4tCode
1b08e0d822 🔀 Merge pull request #748
Fix: dict(config) raise error
2022-01-27 11:31:05 +08:00
yanyongyu
1da7da9fc1 🐛 fix dict(config) raise error 2022-01-27 11:15:44 +08:00
Mix
1b6b9d61fe 🔀 Merge pull request #747
Plugin: go-cqhttp 频道支持适配补丁
2022-01-26 22:53:51 +08:00
mnixry
b222863f63 🍻 publish plugin go-cqhttp 频道支持适配补丁 2022-01-26 14:52:54 +00:00
Ju4tCode
e1dffa99a0 🔀 Merge pull request #743
Fix: require load plugin error
2022-01-26 21:20:43 +08:00
yanyongyu
0885474b94 improve plugin finder 2022-01-26 20:55:23 +08:00
yanyongyu
482fba234b 📝 add nb-cli doc 2022-01-26 16:21:18 +08:00
yanyongyu
770e808f1d add require tests 2022-01-26 15:37:35 +08:00
Ju4tCode
fdf05173be 🔀 Merge pull request #741
Update plugins.json
2022-01-26 15:10:52 +08:00
yanyongyu
5c73c80c65 🐛 fix require error 2022-01-26 15:06:53 +08:00
KafCoppelia
bda2991c30 Update plugins.json
删去了自己几个已经适配b1的alpha标签;修改了自己几个插件的描述,统一风格;修改了几个原a:cqhttp标签为a:onebot
2022-01-26 14:18:59 +08:00
KafCoppelia
efbfea3d94 Update plugins.json
由于均适配beta.1,将fortune与what2eat插件“alpha”tag删除;修改几个插件“desc”字段描述,统一描述风格;
2022-01-26 14:09:09 +08:00
yanyongyu
956ee7e321 📝 add bot api doc 2022-01-26 12:30:05 +08:00
Ju4tCode
ba8dfb6d94 🔀 Merge pull request #739
Plugin: nonebot-general-rss
2022-01-26 11:02:59 +08:00
Mix
1a202e918d 🔥 remove plugins with wrong dependencies 2022-01-25 21:43:10 +08:00
mobyw
2e78de3d5d 🍻 publish plugin nonebot-general-rss 2022-01-25 11:27:15 +00:00
yanyongyu
f53e374521 ✏️ fix doc api link error 2022-01-25 16:03:49 +08:00
Ju4tCode
b773083b4e 🔀 Merge pull request #737
Fix wrong module name
2022-01-25 10:43:38 +08:00
Lan
ebd45dc5c1 🏷️ Update types 2022-01-25 10:19:37 +08:00
Lan
2f61c6a6f9 ✏️ Fix wrong module name 2022-01-25 10:09:20 +08:00
Mix
22597959cf 🔀 Merge pull request #736
Plugin: NoneBot的go-cqhttp启动器
2022-01-24 21:41:51 +08:00
mnixry
6b930cd7be 🍻 publish plugin NoneBot的go-cqhttp启动器 2022-01-24 13:41:00 +00:00
yanyongyu
c8369f599f 📝 add configuration and matcher operation 2022-01-24 20:32:46 +08:00
Ju4tCode
c09ecb4a88 🔀 Merge pull request #734
Plugin: NoneBot 数据存储
2022-01-24 16:39:11 +08:00
Ju4tCode
74c3961998 🔀 Merge pull request #732
修改自己几个插件的Tag
2022-01-24 16:38:23 +08:00
Ju4tCode
e7b2dde44a ✏️ fix trailing comma 2022-01-24 16:25:40 +08:00
风屿
77faa027bd 删掉了v:voice的tag 2022-01-24 15:37:24 +08:00
he0119
40f7c4013b 🍻 publish plugin NoneBot 数据存储 2022-01-24 07:24:19 +00:00
风屿
ba6a440bbd 修改自己几个插件的Tag 2022-01-24 14:23:38 +08:00
Ju4tCode
d0d6e0fc25 🔀 Merge pull request #730
Update plugins.json
2022-01-24 10:15:37 +08:00
KafCoppelia
26934124a1 Update plugins.json
修改nonebot_plugin_fortune "module_name" 与 nonebot_plugin_tarot 标签"alpha only" to "beta first",tarot插件已适配beta.1并已发行pypi,beta.1适配优先
2022-01-23 23:10:50 +08:00
Ju4tCode
05f4a8b363 🔀 Merge pull request #729
Plugin: 今天吃什么
2022-01-23 21:17:45 +08:00
KafCoppelia
cb92bb7007 🍻 publish plugin 今天吃什么 2022-01-23 13:13:15 +00:00
Ju4tCode
bc2abc9c51 🔀 Merge pull request #726
Plugin: emoji 合成器
2022-01-23 21:12:48 +08:00
Ju4tCode
ebd7e4df19 🔀 Merge pull request #727
Fix wrong module name
2022-01-23 21:12:01 +08:00
yanyongyu
48b507e272 📝 add config docs 2022-01-23 21:09:47 +08:00
Lancercmd
6a17ce5fda ✏️ Fix wrong module name 2022-01-23 20:59:02 +08:00
MeetWq
44ca11cceb 🍻 publish plugin emoji 合成器 2022-01-23 12:15:55 +00:00
Ju4tCode
12d28eb77d 🔀 Merge pull request #724
Plugin: Fix homepage for 2 plugins
2022-01-23 17:23:51 +08:00
Jigsaw
0513b5884d 🍺 Fix homepage of nonebot-plugin-manager & nonebot-plugin-puppet 2022-01-23 17:09:26 +08:00
yanyongyu
97563a0090 🐛 fix missing api docs include 2022-01-23 13:35:09 +08:00
Ju4tCode
596f763c46 🔀 Merge pull request #691
:construction_workerusing: nb-autodoc to generate api docs
2022-01-23 12:07:51 +08:00
yanyongyu
5f3902fe61 ✏️ fix hook typo 2022-01-23 11:48:35 +08:00
Mix
ceb7589b27 🔀 Merge pull request #723
Plugin: 塔罗牌
2022-01-23 11:09:39 +08:00
KafCoppelia
fa8a3987e7 🍻 publish plugin 塔罗牌 2022-01-23 03:01:21 +00:00
Ju4tCode
8f51aff483 🔀 Merge pull request #720
Plugin: nonebot_plugin_fortune
2022-01-22 23:47:12 +08:00
KafCoppelia
c1084cc437 🍻 publish plugin nonebot_plugin_fortune 2022-01-22 15:35:38 +00:00
yanyongyu
46757a315b ⬆️ upgrade dependencies 2022-01-22 17:09:45 +08:00
yanyongyu
4316990aab ⬆️ upgrade dependencies 2022-01-22 15:50:11 +08:00
yanyongyu
7d81e079de 🎨 improve tree style 2022-01-22 15:35:52 +08:00
yanyongyu
12d9a68351 🐛 fix typing error for mutex 2022-01-22 15:26:22 +08:00
yanyongyu
c4e204001e 💡 add docstrings 2022-01-22 15:23:07 +08:00
yanyongyu
f9674da6ea 💡 add di docstrings 2022-01-21 21:04:17 +08:00
Ju4tCode
794a640681 🔀 Merge pull request #718
Adapter: mirai2
2022-01-21 11:07:12 +08:00
ieew
1bdb27d109 🍻 publish adapter mirai2 2022-01-21 02:36:08 +00:00
yanyongyu
aef585c60c 💡 add adapter docstring 2022-01-20 14:49:46 +08:00
Ju4tCode
215cfc90f4 🔀 Merge pull request #716
👷 checkout with PAT
2022-01-20 13:42:12 +08:00
hemengyang
b671a9658b 👷checkout with PAT
CI can be triggered when github action pushes a commit.
2022-01-20 13:35:58 +08:00
Ju4tCode
017049f321 🔀 Merge pull request #715
Plugin: 天气查询
2022-01-20 12:16:12 +08:00
zjkwdy
10733e7520 🍻 publish plugin 天气查询 2022-01-20 04:05:30 +00:00
Ju4tCode
b343fb8e6f 🔀 Merge pull request #711
Fix: single_session potential bug
2022-01-20 10:59:10 +08:00
AkiraXie
f6308916ad 🐛 fix bug and improve test coverage 2022-01-20 03:16:04 +08:00
pre-commit-ci[bot]
b3d0e7c548 🚨 auto fix by pre-commit hooks 2022-01-19 18:10:30 +00:00
AkiraXie
67f5b87492 🐛 fix single_session bug 2022-01-20 02:05:57 +08:00
Ju4tCode
30a14eb99c 🔀 Merge pull request #710
Update plugins.json
2022-01-19 19:19:49 +08:00
meetwq
92714de522 Update plugins.json 2022-01-19 18:53:55 +08:00
yanyongyu
98ef09585a 💡 add docstrings 2022-01-19 16:16:56 +08:00
Ju4tCode
13e021fa1e 🔀 Merge pull request #708
商店链接指向错误
2022-01-19 15:12:45 +08:00
wzl19371
82224da9ab Fix store link 2022-01-19 14:59:22 +08:00
Ju4tCode
2877c39288 🔀 Merge pull request #707
Plugin: 移除冷却事件插件
2022-01-19 01:06:43 +08:00
Satoshi Jek
af0b0ae823 Plugin: 移除冷却事件插件
由于 OneBot v11 适配器自 v2.0.0b1 起提供了命令冷却支持,且该插件已无力继续维护,故将该插件从商店移除。
2022-01-19 00:33:10 +08:00
yanyongyu
4701537a48 💡 add docstrings 2022-01-18 23:46:10 +08:00
yanyongyu
608cf859c8 💡 add log, exception, consts docstring 2022-01-18 16:12:12 +08:00
yanyongyu
27b1ded9a1 💡 add config docstring 2022-01-18 12:31:08 +08:00
Ju4tCode
60800b1d9e 🔀 Merge pull request #706
更新插件tag
2022-01-17 22:32:03 +08:00
felinae98
c675addeef 更新插件tag 2022-01-17 21:58:29 +08:00
yanyongyu
a07919ad5c 📝 rewrite docstring 2022-01-17 15:06:53 +08:00
Ju4tCode
ce0e230887 🔀 Merge pull request #705
Feature: Advanced message slice support
2022-01-17 10:59:26 +08:00
Mix
b037be4485 add unit test for message slice 2022-01-17 00:29:09 +08:00
Mix
3b4c4d3081 Implement .count and optimize .get performance for message slice 2022-01-17 00:28:36 +08:00
Mix
1221baaa94 Implement .get and .index methods for Message 2022-01-16 17:13:26 +08:00
pre-commit-ci[bot]
39822378a7 🚨 auto fix by pre-commit hooks 2022-01-16 05:26:55 +00:00
Mix
3041650b4b add advanced message slice support 2022-01-16 13:17:05 +08:00
yanyongyu
3a9a5a9ce9 📝 add frontmatter config 2022-01-16 11:30:09 +08:00
yanyongyu
34c086a046 ⚗️ reduce prettier file 2022-01-16 10:52:12 +08:00
Ju4tCode
cf819af179 🔀 Merge pull request #704
Plugin: 彩云小梦AI续写
2022-01-16 10:40:03 +08:00
yanyongyu
9b6d8b6efa 📝 test autogen docs 2022-01-15 23:53:36 +08:00
MeetWq
478dff512f 🍻 publish plugin 彩云小梦AI续写 2022-01-15 14:19:20 +00:00
yanyongyu
cc1dc10b5d add nb-autodoc 2022-01-15 22:16:31 +08:00
yanyongyu
b7a0ba11d5 🔀 Merge branch dev 2022-01-15 22:12:50 +08:00
yanyongyu
93aec6d3f6 🏷️ add pre-commit flow 2022-01-15 21:27:43 +08:00
Ju4tCode
9c729d194b 🔀 Merge pull request #702
Docs: remove unsupported char
2022-01-15 19:46:00 +08:00
StarHeart
dcfba4e94a 🐛 remove unsupported char 2022-01-15 17:41:22 +08:00
Mix
55b1f962ac 🔀 Merge pull request #701
Plugin: 人生重开模拟器
2022-01-15 16:58:15 +08:00
MeetWq
6c0ab74956 🍻 publish plugin 人生重开模拟器 2022-01-15 08:54:07 +00:00
Mix
9cbac0b4ca 🔀 Merge pull request #699
Plugin: 新冠疫情查询😷
2022-01-15 10:16:49 +08:00
hemengyang
8d0951a816 📝 fix wrong syntax and link 2022-01-15 09:35:30 +08:00
Zeta-qixi
8a896b42a3 🍻 publish plugin 新冠疫情查询😷 2022-01-14 19:32:35 +00:00
hemengyang
d26fda7320 🐛 fix docs build fail 2022-01-14 22:05:56 +08:00
Ju4tCode
9af7079fdd 🔀 Merge pull request #697
Plugin: 疯狂星期四
2022-01-14 21:53:15 +08:00
KafCoppelia
1d49defdfa 🍻 publish plugin 疯狂星期四 2022-01-14 13:35:22 +00:00
Ju4tCode
0e31a5fbf4 🔀 Merge pull request #695
Plugin: 扔骰子
2022-01-14 21:19:01 +08:00
KafCoppelia
ad5a011b45 🍻 publish plugin 扔骰子 2022-01-14 13:11:42 +00:00
hemengyang
341ee94848 🔥remove sphinx 2022-01-14 20:04:19 +08:00
hemengyang
a47b8c68c0 👷using nb-autodoc to generate api docs 2022-01-14 19:58:22 +08:00
yanyongyu
cc343c981f 💩 add logger format todo 2022-01-14 12:32:03 +08:00
Ju4tCode
10395d6bce 🔀 Merge pull request #686
Cross platform code coverage test
2022-01-13 18:17:15 +08:00
yanyongyu
6713f4d9b0 add test flags for os, python version 2022-01-13 18:11:00 +08:00
yanyongyu
8494768a89 🔥 remove pypy test 2022-01-13 18:06:07 +08:00
yanyongyu
88bd49de45 👷 support cross platform test 2022-01-13 17:58:35 +08:00
yanyongyu
b52ccc3690 👷 reuse workflow setup step 2022-01-13 16:41:01 +08:00
Ju4tCode
a8a1b4d65d 🔀 Merge pull request #683
📝 prepare docstring for autodoc
2022-01-13 11:14:03 +08:00
hemengyang
98deadb4d6 粗略过一遍,修复一眼可见的问题 2022-01-12 19:41:42 +08:00
hemengyang
ec06010298 令标题符合 markdown 语法 2022-01-12 19:24:01 +08:00
hemengyang
cd69b22d43 令 tip 符合格式 2022-01-12 19:20:59 +08:00
hemengyang
f298930f9d 更新依赖参数的注释 2022-01-12 19:15:56 +08:00
hemengyang
0e97022d3b 调整缩进 2022-01-12 19:10:29 +08:00
hemengyang
6d21cbed55 令用法符合格式 2022-01-12 18:53:30 +08:00
hemengyang
edb20a4786 令异常符合格式 2022-01-12 18:45:35 +08:00
hemengyang
187532930b 令返回符合格式 2022-01-12 18:43:07 +08:00
hemengyang
456d333568 令参数符合标准 2022-01-12 18:31:12 +08:00
hemengyang
a5f32febbd 令参数列表符合 autodoc 格式 2022-01-12 18:25:25 +08:00
hemengyang
4ba1a09fb7 使用 markdown 格式的强调语法 2022-01-12 18:19:21 +08:00
hemengyang
0099364838 删除说明前缀 2022-01-12 18:16:05 +08:00
hemengyang
0e11959347 删除默认值 2022-01-12 18:02:08 +08:00
hemengyang
705a6f7fbf 删除类型 2022-01-12 18:00:56 +08:00
Ju4tCode
eeb54c8f8a 🔀 Merge pull request #682
Docs: format adapter name
2022-01-12 17:25:15 +08:00
StarHeart
c9056c6fd0 📝 format adapter name 2022-01-12 16:46:56 +08:00
yanyongyu
e29db806cc 📝 update changelog 2022-01-12 11:54:01 +08:00
Ju4tCode
f837e5f3fa Merge pull request #673 from A-kirami/dev
从 Fastapi 的文档中排除驱动器的 HTTP 上报地址
2022-01-11 15:12:37 +08:00
yanyongyu
d82f0c6310 ⚗️ add config option for fastapi 2022-01-11 15:03:17 +08:00
Ju4tCode
18082d0dc7 🔀 Merge pull request #681
Remove mirai-api-http related info from readme
2022-01-11 14:51:23 +08:00
yanyongyu
6ca54574ce ✏️ fix context shell command typo 2022-01-11 12:53:19 +08:00
Mix
85fc2422d1 🔥 remove mirai-api-http adapter from store 2022-01-11 00:04:36 +08:00
Mix
bb7313f12c 📝 remove mirai-api-http related info from readme, fix #470
Mirai-API-HTTP will no longer be maintained by NoneBot officialy.
2022-01-10 23:41:57 +08:00
yanyongyu
742d1f1f02 ⚗️ fix load builtin plugin 2022-01-10 22:52:10 +08:00
yanyongyu
1b035ed19b 🔥 add State() deprecation warning 2022-01-10 22:24:45 +08:00
yanyongyu
ea65ee5b4f 👷 update eci python version 2022-01-10 12:07:11 +08:00
yanyongyu
6572622e8a improve test performance 2022-01-10 11:53:02 +08:00
yanyongyu
00de3bcc54 📝 update changelog 2022-01-10 11:22:35 +08:00
yanyongyu
2ccdc218e0 improve state detect #677 2022-01-10 11:21:01 +08:00
Ju4tCode
c11c62f622 🔀 Merge pull request #678
Docs: fix di import path
2022-01-10 10:44:25 +08:00
HibiKier
59c77d312a 📝 fix di import path 2022-01-09 23:49:24 +08:00
Ju4tCode
a14cfc8d77 🔀 Merge pull request #679
Fix: plugin load fail process
2022-01-09 23:29:53 +08:00
yanyongyu
d6f5216d29 🐛 fix plugin load fail 2022-01-09 23:15:33 +08:00
Ju4tCode
d549087db2 🔀 Merge pull request #676
Fix: fix matcher receive and arg detect
2022-01-09 20:45:19 +08:00
yanyongyu
3fa8bd2cd0 🐛 fix receive and got missing detect error 2022-01-09 20:34:03 +08:00
yanyongyu
8674d18543 🐛 fix pagination display error 2022-01-09 20:27:43 +08:00
明见
096f0d72f1 从 Fastapi 的文档中排除驱动器的 HTTP 上报地址 2022-01-08 22:19:45 +08:00
Ju4tCode
f0e935d4ff 🔀 Merge pull request #672
Plugin: Add tags for j1g5awi's plugins
2022-01-08 21:51:43 +08:00
Jigsaw
490a8146bc ✏️ Fix typo 2022-01-08 21:17:31 +08:00
Jigsaw
a74c6e220b ✏️ Fix typo 2022-01-08 21:05:39 +08:00
Jigsaw
0e8ef3def2 🍻 Add tags for j1g5awi's plugins 2022-01-08 20:57:36 +08:00
Ju4tCode
ba4e90b14f 🔀 Merge pull request #671
Adapter: 开黑啦
2022-01-08 20:18:35 +08:00
Tian-que
d02d8f06b2 🍻 publish adapter 开黑啦 2022-01-08 12:11:08 +00:00
586 changed files with 75591 additions and 20203 deletions

View File

@@ -0,0 +1,56 @@
{
"name": "Default Linux Universal",
"image": "mcr.microsoft.com/devcontainers/universal:2-linux",
"features": {
"ghcr.io/devcontainers-contrib/features/poetry:2": {}
},
"postCreateCommand": "poetry config virtualenvs.in-project true && poetry install -E all && poetry run pre-commit install && yarn install",
"customizations": {
"vscode": {
"settings": {
"python.analysis.diagnosticMode": "workspace",
"python.analysis.typeCheckingMode": "basic",
"ruff.organizeImports": false,
"[python]": {
"editor.defaultFormatter": "ms-python.black-formatter",
"editor.codeActionsOnSave": {
"source.fixAll.ruff": true,
"source.organizeImports": true
}
},
"[javascript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[html]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[typescript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[javascriptreact]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[typescriptreact]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"files.exclude": {
"**/__pycache__": true
},
"files.watcherExclude": {
"**/target/**": true,
"**/__pycache__": true
}
},
"extensions": [
"ms-python.python",
"ms-python.vscode-pylance",
"ms-python.isort",
"ms-python.black-formatter",
"charliermarsh.ruff",
"EditorConfig.EditorConfig",
"esbenp.prettier-vscode",
"bradlc.vscode-tailwindcss"
]
}
}
}

View File

@@ -0,0 +1,57 @@
name: 发布适配器
title: "Adapter: {name}"
description: 发布适配器到 NoneBot 官方商店
labels: ["Adapter"]
body:
- type: input
id: name
attributes:
label: 适配器名称
description: 适配器名称
validations:
required: true
- type: input
id: description
attributes:
label: 适配器描述
description: 适配器描述
validations:
required: true
- type: input
id: pypi
attributes:
label: PyPI 项目名
description: PyPI 项目名
placeholder: e.g. nonebot-adapter-xxx
validations:
required: true
- type: input
id: module
attributes:
label: 适配器 import 包名
description: 适配器 import 包名
placeholder: e.g. nonebot_adapter_xxx
validations:
required: true
- type: input
id: homepage
attributes:
label: 适配器项目仓库/主页链接
description: 适配器项目仓库/主页链接
placeholder: e.g. https://github.com/xxx/xxx
validations:
required: true
- type: input
id: tags
attributes:
label: 标签
description: 标签
placeholder: 'e.g. [{"label": "标签名", "color": "#ea5252"}]'
value: "[]"
validations:
required: true

37
.github/ISSUE_TEMPLATE/bot_publish.yml vendored Normal file
View File

@@ -0,0 +1,37 @@
name: 发布机器人
title: "Bot: {name}"
description: 发布机器人到 NoneBot 官方商店
labels: ["Bot"]
body:
- type: input
id: name
attributes:
label: 机器人名称
description: 机器人名称
validations:
required: true
- type: input
id: description
attributes:
label: 机器人描述
description: 机器人描述
validations:
required: true
- type: input
id: homepage
attributes:
label: 机器人项目仓库/主页链接
description: 机器人项目仓库/主页链接
placeholder: e.g. https://github.com/xxx/xxx
- type: input
id: tags
attributes:
label: 标签
description: 标签
placeholder: 'e.g. [{"label": "标签名", "color": "#ea5252"}]'
value: "[]"
validations:
required: true

View File

@@ -1,38 +0,0 @@
---
name: Bug report
about: Create a bug report to help us improve
title: 'Bug: Something went wrong'
labels: bug
assignees: ''
---
**描述问题:**
A clear and concise description of what the bug is.
**如何复现?**
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**期望的结果**
A clear and concise description of what you expected to happen.
**环境信息:**
- OS: [e.g. Linux]
- Python Version: [e.g. 3.8]
- Nonebot Version: [e.g. 2.0.0]
**协议端信息:**
- 协议端: [e.g. go-cqhttp]
- 协议端版本: [e.g. 1.0.0]
**截图或日志**
If applicable, add screenshots to help explain your problem.

85
.github/ISSUE_TEMPLATE/bug_report.yml vendored Normal file
View File

@@ -0,0 +1,85 @@
name: Bug 反馈
title: "Bug: 出现异常"
description: 提交 Bug 反馈以帮助我们改进代码
labels: ["bug"]
body:
- type: dropdown
id: env-os
attributes:
label: 操作系统
description: 选择运行 NoneBot 的系统
options:
- Windows
- MacOS
- Linux
- Other
validations:
required: true
- type: input
id: env-python-ver
attributes:
label: Python 版本
description: 填写运行 NoneBot 的 Python 版本
placeholder: e.g. 3.11.0
validations:
required: true
- type: input
id: env-nb-ver
attributes:
label: NoneBot 版本
description: 填写 NoneBot 版本
placeholder: e.g. 2.0.0
validations:
required: true
- type: input
id: env-adapter
attributes:
label: 适配器
description: 填写使用的适配器以及版本
placeholder: e.g. OneBot v11 2.2.2
validations:
required: true
- type: input
id: env-protocol
attributes:
label: 协议端
description: 填写连接 NoneBot 的协议端及版本
placeholder: e.g. go-cqhttp 1.0.0
validations:
required: true
- type: textarea
id: describe
attributes:
label: 描述问题
description: 清晰简洁地说明问题是什么
validations:
required: true
- type: textarea
id: reproduction
attributes:
label: 复现步骤
description: 提供能复现此问题的详细操作步骤
placeholder: |
1. 首先……
2. 然后……
3. 发生……
validations:
required: true
- type: textarea
id: expected
attributes:
label: 期望的结果
description: 清晰简洁地描述你期望发生的事情
- type: textarea
id: logs
attributes:
label: 截图或日志
description: 提供有助于诊断问题的任何日志和截图

View File

@@ -1,14 +1,5 @@
blank_issues_enabled: false blank_issues_enabled: false
contact_links: contact_links:
- name: Question - name: NoneBot 论坛
url: https://discussions.nonebot.dev/ url: https://discussions.nonebot.dev/
about: Ask questions about nonebot about: 前往 NoneBot 论坛提问
- name: Plugin Publish
url: https://v2.nonebot.dev/store.html
about: Publish your plugin to nonebot homepage and nb-cli
- name: Adapter Publish
url: https://v2.nonebot.dev/store.html
about: Publish your adapter to nonebot homepage and nb-cli
- name: Bot Publish
url: https://v2.nonebot.dev/store.html
about: Publish your bot to nonebot homepage and nb-cli

View File

@@ -1,17 +0,0 @@
---
name: Document improvement
about: Feedback on documentation, including errors and ideas
title: 'Docs: some description'
labels: documentation
assignees: ''
---
**描述问题或主题:**
**需做出的修改:**
* [ ] 一些修改
* [ ] 一些修改
* [ ] 一些修改

18
.github/ISSUE_TEMPLATE/document.yml vendored Normal file
View File

@@ -0,0 +1,18 @@
name: 文档改进
title: "Docs: 描述"
description: 文档错误及改进意见反馈
labels: ["documentation"]
body:
- type: textarea
id: problem
attributes:
label: 描述问题或主题
validations:
required: true
- type: textarea
id: improve
attributes:
label: 需做出的修改
validations:
required: true

View File

@@ -1,16 +0,0 @@
---
name: Feature request
about: Suggest an idea for this project
title: 'Feature: Something you want'
labels: enhancement
assignees: ''
---
**是否在使用中遇到某些问题而需要新的特性?请描述:**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**描述你所需要的特性:**
A clear and concise description of what you want to happen.

View File

@@ -0,0 +1,20 @@
name: 功能建议
title: "Feature: 功能描述"
description: 提出关于项目新功能的想法
labels: ["enhancement"]
body:
- type: textarea
id: problem
attributes:
label: 希望能解决的问题
description: 在使用中遇到什么问题而需要新的功能?
validations:
required: true
- type: textarea
id: feature
attributes:
label: 描述所需要的功能
description: 请说明需要的功能或解决方法
validations:
required: true

View File

@@ -0,0 +1,43 @@
name: 发布插件
title: "Plugin: {name}"
description: 发布插件到 NoneBot 官方商店
labels: ["Plugin"]
body:
- type: input
id: pypi
attributes:
label: PyPI 项目名
description: PyPI 项目名
placeholder: e.g. nonebot-plugin-xxx
validations:
required: true
- type: input
id: module
attributes:
label: 插件 import 包名
description: 插件 import 包名
placeholder: e.g. nonebot_plugin_xxx
validations:
required: true
- type: input
id: tags
attributes:
label: 标签
description: 标签
placeholder: 'e.g. [{"label": "标签名", "color": "#ea5252"}]'
value: "[]"
validations:
required: true
- type: textarea
id: config
attributes:
label: 插件配置项
description: 插件配置项
render: dotenv
placeholder: |
# e.g.
# KEY=VALUE
# KEY2=VALUE2

View File

@@ -0,0 +1,14 @@
name: Build API Doc
description: Build API Doc
runs:
using: "composite"
steps:
- run: |
poetry run nb-autodoc nonebot \
-s nonebot.plugins \
-u nonebot.internal \
-u nonebot.internal.*
cp -r ./build/nonebot/* ./website/docs/api/
yarn prettier
shell: bash

21
.github/actions/setup-node/action.yml vendored Normal file
View File

@@ -0,0 +1,21 @@
name: Setup Node
description: Setup Node
runs:
using: "composite"
steps:
- uses: actions/setup-node@v2
with:
node-version: "16"
- id: yarn-cache-dir-path
run: echo "dir=$(yarn cache dir)" >> $GITHUB_OUTPUT
shell: bash
- uses: actions/cache@v3
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
- run: yarn install
shell: bash

24
.github/actions/setup-python/action.yml vendored Normal file
View File

@@ -0,0 +1,24 @@
name: Setup Python
description: Setup Python
inputs:
python-version:
description: Python version
required: false
default: "3.10"
runs:
using: "composite"
steps:
- name: Install poetry
run: pipx install poetry
shell: bash
- uses: actions/setup-python@v4
with:
python-version: ${{ inputs.python-version }}
architecture: "x64"
cache: "poetry"
- run: poetry install -E all
shell: bash

6
.github/dependabot.yml vendored Normal file
View File

@@ -0,0 +1,6 @@
version: 2
updates:
- package-ecosystem: github-actions
directory: "/"
schedule:
interval: daily

View File

@@ -1,42 +1,43 @@
name-template: 'Release v$RESOLVED_VERSION 🌈' template: $CHANGES
tag-template: 'v$RESOLVED_VERSION' category-template: "### $TITLE"
name-template: "Release v$RESOLVED_VERSION 🌈"
tag-template: "v$RESOLVED_VERSION"
change-template: "- $TITLE [@$AUTHOR](https://github.com/$AUTHOR) ([#$NUMBER]($URL))"
change-title-escapes: '\<&'
exclude-labels:
- "dependencies"
- "skip-changelog"
categories: categories:
- title: '💥 Breaking Changes' - title: "💥 破坏性变更"
labels: labels:
- 'Breaking' - "Breaking"
- title: '🚀 Features' - title: "🚀 新功能"
labels: labels:
- 'feature' - "feature"
- 'enhancement' - "enhancement"
- title: '🐛 Bug Fixes' - title: "🐛 Bug 修复"
labels: labels:
- 'fix' - "fix"
- 'bugfix' - "bugfix"
- 'bug' - "bug"
- title: '🍻 Plugin Publish' - title: "📝 文档"
label: 'Plugin' labels:
- title: '🍻 Bot Publish' - "documentation"
label: 'Bot' - title: "💫 杂项"
- title: '🍻 Adapter Publish' - title: "🍻 插件发布"
label: 'Adapter' label: "Plugin"
change-template: '- $TITLE @$AUTHOR (#$NUMBER)' - title: "🍻 机器人发布"
change-title-escapes: '\<*_&' # You can add # and @ to disable mentions, and add ` to disable code blocks. label: "Bot"
- title: "🍻 适配器发布"
label: "Adapter"
version-resolver: version-resolver:
major: major:
labels: labels:
- 'major' - "major"
minor: minor:
labels: labels:
- 'minor' - "minor"
patch: patch:
labels: labels:
- 'patch' - "patch"
default: patch default: patch
template: |
## Documentation
See: https://v2.nonebot.dev
## 💫 Changes
$CHANGES

View File

@@ -2,38 +2,46 @@ name: Code Coverage
on: on:
push: push:
branches:
- master
pull_request: pull_request:
paths:
- "nonebot/**"
- "packages/**"
- "tests/**"
jobs: jobs:
test: test:
runs-on: ubuntu-latest name: Test Coverage
name: Run Pytest and Upload Coverage runs-on: ${{ matrix.os }}
concurrency:
group: test-coverage-${{ github.ref }}-${{ matrix.os }}-${{ matrix.python-version }}
cancel-in-progress: true
strategy:
matrix:
python-version: ["3.8", "3.9", "3.10", "3.11"]
os: [ubuntu-latest, windows-latest, macos-latest]
fail-fast: false
env:
OS: ${{ matrix.os }}
PYTHON_VERSION: ${{ matrix.python-version }}
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
- name: Set up Python environment - name: Setup Python environment
uses: actions/setup-python@v2 uses: ./.github/actions/setup-python
with: with:
python-version: "3.9" python-version: ${{ matrix.python-version }}
architecture: "x64"
- uses: Gr1N/setup-poetry@v7
- uses: actions/cache@v2
with:
path: ~/.cache/pypoetry/virtualenvs
key: ${{ runner.os }}-poetry-${{ hashFiles('**/poetry.lock') }}
- name: Install dependencies
run: poetry install -E all
- name: Run Pytest - name: Run Pytest
run: | run: |
cd tests/ cd tests/
poetry run pytest --cov-report xml poetry run pytest -n auto --cov-report xml
- name: Upload coverage report - name: Upload coverage report
uses: codecov/codecov-action@v2 uses: codecov/codecov-action@v3
with: with:
env_vars: OS,PYTHON_VERSION
files: ./tests/coverage.xml files: ./tests/coverage.xml
flags: unittests flags: unittests

100
.github/workflows/noneflow.yml vendored Normal file
View File

@@ -0,0 +1,100 @@
name: NoneFlow
on:
issues:
types: [opened, reopened, edited]
pull_request_target:
types: [closed]
issue_comment:
types: [created]
pull_request_review:
types: [submitted]
concurrency:
group: ${{ github.workflow }}-${{ github.event.issue.number || github.run_id }}
cancel-in-progress: false
jobs:
plugin_test:
runs-on: ubuntu-latest
name: nonebot2 plugin test
if: |
!(
(
github.event.pull_request &&
(
github.event.pull_request.head.repo.fork ||
!(
contains(github.event.pull_request.labels.*.name, 'Plugin') ||
contains(github.event.pull_request.labels.*.name, 'Adapter') ||
contains(github.event.pull_request.labels.*.name, 'Bot')
)
)
) ||
(
github.event_name == 'issue_comment' && github.event.issue.pull_request
)
)
permissions:
issues: read
outputs:
result: ${{ steps.plugin-test.outputs.RESULT }}
output: ${{ steps.plugin-test.outputs.OUTPUT }}
metadata: ${{ steps.plugin-test.outputs.METADATA }}
steps:
- name: Install Poetry
if: ${{ !startsWith(github.event_name, 'pull_request') }}
run: pipx install poetry
- name: Setup Python
uses: actions/setup-python@v4
with:
python-version: "3.10"
- name: Test Plugin
id: plugin-test
run: |
curl -sSL https://github.com/nonebot/noneflow/releases/latest/download/plugin_test.py | python -
noneflow:
runs-on: ubuntu-latest
name: noneflow
needs: plugin_test
steps:
- name: Generate token
id: generate-token
uses: tibdex/github-app-token@v1
with:
app_id: ${{ secrets.APP_ID }}
private_key: ${{ secrets.APP_KEY }}
- name: Checkout Code
uses: actions/checkout@v3
with:
token: ${{ steps.generate-token.outputs.token }}
- name: Cache pre-commit hooks
uses: actions/cache@v3
with:
path: .cache/.pre-commit
key: noneflow-${{ runner.os }}-${{ hashFiles('.pre-commit-config.yaml') }}
- name: NoneFlow
uses: docker://ghcr.io/nonebot/noneflow:latest
with:
config: >
{
"base": "master",
"plugin_path": "website/static/plugins.json",
"bot_path": "website/static/bots.json",
"adapter_path": "website/static/adapters.json"
}
env:
PLUGIN_TEST_RESULT: ${{ needs.plugin_test.outputs.result }}
PLUGIN_TEST_OUTPUT: ${{ needs.plugin_test.outputs.output }}
PLUGIN_TEST_METADATA: ${{ needs.plugin_test.outputs.metadata }}
APP_ID: ${{ secrets.APP_ID }}
PRIVATE_KEY: ${{ secrets.APP_KEY }}
PRE_COMMIT_HOME: /github/workspace/.cache/.pre-commit
- name: Fix permission
run: sudo chown -R $(whoami):$(id -ng) .cache/.pre-commit

View File

@@ -1,30 +0,0 @@
name: NoneBot2 Publish Bot
on:
push:
branches:
- master
issues:
types: [opened, reopened, edited]
pull_request:
types: [closed]
jobs:
publish_bot:
runs-on: ubuntu-latest
name: nonebot2 publish bot
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: NoneBot2 Publish Bot
uses: nonebot/nonebot2-publish-bot@main
with:
token: ${{ secrets.GH_TOKEN }}
config: >
{
"base": "master",
"plugin_path": "website/static/plugins.json",
"bot_path": "website/static/bots.json",
"adapter_path": "website/static/adapters.json"
}

26
.github/workflows/pyright.yml vendored Normal file
View File

@@ -0,0 +1,26 @@
name: Pyright Lint
on:
push:
branches:
- master
pull_request:
paths:
- "nonebot/**"
- "packages/**"
- "tests/**"
jobs:
pyright:
name: Pyright Lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Python environment
uses: ./.github/actions/setup-python
- run: echo "$(poetry env info --path)/bin" >> $GITHUB_PATH
- name: Run Pyright
uses: jakebailey/pyright-action@v1

103
.github/workflows/release-drafter.yml vendored Normal file
View File

@@ -0,0 +1,103 @@
name: Release Drafter
on:
push:
tags:
- v*
pull_request_target:
branches:
- master
types:
- closed
jobs:
update-release-draft:
if: github.event_name == 'pull_request_target'
runs-on: ubuntu-latest
concurrency:
group: pull-request-changelog
cancel-in-progress: true
steps:
- name: Generate token
id: generate-token
uses: tibdex/github-app-token@v1
with:
app_id: ${{ secrets.APP_ID }}
private_key: ${{ secrets.APP_KEY }}
- uses: actions/checkout@v3
with:
token: ${{ steps.generate-token.outputs.token }}
- name: Setup Node Environment
uses: ./.github/actions/setup-node
- uses: release-drafter/release-drafter@v5
id: release-drafter
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Update Changelog
uses: docker://ghcr.io/nonebot/auto-changelog:master
with:
changelog_file: website/src/pages/changelog.md
latest_changes_position: '# 更新日志\n\n'
latest_changes_title: "## 最近更新"
replace_regex: '(?<=## 最近更新\n)[\s\S]*?(?=\n## )'
changelog_body: ${{ steps.release-drafter.outputs.body }}
commit_and_push: false
- name: Commit and Push
run: |
yarn prettier
git config user.name noneflow[bot]
git config user.email 129742071+noneflow[bot]@users.noreply.github.com
git add .
git diff-index --quiet HEAD || git commit -m ":memo: Update changelog"
git push
release:
if: startsWith(github.ref, 'refs/tags/')
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Python Environment
uses: ./.github/actions/setup-python
- name: Setup Node Environment
uses: ./.github/actions/setup-node
- name: Build API Doc
uses: ./.github/actions/build-api-doc
- run: |
echo "TAG_NAME=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV
- uses: release-drafter/release-drafter@v5
with:
name: Release ${{ env.TAG_NAME }} 🌈
tag: ${{ env.TAG_NAME }}
publish: true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Build and Publish Package
run: |
poetry build
poetry publish -u ${{secrets.PYPI_USERNAME}} -p ${{secrets.PYPI_PASSWORD}}
gh release upload --clobber ${{ env.TAG_NAME }} dist/*.tar.gz dist/*.whl
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Build and Publish Doc Package
run: |
yarn build:plugin --out-dir ../packages/nonebot-plugin-docs/nonebot_plugin_docs/dist
export NONEBOT_VERSION=`poetry version -s`
cd packages/nonebot-plugin-docs/
poetry version $NONEBOT_VERSION
poetry build
poetry publish -u ${{secrets.PYPI_USERNAME}} -p ${{secrets.PYPI_PASSWORD}}
gh release upload --clobber ${{ env.TAG_NAME }} dist/*.tar.gz dist/*.whl
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -1,36 +0,0 @@
name: Release Drafter
on:
push:
branches:
- master
tags:
- v*
jobs:
update-release-draft:
if: github.ref == 'refs/heads/master'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: release-drafter/release-drafter@v5
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
release:
if: startsWith(github.ref, 'refs/tags/')
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- run: |
echo "TAG_NAME=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV
- uses: release-drafter/release-drafter@v5
with:
name: Release ${{ env.TAG_NAME }} 🌈
tag: ${{ env.TAG_NAME }}
publish: true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -1,58 +0,0 @@
name: Release Nonebot Plugin Docs
on:
push:
tags:
- v*
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: "3.9"
architecture: "x64"
- uses: Gr1N/setup-poetry@v7
- uses: actions/cache@v2
with:
path: ~/.cache/pypoetry/virtualenvs
key: ${{ runner.os }}-poetry-${{ hashFiles('**/poetry.lock') }}
- name: Setup Node
uses: actions/setup-node@v2
with:
node-version: "16"
- name: Get yarn cache directory path
id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn cache dir)"
- name: Cache Packages
uses: actions/cache@v2
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
- name: Install and build
run: |
poetry install -E all
poetry run sphinx-build -M markdown ./docs_build ./build
cp -r ./build/markdown/* ./website/docs/api/
yarn install
yarn prettier
yarn build:plugin --out-dir ../packages/nonebot-plugin-docs/nonebot_plugin_docs/dist
- name: Publish Package
run: |
export NONEBOT_VERSION=`poetry version -s`
cd packages/nonebot-plugin-docs/
poetry version $NONEBOT_VERSION
poetry build
poetry publish -u ${{secrets.PYPI_USERNAME}} -p ${{secrets.PYPI_PASSWORD}}

View File

@@ -6,64 +6,47 @@ on:
jobs: jobs:
build: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v2 - name: Generate token
id: generate-token
uses: tibdex/github-app-token@v1
with: with:
ref: master app_id: ${{ secrets.APP_ID }}
token: ${{ secrets.GH_TOKEN }} private_key: ${{ secrets.APP_KEY }}
- name: Set up Python - uses: actions/checkout@v3
uses: actions/setup-python@v2
with: with:
python-version: "3.9" token: ${{ steps.generate-token.outputs.token }}
architecture: "x64"
- uses: Gr1N/setup-poetry@v7 - name: Setup Python Environment
uses: ./.github/actions/setup-python
- uses: actions/cache@v2 - name: Setup Node Environment
uses: ./.github/actions/setup-node
- name: Build API Doc
uses: ./.github/actions/build-api-doc
- run: echo "TAG_NAME=v$(poetry version -s)" >> $GITHUB_ENV
- name: Archive Changelog
uses: docker://ghcr.io/nonebot/auto-changelog:master
with: with:
path: ~/.cache/pypoetry/virtualenvs changelog_file: website/src/pages/changelog.md
key: ${{ runner.os }}-poetry-${{ hashFiles('**/poetry.lock') }} archive_regex: '(?<=## )最近更新(?=\n)'
archive_title: ${{ env.TAG_NAME }}
- name: Setup Node commit_and_push: false
uses: actions/setup-node@v2
with:
node-version: "16"
- name: Get yarn cache directory path
id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn cache dir)"
- name: Cache Packages
uses: actions/cache@v2
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
- name: Set up dependencies
run: |
poetry install -E all
- name: Build Doc
run: poetry run sphinx-build -M markdown ./docs_build ./build
- name: Copy Files
run: cp -r ./build/markdown/* ./website/docs/api/
- name: Archive Files - name: Archive Files
run: | run: |
yarn install
yarn prettier
yarn archive $(poetry version -s) yarn archive $(poetry version -s)
yarn prettier
- name: Push Tag and Release to PyPI - name: Push Tag
run: | run: |
git config user.name github-actions git config user.name noneflow[bot]
git config user.email github-actions@github.com git config user.email 129742071+noneflow[bot]@users.noreply.github.com
git add . git add .
git commit -m ":bookmark: Release $(poetry version -s)" git commit -m ":bookmark: Release $(poetry version -s)"
git tag v$(poetry version -s) git tag ${{ env.TAG_NAME }}
git push && git push --tags git push && git push --tags
poetry build
poetry publish -u ${{secrets.PYPI_USERNAME}} -p ${{secrets.PYPI_PASSWORD}}

21
.github/workflows/ruff.yml vendored Normal file
View File

@@ -0,0 +1,21 @@
name: Ruff Lint
on:
push:
branches:
- master
pull_request:
paths:
- "nonebot/**"
- "packages/**"
- "tests/**"
jobs:
ruff:
name: Ruff Lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Run Ruff Lint
uses: chartboost/ruff-action@v1

View File

@@ -1,83 +1,44 @@
name: Build Upload Site name: Site Deploy
on: on:
push: push:
pull_request_target: branches:
- master
jobs: jobs:
publish: publish:
runs-on: ubuntu-latest runs-on: ubuntu-latest
concurrency:
group: website-deploy-${{ github.ref }}
cancel-in-progress: true
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
if: github.event_name == 'push'
- uses: actions/checkout@v2 - name: Setup Python Environment
if: github.event_name != 'push' uses: ./.github/actions/setup-python
with:
ref: ${{ github.event.pull_request.head.sha }}
- name: Set up Python - name: Setup Node Environment
uses: actions/setup-python@v2 uses: ./.github/actions/setup-node
with:
python-version: 3.8
architecture: "x64"
- uses: Gr1N/setup-poetry@v7 - name: Build API Doc
uses: ./.github/actions/build-api-doc
- uses: actions/cache@v2 - name: Build Doc
with: run: yarn build
path: ~/.cache/pypoetry/virtualenvs
key: ${{ runner.os }}-poetry-${{ hashFiles('**/poetry.lock') }}
- name: Setup Node
uses: actions/setup-node@v2
with:
node-version: "16"
- name: Get yarn cache directory path
id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn cache dir)"
- name: Cache Packages
uses: actions/cache@v2
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
- name: Install and build
run: |
poetry install -E all
poetry run sphinx-build -M markdown ./docs_build ./build
cp -r ./build/markdown/* ./website/docs/api/
yarn install
yarn prettier
yarn build
- name: Get Branch Name - name: Get Branch Name
run: echo "BRANCH_NAME=$(echo ${GITHUB_REF#refs/heads/})" >> $GITHUB_ENV run: echo "BRANCH_NAME=$(echo ${GITHUB_REF#refs/heads/})" >> $GITHUB_ENV
- name: Get Deploy Name
if: github.event_name == 'push'
run: |
echo "DEPLOY_NAME=${{ env.BRANCH_NAME }}" >> $GITHUB_ENV
echo "PRODUCTION=${{ env.BRANCH_NAME == 'master' }}" >> $GITHUB_ENV
- name: Get Deploy Name
if: github.event_name != 'push'
run: |
echo "DEPLOY_NAME=deploy-preview-${{ github.event.number }}" >> $GITHUB_ENV
echo "PRODUCTION=false" >> $GITHUB_ENV
- name: Deploy to Netlify - name: Deploy to Netlify
uses: nwtgck/actions-netlify@v1 uses: nwtgck/actions-netlify@v2
with: with:
publish-dir: './website/build' publish-dir: "./website/build"
production-deploy: ${{ env.PRODUCTION }} production-deploy: true
github-token: ${{ secrets.GITHUB_TOKEN }} github-token: ${{ secrets.GITHUB_TOKEN }}
deploy-message: 'Deploy ${{ env.DEPLOY_NAME }}@${{ github.sha }}' deploy-message: "Deploy ${{ env.BRANCH_NAME }}@${{ github.sha }}"
enable-commit-comment: false enable-commit-comment: false
alias: ${{ env.DEPLOY_NAME }} alias: ${{ env.BRANCH_NAME }}
env: env:
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }} NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
NETLIFY_SITE_ID: ${{ secrets.SITE_ID }} NETLIFY_SITE_ID: ${{ secrets.SITE_ID }}

45
.github/workflows/website-preview.yml vendored Normal file
View File

@@ -0,0 +1,45 @@
name: Site Deploy(Preview)
on:
pull_request_target:
jobs:
preview:
runs-on: ubuntu-latest
concurrency:
group: pull-request-preview-${{ github.event.number }}
cancel-in-progress: true
steps:
- uses: actions/checkout@v3
with:
ref: ${{ github.event.pull_request.head.sha }}
- name: Setup Python Environment
uses: ./.github/actions/setup-python
- name: Setup Node Environment
uses: ./.github/actions/setup-node
- name: Build API Doc
uses: ./.github/actions/build-api-doc
- name: Build Doc
run: yarn build
- name: Get Deploy Name
run: |
echo "DEPLOY_NAME=deploy-preview-${{ github.event.number }}" >> $GITHUB_ENV
- name: Deploy to Netlify
uses: nwtgck/actions-netlify@v2
with:
publish-dir: "./website/build"
production-deploy: false
github-token: ${{ secrets.GITHUB_TOKEN }}
deploy-message: "Deploy ${{ env.DEPLOY_NAME }}@${{ github.sha }}"
enable-commit-comment: false
alias: ${{ env.DEPLOY_NAME }}
env:
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
NETLIFY_SITE_ID: ${{ secrets.SITE_ID }}

4
.markdownlint.yaml Normal file
View File

@@ -0,0 +1,4 @@
MD013: false
MD024: # 重复标题
siblings_only: true
MD033: false # 允许 html

39
.pre-commit-config.yaml Normal file
View File

@@ -0,0 +1,39 @@
default_install_hook_types: [pre-commit, prepare-commit-msg]
ci:
autofix_commit_msg: ":rotating_light: auto fix by pre-commit hooks"
autofix_prs: true
autoupdate_branch: master
autoupdate_schedule: monthly
autoupdate_commit_msg: ":arrow_up: auto update by pre-commit hooks"
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.0.276
hooks:
- id: ruff
args: [--fix, --exit-non-zero-on-fix]
stages: [commit]
- repo: https://github.com/pycqa/isort
rev: 5.12.0
hooks:
- id: isort
stages: [commit]
- repo: https://github.com/psf/black
rev: 23.3.0
hooks:
- id: black
stages: [commit]
- repo: https://github.com/pre-commit/mirrors-prettier
rev: v3.0.0-alpha.9-for-vscode
hooks:
- id: prettier
types_or: [javascript, jsx, ts, tsx, markdown, yaml, json]
stages: [commit]
- repo: https://github.com/nonebot/nonemoji
rev: v0.1.4
hooks:
- id: nonemoji
stages: [prepare-commit-msg]

View File

@@ -1,3 +1,3 @@
# Changelog # Changelog
See [changelog.md](./website/src/pages/changelog.md) or <https://v2.nonebot.dev/changelog> See [changelog.md](./website/src/pages/changelog.md) or <https://nonebot.dev/changelog>

86
CODE_OF_CONDUCT.md Normal file
View File

@@ -0,0 +1,86 @@
# NoneBot2 贡献者公约
## 我们的承诺
身为项目成员、贡献者、负责人,我们保证参与此社区的每个人都不受骚扰,不论其年龄、体型、身体条件、民族、性征、性别认同与表现、经验水平、教育程度、社会地位、国籍、相貌、种族、宗教信仰及性取向如何。
我们承诺致力于建设开放、友善、多元、包容、健康的社区环境。
## 我们的准则
有助于促进本社区积极环境的行为包括但不限于:
- 与人为善、推己及人
- 尊重不同的主张、观点和经历
- 积极提出、耐心接受有益批评
- 面对过失,承担责任、认真道歉、从中学习
- 关注社区共同诉求,而非一己私利
不当行为包括但不限于:
- 发布与性有关的言论或图像,以及任何形式的献殷勤或勾引
- 挑衅行为、侮辱或贬损的言论、人身及政治攻击
- 公开或私下骚扰
- 未获明确授权擅自发布他人的资料,如地址、电子邮箱等
- 其他有理由认定为违反职业操守的不当行为
## 落实之义务
社区负责人有责任诠释什么是“妥当行为”,并据此准则,妥善公正地认定与处置不当、威胁、冒犯及有害的行为。
社区负责人有权利和义务删除、编辑、拒绝违背本公约的评论comment、提交commit、代码、维基wiki编辑、问题issue等贡献。如有必要需告知采取措施的理由。
## 适用范围
此行为标准适用于本社区全部场合,以及在其他场合代表本社区的个人。
代表本社区的情形包括但不限于:使用官方电子邮件与社交平台、作为指定代表参与在线或线下活动。
## 贯彻落实
如遇滥用、骚扰等不当行为,请通过 contact@nonebot.dev 向我们举报。我们将迅速审议并调查全部投诉。
社区全体负责人有义务保密举报者信息。
## 指导方针
社区负责人将依据下列方案判断并处置违纪行为:
### 一、督促
**社区影响**:用语不当、举止不符合道德或不受社区欢迎。
**处理意见**:由社区负责人予以非公开的书面警告,阐明违纪事由、解释举止如何不妥。或要求公开道歉。
### 二、警告
**社区影响**:一起或多起事件中的违纪行为。
**处理意见**:警告继续违纪的后果、违纪者在特定时间内禁止与当事人往来、不得擅自与社区执法者往来,禁令涵盖社区内外、社交网络在内的一切联络。如有违反,可致封禁乃至开除。
### 三、封禁
**社区影响**:严重违纪行为,包括屡教不改。
**处理意见**:违纪者在特定时间内禁止与社区的任何往来或公开联络,禁止任何与当事人公开或私下往来,不得擅自与社区管理者往来。如有违反,可导致开除。
### 四、开除
**社区影响**:典型违纪行为,例如屡教不改、骚扰某个人、敌对或贬低某个群体。
**处理意见**:无限期禁止违纪者与项目社区的一切公开往来。
## 来源
本行为标准改编自[参与者公约][homepage]2.0 版,可在此查阅:[https://www.contributor-covenant.org/zh-cn/version/2/0/code_of_conduct.html][v2.0]
指导方针借鉴自[Mozilla 纪检分级][mozilla coc]。
此行为标准常见问题请洽:[https://www.contributor-covenant.org/faq][faq]。
另有诸译本:[https://www.contributor-covenant.org/translations][translations]。
[homepage]: https://www.contributor-covenant.org
[v2.0]: https://www.contributor-covenant.org/version/2/0/code_of_conduct.html
[mozilla coc]: https://github.com/mozilla/diversity
[faq]: https://www.contributor-covenant.org/faq
[translations]: https://www.contributor-covenant.org/translations

93
CONTRIBUTING.md Normal file
View File

@@ -0,0 +1,93 @@
# NoneBot2 贡献指南
首先,感谢你愿意为 NoneBot2 贡献自己的一份力量!
本指南旨在引导你更规范地向 NoneBot2 提交贡献,请务必认真阅读。
## 提交 Issue
在提交 Issue 前,我们建议你先查看 [FAQ](https://github.com/nonebot/discussions/discussions/13) 与 [已有的 Issues](https://github.com/nonebot/nonebot2/issues),以防重复提交。
### 报告问题、故障与漏洞
NoneBot2 仍然是一个不够稳定的开发中项目,如果你在使用过程中发现问题并确信是由 NoneBot2 引起的,欢迎提交 Issue。
### 建议功能
NoneBot2 还未进入正式版,欢迎在 Issue 中提议要加入哪些新功能。
为了让开发者更好地理解你的意图,请认真描述你所需要的特性,可能的话可以提出你认为可行的解决方案。
## Pull Request
NoneBot 使用 [poetry](https://python-poetry.org/) 管理项目依赖,由于 pre-commit 也经其管理,所以在此一并说明。
下面的命令能在已安装 poetry 和 yarn 的情况下帮你快速配置开发环境。
```bash
# 安装 python 依赖
poetry install
# 安装 pre-commit git hook
pre-commit install
```
### 使用 GitHub CodespacesDev Container
[![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://github.com/codespaces/new?hide_repo_select=true&ref=master&repo=289605524)
### Commit 规范
请确保你的每一个 commit 都能清晰地描述其意图,一个 commit 尽量只有一个意图。
NoneBot 的 commit message 格式遵循 [gitmoji](https://gitmoji.dev/) 规范,在创建 commit 时请牢记这一点。
或者使用 [nonemoji](https://github.com/nonebot/nonemoji) 代替 git 进行 commitnonemoji 已默认作为项目开发依赖安装。
```bash
nonemoji commit [-e EMOJI] [-m MESSAGE] [-- ...]
```
### 工作流概述
`master` 分支为 NoneBot 的开发分支,在任何情况下都请不要直接修改 `master` 分支,而是创建一个目标分支为 `nonebot:master` 的 Pull Request 来提交修改。Pull Request 标题请尽量更改成中文,以便自动生成更新日志。
如果你不是 NoneBot 团队的成员,可在 fork 本仓库后,向本仓库的 `master` 分支发起 Pull Request注意遵循先前提到的 commit message 规范创建 commit。我们将在 code review 通过后通过 squash merge 方式将您的贡献合并到主分支。
### 撰写文档
NoneBot2 的文档使用 [docusaurus](https://docusaurus.io/),它有一些 [Markdown 特性](https://docusaurus.io/zh-CN/docs/markdown-features) 可能会帮助到你。
如果你需要在本地预览修改后的文档,可以使用 yarn 安装文档依赖后启动 dev server如下所示
```bash
yarn install
yarn start
```
NoneBot2 文档并没有具体的行文风格规范,但我们建议你尽量写得简单易懂。
以下是比较重要的编写与排版规范。目前 NoneBot2 文档中仍有部分文档不完全遵守此规范,如果在阅读时发现欢迎提交 PR。
1. 中文与英文、数字、半角符号之间需要有空格。例:`NoneBot2 是一个可扩展的 Python 异步机器人框架。`
2. 若非英文整句,使用全角标点符号。例:`现在你可以看到机器人回复你“Hello, World !”。`
3. 直引号`「」`和弯引号`“”`都可接受,但同一份文件里应使用同种引号。
4. **不要使用斜体**,你不需要一种与粗体不同的强调。除此之外,你也可以考虑使用 docusaurus 提供的[告示](https://docusaurus.io/zh-CN/docs/markdown-features/admonitions)功能。
5. 文档中应以“我们”指代机器人开发者,以“机器人用户”指代机器人的使用者。
以上由[社区创始人 richardchien 的中文排版规范](https://stdrc.cc/style-guides/chinese)补充修改得到。
如果你需要编辑器检查 Markdown 规范,可以在 VSCode 中安装 markdownlint 扩展。
### 参与开发
NoneBot2 的代码风格遵循 [PEP 8](https://www.python.org/dev/peps/pep-0008/) 与 [PEP 484](https://www.python.org/dev/peps/pep-0484/) 规范,请确保你的代码风格和项目已有的代码保持一致,变量命名清晰,有适当的注释与测试代码。
## 为社区做贡献
你可以在 NoneBot 商店上架自己的适配器、插件、机器人,具体步骤可参考 [发布插件](https://nonebot.dev/docs/developer/plugin-publishing) 一节。
我们仅对插件的兼容性进行简单测试,并会在下一个版本发布前对与该版本不兼容的插件作出处理。
虽然对插件的内容没有严格限制,但我们还是建议在上架插件之前先查看商店有无功能一致的插件。如果你想要上架商店的插件功能与现有插件不完全重合,请在插件说明中补充其与现有插件的区别。
同时,如果你参考或基于他人发行的代码进行开发,请注意遵守各代码所使用的开源许可协议。

156
README.md
View File

@@ -1,6 +1,6 @@
<!-- markdownlint-disable MD033 MD041 --> <!-- markdownlint-disable MD033 MD041 -->
<p align="center"> <p align="center">
<a href="https://v2.nonebot.dev/"><img src="https://v2.nonebot.dev/logo.png" width="200" height="200" alt="nonebot"></a> <a href="https://nonebot.dev/"><img src="https://nonebot.dev/logo.png" width="200" height="200" alt="nonebot"></a>
</p> </p>
<div align="center"> <div align="center">
@@ -19,34 +19,62 @@ _✨ 跨平台 Python 异步机器人框架 ✨_
<img src="https://img.shields.io/github/license/nonebot/nonebot2" alt="license"> <img src="https://img.shields.io/github/license/nonebot/nonebot2" alt="license">
</a> </a>
<a href="https://pypi.python.org/pypi/nonebot2"> <a href="https://pypi.python.org/pypi/nonebot2">
<img src="https://img.shields.io/pypi/v/nonebot2" alt="pypi"> <img src="https://img.shields.io/pypi/v/nonebot2?logo=python&logoColor=edb641" alt="pypi">
</a> </a>
<img src="https://img.shields.io/badge/python-3.7.3+-blue" alt="python"> <img src="https://img.shields.io/badge/python-3.8+-blue?logo=python&logoColor=edb641" alt="python">
<a href="https://github.com/psf/black">
<img src="https://img.shields.io/badge/code%20style-black-000000.svg?logo=python&logoColor=edb641" alt="black">
</a>
<a href="https://github.com/Microsoft/pyright">
<img src="https://img.shields.io/badge/types-pyright-797952.svg?logo=python&logoColor=edb641" alt="pyright">
</a>
<a href="https://github.com/astral-sh/ruff">
<img src="https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/charliermarsh/ruff/main/assets/badge/v2.json" alt="ruff">
</a>
<br />
<a href="https://codecov.io/gh/nonebot/nonebot2"> <a href="https://codecov.io/gh/nonebot/nonebot2">
<img src="https://codecov.io/gh/nonebot/nonebot2/branch/master/graph/badge.svg?token=2P0G0VS7N4"/> <img src="https://codecov.io/gh/nonebot/nonebot2/branch/master/graph/badge.svg?token=2P0G0VS7N4" alt="codecov"/>
</a>
<a href="https://github.com/nonebot/nonebot2/actions/workflows/website-deploy.yml">
<img src="https://github.com/nonebot/nonebot2/actions/workflows/website-deploy.yml/badge.svg?branch=master&event=push" alt="site"/>
</a>
<a href="https://results.pre-commit.ci/latest/github/nonebot/nonebot2/master">
<img src="https://results.pre-commit.ci/badge/github/nonebot/nonebot2/master.svg" alt="pre-commit" />
</a>
<a href="https://github.com/nonebot/nonebot2/actions/workflows/pyright.yml">
<img src="https://github.com/nonebot/nonebot2/actions/workflows/pyright.yml/badge.svg?branch=master&event=push" alt="pyright">
</a>
<a href="https://github.com/nonebot/nonebot2/actions/workflows/ruff.yml">
<img src="https://github.com/nonebot/nonebot2/actions/workflows/ruff.yml/badge.svg?branch=master&event=push" alt="ruff">
</a> </a>
<br /> <br />
<a href="https://onebot.dev/"> <a href="https://onebot.dev/">
<img src="https://img.shields.io/badge/OneBot-v11-black?style=social&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABABAMAAABYR2ztAAAAIVBMVEUAAAAAAAADAwMHBwceHh4UFBQNDQ0ZGRkoKCgvLy8iIiLWSdWYAAAAAXRSTlMAQObYZgAAAQVJREFUSMftlM0RgjAQhV+0ATYK6i1Xb+iMd0qgBEqgBEuwBOxU2QDKsjvojQPvkJ/ZL5sXkgWrFirK4MibYUdE3OR2nEpuKz1/q8CdNxNQgthZCXYVLjyoDQftaKuniHHWRnPh2GCUetR2/9HsMAXyUT4/3UHwtQT2AggSCGKeSAsFnxBIOuAggdh3AKTL7pDuCyABcMb0aQP7aM4AnAbc/wHwA5D2wDHTTe56gIIOUA/4YYV2e1sg713PXdZJAuncdZMAGkAukU9OAn40O849+0ornPwT93rphWF0mgAbauUrEOthlX8Zu7P5A6kZyKCJy75hhw1Mgr9RAUvX7A3csGqZegEdniCx30c3agAAAABJRU5ErkJggg==" alt="cqhttp"> <img src="https://img.shields.io/badge/OneBot-v11-black?style=social&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABABAMAAABYR2ztAAAAIVBMVEUAAAAAAAADAwMHBwceHh4UFBQNDQ0ZGRkoKCgvLy8iIiLWSdWYAAAAAXRSTlMAQObYZgAAAQVJREFUSMftlM0RgjAQhV+0ATYK6i1Xb+iMd0qgBEqgBEuwBOxU2QDKsjvojQPvkJ/ZL5sXkgWrFirK4MibYUdE3OR2nEpuKz1/q8CdNxNQgthZCXYVLjyoDQftaKuniHHWRnPh2GCUetR2/9HsMAXyUT4/3UHwtQT2AggSCGKeSAsFnxBIOuAggdh3AKTL7pDuCyABcMb0aQP7aM4AnAbc/wHwA5D2wDHTTe56gIIOUA/4YYV2e1sg713PXdZJAuncdZMAGkAukU9OAn40O849+0ornPwT93rphWF0mgAbauUrEOthlX8Zu7P5A6kZyKCJy75hhw1Mgr9RAUvX7A3csGqZegEdniCx30c3agAAAABJRU5ErkJggg==" alt="onebot">
</a> </a>
<a href="http://github.com/mamoe/mirai"> <a href="https://onebot.dev/">
<img src="https://img.shields.io/badge/mirai-HTTP-lightgrey?style=social"> <img src="https://img.shields.io/badge/OneBot-v12-black?style=social&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABABAMAAABYR2ztAAAAIVBMVEUAAAAAAAADAwMHBwceHh4UFBQNDQ0ZGRkoKCgvLy8iIiLWSdWYAAAAAXRSTlMAQObYZgAAAQVJREFUSMftlM0RgjAQhV+0ATYK6i1Xb+iMd0qgBEqgBEuwBOxU2QDKsjvojQPvkJ/ZL5sXkgWrFirK4MibYUdE3OR2nEpuKz1/q8CdNxNQgthZCXYVLjyoDQftaKuniHHWRnPh2GCUetR2/9HsMAXyUT4/3UHwtQT2AggSCGKeSAsFnxBIOuAggdh3AKTL7pDuCyABcMb0aQP7aM4AnAbc/wHwA5D2wDHTTe56gIIOUA/4YYV2e1sg713PXdZJAuncdZMAGkAukU9OAn40O849+0ornPwT93rphWF0mgAbauUrEOthlX8Zu7P5A6kZyKCJy75hhw1Mgr9RAUvX7A3csGqZegEdniCx30c3agAAAABJRU5ErkJggg==" alt="onebot">
</a>
<a href="https://ding-doc.dingtalk.com/document#/org-dev-guide/elzz1p">
<img src="https://img.shields.io/badge/%E9%92%89%E9%92%89-Bot-lightgrey?style=social&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAMAAACdt4HsAAAAnFBMVEUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD4jUzeAAAAM3RSTlMAQKSRaA+/f0YyFevh29R3cyklIfrlyrGsn41tVUs48c/HqJm9uZdhX1otGwkF9IN8V1CX0Q+IAAABY0lEQVRYw+3V2W7CMBAF0JuNQAhhX9OEfYdu9///rUVWpagE27Ef2gfO+0zGozsKnv6bMGzAhkNytIe5gDdzrwtTCwrbI8x4/NF668NAxgI3Q3UtFi3TyPwNQtPLUUmDd8YfqGLNe4v22XwEYb5zoOuF5baHq2UHtsKe5ivWfGAwrWu2mC34QM0PoCAuqZdOmiwV+5BLyMRtZ7dTSEcs48rzWfzwptMLyzpApka1SJ5FtR4kfCqNIBPEVDmqoqgwUYY5plQOlf6UEjNoOPnuKB6wzDyCrks///TDza8+PnR109WQdxLo8RKWq0PPnuXG0OXKQ6wWLFnCg75uYYbhmMIVVdQ709q33aHbGIj6Duz+2k1HQFX9VwqmY8xYsEJll2ahvhWgsjYLHFRXvIi2Qb0jzMQCzC3FAoydxCma88UCzE3JCWwkjCNYyMUCzHX4DiuTMawEwwhW6hnshPhjZzzJfAH0YacpbmRd7QAAAABJRU5ErkJggg==" alt="ding">
</a> </a>
<a href="https://core.telegram.org/bots/api"> <a href="https://core.telegram.org/bots/api">
<img src="https://img.shields.io/badge/telegram-Bot-lightgrey?style=social&logo=telegram"> <img src="https://img.shields.io/badge/telegram-Bot-lightgrey?style=social&logo=telegram" alt="telegram">
</a> </a>
<a href="https://open.feishu.cn/document/home/index"> <a href="https://open.feishu.cn/document/home/index">
<img src="https://img.shields.io/badge/%E9%A3%9E%E4%B9%A6-Bot-lightgrey?style=social&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAADICAMAAACahl6sAAAAk1BMVEX///8zMzNJSUlSUlJcXFxtbW0zMzNLS0szMzMzMzNBQUGVlZUzMzM1NTU0NDQzMzMzMzM0NDQ0NDQ0NDQ3NzdDQ0M0NDQ2NjY4ODg9PT0zMzM0NDQ5OTk7OzszMzM0NDQ3NzczMzM0NDQ0NDQ0NDQ0NDQ3Nzc2NjY4ODg2NjY7Ozs0NDQ6Ojo6Ojo3Nzc4ODgzMzNGdMWJAAAAMHRSTlMD6h0TDgr8GNf0KQbvhLT45KKTmm4jwHVJLdLFQzbcjFTgzsq7rl58T2kyqD46Y1riMDRhAAAFr0lEQVR42uzZWXKiUACF4YMyqKAQhyjOc7STmLP/1bVlLukESIJ3sLGKbwFU/Q8HuIBKpVKpVCqVSqVSqVQqlUqlUvmNM10Mcfda/U6TPdw3e9lb8ayLO+bPniYu+amjNcPd8U7PFhML0RE5uCvnaY/5SVt0WFvckcu0vxjiYmDxbu5cl2mn9UVHRMa4B2LaP3RYKD1vL6adccRFLSLL/izxxbRz7UXHimdLlFdq2mlvnztYRznZh96cP3G/dkxQRrOnR5c/c5eiQ+S2UTbe/sHir9zD1w6+okz8aXvMItyRqE46ApSHmHYRYdLRoPCMcrAP3TkLC6fpDp5QAn/EtItqij3UG/zgQZH5aWc7ZqJjzA9jKFGf9ppXC3I6uMB/Mzh2mpQQ/Mnp4BSy1Kctx4pFx5qfhA4kqE87pCyrldfBDm6sLqat2mGnttXHDfkvYtryooHo2PCrFm5lcNw1qWr1XUeEm7BH3QYVRJNGcOmoietNmNKDWeKFnCo6b3Wc1drW/NsOLpFRqmmT4xgfPFw42Q7XhkFi2kq2DtKcR2Y8wpTacRdQ3aZYB59ggiOmrS6sFevgDNr9GW6pzRAZdsQsC3rV3x4i6uQha8+sB2h9am9c6rVBDj9ixr5k007rIs+CGV65pl3wXjRi2hrKYjFtM/rI02JaW3XaPYtGtZHHY9qL0lN7QuO2yBMzpenLTvtkZNos+AY1ZcpObtoLtWmrj6TNlCOuJqZ9M3PkaDBlIBHCmwpHyHpjSgMS2ryhcIqsmsWULmR0eTPhK7LsMdMOKHdJM+nw8E+8ZoYDOT3eRDDDuz6HNt7VeszaQtYDJch+38WRZ51TDO+0Y54hylzy0XHib2JI83c0zIoLd1hAeUus1jenQe2HQ79Dg6wB3i1d/uoNpS2JrulgHWqcRxqySjoObrFjfUlLVrVrOtiGMmdCA+ZJx8hlEa9QZ2+oXcNLOkIWEUAHe22sYxqykGdoUV//5w6eoKlkTI3Gdbx7CVmQB10lDWqzSTpemyyoAW28ubYO++oOLqBPbUUtJknHrMnCRihdyaOTdAQsLHSgtSTS2BEHLK4DvQYWFW2lOtiHZi3Fko6fXCjgNVooV0nHl7tMBP1aAaXtJDvYgwGxdMmzLzu1JUyYNSU7IAwiiZ8OJrxKlTzI38QnMORFoqSn8Fh9gikvIa/UVejgDMZMQ9mOOa8WwKCRyysslF6hn2HSwZX4+ew1KGEPCSZKhoqHMw9mLd1rO8aUMYZpexbRV/2AsYBxy7/t3NtuglAQheFR6wEPVEQtaq1WxQNqnfd/urY08QJFYHZS15D9vcHckMzOz/QWA9/3jqHrbmbr1bT10a90ncQcoiclgKY/Vq81q6P2JJqfI+NHPqdDSMRzsEtIXmYGcQcQk2fwKgHxTCIVJGMWwTu6sWGxPSFx+QpkOfz3QcYEJWQhtGsbR5aKCIrHInjXNsSDeITFZ6ELYZEMAnltY8AyawKz4KJAr21IBzkRmB6LOIRGOEhIaHYsciA0uxIshwa/DLQIzrAEy2HswBIBwck9yNOvbWT4YgHEU4zbEiyHsQsXhnmKccmxp2cbxvb8CyDbMBXwD4hsw1BQguUw9s4Mk20YOTFQtmHiDJVtGJhjZRtyEVi2ITbhnLBOMd5qOvqXwz9RFy3bkJpU0LINeTCsJdvIztHVZhsJo77SbOPG6FNltpFQqMxsE7hmS+9ymJxE7XKYUGupyzZS1Kbaso00tbWybONBTadyObyjPlaVbTycRFO28Uh9oyjbEJ/E2JImnVDXy1y6zpHvW5E2npJsI5unI9vIwVe3HKYZaMg2clkoyDby6Wl5mcv0Bp9t5DVEzzZyG4JnG/kdsLONArbQ2UYRlwZwtlHIsoGbbRSdRNtymGbf0LYcpgleQbMNwdUCbcthmrP2j++VjqdSy7Isy7Isy4LxDTcBnqEPd5jdAAAAAElFTkSuQmCC" alt="feishu"> <img src="https://img.shields.io/badge/%E9%A3%9E%E4%B9%A6-Bot-lightgrey?style=social&logo=data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48c3ZnIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDQ4IDQ4IiBmaWxsPSJub25lIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxwYXRoIGQ9Ik0xNyAyOUMyMSAyOSAyNSAyNi45MzM5IDI4IDIzLjQwNjVDMzYgMTQgNDEuNDI0MiAxNi44MTY2IDQ0IDE3Ljk5OThDMzguNSAyMC45OTk4IDQwLjUgMjkuNjIzMyAzMyAzNS45OTk4QzI4LjM4MiAzOS45MjU5IDIzLjQ5NDUgNDEuMDE0IDE5IDQxQzEyLjUyMzEgNDAuOTc5OSA2Ljg2MjI2IDM3Ljc2MzcgNCAzNS40MDYzVjE2Ljk5OTgiIHN0cm9rZT0iIzMzMyIgc3Ryb2tlLXdpZHRoPSI0IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiLz48cGF0aCBkPSJNNS42NDgwOCAxNS44NjY5QzUuMDIyMzEgMTQuOTU2NyAzLjc3NzE1IDE0LjcyNjEgMi44NjY5NCAxNS4zNTE5QzEuOTU2NzMgMTUuOTc3NyAxLjcyNjE1IDE3LjIyMjggMi4zNTE5MiAxOC4xMzMxTDUuNjQ4MDggMTUuODY2OVpNMzYuMDAyMSAzNS43MzA5QzM2Ljk1OCAzNS4xNzc0IDM3LjI4NDMgMzMuOTUzOSAzNi43MzA5IDMyLjk5NzlDMzYuMTc3NCAzMi4wNDIgMzQuOTUzOSAzMS43MTU3IDMzLjk5NzkgMzIuMjY5MUwzNi4wMDIxIDM1LjczMDlaTTIuMzUxOTIgMTguMTMzMUM1LjI0MzUgMjIuMzM5IDEwLjc5OTIgMjguMTQ0IDE2Ljg4NjUgMzIuMjIzOUMxOS45MzQ1IDM0LjI2NjcgMjMuMjE3IDM1Ljk0NiAyNi40NDkgMzYuNzMyNEMyOS42OTQ2IDM3LjUyMiAzMy4wNDUxIDM3LjQ0MjggMzYuMDAyMSAzNS43MzA5TDMzLjk5NzkgMzIuMjY5MUMzMi4yMDQ5IDMzLjMwNzIgMjkuOTkyOSAzMy40NzggMjcuMzk0NyAzMi44NDU4QzI0Ljc4MyAzMi4yMTAzIDIxLjk0MDUgMzAuNzk1OCAxOS4xMTM1IDI4LjkwMTFDMTMuNDUwOCAyNS4xMDYgOC4yNTY1IDE5LjY2MSA1LjY0ODA4IDE1Ljg2NjlMMi4zNTE5MiAxOC4xMzMxWiIgZmlsbD0iIzMzMyIvPjxwYXRoIGQ9Ik0zMy41OTQ1IDE3QzMyLjgzOTggMTQuNzAyNyAzMC44NTQ5IDkuOTQwNTQgMjcuNTk0NSA3SDExLjU5NDVDMTUuMjE3MSAxMC42NzU3IDIzIDE2IDI3IDI0IiBzdHJva2U9IiMzMzMiIHN0cm9rZS13aWR0aD0iNCIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIi8+PC9zdmc+" alt="feishu">
</a>
<a href="https://docs.github.com/en/developers/apps">
<img src="https://img.shields.io/badge/GitHub-Bot-181717?style=social&logo=github" alt="github"/>
</a> </a>
<a href="https://bot.q.qq.com/wiki/"> <a href="https://bot.q.qq.com/wiki/">
<img src="https://img.shields.io/badge/QQ%E9%A2%91%E9%81%93-Bot-lightgrey?style=social&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAMAAACdt4HsAAAC+lBMVEUAAAApRHRAbvYyVI4dMlsNGjcvTH5anN0/a5xdo+Vdpvpms/dMfvdcoPZjr/hJfu5krvA9Z+8/a+M9adxQjNdYmdVAbdU4XtRRjcxFd8wyVbM+aZowUJwpRJlZneRIevU5YORWl+1fqOZVle9ZnOpEdOk/aelepus/a+NQiuVKgeQ6YeRQi905X91EdtxAbthZnNlRjdk5YdpQjuhLgehhquc7Y+c6Y8k3XctWlcs0V8tJfMMzVsM9aMJVksdHebVKgLQyVJknP5REdKD///9irv9Ccf5EdP9GfP9jsf7+/f5SkP9mtf9GeP/+/vxJfv5Ukv9Ng/9boP9Gef1ls/9Ulf9Wl//+//5KgP9Nhv9dpP9Oiv9Tk/5Abv9Lg/9Fdv9NiP7///5Qj/7+/fxfpv9Nh/xhq/9QjP9Wm/79//1co/9ir/5fqf5bov9Qi//8/f1XmP1Wl/1ksv/7/v38/vv9/vlgqv9Znf9IfPzZ5fdeqP9nt/5anv5Xmf9Uk/1anvxpuf73/P34+/tot/9hrf1anvpMhvpSjv1pt/9ZnP1aofxTkfxNivza5/dOhPf+//1Ri/z+//vz+fvw9/tIf/v6/fpdofrI2PlYmfnF3Pe0y/Pm7/jW4/e0zvRapPxXlfpTkvpGevri7ffS3vVMiP5XnvxXnPtQivn3/Ph0r/dgpPZ1qfXi6/S9z/NYmf/z+v5Yn/7L2vlfmflUk/hXi/dRgvSw0fGnu+1dqP3t9Pthq/vA1vjc6vfU4vd4sfdepPdsn/dam/fU5fawzfRjjfRhi/NJhP0/a/y80fhTjfhrrfeXvvZuk/WyyPR9rvSqz/OcwfOmv/OSuPO0yfKguPCWr/BPi+9MheRKgv1gsPzq9PpgpvpVjvrN2/jG1vdonvbA0PSrx/OtxPCkxPDp8PvN4ftnuPuCufloqPiex/a3z/WPuvWQqvR2mfRplvReivSOqvOKufKGufKCrPKBoPJzlfFjr+88Ze6ftux8nOxxq/SGr/CFr/Cvx+3R19+QAAAARHRSTlMAEP4ZEwUEvC3S/v7+/Pv08PDXvJqampqSgEtJLS3f/Pz4+O/n5+bl4tTU1Lq6ure2trGdnZ2dlI6Afl5eXldMS0lJLRAR2gcAAAewSURBVFjDlZcFfBJRHMdvdnd3d3cn1qk3h4Et7EQ965h3KKgTQUGdOgYMHII6a5vO2d3d3d3d3X4+/t974A51xvfDu7vfP348HsdxR6VAulaN6lUu8SHVgQOpPpSoXK9Rq3TUf5CmSdWcB/oFcCBn1SZp/rG9aP6c/X5LzvxF/6W9Vqq2KZKq1t8sgvKT9pQt8gf9qb9QOVLWjux+K8oVSrE9bYNU7f6BVA3SpjD93O3+kdy//Ripcw34QefOnaViwM8iV+rf9Ffo7Kdr166du87vSgQGZGcIJocq/OIQlAv1EbAHDL/0hXzOhFxBP61f7vbJdJ0PFfN1RBB0MIPAUO7Alazvz+l0WY4+Pbthw4az66QG63Do6dEsuh+W9QO+//moVRet071L8phomUzBGPcGBwe3hxdmn5FVyGS0yZP0TodpHzy/kGQBygYjonUvbtpFraDwGUjYZ2JRVNSKK26+0EXjWNnkZciH26OjPy3gOZ5nVVCq+p0BzfK8wC/4FB2MLfL5+4sU7wkER7/dhN6aYxia0zKmvT0l7DUxHE1zHMcoVLJNb6ODUbB4EYpQHddcPHpXK5OpWNHp8CxbtmzmPqnBvpnLAIeTQdPT3j16EUer+yaQpT8i8pqAVi9h4cu1kZGR69bt7y9h/7p1EFz7cmEC+iTiwkgczUKmkC8ECkIs54wiI4j2j5EXQwj9QyCBNgCRIZbIj3ZRYET+nCVkP4TwKqTJHoJTXlbFscZzlv6WXr1CkpGKXhb0PozVynoj1SiTHV3lmvYC1JZnCQzNOG9a1BY1aAlKqbBYLEm8jWYSnpGypmBQTQ0sUp9iVCqr/bl60SK1UqlchIIzYCABB/BCIRR/breqVMwp9SIlUA2u39mVCPUWBlZQHz8DiT591t5euPD2WiUR6rXfiAKhnBHvhXVkrs9AGWX2dFTLGX0AZfxCTiaTHYwChTjl1LL8HZ+YccvJa52nfCL+IHyT3Lb4kVi1pAqOBPqMjPfSYHBmZBQSI6Nm0TTDXicCFCsy9HWfiDqGDPTxIzEFqTqDMVF6BTIYHEXUKhDMrMGEKddstIr2qajQYzT6sIeIrENVCsVc1VuRQeiU0FAYU4gBEkhfY5CBT4TCDJABkZWoCx0xxGB26BSspqyiFSowwAKUSNvoWUQQA1p/NRTLC9R5El+jZ5BBR9Qzffr0VbTICrNiQSC9yskKrN9g+hkoVOljp2N5njrUBdExlhj07UI4qGVZ7ePpREw/KLJa5xxfqu9sBRjMjCXJQ5SvaI3ehg0mELlz94oVu3ei3IQJE2J3eu0rdt/AAvAZoEMwp2L7Iias0aOlAYO+hHmrV89De5KVqAndicEaUhlLremOCJuHDeZ0D+v+V4jBPFK5hjrfA+E3CAvr8RfCwvwGWJ6nLmgQYTF6Gr4cZACCELN6Z+Kbr1/eJO5cHaOBBAyNpkeYZg86kWbGgAAuUBXDw8OXh4fH6BlkoNGgY43mxumkbR6HySiIomBKcHi2JZ2+odHELIdSzR5YbevMmHBMRar2EEyMXgXxOcvD4Viza88mnuNYhmNpDMMwIr9pzy4NJMOXIwMGDHBfbaog3k+LQzNgHscNmTZkSNwut0mgaQH6OK0NtmDG8yb3rrghY8YMiZuD5jozbhoSQwpShaeNAaYt3obWYIt58TQkzKe3JYisKCoUNKegwUFI8J7257agSi8SIAtT6UqPRxjc+De2axocLzYbzObjSVtX8Dyj1TICv2Jr0nEzsHLMmPFxZvS75d2GMSDGl4bbxypyuXy8wXBLUMloe6JBvtJgGB8BjitdiUfmuO/dc885kuhaaRg/fiVUwUi00zKVcAuqoLEKRVGN5UCE4bhJRdPiFrPZJZcQESEPwOUybxFpWmU6bsCZxuiyXnKEfIQ8wjWTZxSs6WRExNwRfiCByn4IOSRPwr+kit/qikChkvjmNc/Qob2HznWd5G1WgXbc2TF36A9GkI0fqLrj4AWrjT/pmju0d++heShEhqy9EUt3CwKso9F75OHSHTuWLp3bW8LcpSj28IjXCCsoCLuv4GjWDBSm5sCBSG6/zMNJDldjh2fBggVbEwdicOXAz1sh5HHwrBbON3bTdpKoCc1kCrg00xMHzICzwanD0VrTkoESlpi06L/dZuMUMoXjSaaNKIgmQMgzFti48corj1aEk88KJwpjXDJWwhIjSysUVi0HJ4bn1ZWNG1EwT/ItTpnhhCVuO8sKCjDgjNuHSwAD9M8lsJzdvcQXKyO502ueaTQwaNDmza/dl50+g9EStsMMwMB52f168+bho4dDKFNzSkKBQX4mH37w6MT69etPHCaacBiHHj04PHnQoNGkrkDgjWb6NslMJrSRhjZPHhQYSp+WCiCoWHJu2LBhl2BIDSASGMoY9MvNdjFfKUA2RCRHpQbFfne7nrHDqA4dOozCG/Qie6mAQURG6P+VoPS4DED7wONAkT4ohUeeAtm6/QPZCqSlUqJF+U6dOnVDA23Q/ldRvsUfH/vyZuv0R7LlDaL+TOsamSdNmjTuBwEic43W1N/JkDfH1EmIqT78xznyZvjXh+9m6XNM/Ikc6Zulof6DdIUb1s1Y6n3m+/czvy+VsW7Dwik9/n8HzjZEy9x05tIAAAAASUVORK5CYII=" alt="QQ频道"> <img src="https://img.shields.io/badge/QQ%E9%A2%91%E9%81%93-Bot-lightgrey?style=social&logo=data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMTIuODIgMTMwLjg5Ij48ZyBkYXRhLW5hbWU9IuWbvuWxgiAyIj48ZyBkYXRhLW5hbWU9IuWbvuWxgiAxIj48cGF0aCBkPSJNNTUuNjMgMTMwLjhjLTcgMC0xMy45LjA4LTIwLjg2IDAtMTkuMTUtLjI1LTMxLjcxLTExLjQtMzQuMjItMzAuMy00LjA3LTMwLjY2IDE0LjkzLTU5LjIgNDQuODMtNjYuNjQgMi0uNTEgNS4yMS0uMzEgNS4yMS0xLjYzIDAtMi4xMy4xNC0yLjEzLjE0LTUuNTcgMC0uODktMS4zLTEuNDYtMi4yMi0yLjMxLTYuNzMtNi4yMy03LjY3LTEzLjQxLTEtMjAuMTggNS40LTUuNTIgMTEuODctNS40IDE3LjgtLjU5IDYuNDkgNS4yNiA2LjMxIDEzLjA4LS44NiAyMS0uNjguNzQtMS43OCAxLjYtMS43OCAyLjY3djQuMjFjMCAxLjM1IDIuMiAxLjYyIDQuNzkgMi4zNSAzMS4wOSA4LjY1IDQ4LjE3IDM0LjEzIDQ1IDY2LjM3LTEuNzYgMTguMTUtMTQuNTYgMzAuMjMtMzIuNyAzMC42My04LjAyLjE5LTE2LjA3LS4wMS0yNC4xMy0uMDF6IiBmaWxsPSIjMDI5OWZlIi8+PHBhdGggZD0iTTMxLjQ2IDExOC4zOGMtMTAuNS0uNjktMTYuOC02Ljg2LTE4LjM4LTE3LjI3LTMtMTkuNDIgMi43OC0zNS44NiAxOC40Ni00Ny44MyAxNC4xNi0xMC44IDI5Ljg3LTEyIDQ1LjM4LTMuMTkgMTcuMjUgOS44NCAyNC41OSAyNS44MSAyNCA0NS4yOS0uNDkgMTUuOS04LjQyIDIzLjE0LTI0LjM4IDIzLjUtNi41OS4xNC0xMy4xOSAwLTE5Ljc5IDAiIGZpbGw9IiNmZWZlZmUiLz48cGF0aCBkPSJNNDYuMDUgNzkuNThjLjA5IDUgLjIzIDkuODItNyA5Ljc3LTcuODItLjA2LTYuMS01LjY5LTYuMjQtMTAuMTktLjE1LTQuODItLjczLTEwIDYuNzMtOS44NHM2LjM3IDUuNTUgNi41MSAxMC4yNnoiIGZpbGw9IiMxMDlmZmUiLz48cGF0aCBkPSJNODAuMjcgNzkuMjdjLS41MyAzLjkxIDEuNzUgOS42NC01Ljg4IDEwLTcuNDcuMzctNi44MS00LjgyLTYuNjEtOS41LjItNC4zMi0xLjgzLTEwIDUuNzgtMTAuNDJzNi41OSA0Ljg5IDYuNzEgOS45MnoiIGZpbGw9IiMwODljZmUiLz48L2c+PC9nPjwvc3ZnPg==" alt="QQ频道">
</a>
<!-- <a href="https://ding-doc.dingtalk.com/document#/org-dev-guide/elzz1p">
<img src="https://img.shields.io/badge/%E9%92%89%E9%92%89-Bot-lightgrey?style=social&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAMAAACdt4HsAAAAnFBMVEUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD4jUzeAAAAM3RSTlMAQKSRaA+/f0YyFevh29R3cyklIfrlyrGsn41tVUs48c/HqJm9uZdhX1otGwkF9IN8V1CX0Q+IAAABY0lEQVRYw+3V2W7CMBAF0JuNQAhhX9OEfYdu9///rUVWpagE27Ef2gfO+0zGozsKnv6bMGzAhkNytIe5gDdzrwtTCwrbI8x4/NF668NAxgI3Q3UtFi3TyPwNQtPLUUmDd8YfqGLNe4v22XwEYb5zoOuF5baHq2UHtsKe5ivWfGAwrWu2mC34QM0PoCAuqZdOmiwV+5BLyMRtZ7dTSEcs48rzWfzwptMLyzpApka1SJ5FtR4kfCqNIBPEVDmqoqgwUYY5plQOlf6UEjNoOPnuKB6wzDyCrks///TDza8+PnR109WQdxLo8RKWq0PPnuXG0OXKQ6wWLFnCg75uYYbhmMIVVdQ709q33aHbGIj6Duz+2k1HQFX9VwqmY8xYsEJll2ahvhWgsjYLHFRXvIi2Qb0jzMQCzC3FAoydxCma88UCzE3JCWwkjCNYyMUCzHX4DiuTMawEwwhW6hnshPhjZzzJfAH0YacpbmRd7QAAAABJRU5ErkJggg==" alt="dingtalk"> -->
</a> </a>
<br /> <br />
<a href="https://jq.qq.com/?_wv=1027&k=5OFifDh"> <a href="https://jq.qq.com/?_wv=1027&k=5OFifDh">
<img src="https://img.shields.io/badge/qq%E7%BE%A4-768887710-orange?style=flat-square" alt="QQ Chat"> <img src="https://img.shields.io/badge/QQ%E7%BE%A4-768887710-orange?style=flat-square" alt="QQ Chat Group">
</a>
<a href="https://qun.qq.com/qqweb/qunpro/share?_wv=3&_wwv=128&appChannel=share&inviteCode=7b4a3&appChannel=share&businessType=9&from=246610&biz=ka">
<img src="https://img.shields.io/badge/QQ%E9%A2%91%E9%81%93-NoneBot-5492ff?style=flat-square" alt="QQ Channel">
</a> </a>
<a href="https://t.me/botuniverse"> <a href="https://t.me/botuniverse">
<img src="https://img.shields.io/badge/telegram-botuniverse-blue?style=flat-square" alt="Telegram Channel"> <img src="https://img.shields.io/badge/telegram-botuniverse-blue?style=flat-square" alt="Telegram Channel">
@@ -57,16 +85,18 @@ _✨ 跨平台 Python 异步机器人框架 ✨_
</p> </p>
<p align="center"> <p align="center">
<a href="https://v2.nonebot.dev/">文档</a> <a href="https://nonebot.dev/">文档</a>
· ·
<a href="https://v2.nonebot.dev/guide/installation.html">安装</a> <a href="https://nonebot.dev/docs/quick-start">快速上手</a>
·
<a href="https://v2.nonebot.dev/guide/getting-started.html">开始使用</a>
· ·
<a href="#插件">文档打不开?</a> <a href="#插件">文档打不开?</a>
</p> </p>
<!-- TODO: asciinema for install --> <p align="center">
<a href="https://asciinema.org/a/569440">
<img src="https://nonebot.dev/img/setup.svg">
</a>
</p>
## 简介 ## 简介
@@ -76,23 +106,38 @@ NoneBot2 是一个现代、跨平台、可扩展的 Python 聊天机器人框架
- 异步优先:基于 Python 的异步特性,即使是~~非常~~大量的消息,也能吞吐自如 - 异步优先:基于 Python 的异步特性,即使是~~非常~~大量的消息,也能吞吐自如
- 易于开发:配合 NB-CLI 脚手架,代码编写上手简单,没有过多的冗余代码,可以让开发者专注于业务逻辑 - 易于开发:配合 NB-CLI 脚手架,代码编写上手简单,没有过多的冗余代码,可以让开发者专注于业务逻辑
- 生而可靠100% 类型注解覆盖,配合编辑器的类型推导功能,能将绝大多数的 Bug 杜绝在编辑器中 ([编辑器支持](https://v2.nonebot.dev/docs/start/editor-support)) - 生而可靠100% 类型注解覆盖,配合编辑器的类型推导功能,能将绝大多数的 Bug 杜绝在编辑器中 ([编辑器支持](https://nonebot.dev/docs/editor-support))
- 社区丰富:社区用户众多,直接和间接用户超过十万人,每天都有大量的活跃用户 ([社区资源](#社区资源)) - 社区丰富:社区用户众多,直接和间接用户超过十万人,每天都有大量的活跃用户 ([社区资源](#社区资源))
- 海纳百川:一个框架,支持多个聊天软件平台,可自定义通信协议 - 海纳百川:一个框架,支持多个聊天软件平台,可自定义通信协议
- [OneBot 协议](https://onebot.dev/) (QQ 等)
- [Mirai-API-HTTP 协议](https://github.com/project-mirai/mirai-api-http)
- [钉钉](https://ding-doc.dingtalk.com/document#/org-dev-guide/elzz1p)
- [Telegram](https://core.telegram.org/bots/api)
- [飞书](https://open.feishu.cn/document/home/index)
- [QQ 频道](https://bot.q.qq.com/wiki/)
- 坚实后盾:支持多种 web 框架,可自定义替换
- [FastAPI](https://fastapi.tiangolo.com/)
- [Quart](https://pgjones.gitlab.io/quart/) (异步 Flask)
- [aiohttp](https://docs.aiohttp.org/en/stable/)
- [httpx](https://www.python-httpx.org/)
- [websockets](https://websockets.readthedocs.io/en/stable/)
更多:[概览](https://v2.nonebot.dev/docs/) | 协议名称 | 状态 | 注释 |
| :-------------------------------------------------------------------------------------------------------------------: | :--: | :-----------------------------------------------------------------------: |
| OneBot[仓库](https://github.com/nonebot/adapter-onebot)[协议](https://onebot.dev/) | ✅ | 支持 QQ、TG、微信公众号、KOOK 等[平台](https://onebot.dev/ecosystem.html) |
| Telegram[仓库](https://github.com/nonebot/adapter-telegram)[协议](https://core.telegram.org/bots/api) | ✅ | |
| 飞书([仓库](https://github.com/nonebot/adapter-feishu)[协议](https://open.feishu.cn/document/home/index) | ✅ | |
| GitHub[仓库](https://github.com/nonebot/adapter-github)[协议](https://docs.github.com/en/apps) | ✅ | GitHub APP & OAuth APP |
| QQ 频道([仓库](https://github.com/nonebot/adapter-qqguild)[协议](https://bot.q.qq.com/wiki/) | ✅ | 官方接口调整较多 |
| 钉钉([仓库](https://github.com/nonebot/adapter-ding)[协议](https://open.dingtalk.com/document/) | 🤗 | 寻找 Maintainer暂不可用 |
| Console[仓库](https://github.com/nonebot/adapter-console) | ✅ | 控制台交互 |
| 开黑啦([仓库](https://github.com/Tian-que/nonebot-adapter-kaiheila)[协议](https://developer.kookapp.cn/) | ↗️ | 由社区贡献 |
| Mirai[仓库](https://github.com/ieew/nonebot_adapter_mirai2)[协议](https://docs.mirai.mamoe.net/mirai-api-http/) | ↗️ | QQ 协议,由社区贡献 |
| Ntchat[仓库](https://github.com/JustUndertaker/adapter-ntchat) | ↗️ | 微信协议,由社区贡献 |
| MineCraft[仓库](https://github.com/17TheWord/nonebot-adapter-minecraft) | ↗️ | 由社区贡献 |
| BiliBili Live[仓库](https://github.com/wwweww/adapter-bilibili) | ↗️ | 由社区贡献 |
| Walle-Q[仓库](https://github.com/onebot-walle/nonebot_adapter_walleq) | ↗️ | QQ 协议,由社区贡献 |
| Villa[仓库](https://github.com/CMHopeSunshine/nonebot-adapter-villa) | ↗️ | 米游社大别野 Bot 协议,由社区贡献 |
- 坚实后盾:支持多种 web 框架,可自定义替换、组合
| 驱动框架 | 类型 |
| :-----------------------------------------------------------------: | :----: |
| [FastAPI](https://fastapi.tiangolo.com/) | 服务端 |
| [Quart](https://quart.palletsprojects.com/en/latest/)(异步 Flask | 服务端 |
| [aiohttp](https://docs.aiohttp.org/en/stable/) | 客户端 |
| [httpx](https://www.python-httpx.org/) | 客户端 |
| [websockets](https://websockets.readthedocs.io/en/stable/) | 客户端 |
更多:[概览](https://nonebot.dev/docs/)
## 什么不是 NoneBot2 ## 什么不是 NoneBot2
@@ -104,16 +149,21 @@ NoneBot2 不是 NoneBot1 的替代品。事实上,它们都在被积极的维
## 即刻开始 ## 即刻开始
~~完整~~文档可以在 [这里](https://v2.nonebot.dev/) 查看。 ~~完整~~文档可以在 [这里](https://nonebot.dev/) 查看。
懒得看文档?下面是快速安装指南: 懒得看文档?下面是快速安装指南:
1. (**强烈建议**)使用你喜欢的 Python 环境管理工具创建新的虚拟环境。 1. 安装 [pipx](https://pypa.github.io/pipx/)
2. 使用 `pip` (或其他) 安装 NoneBot 脚手架。
```bash ```bash
pip install nb-cli python -m pip install --user pipx
python -m pipx ensurepath
```
2. 安装脚手架
```bash
pipx install nb-cli
``` ```
3. 使用脚手架创建项目 3. 使用脚手架创建项目
@@ -122,6 +172,12 @@ NoneBot2 不是 NoneBot1 的替代品。事实上,它们都在被积极的维
nb create nb create
``` ```
4. 运行项目
```bash
nb run
```
## 社区资源 ## 社区资源
### 常见问题 ### 常见问题
@@ -150,31 +206,29 @@ NoneBot2 不是 NoneBot1 的替代品。事实上,它们都在被积极的维
- [文档镜像(中国境内)](https://nb2.baka.icu) - [文档镜像(中国境内)](https://nb2.baka.icu)
- [文档镜像(Vercel)](https://nonebot2-vercel-mirror.vercel.app) - [文档镜像(Vercel)](https://nonebot2-vercel-mirror.vercel.app)
- 其他插件请查看 [商店](https://v2.nonebot.dev/store.html) - 其他插件请查看 [商店](https://nonebot.dev/store)
## 许可证 ## 许可证
`NoneBot` 采用 `MIT` 许可证进行开源 `NoneBot` 采用 `MIT` 许可证进行开源
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR ```text
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
```
## 贡献 ## 贡献
如果你在使用过程中发现任何问题,可以 [提交 Issue](https://github.com/nonebot/nonebot2/issues/new) 或自行 Fork 修改后提交 Pull Request。 请参考 [贡献指南](./CONTRIBUTING.md)
如果你要提交 Pull Request请确保你的代码风格和项目已有的代码保持一致遵循 [PEP 8](https://www.python.org/dev/peps/pep-0008/) 与 [PEP 484](https://www.python.org/dev/peps/pep-0484/),变量命名清晰,有适当的注释与测试代码,**并且请以 `dev` 分支作为 Pull Request 目标分支**。
<!--TODO: Add a CONTRIBUTING.md-->
### 鸣谢 ### 鸣谢
感谢以下开发者对 NoneBot2 作出的贡献: 感谢以下开发者对 NoneBot2 作出的贡献:
<a href="https://github.com/nonebot/nonebot2/graphs/contributors"> <a href="https://github.com/nonebot/nonebot2/graphs/contributors">
<img src="https://contrib.rocks/image?repo=nonebot/nonebot2" /> <img src="https://contrib.rocks/image?repo=nonebot/nonebot2&max=1000" />
</a> </a>

View File

@@ -1,20 +0,0 @@
# Minimal makefile for Sphinx documentation
#
# You can set these variables from the command line, and also
# from the environment for the first two.
SPHINXOPTS ?=
SPHINXBUILD ?= sphinx-build
SOURCEDIR = .
BUILDDIR = _build
# Put it first so that "make" without argument is like "make help".
help:
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
.PHONY: help Makefile
# Catch-all target: route all unknown targets to Sphinx using the new
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
%: Makefile
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)

View File

@@ -1,12 +0,0 @@
\-\-\-
sidebar_position: 1
id: index
slug: /api
\-\-\-
NoneBot 模块
===============
.. automodule:: nonebot
:members:
:show-inheritance:

View File

@@ -1,43 +0,0 @@
\-\-\-
id: index
slug: /api/adapters/
\-\-\-
NoneBot.adapters 模块
=====================
.. automodule:: nonebot.adapters
:members:
:private-members:
:special-members: __init__
:show-inheritance:
.. automodule:: nonebot.adapters._adapter
:members:
:private-members:
:special-members: __init__
:show-inheritance:
.. automodule:: nonebot.adapters._bot
:members:
:private-members:
:special-members: __init__
:show-inheritance:
.. automodule:: nonebot.adapters._message
:members:
:private-members:
:special-members: __init__
:show-inheritance:
.. automodule:: nonebot.adapters._event
:members:
:private-members:
:special-members: __init__
:show-inheritance:
.. automodule:: nonebot.adapters._template
:members:
:private-members:
:special-members: __init__
:show-inheritance:

View File

@@ -1,82 +0,0 @@
# Configuration file for the Sphinx documentation builder.
#
# This file only contains a selection of the most common options. For a full
# list see the documentation:
# https://www.sphinx-doc.org/en/master/usage/configuration.html
# -- Path setup --------------------------------------------------------------
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
#
import os
import sys
sys.path.insert(0, os.path.abspath(os.path.dirname(os.path.dirname(__file__))))
# -- Project information -----------------------------------------------------
project = 'nonebot'
copyright = '2020, richardchien'
author = 'richardchien'
# The short X.Y version
version = '2.0.0'
# The full version, including alpha/beta/rc tags
release = '2.0.0'
# -- General configuration ---------------------------------------------------
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = [
'sphinx.ext.autodoc',
'sphinx.ext.viewcode',
'sphinx.ext.todo',
]
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
#
# This is also used if you do content translation via gettext catalogs.
# Usually you set "language" from the command line for these cases.
language = 'zh_CN'
master_doc = "README"
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
# This pattern also affects html_static_path and html_extra_path.
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
# -- Options for HTML output -------------------------------------------------
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
#
html_theme = 'alabaster'
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['_static']
# html_baseurl = '/api/'
# -- Extension configuration -------------------------------------------------
# -- Options for autodoc extension ----------------------------------------------
autodoc_default_options = {'member-order': 'bysource'}
autodoc_inherit_docstrings = False
autodoc_typehints = 'none'
# -- Options for todo extension ----------------------------------------------
# If true, `todo` and `todoList` produce output, else they produce nothing.
todo_include_todos = True

View File

@@ -1,10 +0,0 @@
\-\-\-
sidebar_position: 2
\-\-\-
NoneBot.config 模块
===================
.. automodule:: nonebot.config
:members: Env, Config
:show-inheritance:

View File

@@ -1,11 +0,0 @@
\-\-\-
sidebar_position: 8
\-\-\-
NoneBot.dependencies 模块
====================
.. automodule:: nonebot.dependencies
:members:
:private-members:
:show-inheritance:

View File

@@ -1,13 +0,0 @@
\-\-\-
id: index
slug: /api/drivers/
\-\-\-
NoneBot.drivers 模块
=====================
.. automodule:: nonebot.drivers
:members:
:private-members:
:special-members: __init__
:show-inheritance:

View File

@@ -1,9 +0,0 @@
NoneBot.drivers.aiohttp 模块
=============================
.. automodule:: nonebot.drivers.aiohttp
:members:
:private-members:
:show-inheritance:

View File

@@ -1,9 +0,0 @@
NoneBot.drivers.fastapi 模块
=============================
.. automodule:: nonebot.drivers.fastapi
:members:
:private-members:
:show-inheritance:

View File

@@ -1,9 +0,0 @@
NoneBot.drivers.httpx 模块
=============================
.. automodule:: nonebot.drivers.httpx
:members:
:private-members:
:show-inheritance:

View File

@@ -1,9 +0,0 @@
NoneBot.drivers.quart 模块
==========================
.. automodule:: nonebot.drivers.quart
:members:
:private-members:
:show-inheritance:

View File

@@ -1,9 +0,0 @@
NoneBot.drivers.websockets 模块
=============================
.. automodule:: nonebot.drivers.websockets
:members:
:private-members:
:show-inheritance:

View File

@@ -1,10 +0,0 @@
\-\-\-
sidebar_position: 12
\-\-\-
NoneBot.exception 模块
======================
.. automodule:: nonebot.exception
:members:
:show-inheritance:

View File

@@ -1,10 +0,0 @@
\-\-\-
sidebar_position: 9
\-\-\-
NoneBot.log 模块
=================
.. automodule:: nonebot.log
:members:
:show-inheritance:

View File

@@ -1,35 +0,0 @@
@ECHO OFF
pushd %~dp0
REM Command file for Sphinx documentation
if "%SPHINXBUILD%" == "" (
set SPHINXBUILD=sphinx-build
)
set SOURCEDIR=.
set BUILDDIR=_build
if "%1" == "" goto help
%SPHINXBUILD% >NUL 2>NUL
if errorlevel 9009 (
echo.
echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
echo.installed, then set the SPHINXBUILD environment variable to point
echo.to the full path of the 'sphinx-build' executable. Alternatively you
echo.may add the Sphinx directory to PATH.
echo.
echo.If you don't have Sphinx installed, grab it from
echo.http://sphinx-doc.org/
exit /b 1
)
%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
goto end
:help
%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
:end
popd

View File

@@ -1,12 +0,0 @@
\-\-\-
sidebar_position: 5
\-\-\-
NoneBot.matcher 模块
====================
.. automodule:: nonebot.matcher
:members:
:private-members:
:special-members: __init__
:show-inheritance:

View File

@@ -1,10 +0,0 @@
\-\-\-
sidebar_position: 4
\-\-\-
NoneBot.message 模块
======================
.. automodule:: nonebot.message
:members:
:show-inheritance:

View File

@@ -1,11 +0,0 @@
\-\-\-
sidebar_position: 7
\-\-\-
NoneBot.permission 模块
=======================
.. automodule:: nonebot.permission
:members:
:show-inheritance:
:special-members:

View File

@@ -1,31 +0,0 @@
\-\-\-
sidebar_position: 3
\-\-\-
NoneBot.plugin 模块
====================
.. automodule:: nonebot.plugin
:members:
:show-inheritance:
:special-members: __init__
.. automodule:: nonebot.plugin.plugin
:members:
:show-inheritance:
:special-members: __init__
.. automodule:: nonebot.plugin.on
:members:
:show-inheritance:
:special-members: __init__
.. automodule:: nonebot.plugin.load
:members:
:show-inheritance:
:special-members: __init__
.. automodule:: nonebot.plugin.export
:members:
:show-inheritance:
:special-members: __init__

View File

@@ -1,11 +0,0 @@
\-\-\-
sidebar_position: 6
\-\-\-
NoneBot.rule 模块
====================
.. automodule:: nonebot.rule
:members:
:special-members:
:show-inheritance:

View File

@@ -1,10 +0,0 @@
\-\-\-
sidebar_position: 11
\-\-\-
NoneBot.typing 模块
===================
.. automodule:: nonebot.typing
:members:
:show-inheritance:

View File

@@ -1,10 +0,0 @@
\-\-\-
sidebar_position: 10
\-\-\-
NoneBot.utils 模块
==================
.. automodule:: nonebot.utils
:members:
:show-inheritance:

View File

@@ -1,101 +1,168 @@
""" """本模块主要定义了 NoneBot 启动所需函数,供 bot 入口文件调用。
快捷导入
========
为方便使用,``nonebot`` 模块从子模块导入了部分内容 ## 快捷导入
- ``on_message`` => ``nonebot.plugin.on_message`` 为方便使用,本模块从子模块导入了部分内容,以下内容可以直接通过本模块导入:
- ``on_notice`` => ``nonebot.plugin.on_notice``
- ``on_request`` => ``nonebot.plugin.on_request`` - `on` => {ref}``on` <nonebot.plugin.on.on>`
- ``on_metaevent`` => ``nonebot.plugin.on_metaevent`` - `on_metaevent` => {ref}``on_metaevent` <nonebot.plugin.on.on_metaevent>`
- ``on_startswith`` => ``nonebot.plugin.on_startswith`` - `on_message` => {ref}``on_message` <nonebot.plugin.on.on_message>`
- ``on_endswith`` => ``nonebot.plugin.on_endswith`` - `on_notice` => {ref}``on_notice` <nonebot.plugin.on.on_notice>`
- ``on_keyword`` => ``nonebot.plugin.on_keyword`` - `on_request` => {ref}``on_request` <nonebot.plugin.on.on_request>`
- ``on_command`` => ``nonebot.plugin.on_command`` - `on_startswith` => {ref}``on_startswith` <nonebot.plugin.on.on_startswith>`
- ``on_shell_command`` => ``nonebot.plugin.on_shell_command`` - `on_endswith` => {ref}``on_endswith` <nonebot.plugin.on.on_endswith>`
- ``on_regex`` => ``nonebot.plugin.on_regex`` - `on_fullmatch` => {ref}``on_fullmatch` <nonebot.plugin.on.on_fullmatch>`
- ``CommandGroup`` => ``nonebot.plugin.CommandGroup`` - `on_keyword` => {ref}``on_keyword` <nonebot.plugin.on.on_keyword>`
- ``Matchergroup`` => ``nonebot.plugin.MatcherGroup`` - `on_command` => {ref}``on_command` <nonebot.plugin.on.on_command>`
- ``load_plugin`` => ``nonebot.plugin.load_plugin`` - `on_shell_command` => {ref}``on_shell_command` <nonebot.plugin.on.on_shell_command>`
- ``load_plugins`` => ``nonebot.plugin.load_plugins`` - `on_regex` => {ref}``on_regex` <nonebot.plugin.on.on_regex>`
- ``load_all_plugins`` => ``nonebot.plugin.load_all_plugins`` - `on_type` => {ref}``on_type` <nonebot.plugin.on.on_type>`
- ``load_from_json`` => ``nonebot.plugin.load_from_json`` - `CommandGroup` => {ref}``CommandGroup` <nonebot.plugin.on.CommandGroup>`
- ``load_from_toml`` => ``nonebot.plugin.load_from_toml`` - `Matchergroup` => {ref}``MatcherGroup` <nonebot.plugin.on.MatcherGroup>`
- ``load_builtin_plugins`` => ``nonebot.plugin.load_builtin_plugins`` - `load_plugin` => {ref}``load_plugin` <nonebot.plugin.load.load_plugin>`
- ``get_plugin`` => ``nonebot.plugin.get_plugin`` - `load_plugins` => {ref}``load_plugins` <nonebot.plugin.load.load_plugins>`
- ``get_loaded_plugins`` => ``nonebot.plugin.get_loaded_plugins`` - `load_all_plugins` => {ref}``load_all_plugins` <nonebot.plugin.load.load_all_plugins>`
- ``export`` => ``nonebot.plugin.export`` - `load_from_json` => {ref}``load_from_json` <nonebot.plugin.load.load_from_json>`
- ``require`` => ``nonebot.plugin.require`` - `load_from_toml` => {ref}``load_from_toml` <nonebot.plugin.load.load_from_toml>`
- `load_builtin_plugin` =>
{ref}``load_builtin_plugin` <nonebot.plugin.load.load_builtin_plugin>`
- `load_builtin_plugins` =>
{ref}``load_builtin_plugins` <nonebot.plugin.load.load_builtin_plugins>`
- `get_plugin` => {ref}``get_plugin` <nonebot.plugin.get_plugin>`
- `get_plugin_by_module_name` =>
{ref}``get_plugin_by_module_name` <nonebot.plugin.get_plugin_by_module_name>`
- `get_loaded_plugins` =>
{ref}``get_loaded_plugins` <nonebot.plugin.get_loaded_plugins>`
- `get_available_plugin_names` =>
{ref}``get_available_plugin_names` <nonebot.plugin.get_available_plugin_names>`
- `require` => {ref}``require` <nonebot.plugin.load.require>`
FrontMatter:
sidebar_position: 0
description: nonebot 模块
""" """
import importlib import os
from typing import Any, Dict, Type, Optional from importlib.metadata import version
from typing import Any, Dict, Type, Union, TypeVar, Optional, overload
import pkg_resources import loguru
from pydantic.env_settings import DotenvType
from nonebot.adapters import Bot
from nonebot.utils import escape_tag
from nonebot.config import Env, Config from nonebot.config import Env, Config
from nonebot.log import logger, default_filter from nonebot.log import logger as logger
from nonebot.adapters import Bot, Adapter
from nonebot.utils import escape_tag, resolve_dot_notation
from nonebot.drivers import Driver, ReverseDriver, combine_driver from nonebot.drivers import Driver, ReverseDriver, combine_driver
try: try:
_dist: pkg_resources.Distribution = pkg_resources.get_distribution("nonebot2") __version__ = version("nonebot2")
__version__ = _dist.version except Exception: # pragma: no cover
VERSION = _dist.parsed_version
except pkg_resources.DistributionNotFound: # pragma: no cover
__version__ = None __version__ = None
VERSION = None
A = TypeVar("A", bound=Adapter)
_driver: Optional[Driver] = None _driver: Optional[Driver] = None
def get_driver() -> Driver: def get_driver() -> Driver:
""" """获取全局 {ref}`nonebot.drivers.Driver` 实例。
:说明:
获取全局 Driver 对象。可用于在计划任务的回调中获取当前 Driver 对象 可用于在计划任务的回调等情形中获取当前 {ref}`nonebot.drivers.Driver` 实例
:返回: 返回:
全局 {ref}`nonebot.drivers.Driver` 对象
* ``Driver``: 全局 Driver 对象 异常:
ValueError: 全局 {ref}`nonebot.drivers.Driver` 对象尚未初始化
:异常: ({ref}`nonebot.init <nonebot.init>` 尚未调用)
* ``ValueError``: 全局 Driver 对象尚未初始化 (nonebot.init 尚未调用)
:用法:
.. code-block:: python
用法:
```python
driver = nonebot.get_driver() driver = nonebot.get_driver()
```
""" """
if _driver is None: if _driver is None:
raise ValueError("NoneBot has not been initialized.") raise ValueError("NoneBot has not been initialized.")
return _driver return _driver
def get_app() -> Any: @overload
def get_adapter(name: str) -> Adapter:
""" """
:说明: 参数:
name: 适配器名称
获取全局 Driver 对应 Server App 对象。 返回:
指定名称的 {ref}`nonebot.adapters.Adapter` 对象
"""
:返回:
* ``Any``: Server App 对象 @overload
def get_adapter(name: Type[A]) -> A:
"""
参数:
name: 适配器类型
:异常: 返回:
指定类型的 {ref}`nonebot.adapters.Adapter` 对象
"""
* ``ValueError``: 全局 Driver 对象尚未初始化 (nonebot.init 尚未调用)
:用法: def get_adapter(name: Union[str, Type[Adapter]]) -> Adapter:
"""获取已注册的 {ref}`nonebot.adapters.Adapter` 实例。
.. code-block:: python 异常:
ValueError: 指定的 {ref}`nonebot.adapters.Adapter` 未注册
ValueError: 全局 {ref}`nonebot.drivers.Driver` 对象尚未初始化
({ref}`nonebot.init <nonebot.init>` 尚未调用)
用法:
```python
from nonebot.adapters.console import Adapter
adapter = nonebot.get_adapter(Adapter)
```
"""
adapters = get_adapters()
target = name if isinstance(name, str) else name.get_name()
if target not in adapters:
raise ValueError(f"Adapter {target} not registered.")
return adapters[target]
def get_adapters() -> Dict[str, Adapter]:
"""获取所有已注册的 {ref}`nonebot.adapters.Adapter` 实例。
返回:
所有 {ref}`nonebot.adapters.Adapter` 实例字典
异常:
ValueError: 全局 {ref}`nonebot.drivers.Driver` 对象尚未初始化
({ref}`nonebot.init <nonebot.init>` 尚未调用)
用法:
```python
adapters = nonebot.get_adapters()
```
"""
return get_driver()._adapters.copy()
def get_app() -> Any:
"""获取全局 {ref}`nonebot.drivers.ReverseDriver` 对应的 Server App 对象。
返回:
Server App 对象
异常:
AssertionError: 全局 Driver 对象不是 {ref}`nonebot.drivers.ReverseDriver` 类型
ValueError: 全局 {ref}`nonebot.drivers.Driver` 对象尚未初始化
({ref}`nonebot.init <nonebot.init>` 尚未调用)
用法:
```python
app = nonebot.get_app() app = nonebot.get_app()
```
""" """
driver = get_driver() driver = get_driver()
assert isinstance( assert isinstance(
@@ -105,25 +172,21 @@ def get_app() -> Any:
def get_asgi() -> Any: def get_asgi() -> Any:
""" """获取全局 {ref}`nonebot.drivers.ReverseDriver` 对应
:说明: [ASGI](https://asgi.readthedocs.io/) 对象。
获取全局 Driver 对应 Asgi 对象。 返回:
ASGI 对象
:返回: 异常:
AssertionError: 全局 Driver 对象不是 {ref}`nonebot.drivers.ReverseDriver` 类型
* ``Any``: Asgi 对象 ValueError: 全局 {ref}`nonebot.drivers.Driver` 对象尚未初始化
({ref}`nonebot.init <nonebot.init>` 尚未调用)
:异常:
* ``ValueError``: 全局 Driver 对象尚未初始化 (nonebot.init 尚未调用)
:用法:
.. code-block:: python
用法:
```python
asgi = nonebot.get_asgi() asgi = nonebot.get_asgi()
```
""" """
driver = get_driver() driver = get_driver()
assert isinstance( assert isinstance(
@@ -133,32 +196,30 @@ def get_asgi() -> Any:
def get_bot(self_id: Optional[str] = None) -> Bot: def get_bot(self_id: Optional[str] = None) -> Bot:
""" """获取一个连接到 NoneBot 的 {ref}`nonebot.adapters.Bot` 对象。
:说明:
当提供 self_id 时,此函数是 get_bots()[self_id] 的简写;当不提供时,返回一个 Bot。 当提供 `self_id` 时,此函数是 `get_bots()[self_id]` 的简写;
当不提供时,返回一个 {ref}`nonebot.adapters.Bot`。
:参数: 参数:
self_id: 用来识别 {ref}`nonebot.adapters.Bot` 的
{ref}`nonebot.adapters.Bot.self_id` 属性
* ``self_id: Optional[str]``: 用来识别 Bot 的 ID 返回:
{ref}`nonebot.adapters.Bot` 对象
:返回: 异常:
KeyError: 对应 self_id 的 Bot 不存在
ValueError: 没有传入 self_id 且没有 Bot 可用
ValueError: 全局 {ref}`nonebot.drivers.Driver` 对象尚未初始化
({ref}`nonebot.init <nonebot.init>` 尚未调用)
* ``Bot``: Bot 对象 用法:
```python
:异常: assert nonebot.get_bot("12345") == nonebot.get_bots()["12345"]
* ``KeyError``: 对应 ID 的 Bot 不存在
* ``ValueError``: 全局 Driver 对象尚未初始化 (nonebot.init 尚未调用)
* ``ValueError``: 没有传入 ID 且没有 Bot 可用
:用法:
.. code-block:: python
assert nonebot.get_bot('12345') == nonebot.get_bots()['12345']
another_unspecified_bot = nonebot.get_bot() another_unspecified_bot = nonebot.get_bot()
```
""" """
bots = get_bots() bots = get_bots()
if self_id is not None: if self_id is not None:
@@ -171,98 +232,80 @@ def get_bot(self_id: Optional[str] = None) -> Bot:
def get_bots() -> Dict[str, Bot]: def get_bots() -> Dict[str, Bot]:
""" """获取所有连接到 NoneBot 的 {ref}`nonebot.adapters.Bot` 对象。
:说明:
获取所有通过 ws 连接 NoneBot 的 Bot 对象。 返回:
一个以 {ref}`nonebot.adapters.Bot.self_id` 为键
{ref}`nonebot.adapters.Bot` 对象为值的字典
:返回: 异常:
ValueError: 全局 {ref}`nonebot.drivers.Driver` 对象尚未初始化
* ``Dict[str, Bot]``: 一个以字符串 ID 为键Bot 对象为值的字典 ({ref}`nonebot.init <nonebot.init>` 尚未调用)
:异常:
* ``ValueError``: 全局 Driver 对象尚未初始化 (nonebot.init 尚未调用)
:用法:
.. code-block:: python
用法:
```python
bots = nonebot.get_bots() bots = nonebot.get_bots()
```
""" """
driver = get_driver() return get_driver().bots
return driver.bots
def _resolve_dot_notation(
obj_str: str, default_attr: str, default_prefix: Optional[str] = None
) -> Any:
modulename, _, cls = obj_str.partition(":")
if default_prefix is not None and modulename.startswith("~"):
modulename = default_prefix + modulename[1:]
module = importlib.import_module(modulename)
if not cls:
return getattr(module, default_attr)
instance = module
for attr_str in cls.split("."):
instance = getattr(instance, attr_str)
return instance
def _resolve_combine_expr(obj_str: str) -> Type[Driver]: def _resolve_combine_expr(obj_str: str) -> Type[Driver]:
drivers = obj_str.split("+") drivers = obj_str.split("+")
DriverClass = _resolve_dot_notation( DriverClass = resolve_dot_notation(
drivers[0], "Driver", default_prefix="nonebot.drivers." drivers[0], "Driver", default_prefix="nonebot.drivers."
) )
if len(drivers) == 1: if len(drivers) == 1:
logger.trace(f"Detected driver {DriverClass} with no mixins.") logger.trace(f"Detected driver {DriverClass} with no mixins.")
return DriverClass return DriverClass
mixins = [ mixins = [
_resolve_dot_notation(mixin, "Mixin", default_prefix="nonebot.drivers.") resolve_dot_notation(mixin, "Mixin", default_prefix="nonebot.drivers.")
for mixin in drivers[1:] for mixin in drivers[1:]
] ]
logger.trace(f"Detected driver {DriverClass} with mixins {mixins}.") logger.trace(f"Detected driver {DriverClass} with mixins {mixins}.")
return combine_driver(DriverClass, *mixins) return combine_driver(DriverClass, *mixins)
def init(*, _env_file: Optional[str] = None, **kwargs): def _log_patcher(record: "loguru.Record"):
""" record["name"] = (
:说明: plugin.name
if (module_name := record["name"])
and (plugin := get_plugin_by_module_name(module_name))
else (module_name and module_name.split(".")[0])
)
初始化 NoneBot 以及 全局 Driver 对象。
NoneBot 将会从 .env 文件中读取环境信息,并使用相应的 env 文件配置。 def init(*, _env_file: Optional[DotenvType] = None, **kwargs: Any) -> None:
"""初始化 NoneBot 以及 全局 {ref}`nonebot.drivers.Driver` 对象。
你也可以传入自定义的 _env_file 来指定 NoneBot 从该文件读取配置。 NoneBot 将会从 .env 文件读取环境信息,并使用相应的 env 文件配置。
:参数: 也可以传入自定义的 `_env_file` 来指定 NoneBot 从该文件读取配置。
* ``_env_file: Optional[str]``: 配置文件名,默认从 .env.{env_name} 中读取配置 参数:
* ``**kwargs``: 任意变量,将会存储到 Config 对象里 _env_file: 配置文件名,默认从 `.env.{env_name}` 中读取配置
kwargs: 任意变量,将会存储到 {ref}`nonebot.drivers.Driver.config` 对象里
:返回:
- ``None``
:用法:
.. code-block:: python
用法:
```python
nonebot.init(database=Database(...)) nonebot.init(database=Database(...))
```
""" """
global _driver global _driver
if not _driver: if not _driver:
logger.success("NoneBot is initializing...") logger.success("NoneBot is initializing...")
env = Env() env = Env()
_env_file = _env_file or f".env.{env.environment}"
config = Config( config = Config(
**kwargs, **kwargs,
_common_config=env.dict(), _env_file=(".env", _env_file)
_env_file=_env_file or f".env.{env.environment}", if isinstance(_env_file, (str, os.PathLike))
else _env_file,
) )
default_filter.level = config.log_level logger.configure(
extra={"nonebot_log_level": config.log_level}, patcher=_log_patcher
)
logger.opt(colors=True).info( logger.opt(colors=True).info(
f"Current <y><b>Env: {escape_tag(env.environment)}</b></y>" f"Current <y><b>Env: {escape_tag(env.environment)}</b></y>"
) )
@@ -270,38 +313,28 @@ def init(*, _env_file: Optional[str] = None, **kwargs):
f"Loaded <y><b>Config</b></y>: {escape_tag(str(config.dict()))}" f"Loaded <y><b>Config</b></y>: {escape_tag(str(config.dict()))}"
) )
DriverClass: Type[Driver] = _resolve_combine_expr(config.driver) DriverClass = _resolve_combine_expr(config.driver)
_driver = DriverClass(env, config) _driver = DriverClass(env, config)
def run(*args: Any, **kwargs: Any) -> None: def run(*args: Any, **kwargs: Any) -> None:
""" """启动 NoneBot即运行全局 {ref}`nonebot.drivers.Driver` 对象。
:说明:
启动 NoneBot即运行全局 Driver 对象。 参数:
args: 传入 {ref}`nonebot.drivers.Driver.run` 的位置参数
:参数: kwargs: 传入 {ref}`nonebot.drivers.Driver.run` 的命名参数
* ``*args``: 传入 Driver.run 的位置参数
* ``**kwargs``: 传入 Driver.run 的命名参数
:返回:
- ``None``
:用法:
.. code-block:: python
用法:
```python
nonebot.run(host="127.0.0.1", port=8080) nonebot.run(host="127.0.0.1", port=8080)
```
""" """
logger.success("Running NoneBot...") logger.success("Running NoneBot...")
get_driver().run(*args, **kwargs) get_driver().run(*args, **kwargs)
import nonebot.params as params from nonebot.plugin import on as on
from nonebot.plugin import export as export from nonebot.plugin import on_type as on_type
from nonebot.plugin import require as require from nonebot.plugin import require as require
from nonebot.plugin import on_regex as on_regex from nonebot.plugin import on_regex as on_regex
from nonebot.plugin import on_notice as on_notice from nonebot.plugin import on_notice as on_notice
@@ -315,6 +348,7 @@ from nonebot.plugin import on_endswith as on_endswith
from nonebot.plugin import CommandGroup as CommandGroup from nonebot.plugin import CommandGroup as CommandGroup
from nonebot.plugin import MatcherGroup as MatcherGroup from nonebot.plugin import MatcherGroup as MatcherGroup
from nonebot.plugin import load_plugins as load_plugins from nonebot.plugin import load_plugins as load_plugins
from nonebot.plugin import on_fullmatch as on_fullmatch
from nonebot.plugin import on_metaevent as on_metaevent from nonebot.plugin import on_metaevent as on_metaevent
from nonebot.plugin import on_startswith as on_startswith from nonebot.plugin import on_startswith as on_startswith
from nonebot.plugin import load_from_json as load_from_json from nonebot.plugin import load_from_json as load_from_json
@@ -322,4 +356,9 @@ from nonebot.plugin import load_from_toml as load_from_toml
from nonebot.plugin import load_all_plugins as load_all_plugins from nonebot.plugin import load_all_plugins as load_all_plugins
from nonebot.plugin import on_shell_command as on_shell_command from nonebot.plugin import on_shell_command as on_shell_command
from nonebot.plugin import get_loaded_plugins as get_loaded_plugins from nonebot.plugin import get_loaded_plugins as get_loaded_plugins
from nonebot.plugin import load_builtin_plugin as load_builtin_plugin
from nonebot.plugin import load_builtin_plugins as load_builtin_plugins from nonebot.plugin import load_builtin_plugins as load_builtin_plugins
from nonebot.plugin import get_plugin_by_module_name as get_plugin_by_module_name
from nonebot.plugin import get_available_plugin_names as get_available_plugin_names
__autodoc__ = {"internal": False}

View File

@@ -1,28 +1,29 @@
""" """本模块定义了协议适配基类,各协议请继承以下基类。
协议适配基类
============
各协议请继承以下基类,并使用 ``driver.register_adapter`` 注册适配器 使用 {ref}`nonebot.drivers.Driver.register_adapter` 注册适配器
FrontMatter:
sidebar_position: 0
description: nonebot.adapters 模块
""" """
from typing import Iterable from nonebot.internal.adapter import Bot as Bot
from nonebot.internal.adapter import Event as Event
from nonebot.internal.adapter import Adapter as Adapter
from nonebot.internal.adapter import Message as Message
from nonebot.internal.adapter import MessageSegment as MessageSegment
from nonebot.internal.adapter import MessageTemplate as MessageTemplate
try: __autodoc__ = {
import pkg_resources "Bot": True,
"Event": True,
pkg_resources.declare_namespace(__name__) "Adapter": True,
del pkg_resources "Message": True,
except ImportError: "Message.__getitem__": True,
import pkgutil "Message.__contains__": True,
"Message._construct": True,
__path__: Iterable[str] = pkgutil.extend_path(__path__, __name__) # type: ignore "MessageSegment": True,
del pkgutil "MessageSegment.__str__": True,
except Exception: "MessageSegment.__add__": True,
pass "MessageTemplate": True,
}
from ._bot import Bot as Bot
from ._event import Event as Event
from ._adapter import Adapter as Adapter
from ._message import Message as Message
from ._message import MessageSegment as MessageSegment
from ._template import MessageTemplate as MessageTemplate

View File

@@ -1,137 +0,0 @@
import abc
from pydantic import BaseModel
from ._message import Message
from nonebot.utils import DataclassEncoder
class Event(abc.ABC, BaseModel):
"""Event 基类。提供获取关键信息的方法,其余信息可直接获取。"""
class Config:
extra = "allow"
json_encoders = {Message: DataclassEncoder}
@abc.abstractmethod
def get_type(self) -> str:
"""
:说明:
获取事件类型的方法,类型通常为 NoneBot 内置的四种类型。
:返回:
* ``Literal["message", "notice", "request", "meta_event"]``
* 其他自定义 ``str``
"""
raise NotImplementedError
@abc.abstractmethod
def get_event_name(self) -> str:
"""
:说明:
获取事件名称的方法。
:返回:
* ``str``
"""
raise NotImplementedError
@abc.abstractmethod
def get_event_description(self) -> str:
"""
:说明:
获取事件描述的方法,通常为事件具体内容。
:返回:
* ``str``
"""
raise NotImplementedError
def __str__(self) -> str:
return f"[{self.get_event_name()}]: {self.get_event_description()}"
def get_log_string(self) -> str:
"""
:说明:
获取事件日志信息的方法,通常你不需要修改这个方法,只有当希望 NoneBot 隐藏该事件日志时,可以抛出 ``NoLogException`` 异常。
:返回:
* ``str``
:异常:
- ``NoLogException``
"""
return f"[{self.get_event_name()}]: {self.get_event_description()}"
@abc.abstractmethod
def get_user_id(self) -> str:
"""
:说明:
获取事件主体 id 的方法,通常是用户 id 。
:返回:
* ``str``
"""
raise NotImplementedError
@abc.abstractmethod
def get_session_id(self) -> str:
"""
:说明:
获取会话 id 的方法,用于判断当前事件属于哪一个会话,通常是用户 id、群组 id 组合。
:返回:
* ``str``
"""
raise NotImplementedError
@abc.abstractmethod
def get_message(self) -> "Message":
"""
:说明:
获取事件消息内容的方法。
:返回:
* ``Message``
"""
raise NotImplementedError
def get_plaintext(self) -> str:
"""
:说明:
获取消息纯文本的方法,通常不需要修改,默认通过 ``get_message().extract_plain_text`` 获取。
:返回:
* ``str``
"""
return self.get_message().extract_plain_text()
@abc.abstractmethod
def is_tome(self) -> bool:
"""
:说明:
获取事件是否与机器人有关的方法。
:返回:
* ``bool``
"""
raise NotImplementedError

View File

@@ -1,232 +0,0 @@
import abc
from copy import deepcopy
from dataclasses import field, asdict, dataclass
from typing import (
Any,
Dict,
List,
Type,
Union,
Generic,
Mapping,
TypeVar,
Iterable,
)
from ._template import MessageTemplate
T = TypeVar("T")
TMS = TypeVar("TMS", covariant=True)
TM = TypeVar("TM", bound="Message")
@dataclass
class MessageSegment(Mapping, abc.ABC, Generic[TM]):
"""消息段基类"""
type: str
"""
- 类型: ``str``
- 说明: 消息段类型
"""
data: Dict[str, Any] = field(default_factory=lambda: {})
"""
- 类型: ``Dict[str, Union[str, list]]``
- 说明: 消息段数据
"""
@classmethod
@abc.abstractmethod
def get_message_class(cls) -> Type[TM]:
raise NotImplementedError
@abc.abstractmethod
def __str__(self) -> str:
"""该消息段所代表的 str在命令匹配部分使用"""
raise NotImplementedError
def __len__(self) -> int:
return len(str(self))
def __ne__(self: T, other: T) -> bool:
return not self == other
def __add__(self, other: Union[str, Mapping, Iterable[Mapping]]) -> TM:
return self.get_message_class()(self) + other # type: ignore
def __radd__(self, other: Union[str, Mapping, Iterable[Mapping]]) -> TM:
return self.get_message_class()(other) + self # type: ignore
def __getitem__(self, key: str):
return getattr(self, key)
def __setitem__(self, key: str, value: Any):
return setattr(self, key, value)
def __iter__(self):
yield from asdict(self).keys()
def __contains__(self, key: Any) -> bool:
return key in asdict(self).keys()
def get(self, key: str, default: Any = None):
return getattr(self, key, default)
def keys(self):
return asdict(self).keys()
def values(self):
return asdict(self).values()
def items(self):
return asdict(self).items()
def copy(self: T) -> T:
return deepcopy(self)
@abc.abstractmethod
def is_text(self) -> bool:
raise NotImplementedError
class Message(List[TMS], abc.ABC):
"""消息数组"""
def __init__(
self: TM,
message: Union[str, None, Mapping, Iterable[Mapping], TMS, TM, Any] = None,
*args,
**kwargs,
):
"""
:参数:
* ``message: Union[str, list, dict, MessageSegment, Message, Any]``: 消息内容
"""
super().__init__(*args, **kwargs)
if message is None:
return
elif isinstance(message, Message):
self.extend(message)
elif isinstance(message, MessageSegment):
self.append(message)
else:
self.extend(self._construct(message))
@classmethod
def template(cls: Type[TM], format_string: Union[str, TM]) -> MessageTemplate[TM]:
"""
:说明:
根据创建消息模板, 用法和 ``str.format`` 大致相同, 但是可以输出消息对象, 并且支持以 ``Message`` 对象作为消息模板
并且提供了拓展的格式化控制符, 可以用适用于该消息类型的 ``MessageSegment`` 的工厂方法创建消息
:示例:
.. code-block:: python
>>> Message.template("{} {}").format("hello", "world") # 基础演示
Message(MessageSegment(type='text', data={'text': 'hello world'}))
>>> Message.template("{} {}").format(MessageSegment.image("file///..."), "world") # 支持消息段等对象
Message(MessageSegment(type='image', data={'file': 'file///...'}), MessageSegment(type='text', data={'text': 'world'}))
>>> Message.template( # 支持以Message对象作为消息模板
... MessageSegment.text('test {event.user_id}') + MessageSegment.face(233) +
... MessageSegment.text('test {event.message}')).format(event={'user_id':123456, 'message':'hello world'})
Message(MessageSegment(type='text', data={'text': 'test 123456'}),
MessageSegment(type='face', data={'face': 233}),
MessageSegment(type='text', data={'text': 'test hello world'}))
>>> Message.template("{link:image}").format(link='https://...') # 支持拓展格式化控制符
Message(MessageSegment(type='image', data={'file': 'https://...'}))
:参数:
* ``format_string: str``: 格式化字符串
:返回:
- ``MessageFormatter[TM]``: 消息格式化器
"""
return MessageTemplate(format_string, cls)
@classmethod
@abc.abstractmethod
def get_segment_class(cls) -> Type[TMS]:
raise NotImplementedError
def __str__(self):
return "".join(str(seg) for seg in self)
@classmethod
def __get_validators__(cls):
yield cls._validate
@classmethod
def _validate(cls, value):
return cls(value)
@staticmethod
@abc.abstractmethod
def _construct(msg: Union[str, Mapping, Iterable[Mapping], Any]) -> Iterable[TMS]:
raise NotImplementedError
def __add__(self: TM, other: Union[str, Mapping, Iterable[Mapping]]) -> TM:
result = self.copy()
result += other
return result
def __radd__(self: TM, other: Union[str, Mapping, Iterable[Mapping]]) -> TM:
result = self.__class__(other) # type: ignore
return result + self
def __iadd__(self: TM, other: Union[str, Mapping, Iterable[Mapping]]) -> TM:
if isinstance(other, MessageSegment):
self.append(other)
elif isinstance(other, Message):
self.extend(other)
else:
self.extend(self._construct(other))
return self
def append(self: TM, obj: Union[str, TMS]) -> TM:
"""
:说明:
添加一个消息段到消息数组末尾
:参数:
* ``obj: Union[str, MessageSegment]``: 要添加的消息段
"""
if isinstance(obj, MessageSegment):
super(Message, self).append(obj)
elif isinstance(obj, str):
self.extend(self._construct(obj))
else:
raise ValueError(f"Unexpected type: {type(obj)} {obj}")
return self
def extend(self: TM, obj: Union[TM, Iterable[TMS]]) -> TM:
"""
:说明:
拼接一个消息数组或多个消息段到消息数组末尾
:参数:
* ``obj: Union[Message, Iterable[MessageSegment]]``: 要添加的消息数组
"""
for segment in obj:
self.append(segment)
return self
def copy(self: TM) -> TM:
return deepcopy(self)
def extract_plain_text(self: "Message[MessageSegment]") -> str:
"""
:说明:
提取消息内纯文本消息
"""
return "".join(str(seg) for seg in self if seg.is_text())

View File

@@ -1,115 +1,128 @@
"""本模块定义了 NoneBot 本身运行所需的配置项。
NoneBot 使用 [`pydantic`](https://pydantic-docs.helpmanual.io/) 以及
[`python-dotenv`](https://saurabh-kumar.com/python-dotenv/) 来读取配置。
配置项需符合特殊格式或 json 序列化格式
详情见 [`pydantic Field Type`](https://pydantic-docs.helpmanual.io/usage/types/) 文档。
FrontMatter:
sidebar_position: 1
description: nonebot.config 模块
""" """
配置
====
NoneBot 使用 `pydantic`_ 以及 `python-dotenv`_ 来读取配置。
配置项需符合特殊格式或 json 序列化格式。详情见 `pydantic Field Type`_ 文档。
.. _pydantic:
https://pydantic-docs.helpmanual.io/
.. _python-dotenv:
https://saurabh-kumar.com/python-dotenv/
.. _pydantic Field Type:
https://pydantic-docs.helpmanual.io/usage/types/
"""
import os import os
from pathlib import Path
from datetime import timedelta from datetime import timedelta
from ipaddress import IPv4Address from ipaddress import IPv4Address
from typing import Any, Set, Dict, Tuple, Union, Mapping, Optional from typing import TYPE_CHECKING, Any, Set, Dict, Tuple, Union, Mapping, Optional
from pydantic import BaseSettings, IPvAnyAddress from pydantic.utils import deep_update
from pydantic.fields import Undefined, UndefinedType
from pydantic import Extra, Field, BaseSettings, IPvAnyAddress
from pydantic.env_settings import ( from pydantic.env_settings import (
DotenvType,
SettingsError, SettingsError,
EnvSettingsSource, EnvSettingsSource,
InitSettingsSource, InitSettingsSource,
SettingsSourceCallable, SettingsSourceCallable,
read_env_file,
env_file_sentinel,
) )
from nonebot.log import logger from nonebot.log import logger
from nonebot.utils import escape_tag
class CustomEnvSettings(EnvSettingsSource): class CustomEnvSettings(EnvSettingsSource):
def __call__(self, settings: BaseSettings) -> Dict[str, Any]: def __call__(self, settings: BaseSettings) -> Dict[str, Any]:
""" """从环境变量和 dotenv 配置文件中读取配置项。"""
Build environment variables suitable for passing to the Model.
""" d: Dict[str, Any] = {}
d: Dict[str, Optional[str]] = {}
if settings.__config__.case_sensitive: if settings.__config__.case_sensitive:
env_vars: Mapping[str, Optional[str]] = os.environ # pragma: no cover env_vars: Mapping[str, Optional[str]] = os.environ # pragma: no cover
else: else:
env_vars = {k.lower(): v for k, v in os.environ.items()} env_vars = {k.lower(): v for k, v in os.environ.items()}
env_file_vars: Dict[str, Optional[str]] = {} env_file_vars = self._read_env_files(settings.__config__.case_sensitive)
env_file = ( env_vars = {**env_file_vars, **env_vars}
self.env_file
if self.env_file != env_file_sentinel
else settings.__config__.env_file
)
env_file_encoding = (
self.env_file_encoding
if self.env_file_encoding is not None
else settings.__config__.env_file_encoding
)
if env_file is not None:
env_path = Path(env_file)
if env_path.is_file():
env_file_vars = read_env_file(
env_path,
encoding=env_file_encoding,
case_sensitive=settings.__config__.case_sensitive,
)
env_vars = {**env_file_vars, **env_vars}
for field in settings.__fields__.values(): for field in settings.__fields__.values():
env_val: Optional[str] = None env_val: Union[str, None, UndefinedType] = Undefined
for env_name in field.field_info.extra["env_names"]: for env_name in field.field_info.extra["env_names"]:
env_val = env_vars.get(env_name) env_val = env_vars.get(env_name, Undefined)
if env_name in env_file_vars: if env_name in env_file_vars:
del env_file_vars[env_name] del env_file_vars[env_name]
if env_val is not None: if env_val is not Undefined:
break break
if env_val is None: is_complex, allow_parse_failure = self.field_is_complex(field)
continue if is_complex:
if isinstance(env_val, UndefinedType):
# field is complex but no value found so far, try explode_env_vars
if env_val_built := self.explode_env_vars(field, env_vars):
d[field.alias] = env_val_built
elif env_val is None:
d[field.alias] = env_val
else:
# field is complex and there's a value
# decode that as JSON, then add explode_env_vars
try:
env_val = settings.__config__.parse_env_var(field.name, env_val)
except ValueError as e:
if not allow_parse_failure:
raise SettingsError(
f'error parsing env var "{env_name}"' # type: ignore
) from e
if field.is_complex(): if isinstance(env_val, dict):
try: d[field.alias] = deep_update(
env_val = settings.__config__.json_loads(env_val) env_val, self.explode_env_vars(field, env_vars)
except ValueError as e: # pragma: no cover )
raise SettingsError( else:
f'error parsing JSON for "{env_name}"' # type: ignore d[field.alias] = env_val
) from e elif not isinstance(env_val, UndefinedType):
d[field.alias] = env_val # simplest case, field is not complex
# we only need to add the value if it was found
d[field.alias] = env_val
if env_file_vars: # remain user custom config
for env_name, env_val in env_file_vars.items(): for env_name in env_file_vars:
if (env_val is None or len(env_val) == 0) and env_name in env_vars: env_val = env_vars[env_name]
env_val = env_vars[env_name] if env_val and (val_striped := env_val.strip()):
# there's a value, decode that as JSON
try: try:
if env_val: env_val = settings.__config__.parse_env_var(env_name, val_striped)
env_val = settings.__config__.json_loads(env_val.strip()) except ValueError:
except ValueError as e: logger.trace(
logger.opt(colors=True, exception=e).trace( "Error while parsing JSON for "
f"Error while parsing JSON for {escape_tag(env_name)}. Assumed as string." f"{env_name!r}={val_striped!r}. "
"Assumed as string."
) )
# explode value when it's a nested dict
env_name, *nested_keys = env_name.split(self.env_nested_delimiter)
if nested_keys and (env_name not in d or isinstance(d[env_name], dict)):
result = {}
*keys, last_key = nested_keys
_tmp = result
for key in keys:
_tmp = _tmp.setdefault(key, {})
_tmp[last_key] = env_val
d[env_name] = deep_update(d.get(env_name, {}), result)
elif not nested_keys:
d[env_name] = env_val d[env_name] = env_val
return d return d
class BaseConfig(BaseSettings): class BaseConfig(BaseSettings):
# dummy getattr for pylance checking, actually not used if TYPE_CHECKING:
def __getattr__(self, name: str) -> Any: # pragma: no cover # dummy getattr for pylance checking, actually not used
return self.__dict__.get(name) def __getattr__(self, name: str) -> Any: # pragma: no cover
return self.__dict__.get(name)
class Config: class Config:
extra = Extra.allow
env_nested_delimiter = "__"
@classmethod @classmethod
def customise_sources( def customise_sources(
cls, cls,
@@ -121,7 +134,10 @@ class BaseConfig(BaseSettings):
return ( return (
init_settings, init_settings,
CustomEnvSettings( CustomEnvSettings(
env_settings.env_file, env_settings.env_file_encoding env_settings.env_file,
env_settings.env_file_encoding,
env_settings.env_nested_delimiter,
env_settings.env_prefix_len,
), ),
InitSettingsSource(common_config), InitSettingsSource(common_config),
file_secret_settings, file_secret_settings,
@@ -129,159 +145,106 @@ class BaseConfig(BaseSettings):
class Env(BaseConfig): class Env(BaseConfig):
""" """运行环境配置。大小写不敏感。
运行环境配置。大小写不敏感。
将会从 ``nonebot.init 参数`` > ``环境变量`` > ``.env 环境配置文件`` 的优先级读取配置 将会从 **环境变量** > **dotenv 配置文件** 的优先级读取环境信息
""" """
environment: str = "prod" environment: str = "prod"
""" """当前环境名。
- **类型**: ``str``
- **默认值**: ``"prod"``
:说明: NoneBot 将从 `.env.{environment}` 文件中加载配置。
当前环境名。 NoneBot 将从 ``.env.{environment}`` 文件中加载配置。
""" """
class Config: class Config:
extra = "allow"
env_file = ".env" env_file = ".env"
class Config(BaseConfig): class Config(BaseConfig):
""" """NoneBot 主要配置。大小写不敏感。
NoneBot 主要配置。大小写不敏感。
除了 NoneBot 的配置项外,还可以自行添加配置项到 ``.env.{environment}`` 文件中。 除了 NoneBot 的配置项外,还可以自行添加配置项到 `.env.{environment}` 文件中。
这些配置将会在 json 反序列化后一起带入 ``Config`` 类中。 这些配置将会在 json 反序列化后一起带入 `Config` 类中。
配置方法参考: [配置](https://nonebot.dev/docs/appendices/config)
""" """
_common_config: dict _env_file: DotenvType = ".env", ".env.prod"
_env_file: str
# nonebot configs # nonebot configs
driver: str = "~fastapi" driver: str = "~fastapi"
""" """NoneBot 运行所使用的 `Driver` 。继承自 {ref}`nonebot.drivers.Driver` 。
- **类型**: ``str``
- **默认值**: ``"~fastapi"``
:说明: 配置格式为 `<module>[:<Driver>][+<module>[:<Mixin>]]*`。
NoneBot 运行所使用的 ``Driver`` 。继承自 ``nonebot.drivers.Driver`` `~` 为 `nonebot.drivers.` 的缩写
配置格式为 ``<module>[:<Driver>][+<module>[:<Mixin>]]*``。 配置方法参考: [配置驱动器](https://nonebot.dev/docs/advanced/driver#%E9%85%8D%E7%BD%AE%E9%A9%B1%E5%8A%A8%E5%99%A8)
``~`` 为 ``nonebot.drivers.`` 的缩写。
""" """
host: IPvAnyAddress = IPv4Address("127.0.0.1") # type: ignore host: IPvAnyAddress = IPv4Address("127.0.0.1") # type: ignore
""" """NoneBot {ref}`nonebot.drivers.ReverseDriver` 服务端监听的 IP/主机名。"""
- **类型**: ``IPvAnyAddress`` port: int = Field(default=8080, ge=1, le=65535)
- **默认值**: ``127.0.0.1`` """NoneBot {ref}`nonebot.drivers.ReverseDriver` 服务端监听的端口。"""
:说明:
NoneBot 的 HTTP 和 WebSocket 服务端监听的 IP/主机名。
"""
port: int = 8080
"""
- **类型**: ``int``
- **默认值**: ``8080``
:说明:
NoneBot 的 HTTP 和 WebSocket 服务端监听的端口。
"""
log_level: Union[int, str] = "INFO" log_level: Union[int, str] = "INFO"
""" """NoneBot 日志输出等级,可以为 `int` 类型等级或等级名称。
- **类型**: ``Union[int, str]``
- **默认值**: ``INFO``
:说明: 参考 [记录日志](https://nonebot.dev/docs/appendices/log)[loguru 日志等级](https://loguru.readthedocs.io/en/stable/api/logger.html#levels)。
配置 NoneBot 日志输出等级,可以为 ``int`` 类型等级或等级名称,参考 `loguru 日志等级`_。 :::tip 提示
日志等级名称应为大写,如 `INFO`。
:示例: :::
.. code-block:: default
用法:
```conf
LOG_LEVEL=25 LOG_LEVEL=25
LOG_LEVEL=INFO LOG_LEVEL=INFO
```
.. _loguru 日志等级:
https://loguru.readthedocs.io/en/stable/api/logger.html#levels
""" """
# bot connection configs # bot connection configs
api_timeout: Optional[float] = 30.0 api_timeout: Optional[float] = 30.0
""" """API 请求超时时间,单位: 秒。"""
- **类型**: ``Optional[float]``
- **默认值**: ``30.``
:说明:
API 请求超时时间,单位: 秒。
"""
# bot runtime configs # bot runtime configs
superusers: Set[str] = set() superusers: Set[str] = set()
""" """机器人超级用户。
- **类型**: ``Set[str]``
- **默认值**: ``set()``
:说明:
机器人超级用户。
:示例:
.. code-block:: default
用法:
```conf
SUPERUSERS=["12345789"] SUPERUSERS=["12345789"]
```
""" """
nickname: Set[str] = set() nickname: Set[str] = set()
""" """机器人昵称。"""
- **类型**: ``Set[str]``
- **默认值**: ``set()``
:说明:
机器人昵称。
"""
command_start: Set[str] = {"/"} command_start: Set[str] = {"/"}
""" """命令的起始标记,用于判断一条消息是不是命令。
- **类型**: ``Set[str]``
- **默认值**: ``{"/"}``
:说明: 参考[命令响应规则](https://nonebot.dev/docs/advanced/matcher#command)。
命令的起始标记,用于判断一条消息是不是命令。 用法:
```conf
COMMAND_START=["/", ""]
```
""" """
command_sep: Set[str] = {"."} command_sep: Set[str] = {"."}
""" """命令的分隔标记,用于将文本形式的命令切分为元组(实际的命令名)。
- **类型**: ``Set[str]``
- **默认值**: ``{"."}``
:说明: 参考[命令响应规则](https://nonebot.dev/docs/advanced/matcher#command)。
命令的分隔标记,用于将文本形式的命令切分为元组(实际的命令名)。 用法:
```conf
COMMAND_SEP=["."]
```
""" """
session_expire_timeout: timedelta = timedelta(minutes=2) session_expire_timeout: timedelta = timedelta(minutes=2)
""" """等待用户回复的超时时间。
- **类型**: ``timedelta``
- **默认值**: ``timedelta(minutes=2)``
:说明:
等待用户回复的超时时间。
:示例:
.. code-block:: default
用法:
```conf
SESSION_EXPIRE_TIMEOUT=120 # 单位: 秒 SESSION_EXPIRE_TIMEOUT=120 # 单位: 秒
SESSION_EXPIRE_TIMEOUT=[DD ][HH:MM]SS[.ffffff] SESSION_EXPIRE_TIMEOUT=[DD ][HH:MM]SS[.ffffff]
SESSION_EXPIRE_TIMEOUT=P[DD]DT[HH]H[MM]M[SS]S # ISO 8601 SESSION_EXPIRE_TIMEOUT=P[DD]DT[HH]H[MM]M[SS]S # ISO 8601
```
""" """
# adapter configs # adapter configs
@@ -292,5 +255,10 @@ class Config(BaseConfig):
# or from env file using json loads # or from env file using json loads
class Config: class Config:
extra = "allow" env_file = ".env", ".env.prod"
env_file = ".env.prod"
__autodoc__ = {
"CustomEnvSettings": False,
"BaseConfig": False,
}

View File

@@ -1,20 +1,55 @@
"""本模块包含了 NoneBot 事件处理过程中使用到的常量。
FrontMatter:
sidebar_position: 9
description: nonebot.consts 模块
"""
import os
import sys
from typing import Literal
# used by Matcher # used by Matcher
RECEIVE_KEY = "_receive_{id}" RECEIVE_KEY: Literal["_receive_{id}"] = "_receive_{id}"
LAST_RECEIVE_KEY = "_last_receive" """`receive` 存储 key"""
ARG_KEY = "{key}" LAST_RECEIVE_KEY: Literal["_last_receive"] = "_last_receive"
REJECT_TARGET = "_current_target" """`last_receive` 存储 key"""
REJECT_CACHE_TARGET = "_next_target" ARG_KEY: Literal["{key}"] = "{key}"
"""`arg` 存储 key"""
REJECT_TARGET: Literal["_current_target"] = "_current_target"
"""当前 `reject` 目标存储 key"""
REJECT_CACHE_TARGET: Literal["_next_target"] = "_next_target"
"""下一个 `reject` 目标存储 key"""
# used by Rule # used by Rule
PREFIX_KEY = "_prefix" PREFIX_KEY: Literal["_prefix"] = "_prefix"
"""命令前缀存储 key"""
CMD_KEY = "command" CMD_KEY: Literal["command"] = "command"
RAW_CMD_KEY = "raw_command" """命令元组存储 key"""
CMD_ARG_KEY = "command_arg" RAW_CMD_KEY: Literal["raw_command"] = "raw_command"
"""命令文本存储 key"""
CMD_ARG_KEY: Literal["command_arg"] = "command_arg"
"""命令参数存储 key"""
CMD_START_KEY: Literal["command_start"] = "command_start"
"""命令开头存储 key"""
CMD_WHITESPACE_KEY: Literal["command_whitespace"] = "command_whitespace"
"""命令与参数间空白符存储 key"""
SHELL_ARGS = "_args" SHELL_ARGS: Literal["_args"] = "_args"
SHELL_ARGV = "_argv" """shell 命令 parse 后参数字典存储 key"""
SHELL_ARGV: Literal["_argv"] = "_argv"
"""shell 命令原始参数列表存储 key"""
REGEX_MATCHED = "_matched" REGEX_MATCHED: Literal["_matched"] = "_matched"
REGEX_GROUP = "_matched_groups" """正则匹配结果存储 key"""
REGEX_DICT = "_matched_dict" STARTSWITH_KEY: Literal["_startswith"] = "_startswith"
"""响应触发前缀 key"""
ENDSWITH_KEY: Literal["_endswith"] = "_endswith"
"""响应触发后缀 key"""
FULLMATCH_KEY: Literal["_fullmatch"] = "_fullmatch"
"""响应触发完整消息 key"""
KEYWORD_KEY: Literal["_keyword"] = "_keyword"
"""响应触发关键字 key"""
WINDOWS = sys.platform.startswith("win") or (sys.platform == "cli" and os.name == "nt")

View File

@@ -1,145 +1,151 @@
""" """本模块模块实现了依赖注入的定义与处理。
依赖注入处理模块
================
该模块实现了依赖注入的定义与处理。 FrontMatter:
sidebar_position: 0
description: nonebot.dependencies 模块
""" """
import abc import abc
import asyncio
import inspect import inspect
from typing import Any, Dict, List, Type, Generic, TypeVar, Callable, Optional from dataclasses import field, dataclass
from typing import (
Any,
Dict,
List,
Type,
Tuple,
Generic,
TypeVar,
Callable,
Iterable,
Optional,
Awaitable,
cast,
)
from pydantic import BaseConfig from pydantic import BaseConfig
from pydantic.schema import get_annotation_from_field_info from pydantic.schema import get_annotation_from_field_info
from pydantic.fields import Required, FieldInfo, Undefined, ModelField from pydantic.fields import Required, FieldInfo, Undefined, ModelField
from nonebot.log import logger from nonebot.log import logger
from .utils import get_typed_signature from nonebot.typing import _DependentCallable
from nonebot.exception import TypeMisMatch from nonebot.exception import SkippedException
from nonebot.utils import run_sync, is_coroutine_callable from nonebot.utils import run_sync, is_coroutine_callable
T = TypeVar("T", bound="Dependent") from .utils import check_field_type, get_typed_signature
R = TypeVar("R") R = TypeVar("R")
T = TypeVar("T", bound="Dependent")
class Param(abc.ABC, FieldInfo): class Param(abc.ABC, FieldInfo):
"""依赖注入的基本单元 —— 参数。
继承自 `pydantic.fields.FieldInfo`,用于描述参数信息(不包括参数名)。
"""
@classmethod @classmethod
def _check_param( def _check_param(
cls, dependent: "Dependent", name: str, param: inspect.Parameter cls, param: inspect.Parameter, allow_types: Tuple[Type["Param"], ...]
) -> Optional["Param"]: ) -> Optional["Param"]:
return None return
@classmethod @classmethod
def _check_parameterless( def _check_parameterless(
cls, dependent: "Dependent", value: Any cls, value: Any, allow_types: Tuple[Type["Param"], ...]
) -> Optional["Param"]: ) -> Optional["Param"]:
return None return
@abc.abstractmethod @abc.abstractmethod
async def _solve(self, **kwargs: Any) -> Any: async def _solve(self, **kwargs: Any) -> Any:
raise NotImplementedError raise NotImplementedError
async def _check(self, **kwargs: Any) -> None:
return
class CustomConfig(BaseConfig): class CustomConfig(BaseConfig):
arbitrary_types_allowed = True arbitrary_types_allowed = True
@dataclass(frozen=True)
class Dependent(Generic[R]): class Dependent(Generic[R]):
def __init__( """依赖注入容器
self,
*, 参数:
call: Callable[..., Any], call: 依赖注入的可调用对象,可以是任何 Callable 对象
pre_checkers: Optional[List[Param]] = None, pre_checkers: 依赖注入解析前的参数检查
params: Optional[List[ModelField]] = None, params: 具名参数列表
parameterless: Optional[List[Param]] = None, parameterless: 匿名参数列表
allow_types: Optional[List[Type[Param]]] = None, allow_types: 允许的参数类型
) -> None: """
self.call = call
self.pre_checkers = pre_checkers or [] call: _DependentCallable[R]
self.params = params or [] params: Tuple[ModelField, ...] = field(default_factory=tuple)
self.parameterless = parameterless or [] parameterless: Tuple[Param, ...] = field(default_factory=tuple)
self.allow_types = allow_types or []
def __repr__(self) -> str: def __repr__(self) -> str:
if inspect.isfunction(self.call) or inspect.isclass(self.call):
call_str = self.call.__name__
else:
call_str = repr(self.call)
return ( return (
f"<Dependent call={self.call}, params={self.params}," f"Dependent(call={call_str}"
f" parameterless={self.parameterless}>" + (f", parameterless={self.parameterless}" if self.parameterless else "")
+ ")"
) )
def __str__(self) -> str:
return self.__repr__()
async def __call__(self, **kwargs: Any) -> R: async def __call__(self, **kwargs: Any) -> R:
# do pre-check
await self.check(**kwargs)
# solve param values
values = await self.solve(**kwargs) values = await self.solve(**kwargs)
# call function
if is_coroutine_callable(self.call): if is_coroutine_callable(self.call):
return await self.call(**values) return await cast(Callable[..., Awaitable[R]], self.call)(**values)
else: else:
return await run_sync(self.call)(**values) return await run_sync(cast(Callable[..., R], self.call))(**values)
def parse_param(self, name: str, param: inspect.Parameter) -> Param: @staticmethod
for allow_type in self.allow_types: def parse_params(
field_info = allow_type._check_param(self, name, param) call: _DependentCallable[R], allow_types: Tuple[Type[Param], ...]
if field_info: ) -> Tuple[ModelField]:
return field_info fields: List[ModelField] = []
else: params = get_typed_signature(call).parameters.values()
raise ValueError(
f"Unknown parameter {name} for function {self.call} with type {param.annotation}"
)
def parse_parameterless(self, value: Any) -> Param: for param in params:
for allow_type in self.allow_types:
field_info = allow_type._check_parameterless(self, value)
if field_info:
return field_info
else:
raise ValueError(
f"Unknown parameterless {value} for function {self.call} with type {type(value)}"
)
def prepend_parameterless(self, value: Any) -> None:
self.parameterless.insert(0, self.parse_parameterless(value))
def append_parameterless(self, value: Any) -> None:
self.parameterless.append(self.parse_parameterless(value))
@classmethod
def parse(
cls: Type[T],
*,
call: Callable[..., Any],
parameterless: Optional[List[Any]] = None,
allow_types: Optional[List[Type[Param]]] = None,
) -> T:
signature = get_typed_signature(call)
params = signature.parameters
dependent = cls(
call=call,
allow_types=allow_types,
)
for param_name, param in params.items():
default_value = Required default_value = Required
if param.default != param.empty: if param.default != param.empty:
default_value = param.default default_value = param.default
if isinstance(default_value, Param): if isinstance(default_value, Param):
field_info = default_value field_info = default_value
default_value = field_info.default
else: else:
field_info = dependent.parse_param(param_name, param) for allow_type in allow_types:
default_value = field_info.default if field_info := allow_type._check_param(param, allow_types):
break
else:
raise ValueError(
f"Unknown parameter {param.name} "
f"for function {call} with type {param.annotation}"
)
default_value = field_info.default
annotation: Any = Any annotation: Any = Any
required = default_value == Required required = default_value == Required
if param.annotation != param.empty: if param.annotation != param.empty:
annotation = param.annotation annotation = param.annotation
annotation = get_annotation_from_field_info( annotation = get_annotation_from_field_info(
annotation, field_info, param_name annotation, field_info, param.name
) )
dependent.params.append(
fields.append(
ModelField( ModelField(
name=param_name, name=param.name,
type_=annotation, type_=annotation,
class_validators=None, class_validators=None,
model_config=CustomConfig, model_config=CustomConfig,
@@ -149,46 +155,72 @@ class Dependent(Generic[R]):
) )
) )
parameterless_params = [ return tuple(fields)
dependent.parse_parameterless(param) for param in (parameterless or [])
]
dependent.parameterless.extend(parameterless_params)
logger.trace( @staticmethod
f"Parsed dependent with call={call}, " def parse_parameterless(
f"params={[param.field_info for param in dependent.params]}, " parameterless: Tuple[Any, ...], allow_types: Tuple[Type[Param], ...]
f"parameterless={dependent.parameterless}" ) -> Tuple[Param, ...]:
parameterless_params: List[Param] = []
for value in parameterless:
for allow_type in allow_types:
if param := allow_type._check_parameterless(value, allow_types):
break
else:
raise ValueError(f"Unknown parameterless {value}")
parameterless_params.append(param)
return tuple(parameterless_params)
@classmethod
def parse(
cls,
*,
call: _DependentCallable[R],
parameterless: Optional[Iterable[Any]] = None,
allow_types: Iterable[Type[Param]],
) -> "Dependent[R]":
allow_types = tuple(allow_types)
params = cls.parse_params(call, allow_types)
parameterless_params = (
()
if parameterless is None
else cls.parse_parameterless(tuple(parameterless), allow_types)
) )
return dependent return cls(call, params, parameterless_params)
async def solve( async def check(self, **params: Any) -> None:
self, try:
**params: Any, await asyncio.gather(
) -> Dict[str, Any]: *(param._check(**params) for param in self.parameterless)
values: Dict[str, Any] = {} )
await asyncio.gather(
*(
cast(Param, param.field_info)._check(**params)
for param in self.params
)
)
except SkippedException as e:
logger.trace(f"{self} skipped due to {e}")
raise
for checker in self.pre_checkers: async def _solve_field(self, field: ModelField, params: Dict[str, Any]) -> Any:
await checker._solve(**params) value = await cast(Param, field.field_info)._solve(**params)
if value is Undefined:
value = field.get_default()
return check_field_type(field, value)
async def solve(self, **params: Any) -> Dict[str, Any]:
# solve parameterless
for param in self.parameterless: for param in self.parameterless:
await param._solve(**params) await param._solve(**params)
for field in self.params: # solve param values
field_info = field.field_info values = await asyncio.gather(
assert isinstance(field_info, Param), "Params must be subclasses of Param" *(self._solve_field(field, params) for field in self.params)
value = await field_info._solve(**params) )
if value == Undefined: return {field.name: value for field, value in zip(self.params, values)}
value = field.get_default()
_, errs_ = field.validate(value, values, loc=(str(field_info), field.alias))
if errs_:
logger.debug(
f"{field_info} "
f"type {type(value)} not match depends {self.call} "
f"annotation {field._type_display()}, ignored"
)
raise TypeMisMatch(field, value)
else:
values[field.name] = value
return values
__autodoc__ = {"CustomConfig": False}

View File

@@ -1,11 +1,24 @@
"""
FrontMatter:
sidebar_position: 1
description: nonebot.dependencies.utils 模块
"""
import inspect import inspect
from typing import Any, Dict, Callable from typing import Any, Dict, TypeVar, Callable, ForwardRef
from loguru import logger from loguru import logger
from pydantic.typing import ForwardRef, evaluate_forwardref from pydantic.fields import ModelField
from pydantic.typing import evaluate_forwardref
from nonebot.exception import TypeMisMatch
V = TypeVar("V")
def get_typed_signature(call: Callable[..., Any]) -> inspect.Signature: def get_typed_signature(call: Callable[..., Any]) -> inspect.Signature:
"""获取可调用对象签名"""
signature = inspect.signature(call) signature = inspect.signature(call)
globalns = getattr(call, "__globals__", {}) globalns = getattr(call, "__globals__", {})
typed_params = [ typed_params = [
@@ -17,11 +30,12 @@ def get_typed_signature(call: Callable[..., Any]) -> inspect.Signature:
) )
for param in signature.parameters.values() for param in signature.parameters.values()
] ]
typed_signature = inspect.Signature(typed_params) return inspect.Signature(typed_params)
return typed_signature
def get_typed_annotation(param: inspect.Parameter, globalns: Dict[str, Any]) -> Any: def get_typed_annotation(param: inspect.Parameter, globalns: Dict[str, Any]) -> Any:
"""获取参数的类型注解"""
annotation = param.annotation annotation = param.annotation
if isinstance(annotation, str): if isinstance(annotation, str):
annotation = ForwardRef(annotation) annotation = ForwardRef(annotation)
@@ -33,3 +47,12 @@ def get_typed_annotation(param: inspect.Parameter, globalns: Dict[str, Any]) ->
) )
return inspect.Parameter.empty return inspect.Parameter.empty
return annotation return annotation
def check_field_type(field: ModelField, value: V) -> V:
"""检查字段类型是否匹配"""
_, errs_ = field.validate(value, {}, loc=())
if errs_:
raise TypeMisMatch(field, value)
return value

View File

@@ -1,297 +1,38 @@
""" """本模块定义了驱动适配器基类。
后端驱动适配基类
=================
各驱动请继承以下基类 各驱动请继承以下基类
FrontMatter:
sidebar_position: 0
description: nonebot.drivers 模块
""" """
import abc from nonebot.internal.driver import URL as URL
import asyncio from nonebot.internal.driver import Driver as Driver
from dataclasses import dataclass from nonebot.internal.driver import Cookies as Cookies
from contextlib import asynccontextmanager from nonebot.internal.driver import Request as Request
from typing import ( from nonebot.internal.driver import Response as Response
TYPE_CHECKING, from nonebot.internal.driver import WebSocket as WebSocket
Any, from nonebot.internal.driver import HTTPVersion as HTTPVersion
Set, from nonebot.internal.driver import ForwardMixin as ForwardMixin
Dict, from nonebot.internal.driver import ForwardDriver as ForwardDriver
Type, from nonebot.internal.driver import ReverseDriver as ReverseDriver
Callable, from nonebot.internal.driver import combine_driver as combine_driver
Awaitable, from nonebot.internal.driver import HTTPServerSetup as HTTPServerSetup
AsyncGenerator, from nonebot.internal.driver import WebSocketServerSetup as WebSocketServerSetup
)
from ._model import URL as URL __autodoc__ = {
from nonebot.log import logger "URL": True,
from nonebot.utils import escape_tag "Driver": True,
from ._model import Request as Request "Cookies": True,
from nonebot.config import Env, Config "Request": True,
from ._model import Response as Response "Response": True,
from ._model import WebSocket as WebSocket "WebSocket": True,
from ._model import HTTPVersion as HTTPVersion "HTTPVersion": True,
from nonebot.typing import T_BotConnectionHook, T_BotDisconnectionHook "ForwardMixin": True,
"ForwardDriver": True,
if TYPE_CHECKING: "ReverseDriver": True,
from nonebot.adapters import Bot, Adapter "combine_driver": True,
"HTTPServerSetup": True,
"WebSocketServerSetup": True,
class Driver(abc.ABC): }
"""
Driver 基类。
"""
_adapters: Dict[str, "Adapter"] = {}
"""
:类型: ``Dict[str, Adapter]``
:说明: 已注册的适配器列表
"""
_bot_connection_hook: Set[T_BotConnectionHook] = set()
"""
:类型: ``Set[T_BotConnectionHook]``
:说明: Bot 连接建立时执行的函数
"""
_bot_disconnection_hook: Set[T_BotDisconnectionHook] = set()
"""
:类型: ``Set[T_BotDisconnectionHook]``
:说明: Bot 连接断开时执行的函数
"""
def __init__(self, env: Env, config: Config):
"""
:参数:
* ``env: Env``: 包含环境信息的 Env 对象
* ``config: Config``: 包含配置信息的 Config 对象
"""
self.env: str = env.environment
"""
:类型: ``str``
:说明: 环境名称
"""
self.config: Config = config
"""
:类型: ``Config``
:说明: 配置对象
"""
self._clients: Dict[str, "Bot"] = {}
"""
:类型: ``Dict[str, Bot]``
:说明: 已连接的 Bot
"""
@property
def bots(self) -> Dict[str, "Bot"]:
"""
:类型:
``Dict[str, Bot]``
:说明:
获取当前所有已连接的 Bot
"""
return self._clients
def register_adapter(self, adapter: Type["Adapter"], **kwargs) -> None:
"""
:说明:
注册一个协议适配器
:参数:
* ``name: str``: 适配器名称,用于在连接时进行识别
* ``adapter: Type[Bot]``: 适配器 Class
* ``**kwargs``: 其他传递给适配器的参数
"""
name = adapter.get_name()
if name in self._adapters:
logger.opt(colors=True).debug(
f'Adapter "<y>{escape_tag(name)}</y>" already exists'
)
return
self._adapters[name] = adapter(self, **kwargs)
logger.opt(colors=True).debug(
f'Succeeded to load adapter "<y>{escape_tag(name)}</y>"'
)
@property
@abc.abstractmethod
def type(self) -> str:
"""驱动类型名称"""
raise NotImplementedError
@property
@abc.abstractmethod
def logger(self):
"""驱动专属 logger 日志记录器"""
raise NotImplementedError
@abc.abstractmethod
def run(self, *args, **kwargs):
"""
:说明:
启动驱动框架
:参数:
* ``*args``
* ``**kwargs``
"""
logger.opt(colors=True).debug(
f"<g>Loaded adapters: {escape_tag(', '.join(self._adapters))}</g>"
)
@abc.abstractmethod
def on_startup(self, func: Callable) -> Callable:
"""注册一个在驱动启动时运行的函数"""
raise NotImplementedError
@abc.abstractmethod
def on_shutdown(self, func: Callable) -> Callable:
"""注册一个在驱动停止时运行的函数"""
raise NotImplementedError
def on_bot_connect(self, func: T_BotConnectionHook) -> T_BotConnectionHook:
"""
:说明:
装饰一个函数使他在 bot 通过 WebSocket 连接成功时执行。
:函数参数:
* ``bot: Bot``: 当前连接上的 Bot 对象
"""
self._bot_connection_hook.add(func)
return func
def on_bot_disconnect(self, func: T_BotDisconnectionHook) -> T_BotDisconnectionHook:
"""
:说明:
装饰一个函数使他在 bot 通过 WebSocket 连接断开时执行。
:函数参数:
* ``bot: Bot``: 当前连接上的 Bot 对象
"""
self._bot_disconnection_hook.add(func)
return func
def _bot_connect(self, bot: "Bot") -> None:
"""在 WebSocket 连接成功后,调用该函数来注册 bot 对象"""
if bot.self_id in self._clients:
raise RuntimeError(f"Duplicate bot connection with id {bot.self_id}")
self._clients[bot.self_id] = bot
async def _run_hook(bot: "Bot") -> None:
coros = list(map(lambda x: x(bot), self._bot_connection_hook))
if coros:
try:
await asyncio.gather(*coros)
except Exception as e:
logger.opt(colors=True, exception=e).error(
"<r><bg #f8bbd0>Error when running WebSocketConnection hook. "
"Running cancelled!</bg #f8bbd0></r>"
)
asyncio.create_task(_run_hook(bot))
def _bot_disconnect(self, bot: "Bot") -> None:
"""在 WebSocket 连接断开后,调用该函数来注销 bot 对象"""
if bot.self_id in self._clients:
del self._clients[bot.self_id]
async def _run_hook(bot: "Bot") -> None:
coros = list(map(lambda x: x(bot), self._bot_disconnection_hook))
if coros:
try:
await asyncio.gather(*coros)
except Exception as e:
logger.opt(colors=True, exception=e).error(
"<r><bg #f8bbd0>Error when running WebSocketDisConnection hook. "
"Running cancelled!</bg #f8bbd0></r>"
)
asyncio.create_task(_run_hook(bot))
class ForwardMixin(abc.ABC):
@property
@abc.abstractmethod
def type(self) -> str:
raise NotImplementedError
@abc.abstractmethod
async def request(self, setup: Request) -> Response:
raise NotImplementedError
@abc.abstractmethod
@asynccontextmanager
async def websocket(self, setup: Request) -> AsyncGenerator[WebSocket, None]:
raise NotImplementedError
yield # used for static type checking's generator detection
class ForwardDriver(Driver, ForwardMixin):
"""
Forward Driver 基类。将客户端框架封装,以满足适配器使用。
"""
class ReverseDriver(Driver):
"""
Reverse Driver 基类。将后端框架封装,以满足适配器使用。
"""
@property
@abc.abstractmethod
def server_app(self) -> Any:
"""驱动 APP 对象"""
raise NotImplementedError
@property
@abc.abstractmethod
def asgi(self) -> Any:
"""驱动 ASGI 对象"""
raise NotImplementedError
@abc.abstractmethod
def setup_http_server(self, setup: "HTTPServerSetup") -> None:
raise NotImplementedError
@abc.abstractmethod
def setup_websocket_server(self, setup: "WebSocketServerSetup") -> None:
raise NotImplementedError
def combine_driver(driver: Type[Driver], *mixins: Type[ForwardMixin]) -> Type[Driver]:
# check first
assert issubclass(driver, Driver), "`driver` must be subclass of Driver"
assert all(
map(lambda m: issubclass(m, ForwardMixin), mixins)
), "`mixins` must be subclass of ForwardMixin"
class CombinedDriver(*mixins, driver, ForwardDriver): # type: ignore
@property
def type(self) -> str:
return (
driver.type.__get__(self)
+ "+"
+ "+".join(map(lambda x: x.type.__get__(self), mixins))
)
return CombinedDriver
@dataclass
class HTTPServerSetup:
path: URL # path should not be absolute, check it by URL.is_absolute() == False
method: str
name: str
handle_func: Callable[[Request], Awaitable[Response]]
@dataclass
class WebSocketServerSetup:
path: URL # path should not be absolute, check it by URL.is_absolute() == False
name: str
handle_func: Callable[[WebSocket], Awaitable[Any]]

View File

@@ -1,154 +0,0 @@
import signal
import asyncio
import threading
from typing import Set, Callable, Awaitable
from nonebot.log import logger
from nonebot.drivers import Driver
from nonebot.typing import overrides
from nonebot.config import Env, Config
STARTUP_FUNC = Callable[[], Awaitable[None]]
SHUTDOWN_FUNC = Callable[[], Awaitable[None]]
HANDLED_SIGNALS = (
signal.SIGINT, # Unix signal 2. Sent by Ctrl+C.
signal.SIGTERM, # Unix signal 15. Sent by `kill <pid>`.
)
class BlockDriver(Driver):
def __init__(self, env: Env, config: Config):
super().__init__(env, config)
self.startup_funcs: Set[STARTUP_FUNC] = set()
self.shutdown_funcs: Set[SHUTDOWN_FUNC] = set()
self.should_exit: asyncio.Event = asyncio.Event()
self.force_exit: bool = False
@property
@overrides(Driver)
def type(self) -> str:
"""驱动名称: ``block_driver``"""
return "block_driver"
@property
@overrides(Driver)
def logger(self):
"""block driver 使用的 logger"""
return logger
@overrides(Driver)
def on_startup(self, func: STARTUP_FUNC) -> STARTUP_FUNC:
"""
:说明:
注册一个启动时执行的函数
:参数:
* ``func: Callable[[], Awaitable[None]]``
"""
self.startup_funcs.add(func)
return func
@overrides(Driver)
def on_shutdown(self, func: SHUTDOWN_FUNC) -> SHUTDOWN_FUNC:
"""
:说明:
注册一个停止时执行的函数
:参数:
* ``func: Callable[[], Awaitable[None]]``
"""
self.shutdown_funcs.add(func)
return func
@overrides(Driver)
def run(self, *args, **kwargs):
"""启动 block driver"""
super().run(*args, **kwargs)
loop = asyncio.get_event_loop()
loop.run_until_complete(self.serve())
async def serve(self):
self.install_signal_handlers()
await self.startup()
if self.should_exit.is_set():
return
await self.main_loop()
await self.shutdown()
async def startup(self):
# run startup
cors = [startup() for startup in self.startup_funcs]
if cors:
try:
await asyncio.gather(*cors)
except Exception as e:
logger.opt(colors=True, exception=e).error(
"<r><bg #f8bbd0>Error when running startup function. "
"Ignored!</bg #f8bbd0></r>"
)
logger.info("Application startup completed.")
async def main_loop(self):
await self.should_exit.wait()
async def shutdown(self):
logger.info("Shutting down")
logger.info("Waiting for application shutdown.")
# run shutdown
cors = [shutdown() for shutdown in self.shutdown_funcs]
if cors:
try:
await asyncio.gather(*cors)
except Exception as e:
logger.opt(colors=True, exception=e).error(
"<r><bg #f8bbd0>Error when running shutdown function. "
"Ignored!</bg #f8bbd0></r>"
)
for task in asyncio.all_tasks():
if task is not asyncio.current_task() and not task.done():
task.cancel()
await asyncio.sleep(0.1)
tasks = [t for t in asyncio.all_tasks() if t is not asyncio.current_task()]
if tasks and not self.force_exit:
logger.info("Waiting for tasks to finish. (CTRL+C to force quit)")
while tasks and not self.force_exit:
await asyncio.sleep(0.1)
tasks = [t for t in asyncio.all_tasks() if t is not asyncio.current_task()]
for task in tasks:
task.cancel()
await asyncio.gather(*tasks, return_exceptions=True)
logger.info("Application shutdown complete.")
loop = asyncio.get_event_loop()
loop.stop()
def install_signal_handlers(self) -> None:
if threading.current_thread() is not threading.main_thread():
# Signals can only be listened to from the main thread.
return
loop = asyncio.get_event_loop()
try:
for sig in HANDLED_SIGNALS:
loop.add_signal_handler(sig, self.handle_exit, sig, None)
except NotImplementedError:
# Windows
for sig in HANDLED_SIGNALS:
signal.signal(sig, self.handle_exit)
def handle_exit(self, sig, frame):
if self.should_exit.is_set():
self.force_exit = True
else:
self.should_exit.set()

View File

@@ -0,0 +1,46 @@
from typing_extensions import TypeAlias
from typing import Any, List, Union, Callable, Awaitable, cast
from nonebot.utils import run_sync, is_coroutine_callable
SYNC_LIFESPAN_FUNC: TypeAlias = Callable[[], Any]
ASYNC_LIFESPAN_FUNC: TypeAlias = Callable[[], Awaitable[Any]]
LIFESPAN_FUNC: TypeAlias = Union[SYNC_LIFESPAN_FUNC, ASYNC_LIFESPAN_FUNC]
class Lifespan:
def __init__(self) -> None:
self._startup_funcs: List[LIFESPAN_FUNC] = []
self._shutdown_funcs: List[LIFESPAN_FUNC] = []
def on_startup(self, func: LIFESPAN_FUNC) -> LIFESPAN_FUNC:
self._startup_funcs.append(func)
return func
def on_shutdown(self, func: LIFESPAN_FUNC) -> LIFESPAN_FUNC:
self._shutdown_funcs.append(func)
return func
@staticmethod
async def _run_lifespan_func(
funcs: List[LIFESPAN_FUNC],
) -> None:
for func in funcs:
if is_coroutine_callable(func):
await cast(ASYNC_LIFESPAN_FUNC, func)()
else:
await run_sync(cast(SYNC_LIFESPAN_FUNC, func))()
async def startup(self) -> None:
if self._startup_funcs:
await self._run_lifespan_func(self._startup_funcs)
async def shutdown(self) -> None:
if self._shutdown_funcs:
await self._run_lifespan_func(self._shutdown_funcs)
async def __aenter__(self) -> None:
await self.startup()
async def __aexit__(self, exc_type, exc_val, exc_tb) -> None:
await self.shutdown()

View File

@@ -1,35 +1,48 @@
""" """[AIOHTTP](https://aiohttp.readthedocs.io/en/stable/) 驱动适配器。
AIOHTTP 驱动适配
================
```bash
nb driver install aiohttp
# 或者
pip install nonebot2[aiohttp]
```
:::tip 提示
本驱动仅支持客户端连接 本驱动仅支持客户端连接
:::
FrontMatter:
sidebar_position: 2
description: nonebot.drivers.aiohttp 模块
""" """
from typing import AsyncGenerator from typing_extensions import override
from typing import Type, AsyncGenerator
from contextlib import asynccontextmanager from contextlib import asynccontextmanager
from nonebot.typing import overrides
from nonebot.drivers import Request, Response from nonebot.drivers import Request, Response
from nonebot.exception import WebSocketClosed from nonebot.exception import WebSocketClosed
from nonebot.drivers._block_driver import BlockDriver from nonebot.drivers.none import Driver as NoneDriver
from nonebot.drivers import WebSocket as BaseWebSocket from nonebot.drivers import WebSocket as BaseWebSocket
from nonebot.drivers import HTTPVersion, ForwardMixin, combine_driver from nonebot.drivers import HTTPVersion, ForwardMixin, ForwardDriver, combine_driver
try: try:
import aiohttp import aiohttp
except ImportError: except ModuleNotFoundError as e: # pragma: no cover
raise ImportError( raise ImportError(
"Please install aiohttp first to use this driver. `pip install nonebot2[aiohttp]`" "Please install aiohttp first to use this driver. "
) from None "Install with pip: `pip install nonebot2[aiohttp]`"
) from e
class Mixin(ForwardMixin): class Mixin(ForwardMixin):
"""AIOHTTP Mixin"""
@property @property
@overrides(ForwardMixin) @override
def type(self) -> str: def type(self) -> str:
return "aiohttp" return "aiohttp"
@overrides(ForwardMixin) @override
async def request(self, setup: Request) -> Response: async def request(self, setup: Request) -> Response:
if setup.version == HTTPVersion.H10: if setup.version == HTTPVersion.H10:
version = aiohttp.HttpVersion10 version = aiohttp.HttpVersion10
@@ -39,30 +52,36 @@ class Mixin(ForwardMixin):
raise RuntimeError(f"Unsupported HTTP version: {setup.version}") raise RuntimeError(f"Unsupported HTTP version: {setup.version}")
timeout = aiohttp.ClientTimeout(setup.timeout) timeout = aiohttp.ClientTimeout(setup.timeout)
files = None
data = setup.data
if setup.files: if setup.files:
files = aiohttp.FormData() data = aiohttp.FormData(data or {})
for name, file in setup.files: for name, file in setup.files:
files.add_field(name, file[1], content_type=file[2], filename=file[0]) data.add_field(name, file[1], content_type=file[2], filename=file[0])
async with aiohttp.ClientSession(version=version, trust_env=True) as session:
cookies = {
cookie.name: cookie.value for cookie in setup.cookies if cookie.value
}
async with aiohttp.ClientSession(
cookies=cookies, version=version, trust_env=True
) as session:
async with session.request( async with session.request(
setup.method, setup.method,
setup.url, setup.url,
data=setup.content or setup.data or files, data=setup.content or data,
json=setup.json, json=setup.json,
headers=setup.headers, headers=setup.headers,
timeout=timeout, timeout=timeout,
proxy=setup.proxy, proxy=setup.proxy,
) as response: ) as response:
res = Response( return Response(
response.status, response.status,
headers=response.headers.copy(), headers=response.headers.copy(),
content=await response.read(), content=await response.read(),
request=setup, request=setup,
) )
return res
@overrides(ForwardMixin) @override
@asynccontextmanager @asynccontextmanager
async def websocket(self, setup: Request) -> AsyncGenerator["WebSocket", None]: async def websocket(self, setup: Request) -> AsyncGenerator["WebSocket", None]:
if setup.version == HTTPVersion.H10: if setup.version == HTTPVersion.H10:
@@ -80,11 +99,12 @@ class Mixin(ForwardMixin):
headers=setup.headers, headers=setup.headers,
proxy=setup.proxy, proxy=setup.proxy,
) as ws: ) as ws:
websocket = WebSocket(request=setup, session=session, websocket=ws) yield WebSocket(request=setup, session=session, websocket=ws)
yield websocket
class WebSocket(BaseWebSocket): class WebSocket(BaseWebSocket):
"""AIOHTTP Websocket Wrapper"""
def __init__( def __init__(
self, self,
*, *,
@@ -97,15 +117,15 @@ class WebSocket(BaseWebSocket):
self.websocket = websocket self.websocket = websocket
@property @property
@overrides(BaseWebSocket) @override
def closed(self): def closed(self):
return self.websocket.closed return self.websocket.closed
@overrides(BaseWebSocket) @override
async def accept(self): async def accept(self):
raise NotImplementedError raise NotImplementedError
@overrides(BaseWebSocket) @override
async def close(self, code: int = 1000): async def close(self, code: int = 1000):
await self.websocket.close(code=code) await self.websocket.close(code=code)
await self.session.close() await self.session.close()
@@ -116,27 +136,41 @@ class WebSocket(BaseWebSocket):
raise WebSocketClosed(self.websocket.close_code or 1006) raise WebSocketClosed(self.websocket.close_code or 1006)
return msg return msg
@overrides(BaseWebSocket) @override
async def receive(self) -> str: async def receive(self) -> str:
msg = await self._receive() msg = await self._receive()
if msg.type != aiohttp.WSMsgType.TEXT: if msg.type not in (aiohttp.WSMsgType.TEXT, aiohttp.WSMsgType.BINARY):
raise TypeError(f"WebSocket received unexpected frame type: {msg.type}") raise TypeError(
f"WebSocket received unexpected frame type: {msg.type}, {msg.data!r}"
)
return msg.data return msg.data
@overrides(BaseWebSocket) @override
async def receive_bytes(self) -> bytes: async def receive_text(self) -> str:
msg = await self._receive() msg = await self._receive()
if msg.type != aiohttp.WSMsgType.TEXT: if msg.type != aiohttp.WSMsgType.TEXT:
raise TypeError(f"WebSocket received unexpected frame type: {msg.type}") raise TypeError(
f"WebSocket received unexpected frame type: {msg.type}, {msg.data!r}"
)
return msg.data return msg.data
@overrides(BaseWebSocket) @override
async def send(self, data: str) -> None: async def receive_bytes(self) -> bytes:
msg = await self._receive()
if msg.type != aiohttp.WSMsgType.BINARY:
raise TypeError(
f"WebSocket received unexpected frame type: {msg.type}, {msg.data!r}"
)
return msg.data
@override
async def send_text(self, data: str) -> None:
await self.websocket.send_str(data) await self.websocket.send_str(data)
@overrides(BaseWebSocket) @override
async def send_bytes(self, data: bytes) -> None: async def send_bytes(self, data: bytes) -> None:
await self.websocket.send_bytes(data) await self.websocket.send_bytes(data)
Driver = combine_driver(BlockDriver, Mixin) Driver: Type[ForwardDriver] = combine_driver(NoneDriver, Mixin) # type: ignore
"""AIOHTTP Driver"""

View File

@@ -1,34 +1,50 @@
"""[FastAPI](https://fastapi.tiangolo.com/) 驱动适配
```bash
nb driver install fastapi
# 或者
pip install nonebot2[fastapi]
```
:::tip 提示
本驱动仅支持服务端连接
:::
FrontMatter:
sidebar_position: 1
description: nonebot.drivers.fastapi 模块
""" """
FastAPI 驱动适配
================
本驱动同时支持服务端以及客户端连接
后端使用方法请参考: `FastAPI 文档`_
.. _FastAPI 文档:
https://fastapi.tiangolo.com/
"""
import logging import logging
import contextlib
from functools import wraps from functools import wraps
from typing import Any, List, Tuple, Callable, Optional from typing_extensions import override
from typing import Any, Dict, List, Tuple, Union, Optional
import uvicorn
from pydantic import BaseSettings from pydantic import BaseSettings
from fastapi.responses import Response
from fastapi import FastAPI, Request, UploadFile, status
from starlette.websockets import WebSocket, WebSocketState, WebSocketDisconnect
from ._model import FileTypes
from nonebot.config import Env from nonebot.config import Env
from nonebot.typing import overrides
from nonebot.exception import WebSocketClosed from nonebot.exception import WebSocketClosed
from nonebot.internal.driver import FileTypes
from nonebot.config import Config as NoneBotConfig from nonebot.config import Config as NoneBotConfig
from nonebot.drivers import Request as BaseRequest from nonebot.drivers import Request as BaseRequest
from nonebot.drivers import WebSocket as BaseWebSocket from nonebot.drivers import WebSocket as BaseWebSocket
from nonebot.drivers import ReverseDriver, HTTPServerSetup, WebSocketServerSetup from nonebot.drivers import ReverseDriver, HTTPServerSetup, WebSocketServerSetup
from ._lifespan import LIFESPAN_FUNC, Lifespan
try:
import uvicorn
from fastapi.responses import Response
from fastapi import FastAPI, Request, UploadFile, status
from starlette.websockets import WebSocket, WebSocketState, WebSocketDisconnect
except ModuleNotFoundError as e: # pragma: no cover
raise ImportError(
"Please install FastAPI first to use this driver. "
"Install with pip: `pip install nonebot2[fastapi]`"
) from e
def catch_closed(func): def catch_closed(func):
@wraps(func) @wraps(func)
@@ -37,139 +53,83 @@ def catch_closed(func):
return await func(*args, **kwargs) return await func(*args, **kwargs)
except WebSocketDisconnect as e: except WebSocketDisconnect as e:
raise WebSocketClosed(e.code) raise WebSocketClosed(e.code)
except KeyError:
raise TypeError("WebSocket received unexpected frame type")
return decorator return decorator
class Config(BaseSettings): class Config(BaseSettings):
""" """FastAPI 驱动框架设置,详情参考 FastAPI 文档"""
FastAPI 驱动框架设置,详情参考 FastAPI 文档
"""
fastapi_openapi_url: Optional[str] = None fastapi_openapi_url: Optional[str] = None
""" """`openapi.json` 地址,默认为 `None` 即关闭"""
:类型:
``Optional[str]``
:说明:
``openapi.json`` 地址,默认为 ``None`` 即关闭
"""
fastapi_docs_url: Optional[str] = None fastapi_docs_url: Optional[str] = None
""" """`swagger` 地址,默认为 `None` 即关闭"""
:类型:
``Optional[str]``
:说明:
``swagger`` 地址,默认为 ``None`` 即关闭
"""
fastapi_redoc_url: Optional[str] = None fastapi_redoc_url: Optional[str] = None
""" """`redoc` 地址,默认为 `None` 即关闭"""
:类型: fastapi_include_adapter_schema: bool = True
"""是否包含适配器路由的 schema默认为 `True`"""
``Optional[str]``
:说明:
``redoc`` 地址,默认为 ``None`` 即关闭
"""
fastapi_reload: bool = False fastapi_reload: bool = False
""" """开启/关闭冷重载"""
:类型:
``bool``
:说明:
开启/关闭冷重载
"""
fastapi_reload_dirs: Optional[List[str]] = None fastapi_reload_dirs: Optional[List[str]] = None
""" """重载监控文件夹列表,默认为 uvicorn 默认值"""
:类型: fastapi_reload_delay: float = 0.25
"""重载延迟,默认为 uvicorn 默认值"""
``Optional[List[str]]``
:说明:
重载监控文件夹列表,默认为 uvicorn 默认值
"""
fastapi_reload_delay: Optional[float] = None
"""
:类型:
``Optional[float]``
:说明:
重载延迟,默认为 uvicorn 默认值
"""
fastapi_reload_includes: Optional[List[str]] = None fastapi_reload_includes: Optional[List[str]] = None
""" """要监听的文件列表,支持 glob pattern默认为 uvicorn 默认值"""
:类型:
``Optional[List[str]]``
:说明:
要监听的文件列表,支持 glob pattern默认为 uvicorn 默认值
"""
fastapi_reload_excludes: Optional[List[str]] = None fastapi_reload_excludes: Optional[List[str]] = None
""" """不要监听的文件列表,支持 glob pattern默认为 uvicorn 默认值"""
:类型: fastapi_extra: Dict[str, Any] = {}
"""传递给 `FastAPI` 的其他参数。"""
``Optional[List[str]]``
:说明:
不要监听的文件列表,支持 glob pattern默认为 uvicorn 默认值
"""
class Config: class Config:
extra = "ignore" extra = "ignore"
class Driver(ReverseDriver): class Driver(ReverseDriver):
"""FastAPI 驱动框架。包含反向 Server 功能。""" """FastAPI 驱动框架。"""
def __init__(self, env: Env, config: NoneBotConfig): def __init__(self, env: Env, config: NoneBotConfig):
super(Driver, self).__init__(env, config) super().__init__(env, config)
self.fastapi_config: Config = Config(**config.dict()) self.fastapi_config: Config = Config(**config.dict())
self._lifespan = Lifespan()
self._server_app = FastAPI( self._server_app = FastAPI(
lifespan=self._lifespan_manager,
openapi_url=self.fastapi_config.fastapi_openapi_url, openapi_url=self.fastapi_config.fastapi_openapi_url,
docs_url=self.fastapi_config.fastapi_docs_url, docs_url=self.fastapi_config.fastapi_docs_url,
redoc_url=self.fastapi_config.fastapi_redoc_url, redoc_url=self.fastapi_config.fastapi_redoc_url,
**self.fastapi_config.fastapi_extra,
) )
@property @property
@overrides(ReverseDriver) @override
def type(self) -> str: def type(self) -> str:
"""驱动名称: ``fastapi``""" """驱动名称: `fastapi`"""
return "fastapi" return "fastapi"
@property @property
@overrides(ReverseDriver) @override
def server_app(self) -> FastAPI: def server_app(self) -> FastAPI:
"""``FastAPI APP`` 对象""" """`FastAPI APP` 对象"""
return self._server_app return self._server_app
@property @property
@overrides(ReverseDriver) @override
def asgi(self) -> FastAPI: def asgi(self) -> FastAPI:
"""``FastAPI APP`` 对象""" """`FastAPI APP` 对象"""
return self._server_app return self._server_app
@property @property
@overrides(ReverseDriver) @override
def logger(self) -> logging.Logger: def logger(self) -> logging.Logger:
"""fastapi 使用的 logger""" """fastapi 使用的 logger"""
return logging.getLogger("fastapi") return logging.getLogger("fastapi")
@overrides(ReverseDriver) @override
def setup_http_server(self, setup: HTTPServerSetup): def setup_http_server(self, setup: HTTPServerSetup):
async def _handle(request: Request) -> Response: async def _handle(request: Request) -> Response:
return await self._handle_http(request, setup) return await self._handle_http(request, setup)
@@ -179,9 +139,10 @@ class Driver(ReverseDriver):
_handle, _handle,
name=setup.name, name=setup.name,
methods=[setup.method], methods=[setup.method],
include_in_schema=self.fastapi_config.fastapi_include_adapter_schema,
) )
@overrides(ReverseDriver) @override
def setup_websocket_server(self, setup: WebSocketServerSetup) -> None: def setup_websocket_server(self, setup: WebSocketServerSetup) -> None:
async def _handle(websocket: WebSocket) -> None: async def _handle(websocket: WebSocket) -> None:
await self._handle_ws(websocket, setup) await self._handle_ws(websocket, setup)
@@ -192,17 +153,23 @@ class Driver(ReverseDriver):
name=setup.name, name=setup.name,
) )
@overrides(ReverseDriver) @override
def on_startup(self, func: Callable) -> Callable: def on_startup(self, func: LIFESPAN_FUNC) -> LIFESPAN_FUNC:
"""参考文档: `Events <https://fastapi.tiangolo.com/advanced/events/#startup-event>`_""" return self._lifespan.on_startup(func)
return self.server_app.on_event("startup")(func)
@overrides(ReverseDriver) @override
def on_shutdown(self, func: Callable) -> Callable: def on_shutdown(self, func: LIFESPAN_FUNC) -> LIFESPAN_FUNC:
"""参考文档: `Events <https://fastapi.tiangolo.com/advanced/events/#startup-event>`_""" return self._lifespan.on_shutdown(func)
return self.server_app.on_event("shutdown")(func)
@overrides(ReverseDriver) @contextlib.asynccontextmanager
async def _lifespan_manager(self, app: FastAPI):
await self._lifespan.startup()
try:
yield
finally:
await self._lifespan.shutdown()
@override
def run( def run(
self, self,
host: Optional[str] = None, host: Optional[str] = None,
@@ -211,7 +178,7 @@ class Driver(ReverseDriver):
app: Optional[str] = None, app: Optional[str] = None,
**kwargs, **kwargs,
): ):
"""使用 ``uvicorn`` 启动 FastAPI""" """使用 `uvicorn` 启动 FastAPI"""
super().run(host, port, app, **kwargs) super().run(host, port, app, **kwargs)
LOGGING_CONFIG = { LOGGING_CONFIG = {
"version": 1, "version": 1,
@@ -248,14 +215,12 @@ class Driver(ReverseDriver):
setup: HTTPServerSetup, setup: HTTPServerSetup,
) -> Response: ) -> Response:
json: Any = None json: Any = None
try: with contextlib.suppress(Exception):
json = await request.json() json = await request.json()
except Exception:
pass
data: Optional[dict] = None data: Optional[dict] = None
files: Optional[List[Tuple[str, FileTypes]]] = None files: Optional[List[Tuple[str, FileTypes]]] = None
try: with contextlib.suppress(Exception):
form = await request.form() form = await request.form()
data = {} data = {}
files = [] files = []
@@ -266,8 +231,7 @@ class Driver(ReverseDriver):
) )
else: else:
data[key] = value data[key] = value
except Exception:
pass
http_request = BaseRequest( http_request = BaseRequest(
request.method, request.method,
str(request.url), str(request.url),
@@ -281,7 +245,9 @@ class Driver(ReverseDriver):
) )
response = await setup.handle_func(http_request) response = await setup.handle_func(http_request)
return Response(response.content, response.status_code, dict(response.headers)) return Response(
response.content, response.status_code, dict(response.headers.items())
)
async def _handle_ws(self, websocket: WebSocket, setup: WebSocketServerSetup): async def _handle_ws(self, websocket: WebSocket, setup: WebSocketServerSetup):
request = BaseRequest( request = BaseRequest(
@@ -300,43 +266,56 @@ class Driver(ReverseDriver):
class FastAPIWebSocket(BaseWebSocket): class FastAPIWebSocket(BaseWebSocket):
@overrides(BaseWebSocket) """FastAPI WebSocket Wrapper"""
@override
def __init__(self, *, request: BaseRequest, websocket: WebSocket): def __init__(self, *, request: BaseRequest, websocket: WebSocket):
super().__init__(request=request) super().__init__(request=request)
self.websocket = websocket self.websocket = websocket
@property @property
@overrides(BaseWebSocket) @override
def closed(self) -> bool: def closed(self) -> bool:
return ( return (
self.websocket.client_state == WebSocketState.DISCONNECTED self.websocket.client_state == WebSocketState.DISCONNECTED
or self.websocket.application_state == WebSocketState.DISCONNECTED or self.websocket.application_state == WebSocketState.DISCONNECTED
) )
@overrides(BaseWebSocket) @override
async def accept(self) -> None: async def accept(self) -> None:
await self.websocket.accept() await self.websocket.accept()
@overrides(BaseWebSocket) @override
async def close( async def close(
self, code: int = status.WS_1000_NORMAL_CLOSURE, reason: str = "" self, code: int = status.WS_1000_NORMAL_CLOSURE, reason: str = ""
) -> None: ) -> None:
await self.websocket.close(code) await self.websocket.close(code, reason)
@overrides(BaseWebSocket) @override
async def receive(self) -> Union[str, bytes]:
# assert self.websocket.application_state == WebSocketState.CONNECTED
msg = await self.websocket.receive()
if msg["type"] == "websocket.disconnect":
raise WebSocketClosed(msg["code"])
return msg["text"] if "text" in msg else msg["bytes"]
@override
@catch_closed @catch_closed
async def receive(self) -> str: async def receive_text(self) -> str:
return await self.websocket.receive_text() return await self.websocket.receive_text()
@overrides(BaseWebSocket) @override
@catch_closed @catch_closed
async def receive_bytes(self) -> bytes: async def receive_bytes(self) -> bytes:
return await self.websocket.receive_bytes() return await self.websocket.receive_bytes()
@overrides(BaseWebSocket) @override
async def send(self, data: str) -> None: async def send_text(self, data: str) -> None:
await self.websocket.send({"type": "websocket.send", "text": data}) await self.websocket.send({"type": "websocket.send", "text": data})
@overrides(BaseWebSocket) @override
async def send_bytes(self, data: bytes) -> None: async def send_bytes(self, data: bytes) -> None:
await self.websocket.send({"type": "websocket.send", "bytes": data}) await self.websocket.send({"type": "websocket.send", "bytes": data})
__autodoc__ = {"catch_closed": False}

View File

@@ -1,34 +1,56 @@
from typing import AsyncGenerator """[HTTPX](https://www.python-httpx.org/) 驱动适配
```bash
nb driver install httpx
# 或者
pip install nonebot2[httpx]
```
:::tip 提示
本驱动仅支持客户端 HTTP 连接
:::
FrontMatter:
sidebar_position: 3
description: nonebot.drivers.httpx 模块
"""
from typing_extensions import override
from typing import Type, AsyncGenerator
from contextlib import asynccontextmanager from contextlib import asynccontextmanager
from nonebot.typing import overrides from nonebot.drivers.none import Driver as NoneDriver
from nonebot.drivers._block_driver import BlockDriver
from nonebot.drivers import ( from nonebot.drivers import (
Request, Request,
Response, Response,
WebSocket, WebSocket,
HTTPVersion, HTTPVersion,
ForwardMixin, ForwardMixin,
ForwardDriver,
combine_driver, combine_driver,
) )
try: try:
import httpx import httpx
except ImportError: except ModuleNotFoundError as e: # pragma: no cover
raise ImportError( raise ImportError(
"Please install httpx by using `pip install nonebot2[httpx]`" "Please install httpx first to use this driver. "
) from None "Install with pip: `pip install nonebot2[httpx]`"
) from e
class Mixin(ForwardMixin): class Mixin(ForwardMixin):
"""HTTPX Mixin"""
@property @property
@overrides(ForwardMixin) @override
def type(self) -> str: def type(self) -> str:
return "httpx" return "httpx"
@overrides(ForwardMixin) @override
async def request(self, setup: Request) -> Response: async def request(self, setup: Request) -> Response:
async with httpx.AsyncClient( async with httpx.AsyncClient(
cookies=setup.cookies.jar,
http2=setup.version == HTTPVersion.H2, http2=setup.version == HTTPVersion.H2,
proxies=setup.proxy, proxies=setup.proxy,
follow_redirects=True, follow_redirects=True,
@@ -45,16 +67,17 @@ class Mixin(ForwardMixin):
) )
return Response( return Response(
response.status_code, response.status_code,
headers=response.headers, headers=response.headers.multi_items(),
content=response.content, content=response.content,
request=setup, request=setup,
) )
@overrides(ForwardMixin) @override
@asynccontextmanager @asynccontextmanager
async def websocket(self, setup: Request) -> AsyncGenerator[WebSocket, None]: async def websocket(self, setup: Request) -> AsyncGenerator[WebSocket, None]:
async with super(Mixin, self).websocket(setup) as ws: async with super(Mixin, self).websocket(setup) as ws:
yield ws yield ws
Driver = combine_driver(BlockDriver, Mixin) Driver: Type[ForwardDriver] = combine_driver(NoneDriver, Mixin) # type: ignore
"""HTTPX Driver"""

155
nonebot/drivers/none.py Normal file
View File

@@ -0,0 +1,155 @@
"""None 驱动适配
:::tip 提示
本驱动不支持任何服务器或客户端连接
:::
FrontMatter:
sidebar_position: 6
description: nonebot.drivers.none 模块
"""
import signal
import asyncio
import threading
from typing_extensions import override
from nonebot.log import logger
from nonebot.consts import WINDOWS
from nonebot.config import Env, Config
from nonebot.drivers import Driver as BaseDriver
from ._lifespan import LIFESPAN_FUNC, Lifespan
HANDLED_SIGNALS = (
signal.SIGINT, # Unix signal 2. Sent by Ctrl+C.
signal.SIGTERM, # Unix signal 15. Sent by `kill <pid>`.
)
if WINDOWS: # pragma: py-win32
HANDLED_SIGNALS += (signal.SIGBREAK,) # Windows signal 21. Sent by Ctrl+Break.
class Driver(BaseDriver):
"""None 驱动框架"""
def __init__(self, env: Env, config: Config):
super().__init__(env, config)
self._lifespan = Lifespan()
self.should_exit: asyncio.Event = asyncio.Event()
self.force_exit: bool = False
@property
@override
def type(self) -> str:
"""驱动名称: `none`"""
return "none"
@property
@override
def logger(self):
"""none driver 使用的 logger"""
return logger
@override
def on_startup(self, func: LIFESPAN_FUNC) -> LIFESPAN_FUNC:
"""注册一个启动时执行的函数"""
return self._lifespan.on_startup(func)
@override
def on_shutdown(self, func: LIFESPAN_FUNC) -> LIFESPAN_FUNC:
"""注册一个停止时执行的函数"""
return self._lifespan.on_shutdown(func)
@override
def run(self, *args, **kwargs):
"""启动 none driver"""
super().run(*args, **kwargs)
loop = asyncio.get_event_loop()
loop.run_until_complete(self._serve())
async def _serve(self):
self._install_signal_handlers()
await self._startup()
if self.should_exit.is_set():
return
await self._main_loop()
await self._shutdown()
async def _startup(self):
try:
await self._lifespan.startup()
except Exception as e:
logger.opt(colors=True, exception=e).error(
"<r><bg #f8bbd0>Error when running startup function. "
"Ignored!</bg #f8bbd0></r>"
)
logger.info("Application startup completed.")
async def _main_loop(self):
await self.should_exit.wait()
async def _shutdown(self):
logger.info("Shutting down")
logger.info("Waiting for application shutdown.")
try:
await self._lifespan.shutdown()
except Exception as e:
logger.opt(colors=True, exception=e).error(
"<r><bg #f8bbd0>Error when running shutdown function. "
"Ignored!</bg #f8bbd0></r>"
)
for task in asyncio.all_tasks():
if task is not asyncio.current_task() and not task.done():
task.cancel()
await asyncio.sleep(0.1)
tasks = [t for t in asyncio.all_tasks() if t is not asyncio.current_task()]
if tasks and not self.force_exit:
logger.info("Waiting for tasks to finish. (CTRL+C to force quit)")
while tasks and not self.force_exit:
await asyncio.sleep(0.1)
tasks = [t for t in asyncio.all_tasks() if t is not asyncio.current_task()]
for task in tasks:
task.cancel()
await asyncio.gather(*tasks, return_exceptions=True)
logger.info("Application shutdown complete.")
loop = asyncio.get_event_loop()
loop.stop()
def _install_signal_handlers(self) -> None:
if threading.current_thread() is not threading.main_thread():
# Signals can only be listened to from the main thread.
return
loop = asyncio.get_event_loop()
try:
for sig in HANDLED_SIGNALS:
loop.add_signal_handler(sig, self._handle_exit, sig, None)
except NotImplementedError:
# Windows
for sig in HANDLED_SIGNALS:
signal.signal(sig, self._handle_exit)
def _handle_exit(self, sig, frame):
self.exit(force=self.should_exit.is_set())
def exit(self, force: bool = False):
"""退出 none driver
参数:
force: 强制退出
"""
if not self.should_exit.is_set():
self.should_exit.set()
if force:
self.force_exit = True

View File

@@ -1,39 +1,59 @@
""" """[Quart](https://pgjones.gitlab.io/quart/index.html) 驱动适配
Quart 驱动适配
================
后端使用方法请参考: `Quart 文档`_ ```bash
nb driver install quart
# 或者
pip install nonebot2[quart]
```
.. _Quart 文档: :::tip 提示
https://pgjones.gitlab.io/quart/index.html 本驱动仅支持服务端连接
:::
FrontMatter:
sidebar_position: 5
description: nonebot.drivers.quart 模块
""" """
import asyncio import asyncio
from functools import wraps from functools import wraps
from typing import List, Tuple, TypeVar, Callable, Optional, Coroutine from typing_extensions import override
from typing import (
Any,
Dict,
List,
Tuple,
Union,
TypeVar,
Callable,
Optional,
Coroutine,
cast,
)
import uvicorn
from pydantic import BaseSettings from pydantic import BaseSettings
from ._model import FileTypes
from nonebot.config import Env from nonebot.config import Env
from nonebot.typing import overrides
from nonebot.exception import WebSocketClosed from nonebot.exception import WebSocketClosed
from nonebot.internal.driver import FileTypes
from nonebot.config import Config as NoneBotConfig from nonebot.config import Config as NoneBotConfig
from nonebot.drivers import Request as BaseRequest from nonebot.drivers import Request as BaseRequest
from nonebot.drivers import WebSocket as BaseWebSocket from nonebot.drivers import WebSocket as BaseWebSocket
from nonebot.drivers import ReverseDriver, HTTPServerSetup, WebSocketServerSetup from nonebot.drivers import ReverseDriver, HTTPServerSetup, WebSocketServerSetup
try: try:
import uvicorn
from quart import request as _request from quart import request as _request
from quart import websocket as _websocket from quart.ctx import WebsocketContext
from quart.globals import websocket_ctx
from quart import Quart, Request, Response from quart import Quart, Request, Response
from quart.datastructures import FileStorage from quart.datastructures import FileStorage
from quart import Websocket as QuartWebSocket from quart import Websocket as QuartWebSocket
except ImportError: except ModuleNotFoundError as e: # pragma: no cover
raise ValueError( raise ImportError(
"Please install Quart by using `pip install nonebot2[quart]`" "Please install Quart first to use this driver. "
) from None "Install with pip: `pip install nonebot2[quart]`"
) from e
_AsyncCallable = TypeVar("_AsyncCallable", bound=Callable[..., Coroutine]) _AsyncCallable = TypeVar("_AsyncCallable", bound=Callable[..., Coroutine])
@@ -50,102 +70,62 @@ def catch_closed(func):
class Config(BaseSettings): class Config(BaseSettings):
""" """Quart 驱动框架设置"""
Quart 驱动框架设置
"""
quart_reload: bool = False quart_reload: bool = False
""" """开启/关闭冷重载"""
:类型:
``bool``
:说明:
开启/关闭冷重载
"""
quart_reload_dirs: Optional[List[str]] = None quart_reload_dirs: Optional[List[str]] = None
""" """重载监控文件夹列表,默认为 uvicorn 默认值"""
:类型: quart_reload_delay: float = 0.25
"""重载延迟,默认为 uvicorn 默认值"""
``Optional[List[str]]``
:说明:
重载监控文件夹列表,默认为 uvicorn 默认值
"""
quart_reload_delay: Optional[float] = None
"""
:类型:
``Optional[float]``
:说明:
重载延迟,默认为 uvicorn 默认值
"""
quart_reload_includes: Optional[List[str]] = None quart_reload_includes: Optional[List[str]] = None
""" """要监听的文件列表,支持 glob pattern默认为 uvicorn 默认值"""
:类型:
``Optional[List[str]]``
:说明:
要监听的文件列表,支持 glob pattern默认为 uvicorn 默认值
"""
quart_reload_excludes: Optional[List[str]] = None quart_reload_excludes: Optional[List[str]] = None
""" """不要监听的文件列表,支持 glob pattern默认为 uvicorn 默认值"""
:类型: quart_extra: Dict[str, Any] = {}
"""传递给 `Quart` 的其他参数。"""
``Optional[List[str]]``
:说明:
不要监听的文件列表,支持 glob pattern默认为 uvicorn 默认值
"""
class Config: class Config:
extra = "ignore" extra = "ignore"
class Driver(ReverseDriver): class Driver(ReverseDriver):
""" """Quart 驱动框架"""
Quart 驱动框架
"""
def __init__(self, env: Env, config: NoneBotConfig): def __init__(self, env: Env, config: NoneBotConfig):
super().__init__(env, config) super().__init__(env, config)
self.quart_config = Config(**config.dict()) self.quart_config = Config(**config.dict())
self._server_app = Quart(self.__class__.__qualname__) self._server_app = Quart(
self.__class__.__qualname__, **self.quart_config.quart_extra
)
@property @property
@overrides(ReverseDriver) @override
def type(self) -> str: def type(self) -> str:
"""驱动名称: ``quart``""" """驱动名称: `quart`"""
return "quart" return "quart"
@property @property
@overrides(ReverseDriver) @override
def server_app(self) -> Quart: def server_app(self) -> Quart:
"""``Quart`` 对象""" """`Quart` 对象"""
return self._server_app return self._server_app
@property @property
@overrides(ReverseDriver) @override
def asgi(self): def asgi(self):
"""``Quart`` 对象""" """`Quart` 对象"""
return self._server_app return self._server_app
@property @property
@overrides(ReverseDriver) @override
def logger(self): def logger(self):
"""Quart 使用的 logger""" """Quart 使用的 logger"""
return self._server_app.logger return self._server_app.logger
@overrides(ReverseDriver) @override
def setup_http_server(self, setup: HTTPServerSetup): def setup_http_server(self, setup: HTTPServerSetup):
async def _handle() -> Response: async def _handle() -> Response:
return await self._handle_http(setup) return await self._handle_http(setup)
@@ -157,7 +137,7 @@ class Driver(ReverseDriver):
view_func=_handle, view_func=_handle,
) )
@overrides(ReverseDriver) @override
def setup_websocket_server(self, setup: WebSocketServerSetup) -> None: def setup_websocket_server(self, setup: WebSocketServerSetup) -> None:
async def _handle() -> None: async def _handle() -> None:
return await self._handle_ws(setup) return await self._handle_ws(setup)
@@ -168,21 +148,17 @@ class Driver(ReverseDriver):
view_func=_handle, view_func=_handle,
) )
@overrides(ReverseDriver) @override
def on_startup(self, func: _AsyncCallable) -> _AsyncCallable: def on_startup(self, func: _AsyncCallable) -> _AsyncCallable:
"""参考文档: `Startup and Shutdown`_ """参考文档: [`Startup and Shutdown`](https://pgjones.gitlab.io/quart/how_to_guides/startup_shutdown.html)"""
.. _Startup and Shutdown:
https://pgjones.gitlab.io/quart/how_to_guides/startup_shutdown.html
"""
return self.server_app.before_serving(func) # type: ignore return self.server_app.before_serving(func) # type: ignore
@overrides(ReverseDriver) @override
def on_shutdown(self, func: _AsyncCallable) -> _AsyncCallable: def on_shutdown(self, func: _AsyncCallable) -> _AsyncCallable:
"""参考文档: `Startup and Shutdown`_""" """参考文档: [`Startup and Shutdown`](https://pgjones.gitlab.io/quart/how_to_guides/startup_shutdown.html)"""
return self.server_app.after_serving(func) # type: ignore return self.server_app.after_serving(func) # type: ignore
@overrides(ReverseDriver) @override
def run( def run(
self, self,
host: Optional[str] = None, host: Optional[str] = None,
@@ -191,7 +167,7 @@ class Driver(ReverseDriver):
app: Optional[str] = None, app: Optional[str] = None,
**kwargs, **kwargs,
): ):
"""使用 ``uvicorn`` 启动 Quart""" """使用 `uvicorn` 启动 Quart"""
super().run(host, port, app, **kwargs) super().run(host, port, app, **kwargs)
LOGGING_CONFIG = { LOGGING_CONFIG = {
"version": 1, "version": 1,
@@ -225,9 +201,7 @@ class Driver(ReverseDriver):
async def _handle_http(self, setup: HTTPServerSetup) -> Response: async def _handle_http(self, setup: HTTPServerSetup) -> Response:
request: Request = _request request: Request = _request
json = None json = await request.get_json() if request.is_json else None
if request.is_json:
json = await request.get_json()
data = await request.form data = await request.form
files_dict = await request.files files_dict = await request.files
@@ -240,7 +214,7 @@ class Driver(ReverseDriver):
http_request = BaseRequest( http_request = BaseRequest(
request.method, request.method,
request.url, request.url,
headers=request.headers.items(), headers=list(request.headers.items()),
cookies=list(request.cookies.items()), cookies=list(request.cookies.items()),
content=await request.get_data( content=await request.get_data(
cache=False, as_text=False, parse_form_data=False cache=False, as_text=False, parse_form_data=False
@@ -260,49 +234,61 @@ class Driver(ReverseDriver):
) )
async def _handle_ws(self, setup: WebSocketServerSetup) -> None: async def _handle_ws(self, setup: WebSocketServerSetup) -> None:
websocket: QuartWebSocket = _websocket ctx = cast(WebsocketContext, websocket_ctx.copy())
websocket = websocket_ctx.websocket
http_request = BaseRequest( http_request = BaseRequest(
websocket.method, websocket.method,
websocket.url, websocket.url,
headers=websocket.headers.items(), headers=list(websocket.headers.items()),
cookies=list(websocket.cookies.items()), cookies=list(websocket.cookies.items()),
version=websocket.http_version, version=websocket.http_version,
) )
ws = WebSocket(request=http_request, websocket=websocket) ws = WebSocket(request=http_request, websocket_ctx=ctx)
await setup.handle_func(ws) await setup.handle_func(ws)
class WebSocket(BaseWebSocket): class WebSocket(BaseWebSocket):
def __init__(self, *, request: BaseRequest, websocket: QuartWebSocket): """Quart WebSocket Wrapper"""
def __init__(self, *, request: BaseRequest, websocket_ctx: WebsocketContext):
super().__init__(request=request) super().__init__(request=request)
self.websocket = websocket self.websocket_ctx = websocket_ctx
@property @property
@overrides(BaseWebSocket) def websocket(self) -> QuartWebSocket:
return self.websocket_ctx.websocket
@property
@override
def closed(self): def closed(self):
# FIXME # FIXME
return True return True
@overrides(BaseWebSocket) @override
async def accept(self): async def accept(self):
await self.websocket.accept() await self.websocket.accept()
@overrides(BaseWebSocket) @override
async def close(self, code: int = 1000, reason: str = ""): async def close(self, code: int = 1000, reason: str = ""):
await self.websocket.close(code, reason) await self.websocket.close(code, reason)
@overrides(BaseWebSocket) @override
@catch_closed @catch_closed
async def receive(self) -> str: async def receive(self) -> Union[str, bytes]:
return await self.websocket.receive()
@override
@catch_closed
async def receive_text(self) -> str:
msg = await self.websocket.receive() msg = await self.websocket.receive()
if isinstance(msg, bytes): if isinstance(msg, bytes):
raise TypeError("WebSocket received unexpected frame type: bytes") raise TypeError("WebSocket received unexpected frame type: bytes")
return msg return msg
@overrides(BaseWebSocket) @override
@catch_closed @catch_closed
async def receive_bytes(self) -> bytes: async def receive_bytes(self) -> bytes:
msg = await self.websocket.receive() msg = await self.websocket.receive()
@@ -310,10 +296,13 @@ class WebSocket(BaseWebSocket):
raise TypeError("WebSocket received unexpected frame type: str") raise TypeError("WebSocket received unexpected frame type: str")
return msg return msg
@overrides(BaseWebSocket) @override
async def send(self, data: str): async def send_text(self, data: str):
await self.websocket.send(data) await self.websocket.send(data)
@overrides(BaseWebSocket) @override
async def send_bytes(self, data: bytes): async def send_bytes(self, data: bytes):
await self.websocket.send(data) await self.websocket.send(data)
__autodoc__ = {"catch_closed": False}

View File

@@ -1,58 +1,81 @@
"""[websockets](https://websockets.readthedocs.io/) 驱动适配
```bash
nb driver install websockets
# 或者
pip install nonebot2[websockets]
```
:::tip 提示
本驱动仅支持客户端 WebSocket 连接
:::
FrontMatter:
sidebar_position: 4
description: nonebot.drivers.websockets 模块
"""
import logging import logging
from functools import wraps from functools import wraps
from typing import AsyncGenerator
from contextlib import asynccontextmanager from contextlib import asynccontextmanager
from typing_extensions import ParamSpec, override
from typing import Type, Union, TypeVar, Callable, Awaitable, AsyncGenerator
from nonebot.typing import overrides
from nonebot.log import LoguruHandler from nonebot.log import LoguruHandler
from nonebot.drivers import Request, Response from nonebot.drivers import Request, Response
from nonebot.exception import WebSocketClosed from nonebot.exception import WebSocketClosed
from nonebot.drivers._block_driver import BlockDriver from nonebot.drivers.none import Driver as NoneDriver
from nonebot.drivers import WebSocket as BaseWebSocket from nonebot.drivers import WebSocket as BaseWebSocket
from nonebot.drivers import ForwardMixin, combine_driver from nonebot.drivers import ForwardMixin, ForwardDriver, combine_driver
try: try:
from websockets.exceptions import ConnectionClosed from websockets.exceptions import ConnectionClosed
from websockets.legacy.client import Connect, WebSocketClientProtocol from websockets.legacy.client import Connect, WebSocketClientProtocol
except ImportError: except ModuleNotFoundError as e: # pragma: no cover
raise ImportError( raise ImportError(
"Please install websockets by using `pip install nonebot2[websockets]`" "Please install websockets first to use this driver. "
) "Install with pip: `pip install nonebot2[websockets]`"
) from e
T = TypeVar("T")
P = ParamSpec("P")
logger = logging.Logger("websockets.client", "INFO") logger = logging.Logger("websockets.client", "INFO")
logger.addHandler(LoguruHandler()) logger.addHandler(LoguruHandler())
def catch_closed(func): def catch_closed(func: Callable[P, Awaitable[T]]) -> Callable[P, Awaitable[T]]:
@wraps(func) @wraps(func)
async def decorator(*args, **kwargs): async def decorator(*args: P.args, **kwargs: P.kwargs) -> T:
try: try:
return await func(*args, **kwargs) return await func(*args, **kwargs)
except ConnectionClosed as e: except ConnectionClosed as e:
if e.rcvd_then_sent: if e.rcvd_then_sent:
raise WebSocketClosed(e.rcvd.code, e.rcvd.reason) raise WebSocketClosed(e.rcvd.code, e.rcvd.reason) # type: ignore
else: else:
raise WebSocketClosed(e.sent.code, e.sent.reason) raise WebSocketClosed(e.sent.code, e.sent.reason) # type: ignore
return decorator return decorator
class Mixin(ForwardMixin): class Mixin(ForwardMixin):
"""Websockets Mixin"""
@property @property
@overrides(ForwardMixin) @override
def type(self) -> str: def type(self) -> str:
return "websockets" return "websockets"
@overrides(ForwardMixin) @override
async def request(self, setup: Request) -> Response: async def request(self, setup: Request) -> Response:
return await super(Mixin, self).request(setup) return await super(Mixin, self).request(setup)
@overrides(ForwardMixin) @override
@asynccontextmanager @asynccontextmanager
async def websocket(self, setup: Request) -> AsyncGenerator["WebSocket", None]: async def websocket(self, setup: Request) -> AsyncGenerator["WebSocket", None]:
connection = Connect( connection = Connect(
str(setup.url), str(setup.url),
extra_headers=setup.headers.items(), extra_headers={**setup.headers, **setup.cookies.as_header(setup)},
open_timeout=setup.timeout, open_timeout=setup.timeout,
) )
async with connection as ws: async with connection as ws:
@@ -60,33 +83,40 @@ class Mixin(ForwardMixin):
class WebSocket(BaseWebSocket): class WebSocket(BaseWebSocket):
@overrides(BaseWebSocket) """Websockets WebSocket Wrapper"""
@override
def __init__(self, *, request: Request, websocket: WebSocketClientProtocol): def __init__(self, *, request: Request, websocket: WebSocketClientProtocol):
super().__init__(request=request) super().__init__(request=request)
self.websocket = websocket self.websocket = websocket
@property @property
@overrides(BaseWebSocket) @override
def closed(self) -> bool: def closed(self) -> bool:
return self.websocket.closed return self.websocket.closed
@overrides(BaseWebSocket) @override
async def accept(self): async def accept(self):
raise NotImplementedError raise NotImplementedError
@overrides(BaseWebSocket) @override
async def close(self, code: int = 1000, reason: str = ""): async def close(self, code: int = 1000, reason: str = ""):
await self.websocket.close(code, reason) await self.websocket.close(code, reason)
@overrides(BaseWebSocket) @override
@catch_closed @catch_closed
async def receive(self) -> str: async def receive(self) -> Union[str, bytes]:
return await self.websocket.recv()
@override
@catch_closed
async def receive_text(self) -> str:
msg = await self.websocket.recv() msg = await self.websocket.recv()
if isinstance(msg, bytes): if isinstance(msg, bytes):
raise TypeError("WebSocket received unexpected frame type: bytes") raise TypeError("WebSocket received unexpected frame type: bytes")
return msg return msg
@overrides(BaseWebSocket) @override
@catch_closed @catch_closed
async def receive_bytes(self) -> bytes: async def receive_bytes(self) -> bytes:
msg = await self.websocket.recv() msg = await self.websocket.recv()
@@ -94,13 +124,14 @@ class WebSocket(BaseWebSocket):
raise TypeError("WebSocket received unexpected frame type: str") raise TypeError("WebSocket received unexpected frame type: str")
return msg return msg
@overrides(BaseWebSocket) @override
async def send(self, data: str) -> None: async def send_text(self, data: str) -> None:
await self.websocket.send(data) await self.websocket.send(data)
@overrides(BaseWebSocket) @override
async def send_bytes(self, data: bytes) -> None: async def send_bytes(self, data: bytes) -> None:
await self.websocket.send(data) await self.websocket.send(data)
Driver = combine_driver(BlockDriver, Mixin) Driver: Type[ForwardDriver] = combine_driver(NoneDriver, Mixin) # type: ignore
"""Websockets Driver"""

View File

@@ -1,9 +1,32 @@
""" """本模块包含了所有 NoneBot 运行时可能会抛出的异常。
异常
====
下列文档中的异常是所有 NoneBot 运行时可能会抛出的。
这些异常并非所有需要用户处理,在 NoneBot 内部运行时被捕获,并进行对应操作。 这些异常并非所有需要用户处理,在 NoneBot 内部运行时被捕获,并进行对应操作。
```bash
NoneBotException
├── ParserExit
├── ProcessException
| ├── IgnoredException
| ├── SkippedException
| | └── TypeMisMatch
| ├── MockApiException
| └── StopPropagation
├── MatcherException
| ├── PausedException
| ├── RejectedException
| └── FinishedException
├── AdapterException
| ├── NoLogException
| ├── ApiNotAvailable
| ├── NetworkError
| └── ActionFailed
└── DriverException
└── WebSocketClosed
```
FrontMatter:
sidebar_position: 10
description: nonebot.exception 模块
""" """
from typing import Any, Optional from typing import Any, Optional
@@ -12,253 +35,208 @@ from pydantic.fields import ModelField
class NoneBotException(Exception): class NoneBotException(Exception):
""" """所有 NoneBot 发生的异常基类。"""
:说明:
所有 NoneBot 发生的异常基类。 def __str__(self) -> str:
""" return self.__repr__()
# Rule Exception # Rule Exception
class ParserExit(NoneBotException): class ParserExit(NoneBotException):
""" """{ref}`nonebot.rule.shell_command` 处理消息失败时返回的异常。"""
:说明:
``shell command`` 处理消息失败时返回的异常 def __init__(self, status: int = 0, message: Optional[str] = None) -> None:
:参数:
* ``status``
* ``message``
"""
def __init__(self, status: int = 0, message: Optional[str] = None):
self.status = status self.status = status
self.message = message self.message = message
def __repr__(self): def __repr__(self) -> str:
return f"<ParserExit status={self.status} message={self.message}>" return (
f"ParserExit(status={self.status}"
def __str__(self): + (f", message={self.message!r}" if self.message else "")
return self.__repr__() + ")"
)
# Processor Exception # Processor Exception
class ProcessException(NoneBotException): class ProcessException(NoneBotException):
""" """事件处理过程中发生的异常基类。"""
:说明:
事件处理过程中发生的异常基类。
"""
class IgnoredException(ProcessException): class IgnoredException(ProcessException):
""" """指示 NoneBot 应该忽略该事件。可由 PreProcessor 抛出。
:说明:
指示 NoneBot 应该忽略该事件。可由 PreProcessor 抛出。 参数:
reason: 忽略事件的原因
:参数:
* ``reason``: 忽略事件的原因
""" """
def __init__(self, reason): def __init__(self, reason: Any) -> None:
self.reason = reason self.reason: Any = reason
def __repr__(self): def __repr__(self) -> str:
return f"<IgnoredException, reason={self.reason}>" return f"IgnoredException(reason={self.reason!r})"
def __str__(self):
return self.__repr__() class SkippedException(ProcessException):
"""指示 NoneBot 立即结束当前 `Dependent` 的运行。
例如,可以在 `Handler` 中通过 {ref}`nonebot.matcher.Matcher.skip` 抛出。
用法:
```python
def always_skip():
Matcher.skip()
@matcher.handle()
async def handler(dependency = Depends(always_skip)):
# never run
```
"""
class TypeMisMatch(SkippedException):
"""当前 `Handler` 的参数类型不匹配。"""
def __init__(self, param: ModelField, value: Any) -> None:
self.param: ModelField = param
self.value: Any = value
def __repr__(self) -> str:
return (
f"TypeMisMatch(param={self.param.name}, "
f"type={self.param._type_display()}, value={self.value!r}>"
)
class MockApiException(ProcessException): class MockApiException(ProcessException):
""" """指示 NoneBot 阻止本次 API 调用或修改本次调用返回值,并返回自定义内容。
:说明: 可由 api hook 抛出。
指示 NoneBot 阻止本次 API 调用或修改本次调用返回值,并返回自定义内容。可由 api hook 抛出。 参数:
result: 返回的内容
:参数:
* ``result``: 返回的内容
""" """
def __init__(self, result: Any): def __init__(self, result: Any):
self.result = result self.result = result
def __repr__(self): def __repr__(self) -> str:
return f"<ApiCancelledException, result={self.result}>" return f"MockApiException(result={self.result!r})"
def __str__(self):
return self.__repr__()
class StopPropagation(ProcessException): class StopPropagation(ProcessException):
""" """指示 NoneBot 终止事件向下层传播。
:说明:
指示 NoneBot 终止事件向下层传播。 {ref}`nonebot.matcher.Matcher.block` 为 `True`
或使用 {ref}`nonebot.matcher.Matcher.stop_propagation` 方法时抛出。
:用法: 用法:
```python
在 ``Matcher.block == True`` 时抛出。 matcher = on_notice(block=True)
# 或者
@matcher.handle()
async def handler(matcher: Matcher):
matcher.stop_propagation()
```
""" """
# Matcher Exceptions # Matcher Exceptions
class MatcherException(NoneBotException): class MatcherException(NoneBotException):
""" """所有 Matcher 发生的异常基类。"""
:说明:
所有 Matcher 发生的异常基类。
"""
class SkippedException(MatcherException):
"""
:说明:
指示 NoneBot 立即结束当前 ``Handler`` 的处理,继续处理下一个 ``Handler``。
:用法:
可以在 ``Handler`` 中通过 ``Matcher.skip()`` 抛出。
"""
class TypeMisMatch(SkippedException):
"""
:说明:
当前 ``Handler`` 的参数类型不匹配。
"""
def __init__(self, param: ModelField, value: Any):
self.param: ModelField = param
self.value: Any = value
def __repr__(self):
return f"<TypeMisMatch, param={self.param}, value={self.value}>"
def __str__(self):
self.__repr__()
class PausedException(MatcherException): class PausedException(MatcherException):
""" """指示 NoneBot 结束当前 `Handler` 并等待下一条消息后继续下一个 `Handler`。
:说明: 可用于用户输入新信息。
指示 NoneBot 结束当前 ``Handler`` 并等待下一条消息后继续下一个 ``Handler`` 可以在 `Handler` 中通过 {ref}`nonebot.matcher.Matcher.pause` 抛出
可用于用户输入新信息。
:用法: 用法:
```python
可以在 ``Handler`` 中通过 ``Matcher.pause()`` 抛出。 @matcher.handle()
async def handler():
await matcher.pause("some message")
```
""" """
class RejectedException(MatcherException): class RejectedException(MatcherException):
""" """指示 NoneBot 结束当前 `Handler` 并等待下一条消息后重新运行当前 `Handler`。
:说明: 可用于用户重新输入。
指示 NoneBot 结束当前 ``Handler`` 并等待下一条消息后重新运行当前 ``Handler`` 可以在 `Handler` 中通过 {ref}`nonebot.matcher.Matcher.reject` 抛出
可用于用户重新输入。
:用法: 用法:
```python
可以在 ``Handler`` 中通过 ``Matcher.reject()`` 抛出。 @matcher.handle()
async def handler():
await matcher.reject("some message")
```
""" """
class FinishedException(MatcherException): class FinishedException(MatcherException):
""" """指示 NoneBot 结束当前 `Handler` 且后续 `Handler` 不再被运行。可用于结束用户会话。
:说明:
指示 NoneBot 结束当前 ``Handler`` 且后续 ``Handler`` 不再被运行 可以在 `Handler` 中通过 {ref}`nonebot.matcher.Matcher.finish` 抛出
可用于结束用户会话。
:用法: 用法:
```python
可以在 ``Handler`` 中通过 ``Matcher.finish()`` 抛出。 @matcher.handle()
async def handler():
await matcher.finish("some message")
```
""" """
# Adapter Exceptions # Adapter Exceptions
class AdapterException(NoneBotException): class AdapterException(NoneBotException):
""" """代表 `Adapter` 抛出的异常,所有的 `Adapter` 都要在内部继承自这个 `Exception`。
:说明:
代表 ``Adapter`` 抛出的异常,所有的 ``Adapter`` 都要在内部继承自这个 ``Exception`` 参数:
adapter_name: 标识 adapter
:参数:
* ``adapter_name: str``: 标识 adapter
""" """
def __init__(self, adapter_name: str) -> None: def __init__(self, adapter_name: str, *args: object) -> None:
self.adapter_name = adapter_name super().__init__(*args)
self.adapter_name: str = adapter_name
class NoLogException(AdapterException): class NoLogException(AdapterException):
""" """指示 NoneBot 对当前 `Event` 进行处理但不显示 Log 信息。
:说明:
指示 NoneBot 对当前 ``Event`` 进行处理但不显示 Log 信息,可在 ``get_log_string`` 时抛出 可在 {ref}`nonebot.adapters.Event.get_log_string` 时抛出
""" """
pass
class ApiNotAvailable(AdapterException): class ApiNotAvailable(AdapterException):
""" """在 API 连接不可用时抛出。"""
:说明:
在 API 连接不可用时抛出。
"""
pass
class NetworkError(AdapterException): class NetworkError(AdapterException):
"""在网络出现问题时抛出,
如: API 请求地址不正确, API 请求无返回或返回状态非正常等。
""" """
:说明:
在网络出现问题时抛出,如: API 请求地址不正确, API 请求无返回或返回状态非正常等。
"""
pass
class ActionFailed(AdapterException): class ActionFailed(AdapterException):
""" """API 请求成功返回数据,但 API 操作失败。"""
:说明:
API 请求成功返回数据,但 API 操作失败。
"""
pass
# Driver Exceptions # Driver Exceptions
class DriverException(NoneBotException): class DriverException(NoneBotException):
""" """`Driver` 抛出的异常基类。"""
:说明:
``Driver`` 抛出的异常基类
"""
class WebSocketClosed(DriverException): class WebSocketClosed(DriverException):
""" """WebSocket 连接已关闭。"""
:说明:
WebSocket 连接已关闭 def __init__(self, code: int, reason: Optional[str] = None) -> None:
"""
def __init__(self, code: int, reason: Optional[str] = None):
self.code = code self.code = code
self.reason = reason self.reason = reason
def __repr__(self) -> str: def __repr__(self) -> str:
return f"<WebSocketClosed code={self.code} reason={self.reason}>" return (
f"WebSocketClosed(code={self.code}"
+ (f", reason={self.reason!r}" if self.reason else "")
+ ")"
)

View File

@@ -0,0 +1,6 @@
from .bot import Bot as Bot
from .event import Event as Event
from .adapter import Adapter as Adapter
from .message import Message as Message
from .message import MessageSegment as MessageSegment
from .template import MessageTemplate as MessageTemplate

View File

@@ -2,9 +2,8 @@ import abc
from contextlib import asynccontextmanager from contextlib import asynccontextmanager
from typing import Any, Dict, AsyncGenerator from typing import Any, Dict, AsyncGenerator
from ._bot import Bot
from nonebot.config import Config from nonebot.config import Config
from nonebot.drivers import ( from nonebot.internal.driver import (
Driver, Driver,
Request, Request,
Response, Response,
@@ -15,61 +14,97 @@ from nonebot.drivers import (
WebSocketServerSetup, WebSocketServerSetup,
) )
from .bot import Bot
class Adapter(abc.ABC): class Adapter(abc.ABC):
"""协议适配器基类。
通常 Adapter 中编写协议通信相关代码: 建立通信连接处理接收与发送 data
参数:
driver: {ref}`nonebot.drivers.Driver` 实例
kwargs: 其他由 {ref}`nonebot.drivers.Driver.register_adapter` 传入的额外参数
"""
def __init__(self, driver: Driver, **kwargs: Any): def __init__(self, driver: Driver, **kwargs: Any):
self.driver: Driver = driver self.driver: Driver = driver
"""{ref}`nonebot.drivers.Driver` 实例"""
self.bots: Dict[str, Bot] = {} self.bots: Dict[str, Bot] = {}
"""本协议适配器已建立连接的 {ref}`nonebot.adapters.Bot` 实例"""
def __repr__(self) -> str:
return f"Adapter(name={self.get_name()!r})"
@classmethod @classmethod
@abc.abstractmethod @abc.abstractmethod
def get_name(cls) -> str: def get_name(cls) -> str:
"""当前协议适配器的名称"""
raise NotImplementedError raise NotImplementedError
@property @property
def config(self) -> Config: def config(self) -> Config:
"""全局 NoneBot 配置"""
return self.driver.config return self.driver.config
def bot_connect(self, bot: Bot) -> None: def bot_connect(self, bot: Bot) -> None:
"""告知 NoneBot 建立了一个新的 {ref}`nonebot.adapters.Bot` 连接。
当有新的 {ref}`nonebot.adapters.Bot` 实例连接建立成功时调用
参数:
bot: {ref}`nonebot.adapters.Bot` 实例
"""
self.driver._bot_connect(bot) self.driver._bot_connect(bot)
self.bots[bot.self_id] = bot self.bots[bot.self_id] = bot
def bot_disconnect(self, bot: Bot) -> None: def bot_disconnect(self, bot: Bot) -> None:
"""告知 NoneBot {ref}`nonebot.adapters.Bot` 连接已断开。
当有 {ref}`nonebot.adapters.Bot` 实例连接断开时调用
参数:
bot: {ref}`nonebot.adapters.Bot` 实例
"""
if self.bots.pop(bot.self_id, None) is None:
raise RuntimeError(f"{bot} not found in adapter {self.get_name()}")
self.driver._bot_disconnect(bot) self.driver._bot_disconnect(bot)
self.bots.pop(bot.self_id, None)
def setup_http_server(self, setup: HTTPServerSetup): def setup_http_server(self, setup: HTTPServerSetup):
"""设置一个 HTTP 服务器路由配置"""
if not isinstance(self.driver, ReverseDriver): if not isinstance(self.driver, ReverseDriver):
raise TypeError("Current driver does not support http server") raise TypeError("Current driver does not support http server")
self.driver.setup_http_server(setup) self.driver.setup_http_server(setup)
def setup_websocket_server(self, setup: WebSocketServerSetup): def setup_websocket_server(self, setup: WebSocketServerSetup):
"""设置一个 WebSocket 服务器路由配置"""
if not isinstance(self.driver, ReverseDriver): if not isinstance(self.driver, ReverseDriver):
raise TypeError("Current driver does not support websocket server") raise TypeError("Current driver does not support websocket server")
self.driver.setup_websocket_server(setup) self.driver.setup_websocket_server(setup)
async def request(self, setup: Request) -> Response: async def request(self, setup: Request) -> Response:
"""进行一个 HTTP 客户端请求"""
if not isinstance(self.driver, ForwardDriver): if not isinstance(self.driver, ForwardDriver):
raise TypeError("Current driver does not support http client") raise TypeError("Current driver does not support http client")
return await self.driver.request(setup) return await self.driver.request(setup)
@asynccontextmanager @asynccontextmanager
async def websocket(self, setup: Request) -> AsyncGenerator[WebSocket, None]: async def websocket(self, setup: Request) -> AsyncGenerator[WebSocket, None]:
"""建立一个 WebSocket 客户端连接请求"""
if not isinstance(self.driver, ForwardDriver): if not isinstance(self.driver, ForwardDriver):
raise TypeError("Current driver does not support websocket client") raise TypeError("Current driver does not support websocket client")
async with self.driver.websocket(setup) as ws: async with self.driver.websocket(setup) as ws:
yield ws yield ws
@abc.abstractmethod @abc.abstractmethod
async def _call_api(self, bot: Bot, api: str, **data) -> Any: async def _call_api(self, bot: Bot, api: str, **data: Any) -> Any:
""" """`Adapter` 实际调用 api 的逻辑实现函数,实现该方法以调用 api。
:说明:
``adapter`` 实际调用 api 的逻辑实现函数实现该方法以调用 api 参数:
api: API 名称
:参数: data: API 数据
* ``api: str``: API 名称
* ``**data``: API 数据
""" """
raise NotImplementedError raise NotImplementedError
__autodoc__ = {"Adapter._call_api": True}

View File

@@ -1,8 +1,7 @@
import abc import abc
import asyncio import asyncio
from functools import partial from functools import partial
from typing_extensions import Protocol from typing import TYPE_CHECKING, Any, Set, Union, Optional, Protocol
from typing import TYPE_CHECKING, Any, Set, Union, Optional
from nonebot.log import logger from nonebot.log import logger
from nonebot.config import Config from nonebot.config import Config
@@ -10,79 +9,75 @@ from nonebot.exception import MockApiException
from nonebot.typing import T_CalledAPIHook, T_CallingAPIHook from nonebot.typing import T_CalledAPIHook, T_CallingAPIHook
if TYPE_CHECKING: if TYPE_CHECKING:
from ._event import Event from .event import Event
from ._adapter import Adapter from .adapter import Adapter
from ._message import Message, MessageSegment from .message import Message, MessageSegment
class _ApiCall(Protocol):
class _ApiCall(Protocol): async def __call__(self, **kwargs: Any) -> Any:
async def __call__(self, **kwargs: Any) -> Any: ...
...
class Bot(abc.ABC): class Bot(abc.ABC):
""" """Bot 基类。
Bot 基类用于处理上报消息并提供 API 调用接口
用于处理上报消息并提供 API 调用接口
参数:
adapter: 协议适配器实例
self_id: 机器人 ID
""" """
_calling_api_hook: Set[T_CallingAPIHook] = set() _calling_api_hook: Set[T_CallingAPIHook] = set()
""" """call_api 时执行的函数"""
:类型: ``Set[T_CallingAPIHook]``
:说明: call_api 时执行的函数
"""
_called_api_hook: Set[T_CalledAPIHook] = set() _called_api_hook: Set[T_CalledAPIHook] = set()
""" """call_api 后执行的函数"""
:类型: ``Set[T_CalledAPIHook]``
:说明: call_api 后执行的函数
"""
def __init__(self, adapter: "Adapter", self_id: str): def __init__(self, adapter: "Adapter", self_id: str):
"""
:参数:
* ``self_id: str``: 机器人 ID
* ``request: HTTPConnection``: request 连接对象
"""
self.adapter: "Adapter" = adapter self.adapter: "Adapter" = adapter
"""协议适配器实例"""
self.self_id: str = self_id self.self_id: str = self_id
"""机器人 ID""" """机器人 ID"""
def __getattr__(self, name: str) -> _ApiCall: def __repr__(self) -> str:
return f"Bot(type={self.type!r}, self_id={self.self_id!r})"
def __getattr__(self, name: str) -> "_ApiCall":
if name.startswith("__") and name.endswith("__"):
raise AttributeError(
f"'{self.__class__.__name__}' object has no attribute '{name}'"
)
return partial(self.call_api, name) return partial(self.call_api, name)
@property @property
def type(self) -> str: def type(self) -> str:
"""协议适配器名称"""
return self.adapter.get_name() return self.adapter.get_name()
@property @property
def config(self) -> Config: def config(self) -> Config:
"""全局 NoneBot 配置"""
return self.adapter.config return self.adapter.config
async def call_api(self, api: str, **data: Any) -> Any: async def call_api(self, api: str, **data: Any) -> Any:
""" """调用机器人 API 接口,可以通过该函数或直接通过 bot 属性进行调用
:说明:
调用机器人 API 接口可以通过该函数或直接通过 bot 属性进行调用 参数:
api: API 名称
:参数: data: API 数据
* ``api: str``: API 名称
* ``**data``: API 数据
:示例:
.. code-block:: python
用法:
```python
await bot.call_api("send_msg", message="hello world") await bot.call_api("send_msg", message="hello world")
await bot.send_msg(message="hello world") await bot.send_msg(message="hello world")
```
""" """
result: Any = None result: Any = None
skip_calling_api: bool = False skip_calling_api: bool = False
exception: Optional[Exception] = None exception: Optional[Exception] = None
coros = list(map(lambda x: x(self, api, data), self._calling_api_hook)) if coros := [hook(self, api, data) for hook in self._calling_api_hook]:
if coros:
try: try:
logger.debug("Running CallingAPI hooks...") logger.debug("Running CallingAPI hooks...")
await asyncio.gather(*coros) await asyncio.gather(*coros)
@@ -104,10 +99,9 @@ class Bot(abc.ABC):
except Exception as e: except Exception as e:
exception = e exception = e
coros = list( if coros := [
map(lambda x: x(self, exception, api, data, result), self._called_api_hook) hook(self, exception, api, data, result) for hook in self._called_api_hook
) ]:
if coros:
try: try:
logger.debug("Running CalledAPI hooks...") logger.debug("Running CalledAPI hooks...")
await asyncio.gather(*coros) await asyncio.gather(*coros)
@@ -128,51 +122,44 @@ class Bot(abc.ABC):
@abc.abstractmethod @abc.abstractmethod
async def send( async def send(
self, event: "Event", message: Union[str, "Message", "MessageSegment"], **kwargs self,
event: "Event",
message: Union[str, "Message", "MessageSegment"],
**kwargs: Any,
) -> Any: ) -> Any:
""" """调用机器人基础发送消息接口
:说明:
调用机器人基础发送消息接口 参数:
event: 上报事件
:参数: message: 要发送的消息
kwargs: 任意额外参数
* ``event: Event``: 上报事件
* ``message: Union[str, Message, MessageSegment]``: 要发送的消息
* ``**kwargs``
""" """
raise NotImplementedError raise NotImplementedError
@classmethod @classmethod
def on_calling_api(cls, func: T_CallingAPIHook) -> T_CallingAPIHook: def on_calling_api(cls, func: T_CallingAPIHook) -> T_CallingAPIHook:
""" """调用 api 预处理。
:说明:
调用 api 预处理 钩子函数参数:
:参数: - bot: 当前 bot 对象
- api: 调用的 api 名称
* ``bot: Bot``: 当前 bot 对象 - data: api 调用的参数字典
* ``api: str``: 调用的 api 名称
* ``data: Dict[str, Any]``: api 调用的参数字典
""" """
cls._calling_api_hook.add(func) cls._calling_api_hook.add(func)
return func return func
@classmethod @classmethod
def on_called_api(cls, func: T_CalledAPIHook) -> T_CalledAPIHook: def on_called_api(cls, func: T_CalledAPIHook) -> T_CalledAPIHook:
""" """调用 api 后处理。
:说明:
调用 api 后处理 钩子函数参数:
:参数: - bot: 当前 bot 对象
- exception: 调用 api 时发生的错误
* ``bot: Bot``: 当前 bot 对象 - api: 调用的 api 名称
* ``exception: Optional[Exception]``: 调用 api 时发生的错误 - data: api 调用的参数字典
* ``api: str``: 调用的 api 名称 - result: api 调用的返回
* ``data: Dict[str, Any]``: api 调用的参数字典
* ``result: Any``: api 调用的返回
""" """
cls._called_api_hook.add(func) cls._called_api_hook.add(func)
return func return func

View File

@@ -0,0 +1,82 @@
import abc
from typing import Any, Type, TypeVar
from pydantic import BaseModel
from nonebot.utils import DataclassEncoder
from .message import Message
E = TypeVar("E", bound="Event")
class Event(abc.ABC, BaseModel):
"""Event 基类。提供获取关键信息的方法,其余信息可直接获取。"""
class Config:
extra = "allow"
json_encoders = {Message: DataclassEncoder}
@classmethod
def validate(cls: Type["E"], value: Any) -> "E":
if isinstance(value, Event) and not isinstance(value, cls):
raise TypeError(f"{value} is incompatible with Event type {cls}")
return super().validate(value)
@abc.abstractmethod
def get_type(self) -> str:
"""获取事件类型的方法,类型通常为 NoneBot 内置的四种类型。"""
raise NotImplementedError
@abc.abstractmethod
def get_event_name(self) -> str:
"""获取事件名称的方法。"""
raise NotImplementedError
@abc.abstractmethod
def get_event_description(self) -> str:
"""获取事件描述的方法,通常为事件具体内容。"""
raise NotImplementedError
def __str__(self) -> str:
return f"[{self.get_event_name()}]: {self.get_event_description()}"
def get_log_string(self) -> str:
"""获取事件日志信息的方法。
通常你不需要修改这个方法,只有当希望 NoneBot 隐藏该事件日志时,
可以抛出 `NoLogException` 异常。
异常:
NoLogException: 希望 NoneBot 隐藏该事件日志
"""
return f"[{self.get_event_name()}]: {self.get_event_description()}"
@abc.abstractmethod
def get_user_id(self) -> str:
"""获取事件主体 id 的方法,通常是用户 id 。"""
raise NotImplementedError
@abc.abstractmethod
def get_session_id(self) -> str:
"""获取会话 id 的方法,用于判断当前事件属于哪一个会话,
通常是用户 id、群组 id 组合。
"""
raise NotImplementedError
@abc.abstractmethod
def get_message(self) -> "Message":
"""获取事件消息内容的方法。"""
raise NotImplementedError
def get_plaintext(self) -> str:
"""获取消息纯文本的方法。
通常不需要修改,默认通过 `get_message().extract_plain_text` 获取。
"""
return self.get_message().extract_plain_text()
@abc.abstractmethod
def is_tome(self) -> bool:
"""获取事件是否与机器人有关的方法。"""
raise NotImplementedError

View File

@@ -0,0 +1,430 @@
import abc
from copy import deepcopy
from typing_extensions import Self
from dataclasses import field, asdict, dataclass
from typing import (
Any,
Dict,
List,
Type,
Tuple,
Union,
Generic,
TypeVar,
Iterable,
Optional,
SupportsIndex,
overload,
)
from pydantic import parse_obj_as
from .template import MessageTemplate
TMS = TypeVar("TMS", bound="MessageSegment")
TM = TypeVar("TM", bound="Message")
@dataclass
class MessageSegment(abc.ABC, Generic[TM]):
"""消息段基类"""
type: str
"""消息段类型"""
data: Dict[str, Any] = field(default_factory=dict)
"""消息段数据"""
@classmethod
@abc.abstractmethod
def get_message_class(cls) -> Type[TM]:
"""获取消息数组类型"""
raise NotImplementedError
@abc.abstractmethod
def __str__(self) -> str:
"""该消息段所代表的 str在命令匹配部分使用"""
raise NotImplementedError
def __len__(self) -> int:
return len(str(self))
def __ne__(self, other: Self) -> bool:
return not self == other
def __add__(self: TMS, other: Union[str, TMS, Iterable[TMS]]) -> TM:
return self.get_message_class()(self) + other
def __radd__(self: TMS, other: Union[str, TMS, Iterable[TMS]]) -> TM:
return self.get_message_class()(other) + self
@classmethod
def __get_validators__(cls):
yield cls._validate
@classmethod
def _validate(cls, value) -> Self:
if isinstance(value, cls):
return value
if not isinstance(value, dict):
raise ValueError(f"Expected dict for MessageSegment, got {type(value)}")
if "type" not in value:
raise ValueError(
f"Expected dict with 'type' for MessageSegment, got {value}"
)
return cls(type=value["type"], data=value.get("data", {}))
def get(self, key: str, default: Any = None):
return asdict(self).get(key, default)
def keys(self):
return asdict(self).keys()
def values(self):
return asdict(self).values()
def items(self):
return asdict(self).items()
def join(self: TMS, iterable: Iterable[Union[TMS, TM]]) -> TM:
return self.get_message_class()(self).join(iterable)
def copy(self) -> Self:
return deepcopy(self)
@abc.abstractmethod
def is_text(self) -> bool:
"""当前消息段是否为纯文本"""
raise NotImplementedError
class Message(List[TMS], abc.ABC):
"""消息序列
参数:
message: 消息内容
"""
def __init__(
self,
message: Union[str, None, Iterable[TMS], TMS] = None,
):
super().__init__()
if message is None:
return
elif isinstance(message, str):
self.extend(self._construct(message))
elif isinstance(message, MessageSegment):
self.append(message)
elif isinstance(message, Iterable):
self.extend(message)
else:
self.extend(self._construct(message)) # pragma: no cover
@classmethod
def template(cls, format_string: Union[str, TM]) -> MessageTemplate[Self]:
"""创建消息模板。
用法和 `str.format` 大致相同,支持以 `Message` 对象作为消息模板并输出消息对象。
并且提供了拓展的格式化控制符,
可以通过该消息类型的 `MessageSegment` 工厂方法创建消息。
参数:
format_string: 格式化模板
返回:
消息格式化器
"""
return MessageTemplate(format_string, cls)
@classmethod
@abc.abstractmethod
def get_segment_class(cls) -> Type[TMS]:
"""获取消息段类型"""
raise NotImplementedError
def __str__(self) -> str:
return "".join(str(seg) for seg in self)
@classmethod
def __get_validators__(cls):
yield cls._validate
@classmethod
def _validate(cls, value) -> Self:
if isinstance(value, cls):
return value
elif isinstance(value, Message):
raise ValueError(f"Type {type(value)} can not be converted to {cls}")
elif isinstance(value, str):
pass
elif isinstance(value, dict):
value = parse_obj_as(cls.get_segment_class(), value)
elif isinstance(value, Iterable):
value = [parse_obj_as(cls.get_segment_class(), v) for v in value]
else:
raise ValueError(
f"Expected str, dict or iterable for Message, got {type(value)}"
)
return cls(value)
@staticmethod
@abc.abstractmethod
def _construct(msg: str) -> Iterable[TMS]:
"""构造消息数组"""
raise NotImplementedError
def __add__(self, other: Union[str, TMS, Iterable[TMS]]) -> Self:
result = self.copy()
result += other
return result
def __radd__(self, other: Union[str, TMS, Iterable[TMS]]) -> Self:
result = self.__class__(other)
return result + self
def __iadd__(self, other: Union[str, TMS, Iterable[TMS]]) -> Self:
if isinstance(other, str):
self.extend(self._construct(other))
elif isinstance(other, MessageSegment):
self.append(other)
elif isinstance(other, Iterable):
self.extend(other)
else:
raise TypeError(f"Unsupported type {type(other)!r}")
return self
@overload
def __getitem__(self, args: str) -> Self:
"""获取仅包含指定消息段类型的消息
参数:
args: 消息段类型
返回:
所有类型为 `args` 的消息段
"""
@overload
def __getitem__(self, args: Tuple[str, int]) -> TMS:
"""索引指定类型的消息段
参数:
args: 消息段类型和索引
返回:
类型为 `args[0]` 的消息段第 `args[1]` 个
"""
@overload
def __getitem__(self, args: Tuple[str, slice]) -> Self:
"""切片指定类型的消息段
参数:
args: 消息段类型和切片
返回:
类型为 `args[0]` 的消息段切片 `args[1]`
"""
@overload
def __getitem__(self, args: int) -> TMS:
"""索引消息段
参数:
args: 索引
返回:
第 `args` 个消息段
"""
@overload
def __getitem__(self, args: slice) -> Self:
"""切片消息段
参数:
args: 切片
返回:
消息切片 `args`
"""
def __getitem__(
self,
args: Union[
str,
Tuple[str, int],
Tuple[str, slice],
int,
slice,
],
) -> Union[TMS, Self]:
arg1, arg2 = args if isinstance(args, tuple) else (args, None)
if isinstance(arg1, int) and arg2 is None:
return super().__getitem__(arg1)
elif isinstance(arg1, slice) and arg2 is None:
return self.__class__(super().__getitem__(arg1))
elif isinstance(arg1, str) and arg2 is None:
return self.__class__(seg for seg in self if seg.type == arg1)
elif isinstance(arg1, str) and isinstance(arg2, int):
return [seg for seg in self if seg.type == arg1][arg2]
elif isinstance(arg1, str) and isinstance(arg2, slice):
return self.__class__([seg for seg in self if seg.type == arg1][arg2])
else:
raise ValueError("Incorrect arguments to slice") # pragma: no cover
def __contains__(self, value: Union[TMS, str]) -> bool:
"""检查消息段是否存在
参数:
value: 消息段或消息段类型
返回:
消息内是否存在给定消息段或给定类型的消息段
"""
if isinstance(value, str):
return bool(next((seg for seg in self if seg.type == value), None))
return super().__contains__(value)
def has(self, value: Union[TMS, str]) -> bool:
"""{ref}``__contains__` <nonebot.adapters.Message.__contains__>` 相同"""
return value in self
def index(self, value: Union[TMS, str], *args: SupportsIndex) -> int:
"""索引消息段
参数:
value: 消息段或者消息段类型
arg: start 与 end
返回:
索引 index
异常:
ValueError: 消息段不存在
"""
if isinstance(value, str):
first_segment = next((seg for seg in self if seg.type == value), None)
if first_segment is None:
raise ValueError(f"Segment with type {value!r} is not in message")
return super().index(first_segment, *args)
return super().index(value, *args)
def get(self, type_: str, count: Optional[int] = None) -> Self:
"""获取指定类型的消息段
参数:
type_: 消息段类型
count: 获取个数
返回:
构建的新消息
"""
if count is None:
return self[type_]
iterator, filtered = (
seg for seg in self if seg.type == type_
), self.__class__()
for _ in range(count):
seg = next(iterator, None)
if seg is None:
break
filtered.append(seg)
return filtered
def count(self, value: Union[TMS, str]) -> int:
"""计算指定消息段的个数
参数:
value: 消息段或消息段类型
返回:
个数
"""
return len(self[value]) if isinstance(value, str) else super().count(value)
def only(self, value: Union[TMS, str]) -> bool:
"""检查消息中是否仅包含指定消息段
参数:
value: 指定消息段或消息段类型
返回:
是否仅包含指定消息段
"""
if isinstance(value, str):
return all(seg.type == value for seg in self)
return all(seg == value for seg in self)
def append(self, obj: Union[str, TMS]) -> Self:
"""添加一个消息段到消息数组末尾。
参数:
obj: 要添加的消息段
"""
if isinstance(obj, MessageSegment):
super().append(obj)
elif isinstance(obj, str):
self.extend(self._construct(obj))
else:
raise ValueError(f"Unexpected type: {type(obj)} {obj}") # pragma: no cover
return self
def extend(self, obj: Union[Self, Iterable[TMS]]) -> Self:
"""拼接一个消息数组或多个消息段到消息数组末尾。
参数:
obj: 要添加的消息数组
"""
for segment in obj:
self.append(segment)
return self
def join(self, iterable: Iterable[Union[TMS, Self]]) -> Self:
"""将多个消息连接并将自身作为分割
参数:
iterable: 要连接的消息
返回:
连接后的消息
"""
ret = self.__class__()
for index, msg in enumerate(iterable):
if index != 0:
ret.extend(self)
if isinstance(msg, MessageSegment):
ret.append(msg.copy())
else:
ret.extend(msg.copy())
return ret
def copy(self) -> Self:
"""深拷贝消息"""
return deepcopy(self)
def include(self, *types: str) -> Self:
"""过滤消息
参数:
types: 包含的消息段类型
返回:
新构造的消息
"""
return self.__class__(seg for seg in self if seg.type in types)
def exclude(self, *types: str) -> Self:
"""过滤消息
参数:
types: 不包含的消息段类型
返回:
新构造的消息
"""
return self.__class__(seg for seg in self if seg.type not in types)
def extract_plain_text(self) -> str:
"""提取消息内纯文本消息"""
return "".join(str(seg) for seg in self if seg.is_text())

View File

@@ -1,6 +1,6 @@
import inspect
import functools import functools
from string import Formatter from string import Formatter
from typing_extensions import TypeAlias
from typing import ( from typing import (
TYPE_CHECKING, TYPE_CHECKING,
Any, Any,
@@ -21,17 +21,22 @@ from typing import (
) )
if TYPE_CHECKING: if TYPE_CHECKING:
from . import Message, MessageSegment from .message import Message, MessageSegment
TM = TypeVar("TM", bound="Message") TM = TypeVar("TM", bound="Message")
TF = TypeVar("TF", str, "Message") TF = TypeVar("TF", str, "Message")
FormatSpecFunc = Callable[[Any], str] FormatSpecFunc: TypeAlias = Callable[[Any], str]
FormatSpecFunc_T = TypeVar("FormatSpecFunc_T", bound=FormatSpecFunc) FormatSpecFunc_T = TypeVar("FormatSpecFunc_T", bound=FormatSpecFunc)
class MessageTemplate(Formatter, Generic[TF]): class MessageTemplate(Formatter, Generic[TF]):
"""消息模板格式化实现类""" """消息模板格式化实现类
参数:
template: 模板
factory: 消息类型工厂默认为 `str`
"""
@overload @overload
def __init__( def __init__(
@@ -45,21 +50,16 @@ class MessageTemplate(Formatter, Generic[TF]):
) -> None: ) -> None:
... ...
def __init__(self, template, factory=str) -> None: def __init__(
""" self, template: Union[str, TM], factory: Union[Type[str], Type[TM]] = str
:说明: ) -> None:
self.template: TF = template # type: ignore
创建一个模板 self.factory: Type[TF] = factory # type: ignore
:参数:
* ``template: Union[str, Message]``: 模板
* ``factory: Union[str, Message]``: 消息构造类型默认为 `str`
"""
self.template: TF = template
self.factory: Type[TF] = factory
self.format_specs: Dict[str, FormatSpecFunc] = {} self.format_specs: Dict[str, FormatSpecFunc] = {}
def __repr__(self) -> str:
return f"MessageTemplate({self.template!r}, factory={self.factory!r})"
def add_format_spec( def add_format_spec(
self, spec: FormatSpecFunc_T, name: Optional[str] = None self, spec: FormatSpecFunc_T, name: Optional[str] = None
) -> FormatSpecFunc_T: ) -> FormatSpecFunc_T:
@@ -69,31 +69,46 @@ class MessageTemplate(Formatter, Generic[TF]):
self.format_specs[name] = spec self.format_specs[name] = spec
return spec return spec
def format(self, *args: Any, **kwargs: Any) -> TF: def format(self, *args, **kwargs):
""" """根据传入参数和模板生成消息对象"""
:说明: return self._format(args, kwargs)
def format_map(self, mapping: Mapping[str, Any]) -> TF:
"""根据传入字典和模板生成消息对象, 在传入字段名不是有效标识符时有用"""
return self._format([], mapping)
def _format(self, args: Sequence[Any], kwargs: Mapping[str, Any]) -> TF:
full_message = self.factory()
used_args, arg_index = set(), 0
根据模板和参数生成消息对象
"""
msg = self.factory()
if isinstance(self.template, str): if isinstance(self.template, str):
msg += self.vformat(self.template, args, kwargs) msg, arg_index = self._vformat(
self.template, args, kwargs, used_args, arg_index
)
full_message += msg
elif isinstance(self.template, self.factory): elif isinstance(self.template, self.factory):
template = cast("Message[MessageSegment]", self.template) template = cast("Message[MessageSegment]", self.template)
for seg in template: for seg in template:
msg += self.vformat(str(seg), args, kwargs) if seg.is_text() else seg if not seg.is_text():
full_message += seg
else:
msg, arg_index = self._vformat(
str(seg), args, kwargs, used_args, arg_index
)
full_message += msg
else: else:
raise TypeError("template must be a string or instance of Message!") raise TypeError("template must be a string or instance of Message!")
return msg # type:ignore self.check_unused_args(used_args, args, kwargs)
return cast(TF, full_message)
def vformat( def vformat(
self, format_string: str, args: Sequence[Any], kwargs: Mapping[str, Any] self,
format_string: str,
args: Sequence[Any],
kwargs: Mapping[str, Any],
) -> TF: ) -> TF:
used_args = set() raise NotImplementedError("`vformat` has merged into `_format`")
result, _ = self._vformat(format_string, args, kwargs, used_args, 2)
self.check_unused_args(list(used_args), args, kwargs)
return result
def _vformat( def _vformat(
self, self,
@@ -101,18 +116,13 @@ class MessageTemplate(Formatter, Generic[TF]):
args: Sequence[Any], args: Sequence[Any],
kwargs: Mapping[str, Any], kwargs: Mapping[str, Any],
used_args: Set[Union[int, str]], used_args: Set[Union[int, str]],
recursion_depth: int,
auto_arg_index: int = 0, auto_arg_index: int = 0,
) -> Tuple[TF, int]: ) -> Tuple[TF, int]:
if recursion_depth < 0: results: List[Any] = [self.factory()]
raise ValueError("Max string recursion exceeded")
results: List[Any] = [] for literal_text, field_name, format_spec, conversion in self.parse(
for (literal_text, field_name, format_spec, conversion) in self.parse(
format_string format_string
): ):
# output the literal text # output the literal text
if literal_text: if literal_text:
results.append(literal_text) results.append(literal_text)
@@ -146,36 +156,23 @@ class MessageTemplate(Formatter, Generic[TF]):
obj, arg_used = self.get_field(field_name, args, kwargs) obj, arg_used = self.get_field(field_name, args, kwargs)
used_args.add(arg_used) used_args.add(arg_used)
assert format_spec is not None
# do any conversion on the resulting object # do any conversion on the resulting object
obj = self.convert_field(obj, conversion) if conversion else obj obj = self.convert_field(obj, conversion) if conversion else obj
# expand the format spec, if needed
format_control, auto_arg_index = self._vformat(
format_spec,
args,
kwargs,
used_args,
recursion_depth - 1,
auto_arg_index,
)
# format the object and append to the result # format the object and append to the result
formatted_text = self.format_field(obj, str(format_control)) formatted_text = (
self.format_field(obj, format_spec) if format_spec else obj
)
results.append(formatted_text) results.append(formatted_text)
return ( return functools.reduce(self._add, results), auto_arg_index
self.factory(functools.reduce(self._add, results or [""])),
auto_arg_index,
)
def format_field(self, value: Any, format_spec: str) -> Any: def format_field(self, value: Any, format_spec: str) -> Any:
formatter: Optional[FormatSpecFunc] = self.format_specs.get(format_spec) formatter: Optional[FormatSpecFunc] = self.format_specs.get(format_spec)
if formatter is None and not issubclass(self.factory, str): if formatter is None and not issubclass(self.factory, str):
segment_class: Type["MessageSegment"] = self.factory.get_segment_class() segment_class: Type["MessageSegment"] = self.factory.get_segment_class()
method = getattr(segment_class, format_spec, None) method = getattr(segment_class, format_spec, None)
if inspect.ismethod(method): if callable(method) and not cast(str, method.__name__).startswith("_"):
formatter = getattr(segment_class, format_spec) formatter = getattr(segment_class, format_spec)
return ( return (
super().format_field(value, format_spec) super().format_field(value, format_spec)

View File

@@ -0,0 +1,25 @@
from .model import URL as URL
from .model import RawURL as RawURL
from .driver import Driver as Driver
from .model import Cookies as Cookies
from .model import Request as Request
from .model import FileType as FileType
from .model import Response as Response
from .model import DataTypes as DataTypes
from .model import FileTypes as FileTypes
from .model import WebSocket as WebSocket
from .model import FilesTypes as FilesTypes
from .model import QueryTypes as QueryTypes
from .model import CookieTypes as CookieTypes
from .model import FileContent as FileContent
from .model import HTTPVersion as HTTPVersion
from .model import HeaderTypes as HeaderTypes
from .model import SimpleQuery as SimpleQuery
from .model import ContentTypes as ContentTypes
from .driver import ForwardMixin as ForwardMixin
from .model import QueryVariable as QueryVariable
from .driver import ForwardDriver as ForwardDriver
from .driver import ReverseDriver as ReverseDriver
from .driver import combine_driver as combine_driver
from .model import HTTPServerSetup as HTTPServerSetup
from .model import WebSocketServerSetup as WebSocketServerSetup

View File

@@ -0,0 +1,261 @@
import abc
import asyncio
from contextlib import AsyncExitStack, asynccontextmanager
from typing import TYPE_CHECKING, Any, Set, Dict, Type, Callable, AsyncGenerator
from nonebot.log import logger
from nonebot.config import Env, Config
from nonebot.dependencies import Dependent
from nonebot.exception import SkippedException
from nonebot.utils import escape_tag, run_coro_with_catch
from nonebot.internal.params import BotParam, DependParam, DefaultParam
from nonebot.typing import (
T_DependencyCache,
T_BotConnectionHook,
T_BotDisconnectionHook,
)
from .model import Request, Response, WebSocket, HTTPServerSetup, WebSocketServerSetup
if TYPE_CHECKING:
from nonebot.internal.adapter import Bot, Adapter
BOT_HOOK_PARAMS = [DependParam, BotParam, DefaultParam]
class Driver(abc.ABC):
"""Driver 基类。
参数:
env: 包含环境信息的 Env 对象
config: 包含配置信息的 Config 对象
"""
_adapters: Dict[str, "Adapter"] = {}
"""已注册的适配器列表"""
_bot_connection_hook: Set[Dependent[Any]] = set()
"""Bot 连接建立时执行的函数"""
_bot_disconnection_hook: Set[Dependent[Any]] = set()
"""Bot 连接断开时执行的函数"""
def __init__(self, env: Env, config: Config):
self.env: str = env.environment
"""环境名称"""
self.config: Config = config
"""全局配置对象"""
self._bots: Dict[str, "Bot"] = {}
def __repr__(self) -> str:
return (
f"Driver(type={self.type!r}, "
f"adapters={len(self._adapters)}, bots={len(self._bots)})"
)
@property
def bots(self) -> Dict[str, "Bot"]:
"""获取当前所有已连接的 Bot"""
return self._bots
def register_adapter(self, adapter: Type["Adapter"], **kwargs) -> None:
"""注册一个协议适配器
参数:
adapter: 适配器类
kwargs: 其他传递给适配器的参数
"""
name = adapter.get_name()
if name in self._adapters:
logger.opt(colors=True).debug(
f'Adapter "<y>{escape_tag(name)}</y>" already exists'
)
return
self._adapters[name] = adapter(self, **kwargs)
logger.opt(colors=True).debug(
f'Succeeded to load adapter "<y>{escape_tag(name)}</y>"'
)
@property
@abc.abstractmethod
def type(self) -> str:
"""驱动类型名称"""
raise NotImplementedError
@property
@abc.abstractmethod
def logger(self):
"""驱动专属 logger 日志记录器"""
raise NotImplementedError
@abc.abstractmethod
def run(self, *args, **kwargs):
"""启动驱动框架"""
logger.opt(colors=True).debug(
f"<g>Loaded adapters: {escape_tag(', '.join(self._adapters))}</g>"
)
@abc.abstractmethod
def on_startup(self, func: Callable) -> Callable:
"""注册一个在驱动器启动时执行的函数"""
raise NotImplementedError
@abc.abstractmethod
def on_shutdown(self, func: Callable) -> Callable:
"""注册一个在驱动器停止时执行的函数"""
raise NotImplementedError
@classmethod
def on_bot_connect(cls, func: T_BotConnectionHook) -> T_BotConnectionHook:
"""装饰一个函数使他在 bot 连接成功时执行。
钩子函数参数:
- bot: 当前连接上的 Bot 对象
"""
cls._bot_connection_hook.add(
Dependent[Any].parse(call=func, allow_types=BOT_HOOK_PARAMS)
)
return func
@classmethod
def on_bot_disconnect(cls, func: T_BotDisconnectionHook) -> T_BotDisconnectionHook:
"""装饰一个函数使他在 bot 连接断开时执行。
钩子函数参数:
- bot: 当前连接上的 Bot 对象
"""
cls._bot_disconnection_hook.add(
Dependent[Any].parse(call=func, allow_types=BOT_HOOK_PARAMS)
)
return func
def _bot_connect(self, bot: "Bot") -> None:
"""在连接成功后,调用该函数来注册 bot 对象"""
if bot.self_id in self._bots:
raise RuntimeError(f"Duplicate bot connection with id {bot.self_id}")
self._bots[bot.self_id] = bot
async def _run_hook(bot: "Bot") -> None:
dependency_cache: T_DependencyCache = {}
async with AsyncExitStack() as stack:
if coros := [
run_coro_with_catch(
hook(bot=bot, stack=stack, dependency_cache=dependency_cache),
(SkippedException,),
)
for hook in self._bot_connection_hook
]:
try:
await asyncio.gather(*coros)
except Exception as e:
logger.opt(colors=True, exception=e).error(
"<r><bg #f8bbd0>"
"Error when running WebSocketConnection hook. "
"Running cancelled!"
"</bg #f8bbd0></r>"
)
asyncio.create_task(_run_hook(bot))
def _bot_disconnect(self, bot: "Bot") -> None:
"""在连接断开后,调用该函数来注销 bot 对象"""
if bot.self_id in self._bots:
del self._bots[bot.self_id]
async def _run_hook(bot: "Bot") -> None:
dependency_cache: T_DependencyCache = {}
async with AsyncExitStack() as stack:
if coros := [
run_coro_with_catch(
hook(bot=bot, stack=stack, dependency_cache=dependency_cache),
(SkippedException,),
)
for hook in self._bot_disconnection_hook
]:
try:
await asyncio.gather(*coros)
except Exception as e:
logger.opt(colors=True, exception=e).error(
"<r><bg #f8bbd0>"
"Error when running WebSocketDisConnection hook. "
"Running cancelled!"
"</bg #f8bbd0></r>"
)
asyncio.create_task(_run_hook(bot))
class ForwardMixin(abc.ABC):
"""客户端混入基类。"""
@property
@abc.abstractmethod
def type(self) -> str:
"""客户端驱动类型名称"""
raise NotImplementedError
@abc.abstractmethod
async def request(self, setup: Request) -> Response:
"""发送一个 HTTP 请求"""
raise NotImplementedError
@abc.abstractmethod
@asynccontextmanager
async def websocket(self, setup: Request) -> AsyncGenerator[WebSocket, None]:
"""发起一个 WebSocket 连接"""
raise NotImplementedError
yield # used for static type checking's generator detection
class ForwardDriver(Driver, ForwardMixin):
"""客户端基类。将客户端框架封装,以满足适配器使用。"""
class ReverseDriver(Driver):
"""服务端基类。将后端框架封装,以满足适配器使用。"""
@property
@abc.abstractmethod
def server_app(self) -> Any:
"""驱动 APP 对象"""
raise NotImplementedError
@property
@abc.abstractmethod
def asgi(self) -> Any:
"""驱动 ASGI 对象"""
raise NotImplementedError
@abc.abstractmethod
def setup_http_server(self, setup: "HTTPServerSetup") -> None:
"""设置一个 HTTP 服务器路由配置"""
raise NotImplementedError
@abc.abstractmethod
def setup_websocket_server(self, setup: "WebSocketServerSetup") -> None:
"""设置一个 WebSocket 服务器路由配置"""
raise NotImplementedError
def combine_driver(driver: Type[Driver], *mixins: Type[ForwardMixin]) -> Type[Driver]:
"""将一个驱动器和多个混入类合并。"""
# check first
assert issubclass(driver, Driver), "`driver` must be subclass of Driver"
assert all(
issubclass(m, ForwardMixin) for m in mixins
), "`mixins` must be subclass of ForwardMixin"
if not mixins:
return driver
def type_(self: ForwardDriver) -> str:
return (
driver.type.__get__(self)
+ "+"
+ "+".join(x.type.__get__(self) for x in mixins)
)
return type(
"CombinedDriver", (*mixins, driver, ForwardDriver), {"type": property(type_)}
) # type: ignore

View File

@@ -1,5 +1,8 @@
import abc import abc
import urllib.request
from enum import Enum from enum import Enum
from dataclasses import dataclass
from typing_extensions import TypeAlias
from http.cookiejar import Cookie, CookieJar from http.cookiejar import Cookie, CookieJar
from typing import ( from typing import (
IO, IO,
@@ -9,36 +12,40 @@ from typing import (
Tuple, Tuple,
Union, Union,
Mapping, Mapping,
Callable,
Iterator, Iterator,
Optional, Optional,
Awaitable,
MutableMapping, MutableMapping,
) )
from yarl import URL as URL from yarl import URL as URL
from multidict import CIMultiDict from multidict import CIMultiDict
RawURL = Tuple[bytes, bytes, Optional[int], bytes] RawURL: TypeAlias = Tuple[bytes, bytes, Optional[int], bytes]
SimpleQuery = Union[str, int, float] SimpleQuery: TypeAlias = Union[str, int, float]
QueryVariable = Union[SimpleQuery, List[SimpleQuery]] QueryVariable: TypeAlias = Union[SimpleQuery, List[SimpleQuery]]
QueryTypes = Union[ QueryTypes: TypeAlias = Union[
None, str, Mapping[str, QueryVariable], List[Tuple[str, QueryVariable]] None, str, Mapping[str, QueryVariable], List[Tuple[str, QueryVariable]]
] ]
HeaderTypes = Union[ HeaderTypes: TypeAlias = Union[
None, None,
CIMultiDict[str], CIMultiDict[str],
Dict[str, str], Dict[str, str],
List[Tuple[str, str]], List[Tuple[str, str]],
] ]
CookieTypes = Union[None, "Cookies", CookieJar, Dict[str, str], List[Tuple[str, str]]] CookieTypes: TypeAlias = Union[
None, "Cookies", CookieJar, Dict[str, str], List[Tuple[str, str]]
]
ContentTypes = Union[str, bytes, None] ContentTypes: TypeAlias = Union[str, bytes, None]
DataTypes = Union[dict, None] DataTypes: TypeAlias = Union[dict, None]
FileContent = Union[IO[bytes], bytes] FileContent: TypeAlias = Union[IO[bytes], bytes]
FileType = Tuple[Optional[str], FileContent, Optional[str]] FileType: TypeAlias = Tuple[Optional[str], FileContent, Optional[str]]
FileTypes = Union[ FileTypes: TypeAlias = Union[
# file (or bytes) # file (or bytes)
FileContent, FileContent,
# (filename, file (or bytes)) # (filename, file (or bytes))
@@ -46,7 +53,7 @@ FileTypes = Union[
# (filename, file (or bytes), content_type) # (filename, file (or bytes), content_type)
FileType, FileType,
] ]
FilesTypes = Union[Dict[str, FileTypes], List[Tuple[str, FileTypes]], None] FilesTypes: TypeAlias = Union[Dict[str, FileTypes], List[Tuple[str, FileTypes]], None]
class HTTPVersion(Enum): class HTTPVersion(Enum):
@@ -102,12 +109,9 @@ class Request:
self.url: URL = url self.url: URL = url
# headers # headers
self.headers: CIMultiDict[str] self.headers: CIMultiDict[str] = (
if headers is not None: CIMultiDict(headers) if headers is not None else CIMultiDict()
self.headers = CIMultiDict(headers) )
else:
self.headers = CIMultiDict()
# cookies # cookies
self.cookies = Cookies(cookies) self.cookies = Cookies(cookies)
@@ -128,9 +132,7 @@ class Request:
self.files.append((name, file_info)) # type: ignore self.files.append((name, file_info)) # type: ignore
def __repr__(self) -> str: def __repr__(self) -> str:
class_name = self.__class__.__name__ return f"{self.__class__.__name__}(method={self.method!r}, url='{self.url!s}')"
url = str(self.url)
return f"<{class_name}({self.method!r}, {url!r})>"
class Response: class Response:
@@ -146,31 +148,30 @@ class Response:
self.status_code: int = status_code self.status_code: int = status_code
# headers # headers
self.headers: CIMultiDict[str] self.headers: CIMultiDict[str] = (
if headers is not None: CIMultiDict(headers) if headers is not None else CIMultiDict()
self.headers = CIMultiDict(headers) )
else:
self.headers = CIMultiDict()
# body # body
self.content: ContentTypes = content self.content: ContentTypes = content
# request # request
self.request: Optional[Request] = request self.request: Optional[Request] = request
def __repr__(self) -> str:
return f"{self.__class__.__name__}(status_code={self.status_code!r})"
class WebSocket(abc.ABC): class WebSocket(abc.ABC):
def __init__(self, *, request: Request): def __init__(self, *, request: Request):
# request
self.request: Request = request self.request: Request = request
def __repr__(self) -> str:
return f"{self.__class__.__name__}('{self.request.url!s}')"
@property @property
@abc.abstractmethod @abc.abstractmethod
def closed(self) -> bool: def closed(self) -> bool:
""" """连接是否已经关闭"""
:类型: ``bool``
:说明: 连接是否已经关闭
"""
raise NotImplementedError raise NotImplementedError
@abc.abstractmethod @abc.abstractmethod
@@ -184,7 +185,12 @@ class WebSocket(abc.ABC):
raise NotImplementedError raise NotImplementedError
@abc.abstractmethod @abc.abstractmethod
async def receive(self) -> str: async def receive(self) -> Union[str, bytes]:
"""接收一条 WebSocket text/bytes 信息"""
raise NotImplementedError
@abc.abstractmethod
async def receive_text(self) -> str:
"""接收一条 WebSocket text 信息""" """接收一条 WebSocket text 信息"""
raise NotImplementedError raise NotImplementedError
@@ -193,8 +199,17 @@ class WebSocket(abc.ABC):
"""接收一条 WebSocket binary 信息""" """接收一条 WebSocket binary 信息"""
raise NotImplementedError raise NotImplementedError
async def send(self, data: Union[str, bytes]) -> None:
"""发送一条 WebSocket text/bytes 信息"""
if isinstance(data, str):
await self.send_text(data)
elif isinstance(data, bytes):
await self.send_bytes(data)
else:
raise TypeError("WebSocker send method expects str or bytes!")
@abc.abstractmethod @abc.abstractmethod
async def send(self, data: str) -> None: async def send_text(self, data: str) -> None:
"""发送一条 WebSocket text 信息""" """发送一条 WebSocket text 信息"""
raise NotImplementedError raise NotImplementedError
@@ -246,8 +261,8 @@ class Cookies(MutableMapping):
self, self,
name: str, name: str,
default: Optional[str] = None, default: Optional[str] = None,
domain: str = None, domain: Optional[str] = None,
path: str = None, path: Optional[str] = None,
) -> Optional[str]: ) -> Optional[str]:
value: Optional[str] = None value: Optional[str] = None
for cookie in self.jar: for cookie in self.jar:
@@ -288,6 +303,11 @@ class Cookies(MutableMapping):
for cookie in cookies.jar: for cookie in cookies.jar:
self.jar.set_cookie(cookie) self.jar.set_cookie(cookie)
def as_header(self, request: Request) -> Dict[str, str]:
urllib_request = self._CookieCompatRequest(request)
self.jar.add_cookie_header(urllib_request)
return urllib_request.added_headers
def __setitem__(self, name: str, value: str) -> None: def __setitem__(self, name: str, value: str) -> None:
return self.set(name, value) return self.set(name, value)
@@ -304,14 +324,44 @@ class Cookies(MutableMapping):
return len(self.jar) return len(self.jar)
def __iter__(self) -> Iterator[Cookie]: def __iter__(self) -> Iterator[Cookie]:
return (cookie for cookie in self.jar) return iter(self.jar)
def __repr__(self) -> str: def __repr__(self) -> str:
cookies_repr = ", ".join( cookies_repr = ", ".join(
[ f"Cookie({cookie.name}={cookie.value} for {cookie.domain})"
f"<Cookie {cookie.name}={cookie.value} for {cookie.domain} />" for cookie in self.jar
for cookie in self.jar
]
) )
return f"{self.__class__.__name__}({cookies_repr})"
return f"<Cookies [{cookies_repr}]>" class _CookieCompatRequest(urllib.request.Request):
def __init__(self, request: Request) -> None:
super().__init__(
url=str(request.url),
headers=dict(request.headers),
method=request.method,
)
self.request = request
self.added_headers: Dict[str, str] = {}
def add_unredirected_header(self, key: str, value: str) -> None:
super().add_unredirected_header(key, value)
self.added_headers[key] = value
@dataclass
class HTTPServerSetup:
"""HTTP 服务器路由配置。"""
path: URL # path should not be absolute, check it by URL.is_absolute() == False
method: str
name: str
handle_func: Callable[[Request], Awaitable[Response]]
@dataclass
class WebSocketServerSetup:
"""WebSocket 服务器路由配置。"""
path: URL # path should not be absolute, check it by URL.is_absolute() == False
name: str
handle_func: Callable[[WebSocket], Awaitable[Any]]

View File

@@ -0,0 +1,11 @@
from .manager import MatcherManager as MatcherManager
from .provider import MatcherProvider as MatcherProvider
from .provider import DEFAULT_PROVIDER_CLASS as DEFAULT_PROVIDER_CLASS
matchers = MatcherManager()
from .matcher import Matcher as Matcher
from .matcher import current_bot as current_bot
from .matcher import current_event as current_event
from .matcher import current_handler as current_handler
from .matcher import current_matcher as current_matcher

View File

@@ -0,0 +1,104 @@
from typing import (
TYPE_CHECKING,
Any,
List,
Type,
Tuple,
Union,
TypeVar,
Iterator,
KeysView,
Optional,
ItemsView,
ValuesView,
MutableMapping,
overload,
)
from .provider import DEFAULT_PROVIDER_CLASS, MatcherProvider
if TYPE_CHECKING:
from .matcher import Matcher
T = TypeVar("T")
class MatcherManager(MutableMapping[int, List[Type["Matcher"]]]):
"""事件响应器管理器
实现了常用字典操作,用于管理事件响应器。
"""
def __init__(self):
self.provider: MatcherProvider = DEFAULT_PROVIDER_CLASS({})
def __repr__(self) -> str:
return f"MatcherManager(provider={self.provider!r})"
def __contains__(self, o: object) -> bool:
return o in self.provider
def __iter__(self) -> Iterator[int]:
return iter(self.provider)
def __len__(self) -> int:
return len(self.provider)
def __getitem__(self, key: int) -> List[Type["Matcher"]]:
return self.provider[key]
def __setitem__(self, key: int, value: List[Type["Matcher"]]) -> None:
self.provider[key] = value
def __delitem__(self, key: int) -> None:
del self.provider[key]
def __eq__(self, other: Any) -> bool:
return isinstance(other, MatcherManager) and self.provider == other.provider
def keys(self) -> KeysView[int]:
return self.provider.keys()
def values(self) -> ValuesView[List[Type["Matcher"]]]:
return self.provider.values()
def items(self) -> ItemsView[int, List[Type["Matcher"]]]:
return self.provider.items()
@overload
def get(self, key: int) -> Optional[List[Type["Matcher"]]]:
...
@overload
def get(self, key: int, default: T) -> Union[List[Type["Matcher"]], T]:
...
def get(
self, key: int, default: Optional[T] = None
) -> Optional[Union[List[Type["Matcher"]], T]]:
return self.provider.get(key, default)
def pop(self, key: int) -> List[Type["Matcher"]]:
return self.provider.pop(key)
def popitem(self) -> Tuple[int, List[Type["Matcher"]]]:
return self.provider.popitem()
def clear(self) -> None:
self.provider.clear()
def update(self, __m: MutableMapping[int, List[Type["Matcher"]]]) -> None:
self.provider.update(__m)
def setdefault(
self, key: int, default: List[Type["Matcher"]]
) -> List[Type["Matcher"]]:
return self.provider.setdefault(key, default)
def set_provider(self, provider_class: Type[MatcherProvider]) -> None:
"""设置事件响应器存储器
参数:
provider_class: 事件响应器存储器类
"""
self.provider = provider_class(self.provider)

View File

@@ -0,0 +1,805 @@
from types import ModuleType
from contextvars import ContextVar
from typing_extensions import Self
from datetime import datetime, timedelta
from contextlib import AsyncExitStack, contextmanager
from typing import (
TYPE_CHECKING,
Any,
List,
Type,
Union,
TypeVar,
Callable,
ClassVar,
Iterable,
NoReturn,
Optional,
overload,
)
from nonebot.log import logger
from nonebot.internal.rule import Rule
from nonebot.dependencies import Dependent
from nonebot.internal.permission import User, Permission
from nonebot.internal.adapter import (
Bot,
Event,
Message,
MessageSegment,
MessageTemplate,
)
from nonebot.typing import (
T_State,
T_Handler,
T_TypeUpdater,
T_DependencyCache,
T_PermissionUpdater,
)
from nonebot.consts import (
ARG_KEY,
RECEIVE_KEY,
REJECT_TARGET,
LAST_RECEIVE_KEY,
REJECT_CACHE_TARGET,
)
from nonebot.exception import (
PausedException,
StopPropagation,
SkippedException,
FinishedException,
RejectedException,
)
from nonebot.internal.params import (
Depends,
ArgParam,
BotParam,
EventParam,
StateParam,
DependParam,
DefaultParam,
MatcherParam,
)
from . import matchers
if TYPE_CHECKING:
from nonebot.plugin import Plugin
T = TypeVar("T")
current_bot: ContextVar[Bot] = ContextVar("current_bot")
current_event: ContextVar[Event] = ContextVar("current_event")
current_matcher: ContextVar["Matcher"] = ContextVar("current_matcher")
current_handler: ContextVar[Dependent] = ContextVar("current_handler")
class MatcherMeta(type):
if TYPE_CHECKING:
module_name: Optional[str]
type: str
def __repr__(self) -> str:
return (
f"{self.__name__}(type={self.type!r}"
+ (f", module={self.module_name}" if self.module_name else "")
+ ")"
)
class Matcher(metaclass=MatcherMeta):
"""事件响应器类"""
plugin: ClassVar[Optional["Plugin"]] = None
"""事件响应器所在插件"""
module: ClassVar[Optional[ModuleType]] = None
"""事件响应器所在插件模块"""
plugin_name: ClassVar[Optional[str]] = None
"""事件响应器所在插件名"""
module_name: ClassVar[Optional[str]] = None
"""事件响应器所在点分割插件模块路径"""
type: ClassVar[str] = ""
"""事件响应器类型"""
rule: ClassVar[Rule] = Rule()
"""事件响应器匹配规则"""
permission: ClassVar[Permission] = Permission()
"""事件响应器触发权限"""
handlers: List[Dependent[Any]] = []
"""事件响应器拥有的事件处理函数列表"""
priority: ClassVar[int] = 1
"""事件响应器优先级"""
block: bool = False
"""事件响应器是否阻止事件传播"""
temp: ClassVar[bool] = False
"""事件响应器是否为临时"""
expire_time: ClassVar[Optional[datetime]] = None
"""事件响应器过期时间点"""
_default_state: ClassVar[T_State] = {}
"""事件响应器默认状态"""
_default_type_updater: ClassVar[Optional[Dependent[str]]] = None
"""事件响应器类型更新函数"""
_default_permission_updater: ClassVar[Optional[Dependent[Permission]]] = None
"""事件响应器权限更新函数"""
HANDLER_PARAM_TYPES = (
DependParam,
BotParam,
EventParam,
StateParam,
ArgParam,
MatcherParam,
DefaultParam,
)
def __init__(self):
self.handlers = self.handlers.copy()
self.state = self._default_state.copy()
def __repr__(self) -> str:
return (
f"{self.__class__.__name__}(type={self.type!r}"
+ (f", module={self.module_name}" if self.module_name else "")
+ ")"
)
@classmethod
def new(
cls,
type_: str = "",
rule: Optional[Rule] = None,
permission: Optional[Permission] = None,
handlers: Optional[List[Union[T_Handler, Dependent[Any]]]] = None,
temp: bool = False,
priority: int = 1,
block: bool = False,
*,
plugin: Optional["Plugin"] = None,
module: Optional[ModuleType] = None,
expire_time: Optional[Union[datetime, timedelta]] = None,
default_state: Optional[T_State] = None,
default_type_updater: Optional[Union[T_TypeUpdater, Dependent[str]]] = None,
default_permission_updater: Optional[
Union[T_PermissionUpdater, Dependent[Permission]]
] = None,
) -> Type[Self]:
"""
创建一个新的事件响应器,并存储至 `matchers <#matchers>`_
参数:
type_: 事件响应器类型,与 `event.get_type()` 一致时触发,空字符串表示任意
rule: 匹配规则
permission: 权限
handlers: 事件处理函数列表
temp: 是否为临时事件响应器,即触发一次后删除
priority: 响应优先级
block: 是否阻止事件向更低优先级的响应器传播
plugin: 事件响应器所在插件
module: 事件响应器所在模块
default_state: 默认状态 `state`
expire_time: 事件响应器最终有效时间点,过时即被删除
返回:
Type[Matcher]: 新的事件响应器类
"""
NewMatcher = type(
cls.__name__,
(cls,),
{
"plugin": plugin,
"module": module,
"plugin_name": plugin and plugin.name,
"module_name": module and module.__name__,
"type": type_,
"rule": rule or Rule(),
"permission": permission or Permission(),
"handlers": [
handler
if isinstance(handler, Dependent)
else Dependent[Any].parse(
call=handler, allow_types=cls.HANDLER_PARAM_TYPES
)
for handler in handlers
]
if handlers
else [],
"temp": temp,
"expire_time": (
expire_time
and (
expire_time
if isinstance(expire_time, datetime)
else datetime.now() + expire_time
)
),
"priority": priority,
"block": block,
"_default_state": default_state or {},
"_default_type_updater": (
default_type_updater
and (
default_type_updater
if isinstance(default_type_updater, Dependent)
else Dependent[str].parse(
call=default_type_updater,
allow_types=cls.HANDLER_PARAM_TYPES,
)
)
),
"_default_permission_updater": (
default_permission_updater
and (
default_permission_updater
if isinstance(default_permission_updater, Dependent)
else Dependent[Permission].parse(
call=default_permission_updater,
allow_types=cls.HANDLER_PARAM_TYPES,
)
)
),
},
)
logger.trace(f"Define new matcher {NewMatcher}")
matchers[priority].append(NewMatcher)
return NewMatcher
@classmethod
def destroy(cls) -> None:
"""销毁当前的事件响应器"""
matchers[cls.priority].remove(cls)
@classmethod
async def check_perm(
cls,
bot: Bot,
event: Event,
stack: Optional[AsyncExitStack] = None,
dependency_cache: Optional[T_DependencyCache] = None,
) -> bool:
"""检查是否满足触发权限
参数:
bot: Bot 对象
event: 上报事件
stack: 异步上下文栈
dependency_cache: 依赖缓存
返回:
是否满足权限
"""
event_type = event.get_type()
return event_type == (cls.type or event_type) and await cls.permission(
bot, event, stack, dependency_cache
)
@classmethod
async def check_rule(
cls,
bot: Bot,
event: Event,
state: T_State,
stack: Optional[AsyncExitStack] = None,
dependency_cache: Optional[T_DependencyCache] = None,
) -> bool:
"""检查是否满足匹配规则
参数:
bot: Bot 对象
event: 上报事件
state: 当前状态
stack: 异步上下文栈
dependency_cache: 依赖缓存
返回:
是否满足匹配规则
"""
event_type = event.get_type()
return event_type == (cls.type or event_type) and await cls.rule(
bot, event, state, stack, dependency_cache
)
@classmethod
def type_updater(cls, func: T_TypeUpdater) -> T_TypeUpdater:
"""装饰一个函数来更改当前事件响应器的默认响应事件类型更新函数
参数:
func: 响应事件类型更新函数
"""
cls._default_type_updater = Dependent[str].parse(
call=func, allow_types=cls.HANDLER_PARAM_TYPES
)
return func
@classmethod
def permission_updater(cls, func: T_PermissionUpdater) -> T_PermissionUpdater:
"""装饰一个函数来更改当前事件响应器的默认会话权限更新函数
参数:
func: 会话权限更新函数
"""
cls._default_permission_updater = Dependent[Permission].parse(
call=func, allow_types=cls.HANDLER_PARAM_TYPES
)
return func
@classmethod
def append_handler(
cls, handler: T_Handler, parameterless: Optional[Iterable[Any]] = None
) -> Dependent[Any]:
handler_ = Dependent[Any].parse(
call=handler,
parameterless=parameterless,
allow_types=cls.HANDLER_PARAM_TYPES,
)
cls.handlers.append(handler_)
return handler_
@classmethod
def handle(
cls, parameterless: Optional[Iterable[Any]] = None
) -> Callable[[T_Handler], T_Handler]:
"""装饰一个函数来向事件响应器直接添加一个处理函数
参数:
parameterless: 非参数类型依赖列表
"""
def _decorator(func: T_Handler) -> T_Handler:
cls.append_handler(func, parameterless=parameterless)
return func
return _decorator
@classmethod
def receive(
cls, id: str = "", parameterless: Optional[Iterable[Any]] = None
) -> Callable[[T_Handler], T_Handler]:
"""装饰一个函数来指示 NoneBot 在接收用户新的一条消息后继续运行该函数
参数:
id: 消息 ID
parameterless: 非参数类型依赖列表
"""
async def _receive(event: Event, matcher: "Matcher") -> Union[None, NoReturn]:
matcher.set_target(RECEIVE_KEY.format(id=id))
if matcher.get_target() == RECEIVE_KEY.format(id=id):
matcher.set_receive(id, event)
return
if matcher.get_receive(id, ...) is not ...:
return
await matcher.reject()
_parameterless = (Depends(_receive), *(parameterless or ()))
def _decorator(func: T_Handler) -> T_Handler:
if cls.handlers and cls.handlers[-1].call is func:
func_handler = cls.handlers[-1]
new_handler = Dependent(
call=func_handler.call,
params=func_handler.params,
parameterless=Dependent.parse_parameterless(
tuple(_parameterless), cls.HANDLER_PARAM_TYPES
)
+ func_handler.parameterless,
)
cls.handlers[-1] = new_handler
else:
cls.append_handler(func, parameterless=_parameterless)
return func
return _decorator
@classmethod
def got(
cls,
key: str,
prompt: Optional[Union[str, Message, MessageSegment, MessageTemplate]] = None,
parameterless: Optional[Iterable[Any]] = None,
) -> Callable[[T_Handler], T_Handler]:
"""装饰一个函数来指示 NoneBot 获取一个参数 `key`
当要获取的 `key` 不存在时接收用户新的一条消息再运行该函数,
如果 `key` 已存在则直接继续运行
参数:
key: 参数名
prompt: 在参数不存在时向用户发送的消息
parameterless: 非参数类型依赖列表
"""
async def _key_getter(event: Event, matcher: "Matcher"):
matcher.set_target(ARG_KEY.format(key=key))
if matcher.get_target() == ARG_KEY.format(key=key):
matcher.set_arg(key, event.get_message())
return
if matcher.get_arg(key, ...) is not ...:
return
await matcher.reject(prompt)
_parameterless = (Depends(_key_getter), *(parameterless or ()))
def _decorator(func: T_Handler) -> T_Handler:
if cls.handlers and cls.handlers[-1].call is func:
func_handler = cls.handlers[-1]
new_handler = Dependent(
call=func_handler.call,
params=func_handler.params,
parameterless=Dependent.parse_parameterless(
tuple(_parameterless), cls.HANDLER_PARAM_TYPES
)
+ func_handler.parameterless,
)
cls.handlers[-1] = new_handler
else:
cls.append_handler(func, parameterless=_parameterless)
return func
return _decorator
@classmethod
async def send(
cls,
message: Union[str, Message, MessageSegment, MessageTemplate],
**kwargs: Any,
) -> Any:
"""发送一条消息给当前交互用户
参数:
message: 消息内容
kwargs: {ref}`nonebot.adapters.Bot.send` 的参数,
请参考对应 adapter 的 bot 对象 api
"""
bot = current_bot.get()
event = current_event.get()
state = current_matcher.get().state
if isinstance(message, MessageTemplate):
_message = message.format(**state)
else:
_message = message
return await bot.send(event=event, message=_message, **kwargs)
@classmethod
async def finish(
cls,
message: Optional[Union[str, Message, MessageSegment, MessageTemplate]] = None,
**kwargs,
) -> NoReturn:
"""发送一条消息给当前交互用户并结束当前事件响应器
参数:
message: 消息内容
kwargs: {ref}`nonebot.adapters.Bot.send` 的参数,
请参考对应 adapter 的 bot 对象 api
"""
if message is not None:
await cls.send(message, **kwargs)
raise FinishedException
@classmethod
async def pause(
cls,
prompt: Optional[Union[str, Message, MessageSegment, MessageTemplate]] = None,
**kwargs,
) -> NoReturn:
"""发送一条消息给当前交互用户并暂停事件响应器,在接收用户新的一条消息后继续下一个处理函数
参数:
prompt: 消息内容
kwargs: {ref}`nonebot.adapters.Bot.send` 的参数,
请参考对应 adapter 的 bot 对象 api
"""
if prompt is not None:
await cls.send(prompt, **kwargs)
raise PausedException
@classmethod
async def reject(
cls,
prompt: Optional[Union[str, Message, MessageSegment, MessageTemplate]] = None,
**kwargs,
) -> NoReturn:
"""最近使用 `got` / `receive` 接收的消息不符合预期,
发送一条消息给当前交互用户并将当前事件处理流程中断在当前位置,在接收用户新的一个事件后从头开始执行当前处理函数
参数:
prompt: 消息内容
kwargs: {ref}`nonebot.adapters.Bot.send` 的参数,
请参考对应 adapter 的 bot 对象 api
"""
if prompt is not None:
await cls.send(prompt, **kwargs)
raise RejectedException
@classmethod
async def reject_arg(
cls,
key: str,
prompt: Optional[Union[str, Message, MessageSegment, MessageTemplate]] = None,
**kwargs,
) -> NoReturn:
"""最近使用 `got` 接收的消息不符合预期,
发送一条消息给当前交互用户并将当前事件处理流程中断在当前位置,在接收用户新的一条消息后从头开始执行当前处理函数
参数:
key: 参数名
prompt: 消息内容
kwargs: {ref}`nonebot.adapters.Bot.send` 的参数,
请参考对应 adapter 的 bot 对象 api
"""
matcher = current_matcher.get()
matcher.set_target(ARG_KEY.format(key=key))
if prompt is not None:
await cls.send(prompt, **kwargs)
raise RejectedException
@classmethod
async def reject_receive(
cls,
id: str = "",
prompt: Optional[Union[str, Message, MessageSegment, MessageTemplate]] = None,
**kwargs,
) -> NoReturn:
"""最近使用 `receive` 接收的消息不符合预期,
发送一条消息给当前交互用户并将当前事件处理流程中断在当前位置,在接收用户新的一个事件后从头开始执行当前处理函数
参数:
id: 消息 id
prompt: 消息内容
kwargs: {ref}`nonebot.adapters.Bot.send` 的参数,
请参考对应 adapter 的 bot 对象 api
"""
matcher = current_matcher.get()
matcher.set_target(RECEIVE_KEY.format(id=id))
if prompt is not None:
await cls.send(prompt, **kwargs)
raise RejectedException
@classmethod
def skip(cls) -> NoReturn:
"""跳过当前事件处理函数,继续下一个处理函数
通常在事件处理函数的依赖中使用。
"""
raise SkippedException
@overload
def get_receive(self, id: str) -> Union[Event, None]:
...
@overload
def get_receive(self, id: str, default: T) -> Union[Event, T]:
...
def get_receive(
self, id: str, default: Optional[T] = None
) -> Optional[Union[Event, T]]:
"""获取一个 `receive` 事件
如果没有找到对应的事件,返回 `default` 值
"""
return self.state.get(RECEIVE_KEY.format(id=id), default)
def set_receive(self, id: str, event: Event) -> None:
"""设置一个 `receive` 事件"""
self.state[RECEIVE_KEY.format(id=id)] = event
self.state[LAST_RECEIVE_KEY] = event
@overload
def get_last_receive(self) -> Union[Event, None]:
...
@overload
def get_last_receive(self, default: T) -> Union[Event, T]:
...
def get_last_receive(
self, default: Optional[T] = None
) -> Optional[Union[Event, T]]:
"""获取最近一次 `receive` 事件
如果没有事件,返回 `default` 值
"""
return self.state.get(LAST_RECEIVE_KEY, default)
@overload
def get_arg(self, key: str) -> Union[Message, None]:
...
@overload
def get_arg(self, key: str, default: T) -> Union[Message, T]:
...
def get_arg(
self, key: str, default: Optional[T] = None
) -> Optional[Union[Message, T]]:
"""获取一个 `got` 消息
如果没有找到对应的消息,返回 `default` 值
"""
return self.state.get(ARG_KEY.format(key=key), default)
def set_arg(self, key: str, message: Message) -> None:
"""设置一个 `got` 消息"""
self.state[ARG_KEY.format(key=key)] = message
def set_target(self, target: str, cache: bool = True) -> None:
if cache:
self.state[REJECT_CACHE_TARGET] = target
else:
self.state[REJECT_TARGET] = target
@overload
def get_target(self) -> Union[str, None]:
...
@overload
def get_target(self, default: T) -> Union[str, T]:
...
def get_target(self, default: Optional[T] = None) -> Optional[Union[str, T]]:
return self.state.get(REJECT_TARGET, default)
def stop_propagation(self):
"""阻止事件传播"""
self.block = True
async def update_type(
self,
bot: Bot,
event: Event,
stack: Optional[AsyncExitStack] = None,
dependency_cache: Optional[T_DependencyCache] = None,
) -> str:
updater = self.__class__._default_type_updater
return (
await updater(
bot=bot,
event=event,
state=self.state,
matcher=self,
stack=stack,
dependency_cache=dependency_cache,
)
if updater
else "message"
)
async def update_permission(
self,
bot: Bot,
event: Event,
stack: Optional[AsyncExitStack] = None,
dependency_cache: Optional[T_DependencyCache] = None,
) -> Permission:
if updater := self.__class__._default_permission_updater:
return await updater(
bot=bot,
event=event,
state=self.state,
matcher=self,
stack=stack,
dependency_cache=dependency_cache,
)
return Permission(User.from_event(event, perm=self.permission))
async def resolve_reject(self):
handler = current_handler.get()
self.handlers.insert(0, handler)
if REJECT_CACHE_TARGET in self.state:
self.state[REJECT_TARGET] = self.state[REJECT_CACHE_TARGET]
@contextmanager
def ensure_context(self, bot: Bot, event: Event):
b_t = current_bot.set(bot)
e_t = current_event.set(event)
m_t = current_matcher.set(self)
try:
yield
finally:
current_bot.reset(b_t)
current_event.reset(e_t)
current_matcher.reset(m_t)
async def simple_run(
self,
bot: Bot,
event: Event,
state: T_State,
stack: Optional[AsyncExitStack] = None,
dependency_cache: Optional[T_DependencyCache] = None,
):
logger.trace(
f"{self} run with incoming args: "
f"bot={bot}, event={event!r}, state={state!r}"
)
with self.ensure_context(bot, event):
try:
# Refresh preprocess state
self.state.update(state)
while self.handlers:
handler = self.handlers.pop(0)
current_handler.set(handler)
logger.debug(f"Running handler {handler}")
try:
await handler(
matcher=self,
bot=bot,
event=event,
state=self.state,
stack=stack,
dependency_cache=dependency_cache,
)
except SkippedException:
logger.debug(f"Handler {handler} skipped")
except StopPropagation:
self.block = True
finally:
logger.info(f"{self} running complete")
# 运行handlers
async def run(
self,
bot: Bot,
event: Event,
state: T_State,
stack: Optional[AsyncExitStack] = None,
dependency_cache: Optional[T_DependencyCache] = None,
):
try:
await self.simple_run(bot, event, state, stack, dependency_cache)
except RejectedException:
await self.resolve_reject()
type_ = await self.update_type(bot, event, stack, dependency_cache)
permission = await self.update_permission(
bot, event, stack, dependency_cache
)
self.new(
type_,
Rule(),
permission,
self.handlers,
temp=True,
priority=0,
block=True,
plugin=self.plugin,
module=self.module,
expire_time=bot.config.session_expire_timeout,
default_state=self.state,
default_type_updater=self.__class__._default_type_updater,
default_permission_updater=self.__class__._default_permission_updater,
)
except PausedException:
type_ = await self.update_type(bot, event, stack, dependency_cache)
permission = await self.update_permission(
bot, event, stack, dependency_cache
)
self.new(
type_,
Rule(),
permission,
self.handlers,
temp=True,
priority=0,
block=True,
plugin=self.plugin,
module=self.module,
expire_time=bot.config.session_expire_timeout,
default_state=self.state,
default_type_updater=self.__class__._default_type_updater,
default_permission_updater=self.__class__._default_permission_updater,
)
except FinishedException:
pass

View File

@@ -0,0 +1,27 @@
import abc
from collections import defaultdict
from typing import TYPE_CHECKING, List, Type, Mapping, MutableMapping
if TYPE_CHECKING:
from .matcher import Matcher
class MatcherProvider(abc.ABC, MutableMapping[int, List[Type["Matcher"]]]):
"""事件响应器存储器基类
参数:
matchers: 当前存储器中已有的事件响应器
"""
@abc.abstractmethod
def __init__(self, matchers: Mapping[int, List[Type["Matcher"]]]):
raise NotImplementedError
class _DictProvider(defaultdict, MatcherProvider):
def __init__(self, matchers: Mapping[int, List[Type["Matcher"]]]):
super().__init__(list, matchers)
DEFAULT_PROVIDER_CLASS = _DictProvider
"""默认存储器类型"""

462
nonebot/internal/params.py Normal file
View File

@@ -0,0 +1,462 @@
import asyncio
import inspect
from typing_extensions import Annotated
from contextlib import AsyncExitStack, contextmanager, asynccontextmanager
from typing import TYPE_CHECKING, Any, Type, Tuple, Literal, Callable, Optional, cast
from pydantic.typing import get_args, get_origin
from pydantic.fields import Required, Undefined, ModelField
from nonebot.dependencies.utils import check_field_type
from nonebot.dependencies import Param, Dependent, CustomConfig
from nonebot.typing import T_State, T_Handler, T_DependencyCache
from nonebot.utils import (
get_name,
run_sync,
is_gen_callable,
run_sync_ctx_manager,
is_async_gen_callable,
is_coroutine_callable,
generic_check_issubclass,
)
if TYPE_CHECKING:
from nonebot.matcher import Matcher
from nonebot.adapters import Bot, Event
class DependsInner:
def __init__(
self,
dependency: Optional[T_Handler] = None,
*,
use_cache: bool = True,
) -> None:
self.dependency = dependency
self.use_cache = use_cache
def __repr__(self) -> str:
dep = get_name(self.dependency)
cache = "" if self.use_cache else ", use_cache=False"
return f"DependsInner({dep}{cache})"
def Depends(
dependency: Optional[T_Handler] = None,
*,
use_cache: bool = True,
) -> Any:
"""子依赖装饰器
参数:
dependency: 依赖函数。默认为参数的类型注释。
use_cache: 是否使用缓存。默认为 `True`。
用法:
```python
def depend_func() -> Any:
return ...
def depend_gen_func():
try:
yield ...
finally:
...
async def handler(
param_name: Any = Depends(depend_func),
gen: Any = Depends(depend_gen_func),
):
...
```
"""
return DependsInner(dependency, use_cache=use_cache)
class DependParam(Param):
"""子依赖注入参数。
本注入解析所有子依赖注入,然后将它们的返回值作为参数值传递给父依赖。
本注入应该具有最高优先级,因此应该在其他参数之前检查。
"""
def __repr__(self) -> str:
return f"Depends({self.extra['dependent']})"
@classmethod
def _check_param(
cls, param: inspect.Parameter, allow_types: Tuple[Type[Param], ...]
) -> Optional["DependParam"]:
type_annotation, depends_inner = param.annotation, None
if get_origin(param.annotation) is Annotated:
type_annotation, *extra_args = get_args(param.annotation)
depends_inner = next(
(x for x in extra_args if isinstance(x, DependsInner)), None
)
depends_inner = (
param.default if isinstance(param.default, DependsInner) else depends_inner
)
if depends_inner is None:
return
dependency: T_Handler
if depends_inner.dependency is None:
assert (
type_annotation is not inspect.Signature.empty
), "Dependency cannot be empty"
dependency = type_annotation
else:
dependency = depends_inner.dependency
sub_dependent = Dependent[Any].parse(
call=dependency,
allow_types=allow_types,
)
return cls(Required, use_cache=depends_inner.use_cache, dependent=sub_dependent)
@classmethod
def _check_parameterless(
cls, value: Any, allow_types: Tuple[Type[Param], ...]
) -> Optional["Param"]:
if isinstance(value, DependsInner):
assert value.dependency, "Dependency cannot be empty"
dependent = Dependent[Any].parse(
call=value.dependency, allow_types=allow_types
)
return cls(Required, use_cache=value.use_cache, dependent=dependent)
async def _solve(
self,
stack: Optional[AsyncExitStack] = None,
dependency_cache: Optional[T_DependencyCache] = None,
**kwargs: Any,
) -> Any:
use_cache: bool = self.extra["use_cache"]
dependency_cache = {} if dependency_cache is None else dependency_cache
sub_dependent: Dependent = self.extra["dependent"]
call = cast(Callable[..., Any], sub_dependent.call)
# solve sub dependency with current cache
sub_values = await sub_dependent.solve(
stack=stack,
dependency_cache=dependency_cache,
**kwargs,
)
# run dependency function
task: asyncio.Task[Any]
if use_cache and call in dependency_cache:
return await dependency_cache[call]
elif is_gen_callable(call) or is_async_gen_callable(call):
assert isinstance(
stack, AsyncExitStack
), "Generator dependency should be called in context"
if is_gen_callable(call):
cm = run_sync_ctx_manager(contextmanager(call)(**sub_values))
else:
cm = asynccontextmanager(call)(**sub_values)
task = asyncio.create_task(stack.enter_async_context(cm))
dependency_cache[call] = task
return await task
elif is_coroutine_callable(call):
task = asyncio.create_task(call(**sub_values))
dependency_cache[call] = task
return await task
else:
task = asyncio.create_task(run_sync(call)(**sub_values))
dependency_cache[call] = task
return await task
async def _check(self, **kwargs: Any) -> None:
# run sub dependent pre-checkers
sub_dependent: Dependent = self.extra["dependent"]
await sub_dependent.check(**kwargs)
class BotParam(Param):
"""{ref}`nonebot.adapters.Bot` 注入参数。
本注入解析所有类型为且仅为 {ref}`nonebot.adapters.Bot` 及其子类或 `None` 的参数。
为保证兼容性,本注入还会解析名为 `bot` 且没有类型注解的参数。
"""
def __repr__(self) -> str:
return (
"BotParam("
+ (
repr(cast(ModelField, checker).type_)
if (checker := self.extra.get("checker"))
else ""
)
+ ")"
)
@classmethod
def _check_param(
cls, param: inspect.Parameter, allow_types: Tuple[Type[Param], ...]
) -> Optional["BotParam"]:
from nonebot.adapters import Bot
# param type is Bot(s) or subclass(es) of Bot or None
if generic_check_issubclass(param.annotation, Bot):
checker: Optional[ModelField] = None
if param.annotation is not Bot:
checker = ModelField(
name=param.name,
type_=param.annotation,
class_validators=None,
model_config=CustomConfig,
default=None,
required=True,
)
return cls(Required, checker=checker)
# legacy: param is named "bot" and has no type annotation
elif param.annotation == param.empty and param.name == "bot":
return cls(Required)
async def _solve(self, bot: "Bot", **kwargs: Any) -> Any:
return bot
async def _check(self, bot: "Bot", **kwargs: Any) -> None:
if checker := self.extra.get("checker"):
check_field_type(checker, bot)
class EventParam(Param):
"""{ref}`nonebot.adapters.Event` 注入参数
本注入解析所有类型为且仅为 {ref}`nonebot.adapters.Event` 及其子类或 `None` 的参数。
为保证兼容性,本注入还会解析名为 `event` 且没有类型注解的参数。
"""
def __repr__(self) -> str:
return (
"EventParam("
+ (
repr(cast(ModelField, checker).type_)
if (checker := self.extra.get("checker"))
else ""
)
+ ")"
)
@classmethod
def _check_param(
cls, param: inspect.Parameter, allow_types: Tuple[Type[Param], ...]
) -> Optional["EventParam"]:
from nonebot.adapters import Event
# param type is Event(s) or subclass(es) of Event or None
if generic_check_issubclass(param.annotation, Event):
checker: Optional[ModelField] = None
if param.annotation is not Event:
checker = ModelField(
name=param.name,
type_=param.annotation,
class_validators=None,
model_config=CustomConfig,
default=None,
required=True,
)
return cls(Required, checker=checker)
# legacy: param is named "event" and has no type annotation
elif param.annotation == param.empty and param.name == "event":
return cls(Required)
async def _solve(self, event: "Event", **kwargs: Any) -> Any:
return event
async def _check(self, event: "Event", **kwargs: Any) -> Any:
if checker := self.extra.get("checker", None):
check_field_type(checker, event)
class StateParam(Param):
"""事件处理状态注入参数
本注入解析所有类型为 `T_State` 的参数。
为保证兼容性,本注入还会解析名为 `state` 且没有类型注解的参数。
"""
def __repr__(self) -> str:
return "StateParam()"
@classmethod
def _check_param(
cls, param: inspect.Parameter, allow_types: Tuple[Type[Param], ...]
) -> Optional["StateParam"]:
# param type is T_State
if param.annotation is T_State:
return cls(Required)
# legacy: param is named "state" and has no type annotation
elif param.annotation == param.empty and param.name == "state":
return cls(Required)
async def _solve(self, state: T_State, **kwargs: Any) -> Any:
return state
class MatcherParam(Param):
"""事件响应器实例注入参数
本注入解析所有类型为且仅为 {ref}`nonebot.matcher.Matcher` 及其子类或 `None` 的参数。
为保证兼容性,本注入还会解析名为 `matcher` 且没有类型注解的参数。
"""
def __repr__(self) -> str:
return "MatcherParam()"
@classmethod
def _check_param(
cls, param: inspect.Parameter, allow_types: Tuple[Type[Param], ...]
) -> Optional["MatcherParam"]:
from nonebot.matcher import Matcher
# param type is Matcher(s) or subclass(es) of Matcher or None
if generic_check_issubclass(param.annotation, Matcher):
checker: Optional[ModelField] = None
if param.annotation is not Matcher:
checker = ModelField(
name=param.name,
type_=param.annotation,
class_validators=None,
model_config=CustomConfig,
default=None,
required=True,
)
return cls(Required, checker=checker)
# legacy: param is named "matcher" and has no type annotation
elif param.annotation == param.empty and param.name == "matcher":
return cls(Required)
async def _solve(self, matcher: "Matcher", **kwargs: Any) -> Any:
return matcher
async def _check(self, matcher: "Matcher", **kwargs: Any) -> Any:
if checker := self.extra.get("checker", None):
check_field_type(checker, matcher)
class ArgInner:
def __init__(
self, key: Optional[str], type: Literal["message", "str", "plaintext"]
) -> None:
self.key = key
self.type = type
def __repr__(self) -> str:
return f"ArgInner(key={self.key!r}, type={self.type!r})"
def Arg(key: Optional[str] = None) -> Any:
"""Arg 参数消息"""
return ArgInner(key, "message")
def ArgStr(key: Optional[str] = None) -> str:
"""Arg 参数消息文本"""
return ArgInner(key, "str") # type: ignore
def ArgPlainText(key: Optional[str] = None) -> str:
"""Arg 参数消息纯文本"""
return ArgInner(key, "plaintext") # type: ignore
class ArgParam(Param):
"""Arg 注入参数
本注入解析事件响应器操作 `got` 所获取的参数。
可以通过 `Arg`、`ArgStr`、`ArgPlainText` 等函数参数 `key` 指定获取的参数,
留空则会根据参数名称获取。
"""
def __repr__(self) -> str:
return f"ArgParam(key={self.extra['key']!r}, type={self.extra['type']!r})"
@classmethod
def _check_param(
cls, param: inspect.Parameter, allow_types: Tuple[Type[Param], ...]
) -> Optional["ArgParam"]:
if isinstance(param.default, ArgInner):
return cls(
Required, key=param.default.key or param.name, type=param.default.type
)
elif get_origin(param.annotation) is Annotated:
for arg in get_args(param.annotation):
if isinstance(arg, ArgInner):
return cls(Required, key=arg.key or param.name, type=arg.type)
async def _solve(self, matcher: "Matcher", **kwargs: Any) -> Any:
key: str = self.extra["key"]
message = matcher.get_arg(key)
if message is None:
return message
if self.extra["type"] == "message":
return message
elif self.extra["type"] == "str":
return str(message)
else:
return message.extract_plain_text()
class ExceptionParam(Param):
"""{ref}`nonebot.message.run_postprocessor` 的异常注入参数
本注入解析所有类型为 `Exception` 或 `None` 的参数。
为保证兼容性,本注入还会解析名为 `exception` 且没有类型注解的参数。
"""
def __repr__(self) -> str:
return "ExceptionParam()"
@classmethod
def _check_param(
cls, param: inspect.Parameter, allow_types: Tuple[Type[Param], ...]
) -> Optional["ExceptionParam"]:
# param type is Exception(s) or subclass(es) of Exception or None
if generic_check_issubclass(param.annotation, Exception):
return cls(Required)
# legacy: param is named "exception" and has no type annotation
elif param.annotation == param.empty and param.name == "exception":
return cls(Required)
async def _solve(self, exception: Optional[Exception] = None, **kwargs: Any) -> Any:
return exception
class DefaultParam(Param):
"""默认值注入参数
本注入解析所有剩余未能解析且具有默认值的参数。
本注入参数应该具有最低优先级,因此应该在所有其他注入参数之后使用。
"""
def __repr__(self) -> str:
return f"DefaultParam(default={self.default!r})"
@classmethod
def _check_param(
cls, param: inspect.Parameter, allow_types: Tuple[Type[Param], ...]
) -> Optional["DefaultParam"]:
if param.default != param.empty:
return cls(param.default)
async def _solve(self, **kwargs: Any) -> Any:
return Undefined
__autodoc__ = {
"DependsInner": False,
"StateInner": False,
"ArgInner": False,
}

View File

@@ -0,0 +1,187 @@
import asyncio
from typing_extensions import Self
from contextlib import AsyncExitStack
from typing import Set, Tuple, Union, NoReturn, Optional
from nonebot.dependencies import Dependent
from nonebot.utils import run_coro_with_catch
from nonebot.exception import SkippedException
from nonebot.typing import T_DependencyCache, T_PermissionChecker
from .adapter import Bot, Event
from .params import BotParam, EventParam, DependParam, DefaultParam
class Permission:
"""{ref}`nonebot.matcher.Matcher` 权限类。
当事件传递时,在 {ref}`nonebot.matcher.Matcher` 运行前进行检查。
参数:
checkers: PermissionChecker
用法:
```python
Permission(async_function) | sync_function
# 等价于
Permission(async_function, sync_function)
```
"""
__slots__ = ("checkers",)
HANDLER_PARAM_TYPES = [
DependParam,
BotParam,
EventParam,
DefaultParam,
]
def __init__(self, *checkers: Union[T_PermissionChecker, Dependent[bool]]) -> None:
self.checkers: Set[Dependent[bool]] = {
checker
if isinstance(checker, Dependent)
else Dependent[bool].parse(
call=checker, allow_types=self.HANDLER_PARAM_TYPES
)
for checker in checkers
}
"""存储 `PermissionChecker`"""
def __repr__(self) -> str:
return f"Permission({', '.join(repr(checker) for checker in self.checkers)})"
async def __call__(
self,
bot: Bot,
event: Event,
stack: Optional[AsyncExitStack] = None,
dependency_cache: Optional[T_DependencyCache] = None,
) -> bool:
"""检查是否满足某个权限。
参数:
bot: Bot 对象
event: Event 对象
stack: 异步上下文栈
dependency_cache: 依赖缓存
"""
if not self.checkers:
return True
results = await asyncio.gather(
*(
run_coro_with_catch(
checker(
bot=bot,
event=event,
stack=stack,
dependency_cache=dependency_cache,
),
(SkippedException,),
False,
)
for checker in self.checkers
),
)
return any(results)
def __and__(self, other: object) -> NoReturn:
raise RuntimeError("And operation between Permissions is not allowed.")
def __or__(
self, other: Optional[Union["Permission", T_PermissionChecker]]
) -> "Permission":
if other is None:
return self
elif isinstance(other, Permission):
return Permission(*self.checkers, *other.checkers)
else:
return Permission(*self.checkers, other)
def __ror__(
self, other: Optional[Union["Permission", T_PermissionChecker]]
) -> "Permission":
if other is None:
return self
elif isinstance(other, Permission):
return Permission(*other.checkers, *self.checkers)
else:
return Permission(other, *self.checkers)
class User:
"""检查当前事件是否属于指定会话。
参数:
users: 会话 ID 元组
perm: 需同时满足的权限
"""
__slots__ = ("users", "perm")
def __init__(
self, users: Tuple[str, ...], perm: Optional[Permission] = None
) -> None:
self.users = users
self.perm = perm
def __repr__(self) -> str:
return (
f"User(users={self.users}"
+ (f", permission={self.perm})" if self.perm else "")
+ ")"
)
async def __call__(self, bot: Bot, event: Event) -> bool:
try:
session = event.get_session_id()
except Exception:
return False
return bool(
session in self.users and (self.perm is None or await self.perm(bot, event))
)
@classmethod
def _clean_permission(cls, perm: Permission) -> Optional[Permission]:
if len(perm.checkers) == 1 and isinstance(
user_perm := tuple(perm.checkers)[0].call, cls
):
return user_perm.perm
return perm
@classmethod
def from_event(cls, event: Event, perm: Optional[Permission] = None) -> Self:
"""从事件中获取会话 ID。
如果 `perm` 中仅有 `User` 类型的权限检查函数,则会去除原有的会话 ID 限制。
参数:
event: Event 对象
perm: 需同时满足的权限
"""
return cls((event.get_session_id(),), perm=perm and cls._clean_permission(perm))
@classmethod
def from_permission(cls, *users: str, perm: Optional[Permission] = None) -> Self:
"""指定会话与权限。
如果 `perm` 中仅有 `User` 类型的权限检查函数,则会去除原有的会话 ID 限制。
参数:
users: 会话白名单
perm: 需同时满足的权限
"""
return cls(users, perm=perm and cls._clean_permission(perm))
def USER(*users: str, perm: Optional[Permission] = None):
"""匹配当前事件属于指定会话。
如果 `perm` 中仅有 `User` 类型的权限检查函数,则会去除原有检查函数的会话 ID 限制。
参数:
user: 会话白名单
perm: 需要同时满足的权限
"""
return Permission(User.from_permission(*users, perm=perm))

106
nonebot/internal/rule.py Normal file
View File

@@ -0,0 +1,106 @@
import asyncio
from contextlib import AsyncExitStack
from typing import Set, Union, NoReturn, Optional
from nonebot.dependencies import Dependent
from nonebot.exception import SkippedException
from nonebot.typing import T_State, T_RuleChecker, T_DependencyCache
from .adapter import Bot, Event
from .params import BotParam, EventParam, StateParam, DependParam, DefaultParam
class Rule:
"""{ref}`nonebot.matcher.Matcher` 规则类。
当事件传递时,在 {ref}`nonebot.matcher.Matcher` 运行前进行检查。
参数:
*checkers: RuleChecker
用法:
```python
Rule(async_function) & sync_function
# 等价于
Rule(async_function, sync_function)
```
"""
__slots__ = ("checkers",)
HANDLER_PARAM_TYPES = [
DependParam,
BotParam,
EventParam,
StateParam,
DefaultParam,
]
def __init__(self, *checkers: Union[T_RuleChecker, Dependent[bool]]) -> None:
self.checkers: Set[Dependent[bool]] = {
checker
if isinstance(checker, Dependent)
else Dependent[bool].parse(
call=checker, allow_types=self.HANDLER_PARAM_TYPES
)
for checker in checkers
}
"""存储 `RuleChecker`"""
def __repr__(self) -> str:
return f"Rule({', '.join(repr(checker) for checker in self.checkers)})"
async def __call__(
self,
bot: Bot,
event: Event,
state: T_State,
stack: Optional[AsyncExitStack] = None,
dependency_cache: Optional[T_DependencyCache] = None,
) -> bool:
"""检查是否符合所有规则
参数:
bot: Bot 对象
event: Event 对象
state: 当前 State
stack: 异步上下文栈
dependency_cache: 依赖缓存
"""
if not self.checkers:
return True
try:
results = await asyncio.gather(
*(
checker(
bot=bot,
event=event,
state=state,
stack=stack,
dependency_cache=dependency_cache,
)
for checker in self.checkers
)
)
except SkippedException:
return False
return all(results)
def __and__(self, other: Optional[Union["Rule", T_RuleChecker]]) -> "Rule":
if other is None:
return self
elif isinstance(other, Rule):
return Rule(*self.checkers, *other.checkers)
else:
return Rule(*self.checkers, other)
def __rand__(self, other: Optional[Union["Rule", T_RuleChecker]]) -> "Rule":
if other is None:
return self
elif isinstance(other, Rule):
return Rule(*other.checkers, *self.checkers)
else:
return Rule(other, *self.checkers)
def __or__(self, other: object) -> NoReturn:
raise RuntimeError("Or operation between rules is not allowed.")

View File

@@ -1,44 +1,42 @@
""" """本模块定义了 NoneBot 的日志记录 Logger。
日志
====
NoneBot 使用 `loguru`_ 来记录日志信息。 NoneBot 使用 [`loguru`][loguru] 来记录日志信息。
自定义 logger 请参考 `loguru`_ 文档。 自定义 logger 请参考 [自定义日志](https://nonebot.dev/docs/appendices/log)
以及 [`loguru`][loguru] 文档。
.. _loguru: [loguru]: https://github.com/Delgan/loguru
https://github.com/Delgan/loguru
FrontMatter:
sidebar_position: 7
description: nonebot.log 模块
""" """
import sys import sys
import logging import logging
from typing import TYPE_CHECKING, Union from typing import TYPE_CHECKING
import loguru import loguru
if TYPE_CHECKING: if TYPE_CHECKING:
# avoid sphinx autodoc resolve annotation failed # avoid sphinx autodoc resolve annotation failed
# because loguru module do not have `Logger` class actually # because loguru module do not have `Logger` class actually
from loguru import Logger from loguru import Logger, Record
# logger = logging.getLogger("nonebot") # logger = logging.getLogger("nonebot")
logger: "Logger" = loguru.logger logger: "Logger" = loguru.logger
""" """NoneBot 日志记录器对象。
:说明:
NoneBot 日志记录器对象。 默认信息:
:默认信息: - 格式: `[%(asctime)s %(name)s] %(levelname)s: %(message)s`
- 等级: `INFO` ,根据 `config.log_level` 配置改变
* 格式: ``[%(asctime)s %(name)s] %(levelname)s: %(message)s`` - 输出: 输出至 stdout
* 等级: ``INFO`` ,根据 ``config.log_level`` 配置改变
* 输出: 输出至 stdout
:用法:
.. code-block:: python
用法:
```python
from nonebot.log import logger from nonebot.log import logger
```
""" """
# default_handler = logging.StreamHandler(sys.stdout) # default_handler = logging.StreamHandler(sys.stdout)
@@ -47,30 +45,16 @@ logger: "Logger" = loguru.logger
# logger.addHandler(default_handler) # logger.addHandler(default_handler)
class Filter:
def __init__(self) -> None:
self.level: Union[int, str] = "INFO"
def __call__(self, record):
module_name: str = record["name"]
module = sys.modules.get(module_name)
if module:
module_name = getattr(module, "__module_name__", module_name)
record["name"] = module_name.split(".")[0]
levelno = (
logger.level(self.level).no if isinstance(self.level, str) else self.level
)
return record["level"].no >= levelno
class LoguruHandler(logging.Handler): # pragma: no cover class LoguruHandler(logging.Handler): # pragma: no cover
def emit(self, record): """logging 与 loguru 之间的桥梁,将 logging 的日志转发到 loguru。"""
def emit(self, record: logging.LogRecord):
try: try:
level = logger.level(record.levelname).name level = logger.level(record.levelname).name
except ValueError: except ValueError:
level = record.levelno level = record.levelno
frame, depth = logging.currentframe(), 2 frame, depth = sys._getframe(6), 6
while frame and frame.f_code.co_filename == logging.__file__: while frame and frame.f_code.co_filename == logging.__file__:
frame = frame.f_back frame = frame.f_back
depth += 1 depth += 1
@@ -80,20 +64,30 @@ class LoguruHandler(logging.Handler): # pragma: no cover
) )
logger.remove() def default_filter(record: "Record"):
default_filter = Filter() """默认的日志过滤器,根据 `config.log_level` 配置改变日志等级。"""
default_format = ( log_level = record["extra"].get("nonebot_log_level", "INFO")
levelno = logger.level(log_level).no if isinstance(log_level, str) else log_level
return record["level"].no >= levelno
default_format: str = (
"<g>{time:MM-DD HH:mm:ss}</g> " "<g>{time:MM-DD HH:mm:ss}</g> "
"[<lvl>{level}</lvl>] " "[<lvl>{level}</lvl>] "
"<c><u>{name}</u></c> | " "<c><u>{name}</u></c> | "
# "<c>{function}:{line}</c>| " # "<c>{function}:{line}</c>| "
"{message}" "{message}"
) )
"""默认日志格式"""
logger.remove()
logger_id = logger.add( logger_id = logger.add(
sys.stdout, sys.stdout,
level=0, level=0,
colorize=True,
diagnose=False, diagnose=False,
filter=default_filter, filter=default_filter,
format=default_format, format=default_format,
) )
"""默认日志处理器 id"""
__autodoc__ = {"logger_id": False}

View File

@@ -1,784 +1,24 @@
""" """本模块实现事件响应器的创建与运行,并提供一些快捷方法来帮助用户更好的与机器人进行对话。
事件响应器
==========
该模块实现事件响应器的创建与运行,并提供一些快捷方法来帮助用户更好的与机器人进行对话 。 FrontMatter:
sidebar_position: 3
description: nonebot.matcher 模块
""" """
from types import ModuleType from nonebot.internal.matcher import Matcher as Matcher
from datetime import datetime from nonebot.internal.matcher import matchers as matchers
from contextvars import ContextVar from nonebot.internal.matcher import current_bot as current_bot
from collections import defaultdict from nonebot.internal.matcher import current_event as current_event
from contextlib import AsyncExitStack from nonebot.internal.matcher import MatcherManager as MatcherManager
from typing import ( from nonebot.internal.matcher import MatcherProvider as MatcherProvider
TYPE_CHECKING, from nonebot.internal.matcher import current_handler as current_handler
Any, from nonebot.internal.matcher import current_matcher as current_matcher
Dict, from nonebot.internal.matcher import DEFAULT_PROVIDER_CLASS as DEFAULT_PROVIDER_CLASS
List,
Type, __autodoc__ = {
Union, "Matcher": True,
TypeVar, "matchers": True,
Callable, "MatcherManager": True,
NoReturn, "MatcherProvider": True,
Optional, "DEFAULT_PROVIDER_CLASS": True,
) }
from nonebot import params
from nonebot.rule import Rule
from nonebot.log import logger
from nonebot.dependencies import Dependent
from nonebot.permission import USER, Permission
from nonebot.adapters import (
Bot,
Event,
Message,
MessageSegment,
MessageTemplate,
)
from nonebot.consts import (
ARG_KEY,
RECEIVE_KEY,
REJECT_TARGET,
LAST_RECEIVE_KEY,
REJECT_CACHE_TARGET,
)
from nonebot.typing import (
Any,
T_State,
T_Handler,
T_TypeUpdater,
T_DependencyCache,
T_PermissionUpdater,
)
from nonebot.exception import (
TypeMisMatch,
PausedException,
StopPropagation,
SkippedException,
FinishedException,
RejectedException,
)
if TYPE_CHECKING:
from nonebot.plugin import Plugin
T = TypeVar("T")
matchers: Dict[int, List[Type["Matcher"]]] = defaultdict(list)
"""
:类型: ``Dict[int, List[Type[Matcher]]]``
:说明: 用于存储当前所有的事件响应器
"""
current_bot: ContextVar[Bot] = ContextVar("current_bot")
current_event: ContextVar[Event] = ContextVar("current_event")
current_matcher: ContextVar["Matcher"] = ContextVar("current_matcher")
current_handler: ContextVar[Dependent] = ContextVar("current_handler")
class MatcherMeta(type):
if TYPE_CHECKING:
module: Optional[str]
plugin_name: Optional[str]
module_name: Optional[str]
module_prefix: Optional[str]
type: str
rule: Rule
permission: Permission
handlers: List[T_Handler]
priority: int
block: bool
temp: bool
expire_time: Optional[datetime]
def __repr__(self) -> str:
return (
f"<Matcher from {self.module_name or 'unknown'}, "
f"type={self.type}, priority={self.priority}, "
f"temp={self.temp}>"
)
def __str__(self) -> str:
return repr(self)
class Matcher(metaclass=MatcherMeta):
"""事件响应器类"""
plugin: Optional["Plugin"] = None
"""
:类型: ``Optional[Plugin]``
:说明: 事件响应器所在插件
"""
module: Optional[ModuleType] = None
"""
:类型: ``Optional[ModuleType]``
:说明: 事件响应器所在插件模块
"""
plugin_name: Optional[str] = None
"""
:类型: ``Optional[str]``
:说明: 事件响应器所在插件名
"""
module_name: Optional[str] = None
"""
:类型: ``Optional[str]``
:说明: 事件响应器所在点分割插件模块路径
"""
type: str = ""
"""
:类型: ``str``
:说明: 事件响应器类型
"""
rule: Rule = Rule()
"""
:类型: ``Rule``
:说明: 事件响应器匹配规则
"""
permission: Permission = Permission()
"""
:类型: ``Permission``
:说明: 事件响应器触发权限
"""
handlers: List[Dependent[Any]] = []
"""
:类型: ``List[Handler]``
:说明: 事件响应器拥有的事件处理函数列表
"""
priority: int = 1
"""
:类型: ``int``
:说明: 事件响应器优先级
"""
block: bool = False
"""
:类型: ``bool``
:说明: 事件响应器是否阻止事件传播
"""
temp: bool = False
"""
:类型: ``bool``
:说明: 事件响应器是否为临时
"""
expire_time: Optional[datetime] = None
"""
:类型: ``Optional[datetime]``
:说明: 事件响应器过期时间点
"""
_default_state: T_State = {}
"""
:类型: ``T_State``
:说明: 事件响应器默认状态
"""
_default_type_updater: Optional[Dependent[str]] = None
"""
:类型: ``Optional[Dependent]``
:说明: 事件响应器类型更新函数
"""
_default_permission_updater: Optional[Dependent[Permission]] = None
"""
:类型: ``Optional[Dependent]``
:说明: 事件响应器权限更新函数
"""
HANDLER_PARAM_TYPES = [
params.DependParam,
params.BotParam,
params.EventParam,
params.StateParam,
params.ArgParam,
params.MatcherParam,
params.DefaultParam,
]
def __init__(self):
"""实例化 Matcher 以便运行"""
self.handlers = self.handlers.copy()
self.state = self._default_state.copy()
def __repr__(self) -> str:
return (
f"<Matcher from {self.module_name or 'unknown'}, type={self.type}, "
f"priority={self.priority}, temp={self.temp}>"
)
def __str__(self) -> str:
return repr(self)
@classmethod
def new(
cls,
type_: str = "",
rule: Optional[Rule] = None,
permission: Optional[Permission] = None,
handlers: Optional[List[Union[T_Handler, Dependent[Any]]]] = None,
temp: bool = False,
priority: int = 1,
block: bool = False,
*,
plugin: Optional["Plugin"] = None,
module: Optional[ModuleType] = None,
expire_time: Optional[datetime] = None,
default_state: Optional[T_State] = None,
default_type_updater: Optional[T_TypeUpdater] = None,
default_permission_updater: Optional[T_PermissionUpdater] = None,
) -> Type["Matcher"]:
"""
:说明:
创建一个新的事件响应器,并存储至 `matchers <#matchers>`_
:参数:
* ``type_: str``: 事件响应器类型,与 ``event.get_type()`` 一致时触发,空字符串表示任意
* ``rule: Optional[Rule]``: 匹配规则
* ``permission: Optional[Permission]``: 权限
* ``handlers: Optional[List[T_Handler]]``: 事件处理函数列表
* ``temp: bool``: 是否为临时事件响应器,即触发一次后删除
* ``priority: int``: 响应优先级
* ``block: bool``: 是否阻止事件向更低优先级的响应器传播
* ``plugin: Optional[Plugin]``: 事件响应器所在插件
* ``module: Optional[ModuleType]``: 事件响应器所在模块
* ``default_state: Optional[T_State]``: 默认状态 ``state``
* ``expire_time: Optional[datetime]``: 事件响应器最终有效时间点,过时即被删除
:返回:
- ``Type[Matcher]``: 新的事件响应器类
"""
NewMatcher = type(
"Matcher",
(Matcher,),
{
"plugin": plugin,
"module": module,
"plugin_name": plugin and plugin.name,
"module_name": module and module.__name__,
"type": type_,
"rule": rule or Rule(),
"permission": permission or Permission(),
"handlers": [
handler
if isinstance(handler, Dependent)
else Dependent[Any].parse(
call=handler, allow_types=cls.HANDLER_PARAM_TYPES
)
for handler in handlers
]
if handlers
else [],
"temp": temp,
"expire_time": expire_time,
"priority": priority,
"block": block,
"_default_state": default_state or {},
"_default_type_updater": default_type_updater,
"_default_permission_updater": default_permission_updater,
},
)
logger.trace(f"Define new matcher {NewMatcher}")
matchers[priority].append(NewMatcher)
return NewMatcher
@classmethod
async def check_perm(
cls,
bot: Bot,
event: Event,
stack: Optional[AsyncExitStack] = None,
dependency_cache: Optional[T_DependencyCache] = None,
) -> bool:
"""
:说明:
检查是否满足触发权限
:参数:
* ``bot: Bot``: Bot 对象
* ``event: Event``: 上报事件
:返回:
- ``bool``: 是否满足权限
"""
event_type = event.get_type()
return event_type == (cls.type or event_type) and await cls.permission(
bot, event, stack, dependency_cache
)
@classmethod
async def check_rule(
cls,
bot: Bot,
event: Event,
state: T_State,
stack: Optional[AsyncExitStack] = None,
dependency_cache: Optional[T_DependencyCache] = None,
) -> bool:
"""
:说明:
检查是否满足匹配规则
:参数:
* ``bot: Bot``: Bot 对象
* ``event: Event``: 上报事件
* ``state: T_State``: 当前状态
:返回:
- ``bool``: 是否满足匹配规则
"""
event_type = event.get_type()
return event_type == (cls.type or event_type) and await cls.rule(
bot, event, state, stack, dependency_cache
)
@classmethod
def type_updater(cls, func: T_TypeUpdater) -> T_TypeUpdater:
"""
:说明:
装饰一个函数来更改当前事件响应器的默认响应事件类型更新函数
:参数:
* ``func: T_TypeUpdater``: 响应事件类型更新函数
"""
cls._default_type_updater = Dependent[str].parse(
call=func, allow_types=cls.HANDLER_PARAM_TYPES
)
return func
@classmethod
def permission_updater(cls, func: T_PermissionUpdater) -> T_PermissionUpdater:
"""
:说明:
装饰一个函数来更改当前事件响应器的默认会话权限更新函数
:参数:
* ``func: T_PermissionUpdater``: 会话权限更新函数
"""
cls._default_permission_updater = Dependent[Permission].parse(
call=func, allow_types=cls.HANDLER_PARAM_TYPES
)
return func
@classmethod
def append_handler(
cls, handler: T_Handler, parameterless: Optional[List[Any]] = None
) -> Dependent[Any]:
handler_ = Dependent[Any].parse(
call=handler,
parameterless=parameterless,
allow_types=cls.HANDLER_PARAM_TYPES,
)
cls.handlers.append(handler_)
return handler_
@classmethod
def handle(
cls, parameterless: Optional[List[Any]] = None
) -> Callable[[T_Handler], T_Handler]:
"""
:说明:
装饰一个函数来向事件响应器直接添加一个处理函数
:参数:
* ``parameterless: Optional[List[Any]]``: 非参数类型依赖列表
"""
def _decorator(func: T_Handler) -> T_Handler:
cls.append_handler(func, parameterless=parameterless)
return func
return _decorator
@classmethod
def receive(
cls, id: str = "", parameterless: Optional[List[Any]] = None
) -> Callable[[T_Handler], T_Handler]:
"""
:说明:
装饰一个函数来指示 NoneBot 在接收用户新的一条消息后继续运行该函数
:参数:
* ``id: str``: 消息 ID
* ``parameterless: Optional[List[Any]]``: 非参数类型依赖列表
"""
async def _receive(event: Event, matcher: "Matcher") -> Union[None, NoReturn]:
matcher.set_target(RECEIVE_KEY.format(id=id))
if matcher.get_target() == RECEIVE_KEY.format(id=id):
matcher.set_receive(id, event)
return
if matcher.get_receive(id):
return
await matcher.reject()
_parameterless = [params.Depends(_receive), *(parameterless or [])]
def _decorator(func: T_Handler) -> T_Handler:
if cls.handlers and cls.handlers[-1].call is func:
func_handler = cls.handlers[-1]
for depend in reversed(_parameterless):
func_handler.prepend_parameterless(depend)
else:
cls.append_handler(func, parameterless=_parameterless)
return func
return _decorator
@classmethod
def got(
cls,
key: str,
prompt: Optional[Union[str, Message, MessageSegment, MessageTemplate]] = None,
parameterless: Optional[List[Any]] = None,
) -> Callable[[T_Handler], T_Handler]:
"""
:说明:
装饰一个函数来指示 NoneBot 当要获取的 ``key`` 不存在时接收用户新的一条消息并经过 ``ArgsParser`` 处理后再运行该函数,如果 ``key`` 已存在则直接继续运行
:参数:
* ``key: str``: 参数名
* ``prompt: Optional[Union[str, Message, MessageSegment, MessageFormatter]]``: 在参数不存在时向用户发送的消息
* ``args_parser: Optional[T_ArgsParser]``: 可选参数解析函数,空则使用默认解析函数
* ``parameterless: Optional[List[Any]]``: 非参数类型依赖列表
"""
async def _key_getter(event: Event, matcher: "Matcher"):
matcher.set_target(ARG_KEY.format(key=key))
if matcher.get_target() == ARG_KEY.format(key=key):
matcher.set_arg(key, event.get_message())
return
if matcher.get_arg(key):
return
await matcher.reject(prompt)
_parameterless = [
params.Depends(_key_getter),
*(parameterless or []),
]
def _decorator(func: T_Handler) -> T_Handler:
if cls.handlers and cls.handlers[-1].call is func:
func_handler = cls.handlers[-1]
for depend in reversed(_parameterless):
func_handler.prepend_parameterless(depend)
else:
cls.append_handler(func, parameterless=_parameterless)
return func
return _decorator
@classmethod
async def send(
cls,
message: Union[str, Message, MessageSegment, MessageTemplate],
**kwargs: Any,
) -> Any:
"""
:说明:
发送一条消息给当前交互用户
:参数:
* ``message: Union[str, Message, MessageSegment]``: 消息内容
* ``**kwargs``: 其他传递给 ``bot.send`` 的参数,请参考对应 adapter 的 bot 对象 api
"""
bot = current_bot.get()
event = current_event.get()
state = current_matcher.get().state
if isinstance(message, MessageTemplate):
_message = message.format(**state)
else:
_message = message
return await bot.send(event=event, message=_message, **kwargs)
@classmethod
async def finish(
cls,
message: Optional[Union[str, Message, MessageSegment, MessageTemplate]] = None,
**kwargs,
) -> NoReturn:
"""
:说明:
发送一条消息给当前交互用户并结束当前事件响应器
:参数:
* ``message: Union[str, Message, MessageSegment, MessageTemplate]``: 消息内容
* ``**kwargs``: 其他传递给 ``bot.send`` 的参数,请参考对应 adapter 的 bot 对象 api
"""
if message is not None:
await cls.send(message, **kwargs)
raise FinishedException
@classmethod
async def pause(
cls,
prompt: Optional[Union[str, Message, MessageSegment, MessageTemplate]] = None,
**kwargs,
) -> NoReturn:
"""
:说明:
发送一条消息给当前交互用户并暂停事件响应器,在接收用户新的一条消息后继续下一个处理函数
:参数:
* ``prompt: Union[str, Message, MessageSegment, MessageTemplate]``: 消息内容
* ``**kwargs``: 其他传递给 ``bot.send`` 的参数,请参考对应 adapter 的 bot 对象 api
"""
if prompt is not None:
await cls.send(prompt, **kwargs)
raise PausedException
@classmethod
async def reject(
cls,
prompt: Optional[Union[str, Message, MessageSegment, MessageTemplate]] = None,
**kwargs,
) -> NoReturn:
"""
:说明:
最近使用 ``got`` / ``receive`` 接收的消息不符合预期,发送一条消息给当前交互用户并暂停事件响应器,
在接收用户新的一条消息后继续当前处理函数
:参数:
* ``prompt: Union[str, Message, MessageSegment, MessageTemplate]``: 消息内容
* ``**kwargs``: 其他传递给 ``bot.send`` 的参数,请参考对应 adapter 的 bot 对象 api
"""
if prompt is not None:
await cls.send(prompt, **kwargs)
raise RejectedException
@classmethod
async def reject_arg(
cls,
key: str,
prompt: Optional[Union[str, Message, MessageSegment, MessageTemplate]] = None,
**kwargs,
) -> NoReturn:
"""
:说明:
最近使用 ``got`` 接收的消息不符合预期,发送一条消息给当前交互用户并暂停事件响应器,
在接收用户新的一条消息后继续当前处理函数
:参数:
* ``key: str``: 参数名
* ``prompt: Union[str, Message, MessageSegment, MessageTemplate]``: 消息内容
* ``**kwargs``: 其他传递给 ``bot.send`` 的参数,请参考对应 adapter 的 bot 对象 api
"""
matcher = current_matcher.get()
matcher.set_target(ARG_KEY.format(key=key))
if prompt is not None:
await cls.send(prompt, **kwargs)
raise RejectedException
@classmethod
async def reject_receive(
cls,
id: str = "",
prompt: Optional[Union[str, Message, MessageSegment, MessageTemplate]] = None,
**kwargs,
) -> NoReturn:
"""
:说明:
最近使用 ``got`` 接收的消息不符合预期,发送一条消息给当前交互用户并暂停事件响应器,
在接收用户新的一条消息后继续当前处理函数
:参数:
* ``id: str``: 消息 id
* ``prompt: Union[str, Message, MessageSegment, MessageTemplate]``: 消息内容
* ``**kwargs``: 其他传递给 ``bot.send`` 的参数,请参考对应 adapter 的 bot 对象 api
"""
matcher = current_matcher.get()
matcher.set_target(RECEIVE_KEY.format(id=id))
if prompt is not None:
await cls.send(prompt, **kwargs)
raise RejectedException
@classmethod
def skip(cls) -> NoReturn:
raise SkippedException
def get_receive(self, id: str, default: T = None) -> Union[Event, T]:
return self.state.get(RECEIVE_KEY.format(id=id), default)
def set_receive(self, id: str, event: Event) -> None:
self.state[RECEIVE_KEY.format(id=id)] = event
self.state[LAST_RECEIVE_KEY] = event
def get_last_receive(self, default: T = None) -> Union[Event, T]:
return self.state.get(LAST_RECEIVE_KEY, default)
def get_arg(self, key: str, default: T = None) -> Union[Message, T]:
return self.state.get(ARG_KEY.format(key=key), default)
def set_arg(self, key: str, message: Message) -> None:
self.state[ARG_KEY.format(key=key)] = message
def set_target(self, target: str, cache: bool = True) -> None:
if cache:
self.state[REJECT_CACHE_TARGET] = target
else:
self.state[REJECT_TARGET] = target
def get_target(self, default: T = None) -> Union[str, T]:
return self.state.get(REJECT_TARGET, default)
def stop_propagation(self):
"""
:说明:
阻止事件传播
"""
self.block = True
async def update_type(self, bot: Bot, event: Event) -> str:
updater = self.__class__._default_type_updater
if not updater:
return "message"
return await updater(bot=bot, event=event, state=self.state, matcher=self)
async def update_permission(self, bot: Bot, event: Event) -> Permission:
updater = self.__class__._default_permission_updater
if not updater:
return USER(event.get_session_id(), perm=self.permission)
return await updater(bot=bot, event=event, state=self.state, matcher=self)
async def resolve_reject(self):
handler = current_handler.get()
self.handlers.insert(0, handler)
if REJECT_CACHE_TARGET in self.state:
self.state[REJECT_TARGET] = self.state[REJECT_CACHE_TARGET]
async def simple_run(
self,
bot: Bot,
event: Event,
state: T_State,
stack: Optional[AsyncExitStack] = None,
dependency_cache: Optional[T_DependencyCache] = None,
):
logger.trace(
f"Matcher {self} run with incoming args: "
f"bot={bot}, event={event}, state={state}"
)
b_t = current_bot.set(bot)
e_t = current_event.set(event)
m_t = current_matcher.set(self)
try:
# Refresh preprocess state
self.state.update(state)
while self.handlers:
handler = self.handlers.pop(0)
current_handler.set(handler)
logger.debug(f"Running handler {handler}")
try:
await handler(
matcher=self,
bot=bot,
event=event,
state=self.state,
stack=stack,
dependency_cache=dependency_cache,
)
except TypeMisMatch as e:
logger.debug(
f"Handler {handler} param {e.param.name} value {e.value} "
f"mismatch type {e.param._type_display()}, skipped"
)
except SkippedException as e:
logger.debug(f"Handler {handler} skipped")
except StopPropagation:
self.block = True
finally:
logger.info(f"Matcher {self} running complete")
current_bot.reset(b_t)
current_event.reset(e_t)
current_matcher.reset(m_t)
# 运行handlers
async def run(
self,
bot: Bot,
event: Event,
state: T_State,
stack: Optional[AsyncExitStack] = None,
dependency_cache: Optional[T_DependencyCache] = None,
):
try:
await self.simple_run(bot, event, state, stack, dependency_cache)
except RejectedException:
await self.resolve_reject()
type_ = await self.update_type(bot, event)
permission = await self.update_permission(bot, event)
Matcher.new(
type_,
Rule(),
permission,
self.handlers,
temp=True,
priority=0,
block=True,
plugin=self.plugin,
module=self.module,
expire_time=datetime.now() + bot.config.session_expire_timeout,
default_state=self.state,
default_type_updater=self.__class__._default_type_updater,
default_permission_updater=self.__class__._default_permission_updater,
)
except PausedException:
type_ = await self.update_type(bot, event)
permission = await self.update_permission(bot, event)
Matcher.new(
type_,
Rule(),
permission,
self.handlers,
temp=True,
priority=0,
block=True,
plugin=self.plugin,
module=self.module,
expire_time=datetime.now() + bot.config.session_expire_timeout,
default_state=self.state,
default_type_updater=self.__class__._default_type_updater,
default_permission_updater=self.__class__._default_permission_updater,
)
except FinishedException:
pass

View File

@@ -1,21 +1,23 @@
""" """本模块定义了事件处理主要流程。
事件处理
========
NoneBot 内部处理并按优先级分发事件给所有事件响应器,提供了多个插槽以进行事件的预处理等。 NoneBot 内部处理并按优先级分发事件给所有事件响应器,提供了多个插槽以进行事件的预处理等。
FrontMatter:
sidebar_position: 2
description: nonebot.message 模块
""" """
import asyncio import asyncio
import contextlib
from datetime import datetime from datetime import datetime
from contextlib import AsyncExitStack from contextlib import AsyncExitStack
from typing import TYPE_CHECKING, Any, Set, Dict, Type, Optional, Coroutine from typing import TYPE_CHECKING, Any, Set, Dict, Type, Optional
from nonebot import params
from nonebot.log import logger from nonebot.log import logger
from nonebot.rule import TrieRule from nonebot.rule import TrieRule
from nonebot.utils import escape_tag
from nonebot.dependencies import Dependent from nonebot.dependencies import Dependent
from nonebot.matcher import Matcher, matchers from nonebot.matcher import Matcher, matchers
from nonebot.utils import escape_tag, run_coro_with_catch
from nonebot.exception import ( from nonebot.exception import (
NoLogException, NoLogException,
StopPropagation, StopPropagation,
@@ -30,132 +32,343 @@ from nonebot.typing import (
T_EventPreProcessor, T_EventPreProcessor,
T_EventPostProcessor, T_EventPostProcessor,
) )
from nonebot.internal.params import (
ArgParam,
BotParam,
EventParam,
StateParam,
DependParam,
DefaultParam,
MatcherParam,
ExceptionParam,
)
if TYPE_CHECKING: if TYPE_CHECKING:
from nonebot.adapters import Bot, Event from nonebot.adapters import Bot, Event
_event_preprocessors: Set[Dependent[None]] = set() _event_preprocessors: Set[Dependent[Any]] = set()
_event_postprocessors: Set[Dependent[None]] = set() _event_postprocessors: Set[Dependent[Any]] = set()
_run_preprocessors: Set[Dependent[None]] = set() _run_preprocessors: Set[Dependent[Any]] = set()
_run_postprocessors: Set[Dependent[None]] = set() _run_postprocessors: Set[Dependent[Any]] = set()
EVENT_PCS_PARAMS = [ EVENT_PCS_PARAMS = (
params.DependParam, DependParam,
params.BotParam, BotParam,
params.EventParam, EventParam,
params.StateParam, StateParam,
params.DefaultParam, DefaultParam,
] )
RUN_PREPCS_PARAMS = [ RUN_PREPCS_PARAMS = (
params.DependParam, DependParam,
params.BotParam, BotParam,
params.EventParam, EventParam,
params.StateParam, StateParam,
params.ArgParam, ArgParam,
params.MatcherParam, MatcherParam,
params.DefaultParam, DefaultParam,
] )
RUN_POSTPCS_PARAMS = [ RUN_POSTPCS_PARAMS = (
params.DependParam, DependParam,
params.ExceptionParam, ExceptionParam,
params.BotParam, BotParam,
params.EventParam, EventParam,
params.StateParam, StateParam,
params.ArgParam, ArgParam,
params.MatcherParam, MatcherParam,
params.DefaultParam, DefaultParam,
] )
def event_preprocessor(func: T_EventPreProcessor) -> T_EventPreProcessor: def event_preprocessor(func: T_EventPreProcessor) -> T_EventPreProcessor:
""" """事件预处理。
:说明:
事件预处理。装饰一个函数,使它在每次接收到事件并分发给各响应器之前执行。 装饰一个函数,使它在每次接收到事件并分发给各响应器之前执行。
""" """
_event_preprocessors.add( _event_preprocessors.add(
Dependent[None].parse(call=func, allow_types=EVENT_PCS_PARAMS) Dependent[Any].parse(call=func, allow_types=EVENT_PCS_PARAMS)
) )
return func return func
def event_postprocessor(func: T_EventPostProcessor) -> T_EventPostProcessor: def event_postprocessor(func: T_EventPostProcessor) -> T_EventPostProcessor:
""" """事件后处理。
:说明:
事件后处理。装饰一个函数,使它在每次接收到事件并分发给各响应器之后执行。 装饰一个函数,使它在每次接收到事件并分发给各响应器之后执行。
""" """
_event_postprocessors.add( _event_postprocessors.add(
Dependent[None].parse(call=func, allow_types=EVENT_PCS_PARAMS) Dependent[Any].parse(call=func, allow_types=EVENT_PCS_PARAMS)
) )
return func return func
def run_preprocessor(func: T_RunPreProcessor) -> T_RunPreProcessor: def run_preprocessor(func: T_RunPreProcessor) -> T_RunPreProcessor:
""" """运行预处理。
:说明:
运行预处理。装饰一个函数,使它在每次事件响应器运行前执行。 装饰一个函数,使它在每次事件响应器运行前执行。
""" """
_run_preprocessors.add( _run_preprocessors.add(
Dependent[None].parse(call=func, allow_types=RUN_PREPCS_PARAMS) Dependent[Any].parse(call=func, allow_types=RUN_PREPCS_PARAMS)
) )
return func return func
def run_postprocessor(func: T_RunPostProcessor) -> T_RunPostProcessor: def run_postprocessor(func: T_RunPostProcessor) -> T_RunPostProcessor:
""" """运行后处理。
:说明:
运行后处理。装饰一个函数,使它在每次事件响应器运行后执行。 装饰一个函数,使它在每次事件响应器运行后执行。
""" """
_run_postprocessors.add( _run_postprocessors.add(
Dependent[None].parse(call=func, allow_types=RUN_POSTPCS_PARAMS) Dependent[Any].parse(call=func, allow_types=RUN_POSTPCS_PARAMS)
) )
return func return func
async def _run_coro_with_catch(coro: Coroutine[Any, Any, Any]) -> Any: async def _apply_event_preprocessors(
bot: "Bot",
event: "Event",
state: T_State,
stack: Optional[AsyncExitStack] = None,
dependency_cache: Optional[T_DependencyCache] = None,
show_log: bool = True,
) -> bool:
"""运行事件预处理。
参数:
bot: Bot 对象
event: Event 对象
state: 会话状态
stack: 异步上下文栈
dependency_cache: 依赖缓存
show_log: 是否显示日志
返回:
是否继续处理事件
"""
if not _event_preprocessors:
return True
if show_log:
logger.debug("Running PreProcessors...")
try: try:
return await coro await asyncio.gather(
except SkippedException: *(
pass run_coro_with_catch(
proc(
bot=bot,
event=event,
state=state,
stack=stack,
dependency_cache=dependency_cache,
),
(SkippedException,),
)
for proc in _event_preprocessors
)
)
except IgnoredException:
logger.opt(colors=True).info(
f"Event {escape_tag(event.get_event_name())} is <b>ignored</b>"
)
return False
except Exception as e:
logger.opt(colors=True, exception=e).error(
"<r><bg #f8bbd0>Error when running EventPreProcessors. "
"Event ignored!</bg #f8bbd0></r>"
)
return False
return True
async def _apply_event_postprocessors(
bot: "Bot",
event: "Event",
state: T_State,
stack: Optional[AsyncExitStack] = None,
dependency_cache: Optional[T_DependencyCache] = None,
show_log: bool = True,
) -> None:
"""运行事件后处理。
参数:
bot: Bot 对象
event: Event 对象
state: 会话状态
stack: 异步上下文栈
dependency_cache: 依赖缓存
show_log: 是否显示日志
"""
if not _event_postprocessors:
return
if show_log:
logger.debug("Running PostProcessors...")
try:
await asyncio.gather(
*(
run_coro_with_catch(
proc(
bot=bot,
event=event,
state=state,
stack=stack,
dependency_cache=dependency_cache,
),
(SkippedException,),
)
for proc in _event_postprocessors
)
)
except Exception as e:
logger.opt(colors=True, exception=e).error(
"<r><bg #f8bbd0>Error when running EventPostProcessors</bg #f8bbd0></r>"
)
async def _apply_run_preprocessors(
bot: "Bot",
event: "Event",
state: T_State,
matcher: Matcher,
stack: Optional[AsyncExitStack] = None,
dependency_cache: Optional[T_DependencyCache] = None,
) -> bool:
"""运行事件响应器运行前处理。
参数:
bot: Bot 对象
event: Event 对象
state: 会话状态
matcher: 事件响应器
stack: 异步上下文栈
dependency_cache: 依赖缓存
返回:
是否继续处理事件
"""
if not _run_preprocessors:
return True
# ensure matcher function can be correctly called
with matcher.ensure_context(bot, event):
try:
await asyncio.gather(
*(
run_coro_with_catch(
proc(
matcher=matcher,
bot=bot,
event=event,
state=state,
stack=stack,
dependency_cache=dependency_cache,
),
(SkippedException,),
)
for proc in _run_preprocessors
)
)
except IgnoredException:
logger.opt(colors=True).info(f"{matcher} running is <b>cancelled</b>")
return False
except Exception as e:
logger.opt(colors=True, exception=e).error(
"<r><bg #f8bbd0>Error when running RunPreProcessors. "
"Running cancelled!</bg #f8bbd0></r>"
)
return False
return True
async def _apply_run_postprocessors(
bot: "Bot",
event: "Event",
matcher: Matcher,
exception: Optional[Exception] = None,
stack: Optional[AsyncExitStack] = None,
dependency_cache: Optional[T_DependencyCache] = None,
) -> None:
"""运行事件响应器运行后处理。
参数:
bot: Bot 对象
event: Event 对象
matcher: 事件响应器
exception: 事件响应器运行异常
stack: 异步上下文栈
dependency_cache: 依赖缓存
"""
if not _run_postprocessors:
return
with matcher.ensure_context(bot, event):
try:
await asyncio.gather(
*(
run_coro_with_catch(
proc(
matcher=matcher,
exception=exception,
bot=bot,
event=event,
state=matcher.state,
stack=stack,
dependency_cache=dependency_cache,
),
(SkippedException,),
)
for proc in _run_postprocessors
)
)
except Exception as e:
logger.opt(colors=True, exception=e).error(
"<r><bg #f8bbd0>Error when running RunPostProcessors</bg #f8bbd0></r>"
)
async def _check_matcher( async def _check_matcher(
priority: int,
Matcher: Type[Matcher], Matcher: Type[Matcher],
bot: "Bot", bot: "Bot",
event: "Event", event: "Event",
state: T_State, state: T_State,
stack: Optional[AsyncExitStack] = None, stack: Optional[AsyncExitStack] = None,
dependency_cache: Optional[T_DependencyCache] = None, dependency_cache: Optional[T_DependencyCache] = None,
) -> None: ) -> bool:
"""检查事件响应器是否符合运行条件。
请注意,过时的事件响应器将被**销毁**。对于未过时的事件响应器,将会一次检查其响应类型、权限和规则。
参数:
Matcher: 要检查的事件响应器
bot: Bot 对象
event: Event 对象
state: 会话状态
stack: 异步上下文栈
dependency_cache: 依赖缓存
返回:
bool: 是否符合运行条件
"""
if Matcher.expire_time and datetime.now() > Matcher.expire_time: if Matcher.expire_time and datetime.now() > Matcher.expire_time:
try: with contextlib.suppress(Exception):
matchers[priority].remove(Matcher) Matcher.destroy()
except Exception: return False
pass
return
try: try:
if not await Matcher.check_perm( if not await Matcher.check_perm(
bot, event, stack, dependency_cache bot, event, stack, dependency_cache
) or not await Matcher.check_rule(bot, event, state, stack, dependency_cache): ) or not await Matcher.check_rule(bot, event, state, stack, dependency_cache):
return return False
except Exception as e: except Exception as e:
logger.opt(colors=True, exception=e).error( logger.opt(colors=True, exception=e).error(
f"<r><bg #f8bbd0>Rule check failed for {Matcher}.</bg #f8bbd0></r>" f"<r><bg #f8bbd0>Rule check failed for {Matcher}.</bg #f8bbd0></r>"
) )
return return False
if Matcher.temp: return True
try:
matchers[priority].remove(Matcher)
except Exception:
pass
await _run_matcher(Matcher, bot, event, state, stack, dependency_cache)
async def _run_matcher( async def _run_matcher(
@@ -166,100 +379,116 @@ async def _run_matcher(
stack: Optional[AsyncExitStack] = None, stack: Optional[AsyncExitStack] = None,
dependency_cache: Optional[T_DependencyCache] = None, dependency_cache: Optional[T_DependencyCache] = None,
) -> None: ) -> None:
"""运行事件响应器。
临时事件响应器将在运行前被**销毁**。
参数:
Matcher: 事件响应器
bot: Bot 对象
event: Event 对象
state: 会话状态
stack: 异步上下文栈
dependency_cache: 依赖缓存
异常:
StopPropagation: 阻止事件继续传播
"""
logger.info(f"Event will be handled by {Matcher}") logger.info(f"Event will be handled by {Matcher}")
if Matcher.temp:
with contextlib.suppress(Exception):
Matcher.destroy()
matcher = Matcher() matcher = Matcher()
coros = list( if not await _apply_run_preprocessors(
map( bot=bot,
lambda x: _run_coro_with_catch( event=event,
x( state=state,
matcher=matcher, matcher=matcher,
bot=bot, stack=stack,
event=event, dependency_cache=dependency_cache,
state=state, ):
stack=stack, return
dependency_cache=dependency_cache,
)
),
_run_preprocessors,
)
)
if coros:
try:
await asyncio.gather(*coros)
except IgnoredException:
logger.opt(colors=True).info(
f"Matcher {matcher} running is <b>cancelled</b>"
)
return
except Exception as e:
logger.opt(colors=True, exception=e).error(
"<r><bg #f8bbd0>Error when running RunPreProcessors. "
"Running cancelled!</bg #f8bbd0></r>"
)
return
exception = None exception = None
try: try:
logger.debug(f"Running matcher {matcher}") logger.debug(f"Running {matcher}")
await matcher.run(bot, event, state, stack, dependency_cache) await matcher.run(bot, event, state, stack, dependency_cache)
except Exception as e: except Exception as e:
logger.opt(colors=True, exception=e).error( logger.opt(colors=True, exception=e).error(
f"<r><bg #f8bbd0>Running matcher {matcher} failed.</bg #f8bbd0></r>" f"<r><bg #f8bbd0>Running {matcher} failed.</bg #f8bbd0></r>"
) )
exception = e exception = e
coros = list( await _apply_run_postprocessors(
map( bot=bot,
lambda x: _run_coro_with_catch( event=event,
x( matcher=matcher,
matcher=matcher, exception=exception,
exception=exception, stack=stack,
bot=bot, dependency_cache=dependency_cache,
event=event,
state=state,
stack=stack,
dependency_cache=dependency_cache,
)
),
_run_postprocessors,
)
) )
if coros:
try:
await asyncio.gather(*coros)
except Exception as e:
logger.opt(colors=True, exception=e).error(
"<r><bg #f8bbd0>Error when running RunPostProcessors</bg #f8bbd0></r>"
)
if matcher.block: if matcher.block:
raise StopPropagation raise StopPropagation
return
async def check_and_run_matcher(
Matcher: Type[Matcher],
bot: "Bot",
event: "Event",
state: T_State,
stack: Optional[AsyncExitStack] = None,
dependency_cache: Optional[T_DependencyCache] = None,
) -> None:
"""检查并运行事件响应器。
参数:
Matcher: 事件响应器
bot: Bot 对象
event: Event 对象
state: 会话状态
stack: 异步上下文栈
dependency_cache: 依赖缓存
"""
if not await _check_matcher(
Matcher=Matcher,
bot=bot,
event=event,
state=state,
stack=stack,
dependency_cache=dependency_cache,
):
return
await _run_matcher(
Matcher=Matcher,
bot=bot,
event=event,
state=state,
stack=stack,
dependency_cache=dependency_cache,
)
async def handle_event(bot: "Bot", event: "Event") -> None: async def handle_event(bot: "Bot", event: "Event") -> None:
""" """处理一个事件。调用该函数以实现分发事件。
:说明:
处理一个事件。调用该函数以实现分发事件。 参数:
bot: Bot 对象
:参数: event: Event 对象
* ``bot: Bot``: Bot 对象
* ``event: Event``: Event 对象
:示例:
.. code-block:: python
用法:
```python
import asyncio import asyncio
asyncio.create_task(handle_event(bot, event)) asyncio.create_task(handle_event(bot, event))
```
""" """
show_log = True show_log = True
log_msg = f"<m>{escape_tag(bot.type.upper())} {escape_tag(bot.self_id)}</m> | " log_msg = f"<m>{escape_tag(bot.type)} {escape_tag(bot.self_id)}</m> | "
try: try:
log_msg += event.get_log_string() log_msg += event.get_log_string()
except NoLogException: except NoLogException:
@@ -270,37 +499,16 @@ async def handle_event(bot: "Bot", event: "Event") -> None:
state: Dict[Any, Any] = {} state: Dict[Any, Any] = {}
dependency_cache: T_DependencyCache = {} dependency_cache: T_DependencyCache = {}
# create event scope context
async with AsyncExitStack() as stack: async with AsyncExitStack() as stack:
coros = list( if not await _apply_event_preprocessors(
map( bot=bot,
lambda x: _run_coro_with_catch( event=event,
x( state=state,
bot=bot, stack=stack,
event=event, dependency_cache=dependency_cache,
state=state, ):
stack=stack, return
dependency_cache=dependency_cache,
)
),
_event_preprocessors,
)
)
if coros:
try:
if show_log:
logger.debug("Running PreProcessors...")
await asyncio.gather(*coros)
except IgnoredException as e:
logger.opt(colors=True).info(
f"Event {escape_tag(event.get_event_name())} is <b>ignored</b>"
)
return
except Exception as e:
logger.opt(colors=True, exception=e).error(
"<r><bg #f8bbd0>Error when running EventPreProcessors. "
"Event ignored!</bg #f8bbd0></r>"
)
return
# Trie Match # Trie Match
try: try:
@@ -311,6 +519,7 @@ async def handle_event(bot: "Bot", event: "Event") -> None:
) )
break_flag = False break_flag = False
# iterate through all priority until stop propagation
for priority in sorted(matchers.keys()): for priority in sorted(matchers.keys()):
if break_flag: if break_flag:
break break
@@ -319,14 +528,12 @@ async def handle_event(bot: "Bot", event: "Event") -> None:
logger.debug(f"Checking for matchers in priority {priority}...") logger.debug(f"Checking for matchers in priority {priority}...")
pending_tasks = [ pending_tasks = [
_check_matcher( check_and_run_matcher(
priority, matcher, bot, event, state.copy(), stack, dependency_cache matcher, bot, event, state.copy(), stack, dependency_cache
) )
for matcher in matchers[priority] for matcher in matchers[priority]
] ]
results = await asyncio.gather(*pending_tasks, return_exceptions=True) results = await asyncio.gather(*pending_tasks, return_exceptions=True)
for result in results: for result in results:
if not isinstance(result, Exception): if not isinstance(result, Exception):
continue continue
@@ -338,26 +545,7 @@ async def handle_event(bot: "Bot", event: "Event") -> None:
"<r><bg #f8bbd0>Error when checking Matcher.</bg #f8bbd0></r>" "<r><bg #f8bbd0>Error when checking Matcher.</bg #f8bbd0></r>"
) )
coros = list( if show_log:
map( logger.debug("Checking for matchers completed")
lambda x: _run_coro_with_catch(
x( await _apply_event_postprocessors(bot, event, state, stack, dependency_cache)
bot=bot,
event=event,
state=state,
stack=stack,
dependency_cache=dependency_cache,
)
),
_event_postprocessors,
)
)
if coros:
try:
if show_log:
logger.debug("Running PostProcessors...")
await asyncio.gather(*coros)
except Exception as e:
logger.opt(colors=True, exception=e).error(
"<r><bg #f8bbd0>Error when running EventPostProcessors</bg #f8bbd0></r>"
)

View File

@@ -1,249 +1,42 @@
import asyncio """本模块定义了依赖注入的各类参数。
import inspect
from typing_extensions import Literal
from typing import Any, Dict, List, Tuple, Callable, Optional, cast
from contextlib import AsyncExitStack, contextmanager, asynccontextmanager
from pydantic.fields import Required, Undefined, ModelField FrontMatter:
sidebar_position: 4
description: nonebot.params 模块
"""
from nonebot.log import logger from typing import Any, Dict, List, Match, Tuple, Union, Optional
from nonebot.exception import TypeMisMatch
from nonebot.adapters import Bot, Event, Message from nonebot.typing import T_State
from nonebot.dependencies import Param, Dependent, CustomConfig from nonebot.matcher import Matcher
from nonebot.typing import T_State, T_Handler, T_DependencyCache from nonebot.internal.params import Arg as Arg
from nonebot.internal.params import ArgStr as ArgStr
from nonebot.internal.params import Depends as Depends
from nonebot.internal.params import ArgParam as ArgParam
from nonebot.internal.params import BotParam as BotParam
from nonebot.adapters import Event, Message, MessageSegment
from nonebot.internal.params import EventParam as EventParam
from nonebot.internal.params import StateParam as StateParam
from nonebot.internal.params import DependParam as DependParam
from nonebot.internal.params import ArgPlainText as ArgPlainText
from nonebot.internal.params import DefaultParam as DefaultParam
from nonebot.internal.params import MatcherParam as MatcherParam
from nonebot.internal.params import ExceptionParam as ExceptionParam
from nonebot.consts import ( from nonebot.consts import (
CMD_KEY, CMD_KEY,
PREFIX_KEY, PREFIX_KEY,
REGEX_DICT,
SHELL_ARGS, SHELL_ARGS,
SHELL_ARGV, SHELL_ARGV,
CMD_ARG_KEY, CMD_ARG_KEY,
KEYWORD_KEY,
RAW_CMD_KEY, RAW_CMD_KEY,
REGEX_GROUP, ENDSWITH_KEY,
CMD_START_KEY,
FULLMATCH_KEY,
REGEX_MATCHED, REGEX_MATCHED,
STARTSWITH_KEY,
CMD_WHITESPACE_KEY,
) )
from nonebot.utils import (
get_name,
run_sync,
is_gen_callable,
run_sync_ctx_manager,
is_async_gen_callable,
is_coroutine_callable,
generic_check_issubclass,
)
class DependsInner:
def __init__(
self,
dependency: Optional[T_Handler] = None,
*,
use_cache: bool = True,
) -> None:
self.dependency = dependency
self.use_cache = use_cache
def __repr__(self) -> str:
dep = get_name(self.dependency)
cache = "" if self.use_cache else ", use_cache=False"
return f"{self.__class__.__name__}({dep}{cache})"
def Depends(
dependency: Optional[T_Handler] = None,
*,
use_cache: bool = True,
) -> Any:
"""
:说明:
参数依赖注入装饰器
:参数:
* ``dependency: Optional[Callable[..., Any]] = None``: 依赖函数。默认为参数的类型注释。
* ``use_cache: bool = True``: 是否使用缓存。默认为 ``True``。
.. code-block:: python
def depend_func() -> Any:
return ...
def depend_gen_func():
try:
yield ...
finally:
...
async def handler(param_name: Any = Depends(depend_func), gen: Any = Depends(depend_gen_func)):
...
"""
return DependsInner(dependency, use_cache=use_cache)
class DependParam(Param):
@classmethod
def _check_param(
cls,
dependent: Dependent,
name: str,
param: inspect.Parameter,
) -> Optional["DependParam"]:
if isinstance(param.default, DependsInner):
dependency: T_Handler
if param.default.dependency is None:
assert param.annotation is not param.empty, "Dependency cannot be empty"
dependency = param.annotation
else:
dependency = param.default.dependency
sub_dependent = Dependent[Any].parse(
call=dependency,
allow_types=dependent.allow_types,
)
dependent.pre_checkers.extend(sub_dependent.pre_checkers)
sub_dependent.pre_checkers.clear()
return cls(
Required, use_cache=param.default.use_cache, dependent=sub_dependent
)
@classmethod
def _check_parameterless(
cls, dependent: "Dependent", value: Any
) -> Optional["Param"]:
if isinstance(value, DependsInner):
assert value.dependency, "Dependency cannot be empty"
dependent = Dependent[Any].parse(
call=value.dependency, allow_types=dependent.allow_types
)
return cls(Required, use_cache=value.use_cache, dependent=dependent)
async def _solve(
self,
stack: Optional[AsyncExitStack] = None,
dependency_cache: Optional[T_DependencyCache] = None,
**kwargs: Any,
) -> Any:
use_cache: bool = self.extra["use_cache"]
dependency_cache = {} if dependency_cache is None else dependency_cache
sub_dependent: Dependent = self.extra["dependent"]
sub_dependent.call = cast(Callable[..., Any], sub_dependent.call)
call = sub_dependent.call
# solve sub dependency with current cache
sub_values = await sub_dependent.solve(
stack=stack,
dependency_cache=dependency_cache,
**kwargs,
)
# run dependency function
task: asyncio.Task[Any]
if use_cache and call in dependency_cache:
solved = await dependency_cache[call]
elif is_gen_callable(call) or is_async_gen_callable(call):
assert isinstance(
stack, AsyncExitStack
), "Generator dependency should be called in context"
if is_gen_callable(call):
cm = run_sync_ctx_manager(contextmanager(call)(**sub_values))
else:
cm = asynccontextmanager(call)(**sub_values)
task = asyncio.create_task(stack.enter_async_context(cm))
dependency_cache[call] = task
solved = await task
elif is_coroutine_callable(call):
task = asyncio.create_task(call(**sub_values))
dependency_cache[call] = task
solved = await task
else:
task = asyncio.create_task(run_sync(call)(**sub_values))
dependency_cache[call] = task
solved = await task
return solved
class _BotChecker(Param):
async def _solve(self, bot: Bot, **kwargs: Any) -> Any:
field: ModelField = self.extra["field"]
_, errs_ = field.validate(bot, {}, loc=("bot",))
if errs_:
logger.debug(
f"Bot type {type(bot)} not match "
f"annotation {field._type_display()}, ignored"
)
raise TypeMisMatch(field, bot)
class BotParam(Param):
@classmethod
def _check_param(
cls, dependent: Dependent, name: str, param: inspect.Parameter
) -> Optional["BotParam"]:
if param.default == param.empty:
if generic_check_issubclass(param.annotation, Bot):
if param.annotation is not Bot:
dependent.pre_checkers.append(
_BotChecker(
Required,
field=ModelField(
name=name,
type_=param.annotation,
class_validators=None,
model_config=CustomConfig,
default=None,
required=True,
),
)
)
return cls(Required)
elif param.annotation == param.empty and name == "bot":
return cls(Required)
async def _solve(self, bot: Bot, **kwargs: Any) -> Any:
return bot
class _EventChecker(Param):
async def _solve(self, event: Event, **kwargs: Any) -> Any:
field: ModelField = self.extra["field"]
_, errs_ = field.validate(event, {}, loc=("event",))
if errs_:
logger.debug(
f"Event type {type(event)} not match "
f"annotation {field._type_display()}, ignored"
)
raise TypeMisMatch(field, event)
class EventParam(Param):
@classmethod
def _check_param(
cls, dependent: Dependent, name: str, param: inspect.Parameter
) -> Optional["EventParam"]:
if param.default == param.empty:
if generic_check_issubclass(param.annotation, Event):
if param.annotation is not Event:
dependent.pre_checkers.append(
_EventChecker(
Required,
field=ModelField(
name=name,
type_=param.annotation,
class_validators=None,
model_config=CustomConfig,
default=None,
required=True,
),
)
)
return cls(Required)
elif param.annotation == param.empty and name == "event":
return cls(Required)
async def _solve(self, event: Event, **kwargs: Any) -> Any:
return event
async def _event_type(event: Event) -> str: async def _event_type(event: Event) -> str:
@@ -251,6 +44,7 @@ async def _event_type(event: Event) -> str:
def EventType() -> str: def EventType() -> str:
"""{ref}`nonebot.adapters.Event` 类型参数"""
return Depends(_event_type) return Depends(_event_type)
@@ -259,6 +53,7 @@ async def _event_message(event: Event) -> Message:
def EventMessage() -> Any: def EventMessage() -> Any:
"""{ref}`nonebot.adapters.Event` 消息参数"""
return Depends(_event_message) return Depends(_event_message)
@@ -267,6 +62,7 @@ async def _event_plain_text(event: Event) -> str:
def EventPlainText() -> str: def EventPlainText() -> str:
"""{ref}`nonebot.adapters.Event` 纯文本消息参数"""
return Depends(_event_plain_text) return Depends(_event_plain_text)
@@ -275,185 +71,174 @@ async def _event_to_me(event: Event) -> bool:
def EventToMe() -> bool: def EventToMe() -> bool:
"""{ref}`nonebot.adapters.Event` `to_me` 参数"""
return Depends(_event_to_me) return Depends(_event_to_me)
class StateInner: def _command(state: T_State) -> Message:
...
def State() -> T_State:
return StateInner() # type: ignore
class StateParam(Param):
@classmethod
def _check_param(
cls, dependent: Dependent, name: str, param: inspect.Parameter
) -> Optional["StateParam"]:
if isinstance(param.default, StateInner):
return cls(Required)
async def _solve(self, state: T_State, **kwargs: Any) -> Any:
return state
def _command(state=State()) -> Message:
return state[PREFIX_KEY][CMD_KEY] return state[PREFIX_KEY][CMD_KEY]
def Command() -> Tuple[str, ...]: def Command() -> Tuple[str, ...]:
return Depends(_command, use_cache=False) """消息命令元组"""
return Depends(_command)
def _raw_command(state=State()) -> Message: def _raw_command(state: T_State) -> Message:
return state[PREFIX_KEY][RAW_CMD_KEY] return state[PREFIX_KEY][RAW_CMD_KEY]
def RawCommand() -> str: def RawCommand() -> str:
return Depends(_raw_command, use_cache=False) """消息命令文本"""
return Depends(_raw_command)
def _command_arg(state=State()) -> Message: def _command_arg(state: T_State) -> Message:
return state[PREFIX_KEY][CMD_ARG_KEY] return state[PREFIX_KEY][CMD_ARG_KEY]
def CommandArg() -> Any: def CommandArg() -> Any:
return Depends(_command_arg, use_cache=False) """消息命令参数"""
return Depends(_command_arg)
def _shell_command_args(state=State()) -> Any: def _command_start(state: T_State) -> str:
return state[SHELL_ARGS] return state[PREFIX_KEY][CMD_START_KEY]
def ShellCommandArgs(): def CommandStart() -> str:
"""消息命令开头"""
return Depends(_command_start)
def _command_whitespace(state: T_State) -> str:
return state[PREFIX_KEY][CMD_WHITESPACE_KEY]
def CommandWhitespace() -> str:
"""消息命令与参数之间的空白"""
return Depends(_command_whitespace)
def _shell_command_args(state: T_State) -> Any:
return state[SHELL_ARGS] # Namespace or ParserExit
def ShellCommandArgs() -> Any:
"""shell 命令解析后的参数字典"""
return Depends(_shell_command_args, use_cache=False) return Depends(_shell_command_args, use_cache=False)
def _shell_command_argv(state=State()) -> List[str]: def _shell_command_argv(state: T_State) -> List[Union[str, MessageSegment]]:
return state[SHELL_ARGV] return state[SHELL_ARGV]
def ShellCommandArgv() -> Any: def ShellCommandArgv() -> Any:
"""shell 命令原始参数列表"""
return Depends(_shell_command_argv, use_cache=False) return Depends(_shell_command_argv, use_cache=False)
def _regex_matched(state=State()) -> str: def _regex_matched(state: T_State) -> Match[str]:
return state[REGEX_MATCHED] return state[REGEX_MATCHED]
def RegexMatched() -> str: def RegexMatched() -> Match[str]:
"""正则匹配结果"""
return Depends(_regex_matched, use_cache=False) return Depends(_regex_matched, use_cache=False)
def _regex_group(state=State()): def _regex_str(state: T_State) -> str:
return state[REGEX_GROUP] return _regex_matched(state).group()
def RegexStr() -> str:
"""正则匹配结果文本"""
return Depends(_regex_str, use_cache=False)
def _regex_group(state: T_State) -> Tuple[Any, ...]:
return _regex_matched(state).groups()
def RegexGroup() -> Tuple[Any, ...]: def RegexGroup() -> Tuple[Any, ...]:
"""正则匹配结果 group 元组"""
return Depends(_regex_group, use_cache=False) return Depends(_regex_group, use_cache=False)
def _regex_dict(state=State()): def _regex_dict(state: T_State) -> Dict[str, Any]:
return state[REGEX_DICT] return _regex_matched(state).groupdict()
def RegexDict() -> Dict[str, Any]: def RegexDict() -> Dict[str, Any]:
"""正则匹配结果 group 字典"""
return Depends(_regex_dict, use_cache=False) return Depends(_regex_dict, use_cache=False)
class MatcherParam(Param): def _startswith(state: T_State) -> str:
@classmethod return state[STARTSWITH_KEY]
def _check_param(
cls, dependent: Dependent, name: str, param: inspect.Parameter
) -> Optional["MatcherParam"]:
if generic_check_issubclass(param.annotation, Matcher) or (
param.annotation == param.empty and name == "matcher"
):
return cls(Required)
async def _solve(self, matcher: "Matcher", **kwargs: Any) -> Any:
return matcher def Startswith() -> str:
"""响应触发前缀"""
return Depends(_startswith, use_cache=False)
def _endswith(state: T_State) -> str:
return state[ENDSWITH_KEY]
def Endswith() -> str:
"""响应触发后缀"""
return Depends(_endswith, use_cache=False)
def _fullmatch(state: T_State) -> str:
return state[FULLMATCH_KEY]
def Fullmatch() -> str:
"""响应触发完整消息"""
return Depends(_fullmatch, use_cache=False)
def _keyword(state: T_State) -> str:
return state[KEYWORD_KEY]
def Keyword() -> str:
"""响应触发关键字"""
return Depends(_keyword, use_cache=False)
def Received(id: Optional[str] = None, default: Any = None) -> Any: def Received(id: Optional[str] = None, default: Any = None) -> Any:
def _received(matcher: "Matcher"): """`receive` 事件参数"""
def _received(matcher: "Matcher") -> Any:
return matcher.get_receive(id or "", default) return matcher.get_receive(id or "", default)
return Depends(_received, use_cache=False) return Depends(_received, use_cache=False)
def LastReceived(default: Any = None) -> Any: def LastReceived(default: Any = None) -> Any:
"""`last_receive` 事件参数"""
def _last_received(matcher: "Matcher") -> Any: def _last_received(matcher: "Matcher") -> Any:
return matcher.get_last_receive(default) return matcher.get_last_receive(default)
return Depends(_last_received, use_cache=False) return Depends(_last_received, use_cache=False)
class ArgInner: __autodoc__ = {
def __init__( "Arg": True,
self, key: Optional[str], type: Literal["message", "str", "plaintext"] "ArgStr": True,
) -> None: "Depends": True,
self.key = key "ArgParam": True,
self.type = type "BotParam": True,
"EventParam": True,
"StateParam": True,
def Arg(key: Optional[str] = None) -> Any: "DependParam": True,
return ArgInner(key, "message") "ArgPlainText": True,
"DefaultParam": True,
"MatcherParam": True,
def ArgStr(key: Optional[str] = None) -> str: "ExceptionParam": True,
return ArgInner(key, "str") # type: ignore }
def ArgPlainText(key: Optional[str] = None) -> str:
return ArgInner(key, "plaintext") # type: ignore
class ArgParam(Param):
@classmethod
def _check_param(
cls, dependent: Dependent, name: str, param: inspect.Parameter
) -> Optional["ArgParam"]:
if isinstance(param.default, ArgInner):
return cls(Required, key=param.default.key or name, type=param.default.type)
async def _solve(self, matcher: "Matcher", **kwargs: Any) -> Any:
message = matcher.get_arg(self.extra["key"])
if message is None:
return message
if self.extra["type"] == "message":
return message
elif self.extra["type"] == "str":
return str(message)
else:
return message.extract_plain_text()
class ExceptionParam(Param):
@classmethod
def _check_param(
cls, dependent: Dependent, name: str, param: inspect.Parameter
) -> Optional["ExceptionParam"]:
if generic_check_issubclass(param.annotation, Exception) or (
param.annotation == param.empty and name == "exception"
):
return cls(Required)
async def _solve(self, exception: Optional[Exception] = None, **kwargs: Any) -> Any:
return exception
class DefaultParam(Param):
@classmethod
def _check_param(
cls, dependent: Dependent, name: str, param: inspect.Parameter
) -> Optional["DefaultParam"]:
if param.default != param.empty:
return cls(param.default)
async def _solve(self, **kwargs: Any) -> Any:
return Undefined
from nonebot.matcher import Matcher

View File

@@ -1,219 +1,117 @@
r""" """本模块是 {ref}`nonebot.matcher.Matcher.permission` 的类型定义。
权限
====
每个 ``Matcher`` 拥有一个 ``Permission`` ,其中是 ``PermissionChecker`` 的集合,只要有一个 ``PermissionChecker`` 检查结果为 ``True`` 时就会继续运行。 每个{ref}`事件响应器 <nonebot.matcher.Matcher>`
拥有一个 {ref}`nonebot.permission.Permission`,其中是 `PermissionChecker` 的集合。
只要有一个 `PermissionChecker` 检查结果为 `True` 时就会继续运行。
\:\:\:tip 提示 FrontMatter:
``PermissionChecker`` 既可以是 async function 也可以是 sync function sidebar_position: 6
\:\:\: description: nonebot.permission 模块
""" """
import asyncio from nonebot.params import EventType
from contextlib import AsyncExitStack
from typing import Any, Set, Tuple, Union, NoReturn, Optional, Coroutine
from nonebot.adapters import Bot, Event from nonebot.adapters import Bot, Event
from nonebot.dependencies import Dependent from nonebot.internal.permission import USER as USER
from nonebot.exception import SkippedException from nonebot.internal.permission import User as User
from nonebot.typing import T_Handler, T_DependencyCache, T_PermissionChecker from nonebot.internal.permission import Permission as Permission
from nonebot.params import (
BotParam,
EventType,
EventParam,
DependParam,
DefaultParam,
)
async def _run_coro_with_catch(coro: Coroutine[Any, Any, Any]):
try:
return await coro
except SkippedException:
return False
class Permission:
"""
:说明:
``Matcher`` 规则类,当事件传递时,在 ``Matcher`` 运行前进行检查。
:示例:
.. code-block:: python
Permission(async_function) | sync_function
# 等价于
from nonebot.utils import run_sync
Permission(async_function, run_sync(sync_function))
"""
__slots__ = ("checkers",)
HANDLER_PARAM_TYPES = [
DependParam,
BotParam,
EventParam,
DefaultParam,
]
def __init__(self, *checkers: Union[T_PermissionChecker, Dependent[bool]]) -> None:
"""
:参数:
* ``*checkers: Union[T_PermissionChecker, Dependent[bool]``: PermissionChecker
"""
self.checkers: Set[Dependent[bool]] = set(
checker
if isinstance(checker, Dependent)
else Dependent[bool].parse(
call=checker, allow_types=self.HANDLER_PARAM_TYPES
)
for checker in checkers
)
"""
:说明:
存储 ``PermissionChecker``
:类型:
* ``Set[Dependent[bool]]``
"""
async def __call__(
self,
bot: Bot,
event: Event,
stack: Optional[AsyncExitStack] = None,
dependency_cache: Optional[T_DependencyCache] = None,
) -> bool:
"""
:说明:
检查是否满足某个权限
:参数:
* ``bot: Bot``: Bot 对象
* ``event: Event``: Event 对象
* ``stack: Optional[AsyncExitStack]``: 异步上下文栈
* ``dependency_cache: Optional[CacheDict[T_Handler, Any]]``: 依赖缓存
:返回:
- ``bool``
"""
if not self.checkers:
return True
results = await asyncio.gather(
*(
_run_coro_with_catch(
checker(
bot=bot,
event=event,
stack=stack,
dependency_cache=dependency_cache,
)
)
for checker in self.checkers
),
)
return any(results)
def __and__(self, other) -> NoReturn:
raise RuntimeError("And operation between Permissions is not allowed.")
def __or__(
self, other: Optional[Union["Permission", T_PermissionChecker]]
) -> "Permission":
if other is None:
return self
elif isinstance(other, Permission):
return Permission(*self.checkers, *other.checkers)
else:
return Permission(*self.checkers, other)
class Message: class Message:
"""检查是否为消息事件"""
__slots__ = ()
def __repr__(self) -> str:
return "Message()"
async def __call__(self, type: str = EventType()) -> bool: async def __call__(self, type: str = EventType()) -> bool:
return type == "message" return type == "message"
class Notice: class Notice:
"""检查是否为通知事件"""
__slots__ = ()
def __repr__(self) -> str:
return "Notice()"
async def __call__(self, type: str = EventType()) -> bool: async def __call__(self, type: str = EventType()) -> bool:
return type == "notice" return type == "notice"
class Request: class Request:
"""检查是否为请求事件"""
__slots__ = ()
def __repr__(self) -> str:
return "Request()"
async def __call__(self, type: str = EventType()) -> bool: async def __call__(self, type: str = EventType()) -> bool:
return type == "request" return type == "request"
class MetaEvent: class MetaEvent:
"""检查是否为元事件"""
__slots__ = ()
def __repr__(self) -> str:
return "MetaEvent()"
async def __call__(self, type: str = EventType()) -> bool: async def __call__(self, type: str = EventType()) -> bool:
return type == "meta_event" return type == "meta_event"
MESSAGE = Permission(Message()) MESSAGE: Permission = Permission(Message())
"""匹配任意 `message` 类型事件
仅在需要同时捕获不同类型事件时使用,优先使用 message type 的 Matcher。
""" """
- **说明**: 匹配任意 ``message`` 类型事件,仅在需要同时捕获不同类型事件时使用。优先使用 message type 的 Matcher。 NOTICE: Permission = Permission(Notice())
"""匹配任意 `notice` 类型事件
仅在需要同时捕获不同类型事件时使用,优先使用 notice type 的 Matcher。
""" """
NOTICE = Permission(Notice()) REQUEST: Permission = Permission(Request())
"""匹配任意 `request` 类型事件
仅在需要同时捕获不同类型事件时使用,优先使用 request type 的 Matcher。
""" """
- **说明**: 匹配任意 ``notice`` 类型事件,仅在需要同时捕获不同类型事件时使用。优先使用 notice type 的 Matcher。 METAEVENT: Permission = Permission(MetaEvent())
"""匹配任意 `meta_event` 类型事件
仅在需要同时捕获不同类型事件时使用,优先使用 meta_event type 的 Matcher。
""" """
REQUEST = Permission(Request())
"""
- **说明**: 匹配任意 ``request`` 类型事件,仅在需要同时捕获不同类型事件时使用。优先使用 request type 的 Matcher。
"""
METAEVENT = Permission(MetaEvent())
"""
- **说明**: 匹配任意 ``meta_event`` 类型事件,仅在需要同时捕获不同类型事件时使用。优先使用 meta_event type 的 Matcher。
"""
class User:
def __init__(
self, users: Tuple[str, ...], perm: Optional[Permission] = None
) -> None:
self.users = users
self.perm = perm
async def __call__(self, bot: Bot, event: Event) -> bool:
return bool(
event.get_session_id() in self.users
and (self.perm is None or await self.perm(bot, event))
)
def USER(*users: str, perm: Optional[Permission] = None):
"""
:说明:
``event`` 的 ``session_id`` 在白名单内且满足 perm
:参数:
* ``*user: str``: 白名单
* ``perm: Optional[Permission]``: 需要同时满足的权限
"""
return Permission(User(users, perm))
class SuperUser: class SuperUser:
"""检查当前事件是否是消息事件且属于超级管理员"""
__slots__ = ()
def __repr__(self) -> str:
return "Superuser()"
async def __call__(self, bot: Bot, event: Event) -> bool: async def __call__(self, bot: Bot, event: Event) -> bool:
return event.get_type() == "message" and ( try:
f"{bot.adapter.get_name().split(maxsplit=1)[0].lower()}:{event.get_user_id()}" user_id = event.get_user_id()
except Exception:
return False
return (
f"{bot.adapter.get_name().split(maxsplit=1)[0].lower()}:{user_id}"
in bot.config.superusers in bot.config.superusers
or event.get_user_id() in bot.config.superusers # 兼容旧配置 or user_id in bot.config.superusers # 兼容旧配置
) )
SUPERUSER = Permission(SuperUser()) SUPERUSER: Permission = Permission(SuperUser())
""" """匹配任意超级用户事件"""
- **说明**: 匹配任意超级用户消息类型事件
""" __autodoc__ = {
"Permission": True,
"Permission.__call__": True,
"User": True,
"USER": True,
}

View File

@@ -1,22 +1,116 @@
""" """本模块为 NoneBot 插件开发提供便携的定义函数。
插件
====
为 NoneBot 插件开发提供便携的定义函数。 ## 快捷导入
为方便使用,本模块从子模块导入了部分内容,以下内容可以直接通过本模块导入:
- `on` => {ref}``on` <nonebot.plugin.on.on>`
- `on_metaevent` => {ref}``on_metaevent` <nonebot.plugin.on.on_metaevent>`
- `on_message` => {ref}``on_message` <nonebot.plugin.on.on_message>`
- `on_notice` => {ref}``on_notice` <nonebot.plugin.on.on_notice>`
- `on_request` => {ref}``on_request` <nonebot.plugin.on.on_request>`
- `on_startswith` => {ref}``on_startswith` <nonebot.plugin.on.on_startswith>`
- `on_endswith` => {ref}``on_endswith` <nonebot.plugin.on.on_endswith>`
- `on_fullmatch` => {ref}``on_fullmatch` <nonebot.plugin.on.on_fullmatch>`
- `on_keyword` => {ref}``on_keyword` <nonebot.plugin.on.on_keyword>`
- `on_command` => {ref}``on_command` <nonebot.plugin.on.on_command>`
- `on_shell_command` => {ref}``on_shell_command` <nonebot.plugin.on.on_shell_command>`
- `on_regex` => {ref}``on_regex` <nonebot.plugin.on.on_regex>`
- `on_type` => {ref}``on_type` <nonebot.plugin.on.on_type>`
- `CommandGroup` => {ref}``CommandGroup` <nonebot.plugin.on.CommandGroup>`
- `Matchergroup` => {ref}``MatcherGroup` <nonebot.plugin.on.MatcherGroup>`
- `load_plugin` => {ref}``load_plugin` <nonebot.plugin.load.load_plugin>`
- `load_plugins` => {ref}``load_plugins` <nonebot.plugin.load.load_plugins>`
- `load_all_plugins` => {ref}``load_all_plugins` <nonebot.plugin.load.load_all_plugins>`
- `load_from_json` => {ref}``load_from_json` <nonebot.plugin.load.load_from_json>`
- `load_from_toml` => {ref}``load_from_toml` <nonebot.plugin.load.load_from_toml>`
- `load_builtin_plugin` =>
{ref}``load_builtin_plugin` <nonebot.plugin.load.load_builtin_plugin>`
- `load_builtin_plugins` =>
{ref}``load_builtin_plugins` <nonebot.plugin.load.load_builtin_plugins>`
- `require` => {ref}``require` <nonebot.plugin.load.require>`
- `PluginMetadata` => {ref}``PluginMetadata` <nonebot.plugin.plugin.PluginMetadata>`
FrontMatter:
sidebar_position: 0
description: nonebot.plugin 模块
""" """
from typing import List, Optional from itertools import chain
from types import ModuleType
from contextvars import ContextVar from contextvars import ContextVar
from typing import Set, Dict, List, Tuple, Optional
_plugins: Dict[str, "Plugin"] = {}
_managers: List["PluginManager"] = [] _managers: List["PluginManager"] = []
_current_plugin: ContextVar[Optional["Plugin"]] = ContextVar( _current_plugin_chain: ContextVar[Tuple["Plugin", ...]] = ContextVar(
"_current_plugin", default=None "_current_plugin_chain", default=()
) )
def _module_name_to_plugin_name(module_name: str) -> str:
return module_name.rsplit(".", 1)[-1]
def _new_plugin(
module_name: str, module: ModuleType, manager: "PluginManager"
) -> "Plugin":
plugin_name = _module_name_to_plugin_name(module_name)
if plugin_name in _plugins:
raise RuntimeError("Plugin already exists! Check your plugin name.")
plugin = Plugin(plugin_name, module, module_name, manager)
_plugins[plugin_name] = plugin
return plugin
def _revert_plugin(plugin: "Plugin") -> None:
if plugin.name not in _plugins:
raise RuntimeError("Plugin not found!")
del _plugins[plugin.name]
if parent_plugin := plugin.parent_plugin:
parent_plugin.sub_plugins.remove(plugin)
def get_plugin(name: str) -> Optional["Plugin"]:
"""获取已经导入的某个插件。
如果为 `load_plugins` 文件夹导入的插件,则为文件(夹)名。
参数:
name: 插件名,即 {ref}`nonebot.plugin.plugin.Plugin.name`。
"""
return _plugins.get(name)
def get_plugin_by_module_name(module_name: str) -> Optional["Plugin"]:
"""通过模块名获取已经导入的某个插件。
如果提供的模块名为某个插件的子模块,同样会返回该插件。
参数:
module_name: 模块名,即 {ref}`nonebot.plugin.plugin.Plugin.module_name`。
"""
loaded = {plugin.module_name: plugin for plugin in _plugins.values()}
has_parent = True
while has_parent:
if module_name in loaded:
return loaded[module_name]
module_name, *has_parent = module_name.rsplit(".", 1)
def get_loaded_plugins() -> Set["Plugin"]:
"""获取当前已导入的所有插件。"""
return set(_plugins.values())
def get_available_plugin_names() -> Set[str]:
"""获取当前所有可用的插件名(包含尚未加载的插件)。"""
return {*chain.from_iterable(manager.available_plugins for manager in _managers)}
from .on import on as on from .on import on as on
from .manager import PluginManager from .manager import PluginManager
from .export import Export as Export from .on import on_type as on_type
from .export import export as export
from .load import require as require from .load import require as require
from .on import on_regex as on_regex from .on import on_regex as on_regex
from .plugin import Plugin as Plugin from .plugin import Plugin as Plugin
@@ -29,13 +123,15 @@ from .on import on_endswith as on_endswith
from .load import load_plugin as load_plugin from .load import load_plugin as load_plugin
from .on import CommandGroup as CommandGroup from .on import CommandGroup as CommandGroup
from .on import MatcherGroup as MatcherGroup from .on import MatcherGroup as MatcherGroup
from .on import on_fullmatch as on_fullmatch
from .on import on_metaevent as on_metaevent from .on import on_metaevent as on_metaevent
from .plugin import get_plugin as get_plugin
from .load import load_plugins as load_plugins from .load import load_plugins as load_plugins
from .on import on_startswith as on_startswith from .on import on_startswith as on_startswith
from .load import load_from_json as load_from_json from .load import load_from_json as load_from_json
from .load import load_from_toml as load_from_toml from .load import load_from_toml as load_from_toml
from .on import on_shell_command as on_shell_command from .on import on_shell_command as on_shell_command
from .plugin import PluginMetadata as PluginMetadata
from .load import load_all_plugins as load_all_plugins from .load import load_all_plugins as load_all_plugins
from .plugin import get_loaded_plugins as get_loaded_plugins from .load import load_builtin_plugin as load_builtin_plugin
from .load import load_builtin_plugins as load_builtin_plugins from .load import load_builtin_plugins as load_builtin_plugins
from .load import inherit_supported_adapters as inherit_supported_adapters

View File

@@ -1,60 +0,0 @@
from . import _current_plugin
class Export(dict):
"""
:说明:
插件导出内容以使得其他插件可以获得。
:示例:
.. code-block:: python
nonebot.export().default = "bar"
@nonebot.export()
def some_function():
pass
# this doesn't work before python 3.9
# use
# export = nonebot.export(); @export.sub
# instead
# See also PEP-614: https://www.python.org/dev/peps/pep-0614/
@nonebot.export().sub
def something_else():
pass
"""
def __call__(self, func, **kwargs):
self[func.__name__] = func
self.update(kwargs)
return func
def __setitem__(self, key, value):
super().__setitem__(key, Export(value) if isinstance(value, dict) else value)
def __setattr__(self, name, value):
self[name] = Export(value) if isinstance(value, dict) else value
def __getattr__(self, name):
if name not in self:
self[name] = Export()
return self[name]
def export() -> Export:
"""
:说明:
获取插件的导出内容对象
:返回:
- ``Export``
"""
plugin = _current_plugin.get()
if not plugin:
raise RuntimeError("Export outside of the plugin!")
return plugin.export

View File

@@ -1,48 +1,49 @@
"""本模块定义插件加载接口。
FrontMatter:
sidebar_position: 1
description: nonebot.plugin.load 模块
"""
import json import json
import warnings from pathlib import Path
from typing import Set, Iterable, Optional from types import ModuleType
from typing import Set, Union, Iterable, Optional
import tomlkit from nonebot.utils import path_to_module_name
from . import _managers from .plugin import Plugin
from .export import Export
from .manager import PluginManager from .manager import PluginManager
from .plugin import Plugin, get_plugin from . import _managers, get_plugin, _current_plugin_chain, _module_name_to_plugin_name
try: # pragma: py-gte-311
import tomllib # pyright: ignore[reportMissingImports]
except ModuleNotFoundError: # pragma: py-lt-311
import tomli as tomllib
def load_plugin(module_path: str) -> Optional[Plugin]: def load_plugin(module_path: Union[str, Path]) -> Optional[Plugin]:
"""加载单个插件,可以是本地插件或是通过 `pip` 安装的插件。
参数:
module_path: 插件名称 `path.to.your.plugin`
或插件路径 `pathlib.Path(path/to/your/plugin)`
""" """
:说明: module_path = (
path_to_module_name(module_path)
使用 ``PluginManager`` 加载单个插件,可以是本地插件或是通过 ``pip`` 安装的插件。 if isinstance(module_path, Path)
else module_path
:参数: )
* ``module_path: str``: 插件名称 ``path.to.your.plugin``
:返回:
- ``Optional[Plugin]``
"""
manager = PluginManager([module_path]) manager = PluginManager([module_path])
_managers.append(manager) _managers.append(manager)
return manager.load_plugin(module_path) return manager.load_plugin(module_path)
def load_plugins(*plugin_dir: str) -> Set[Plugin]: def load_plugins(*plugin_dir: str) -> Set[Plugin]:
""" """导入文件夹下多个插件,以 `_` 开头的插件不会被导入!
:说明:
导入目录下多个插件,以 ``_`` 开头的插件不会被导入! 参数:
plugin_dir: 文件夹路径
:参数:
- ``*plugin_dir: str``: 插件路径
:返回:
- ``Set[Plugin]``
""" """
manager = PluginManager(search_path=plugin_dir) manager = PluginManager(search_path=plugin_dir)
_managers.append(manager) _managers.append(manager)
@@ -52,19 +53,11 @@ def load_plugins(*plugin_dir: str) -> Set[Plugin]:
def load_all_plugins( def load_all_plugins(
module_path: Iterable[str], plugin_dir: Iterable[str] module_path: Iterable[str], plugin_dir: Iterable[str]
) -> Set[Plugin]: ) -> Set[Plugin]:
""" """导入指定列表中的插件以及指定目录下多个插件,以 `_` 开头的插件不会被导入!
:说明:
导入指定列表中的插件以及指定目录下多个插件,以 ``_`` 开头的插件不会被导入! 参数:
module_path: 指定插件集合
:参数: plugin_dir: 指定文件夹路径集合
- ``module_path: Iterable[str]``: 指定插件集合
- ``plugin_dir: Iterable[str]``: 指定插件路径集合
:返回:
- ``Set[Plugin]``
""" """
manager = PluginManager(module_path, plugin_dir) manager = PluginManager(module_path, plugin_dir)
_managers.append(manager) _managers.append(manager)
@@ -72,22 +65,29 @@ def load_all_plugins(
def load_from_json(file_path: str, encoding: str = "utf-8") -> Set[Plugin]: def load_from_json(file_path: str, encoding: str = "utf-8") -> Set[Plugin]:
"""导入指定 json 文件中的 `plugins` 以及 `plugin_dirs` 下多个插件。
以 `_` 开头的插件不会被导入!
参数:
file_path: 指定 json 文件路径
encoding: 指定 json 文件编码
用法:
```json title=plugins.json
{
"plugins": ["some_plugin"],
"plugin_dirs": ["some_dir"]
}
```
```python
nonebot.load_from_json("plugins.json")
```
""" """
:说明: with open(file_path, encoding=encoding) as f:
导入指定 json 文件中的 ``plugins`` 以及 ``plugin_dirs`` 下多个插件,以 ``_`` 开头的插件不会被导入!
:参数:
- ``file_path: str``: 指定 json 文件路径
- ``encoding: str``: 指定 json 文件编码
:返回:
- ``Set[Plugin]``
"""
with open(file_path, "r", encoding=encoding) as f:
data = json.load(f) data = json.load(f)
if not isinstance(data, dict):
raise TypeError("json file must contains a dict!")
plugins = data.get("plugins") plugins = data.get("plugins")
plugin_dirs = data.get("plugin_dirs") plugin_dirs = data.get("plugin_dirs")
assert isinstance(plugins, list), "plugins must be a list of plugin name" assert isinstance(plugins, list), "plugins must be a list of plugin name"
@@ -96,34 +96,33 @@ def load_from_json(file_path: str, encoding: str = "utf-8") -> Set[Plugin]:
def load_from_toml(file_path: str, encoding: str = "utf-8") -> Set[Plugin]: def load_from_toml(file_path: str, encoding: str = "utf-8") -> Set[Plugin]:
"""导入指定 toml 文件 `[tool.nonebot]` 中的
`plugins` 以及 `plugin_dirs` 下多个插件。
以 `_` 开头的插件不会被导入!
参数:
file_path: 指定 toml 文件路径
encoding: 指定 toml 文件编码
用法:
```toml title=pyproject.toml
[tool.nonebot]
plugins = ["some_plugin"]
plugin_dirs = ["some_dir"]
```
```python
nonebot.load_from_toml("pyproject.toml")
```
""" """
:说明: with open(file_path, encoding=encoding) as f:
data = tomllib.loads(f.read())
导入指定 toml 文件 ``[tool.nonebot]`` 中的 ``plugins`` 以及 ``plugin_dirs`` 下多个插件,
以 ``_`` 开头的插件不会被导入!
:参数:
- ``file_path: str``: 指定 toml 文件路径
- ``encoding: str``: 指定 toml 文件编码
:返回:
- ``Set[Plugin]``
"""
with open(file_path, "r", encoding=encoding) as f:
data = tomlkit.parse(f.read()) # type: ignore
nonebot_data = data.get("tool", {}).get("nonebot") nonebot_data = data.get("tool", {}).get("nonebot")
if not nonebot_data: if nonebot_data is None:
nonebot_data = data.get("nonebot", {}).get("plugins") raise ValueError("Cannot find '[tool.nonebot]' in given toml file!")
if nonebot_data: if not isinstance(nonebot_data, dict):
warnings.warn( raise TypeError("'[tool.nonebot]' must be a Table!")
"[nonebot.plugins] table are now deprecated. Use [tool.nonebot] instead.",
DeprecationWarning,
)
else:
raise ValueError("Cannot find '[tool.nonebot]' in given toml file!")
plugins = nonebot_data.get("plugins", []) plugins = nonebot_data.get("plugins", [])
plugin_dirs = nonebot_data.get("plugin_dirs", []) plugin_dirs = nonebot_data.get("plugin_dirs", [])
assert isinstance(plugins, list), "plugins must be a list of plugin name" assert isinstance(plugins, list), "plugins must be a list of plugin name"
@@ -131,37 +130,91 @@ def load_from_toml(file_path: str, encoding: str = "utf-8") -> Set[Plugin]:
return load_all_plugins(plugins, plugin_dirs) return load_all_plugins(plugins, plugin_dirs)
def load_builtin_plugins(name: str) -> Optional[Plugin]: def load_builtin_plugin(name: str) -> Optional[Plugin]:
""" """导入 NoneBot 内置插件。
:说明:
导入 NoneBot 内置插件 参数:
name: 插件名称
:返回:
- ``Plugin``
""" """
return load_plugin(f"nonebot.plugins.{name}") return load_plugin(f"nonebot.plugins.{name}")
def require(name: str) -> Export: def load_builtin_plugins(*plugins: str) -> Set[Plugin]:
"""导入多个 NoneBot 内置插件。
参数:
plugins: 插件名称列表
""" """
:说明: return load_all_plugins([f"nonebot.plugins.{p}" for p in plugins], [])
获取一个插件的导出内容
:参数: def _find_manager_by_name(name: str) -> Optional[PluginManager]:
for manager in reversed(_managers):
if name in manager.plugins or name in manager.searched_plugins:
return manager
* ``name: str``: 插件名,与 ``load_plugin`` 参数一致。如果为 ``load_plugins`` 导入的插件,则为文件(夹)名。
:返回: def require(name: str) -> ModuleType:
"""获取一个插件的导出内容。
- ``Export`` 如果为 `load_plugins` 文件夹导入的插件,则为文件(夹)名。
:异常: 参数:
- ``RuntimeError``: 插件无法加载 name: 插件名,即 {ref}`nonebot.plugin.plugin.Plugin.name`。
异常:
RuntimeError: 插件无法加载
""" """
plugin = get_plugin(name) or load_plugin(name) plugin = get_plugin(_module_name_to_plugin_name(name))
# if plugin not loaded
if not plugin:
# plugin already declared
if manager := _find_manager_by_name(name):
plugin = manager.load_plugin(name)
# plugin not declared, try to declare and load it
else:
# clear current plugin chain, ensure plugin loaded in a new context
_t = _current_plugin_chain.set(())
try:
plugin = load_plugin(name)
finally:
_current_plugin_chain.reset(_t)
if not plugin: if not plugin:
raise RuntimeError(f'Cannot load plugin "{name}"!') raise RuntimeError(f'Cannot load plugin "{name}"!')
return plugin.export return plugin.module
def inherit_supported_adapters(*names: str) -> Optional[Set[str]]:
"""获取已加载插件的适配器支持状态集合。
如果传入了多个插件名称,返回值会自动取交集。
参数:
names: 插件名称列表。
异常:
RuntimeError: 插件未加载
ValueError: 插件缺少元数据
"""
final_supported: Optional[Set[str]] = None
for name in names:
plugin = get_plugin(_module_name_to_plugin_name(name))
if plugin is None:
raise RuntimeError(f'Plugin "{name}" is not loaded!')
meta = plugin.metadata
if meta is None:
raise ValueError(f'Plugin "{name}" has no metadata!')
support = meta.supported_adapters
if support is None:
continue
final_supported = (
support if final_supported is None else (final_supported & support)
)
return final_supported and {
f"nonebot.adapters.{adapter_name[1:]}"
if adapter_name.startswith("~")
else adapter_name
for adapter_name in final_supported
}

View File

@@ -1,3 +1,12 @@
"""本模块实现插件加载流程。
参考: [import hooks](https://docs.python.org/3/reference/import.html#import-hooks), [PEP302](https://www.python.org/dev/peps/pep-0302/)
FrontMatter:
sidebar_position: 5
description: nonebot.plugin.manager 模块
"""
import sys import sys
import pkgutil import pkgutil
import importlib import importlib
@@ -6,109 +15,166 @@ from itertools import chain
from types import ModuleType from types import ModuleType
from importlib.abc import MetaPathFinder from importlib.abc import MetaPathFinder
from importlib.machinery import PathFinder, SourceFileLoader from importlib.machinery import PathFinder, SourceFileLoader
from typing import Set, Dict, List, Union, Iterable, Optional, Sequence from typing import Set, Dict, List, Iterable, Optional, Sequence
from nonebot.log import logger from nonebot.log import logger
from nonebot.utils import escape_tag from nonebot.utils import escape_tag, path_to_module_name
from .plugin import Plugin, _new_plugin
from . import _managers, _current_plugin from .plugin import Plugin, PluginMetadata
from . import (
_managers,
_new_plugin,
_revert_plugin,
_current_plugin_chain,
_module_name_to_plugin_name,
)
class PluginManager: class PluginManager:
"""插件管理器。
参数:
plugins: 独立插件模块名集合。
search_path: 插件搜索路径(文件夹)。
"""
def __init__( def __init__(
self, self,
plugins: Optional[Iterable[str]] = None, plugins: Optional[Iterable[str]] = None,
search_path: Optional[Iterable[str]] = None, search_path: Optional[Iterable[str]] = None,
): ):
# simple plugin not in search path # simple plugin not in search path
self.plugins: Set[str] = set(plugins or []) self.plugins: Set[str] = set(plugins or [])
self.search_path: Set[str] = set(search_path or []) self.search_path: Set[str] = set(search_path or [])
# cache plugins # cache plugins
self.searched_plugins: Dict[str, Path] = {} self._third_party_plugin_names: Dict[str, str] = {}
self.list_plugins() self._searched_plugin_names: Dict[str, Path] = {}
self.prepare_plugins()
def _path_to_module_name(self, path: Path) -> str: def __repr__(self) -> str:
rel_path = path.resolve().relative_to(Path(".").resolve()) return f"PluginManager(plugins={self.plugins}, search_path={self.search_path})"
if rel_path.stem == "__init__":
return ".".join(rel_path.parts[:-1])
else:
return ".".join(rel_path.parts[:-1] + (rel_path.stem,))
def _previous_plugins(self) -> List[str]: @property
def third_party_plugins(self) -> Set[str]:
"""返回所有独立插件名称。"""
return set(self._third_party_plugin_names.keys())
@property
def searched_plugins(self) -> Set[str]:
"""返回已搜索到的插件名称。"""
return set(self._searched_plugin_names.keys())
@property
def available_plugins(self) -> Set[str]:
"""返回当前插件管理器中可用的插件名称。"""
return self.third_party_plugins | self.searched_plugins
def _previous_plugins(self) -> Set[str]:
_pre_managers: List[PluginManager] _pre_managers: List[PluginManager]
if self in _managers: if self in _managers:
_pre_managers = _managers[: _managers.index(self)] _pre_managers = _managers[: _managers.index(self)]
else: else:
_pre_managers = _managers[:] _pre_managers = _managers[:]
return [ return {
*chain.from_iterable( *chain.from_iterable(manager.available_plugins for manager in _pre_managers)
[*manager.plugins, *manager.searched_plugins.keys()] }
for manager in _pre_managers
)
]
def list_plugins(self) -> Set[str]: def prepare_plugins(self) -> Set[str]:
"""搜索插件并缓存插件名称。"""
# get all previous ready to load plugins # get all previous ready to load plugins
previous_plugins = self._previous_plugins() previous_plugins = self._previous_plugins()
searched_plugins: Dict[str, Path] = {} searched_plugins: Dict[str, Path] = {}
third_party_plugins: Set[str] = set() third_party_plugins: Dict[str, str] = {}
# check third party plugins
for plugin in self.plugins: for plugin in self.plugins:
name = plugin.rsplit(".", 1)[-1] if "." in plugin else plugin name = _module_name_to_plugin_name(plugin)
if name in third_party_plugins or name in previous_plugins: if name in third_party_plugins or name in previous_plugins:
raise RuntimeError( raise RuntimeError(
f"Plugin already exists: {name}! Check your plugin name" f"Plugin already exists: {name}! Check your plugin name"
) )
third_party_plugins.add(plugin) third_party_plugins[name] = plugin
self._third_party_plugin_names = third_party_plugins
# check plugins in search path
for module_info in pkgutil.iter_modules(self.search_path): for module_info in pkgutil.iter_modules(self.search_path):
# ignore if startswith "_"
if module_info.name.startswith("_"): if module_info.name.startswith("_"):
continue continue
if ( if (
module_info.name in searched_plugins.keys() module_info.name in searched_plugins
or module_info.name in previous_plugins or module_info.name in previous_plugins
or module_info.name in third_party_plugins or module_info.name in third_party_plugins
): ):
raise RuntimeError( raise RuntimeError(
f"Plugin already exists: {module_info.name}! Check your plugin name" f"Plugin already exists: {module_info.name}! Check your plugin name"
) )
module_spec = module_info.module_finder.find_spec(module_info.name, None)
if not module_spec: if not (
module_spec := module_info.module_finder.find_spec(
module_info.name, None
)
):
continue continue
module_path = module_spec.origin if not (module_path := module_spec.origin):
if not module_path:
continue continue
searched_plugins[module_info.name] = Path(module_path).resolve() searched_plugins[module_info.name] = Path(module_path).resolve()
self.searched_plugins = searched_plugins self._searched_plugin_names = searched_plugins
return third_party_plugins | set(self.searched_plugins.keys()) return self.available_plugins
def load_plugin(self, name: str) -> Optional[Plugin]:
"""加载指定插件。
对于独立插件,可以使用完整插件模块名或者插件名称。
参数:
name: 插件名称。
"""
def load_plugin(self, name) -> Optional[Plugin]:
try: try:
if name in self.plugins: if name in self.plugins:
module = importlib.import_module(name) module = importlib.import_module(name)
elif name not in self.searched_plugins: elif name in self._third_party_plugin_names:
raise RuntimeError(f"Plugin not found: {name}! Check your plugin name") module = importlib.import_module(self._third_party_plugin_names[name])
else: elif name in self._searched_plugin_names:
module = importlib.import_module( module = importlib.import_module(
self._path_to_module_name(self.searched_plugins[name]) path_to_module_name(self._searched_plugin_names[name])
) )
else:
raise RuntimeError(f"Plugin not found: {name}! Check your plugin name")
if (
plugin := getattr(module, "__plugin__", None)
) is None or not isinstance(plugin, Plugin):
raise RuntimeError(
f"Module {module.__name__} is not loaded as a plugin! "
"Make sure not to import it before loading."
)
logger.opt(colors=True).success( logger.opt(colors=True).success(
f'Succeeded to import "<y>{escape_tag(name)}</y>"' f'Succeeded to load plugin "<y>{escape_tag(plugin.name)}</y>"'
+ (
f' from "<m>{escape_tag(plugin.module_name)}</m>"'
if plugin.module_name != plugin.name
else ""
)
) )
return getattr(module, "__plugin__", None) return plugin
except Exception as e: except Exception as e:
logger.opt(colors=True, exception=e).error( logger.opt(colors=True, exception=e).error(
f'<r><bg #f8bbd0>Failed to import "{escape_tag(name)}"</bg #f8bbd0></r>' f'<r><bg #f8bbd0>Failed to import "{escape_tag(name)}"</bg #f8bbd0></r>'
) )
def load_all_plugins(self) -> Set[Plugin]: def load_all_plugins(self) -> Set[Plugin]:
"""加载所有可用插件。"""
return set( return set(
filter(None, (self.load_plugin(name) for name in self.list_plugins())) filter(None, (self.load_plugin(name) for name in self.available_plugins))
) )
@@ -116,11 +182,10 @@ class PluginFinder(MetaPathFinder):
def find_spec( def find_spec(
self, self,
fullname: str, fullname: str,
path: Optional[Sequence[Union[bytes, str]]], path: Optional[Sequence[str]],
target: Optional[ModuleType] = None, target: Optional[ModuleType] = None,
): ):
if _managers: if _managers:
index = -1
module_spec = PathFinder.find_spec(fullname, path, target) module_spec = PathFinder.find_spec(fullname, path, target)
if not module_spec: if not module_spec:
return return
@@ -129,17 +194,14 @@ class PluginFinder(MetaPathFinder):
return return
module_path = Path(module_origin).resolve() module_path = Path(module_origin).resolve()
while -index <= len(_managers): for manager in reversed(_managers):
manager = _managers[index] # use path instead of name in case of submodule name conflict
if ( if (
fullname in manager.plugins fullname in manager.plugins
or module_path in manager.searched_plugins.values() or module_path in manager._searched_plugin_names.values()
): ):
module_spec.loader = PluginLoader(manager, fullname, module_origin) module_spec.loader = PluginLoader(manager, fullname, module_origin)
return module_spec return module_spec
index -= 1
return return
@@ -160,27 +222,35 @@ class PluginLoader(SourceFileLoader):
if self.loaded: if self.loaded:
return return
# create plugin before executing
plugin = _new_plugin(self.name, module, self.manager) plugin = _new_plugin(self.name, module, self.manager)
parent_plugin = _current_plugin.get()
if parent_plugin and _managers.index(parent_plugin.manager) < _managers.index(
self.manager
):
plugin.parent_plugin = parent_plugin
parent_plugin.sub_plugins.add(plugin)
_plugin_token = _current_plugin.set(plugin)
setattr(module, "__plugin__", plugin) setattr(module, "__plugin__", plugin)
# try: # detect parent plugin before entering current plugin context
# super().exec_module(module) parent_plugins = _current_plugin_chain.get()
# except Exception as e: for pre_plugin in reversed(parent_plugins):
# raise ImportError( # ensure parent plugin is declared before current plugin
# f"Error when executing module {module_name} from {module.__file__}." if _managers.index(pre_plugin.manager) < _managers.index(self.manager):
# ) from e plugin.parent_plugin = pre_plugin
super().exec_module(module) pre_plugin.sub_plugins.add(plugin)
break
# enter plugin context
_plugin_token = _current_plugin_chain.set(parent_plugins + (plugin,))
try:
super().exec_module(module)
except Exception:
_revert_plugin(plugin)
raise
finally:
# leave plugin context
_current_plugin_chain.reset(_plugin_token)
# get plugin metadata
metadata: Optional[PluginMetadata] = getattr(module, "__plugin_meta__", None)
plugin.metadata = metadata
_current_plugin.reset(_plugin_token)
return return

File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More