Compare commits

...

953 Commits

Author SHA1 Message Date
noneflow[bot]
1e8c2cfc9f 🔖 Release 2.1.0 2023-09-10 03:45:49 +00:00
Ju4tCode
5ce0238ace 🔖 bump version 2.1.0 (#2354) 2023-09-10 11:34:15 +08:00
noneflow[bot]
4e6b52b85c 📝 Update changelog 2023-09-09 17:22:00 +00:00
幼稚园园长
05fe7bb715 ✏️ Plugin: 删除插件 nonebot-plugin-heisi (#2353) 2023-09-10 01:20:48 +08:00
noneflow[bot]
c555e2fac6 📝 Update changelog 2023-09-09 13:00:23 +00:00
Tarrailt
fd126ae154 Feature: 为 Matcher.HANDLER_PARAM_TYPES 补增类型 (#2352)
Co-authored-by: Ju4tCode <42488585+yanyongyu@users.noreply.github.com>
2023-09-09 20:58:50 +08:00
noneflow[bot]
6c7b6a9575 📝 Update changelog 2023-09-09 05:47:23 +00:00
Ju4tCode
c4716e3e17 Feature: 为事件响应器添加更多源码信息 (#2351) 2023-09-09 13:46:09 +08:00
noneflow[bot]
3601a33f20 📝 Update changelog 2023-09-09 03:54:56 +00:00
Tarrailt
451023518b 📝 Docs: 更新最佳实践部分的 Alconna 章节 (#2349)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2023-09-09 11:53:06 +08:00
noneflow[bot]
2bd377a221 📝 Update changelog 2023-09-07 04:02:19 +00:00
Noctulus
66384adad4 🍻 publish plugin 文心一言 (#2341) 2023-09-07 04:00:51 +00:00
pre-commit-ci[bot]
ec1f7ba5bc ⬆️ auto update by pre-commit hooks (#2347)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2023-09-05 18:33:56 +08:00
dependabot[bot]
e7fc5b7b7e ⬆️ Bump actions/checkout from 3 to 4 (#2345)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-05 12:00:44 +08:00
noneflow[bot]
11477ea9d7 📝 Update changelog 2023-09-05 02:50:44 +00:00
StarHeart
6adf40f45d ⬆️ bump node version to 18 (#2344) 2023-09-05 10:49:09 +08:00
noneflow[bot]
1bdf169980 📝 Update changelog 2023-09-04 16:19:22 +00:00
Akirami
81cb356503 📝 Feature: 补充依赖注入部分情况下类型错误时的日志提示 (#2343) 2023-09-05 00:17:55 +08:00
noneflow[bot]
805778794c 📝 Update changelog 2023-09-04 03:58:08 +00:00
Rikka-desu
28cd8dd08a 🍻 publish plugin nonebot_plugin_group_whitelist (#2319) 2023-09-04 03:56:46 +00:00
noneflow[bot]
139b39984e 📝 Update changelog 2023-09-03 10:59:42 +00:00
GuGuMur
f9b5fece80 🍻 publish plugin 森空岛明日方舟签到器 (#2339) 2023-09-03 10:58:10 +00:00
noneflow[bot]
8076c6bc0a 📝 Update changelog 2023-09-03 05:40:53 +00:00
Ju4tCode
44b89d13f8 🚨 fix ruff error (#2338) 2023-09-03 13:39:26 +08:00
noneflow[bot]
fbc4225110 📝 Update changelog 2023-09-03 02:48:40 +00:00
Lfhsheng
f07f35ccc1 🍻 publish plugin 女装 ! (#2335) 2023-09-03 02:47:25 +00:00
noneflow[bot]
111dfbf164 📝 Update changelog 2023-09-02 11:38:52 +00:00
fR0Z863xF
c713c7723b 🍻 publish plugin helper_plus (#2322) 2023-09-02 11:37:35 +00:00
noneflow[bot]
4fa2af41b0 📝 Update changelog 2023-09-02 03:47:51 +00:00
xiaoWangSec
39c09d22d1 🍻 publish plugin nonebot-plugin-souti (#2333) 2023-09-02 03:46:33 +00:00
noneflow[bot]
4819b21f52 📝 Update changelog 2023-09-02 02:45:09 +00:00
uy/sun
6ef6721527 👷 插件测试使用最新的稳定版 Python 版本 (#2336) 2023-09-02 10:43:48 +08:00
noneflow[bot]
14cb447874 📝 Update changelog 2023-08-31 09:34:31 +00:00
ZM25XC
1b2b89074d ✏️ Plugin: 删除不再维护的插件 (#2330)
Co-authored-by: ZM25XC <xingling25@qq.com>
2023-08-31 17:32:52 +08:00
noneflow[bot]
75c5678782 📝 Update changelog 2023-08-30 15:50:29 +00:00
RF-Tar-Railt
45ec5cdfb4 🍻 publish plugin Alconna 帮助工具 (#2325) 2023-08-30 15:49:08 +00:00
noneflow[bot]
f6dd98825b 📝 Update changelog 2023-08-29 10:46:36 +00:00
Ju4tCode
f59271bd47 Feature: 支持子依赖定义 Pydantic 类型校验 (#2310) 2023-08-29 18:45:12 +08:00
noneflow[bot]
79f833b946 📝 Update changelog 2023-08-29 07:04:44 +00:00
惜月
9ad562bbfd 📝 Docs: 添加 Discord 适配器描述,补充 Villa 适配器协议链接 (#2316)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2023-08-29 15:03:21 +08:00
noneflow[bot]
267b49247d 📝 Update changelog 2023-08-29 05:53:43 +00:00
Ju4tCode
dbda4150fb Update website/static/adapters.json 2023-08-29 05:52:08 +00:00
CMHopeSunshine
a4e17f0c49 🍻 publish adapter Discord (#2314) 2023-08-29 05:52:08 +00:00
noneflow[bot]
8d8d1169d1 📝 Update changelog 2023-08-29 02:26:38 +00:00
Cvandia
7bc9e61985 🍻 publish plugin 消息伪造 (#2311) 2023-08-29 02:24:53 +00:00
Tarrailt
35cc6011b5 📝 Docs: 添加 Red 适配器描述 (#2313)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2023-08-29 10:23:17 +08:00
noneflow[bot]
086af8fd22 📝 Update changelog 2023-08-28 03:49:31 +00:00
tomorinao-www
a60d1520e6 🍻 publish plugin 二维码 (#2301) 2023-08-28 03:48:12 +00:00
noneflow[bot]
30c22ba25a 📝 Update changelog 2023-08-28 02:30:21 +00:00
nikissXI
41fbaec42c ✏️ Plugin: 删除插件 poe ai (#2308) 2023-08-28 10:29:04 +08:00
noneflow[bot]
562ec79e3b 📝 Update changelog 2023-08-27 15:11:41 +00:00
XTxiaoting14332
f620bd8eb2 🍻 publish plugin httpcat-状态猫😺 (#2305) 2023-08-27 15:10:11 +00:00
noneflow[bot]
13e40458d7 📝 Update changelog 2023-08-26 13:51:15 +00:00
Tarrailt
dc4ac6d8d7 📝 Docs: 更新最佳实践部分的 Alconna 章节 (#2303)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2023-08-26 21:49:28 +08:00
noneflow[bot]
41498bdf21 📝 Update changelog 2023-08-26 03:14:28 +00:00
Agnes4m
b8eae2eb82 🍻 publish plugin 雪豹闭嘴 (#2299) 2023-08-26 03:12:53 +00:00
noneflow[bot]
039c2b5509 📝 Update changelog 2023-08-26 03:05:06 +00:00
Ju4tCode
2e635370bb Feature: 细化 driver 职责类型 (#2296) 2023-08-26 11:03:24 +08:00
noneflow[bot]
807a86371d 📝 Update changelog 2023-08-24 02:36:22 +00:00
Ailitonia
c66953779c 🍻 publish plugin Nonebot Requests (#2293) 2023-08-24 02:34:50 +00:00
noneflow[bot]
117ef18f1c 📝 Update changelog 2023-08-23 06:36:32 +00:00
Well404
520dd03d77 ✏️ Plugin: 移除不再维护的插件,修改插件信息 (#2292) 2023-08-23 14:35:13 +08:00
noneflow[bot]
63f3ca2f6f 📝 Update changelog 2023-08-23 06:15:39 +00:00
eya46
2e8230e9f4 🐛 Fix: 设置 file request 默认 filename (#2284)
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-08-23 14:14:07 +08:00
noneflow[bot]
4bfea99e54 📝 Update changelog 2023-08-23 02:55:04 +00:00
bingqiu456
f58eba7975 🍻 publish plugin 双向聊天插件 (#2264) 2023-08-23 02:53:48 +00:00
noneflow[bot]
53d1de4aec 📝 Update changelog 2023-08-19 12:46:43 +00:00
tomorinao-www
00f18c1bd8 🍻 publish plugin 识别动漫gal角色 (#2287) 2023-08-19 12:45:05 +00:00
noneflow[bot]
ba4fbb2ec3 📝 Update changelog 2023-08-19 05:43:52 +00:00
LuckySJTU
b3722bd637 🍻 publish plugin arxiv订阅 (#2281) 2023-08-19 05:42:43 +00:00
noneflow[bot]
012bd6d4fb 📝 Update changelog 2023-08-18 11:08:58 +00:00
Ju4tCode
9c4ca28d61 🚨 make linter happy (#2286) 2023-08-18 19:07:35 +08:00
noneflow[bot]
53bcae04ff 📝 Update changelog 2023-08-18 07:01:12 +00:00
This-is-XiaoDeng
754c54e268 🍻 publish plugin SUDO (#2276) 2023-08-18 06:59:44 +00:00
noneflow[bot]
f97fbc814e 📝 Update changelog 2023-08-18 05:50:08 +00:00
mobyw
b8856a0577 🍻 publish plugin 消息推送插件 (#2272) 2023-08-18 05:48:49 +00:00
noneflow[bot]
1c0e88907b 📝 Update changelog 2023-08-18 03:47:58 +00:00
Komorebi
31b6df5b39 📝 Docs: 修复 Alconna 中 CommandResult 描述错误 (#2282) 2023-08-18 11:46:39 +08:00
noneflow[bot]
bca9e4fd08 📝 Update changelog 2023-08-18 03:12:30 +00:00
Akirami
026ceb5028 📝 Docs: 修复子依赖部分代码行号错误 (#2279) 2023-08-18 11:11:23 +08:00
noneflow[bot]
47d5a647b7 📝 Update changelog 2023-08-18 03:02:49 +00:00
Akirami
37d7230949 📝 Docs: 补充 get_last_receive 示例 (#2278) 2023-08-18 11:01:23 +08:00
noneflow[bot]
be458b1d5e 📝 Update changelog 2023-08-17 05:34:53 +00:00
fu050409
f375a4a723 🍻 publish plugin 周易蓍草占卜 (#2267) 2023-08-17 05:33:24 +00:00
noneflow[bot]
3edce9a630 📝 Update changelog 2023-08-16 16:47:20 +00:00
Akirami
c525bda1e0 📝 Docs: 修复文档中错误的标点 (#2275) 2023-08-17 00:45:53 +08:00
noneflow[bot]
417f586e0d 📝 Update changelog 2023-08-16 02:19:02 +00:00
ACnoway
80d7e68835 🍻 publish bot OCNbot (#2260) 2023-08-16 02:17:44 +00:00
noneflow[bot]
a284e6df5c 📝 Update changelog 2023-08-14 13:11:21 +00:00
Akirami
7176a69f81 📝 Docs: 修复配置文档中 Nickname 属性的描述错误 (#2271) 2023-08-14 21:09:56 +08:00
noneflow[bot]
e3a1c02e8a 📝 Update changelog 2023-08-14 02:48:09 +00:00
fu050409
5e789ae4e0 🍻 publish plugin 欧若可骰娘 (#2265) 2023-08-14 02:46:51 +00:00
noneflow[bot]
bb684e20cb 📝 Update changelog 2023-08-13 03:20:03 +00:00
惜月
e11293e46b 📝 Docs: 适配器编写教程 (#2079)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2023-08-13 11:18:41 +08:00
noneflow[bot]
e0d74a1657 📝 Update changelog 2023-08-12 12:43:00 +00:00
A-kirami
fdd36565b1 🍻 publish bot 星见Kirami (#2262) 2023-08-12 12:41:43 +00:00
noneflow[bot]
28c53fe0d7 📝 Update changelog 2023-08-12 03:13:01 +00:00
Alpaca4610
26539bf2b1 🍻 publish plugin 科大讯飞星火大模型聊天 (#2257) 2023-08-12 03:11:53 +00:00
dependabot[bot]
347889c822 ⬆️ Bump actions/setup-node in /.github/actions/setup-node (#2259)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-08-11 20:32:11 +08:00
noneflow[bot]
91849b762c 📝 Update changelog 2023-08-11 12:23:01 +00:00
Ju4tCode
5d1319ddb9 🔧 add dependabot actions check (#2256) 2023-08-11 20:21:31 +08:00
noneflow[bot]
d98228926e 📝 Update changelog 2023-08-09 14:44:06 +00:00
Akirami
493997d998 📝 Docs: 更新贡献指南 (#2255) 2023-08-09 22:42:38 +08:00
noneflow[bot]
3098b7c153 📝 Update changelog 2023-08-09 07:59:41 +00:00
fuyang0811
2b0a050226 🍻 publish plugin 剑网三查询和推送 (#2253) 2023-08-09 07:58:33 +00:00
noneflow[bot]
1f3abc2bb9 📝 Update changelog 2023-08-08 13:24:50 +00:00
XTxiaoting14332
dd5541e658 🍻 publish plugin Muteme(我禁我自己) (#2251) 2023-08-08 13:23:28 +00:00
noneflow[bot]
a76bf27f60 📝 Update changelog 2023-08-08 02:21:17 +00:00
CN171-1
d70ce366cc 🍻 publish plugin MC版本更新检测 (#2246) 2023-08-08 02:19:18 +00:00
noneflow[bot]
f94b802c9b 📝 Update changelog 2023-08-07 12:47:46 +00:00
itsevin
17d7bd4e31 🍻 publish bot 不正经的妹妹 (#2248) 2023-08-07 12:46:37 +00:00
noneflow[bot]
76a40b60ff 📝 Update changelog 2023-08-07 12:40:29 +00:00
SuperGuGuGu
469efedab2 🍻 publish plugin KanonBot (#2243) 2023-08-07 12:39:09 +00:00
noneflow[bot]
383699a8b4 📝 Update changelog 2023-08-05 06:03:22 +00:00
eya46
1b9a07b923 🐛 Docs: 修复文档 Last updated author 错误 (#2241) 2023-08-05 14:01:53 +08:00
noneflow[bot]
15b76c266c 📝 Update changelog 2023-08-04 10:12:23 +00:00
zhaomaoniu
dfdecaddb1 🍻 publish adapter RedProtocol (#2238) 2023-08-04 10:11:11 +00:00
noneflow[bot]
5de9de903d 📝 Update changelog 2023-08-04 09:34:00 +00:00
Tarrailt
327f3fa441 📝 Docs: 更新最佳实践部分的 Alconna 章节 (#2237)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2023-08-04 17:32:43 +08:00
noneflow[bot]
08fde7580c 📝 Update changelog 2023-08-04 04:08:03 +00:00
Sydrr0
4ca91ecc7e 🍻 publish plugin CSGO饰品查询机器人 (#2224) 2023-08-04 04:06:38 +00:00
noneflow[bot]
885db90bc0 📝 Update changelog 2023-08-04 02:55:40 +00:00
nikissXI
c43d631eb5 🍻 publish plugin talk with poe ai (#2229) 2023-08-04 02:54:21 +00:00
noneflow[bot]
cfda433d14 📝 Update changelog 2023-08-03 02:34:48 +00:00
EmiyaGm
ea4a27bf89 🍻 publish plugin 命运方舟流浪商人卡牌刷新提示 (#2233) 2023-08-03 02:33:28 +00:00
noneflow[bot]
23944833f2 📝 Update changelog 2023-08-02 06:50:39 +00:00
Yan-Zero
4a40782be0 🍻 publish plugin Savepic (#2231) 2023-08-02 06:49:20 +00:00
noneflow[bot]
babafcaa87 📝 Update changelog 2023-08-01 13:45:03 +00:00
canxin121
9b164a6f5a 🍻 publish plugin 跨平台账户绑定 (#2226) 2023-08-01 13:43:42 +00:00
pre-commit-ci[bot]
4a07981972 ⬆️ auto update by pre-commit hooks (#2228)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2023-08-01 20:53:53 +08:00
noneflow[bot]
6bb2c46f8a 📝 Update changelog 2023-07-30 02:44:17 +00:00
qwqZYLqwq
2054655912 🍻 publish plugin Among US中的TOH模组职业介绍 (#2220) 2023-07-30 02:43:07 +00:00
noneflow[bot]
062af45367 📝 Update changelog 2023-07-27 11:25:39 +00:00
lgc2333
83c3ed5966 🍻 publish plugin NoneMeme (#2218) 2023-07-27 11:24:31 +00:00
noneflow[bot]
a2f2b818a7 📝 Update changelog 2023-07-25 06:53:29 +00:00
A-kirami
e7941efd9a 🍻 publish plugin The World (#2215) 2023-07-25 06:52:10 +00:00
noneflow[bot]
aa6faba9ae 📝 Update changelog 2023-07-25 02:34:07 +00:00
ZM25XC
8ca72f3c64 🍻 publish plugin Bot上下线邮件通知 (#2213) 2023-07-25 02:32:47 +00:00
noneflow[bot]
45e10e7139 📝 Update changelog 2023-07-24 15:05:00 +00:00
Cypas
73d1b19669 🍻 publish plugin bot断连通知 (#2211) 2023-07-24 15:03:43 +00:00
noneflow[bot]
ad4cf86a96 📝 Update changelog 2023-07-24 03:35:45 +00:00
Ju4tCode
48b3e3aaf3 add git attributes (#2210) 2023-07-24 11:34:34 +08:00
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
550 changed files with 68917 additions and 20098 deletions

View File

@@ -2,7 +2,7 @@
"name": "Default Linux Universal",
"image": "mcr.microsoft.com/devcontainers/universal:2-linux",
"features": {
"ghcr.io/devcontainers-contrib/features/poetry:1": {}
"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": {
@@ -10,9 +10,11 @@
"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
}
},
@@ -44,6 +46,7 @@
"ms-python.vscode-pylance",
"ms-python.isort",
"ms-python.black-formatter",
"charliermarsh.ruff",
"EditorConfig.EditorConfig",
"esbenp.prettier-vscode",
"bradlc.vscode-tailwindcss"

1
.gitattributes vendored Normal file
View File

@@ -0,0 +1 @@
website/versioned_*/** linguist-documentation

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
contact_links:
- name: Question
- name: NoneBot 论坛
url: https://discussions.nonebot.dev/
about: Ask questions about nonebot
- name: Plugin Publish
url: https://v2.nonebot.dev/store
about: Publish your plugin to nonebot homepage and nb-cli
- name: Adapter Publish
url: https://v2.nonebot.dev/store
about: Publish your adapter to nonebot homepage and nb-cli
- name: Bot Publish
url: https://v2.nonebot.dev/store
about: Publish your bot to nonebot homepage and nb-cli
about: 前往 NoneBot 论坛提问

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

@@ -5,7 +5,10 @@ runs:
using: "composite"
steps:
- run: |
poetry run nb-autodoc nonebot
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

View File

@@ -4,9 +4,9 @@ description: Setup Node
runs:
using: "composite"
steps:
- uses: actions/setup-node@v2
- uses: actions/setup-node@v3
with:
node-version: "16"
node-version: "18"
- id: yarn-cache-dir-path
run: echo "dir=$(yarn cache dir)" >> $GITHUB_OUTPUT

View File

@@ -4,3 +4,18 @@ updates:
directory: "/"
schedule:
interval: daily
- package-ecosystem: github-actions
directory: "/.github/actions/build-api-doc"
schedule:
interval: daily
- package-ecosystem: github-actions
directory: "/.github/actions/setup-node"
schedule:
interval: daily
- package-ecosystem: github-actions
directory: "/.github/actions/setup-python"
schedule:
interval: daily

View File

@@ -5,6 +5,10 @@ on:
branches:
- master
pull_request:
paths:
- "nonebot/**"
- "packages/**"
- "tests/**"
jobs:
test:
@@ -23,7 +27,7 @@ jobs:
PYTHON_VERSION: ${{ matrix.python-version }}
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Setup Python environment
uses: ./.github/actions/setup-python

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.x"
- 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@v4
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,29 +0,0 @@
name: NoneBot2 Publish Bot
on:
issues:
types: [opened, reopened, edited]
pull_request_target:
types: [closed]
jobs:
publish_bot:
runs-on: ubuntu-latest
name: nonebot2 publish bot
steps:
- name: Checkout code
uses: actions/checkout@v3
with:
token: ${{ secrets.GH_TOKEN }}
- name: NoneBot2 Publish Bot
uses: docker://ghcr.io/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@v4
- 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

View File

@@ -18,9 +18,16 @@ jobs:
group: pull-request-changelog
cancel-in-progress: true
steps:
- uses: actions/checkout@v3
- name: Generate token
id: generate-token
uses: tibdex/github-app-token@v1
with:
token: ${{ secrets.GH_TOKEN }}
app_id: ${{ secrets.APP_ID }}
private_key: ${{ secrets.APP_KEY }}
- uses: actions/checkout@v4
with:
token: ${{ steps.generate-token.outputs.token }}
- name: Setup Node Environment
uses: ./.github/actions/setup-node
@@ -43,8 +50,8 @@ jobs:
- name: Commit and Push
run: |
yarn prettier
git config user.name github-actions[bot]
git config user.email github-actions[bot]@users.noreply.github.com
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
@@ -53,7 +60,7 @@ jobs:
if: startsWith(github.ref, 'refs/tags/')
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Setup Python Environment
uses: ./.github/actions/setup-python

View File

@@ -6,12 +6,17 @@ on:
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Generate token
id: generate-token
uses: tibdex/github-app-token@v1
with:
ref: master
token: ${{ secrets.GH_TOKEN }}
app_id: ${{ secrets.APP_ID }}
private_key: ${{ secrets.APP_KEY }}
- uses: actions/checkout@v4
with:
token: ${{ steps.generate-token.outputs.token }}
- name: Setup Python Environment
uses: ./.github/actions/setup-python
@@ -39,8 +44,8 @@ jobs:
- name: Push Tag
run: |
git config user.name github-actions[bot]
git config user.email github-actions[bot]@users.noreply.github.com
git config user.name noneflow[bot]
git config user.email 129742071+noneflow[bot]@users.noreply.github.com
git add .
git commit -m ":bookmark: Release $(poetry version -s)"
git tag ${{ env.TAG_NAME }}

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@v4
- name: Run Ruff Lint
uses: chartboost/ruff-action@v1

View File

@@ -13,7 +13,9 @@ jobs:
cancel-in-progress: true
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup Python Environment
uses: ./.github/actions/setup-python
@@ -31,7 +33,7 @@ jobs:
run: echo "BRANCH_NAME=$(echo ${GITHUB_REF#refs/heads/})" >> $GITHUB_ENV
- name: Deploy to Netlify
uses: nwtgck/actions-netlify@v1
uses: nwtgck/actions-netlify@v2
with:
publish-dir: "./website/build"
production-deploy: true

View File

@@ -11,9 +11,10 @@ jobs:
cancel-in-progress: true
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha }}
fetch-depth: 0
- name: Setup Python Environment
uses: ./.github/actions/setup-python
@@ -32,7 +33,7 @@ jobs:
echo "DEPLOY_NAME=deploy-preview-${{ github.event.number }}" >> $GITHUB_ENV
- name: Deploy to Netlify
uses: nwtgck/actions-netlify@v1
uses: nwtgck/actions-netlify@v2
with:
publish-dir: "./website/build"
production-deploy: false

View File

@@ -6,27 +6,34 @@ ci:
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.287
hooks:
- id: ruff
args: [--fix, --exit-non-zero-on-fix]
stages: [commit]
- repo: https://github.com/pycqa/isort
rev: 5.10.1
rev: 5.12.0
hooks:
- id: isort
stages: [commit]
- repo: https://github.com/psf/black
rev: 22.10.0
rev: 23.7.0
hooks:
- id: black
stages: [commit]
- repo: https://github.com/pre-commit/mirrors-prettier
rev: v3.0.0-alpha.4
rev: v3.0.3
hooks:
- id: prettier
types_or: [javascript, jsx, ts, tsx, markdown, yaml, json]
stages: [commit]
- repo: https://github.com/nonebot/nonemoji
rev: v0.1.3
rev: v0.1.4
hooks:
- id: nonemoji
stages: [prepare-commit-msg]

View File

@@ -1,3 +1,3 @@
# 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>

View File

@@ -10,12 +10,10 @@
### 报告问题、故障与漏洞
NoneBot2 仍然是一个不够稳定的开发中项目,如果你在使用过程中发现问题并确信是由 NoneBot2 引起的,欢迎提交 Issue。
如果你在使用过程中发现问题并确信是由 NoneBot2 引起的,欢迎提交 Issue。
### 建议功能
NoneBot2 还未进入正式版,欢迎在 Issue 中提议要加入哪些新功能。
为了让开发者更好地理解你的意图,请认真描述你所需要的特性,可能的话可以提出你认为可行的解决方案。
## Pull Request
@@ -66,16 +64,17 @@ yarn start
NoneBot2 文档并没有具体的行文风格规范,但我们建议你尽量写得简单易懂。
以下是比较重要的排版规范。目前 NoneBot2 文档中仍有部分文档不完全遵守此规范,如果在阅读时发现欢迎提交 PR。
以下是比较重要的编写与排版规范。目前 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>
以上由[社区创始人 richardchien 的中文排版规范](https://stdrc.cc/style-guides/chinese)补充修改得到
如果你需要编辑器提示 Markdown 规范,可以安装 VSCode 上的 markdownlint 插件
如果你需要编辑器检查 Markdown 规范,可以 VSCode 中安装 markdownlint 扩展
### 参与开发
@@ -83,7 +82,7 @@ NoneBot2 的代码风格遵循 [PEP 8](https://www.python.org/dev/peps/pep-0008/
## 为社区做贡献
你可以在 NoneBot 商店上架自己的适配器、插件、机器人,具体步骤可参考 [发布插件](https://v2.nonebot.dev/docs/advanced/publish-plugin) 一节。
你可以在 NoneBot 商店上架自己的适配器、插件、机器人,具体步骤可参考 [发布插件](https://nonebot.dev/docs/developer/plugin-publishing) 一节。
我们仅对插件的兼容性进行简单测试,并会在下一个版本发布前对与该版本不兼容的插件作出处理。

View File

@@ -1,6 +1,6 @@
<!-- markdownlint-disable MD033 MD041 -->
<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>
<div align="center">
@@ -19,9 +19,19 @@ _✨ 跨平台 Python 异步机器人框架 ✨_
<img src="https://img.shields.io/github/license/nonebot/nonebot2" alt="license">
</a>
<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>
<img src="https://img.shields.io/badge/python-3.8+-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">
<img src="https://codecov.io/gh/nonebot/nonebot2/branch/master/graph/badge.svg?token=2P0G0VS7N4" alt="codecov"/>
</a>
@@ -31,6 +41,12 @@ _✨ 跨平台 Python 异步机器人框架 ✨_
<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>
<br />
<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="onebot">
@@ -49,9 +65,9 @@ _✨ 跨平台 Python 异步机器人框架 ✨_
</a>
<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/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMTIuODIgMTMwLjg5Ij48ZyBkYXRhLW5hbWU9IuWbvuWxgiAyIj48ZyBkYXRhLW5hbWU9IuWbvuWxgiAxIj48cGF0aCBkPSJNNTUuNjMgMTMwLjhjLTcgMC0xMy45LjA4LTIwLjg2IDAtMTkuMTUtLjI1LTMxLjcxLTExLjQtMzQuMjItMzAuMy00LjA3LTMwLjY2IDE0LjkzLTU5LjIgNDQuODMtNjYuNjQgMi0uNTEgNS4yMS0uMzEgNS4yMS0xLjYzIDAtMi4xMy4xNC0yLjEzLjE0LTUuNTcgMC0uODktMS4zLTEuNDYtMi4yMi0yLjMxLTYuNzMtNi4yMy03LjY3LTEzLjQxLTEtMjAuMTggNS40LTUuNTIgMTEuODctNS40IDE3LjgtLjU5IDYuNDkgNS4yNiA2LjMxIDEzLjA4LS44NiAyMS0uNjguNzQtMS43OCAxLjYtMS43OCAyLjY3djQuMjFjMCAxLjM1IDIuMiAxLjYyIDQuNzkgMi4zNSAzMS4wOSA4LjY1IDQ4LjE3IDM0LjEzIDQ1IDY2LjM3LTEuNzYgMTguMTUtMTQuNTYgMzAuMjMtMzIuNyAzMC42My04LjAyLjE5LTE2LjA3LS4wMS0yNC4xMy0uMDF6IiBmaWxsPSIjMDI5OWZlIi8+PHBhdGggZD0iTTMxLjQ2IDExOC4zOGMtMTAuNS0uNjktMTYuOC02Ljg2LTE4LjM4LTE3LjI3LTMtMTkuNDIgMi43OC0zNS44NiAxOC40Ni00Ny44MyAxNC4xNi0xMC44IDI5Ljg3LTEyIDQ1LjM4LTMuMTkgMTcuMjUgOS44NCAyNC41OSAyNS44MSAyNCA0NS4yOS0uNDkgMTUuOS04LjQyIDIzLjE0LTI0LjM4IDIzLjUtNi41OS4xNC0xMy4xOSAwLTE5Ljc5IDAiIGZpbGw9IiNmZWZlZmUiLz48cGF0aCBkPSJNNDYuMDUgNzkuNThjLjA5IDUgLjIzIDkuODItNyA5Ljc3LTcuODItLjA2LTYuMS01LjY5LTYuMjQtMTAuMTktLjE1LTQuODItLjczLTEwIDYuNzMtOS44NHM2LjM3IDUuNTUgNi41MSAxMC4yNnoiIGZpbGw9IiMxMDlmZmUiLz48cGF0aCBkPSJNODAuMjcgNzkuMjdjLS41MyAzLjkxIDEuNzUgOS42NC01Ljg4IDEwLTcuNDcuMzctNi44MS00LjgyLTYuNjEtOS41LjItNC4zMi0xLjgzLTEwIDUuNzgtMTAuNDJzNi41OSA0Ljg5IDYuNzEgOS45MnoiIGZpbGw9IiMwODljZmUiLz48L2c+PC9nPjwvc3ZnPg==" alt="QQ频道">
<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 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>
<br />
<a href="https://jq.qq.com/?_wv=1027&k=5OFifDh">
@@ -69,18 +85,16 @@ _✨ 跨平台 Python 异步机器人框架 ✨_
</p>
<p align="center">
<a href="https://v2.nonebot.dev/">文档</a>
<a href="https://nonebot.dev/">文档</a>
·
<a href="https://v2.nonebot.dev/docs/start/installation">安装</a>
·
<a href="https://v2.nonebot.dev/docs/tutorial/create-project">开始使用</a>
<a href="https://nonebot.dev/docs/quick-start">快速上手</a>
·
<a href="#插件">文档打不开?</a>
</p>
<p align="center">
<a href="https://asciinema.org/a/464654">
<img src="https://v2.nonebot.dev/img/setup.svg">
<a href="https://asciinema.org/a/569440">
<img src="https://nonebot.dev/img/setup.svg">
</a>
</p>
@@ -92,34 +106,40 @@ NoneBot2 是一个现代、跨平台、可扩展的 Python 聊天机器人框架
- 异步优先:基于 Python 的异步特性,即使是~~非常~~大量的消息,也能吞吐自如
- 易于开发:配合 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、TG、微信公众号等[平台](https://onebot.dev/ecosystem.html) |
| [Telegram](https://core.telegram.org/bots/api) | ✅ | |
| [飞书](https://open.feishu.cn/document/home/index) | ✅ | |
| [GitHub](https://docs.github.com/en/developers/apps) | ✅ | GitHub APP & OAuth APP |
| [QQ 频道](https://bot.q.qq.com/wiki/) | ✅ | 官方接口调整较多 |
| [钉钉](https://open.dingtalk.com/document/) | 🤗 | 寻找 Maintainer |
| Console | ✅ | 控制台交互 |
| [开黑啦](https://developer.kookapp.cn/) | ↗️ | 由社区贡献 |
| [Mirai](https://docs.mirai.mamoe.net/mirai-api-http/) | ↗️ | 由社区贡献 |
| [Ntchat](https://github.com/JustUndertaker/adapter-ntchat) | ↗️ | 由社区贡献 |
| :--------------------------------------------------------------------------------------------------------------------------: | :--: | :-----------------------------------------------------------------------: |
| 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) | ✅ | 控制台交互 |
| Red [仓库](https://github.com/nonebot/adapter-red)[协议](https://chrononeko.github.io/QQNTRedProtocol/) | ✅ | QQ 协议 |
| Discord [仓库](https://github.com/nonebot/adapter-discord)[协议](https://discord.com/developers/docs/intro) | | Discord Bot 协议 |
| 开黑啦([仓库](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)[协议](https://webstatic.mihoyo.com/vila/bot/doc/) | ↗️ | 米游社大别野 Bot 协议,由社区贡献 |
- 坚实后盾:支持多种 web 框架,可自定义替换、组合
| 驱动框架 | 类型 |
| :--------------------------------------------------------: | :----: |
| :-----------------------------------------------------------------: | :----: |
| [FastAPI](https://fastapi.tiangolo.com/) | 服务端 |
| [Quart](https://pgjones.gitlab.io/quart/) (异步 Flask) | 服务端 |
| [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://v2.nonebot.dev/docs/)
更多:[概览](https://nonebot.dev/docs/)
## 什么不是 NoneBot2
@@ -131,16 +151,21 @@ NoneBot2 不是 NoneBot1 的替代品。事实上,它们都在被积极的维
## 即刻开始
~~完整~~文档可以在 [这里](https://v2.nonebot.dev/) 查看。
~~完整~~文档可以在 [这里](https://nonebot.dev/) 查看。
懒得看文档?下面是快速安装指南:
1. (**强烈建议**)使用你喜欢的 Python 环境管理工具创建新的虚拟环境。
2. 使用 `pip` (或其他) 安装 NoneBot 脚手架。
1. 安装 [pipx](https://pypa.github.io/pipx/)
```bash
pip install nb-cli
python -m pip install --user pipx
python -m pipx ensurepath
```
2. 安装脚手架
```bash
pipx install nb-cli
```
3. 使用脚手架创建项目
@@ -149,6 +174,12 @@ NoneBot2 不是 NoneBot1 的替代品。事实上,它们都在被积极的维
nb create
```
4. 运行项目
```bash
nb run
```
## 社区资源
### 常见问题
@@ -177,7 +208,7 @@ NoneBot2 不是 NoneBot1 的替代品。事实上,它们都在被积极的维
- [文档镜像(中国境内)](https://nb2.baka.icu)
- [文档镜像(Vercel)](https://nonebot2-vercel-mirror.vercel.app)
- 其他插件请查看 [商店](https://v2.nonebot.dev/store)
- 其他插件请查看 [商店](https://nonebot.dev/store)
## 许可证

View File

@@ -24,12 +24,17 @@
- `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>`
- `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>`
- `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:
@@ -38,24 +43,25 @@ FrontMatter:
"""
import os
import importlib
from importlib.metadata import version
from typing import Any, Dict, Type, Optional
from typing import Any, Dict, Type, Union, TypeVar, Optional, overload
import loguru
from pydantic.env_settings import DotenvType
from nonebot.log import logger
from nonebot.adapters import Bot
from nonebot.utils import escape_tag
from nonebot.config import Env, Config
from nonebot.drivers import Driver, ReverseDriver, combine_driver
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, ASGIMixin, combine_driver
try:
__version__ = version("nonebot2")
except Exception: # pragma: no cover
__version__ = None
A = TypeVar("A", bound=Adapter)
_driver: Optional[Driver] = None
@@ -68,7 +74,8 @@ def get_driver() -> Driver:
全局 {ref}`nonebot.drivers.Driver` 对象
异常:
ValueError: 全局 {ref}`nonebot.drivers.Driver` 对象尚未初始化 ({ref}`nonebot.init <nonebot.init>` 尚未调用)
ValueError: 全局 {ref}`nonebot.drivers.Driver` 对象尚未初始化
({ref}`nonebot.init <nonebot.init>` 尚未调用)
用法:
```python
@@ -80,15 +87,77 @@ def get_driver() -> Driver:
return _driver
@overload
def get_adapter(name: str) -> Adapter:
"""
参数:
name: 适配器名称
返回:
指定名称的 {ref}`nonebot.adapters.Adapter` 对象
"""
@overload
def get_adapter(name: Type[A]) -> A:
"""
参数:
name: 适配器类型
返回:
指定类型的 {ref}`nonebot.adapters.Adapter` 对象
"""
def get_adapter(name: Union[str, Type[Adapter]]) -> Adapter:
"""获取已注册的 {ref}`nonebot.adapters.Adapter` 实例。
异常:
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 对象。
"""获取全局 {ref}`nonebot.drivers.ASGIMixin` 对应的 Server App 对象。
返回:
Server App 对象
异常:
AssertionError: 全局 Driver 对象不是 {ref}`nonebot.drivers.ReverseDriver` 类型
ValueError: 全局 {ref}`nonebot.drivers.Driver` 对象尚未初始化 ({ref}`nonebot.init <nonebot.init>` 尚未调用)
AssertionError: 全局 Driver 对象不是 {ref}`nonebot.drivers.ASGIMixin` 类型
ValueError: 全局 {ref}`nonebot.drivers.Driver` 对象尚未初始化
({ref}`nonebot.init <nonebot.init>` 尚未调用)
用法:
```python
@@ -96,21 +165,21 @@ def get_app() -> Any:
```
"""
driver = get_driver()
assert isinstance(
driver, ReverseDriver
), "app object is only available for reverse driver"
assert isinstance(driver, ASGIMixin), "app object is only available for asgi driver"
return driver.server_app
def get_asgi() -> Any:
"""获取全局 {ref}`nonebot.drivers.ReverseDriver` 对应 [ASGI](https://asgi.readthedocs.io/) 对象。
"""获取全局 {ref}`nonebot.drivers.ASGIMixin` 对应
[ASGI](https://asgi.readthedocs.io/) 对象。
返回:
ASGI 对象
异常:
AssertionError: 全局 Driver 对象不是 {ref}`nonebot.drivers.ReverseDriver` 类型
ValueError: 全局 {ref}`nonebot.drivers.Driver` 对象尚未初始化 ({ref}`nonebot.init <nonebot.init>` 尚未调用)
AssertionError: 全局 Driver 对象不是 {ref}`nonebot.drivers.ASGIMixin` 类型
ValueError: 全局 {ref}`nonebot.drivers.Driver` 对象尚未初始化
({ref}`nonebot.init <nonebot.init>` 尚未调用)
用法:
```python
@@ -119,7 +188,7 @@ def get_asgi() -> Any:
"""
driver = get_driver()
assert isinstance(
driver, ReverseDriver
driver, ASGIMixin
), "asgi object is only available for reverse driver"
return driver.asgi
@@ -131,7 +200,8 @@ def get_bot(self_id: Optional[str] = None) -> Bot:
当不提供时,返回一个 {ref}`nonebot.adapters.Bot`。
参数:
self_id: 用来识别 {ref}`nonebot.adapters.Bot` 的 {ref}`nonebot.adapters.Bot.self_id` 属性
self_id: 用来识别 {ref}`nonebot.adapters.Bot` 的
{ref}`nonebot.adapters.Bot.self_id` 属性
返回:
{ref}`nonebot.adapters.Bot` 对象
@@ -139,7 +209,8 @@ def get_bot(self_id: Optional[str] = None) -> Bot:
异常:
KeyError: 对应 self_id 的 Bot 不存在
ValueError: 没有传入 self_id 且没有 Bot 可用
ValueError: 全局 {ref}`nonebot.drivers.Driver` 对象尚未初始化 ({ref}`nonebot.init <nonebot.init>` 尚未调用)
ValueError: 全局 {ref}`nonebot.drivers.Driver` 对象尚未初始化
({ref}`nonebot.init <nonebot.init>` 尚未调用)
用法:
```python
@@ -162,10 +233,12 @@ def get_bots() -> Dict[str, Bot]:
"""获取所有连接到 NoneBot 的 {ref}`nonebot.adapters.Bot` 对象。
返回:
一个以 {ref}`nonebot.adapters.Bot.self_id` 为键{ref}`nonebot.adapters.Bot` 对象为值的字典
一个以 {ref}`nonebot.adapters.Bot.self_id` 为键
{ref}`nonebot.adapters.Bot` 对象为值的字典
异常:
ValueError: 全局 {ref}`nonebot.drivers.Driver` 对象尚未初始化 ({ref}`nonebot.init <nonebot.init>` 尚未调用)
ValueError: 全局 {ref}`nonebot.drivers.Driver` 对象尚未初始化
({ref}`nonebot.init <nonebot.init>` 尚未调用)
用法:
```python
@@ -175,31 +248,16 @@ def get_bots() -> Dict[str, Bot]:
return get_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]:
drivers = obj_str.split("+")
DriverClass = _resolve_dot_notation(
DriverClass = resolve_dot_notation(
drivers[0], "Driver", default_prefix="nonebot.drivers."
)
if len(drivers) == 1:
logger.trace(f"Detected driver {DriverClass} with no mixins.")
return DriverClass
mixins = [
_resolve_dot_notation(mixin, "Mixin", default_prefix="nonebot.drivers.")
resolve_dot_notation(mixin, "Mixin", default_prefix="nonebot.drivers.")
for mixin in drivers[1:]
]
logger.trace(f"Detected driver {DriverClass} with mixins {mixins}.")

View File

@@ -19,6 +19,11 @@ __autodoc__ = {
"Event": True,
"Adapter": True,
"Message": True,
"Message.__getitem__": True,
"Message.__contains__": True,
"Message._construct": True,
"MessageSegment": True,
"MessageSegment.__str__": True,
"MessageSegment.__add__": True,
"MessageTemplate": True,
}

View File

@@ -1,21 +1,24 @@
"""本模块定义了 NoneBot 本身运行所需的配置项。
NoneBot 使用 [`pydantic`](https://pydantic-docs.helpmanual.io/) 以及 [`python-dotenv`](https://saurabh-kumar.com/python-dotenv/) 来读取配置。
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/) 文档。
配置项需符合特殊格式或 json 序列化格式
详情见 [`pydantic Field Type`](https://pydantic-docs.helpmanual.io/usage/types/) 文档。
FrontMatter:
sidebar_position: 1
description: nonebot.config 模块
"""
import os
from pathlib import Path
from datetime import timedelta
from ipaddress import IPv4Address
from typing import TYPE_CHECKING, Any, Set, Dict, Tuple, Union, Mapping, Optional
from pydantic.utils import deep_update
from pydantic import Extra, BaseSettings, IPvAnyAddress
from pydantic.fields import Undefined, UndefinedType
from pydantic import Extra, Field, BaseSettings, IPvAnyAddress
from pydantic.env_settings import (
DotenvType,
SettingsError,
@@ -29,9 +32,8 @@ from nonebot.log import logger
class CustomEnvSettings(EnvSettingsSource):
def __call__(self, settings: BaseSettings) -> Dict[str, Any]:
"""
Build environment variables suitable for passing to the Model.
"""
"""从环境变量和 dotenv 配置文件中读取配置项。"""
d: Dict[str, Any] = {}
if settings.__config__.case_sensitive:
@@ -43,21 +45,25 @@ class CustomEnvSettings(EnvSettingsSource):
env_vars = {**env_file_vars, **env_vars}
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"]:
env_val = env_vars.get(env_name)
env_val = env_vars.get(env_name, Undefined)
if env_name in env_file_vars:
del env_file_vars[env_name]
if env_val is not None:
if env_val is not Undefined:
break
is_complex, allow_parse_failure = self.field_is_complex(field)
if is_complex:
if env_val is None:
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
# 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:
@@ -72,8 +78,9 @@ class CustomEnvSettings(EnvSettingsSource):
)
else:
d[field.alias] = env_val
elif env_val is not None:
# simplest case, field is not complex, we only need to add the value if it was found
elif not isinstance(env_val, UndefinedType):
# simplest case, field is not complex
# we only need to add the value if it was found
d[field.alias] = env_val
# remain user custom config
@@ -83,7 +90,7 @@ class CustomEnvSettings(EnvSettingsSource):
# there's a value, decode that as JSON
try:
env_val = settings.__config__.parse_env_var(env_name, val_striped)
except ValueError as e:
except ValueError:
logger.trace(
"Error while parsing JSON for "
f"{env_name!r}={val_striped!r}. "
@@ -140,7 +147,7 @@ class BaseConfig(BaseSettings):
class Env(BaseConfig):
"""运行环境配置。大小写不敏感。
将会从 `环境变量` > `.env 环境配置文件` 的优先级读取环境信息。
将会从 **环境变量** > **dotenv 配置文件** 的优先级读取环境信息。
"""
environment: str = "prod"
@@ -159,7 +166,7 @@ class Config(BaseConfig):
除了 NoneBot 的配置项外,还可以自行添加配置项到 `.env.{environment}` 文件中。
这些配置将会在 json 反序列化后一起带入 `Config` 类中。
配置方法参考: [配置](https://v2.nonebot.dev/docs/tutorial/configuration)
配置方法参考: [配置](https://nonebot.dev/docs/appendices/config)
"""
_env_file: DotenvType = ".env", ".env.prod"
@@ -171,15 +178,17 @@ class Config(BaseConfig):
配置格式为 `<module>[:<Driver>][+<module>[:<Mixin>]]*`。
`~` 为 `nonebot.drivers.` 的缩写。
配置方法参考: [配置驱动器](https://nonebot.dev/docs/advanced/driver#%E9%85%8D%E7%BD%AE%E9%A9%B1%E5%8A%A8%E5%99%A8)
"""
host: IPvAnyAddress = IPv4Address("127.0.0.1") # type: ignore
"""NoneBot {ref}`nonebot.drivers.ReverseDriver` 服务端监听的 IP/主机名。"""
port: int = 8080
port: int = Field(default=8080, ge=1, le=65535)
"""NoneBot {ref}`nonebot.drivers.ReverseDriver` 服务端监听的端口。"""
log_level: Union[int, str] = "INFO"
"""NoneBot 日志输出等级,可以为 `int` 类型等级或等级名称
"""NoneBot 日志输出等级,可以为 `int` 类型等级或等级名称
参考 [`loguru 日志等级`](https://loguru.readthedocs.io/en/stable/api/logger.html#levels)。
参考 [记录日志](https://nonebot.dev/docs/appendices/log)[loguru 日志等级](https://loguru.readthedocs.io/en/stable/api/logger.html#levels)。
:::tip 提示
日志等级名称应为大写,如 `INFO`。
@@ -210,6 +219,8 @@ class Config(BaseConfig):
command_start: Set[str] = {"/"}
"""命令的起始标记,用于判断一条消息是不是命令。
参考[命令响应规则](https://nonebot.dev/docs/advanced/matcher#command)。
用法:
```conf
COMMAND_START=["/", ""]
@@ -218,6 +229,8 @@ class Config(BaseConfig):
command_sep: Set[str] = {"."}
"""命令的分隔标记,用于将文本形式的命令切分为元组(实际的命令名)。
参考[命令响应规则](https://nonebot.dev/docs/advanced/matcher#command)。
用法:
```conf
COMMAND_SEP=["."]

View File

@@ -4,6 +4,9 @@ FrontMatter:
sidebar_position: 9
description: nonebot.consts 模块
"""
import os
import sys
from typing import Literal
# used by Matcher
@@ -30,6 +33,8 @@ 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: Literal["_args"] = "_args"
"""shell 命令 parse 后参数字典存储 key"""
@@ -38,10 +43,6 @@ SHELL_ARGV: Literal["_argv"] = "_argv"
REGEX_MATCHED: Literal["_matched"] = "_matched"
"""正则匹配结果存储 key"""
REGEX_GROUP: Literal["_matched_groups"] = "_matched_groups"
"""正则匹配 group 元组存储 key"""
REGEX_DICT: Literal["_matched_dict"] = "_matched_dict"
"""正则匹配 group 字典存储 key"""
STARTSWITH_KEY: Literal["_startswith"] = "_startswith"
"""响应触发前缀 key"""
ENDSWITH_KEY: Literal["_endswith"] = "_endswith"
@@ -50,3 +51,5 @@ 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

@@ -45,6 +45,10 @@ class Param(abc.ABC, FieldInfo):
继承自 `pydantic.fields.FieldInfo`,用于描述参数信息(不包括参数名)。
"""
def __init__(self, *args, validate: bool = False, **kwargs: Any) -> None:
super().__init__(*args, **kwargs)
self.validate = validate
@classmethod
def _check_param(
cls, param: inspect.Parameter, allow_types: Tuple[Type["Param"], ...]
@@ -82,8 +86,8 @@ class Dependent(Generic[R]):
"""
call: _DependentCallable[R]
params: Tuple[ModelField] = field(default_factory=tuple)
parameterless: Tuple[Param] = field(default_factory=tuple)
params: Tuple[ModelField, ...] = field(default_factory=tuple)
parameterless: Tuple[Param, ...] = field(default_factory=tuple)
def __repr__(self) -> str:
if inspect.isfunction(self.call) or inspect.isclass(self.call):
@@ -97,6 +101,7 @@ class Dependent(Generic[R]):
)
async def __call__(self, **kwargs: Any) -> R:
try:
# do pre-check
await self.check(**kwargs)
@@ -108,11 +113,14 @@ class Dependent(Generic[R]):
return await cast(Callable[..., Awaitable[R]], self.call)(**values)
else:
return await run_sync(cast(Callable[..., R], self.call))(**values)
except SkippedException as e:
logger.trace(f"{self} skipped due to {e}")
raise
@staticmethod
def parse_params(
call: _DependentCallable[R], allow_types: Tuple[Type[Param], ...]
) -> Tuple[ModelField]:
) -> Tuple[ModelField, ...]:
fields: List[ModelField] = []
params = get_typed_signature(call).parameters.values()
@@ -129,7 +137,8 @@ class Dependent(Generic[R]):
break
else:
raise ValueError(
f"Unknown parameter {param.name} for function {call} with type {param.annotation}"
f"Unknown parameter {param.name} "
f"for function {call} with type {param.annotation}"
)
default_value = field_info.default
@@ -182,7 +191,7 @@ class Dependent(Generic[R]):
params = cls.parse_params(call, allow_types)
parameterless_params = (
tuple()
()
if parameterless is None
else cls.parse_parameterless(tuple(parameterless), allow_types)
)
@@ -190,25 +199,18 @@ class Dependent(Generic[R]):
return cls(call, params, parameterless_params)
async def check(self, **params: Any) -> None:
try:
await asyncio.gather(*(param._check(**params) for param in self.parameterless))
await asyncio.gather(
*(param._check(**params) for param in self.parameterless)
*(cast(Param, param.field_info)._check(**params) for param in self.params)
)
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
async def _solve_field(self, field: ModelField, params: Dict[str, Any]) -> Any:
value = await cast(Param, field.field_info)._solve(**params)
param = cast(Param, field.field_info)
value = await param._solve(**params)
if value is Undefined:
value = field.get_default()
return check_field_type(field, value)
v = check_field_type(field, value)
return v if param.validate else value
async def solve(self, **params: Any) -> Dict[str, Any]:
# solve parameterless

View File

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

View File

@@ -8,30 +8,40 @@ FrontMatter:
"""
from nonebot.internal.driver import URL as URL
from nonebot.internal.driver import Mixin as Mixin
from nonebot.internal.driver import Driver as Driver
from nonebot.internal.driver import Cookies as Cookies
from nonebot.internal.driver import Request as Request
from nonebot.internal.driver import Response as Response
from nonebot.internal.driver import ASGIMixin as ASGIMixin
from nonebot.internal.driver import WebSocket as WebSocket
from nonebot.internal.driver import HTTPVersion as HTTPVersion
from nonebot.internal.driver import ForwardMixin as ForwardMixin
from nonebot.internal.driver import ReverseMixin as ReverseMixin
from nonebot.internal.driver import ForwardDriver as ForwardDriver
from nonebot.internal.driver import ReverseDriver as ReverseDriver
from nonebot.internal.driver import combine_driver as combine_driver
from nonebot.internal.driver import HTTPClientMixin as HTTPClientMixin
from nonebot.internal.driver import HTTPServerSetup as HTTPServerSetup
from nonebot.internal.driver import WebSocketClientMixin as WebSocketClientMixin
from nonebot.internal.driver import WebSocketServerSetup as WebSocketServerSetup
__autodoc__ = {
"URL": True,
"Driver": True,
"Cookies": True,
"Request": True,
"Response": True,
"WebSocket": True,
"HTTPVersion": True,
"Driver": True,
"Mixin": True,
"ForwardMixin": True,
"ForwardDriver": True,
"HTTPClientMixin": True,
"WebSocketClientMixin": True,
"ReverseMixin": True,
"ReverseDriver": True,
"ASGIMixin": True,
"combine_driver": True,
"HTTPServerSetup": True,
"WebSocketServerSetup": True,

View File

@@ -1,152 +0,0 @@
import signal
import asyncio
import threading
from typing import Set, Union, Callable, Awaitable, cast
from nonebot.log import logger
from nonebot.drivers import Driver
from nonebot.typing import overrides
from nonebot.config import Env, Config
from nonebot.utils import run_sync, is_coroutine_callable
HOOK_FUNC = Union[Callable[[], None], 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[HOOK_FUNC] = set()
self.shutdown_funcs: Set[HOOK_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: HOOK_FUNC) -> HOOK_FUNC:
"""
注册一个启动时执行的函数
"""
self.startup_funcs.add(func)
return func
@overrides(Driver)
def on_shutdown(self, func: HOOK_FUNC) -> HOOK_FUNC:
"""
注册一个停止时执行的函数
"""
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 = [
cast(Callable[..., Awaitable[None]], startup)()
if is_coroutine_callable(startup)
else run_sync(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 = [
cast(Callable[..., Awaitable[None]], shutdown)()
if is_coroutine_callable(shutdown)
else run_sync(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

@@ -15,33 +15,39 @@ FrontMatter:
description: nonebot.drivers.aiohttp 模块
"""
from typing import Type, AsyncGenerator
from typing_extensions import override
from contextlib import asynccontextmanager
from typing import TYPE_CHECKING, AsyncGenerator
from nonebot.typing import overrides
from nonebot.drivers import Request, Response
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 HTTPVersion, ForwardMixin, ForwardDriver, combine_driver
from nonebot.drivers import (
HTTPVersion,
HTTPClientMixin,
WebSocketClientMixin,
combine_driver,
)
try:
import aiohttp
except ImportError: # pragma: no cover
except ModuleNotFoundError as e: # pragma: no cover
raise ImportError(
"Please install aiohttp first to use this driver. `pip install nonebot2[aiohttp]`"
) from None
"Please install aiohttp first to use this driver. "
"Install with pip: `pip install nonebot2[aiohttp]`"
) from e
class Mixin(ForwardMixin):
class Mixin(HTTPClientMixin, WebSocketClientMixin):
"""AIOHTTP Mixin"""
@property
@overrides(ForwardMixin)
@override
def type(self) -> str:
return "aiohttp"
@overrides(ForwardMixin)
@override
async def request(self, setup: Request) -> Response:
if setup.version == HTTPVersion.H10:
version = aiohttp.HttpVersion10
@@ -51,30 +57,36 @@ class Mixin(ForwardMixin):
raise RuntimeError(f"Unsupported HTTP version: {setup.version}")
timeout = aiohttp.ClientTimeout(setup.timeout)
files = None
data = setup.data
if setup.files:
files = aiohttp.FormData()
data = aiohttp.FormData(data or {})
for name, file in setup.files:
files.add_field(name, file[1], content_type=file[2], filename=file[0])
async with aiohttp.ClientSession(version=version, trust_env=True) as session:
data.add_field(name, file[1], content_type=file[2], filename=file[0])
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(
setup.method,
setup.url,
data=setup.content or setup.data or files,
data=setup.content or data,
json=setup.json,
headers=setup.headers,
timeout=timeout,
proxy=setup.proxy,
) as response:
res = Response(
return Response(
response.status,
headers=response.headers.copy(),
content=await response.read(),
request=setup,
)
return res
@overrides(ForwardMixin)
@override
@asynccontextmanager
async def websocket(self, setup: Request) -> AsyncGenerator["WebSocket", None]:
if setup.version == HTTPVersion.H10:
@@ -92,8 +104,7 @@ class Mixin(ForwardMixin):
headers=setup.headers,
proxy=setup.proxy,
) as ws:
websocket = WebSocket(request=setup, session=session, websocket=ws)
yield websocket
yield WebSocket(request=setup, session=session, websocket=ws)
class WebSocket(BaseWebSocket):
@@ -111,15 +122,15 @@ class WebSocket(BaseWebSocket):
self.websocket = websocket
@property
@overrides(BaseWebSocket)
@override
def closed(self):
return self.websocket.closed
@overrides(BaseWebSocket)
@override
async def accept(self):
raise NotImplementedError
@overrides(BaseWebSocket)
@override
async def close(self, code: int = 1000):
await self.websocket.close(code=code)
await self.session.close()
@@ -130,7 +141,7 @@ class WebSocket(BaseWebSocket):
raise WebSocketClosed(self.websocket.close_code or 1006)
return msg
@overrides(BaseWebSocket)
@override
async def receive(self) -> str:
msg = await self._receive()
if msg.type not in (aiohttp.WSMsgType.TEXT, aiohttp.WSMsgType.BINARY):
@@ -139,7 +150,7 @@ class WebSocket(BaseWebSocket):
)
return msg.data
@overrides(BaseWebSocket)
@override
async def receive_text(self) -> str:
msg = await self._receive()
if msg.type != aiohttp.WSMsgType.TEXT:
@@ -148,7 +159,7 @@ class WebSocket(BaseWebSocket):
)
return msg.data
@overrides(BaseWebSocket)
@override
async def receive_bytes(self) -> bytes:
msg = await self._receive()
if msg.type != aiohttp.WSMsgType.BINARY:
@@ -157,14 +168,20 @@ class WebSocket(BaseWebSocket):
)
return msg.data
@overrides(BaseWebSocket)
@override
async def send_text(self, data: str) -> None:
await self.websocket.send_str(data)
@overrides(BaseWebSocket)
@override
async def send_bytes(self, data: bytes) -> None:
await self.websocket.send_bytes(data)
Driver: Type[ForwardDriver] = combine_driver(BlockDriver, Mixin) # type: ignore
"""AIOHTTP Driver"""
if TYPE_CHECKING:
class Driver(Mixin, NoneDriver):
...
else:
Driver = combine_driver(NoneDriver, Mixin)
"""AIOHTTP Driver"""

View File

@@ -1,5 +1,11 @@
"""[FastAPI](https://fastapi.tiangolo.com/) 驱动适配
```bash
nb driver install fastapi
# 或者
pip install nonebot2[fastapi]
```
:::tip 提示
本驱动仅支持服务端连接
:::
@@ -13,22 +19,33 @@ FrontMatter:
import logging
import contextlib
from functools import wraps
from typing import Any, List, Tuple, Union, Callable, Optional
from typing_extensions import override
from typing import Any, Dict, List, Tuple, Union, Optional
import uvicorn
from pydantic import BaseSettings
from fastapi.responses import Response
from fastapi import FastAPI, Request, UploadFile, status
from starlette.websockets import WebSocket, WebSocketState, WebSocketDisconnect
from nonebot.config import Env
from nonebot.typing import overrides
from nonebot.drivers import ASGIMixin
from nonebot.exception import WebSocketClosed
from nonebot.internal.driver import FileTypes
from nonebot.drivers import Driver as BaseDriver
from nonebot.config import Config as NoneBotConfig
from nonebot.drivers import Request as BaseRequest
from nonebot.drivers import WebSocket as BaseWebSocket
from nonebot.drivers import ReverseDriver, HTTPServerSetup, WebSocketServerSetup
from nonebot.drivers import 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):
@@ -65,50 +82,56 @@ class Config(BaseSettings):
"""要监听的文件列表,支持 glob pattern默认为 uvicorn 默认值"""
fastapi_reload_excludes: Optional[List[str]] = None
"""不要监听的文件列表,支持 glob pattern默认为 uvicorn 默认值"""
fastapi_extra: Dict[str, Any] = {}
"""传递给 `FastAPI` 的其他参数。"""
class Config:
extra = "ignore"
class Driver(ReverseDriver):
class Driver(BaseDriver, ASGIMixin):
"""FastAPI 驱动框架。"""
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._lifespan = Lifespan()
self._server_app = FastAPI(
lifespan=self._lifespan_manager,
openapi_url=self.fastapi_config.fastapi_openapi_url,
docs_url=self.fastapi_config.fastapi_docs_url,
redoc_url=self.fastapi_config.fastapi_redoc_url,
**self.fastapi_config.fastapi_extra,
)
@property
@overrides(ReverseDriver)
@override
def type(self) -> str:
"""驱动名称: `fastapi`"""
return "fastapi"
@property
@overrides(ReverseDriver)
@override
def server_app(self) -> FastAPI:
"""`FastAPI APP` 对象"""
return self._server_app
@property
@overrides(ReverseDriver)
@override
def asgi(self) -> FastAPI:
"""`FastAPI APP` 对象"""
return self._server_app
@property
@overrides(ReverseDriver)
@override
def logger(self) -> logging.Logger:
"""fastapi 使用的 logger"""
return logging.getLogger("fastapi")
@overrides(ReverseDriver)
@override
def setup_http_server(self, setup: HTTPServerSetup):
async def _handle(request: Request) -> Response:
return await self._handle_http(request, setup)
@@ -121,7 +144,7 @@ class Driver(ReverseDriver):
include_in_schema=self.fastapi_config.fastapi_include_adapter_schema,
)
@overrides(ReverseDriver)
@override
def setup_websocket_server(self, setup: WebSocketServerSetup) -> None:
async def _handle(websocket: WebSocket) -> None:
await self._handle_ws(websocket, setup)
@@ -132,17 +155,23 @@ class Driver(ReverseDriver):
name=setup.name,
)
@overrides(ReverseDriver)
def on_startup(self, func: Callable) -> Callable:
"""参考文档: `Events <https://fastapi.tiangolo.com/advanced/events/#startup-event>`_"""
return self.server_app.on_event("startup")(func)
@override
def on_startup(self, func: LIFESPAN_FUNC) -> LIFESPAN_FUNC:
return self._lifespan.on_startup(func)
@overrides(ReverseDriver)
def on_shutdown(self, func: Callable) -> Callable:
"""参考文档: `Events <https://fastapi.tiangolo.com/advanced/events/#shutdown-event>`_"""
return self.server_app.on_event("shutdown")(func)
@override
def on_shutdown(self, func: LIFESPAN_FUNC) -> LIFESPAN_FUNC:
return self._lifespan.on_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(
self,
host: Optional[str] = None,
@@ -152,7 +181,7 @@ class Driver(ReverseDriver):
**kwargs,
):
"""使用 `uvicorn` 启动 FastAPI"""
super().run(host, port, app, **kwargs)
super().run(host, port, app=app, **kwargs)
LOGGING_CONFIG = {
"version": 1,
"disable_existing_loggers": False,
@@ -241,30 +270,30 @@ class Driver(ReverseDriver):
class FastAPIWebSocket(BaseWebSocket):
"""FastAPI WebSocket Wrapper"""
@overrides(BaseWebSocket)
@override
def __init__(self, *, request: BaseRequest, websocket: WebSocket):
super().__init__(request=request)
self.websocket = websocket
@property
@overrides(BaseWebSocket)
@override
def closed(self) -> bool:
return (
self.websocket.client_state == WebSocketState.DISCONNECTED
or self.websocket.application_state == WebSocketState.DISCONNECTED
)
@overrides(BaseWebSocket)
@override
async def accept(self) -> None:
await self.websocket.accept()
@overrides(BaseWebSocket)
@override
async def close(
self, code: int = status.WS_1000_NORMAL_CLOSURE, reason: str = ""
) -> None:
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()
@@ -272,21 +301,21 @@ class FastAPIWebSocket(BaseWebSocket):
raise WebSocketClosed(msg["code"])
return msg["text"] if "text" in msg else msg["bytes"]
@overrides(BaseWebSocket)
@override
@catch_closed
async def receive_text(self) -> str:
return await self.websocket.receive_text()
@overrides(BaseWebSocket)
@override
@catch_closed
async def receive_bytes(self) -> bytes:
return await self.websocket.receive_bytes()
@overrides(BaseWebSocket)
@override
async def send_text(self, data: str) -> None:
await self.websocket.send({"type": "websocket.send", "text": data})
@overrides(BaseWebSocket)
@override
async def send_bytes(self, data: bytes) -> None:
await self.websocket.send({"type": "websocket.send", "bytes": data})

View File

@@ -14,51 +14,51 @@ FrontMatter:
sidebar_position: 3
description: nonebot.drivers.httpx 模块
"""
from typing import Type, AsyncGenerator
from contextlib import asynccontextmanager
from nonebot.typing import overrides
from nonebot.drivers._block_driver import BlockDriver
from typing import TYPE_CHECKING
from typing_extensions import override
from nonebot.drivers.none import Driver as NoneDriver
from nonebot.drivers import (
Request,
Response,
WebSocket,
HTTPVersion,
ForwardMixin,
ForwardDriver,
HTTPClientMixin,
combine_driver,
)
try:
import httpx
except ImportError: # pragma: no cover
except ModuleNotFoundError as e: # pragma: no cover
raise ImportError(
"Please install httpx by using `pip install nonebot2[httpx]`"
) from None
"Please install httpx first to use this driver. "
"Install with pip: `pip install nonebot2[httpx]`"
) from e
class Mixin(ForwardMixin):
class Mixin(HTTPClientMixin):
"""HTTPX Mixin"""
@property
@overrides(ForwardMixin)
@override
def type(self) -> str:
return "httpx"
@overrides(ForwardMixin)
@override
async def request(self, setup: Request) -> Response:
async with httpx.AsyncClient(
cookies=setup.cookies.jar,
http2=setup.version == HTTPVersion.H2,
proxies=setup.proxy, # type: ignore
proxies=setup.proxy,
follow_redirects=True,
) as client:
response = await client.request(
setup.method,
str(setup.url),
content=setup.content, # type: ignore
data=setup.data, # type: ignore
content=setup.content,
data=setup.data,
json=setup.json,
files=setup.files, # type: ignore
files=setup.files,
headers=tuple(setup.headers.items()),
timeout=setup.timeout,
)
@@ -69,12 +69,12 @@ class Mixin(ForwardMixin):
request=setup,
)
@overrides(ForwardMixin)
@asynccontextmanager
async def websocket(self, setup: Request) -> AsyncGenerator[WebSocket, None]:
async with super(Mixin, self).websocket(setup) as ws:
yield ws
if TYPE_CHECKING:
Driver: Type[ForwardDriver] = combine_driver(BlockDriver, Mixin) # type: ignore
"""HTTPX Driver"""
class Driver(Mixin, NoneDriver):
...
else:
Driver = combine_driver(NoneDriver, Mixin)
"""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

@@ -17,30 +17,45 @@ FrontMatter:
import asyncio
from functools import wraps
from typing import List, Tuple, Union, 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 nonebot.config import Env
from nonebot.typing import overrides
from nonebot.drivers import ASGIMixin
from nonebot.exception import WebSocketClosed
from nonebot.internal.driver import FileTypes
from nonebot.drivers import Driver as BaseDriver
from nonebot.config import Config as NoneBotConfig
from nonebot.drivers import Request as BaseRequest
from nonebot.drivers import WebSocket as BaseWebSocket
from nonebot.drivers import ReverseDriver, HTTPServerSetup, WebSocketServerSetup
from nonebot.drivers import HTTPServerSetup, WebSocketServerSetup
try:
import uvicorn
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.datastructures import FileStorage
from quart import Websocket as QuartWebSocket
except ImportError: # pragma: no cover
except ModuleNotFoundError as e: # pragma: no cover
raise ImportError(
"Please install Quart by using `pip install nonebot2[quart]`"
) from None
"Please install Quart first to use this driver. "
"Install with pip: `pip install nonebot2[quart]`"
) from e
_AsyncCallable = TypeVar("_AsyncCallable", bound=Callable[..., Coroutine])
@@ -69,12 +84,14 @@ class Config(BaseSettings):
"""要监听的文件列表,支持 glob pattern默认为 uvicorn 默认值"""
quart_reload_excludes: Optional[List[str]] = None
"""不要监听的文件列表,支持 glob pattern默认为 uvicorn 默认值"""
quart_extra: Dict[str, Any] = {}
"""传递给 `Quart` 的其他参数。"""
class Config:
extra = "ignore"
class Driver(ReverseDriver):
class Driver(BaseDriver, ASGIMixin):
"""Quart 驱动框架"""
def __init__(self, env: Env, config: NoneBotConfig):
@@ -82,33 +99,35 @@ class Driver(ReverseDriver):
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
@overrides(ReverseDriver)
@override
def type(self) -> str:
"""驱动名称: `quart`"""
return "quart"
@property
@overrides(ReverseDriver)
@override
def server_app(self) -> Quart:
"""`Quart` 对象"""
return self._server_app
@property
@overrides(ReverseDriver)
@override
def asgi(self):
"""`Quart` 对象"""
return self._server_app
@property
@overrides(ReverseDriver)
@override
def logger(self):
"""Quart 使用的 logger"""
return self._server_app.logger
@overrides(ReverseDriver)
@override
def setup_http_server(self, setup: HTTPServerSetup):
async def _handle() -> Response:
return await self._handle_http(setup)
@@ -120,7 +139,7 @@ class Driver(ReverseDriver):
view_func=_handle,
)
@overrides(ReverseDriver)
@override
def setup_websocket_server(self, setup: WebSocketServerSetup) -> None:
async def _handle() -> None:
return await self._handle_ws(setup)
@@ -131,17 +150,17 @@ class Driver(ReverseDriver):
view_func=_handle,
)
@overrides(ReverseDriver)
@override
def on_startup(self, func: _AsyncCallable) -> _AsyncCallable:
"""参考文档: [`Startup and Shutdown`](https://pgjones.gitlab.io/quart/how_to_guides/startup_shutdown.html)"""
return self.server_app.before_serving(func) # type: ignore
@overrides(ReverseDriver)
@override
def on_shutdown(self, func: _AsyncCallable) -> _AsyncCallable:
"""参考文档: [`Startup and Shutdown`](https://pgjones.gitlab.io/quart/how_to_guides/startup_shutdown.html)"""
return self.server_app.after_serving(func) # type: ignore
@overrides(ReverseDriver)
@override
def run(
self,
host: Optional[str] = None,
@@ -184,9 +203,7 @@ class Driver(ReverseDriver):
async def _handle_http(self, setup: HTTPServerSetup) -> Response:
request: Request = _request
json = None
if request.is_json:
json = await request.get_json()
json = await request.get_json() if request.is_json else None
data = await request.form
files_dict = await request.files
@@ -219,7 +236,8 @@ class Driver(ReverseDriver):
)
async def _handle_ws(self, setup: WebSocketServerSetup) -> None:
websocket: QuartWebSocket = _websocket
ctx = cast(WebsocketContext, websocket_ctx.copy())
websocket = websocket_ctx.websocket
http_request = BaseRequest(
websocket.method,
@@ -229,7 +247,7 @@ class Driver(ReverseDriver):
version=websocket.http_version,
)
ws = WebSocket(request=http_request, websocket=websocket)
ws = WebSocket(request=http_request, websocket_ctx=ctx)
await setup.handle_func(ws)
@@ -237,30 +255,34 @@ class Driver(ReverseDriver):
class WebSocket(BaseWebSocket):
"""Quart WebSocket Wrapper"""
def __init__(self, *, request: BaseRequest, websocket: QuartWebSocket):
def __init__(self, *, request: BaseRequest, websocket_ctx: WebsocketContext):
super().__init__(request=request)
self.websocket = websocket
self.websocket_ctx = websocket_ctx
@property
@overrides(BaseWebSocket)
def websocket(self) -> QuartWebSocket:
return self.websocket_ctx.websocket
@property
@override
def closed(self):
# FIXME
return True
@overrides(BaseWebSocket)
@override
async def accept(self):
await self.websocket.accept()
@overrides(BaseWebSocket)
@override
async def close(self, code: int = 1000, reason: str = ""):
await self.websocket.close(code, reason)
@overrides(BaseWebSocket)
@override
@catch_closed
async def receive(self) -> Union[str, bytes]:
return await self.websocket.receive()
@overrides(BaseWebSocket)
@override
@catch_closed
async def receive_text(self) -> str:
msg = await self.websocket.receive()
@@ -268,7 +290,7 @@ class WebSocket(BaseWebSocket):
raise TypeError("WebSocket received unexpected frame type: bytes")
return msg
@overrides(BaseWebSocket)
@override
@catch_closed
async def receive_bytes(self) -> bytes:
msg = await self.websocket.receive()
@@ -276,11 +298,11 @@ class WebSocket(BaseWebSocket):
raise TypeError("WebSocket received unexpected frame type: str")
return msg
@overrides(BaseWebSocket)
@override
async def send_text(self, data: str):
await self.websocket.send(data)
@overrides(BaseWebSocket)
@override
async def send_bytes(self, data: bytes):
await self.websocket.send(data)

View File

@@ -14,34 +14,39 @@ FrontMatter:
sidebar_position: 4
description: nonebot.drivers.websockets 模块
"""
import logging
from functools import wraps
from contextlib import asynccontextmanager
from typing import Type, Union, AsyncGenerator
from typing_extensions import ParamSpec, override
from typing import TYPE_CHECKING, Union, TypeVar, Callable, Awaitable, AsyncGenerator
from nonebot.typing import overrides
from nonebot.drivers import Request
from nonebot.log import LoguruHandler
from nonebot.drivers import Request, Response
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 ForwardMixin, ForwardDriver, combine_driver
from nonebot.drivers import WebSocketClientMixin, combine_driver
try:
from websockets.exceptions import ConnectionClosed
from websockets.legacy.client import Connect, WebSocketClientProtocol
except ImportError: # pragma: no cover
except ModuleNotFoundError as e: # pragma: no cover
raise ImportError(
"Please install websockets by using `pip install nonebot2[websockets]`"
) from None
"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.addHandler(LoguruHandler())
def catch_closed(func):
def catch_closed(func: Callable[P, Awaitable[T]]) -> Callable[P, Awaitable[T]]:
@wraps(func)
async def decorator(*args, **kwargs):
async def decorator(*args: P.args, **kwargs: P.kwargs) -> T:
try:
return await func(*args, **kwargs)
except ConnectionClosed as e:
@@ -53,24 +58,20 @@ def catch_closed(func):
return decorator
class Mixin(ForwardMixin):
class Mixin(WebSocketClientMixin):
"""Websockets Mixin"""
@property
@overrides(ForwardMixin)
@override
def type(self) -> str:
return "websockets"
@overrides(ForwardMixin)
async def request(self, setup: Request) -> Response:
return await super(Mixin, self).request(setup)
@overrides(ForwardMixin)
@override
@asynccontextmanager
async def websocket(self, setup: Request) -> AsyncGenerator["WebSocket", None]:
connection = Connect(
str(setup.url),
extra_headers=setup.headers.items(),
extra_headers={**setup.headers, **setup.cookies.as_header(setup)},
open_timeout=setup.timeout,
)
async with connection as ws:
@@ -80,31 +81,30 @@ class Mixin(ForwardMixin):
class WebSocket(BaseWebSocket):
"""Websockets WebSocket Wrapper"""
@overrides(BaseWebSocket)
@override
def __init__(self, *, request: Request, websocket: WebSocketClientProtocol):
super().__init__(request=request)
self.websocket = websocket
@property
@overrides(BaseWebSocket)
@override
def closed(self) -> bool:
return self.websocket.closed
@overrides(BaseWebSocket)
@override
async def accept(self):
raise NotImplementedError
@overrides(BaseWebSocket)
@override
async def close(self, code: int = 1000, reason: str = ""):
await self.websocket.close(code, reason)
@overrides(BaseWebSocket)
@override
@catch_closed
async def receive(self) -> Union[str, bytes]:
msg = await self.websocket.recv()
return msg
return await self.websocket.recv()
@overrides(BaseWebSocket)
@override
@catch_closed
async def receive_text(self) -> str:
msg = await self.websocket.recv()
@@ -112,7 +112,7 @@ class WebSocket(BaseWebSocket):
raise TypeError("WebSocket received unexpected frame type: bytes")
return msg
@overrides(BaseWebSocket)
@override
@catch_closed
async def receive_bytes(self) -> bytes:
msg = await self.websocket.recv()
@@ -120,14 +120,20 @@ class WebSocket(BaseWebSocket):
raise TypeError("WebSocket received unexpected frame type: str")
return msg
@overrides(BaseWebSocket)
@override
async def send_text(self, data: str) -> None:
await self.websocket.send(data)
@overrides(BaseWebSocket)
@override
async def send_bytes(self, data: bytes) -> None:
await self.websocket.send(data)
Driver: Type[ForwardDriver] = combine_driver(BlockDriver, Mixin) # type: ignore
"""Websockets Driver"""
if TYPE_CHECKING:
class Driver(Mixin, NoneDriver):
...
else:
Driver = combine_driver(NoneDriver, Mixin)
"""Websockets Driver"""

View File

@@ -37,12 +37,15 @@ from pydantic.fields import ModelField
class NoneBotException(Exception):
"""所有 NoneBot 发生的异常基类。"""
def __str__(self) -> str:
return self.__repr__()
# Rule Exception
class ParserExit(NoneBotException):
"""{ref}`nonebot.rule.shell_command` 处理消息失败时返回的异常"""
"""{ref}`nonebot.rule.shell_command` 处理消息失败时返回的异常"""
def __init__(self, status: int = 0, message: Optional[str] = None):
def __init__(self, status: int = 0, message: Optional[str] = None) -> None:
self.status = status
self.message = message
@@ -53,9 +56,6 @@ class ParserExit(NoneBotException):
+ ")"
)
def __str__(self) -> str:
return self.__repr__()
# Processor Exception
class ProcessException(NoneBotException):
@@ -69,15 +69,12 @@ class IgnoredException(ProcessException):
reason: 忽略事件的原因
"""
def __init__(self, reason: Any):
def __init__(self, reason: Any) -> None:
self.reason: Any = reason
def __repr__(self) -> str:
return f"IgnoredException(reason={self.reason!r})"
def __str__(self) -> str:
return self.__repr__()
class SkippedException(ProcessException):
"""指示 NoneBot 立即结束当前 `Dependent` 的运行。
@@ -99,7 +96,7 @@ class SkippedException(ProcessException):
class TypeMisMatch(SkippedException):
"""当前 `Handler` 的参数类型不匹配。"""
def __init__(self, param: ModelField, value: Any):
def __init__(self, param: ModelField, value: Any) -> None:
self.param: ModelField = param
self.value: Any = value
@@ -109,12 +106,10 @@ class TypeMisMatch(SkippedException):
f"type={self.param._type_display()}, value={self.value!r}>"
)
def __str__(self) -> str:
return self.__repr__()
class MockApiException(ProcessException):
"""指示 NoneBot 阻止本次 API 调用或修改本次调用返回值,并返回自定义内容。可由 api hook 抛出。
"""指示 NoneBot 阻止本次 API 调用或修改本次调用返回值,并返回自定义内容。
可由 api hook 抛出。
参数:
result: 返回的内容
@@ -126,9 +121,6 @@ class MockApiException(ProcessException):
def __repr__(self) -> str:
return f"MockApiException(result={self.result!r})"
def __str__(self) -> str:
return self.__repr__()
class StopPropagation(ProcessException):
"""指示 NoneBot 终止事件向下层传播。
@@ -153,7 +145,8 @@ class MatcherException(NoneBotException):
class PausedException(MatcherException):
"""指示 NoneBot 结束当前 `Handler` 并等待下一条消息后继续下一个 `Handler`。可用于用户输入新信息。
"""指示 NoneBot 结束当前 `Handler` 并等待下一条消息后继续下一个 `Handler`。
可用于用户输入新信息。
可以在 `Handler` 中通过 {ref}`nonebot.matcher.Matcher.pause` 抛出。
@@ -167,7 +160,8 @@ class PausedException(MatcherException):
class RejectedException(MatcherException):
"""指示 NoneBot 结束当前 `Handler` 并等待下一条消息后重新运行当前 `Handler`。可用于用户重新输入。
"""指示 NoneBot 结束当前 `Handler` 并等待下一条消息后重新运行当前 `Handler`。
可用于用户重新输入。
可以在 `Handler` 中通过 {ref}`nonebot.matcher.Matcher.reject` 抛出。
@@ -196,7 +190,7 @@ class FinishedException(MatcherException):
# Adapter Exceptions
class AdapterException(NoneBotException):
"""代表 `Adapter` 抛出的异常,所有的 `Adapter` 都要在内部继承自这个 `Exception`
"""代表 `Adapter` 抛出的异常,所有的 `Adapter` 都要在内部继承自这个 `Exception`
参数:
adapter_name: 标识 adapter
@@ -219,7 +213,9 @@ class ApiNotAvailable(AdapterException):
class NetworkError(AdapterException):
"""在网络出现问题时抛出,如: API 请求地址不正确, API 请求无返回或返回状态非正常等。"""
"""在网络出现问题时抛出,
如: API 请求地址不正确, API 请求无返回或返回状态非正常等。
"""
class ActionFailed(AdapterException):
@@ -228,13 +224,13 @@ class ActionFailed(AdapterException):
# Driver Exceptions
class DriverException(NoneBotException):
"""`Driver` 抛出的异常基类"""
"""`Driver` 抛出的异常基类"""
class WebSocketClosed(DriverException):
"""WebSocket 连接已关闭"""
"""WebSocket 连接已关闭"""
def __init__(self, code: int, reason: Optional[str] = None):
def __init__(self, code: int, reason: Optional[str] = None) -> None:
self.code = code
self.reason = reason
@@ -244,6 +240,3 @@ class WebSocketClosed(DriverException):
+ (f", reason={self.reason!r}" if self.reason else "")
+ ")"
)
def __str__(self) -> str:
return self.__repr__()

View File

@@ -7,10 +7,11 @@ from nonebot.internal.driver import (
Driver,
Request,
Response,
ASGIMixin,
WebSocket,
ForwardDriver,
ReverseDriver,
HTTPClientMixin,
HTTPServerSetup,
WebSocketClientMixin,
WebSocketServerSetup,
)
@@ -66,31 +67,32 @@ class Adapter(abc.ABC):
参数:
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.bots.pop(bot.self_id, None)
def setup_http_server(self, setup: HTTPServerSetup):
"""设置一个 HTTP 服务器路由配置"""
if not isinstance(self.driver, ReverseDriver):
if not isinstance(self.driver, ASGIMixin):
raise TypeError("Current driver does not support http server")
self.driver.setup_http_server(setup)
def setup_websocket_server(self, setup: WebSocketServerSetup):
"""设置一个 WebSocket 服务器路由配置"""
if not isinstance(self.driver, ReverseDriver):
if not isinstance(self.driver, ASGIMixin):
raise TypeError("Current driver does not support websocket server")
self.driver.setup_websocket_server(setup)
async def request(self, setup: Request) -> Response:
"""进行一个 HTTP 客户端请求"""
if not isinstance(self.driver, ForwardDriver):
if not isinstance(self.driver, HTTPClientMixin):
raise TypeError("Current driver does not support http client")
return await self.driver.request(setup)
@asynccontextmanager
async def websocket(self, setup: Request) -> AsyncGenerator[WebSocket, None]:
"""建立一个 WebSocket 客户端连接请求"""
if not isinstance(self.driver, ForwardDriver):
if not isinstance(self.driver, WebSocketClientMixin):
raise TypeError("Current driver does not support websocket client")
async with self.driver.websocket(setup) as ws:
yield ws

View File

@@ -44,10 +44,11 @@ class Event(abc.ABC, BaseModel):
def get_log_string(self) -> str:
"""获取事件日志信息的方法。
通常你不需要修改这个方法,只有当希望 NoneBot 隐藏该事件日志时,可以抛出 `NoLogException` 异常。
通常你不需要修改这个方法,只有当希望 NoneBot 隐藏该事件日志时,
可以抛出 `NoLogException` 异常。
异常:
NoLogException
NoLogException: 希望 NoneBot 隐藏该事件日志
"""
return f"[{self.get_event_name()}]: {self.get_event_description()}"
@@ -58,7 +59,9 @@ class Event(abc.ABC, BaseModel):
@abc.abstractmethod
def get_session_id(self) -> str:
"""获取会话 id 的方法,用于判断当前事件属于哪一个会话,通常是用户 id、群组 id 组合。"""
"""获取会话 id 的方法,用于判断当前事件属于哪一个会话,
通常是用户 id、群组 id 组合。
"""
raise NotImplementedError
@abc.abstractmethod

View File

@@ -1,5 +1,6 @@
import abc
from copy import deepcopy
from typing_extensions import Self
from dataclasses import field, asdict, dataclass
from typing import (
Any,
@@ -12,6 +13,7 @@ from typing import (
TypeVar,
Iterable,
Optional,
SupportsIndex,
overload,
)
@@ -19,7 +21,6 @@ from pydantic import parse_obj_as
from .template import MessageTemplate
T = TypeVar("T")
TMS = TypeVar("TMS", bound="MessageSegment")
TM = TypeVar("TM", bound="Message")
@@ -47,7 +48,7 @@ class MessageSegment(abc.ABC, Generic[TM]):
def __len__(self) -> int:
return len(str(self))
def __ne__(self: T, other: T) -> bool:
def __ne__(self, other: Self) -> bool:
return not self == other
def __add__(self: TMS, other: Union[str, TMS, Iterable[TMS]]) -> TM:
@@ -61,7 +62,7 @@ class MessageSegment(abc.ABC, Generic[TM]):
yield cls._validate
@classmethod
def _validate(cls, value):
def _validate(cls, value) -> Self:
if isinstance(value, cls):
return value
if not isinstance(value, dict):
@@ -84,7 +85,10 @@ class MessageSegment(abc.ABC, Generic[TM]):
def items(self):
return asdict(self).items()
def copy(self: T) -> T:
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
@@ -94,7 +98,7 @@ class MessageSegment(abc.ABC, Generic[TM]):
class Message(List[TMS], abc.ABC):
"""消息数组
"""消息序列
参数:
message: 消息内容
@@ -117,12 +121,12 @@ class Message(List[TMS], abc.ABC):
self.extend(self._construct(message)) # pragma: no cover
@classmethod
def template(cls: Type[TM], format_string: Union[str, TM]) -> MessageTemplate[TM]:
def template(cls, format_string: Union[str, TM]) -> MessageTemplate[Self]:
"""创建消息模板。
用法和 `str.format` 大致相同, 但是可以输出消息对象, 并且支持以 `Message` 对象作为消息模板
并且提供了拓展的格式化控制符, 可以用适用于该消息类型的 `MessageSegment` 工厂方法创建消息
用法和 `str.format` 大致相同支持以 `Message` 对象作为消息模板并输出消息对象。
并且提供了拓展的格式化控制符,
可以通过该消息类型的 `MessageSegment` 工厂方法创建消息
参数:
format_string: 格式化模板
@@ -146,7 +150,7 @@ class Message(List[TMS], abc.ABC):
yield cls._validate
@classmethod
def _validate(cls, value):
def _validate(cls, value) -> Self:
if isinstance(value, cls):
return value
elif isinstance(value, Message):
@@ -169,16 +173,16 @@ class Message(List[TMS], abc.ABC):
"""构造消息数组"""
raise NotImplementedError
def __add__(self: TM, other: Union[str, TMS, Iterable[TMS]]) -> TM:
def __add__(self, other: Union[str, TMS, Iterable[TMS]]) -> Self:
result = self.copy()
result += other
return result
def __radd__(self: TM, other: Union[str, TMS, Iterable[TMS]]) -> TM:
def __radd__(self, other: Union[str, TMS, Iterable[TMS]]) -> Self:
result = self.__class__(other)
return result + self
def __iadd__(self: TM, other: Union[str, TMS, Iterable[TMS]]) -> TM:
def __iadd__(self, other: Union[str, TMS, Iterable[TMS]]) -> Self:
if isinstance(other, str):
self.extend(self._construct(other))
elif isinstance(other, MessageSegment):
@@ -190,57 +194,62 @@ class Message(List[TMS], abc.ABC):
return self
@overload
def __getitem__(self: TM, __args: str) -> TM:
"""
def __getitem__(self, args: str) -> Self:
"""获取仅包含指定消息段类型的消息
参数:
__args: 消息段类型
args: 消息段类型
返回:
所有类型为 `__args` 的消息段
所有类型为 `args` 的消息段
"""
@overload
def __getitem__(self, __args: Tuple[str, int]) -> TMS:
"""
def __getitem__(self, args: Tuple[str, int]) -> TMS:
"""索引指定类型的消息段
参数:
__args: 消息段类型和索引
args: 消息段类型和索引
返回:
类型为 `__args[0]` 的消息段第 `__args[1]` 个
类型为 `args[0]` 的消息段第 `args[1]` 个
"""
@overload
def __getitem__(self: TM, __args: Tuple[str, slice]) -> TM:
"""
def __getitem__(self, args: Tuple[str, slice]) -> Self:
"""切片指定类型的消息段
参数:
__args: 消息段类型和切片
args: 消息段类型和切片
返回:
类型为 `__args[0]` 的消息段切片 `__args[1]`
类型为 `args[0]` 的消息段切片 `args[1]`
"""
@overload
def __getitem__(self, __args: int) -> TMS:
"""
def __getitem__(self, args: int) -> TMS:
"""索引消息段
参数:
__args: 索引
args: 索引
返回:
第 `__args` 个消息段
第 `args` 个消息段
"""
@overload
def __getitem__(self: TM, __args: slice) -> TM:
"""
def __getitem__(self, args: slice) -> Self:
"""切片消息段
参数:
__args: 切片
args: 切片
返回:
消息切片 `__args`
消息切片 `args`
"""
def __getitem__(
self: TM,
self,
args: Union[
str,
Tuple[str, int],
@@ -248,7 +257,7 @@ class Message(List[TMS], abc.ABC):
int,
slice,
],
) -> Union[TMS, TM]:
) -> 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)
@@ -263,15 +272,52 @@ class Message(List[TMS], abc.ABC):
else:
raise ValueError("Incorrect arguments to slice") # pragma: no cover
def index(self, value: Union[TMS, str], *args) -> int:
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} is not in message")
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: TM, type_: str, count: Optional[int] = None) -> TM:
def get(self, type_: str, count: Optional[int] = None) -> Self:
"""获取指定类型的消息段
参数:
type_: 消息段类型
count: 获取个数
返回:
构建的新消息
"""
if count is None:
return self[type_]
@@ -286,9 +332,30 @@ class Message(List[TMS], abc.ABC):
return filtered
def count(self, value: Union[TMS, str]) -> int:
"""计算指定消息段的个数
参数:
value: 消息段或消息段类型
返回:
个数
"""
return len(self[value]) if isinstance(value, str) else super().count(value)
def append(self: TM, obj: Union[str, TMS]) -> TM:
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:
"""添加一个消息段到消息数组末尾。
参数:
@@ -302,7 +369,7 @@ class Message(List[TMS], abc.ABC):
raise ValueError(f"Unexpected type: {type(obj)} {obj}") # pragma: no cover
return self
def extend(self: TM, obj: Union[TM, Iterable[TMS]]) -> TM:
def extend(self, obj: Union[Self, Iterable[TMS]]) -> Self:
"""拼接一个消息数组或多个消息段到消息数组末尾。
参数:
@@ -312,18 +379,52 @@ class Message(List[TMS], abc.ABC):
self.append(segment)
return self
def copy(self: TM) -> TM:
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())
__autodoc__ = {
"MessageSegment.__str__": True,
"MessageSegment.__add__": True,
"Message.__getitem__": True,
"Message._construct": True,
}

View File

@@ -1,5 +1,6 @@
import functools
from string import Formatter
from typing_extensions import TypeAlias
from typing import (
TYPE_CHECKING,
Any,
@@ -25,7 +26,7 @@ if TYPE_CHECKING:
TM = TypeVar("TM", bound="Message")
TF = TypeVar("TF", str, "Message")
FormatSpecFunc = Callable[[Any], str]
FormatSpecFunc: TypeAlias = Callable[[Any], str]
FormatSpecFunc_T = TypeVar("FormatSpecFunc_T", bound=FormatSpecFunc)
@@ -49,11 +50,11 @@ class MessageTemplate(Formatter, Generic[TF]):
) -> None:
...
def __init__( # type:ignore
self, template, factory=str
) -> None: # TODO: fix type hint here
self.template: TF = template
self.factory: Type[TF] = factory
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
self.format_specs: Dict[str, FormatSpecFunc] = {}
def __repr__(self) -> str:
@@ -98,7 +99,7 @@ class MessageTemplate(Formatter, Generic[TF]):
else:
raise TypeError("template must be a string or instance of Message!")
self.check_unused_args(list(used_args), args, kwargs)
self.check_unused_args(used_args, args, kwargs)
return cast(TF, full_message)
def vformat(
@@ -119,10 +120,9 @@ class MessageTemplate(Formatter, Generic[TF]):
) -> Tuple[TF, int]:
results: List[Any] = [self.factory()]
for (literal_text, field_name, format_spec, conversion) in self.parse(
for literal_text, field_name, format_spec, conversion in self.parse(
format_string
):
# output the literal text
if literal_text:
results.append(literal_text)

View File

@@ -1,4 +1,5 @@
from .model import URL as URL
from .driver import Mixin as Mixin
from .model import RawURL as RawURL
from .driver import Driver as Driver
from .model import Cookies as Cookies
@@ -8,6 +9,7 @@ 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 .driver import ASGIMixin as ASGIMixin
from .model import FilesTypes as FilesTypes
from .model import QueryTypes as QueryTypes
from .model import CookieTypes as CookieTypes
@@ -17,9 +19,12 @@ 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 .driver import ReverseMixin as ReverseMixin
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 .driver import HTTPClientMixin as HTTPClientMixin
from .model import WebSocketServerSetup as WebSocketServerSetup
from .driver import WebSocketClientMixin as WebSocketClientMixin

View File

@@ -1,15 +1,31 @@
import abc
import asyncio
from contextlib import asynccontextmanager
from typing import TYPE_CHECKING, Any, Set, Dict, Type, Callable, AsyncGenerator
from typing_extensions import TypeAlias
from contextlib import AsyncExitStack, asynccontextmanager
from typing import (
TYPE_CHECKING,
Any,
Set,
Dict,
Type,
Union,
TypeVar,
Callable,
AsyncGenerator,
overload,
)
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.typing import T_BotConnectionHook, T_BotDisconnectionHook
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
@@ -17,11 +33,15 @@ if TYPE_CHECKING:
from nonebot.internal.adapter import Bot, Adapter
D = TypeVar("D", bound="Driver")
BOT_HOOK_PARAMS = [DependParam, BotParam, DefaultParam]
class Driver(abc.ABC):
"""Driver 基类。
"""驱动器基类。
驱动器控制框架的启动和停止,适配器的注册,以及机器人生命周期管理。
参数:
env: 包含环境信息的 Env 对象
@@ -41,6 +61,7 @@ class Driver(abc.ABC):
self.config: Config = config
"""全局配置对象"""
self._bots: Dict[str, "Bot"] = {}
self._bot_tasks: Set[asyncio.Task] = set()
def __repr__(self) -> str:
return (
@@ -85,13 +106,13 @@ class Driver(abc.ABC):
@abc.abstractmethod
def run(self, *args, **kwargs):
"""
启动驱动框架
"""
"""启动驱动框架"""
logger.opt(colors=True).debug(
f"<g>Loaded adapters: {escape_tag(', '.join(self._adapters))}</g>"
)
self.on_shutdown(self._cleanup)
@abc.abstractmethod
def on_startup(self, func: Callable) -> Callable:
"""注册一个在驱动器启动时执行的函数"""
@@ -135,22 +156,28 @@ class Driver(abc.ABC):
self._bots[bot.self_id] = bot
async def _run_hook(bot: "Bot") -> None:
coros = list(
map(
lambda x: run_coro_with_catch(x(bot=bot), (SkippedException,)),
self._bot_connection_hook,
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,),
)
)
if coros:
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>"
"<r><bg #f8bbd0>"
"Error when running WebSocketConnection hook. "
"Running cancelled!"
"</bg #f8bbd0></r>"
)
asyncio.create_task(_run_hook(bot))
task = asyncio.create_task(_run_hook(bot))
task.add_done_callback(self._bot_tasks.discard)
self._bot_tasks.add(task)
def _bot_disconnect(self, bot: "Bot") -> None:
"""在连接断开后,调用该函数来注销 bot 对象"""
@@ -158,38 +185,68 @@ class Driver(abc.ABC):
del self._bots[bot.self_id]
async def _run_hook(bot: "Bot") -> None:
coros = list(
map(
lambda x: run_coro_with_catch(x(bot=bot), (SkippedException,)),
self._bot_disconnection_hook,
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,),
)
)
if coros:
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>"
"<r><bg #f8bbd0>"
"Error when running WebSocketDisConnection hook. "
"Running cancelled!"
"</bg #f8bbd0></r>"
)
asyncio.create_task(_run_hook(bot))
task = asyncio.create_task(_run_hook(bot))
task.add_done_callback(self._bot_tasks.discard)
self._bot_tasks.add(task)
async def _cleanup(self) -> None:
"""清理驱动器资源"""
if self._bot_tasks:
logger.opt(colors=True).debug(
"<y>Waiting for running bot connection hooks...</y>"
)
await asyncio.gather(*self._bot_tasks, return_exceptions=True)
class ForwardMixin(abc.ABC):
"""客户端混入基类。"""
class Mixin(abc.ABC):
"""可与其他驱动器共用的混入基类。"""
@property
@abc.abstractmethod
def type(self) -> str:
"""客户端驱动类型名称"""
"""混入驱动类型名称"""
raise NotImplementedError
class ForwardMixin(Mixin):
"""客户端混入基类。"""
class ReverseMixin(Mixin):
"""服务端混入基类。"""
class HTTPClientMixin(ForwardMixin):
"""HTTP 客户端混入基类。"""
@abc.abstractmethod
async def request(self, setup: Request) -> Response:
"""发送一个 HTTP 请求"""
raise NotImplementedError
class WebSocketClientMixin(ForwardMixin):
"""WebSocket 客户端混入基类。"""
@abc.abstractmethod
@asynccontextmanager
async def websocket(self, setup: Request) -> AsyncGenerator[WebSocket, None]:
@@ -198,12 +255,11 @@ class ForwardMixin(abc.ABC):
yield # used for static type checking's generator detection
class ForwardDriver(Driver, ForwardMixin):
"""客户端基类。将客户端框架封装,以满足适配器使用。"""
class ASGIMixin(ReverseMixin):
"""ASGI 服务端基类。
class ReverseDriver(Driver):
"""服务端基类。将后端框架封装,以满足适配器使用。"""
将后端框架封装,以满足适配器使用。
"""
@property
@abc.abstractmethod
@@ -228,22 +284,55 @@ class ReverseDriver(Driver):
raise NotImplementedError
def combine_driver(driver: Type[Driver], *mixins: Type[ForwardMixin]) -> Type[Driver]:
ForwardDriver: TypeAlias = ForwardMixin
"""支持客户端请求的驱动器。
**Deprecated**,请使用 {ref}`nonebot.drivers.ForwardMixin` 或其子类代替。
"""
ReverseDriver: TypeAlias = ReverseMixin
"""支持服务端请求的驱动器。
**Deprecated**,请使用 {ref}`nonebot.drivers.ReverseMixin` 或其子类代替。
"""
if TYPE_CHECKING:
class CombinedDriver(Driver, Mixin):
...
@overload
def combine_driver(driver: Type[D]) -> Type[D]:
...
@overload
def combine_driver(driver: Type[D], *mixins: Type[Mixin]) -> Type["CombinedDriver"]:
...
def combine_driver(
driver: Type[D], *mixins: Type[Mixin]
) -> Union[Type[D], Type["CombinedDriver"]]:
"""将一个驱动器和多个混入类合并。"""
# 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"
issubclass(m, Mixin) for m in mixins
), "`mixins` must be subclass of Mixin"
if not mixins:
return driver
def type_(self: ForwardDriver) -> str:
def type_(self: "CombinedDriver") -> str:
return (
driver.type.__get__(self)
+ "+"
+ "+".join(map(lambda x: x.type.__get__(self), mixins))
+ "+".join(x.type.__get__(self) for x in mixins)
)
return type("CombinedDriver", (*mixins, driver, ForwardDriver), {"type": property(type_)}) # type: ignore
return type(
"CombinedDriver", (*mixins, driver), {"type": property(type_)}
) # type: ignore

View File

@@ -1,6 +1,8 @@
import abc
import urllib.request
from enum import Enum
from dataclasses import dataclass
from typing_extensions import TypeAlias
from http.cookiejar import Cookie, CookieJar
from typing import (
IO,
@@ -20,28 +22,30 @@ from typing import (
from yarl import URL as URL
from multidict import CIMultiDict
RawURL = Tuple[bytes, bytes, Optional[int], bytes]
RawURL: TypeAlias = Tuple[bytes, bytes, Optional[int], bytes]
SimpleQuery = Union[str, int, float]
QueryVariable = Union[SimpleQuery, List[SimpleQuery]]
QueryTypes = Union[
SimpleQuery: TypeAlias = Union[str, int, float]
QueryVariable: TypeAlias = Union[SimpleQuery, List[SimpleQuery]]
QueryTypes: TypeAlias = Union[
None, str, Mapping[str, QueryVariable], List[Tuple[str, QueryVariable]]
]
HeaderTypes = Union[
HeaderTypes: TypeAlias = Union[
None,
CIMultiDict[str],
Dict[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]
DataTypes = Union[dict, None]
FileContent = Union[IO[bytes], bytes]
FileType = Tuple[Optional[str], FileContent, Optional[str]]
FileTypes = Union[
ContentTypes: TypeAlias = Union[str, bytes, None]
DataTypes: TypeAlias = Union[dict, None]
FileContent: TypeAlias = Union[IO[bytes], bytes]
FileType: TypeAlias = Tuple[Optional[str], FileContent, Optional[str]]
FileTypes: TypeAlias = Union[
# file (or bytes)
FileContent,
# (filename, file (or bytes))
@@ -49,7 +53,7 @@ FileTypes = Union[
# (filename, file (or bytes), content_type)
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):
@@ -105,12 +109,9 @@ class Request:
self.url: URL = url
# headers
self.headers: CIMultiDict[str]
if headers is not None:
self.headers = CIMultiDict(headers)
else:
self.headers = CIMultiDict()
self.headers: CIMultiDict[str] = (
CIMultiDict(headers) if headers is not None else CIMultiDict()
)
# cookies
self.cookies = Cookies(cookies)
@@ -124,7 +125,7 @@ class Request:
files_ = files.items() if isinstance(files, dict) else files
for name, file_info in files_:
if not isinstance(file_info, tuple):
self.files.append((name, (None, file_info, None)))
self.files.append((name, (name, file_info, None)))
elif len(file_info) == 2:
self.files.append((name, (file_info[0], file_info[1], None)))
else:
@@ -147,12 +148,9 @@ class Response:
self.status_code: int = status_code
# headers
self.headers: CIMultiDict[str]
if headers is not None:
self.headers = CIMultiDict(headers)
else:
self.headers = CIMultiDict()
self.headers: CIMultiDict[str] = (
CIMultiDict(headers) if headers is not None else CIMultiDict()
)
# body
self.content: ContentTypes = content
@@ -165,7 +163,6 @@ class Response:
class WebSocket(abc.ABC):
def __init__(self, *, request: Request):
# request
self.request: Request = request
def __repr__(self) -> str:
@@ -174,9 +171,7 @@ class WebSocket(abc.ABC):
@property
@abc.abstractmethod
def closed(self) -> bool:
"""
连接是否已经关闭
"""
"""连接是否已经关闭"""
raise NotImplementedError
@abc.abstractmethod
@@ -308,6 +303,11 @@ class Cookies(MutableMapping):
for cookie in cookies.jar:
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:
return self.set(name, value)
@@ -333,6 +333,20 @@ class Cookies(MutableMapping):
)
return f"{self.__class__.__name__}({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:

View File

@@ -6,6 +6,7 @@ matchers = MatcherManager()
from .matcher import Matcher as Matcher
from .matcher import current_bot as current_bot
from .matcher import MatcherSource as MatcherSource
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

@@ -1,6 +1,5 @@
from typing import (
TYPE_CHECKING,
Any,
List,
Type,
Tuple,
@@ -53,7 +52,7 @@ class MatcherManager(MutableMapping[int, List[Type["Matcher"]]]):
def __delitem__(self, key: int) -> None:
del self.provider[key]
def __eq__(self, other: Any) -> bool:
def __eq__(self, other: object) -> bool:
return isinstance(other, MatcherManager) and self.provider == other.provider
def keys(self) -> KeysView[int]:

View File

@@ -1,5 +1,11 @@
import sys
import inspect
import warnings
from pathlib import Path
from types import ModuleType
from dataclasses import dataclass
from contextvars import ContextVar
from typing_extensions import Self
from datetime import datetime, timedelta
from contextlib import AsyncExitStack, contextmanager
from typing import (
@@ -7,9 +13,11 @@ from typing import (
Any,
List,
Type,
Tuple,
Union,
TypeVar,
Callable,
ClassVar,
Iterable,
NoReturn,
Optional,
@@ -18,8 +26,9 @@ from typing import (
from nonebot.log import logger
from nonebot.internal.rule import Rule
from nonebot.dependencies import Dependent
from nonebot.internal.permission import USER, User, Permission
from nonebot.utils import classproperty
from nonebot.dependencies import Param, Dependent
from nonebot.internal.permission import User, Permission
from nonebot.internal.adapter import (
Bot,
Event,
@@ -27,6 +36,13 @@ from nonebot.internal.adapter import (
MessageSegment,
MessageTemplate,
)
from nonebot.typing import (
T_State,
T_Handler,
T_TypeUpdater,
T_DependencyCache,
T_PermissionUpdater,
)
from nonebot.consts import (
ARG_KEY,
RECEIVE_KEY,
@@ -34,14 +50,6 @@ from nonebot.consts import (
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 (
PausedException,
StopPropagation,
@@ -73,15 +81,51 @@ current_matcher: ContextVar["Matcher"] = ContextVar("current_matcher")
current_handler: ContextVar[Dependent] = ContextVar("current_handler")
@dataclass
class MatcherSource:
"""Matcher 源代码上下文信息"""
plugin_name: Optional[str] = None
"""事件响应器所在插件名称"""
module_name: Optional[str] = None
"""事件响应器所在插件模块的路径名"""
lineno: Optional[int] = None
"""事件响应器所在行号"""
@property
def plugin(self) -> Optional["Plugin"]:
"""事件响应器所在插件"""
from nonebot.plugin import get_plugin
if self.plugin_name is not None:
return get_plugin(self.plugin_name)
@property
def module(self) -> Optional[ModuleType]:
if self.module_name is not None:
return sys.modules.get(self.module_name)
@property
def file(self) -> Optional[Path]:
if self.module is not None and (file := inspect.getsourcefile(self.module)):
return Path(file).absolute()
class MatcherMeta(type):
if TYPE_CHECKING:
module_name: Optional[str]
type: str
_source: Optional[MatcherSource]
module_name: Optional[str]
def __repr__(self) -> str:
return (
f"Matcher(type={self.type!r}"
f"{self.__name__}(type={self.type!r}"
+ (f", module={self.module_name}" if self.module_name else "")
+ (
f", lineno={self._source.lineno}"
if self._source and self._source.lineno is not None
else ""
)
+ ")"
)
@@ -89,41 +133,34 @@ class MatcherMeta(type):
class Matcher(metaclass=MatcherMeta):
"""事件响应器类"""
plugin: Optional["Plugin"] = None
"""事件响应器所在插件"""
module: Optional[ModuleType] = None
"""事件响应器所在插件模块"""
plugin_name: Optional[str] = None
"""事件响应器所在插件名"""
module_name: Optional[str] = None
"""事件响应器所在点分割插件模块路径"""
_source: ClassVar[Optional[MatcherSource]] = None
type: str = ""
type: ClassVar[str] = ""
"""事件响应器类型"""
rule: Rule = Rule()
rule: ClassVar[Rule] = Rule()
"""事件响应器匹配规则"""
permission: Permission = Permission()
permission: ClassVar[Permission] = Permission()
"""事件响应器触发权限"""
handlers: List[Dependent[Any]] = []
"""事件响应器拥有的事件处理函数列表"""
priority: int = 1
priority: ClassVar[int] = 1
"""事件响应器优先级"""
block: bool = False
"""事件响应器是否阻止事件传播"""
temp: bool = False
temp: ClassVar[bool] = False
"""事件响应器是否为临时"""
expire_time: Optional[datetime] = None
expire_time: ClassVar[Optional[datetime]] = None
"""事件响应器过期时间点"""
_default_state: T_State = {}
_default_state: ClassVar[T_State] = {}
"""事件响应器默认状态"""
_default_type_updater: Optional[Dependent[str]] = None
_default_type_updater: ClassVar[Optional[Dependent[str]]] = None
"""事件响应器类型更新函数"""
_default_permission_updater: Optional[Dependent[Permission]] = None
_default_permission_updater: ClassVar[Optional[Dependent[Permission]]] = None
"""事件响应器权限更新函数"""
HANDLER_PARAM_TYPES = (
HANDLER_PARAM_TYPES: ClassVar[Tuple[Type[Param], ...]] = (
DependParam,
BotParam,
EventParam,
@@ -139,8 +176,13 @@ class Matcher(metaclass=MatcherMeta):
def __repr__(self) -> str:
return (
f"Matcher(type={self.type!r}"
f"{self.__class__.__name__}(type={self.type!r}"
+ (f", module={self.module_name}" if self.module_name else "")
+ (
f", lineno={self._source.lineno}"
if self._source and self._source.lineno is not None
else ""
)
+ ")"
)
@@ -157,13 +199,14 @@ class Matcher(metaclass=MatcherMeta):
*,
plugin: Optional["Plugin"] = None,
module: Optional[ModuleType] = None,
source: Optional[MatcherSource] = 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["Matcher"]:
) -> Type[Self]:
"""
创建一个新的事件响应器,并存储至 `matchers <#matchers>`_
@@ -175,22 +218,47 @@ class Matcher(metaclass=MatcherMeta):
temp: 是否为临时事件响应器,即触发一次后删除
priority: 响应优先级
block: 是否阻止事件向更低优先级的响应器传播
plugin: 事件响应器所在插件
module: 事件响应器所在模块
default_state: 默认状态 `state`
plugin: **Deprecated.** 事件响应器所在插件
module: **Deprecated.** 事件响应器所在模块
source: 事件响应器源代码上下文信息
expire_time: 事件响应器最终有效时间点,过时即被删除
default_state: 默认状态 `state`
default_type_updater: 默认事件类型更新函数
default_permission_updater: 默认会话权限更新函数
返回:
Type[Matcher]: 新的事件响应器类
"""
if plugin is not None:
warnings.warn(
(
"Pass `plugin` context info to create Matcher is deprecated. "
"Use `source` instead."
),
DeprecationWarning,
)
if module is not None:
warnings.warn(
(
"Pass `module` context info to create Matcher is deprecated. "
"Use `source` instead."
),
DeprecationWarning,
)
source = source or (
MatcherSource(
plugin_name=plugin and plugin.name,
module_name=module and module.__name__,
)
if plugin is not None or module is not None
else None
)
NewMatcher = type(
"Matcher",
(Matcher,),
cls.__name__,
(cls,),
{
"plugin": plugin,
"module": module,
"plugin_name": plugin and plugin.name,
"module_name": module and module.__name__,
"_source": source,
"type": type_,
"rule": rule or Rule(),
"permission": permission or Permission(),
@@ -247,6 +315,31 @@ class Matcher(metaclass=MatcherMeta):
return NewMatcher
@classmethod
def destroy(cls) -> None:
"""销毁当前的事件响应器"""
matchers[cls.priority].remove(cls)
@classproperty
def plugin(cls) -> Optional["Plugin"]:
"""事件响应器所在插件"""
return cls._source and cls._source.plugin
@classproperty
def module(cls) -> Optional[ModuleType]:
"""事件响应器所在插件模块"""
return cls._source and cls._source.module
@classproperty
def plugin_name(cls) -> Optional[str]:
"""事件响应器所在插件名"""
return cls._source and cls._source.plugin_name
@classproperty
def module_name(cls) -> Optional[str]:
"""事件响应器所在插件模块路径"""
return cls._source and cls._source.module_name
@classmethod
async def check_perm(
cls,
@@ -369,10 +462,9 @@ class Matcher(metaclass=MatcherMeta):
return
await matcher.reject()
_parameterless = (Depends(_receive), *(parameterless or tuple()))
_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(
@@ -400,7 +492,8 @@ class Matcher(metaclass=MatcherMeta):
) -> Callable[[T_Handler], T_Handler]:
"""装饰一个函数来指示 NoneBot 获取一个参数 `key`
当要获取的 `key` 不存在时接收用户新的一条消息再运行该函数,如果 `key` 已存在则直接继续运行
当要获取的 `key` 不存在时接收用户新的一条消息再运行该函数,
如果 `key` 已存在则直接继续运行
参数:
key: 参数名
@@ -417,10 +510,9 @@ class Matcher(metaclass=MatcherMeta):
return
await matcher.reject(prompt)
_parameterless = (Depends(_key_getter), *(parameterless or tuple()))
_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(
@@ -449,7 +541,8 @@ class Matcher(metaclass=MatcherMeta):
参数:
message: 消息内容
kwargs: {ref}`nonebot.adapters.Bot.send` 的参数,请参考对应 adapter 的 bot 对象 api
kwargs: {ref}`nonebot.adapters.Bot.send` 的参数,
请参考对应 adapter 的 bot 对象 api
"""
bot = current_bot.get()
event = current_event.get()
@@ -470,7 +563,8 @@ class Matcher(metaclass=MatcherMeta):
参数:
message: 消息内容
kwargs: {ref}`nonebot.adapters.Bot.send` 的参数,请参考对应 adapter 的 bot 对象 api
kwargs: {ref}`nonebot.adapters.Bot.send` 的参数,
请参考对应 adapter 的 bot 对象 api
"""
if message is not None:
await cls.send(message, **kwargs)
@@ -486,7 +580,8 @@ class Matcher(metaclass=MatcherMeta):
参数:
prompt: 消息内容
kwargs: {ref}`nonebot.adapters.Bot.send` 的参数,请参考对应 adapter 的 bot 对象 api
kwargs: {ref}`nonebot.adapters.Bot.send` 的参数,
请参考对应 adapter 的 bot 对象 api
"""
if prompt is not None:
await cls.send(prompt, **kwargs)
@@ -503,7 +598,8 @@ class Matcher(metaclass=MatcherMeta):
参数:
prompt: 消息内容
kwargs: {ref}`nonebot.adapters.Bot.send` 的参数,请参考对应 adapter 的 bot 对象 api
kwargs: {ref}`nonebot.adapters.Bot.send` 的参数,
请参考对应 adapter 的 bot 对象 api
"""
if prompt is not None:
await cls.send(prompt, **kwargs)
@@ -522,7 +618,8 @@ class Matcher(metaclass=MatcherMeta):
参数:
key: 参数名
prompt: 消息内容
kwargs: {ref}`nonebot.adapters.Bot.send` 的参数,请参考对应 adapter 的 bot 对象 api
kwargs: {ref}`nonebot.adapters.Bot.send` 的参数,
请参考对应 adapter 的 bot 对象 api
"""
matcher = current_matcher.get()
matcher.set_target(ARG_KEY.format(key=key))
@@ -543,7 +640,8 @@ class Matcher(metaclass=MatcherMeta):
参数:
id: 消息 id
prompt: 消息内容
kwargs: {ref}`nonebot.adapters.Bot.send` 的参数,请参考对应 adapter 的 bot 对象 api
kwargs: {ref}`nonebot.adapters.Bot.send` 的参数,
请参考对应 adapter 的 bot 对象 api
"""
matcher = current_matcher.get()
matcher.set_target(RECEIVE_KEY.format(id=id))
@@ -640,23 +738,44 @@ class Matcher(metaclass=MatcherMeta):
"""阻止事件传播"""
self.block = True
async def update_type(self, bot: Bot, event: Event) -> str:
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)
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) -> Permission:
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)
permission = self.permission
if len(permission.checkers) == 1 and isinstance(
user_perm := tuple(permission.checkers)[0].call, User
):
permission = user_perm.perm
return USER(event.get_session_id(), perm=permission)
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()
@@ -728,10 +847,12 @@ class Matcher(metaclass=MatcherMeta):
except RejectedException:
await self.resolve_reject()
type_ = await self.update_type(bot, event)
permission = await self.update_permission(bot, event)
type_ = await self.update_type(bot, event, stack, dependency_cache)
permission = await self.update_permission(
bot, event, stack, dependency_cache
)
Matcher.new(
self.new(
type_,
Rule(),
permission,
@@ -739,18 +860,19 @@ class Matcher(metaclass=MatcherMeta):
temp=True,
priority=0,
block=True,
plugin=self.plugin,
module=self.module,
source=self.__class__._source,
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)
permission = await self.update_permission(bot, event)
type_ = await self.update_type(bot, event, stack, dependency_cache)
permission = await self.update_permission(
bot, event, stack, dependency_cache
)
Matcher.new(
self.new(
type_,
Rule(),
permission,
@@ -758,8 +880,7 @@ class Matcher(metaclass=MatcherMeta):
temp=True,
priority=0,
block=True,
plugin=self.plugin,
module=self.module,
source=self.__class__._source,
expire_time=bot.config.session_expire_timeout,
default_state=self.state,
default_type_updater=self.__class__._default_type_updater,

View File

@@ -1,9 +1,21 @@
import asyncio
import inspect
from typing_extensions import Self, Annotated, override
from contextlib import AsyncExitStack, contextmanager, asynccontextmanager
from typing import TYPE_CHECKING, Any, Type, Tuple, Literal, Callable, Optional, cast
from typing import (
TYPE_CHECKING,
Any,
Type,
Tuple,
Union,
Literal,
Callable,
Optional,
cast,
)
from pydantic.fields import Required, Undefined, ModelField
from pydantic.typing import get_args, get_origin
from pydantic.fields import Required, FieldInfo, Undefined, ModelField
from nonebot.dependencies.utils import check_field_type
from nonebot.dependencies import Param, Dependent, CustomConfig
@@ -22,6 +34,23 @@ if TYPE_CHECKING:
from nonebot.matcher import Matcher
from nonebot.adapters import Bot, Event
EXTRA_FIELD_INFO = (
"gt",
"lt",
"ge",
"le",
"multiple_of",
"allow_inf_nan",
"max_digits",
"decimal_places",
"min_items",
"max_items",
"unique_items",
"min_length",
"max_length",
"regex",
)
class DependsInner:
def __init__(
@@ -29,26 +58,31 @@ class DependsInner:
dependency: Optional[T_Handler] = None,
*,
use_cache: bool = True,
validate: Union[bool, FieldInfo] = False,
) -> None:
self.dependency = dependency
self.use_cache = use_cache
self.validate = validate
def __repr__(self) -> str:
dep = get_name(self.dependency)
cache = "" if self.use_cache else ", use_cache=False"
return f"DependsInner({dep}{cache})"
validate = f", validate={self.validate}" if self.validate else ""
return f"DependsInner({dep}{cache}{validate})"
def Depends(
dependency: Optional[T_Handler] = None,
*,
use_cache: bool = True,
validate: Union[bool, FieldInfo] = False,
) -> Any:
"""子依赖装饰器
参数:
dependency: 依赖函数。默认为参数的类型注释。
use_cache: 是否使用缓存。默认为 `True`。
validate: 是否使用 Pydantic 类型校验。默认为 `False`。
用法:
```python
@@ -61,39 +95,85 @@ def Depends(
finally:
...
async def handler(param_name: Any = Depends(depend_func), gen: Any = Depends(depend_gen_func)):
async def handler(
param_name: Any = Depends(depend_func),
gen: Any = Depends(depend_gen_func),
):
...
```
"""
return DependsInner(dependency, use_cache=use_cache)
return DependsInner(dependency, use_cache=use_cache, validate=validate)
class DependParam(Param):
"""子依赖参数"""
"""子依赖注入参数
本注入解析所有子依赖注入,然后将它们的返回值作为参数值传递给父依赖。
本注入应该具有最高优先级,因此应该在其他参数之前检查。
"""
def __repr__(self) -> str:
return f"Depends({self.extra['dependent']})"
@classmethod
def _from_field(
cls, sub_dependent: Dependent, use_cache: bool, validate: Union[bool, FieldInfo]
) -> Self:
kwargs = {}
if isinstance(validate, FieldInfo):
kwargs.update((k, getattr(validate, k)) for k in EXTRA_FIELD_INFO)
return cls(
Required,
validate=bool(validate),
**kwargs,
dependent=sub_dependent,
use_cache=use_cache,
)
@classmethod
@override
def _check_param(
cls, param: inspect.Parameter, allow_types: Tuple[Type[Param], ...]
) -> Optional["DependParam"]:
if isinstance(param.default, DependsInner):
) -> Optional[Self]:
type_annotation, depends_inner = param.annotation, None
# extract type annotation and dependency from Annotated
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
)
# param default value takes higher priority
depends_inner = (
param.default if isinstance(param.default, DependsInner) else depends_inner
)
# not a dependent
if depends_inner is None:
return
dependency: T_Handler
if param.default.dependency is None:
assert param.annotation is not param.empty, "Dependency cannot be empty"
dependency = param.annotation
# sub dependency is not specified, use type annotation
if depends_inner.dependency is None:
assert (
type_annotation is not inspect.Signature.empty
), "Dependency cannot be empty"
dependency = type_annotation
else:
dependency = param.default.dependency
dependency = depends_inner.dependency
# parse sub dependency
sub_dependent = Dependent[Any].parse(
call=dependency,
allow_types=allow_types,
)
return cls(
Required, use_cache=param.default.use_cache, dependent=sub_dependent
return cls._from_field(
sub_dependent, depends_inner.use_cache, depends_inner.validate
)
@classmethod
@override
def _check_parameterless(
cls, value: Any, allow_types: Tuple[Type[Param], ...]
) -> Optional["Param"]:
@@ -102,8 +182,9 @@ class DependParam(Param):
dependent = Dependent[Any].parse(
call=value.dependency, allow_types=allow_types
)
return cls(Required, use_cache=value.use_cache, dependent=dependent)
return cls._from_field(dependent, value.use_cache, value.validate)
@override
async def _solve(
self,
stack: Optional[AsyncExitStack] = None,
@@ -147,6 +228,7 @@ class DependParam(Param):
dependency_cache[call] = task
return await task
@override
async def _check(self, **kwargs: Any) -> None:
# run sub dependent pre-checkers
sub_dependent: Dependent = self.extra["dependent"]
@@ -154,7 +236,12 @@ class DependParam(Param):
class BotParam(Param):
"""{ref}`nonebot.adapters.Bot` 参数"""
"""{ref}`nonebot.adapters.Bot` 注入参数
本注入解析所有类型为且仅为 {ref}`nonebot.adapters.Bot` 及其子类或 `None` 的参数。
为保证兼容性,本注入还会解析名为 `bot` 且没有类型注解的参数。
"""
def __repr__(self) -> str:
return (
@@ -168,12 +255,13 @@ class BotParam(Param):
)
@classmethod
@override
def _check_param(
cls, param: inspect.Parameter, allow_types: Tuple[Type[Param], ...]
) -> Optional["BotParam"]:
) -> Optional[Self]:
from nonebot.adapters import Bot
if param.default == param.empty:
# 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:
@@ -186,19 +274,27 @@ class BotParam(Param):
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)
@override
async def _solve(self, bot: "Bot", **kwargs: Any) -> Any:
return bot
@override
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` 注入参数
本注入解析所有类型为且仅为 {ref}`nonebot.adapters.Event` 及其子类或 `None` 的参数。
为保证兼容性,本注入还会解析名为 `event` 且没有类型注解的参数。
"""
def __repr__(self) -> str:
return (
@@ -212,12 +308,13 @@ class EventParam(Param):
)
@classmethod
@override
def _check_param(
cls, param: inspect.Parameter, allow_types: Tuple[Type[Param], ...]
) -> Optional["EventParam"]:
) -> Optional[Self]:
from nonebot.adapters import Event
if param.default == param.empty:
# 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:
@@ -230,57 +327,92 @@ class EventParam(Param):
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)
@override
async def _solve(self, event: "Event", **kwargs: Any) -> Any:
return event
@override
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
@override
def _check_param(
cls, param: inspect.Parameter, allow_types: Tuple[Type[Param], ...]
) -> Optional["StateParam"]:
if param.default == param.empty:
) -> Optional[Self]:
# 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)
@override
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
@override
def _check_param(
cls, param: inspect.Parameter, allow_types: Tuple[Type[Param], ...]
) -> Optional["MatcherParam"]:
) -> Optional[Self]:
from nonebot.matcher import Matcher
if generic_check_issubclass(param.annotation, Matcher) or (
param.annotation == param.empty and param.name == "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)
@override
async def _solve(self, matcher: "Matcher", **kwargs: Any) -> Any:
return matcher
@override
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__(
@@ -294,37 +426,49 @@ class ArgInner:
def Arg(key: Optional[str] = None) -> Any:
"""`got` 的 Arg 参数消息"""
"""Arg 参数消息"""
return ArgInner(key, "message")
def ArgStr(key: Optional[str] = None) -> str:
"""`got` 的 Arg 参数消息文本"""
"""Arg 参数消息文本"""
return ArgInner(key, "str") # type: ignore
def ArgPlainText(key: Optional[str] = None) -> str:
"""`got` 的 Arg 参数消息纯文本"""
"""Arg 参数消息纯文本"""
return ArgInner(key, "plaintext") # type: ignore
class ArgParam(Param):
"""`got` 的 Arg 参数"""
"""Arg 注入参数
本注入解析事件响应器操作 `got` 所获取的参数。
可以通过 `Arg`、`ArgStr`、`ArgPlainText` 等函数参数 `key` 指定获取的参数,
留空则会根据参数名称获取。
"""
def __repr__(self) -> str:
return f"ArgParam(key={self.extra['key']!r}, type={self.extra['type']!r})"
@classmethod
@override
def _check_param(
cls, param: inspect.Parameter, allow_types: Tuple[Type[Param], ...]
) -> Optional["ArgParam"]:
) -> Optional[Self]:
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:
message = matcher.get_arg(self.extra["key"])
key: str = self.extra["key"]
message = matcher.get_arg(key)
if message is None:
return message
if self.extra["type"] == "message":
@@ -336,37 +480,53 @@ class ArgParam(Param):
class ExceptionParam(Param):
"""`run_postprocessor` 的异常参数"""
"""{ref}`nonebot.message.run_postprocessor` 的异常注入参数
本注入解析所有类型为 `Exception` 或 `None` 的参数。
为保证兼容性,本注入还会解析名为 `exception` 且没有类型注解的参数。
"""
def __repr__(self) -> str:
return "ExceptionParam()"
@classmethod
@override
def _check_param(
cls, param: inspect.Parameter, allow_types: Tuple[Type[Param], ...]
) -> Optional["ExceptionParam"]:
if generic_check_issubclass(param.annotation, Exception) or (
param.annotation == param.empty and param.name == "exception"
):
) -> Optional[Self]:
# 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)
@override
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
@override
def _check_param(
cls, param: inspect.Parameter, allow_types: Tuple[Type[Param], ...]
) -> Optional["DefaultParam"]:
) -> Optional[Self]:
if param.default != param.empty:
return cls(param.default)
@override
async def _solve(self, **kwargs: Any) -> Any:
return Undefined

View File

@@ -1,4 +1,5 @@
import asyncio
from typing_extensions import Self
from contextlib import AsyncExitStack
from typing import Set, Tuple, Union, NoReturn, Optional
@@ -57,7 +58,7 @@ class Permission:
stack: Optional[AsyncExitStack] = None,
dependency_cache: Optional[T_DependencyCache] = None,
) -> bool:
"""检查是否满足某个权限
"""检查是否满足某个权限
参数:
bot: Bot 对象
@@ -109,7 +110,7 @@ class Permission:
class User:
"""检查当前事件是否属于指定会话
"""检查当前事件是否属于指定会话
参数:
users: 会话 ID 元组
@@ -140,13 +141,47 @@ class User:
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(users, perm))
return Permission(User.from_permission(*users, perm=perm))

View File

@@ -2,7 +2,7 @@
NoneBot 使用 [`loguru`][loguru] 来记录日志信息。
自定义 logger 请参考 [自定义日志](https://v2.nonebot.dev/docs/tutorial/custom-logger)
自定义 logger 请参考 [自定义日志](https://nonebot.dev/docs/appendices/log)
以及 [`loguru`][loguru] 文档。
[loguru]: https://github.com/Delgan/loguru
@@ -54,7 +54,7 @@ class LoguruHandler(logging.Handler): # pragma: no cover
except ValueError:
level = record.levelno
frame, depth = logging.currentframe(), 2
frame, depth = sys._getframe(6), 6
while frame and frame.f_code.co_filename == logging.__file__:
frame = frame.f_back
depth += 1
@@ -88,5 +88,6 @@ logger_id = logger.add(
filter=default_filter,
format=default_format,
)
"""默认日志处理器 id"""
__autodoc__ = {"logger_id": False}

View File

@@ -8,6 +8,7 @@ FrontMatter:
from nonebot.internal.matcher import Matcher as Matcher
from nonebot.internal.matcher import matchers as matchers
from nonebot.internal.matcher import current_bot as current_bot
from nonebot.internal.matcher import MatcherSource as MatcherSource
from nonebot.internal.matcher import current_event as current_event
from nonebot.internal.matcher import MatcherManager as MatcherManager
from nonebot.internal.matcher import MatcherProvider as MatcherProvider

View File

@@ -80,7 +80,10 @@ RUN_POSTPCS_PARAMS = (
def event_preprocessor(func: T_EventPreProcessor) -> T_EventPreProcessor:
"""事件预处理。装饰一个函数,使它在每次接收到事件并分发给各响应器之前执行。"""
"""事件预处理。
装饰一个函数,使它在每次接收到事件并分发给各响应器之前执行。
"""
_event_preprocessors.add(
Dependent[Any].parse(call=func, allow_types=EVENT_PCS_PARAMS)
)
@@ -88,7 +91,10 @@ def event_preprocessor(func: T_EventPreProcessor) -> T_EventPreProcessor:
def event_postprocessor(func: T_EventPostProcessor) -> T_EventPostProcessor:
"""事件后处理。装饰一个函数,使它在每次接收到事件并分发给各响应器之后执行。"""
"""事件后处理。
装饰一个函数,使它在每次接收到事件并分发给各响应器之后执行。
"""
_event_postprocessors.add(
Dependent[Any].parse(call=func, allow_types=EVENT_PCS_PARAMS)
)
@@ -96,7 +102,10 @@ def event_postprocessor(func: T_EventPostProcessor) -> T_EventPostProcessor:
def run_preprocessor(func: T_RunPreProcessor) -> T_RunPreProcessor:
"""运行预处理。装饰一个函数,使它在每次事件响应器运行前执行。"""
"""运行预处理。
装饰一个函数,使它在每次事件响应器运行前执行。
"""
_run_preprocessors.add(
Dependent[Any].parse(call=func, allow_types=RUN_PREPCS_PARAMS)
)
@@ -104,56 +113,149 @@ def run_preprocessor(func: T_RunPreProcessor) -> T_RunPreProcessor:
def run_postprocessor(func: T_RunPostProcessor) -> T_RunPostProcessor:
"""运行后处理。装饰一个函数,使它在每次事件响应器运行后执行。"""
"""运行后处理。
装饰一个函数,使它在每次事件响应器运行后执行。
"""
_run_postprocessors.add(
Dependent[Any].parse(call=func, allow_types=RUN_POSTPCS_PARAMS)
)
return func
async def _check_matcher(
priority: int,
Matcher: Type[Matcher],
async def _apply_event_preprocessors(
bot: "Bot",
event: "Event",
state: T_State,
stack: Optional[AsyncExitStack] = None,
dependency_cache: Optional[T_DependencyCache] = None,
) -> None:
if Matcher.expire_time and datetime.now() > Matcher.expire_time:
with contextlib.suppress(Exception):
matchers[priority].remove(Matcher)
return
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:
if not await Matcher.check_perm(
bot, event, stack, dependency_cache
) or not await Matcher.check_rule(bot, event, state, stack, dependency_cache):
return
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_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(
f"<r><bg #f8bbd0>Rule check failed for {Matcher}.</bg #f8bbd0></r>"
"<r><bg #f8bbd0>Error when running EventPreProcessors. "
"Event ignored!</bg #f8bbd0></r>"
)
return
return False
if Matcher.temp:
with contextlib.suppress(Exception):
matchers[priority].remove(Matcher)
await _run_matcher(Matcher, bot, event, state, stack, dependency_cache)
return True
async def _run_matcher(
Matcher: Type[Matcher],
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:
logger.info(f"Event will be handled by {Matcher}")
"""运行事件后处理。
matcher = Matcher()
if coros := [
参数:
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,
@@ -166,33 +268,46 @@ async def _run_matcher(
(SkippedException,),
)
for proc in _run_preprocessors
]:
# ensure matcher function can be correctly called
with matcher.ensure_context(bot, event):
try:
await asyncio.gather(*coros)
)
)
except IgnoredException:
logger.opt(colors=True).info(f"{matcher} running is <b>cancelled</b>")
return
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>"
"<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
exception = None
with matcher.ensure_context(bot, event):
try:
logger.debug(f"Running {matcher}")
await matcher.run(bot, event, state, stack, dependency_cache)
except Exception as e:
logger.opt(colors=True, exception=e).error(
f"<r><bg #f8bbd0>Running {matcher} failed.</bg #f8bbd0></r>"
)
exception = e
if coros := [
await asyncio.gather(
*(
run_coro_with_catch(
proc(
matcher=matcher,
@@ -206,20 +321,158 @@ async def _run_matcher(
(SkippedException,),
)
for proc in _run_postprocessors
]:
# ensure matcher function can be correctly called
with matcher.ensure_context(bot, event):
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>"
)
async def _check_matcher(
Matcher: Type[Matcher],
bot: "Bot",
event: "Event",
state: T_State,
stack: Optional[AsyncExitStack] = None,
dependency_cache: Optional[T_DependencyCache] = None,
) -> bool:
"""检查事件响应器是否符合运行条件。
请注意,过时的事件响应器将被**销毁**。对于未过时的事件响应器,将会一次检查其响应类型、权限和规则。
参数:
Matcher: 要检查的事件响应器
bot: Bot 对象
event: Event 对象
state: 会话状态
stack: 异步上下文栈
dependency_cache: 依赖缓存
返回:
bool: 是否符合运行条件
"""
if Matcher.expire_time and datetime.now() > Matcher.expire_time:
with contextlib.suppress(Exception):
Matcher.destroy()
return False
try:
if not await Matcher.check_perm(
bot, event, stack, dependency_cache
) or not await Matcher.check_rule(bot, event, state, stack, dependency_cache):
return False
except Exception as e:
logger.opt(colors=True, exception=e).error(
f"<r><bg #f8bbd0>Rule check failed for {Matcher}.</bg #f8bbd0></r>"
)
return False
return True
async def _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: 依赖缓存
异常:
StopPropagation: 阻止事件继续传播
"""
logger.info(f"Event will be handled by {Matcher}")
if Matcher.temp:
with contextlib.suppress(Exception):
Matcher.destroy()
matcher = Matcher()
if not await _apply_run_preprocessors(
bot=bot,
event=event,
state=state,
matcher=matcher,
stack=stack,
dependency_cache=dependency_cache,
):
return
exception = None
try:
logger.debug(f"Running {matcher}")
await matcher.run(bot, event, state, stack, dependency_cache)
except Exception as e:
logger.opt(colors=True, exception=e).error(
f"<r><bg #f8bbd0>Running {matcher} failed.</bg #f8bbd0></r>"
)
exception = e
await _apply_run_postprocessors(
bot=bot,
event=event,
matcher=matcher,
exception=exception,
stack=stack,
dependency_cache=dependency_cache,
)
if matcher.block:
raise StopPropagation
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:
"""处理一个事件。调用该函数以实现分发事件。
@@ -246,34 +499,15 @@ async def handle_event(bot: "Bot", event: "Event") -> None:
state: Dict[Any, Any] = {}
dependency_cache: T_DependencyCache = {}
# create event scope context
async with AsyncExitStack() as stack:
if coros := [
run_coro_with_catch(
proc(
if not await _apply_event_preprocessors(
bot=bot,
event=event,
state=state,
stack=stack,
dependency_cache=dependency_cache,
),
(SkippedException,),
)
for proc in _event_preprocessors
]:
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
@@ -285,6 +519,7 @@ async def handle_event(bot: "Bot", event: "Event") -> None:
)
break_flag = False
# iterate through all priority until stop propagation
for priority in sorted(matchers.keys()):
if break_flag:
break
@@ -293,14 +528,12 @@ async def handle_event(bot: "Bot", event: "Event") -> None:
logger.debug(f"Checking for matchers in priority {priority}...")
pending_tasks = [
_check_matcher(
priority, matcher, bot, event, state.copy(), stack, dependency_cache
check_and_run_matcher(
matcher, bot, event, state.copy(), stack, dependency_cache
)
for matcher in matchers[priority]
]
results = await asyncio.gather(*pending_tasks, return_exceptions=True)
for result in results:
if not isinstance(result, Exception):
continue
@@ -312,24 +545,7 @@ async def handle_event(bot: "Bot", event: "Event") -> None:
"<r><bg #f8bbd0>Error when checking Matcher.</bg #f8bbd0></r>"
)
if coros := [
run_coro_with_catch(
proc(
bot=bot,
event=event,
state=state,
stack=stack,
dependency_cache=dependency_cache,
),
(SkippedException,),
)
for proc in _event_postprocessors
]:
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>"
)
logger.debug("Checking for matchers completed")
await _apply_event_postprocessors(bot, event, state, stack, dependency_cache)

View File

@@ -5,7 +5,7 @@ FrontMatter:
description: nonebot.params 模块
"""
from typing import Any, Dict, List, Tuple, Union, Optional
from typing import Any, Dict, List, Match, Tuple, Union, Optional
from nonebot.typing import T_State
from nonebot.matcher import Matcher
@@ -25,18 +25,17 @@ from nonebot.internal.params import ExceptionParam as ExceptionParam
from nonebot.consts import (
CMD_KEY,
PREFIX_KEY,
REGEX_DICT,
SHELL_ARGS,
SHELL_ARGV,
CMD_ARG_KEY,
KEYWORD_KEY,
RAW_CMD_KEY,
REGEX_GROUP,
ENDSWITH_KEY,
CMD_START_KEY,
FULLMATCH_KEY,
REGEX_MATCHED,
STARTSWITH_KEY,
CMD_WHITESPACE_KEY,
)
@@ -112,6 +111,15 @@ 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
@@ -130,17 +138,26 @@ def ShellCommandArgv() -> Any:
return Depends(_shell_command_argv, use_cache=False)
def _regex_matched(state: T_State) -> str:
def _regex_matched(state: T_State) -> Match[str]:
return state[REGEX_MATCHED]
def RegexMatched() -> str:
def RegexMatched() -> Match[str]:
"""正则匹配结果"""
return Depends(_regex_matched, use_cache=False)
def _regex_group(state: T_State):
return state[REGEX_GROUP]
def _regex_str(state: T_State) -> str:
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, ...]:
@@ -148,8 +165,8 @@ def RegexGroup() -> Tuple[Any, ...]:
return Depends(_regex_group, use_cache=False)
def _regex_dict(state: T_State):
return state[REGEX_DICT]
def _regex_dict(state: T_State) -> Dict[str, Any]:
return _regex_matched(state).groupdict()
def RegexDict() -> Dict[str, Any]:
@@ -196,7 +213,7 @@ def Keyword() -> str:
def Received(id: Optional[str] = None, default: Any = None) -> Any:
"""`receive` 事件参数"""
def _received(matcher: "Matcher"):
def _received(matcher: "Matcher") -> Any:
return matcher.get_receive(id or "", default)
return Depends(_received, use_cache=False)

View File

@@ -1,7 +1,8 @@
"""本模块是 {ref}`nonebot.matcher.Matcher.permission` 的类型定义。
每个 {ref}`nonebot.matcher.Matcher` 拥有一个 {ref}`nonebot.permission.Permission`
其中是 `PermissionChecker` 的集合,只要有一个 `PermissionChecker` 检查结果为 `True` 时就会继续运行
每个{ref}`事件响应器 <nonebot.matcher.Matcher>`
拥有一个 {ref}`nonebot.permission.Permission`,其中是 `PermissionChecker` 的集合
只要有一个 `PermissionChecker` 检查结果为 `True` 时就会继续运行。
FrontMatter:
sidebar_position: 6

View File

@@ -24,8 +24,10 @@
- `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>`
- `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>`
@@ -42,7 +44,7 @@ from typing import Set, Dict, List, Tuple, Optional
_plugins: Dict[str, "Plugin"] = {}
_managers: List["PluginManager"] = []
_current_plugin_chain: ContextVar[Tuple["Plugin", ...]] = ContextVar(
"_current_plugin_chain", default=tuple()
"_current_plugin_chain", default=()
)
@@ -65,6 +67,8 @@ 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"]:
@@ -130,3 +134,4 @@ from .plugin import PluginMetadata as PluginMetadata
from .load import load_all_plugins as load_all_plugins
from .load import load_builtin_plugin as load_builtin_plugin
from .load import load_builtin_plugins as load_builtin_plugins
from .load import inherit_supported_adapters as inherit_supported_adapters

View File

@@ -4,25 +4,30 @@ FrontMatter:
sidebar_position: 1
description: nonebot.plugin.load 模块
"""
import json
from pathlib import Path
from types import ModuleType
from typing import Set, Union, Iterable, Optional
import tomlkit
from nonebot.utils import path_to_module_name
from .plugin import Plugin
from .manager import PluginManager
from . import _managers, get_plugin, _module_name_to_plugin_name
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: Union[str, Path]) -> Optional[Plugin]:
"""加载单个插件,可以是本地插件或是通过 `pip` 安装的插件。
参数:
module_path: 插件名称 `path.to.your.plugin` 或插件路径 `pathlib.Path(path/to/your/plugin)`
module_path: 插件名称 `path.to.your.plugin`
或插件路径 `pathlib.Path(path/to/your/plugin)`
"""
module_path = (
path_to_module_name(module_path)
@@ -60,7 +65,8 @@ def load_all_plugins(
def load_from_json(file_path: str, encoding: str = "utf-8") -> Set[Plugin]:
"""导入指定 json 文件中的 `plugins` 以及 `plugin_dirs` 下多个插件,以 `_` 开头的插件不会被导入!
"""导入指定 json 文件中的 `plugins` 以及 `plugin_dirs` 下多个插件
以 `_` 开头的插件不会被导入!
参数:
file_path: 指定 json 文件路径
@@ -78,7 +84,7 @@ def load_from_json(file_path: str, encoding: str = "utf-8") -> Set[Plugin]:
nonebot.load_from_json("plugins.json")
```
"""
with open(file_path, "r", encoding=encoding) as f:
with open(file_path, encoding=encoding) as f:
data = json.load(f)
if not isinstance(data, dict):
raise TypeError("json file must contains a dict!")
@@ -90,7 +96,9 @@ 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]:
"""导入指定 toml 文件 `[tool.nonebot]` 中的 `plugins` 以及 `plugin_dirs` 下多个插件,以 `_` 开头的插件不会被导入!
"""导入指定 toml 文件 `[tool.nonebot]` 中的
`plugins` 以及 `plugin_dirs` 下多个插件。
以 `_` 开头的插件不会被导入!
参数:
file_path: 指定 toml 文件路径
@@ -107,8 +115,8 @@ def load_from_toml(file_path: str, encoding: str = "utf-8") -> Set[Plugin]:
nonebot.load_from_toml("pyproject.toml")
```
"""
with open(file_path, "r", encoding=encoding) as f:
data = tomlkit.parse(f.read()) # type: ignore
with open(file_path, encoding=encoding) as f:
data = tomllib.loads(f.read())
nonebot_data = data.get("tool", {}).get("nonebot")
if nonebot_data is None:
@@ -158,11 +166,55 @@ def require(name: str) -> ModuleType:
RuntimeError: 插件无法加载
"""
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:
raise RuntimeError(f'Cannot load plugin "{name}"!')
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

@@ -6,6 +6,7 @@ FrontMatter:
sidebar_position: 5
description: nonebot.plugin.manager 模块
"""
import sys
import pkgutil
import importlib
@@ -14,7 +15,7 @@ from itertools import chain
from types import ModuleType
from importlib.abc import MetaPathFinder
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.utils import escape_tag, path_to_module_name
@@ -148,14 +149,21 @@ class PluginManager:
else:
raise RuntimeError(f"Plugin not found: {name}! Check your plugin name")
logger.opt(colors=True).success(
f'Succeeded to import "<y>{escape_tag(name)}</y>"'
)
if (plugin := getattr(module, "__plugin__", None)) is None:
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(
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 plugin
except Exception as e:
logger.opt(colors=True, exception=e).error(
@@ -174,7 +182,7 @@ class PluginFinder(MetaPathFinder):
def find_spec(
self,
fullname: str,
path: Optional[Sequence[Union[bytes, str]]],
path: Optional[Sequence[str]],
target: Optional[ModuleType] = None,
):
if _managers:
@@ -221,6 +229,7 @@ class PluginLoader(SourceFileLoader):
# detect parent plugin before entering current plugin context
parent_plugins = _current_plugin_chain.get()
for pre_plugin in reversed(parent_plugins):
# ensure parent plugin is declared before current plugin
if _managers.index(pre_plugin.manager) < _managers.index(self.manager):
plugin.parent_plugin = pre_plugin
pre_plugin.sub_plugins.add(plugin)

View File

@@ -4,16 +4,18 @@ FrontMatter:
sidebar_position: 2
description: nonebot.plugin.on 模块
"""
import re
import inspect
import warnings
from types import ModuleType
from datetime import datetime, timedelta
from typing import Any, Set, Dict, List, Type, Tuple, Union, Optional
from nonebot.adapters import Event
from nonebot.matcher import Matcher
from nonebot.permission import Permission
from nonebot.dependencies import Dependent
from nonebot.matcher import Matcher, MatcherSource
from nonebot.typing import T_State, T_Handler, T_RuleChecker, T_PermissionChecker
from nonebot.rule import (
Rule,
@@ -28,21 +30,78 @@ from nonebot.rule import (
shell_command,
)
from .plugin import Plugin
from . import get_plugin_by_module_name
from .manager import _current_plugin_chain
def _store_matcher(matcher: Type[Matcher]) -> None:
# only store the matcher defined in the plugin
if plugins := _current_plugin_chain.get():
plugins[-1].matcher.add(matcher)
def store_matcher(matcher: Type[Matcher]) -> None:
"""存储一个事件响应器到插件。
参数:
matcher: 事件响应器
"""
# only store the matcher defined when plugin loading
if plugin_chain := _current_plugin_chain.get():
plugin_chain[-1].matcher.add(matcher)
def _get_matcher_module(depth: int = 1) -> Optional[ModuleType]:
def get_matcher_plugin(depth: int = 1) -> Optional[Plugin]: # pragma: no cover
"""获取事件响应器定义所在插件。
**Deprecated**, 请使用 {ref}`nonebot.plugin.on.get_matcher_source` 获取信息。
参数:
depth: 调用栈深度
"""
warnings.warn(
"`get_matcher_plugin` is deprecated, please use `get_matcher_source` instead",
DeprecationWarning,
)
return (source := get_matcher_source(depth + 1)) and source.plugin
def get_matcher_module(depth: int = 1) -> Optional[ModuleType]: # pragma: no cover
"""获取事件响应器定义所在模块。
**Deprecated**, 请使用 {ref}`nonebot.plugin.on.get_matcher_source` 获取信息。
参数:
depth: 调用栈深度
"""
warnings.warn(
"`get_matcher_module` is deprecated, please use `get_matcher_source` instead",
DeprecationWarning,
)
return (source := get_matcher_source(depth + 1)) and source.module
def get_matcher_source(depth: int = 1) -> Optional[MatcherSource]:
"""获取事件响应器定义所在源码信息。
参数:
depth: 调用栈深度
"""
current_frame = inspect.currentframe()
if current_frame is None:
return None
frame = inspect.getouterframes(current_frame)[depth + 1].frame
return inspect.getmodule(frame)
module_name = (module := inspect.getmodule(frame)) and module.__name__
plugin: Optional["Plugin"] = None
# matcher defined when plugin loading
if plugin_chain := _current_plugin_chain.get():
plugin = plugin_chain[-1]
# matcher defined when plugin running
elif module_name:
plugin = get_plugin_by_module_name(module_name)
return MatcherSource(
plugin_name=plugin and plugin.name,
module_name=module_name,
lineno=frame.f_lineno,
)
def on(
@@ -71,7 +130,6 @@ def on(
block: 是否阻止事件向更低优先级传递
state: 默认 state
"""
plugin_chain = _current_plugin_chain.get()
matcher = Matcher.new(
type,
Rule() & rule,
@@ -81,29 +139,19 @@ def on(
priority=priority,
block=block,
handlers=handlers,
plugin=plugin_chain[-1] if plugin_chain else None,
module=_get_matcher_module(_depth + 1),
source=get_matcher_source(_depth + 1),
default_state=state,
)
_store_matcher(matcher)
store_matcher(matcher)
return matcher
def on_metaevent(
rule: Optional[Union[Rule, T_RuleChecker]] = None,
*,
handlers: Optional[List[Union[T_Handler, Dependent]]] = None,
temp: bool = False,
expire_time: Optional[Union[datetime, timedelta]] = None,
priority: int = 1,
block: bool = False,
state: Optional[T_State] = None,
_depth: int = 0,
) -> Type[Matcher]:
def on_metaevent(*args, _depth: int = 0, **kwargs) -> Type[Matcher]:
"""注册一个元事件响应器。
参数:
rule: 事件响应规则
permission: 事件响应权限
handlers: 事件处理函数列表
temp: 是否为临时事件响应器(仅执行一次)
expire_time: 事件响应器最终有效时间点,过时即被删除
@@ -111,36 +159,10 @@ def on_metaevent(
block: 是否阻止事件向更低优先级传递
state: 默认 state
"""
plugin_chain = _current_plugin_chain.get()
matcher = Matcher.new(
"meta_event",
Rule() & rule,
Permission(),
temp=temp,
expire_time=expire_time,
priority=priority,
block=block,
handlers=handlers,
plugin=plugin_chain[-1] if plugin_chain else None,
module=_get_matcher_module(_depth + 1),
default_state=state,
)
_store_matcher(matcher)
return matcher
return on("meta_event", *args, **kwargs, _depth=_depth + 1)
def on_message(
rule: Optional[Union[Rule, T_RuleChecker]] = None,
permission: Optional[Union[Permission, T_PermissionChecker]] = None,
*,
handlers: Optional[List[Union[T_Handler, Dependent]]] = None,
temp: bool = False,
expire_time: Optional[Union[datetime, timedelta]] = None,
priority: int = 1,
block: bool = True,
state: Optional[T_State] = None,
_depth: int = 0,
) -> Type[Matcher]:
def on_message(*args, _depth: int = 0, **kwargs) -> Type[Matcher]:
"""注册一个消息事件响应器。
参数:
@@ -153,39 +175,16 @@ def on_message(
block: 是否阻止事件向更低优先级传递
state: 默认 state
"""
plugin_chain = _current_plugin_chain.get()
matcher = Matcher.new(
"message",
Rule() & rule,
Permission() | permission,
temp=temp,
expire_time=expire_time,
priority=priority,
block=block,
handlers=handlers,
plugin=plugin_chain[-1] if plugin_chain else None,
module=_get_matcher_module(_depth + 1),
default_state=state,
)
_store_matcher(matcher)
return matcher
kwargs.setdefault("block", True)
return on("message", *args, **kwargs, _depth=_depth + 1)
def on_notice(
rule: Optional[Union[Rule, T_RuleChecker]] = None,
*,
handlers: Optional[List[Union[T_Handler, Dependent]]] = None,
temp: bool = False,
expire_time: Optional[Union[datetime, timedelta]] = None,
priority: int = 1,
block: bool = False,
state: Optional[T_State] = None,
_depth: int = 0,
) -> Type[Matcher]:
def on_notice(*args, _depth: int = 0, **kwargs) -> Type[Matcher]:
"""注册一个通知事件响应器。
参数:
rule: 事件响应规则
permission: 事件响应权限
handlers: 事件处理函数列表
temp: 是否为临时事件响应器(仅执行一次)
expire_time: 事件响应器最终有效时间点,过时即被删除
@@ -193,39 +192,15 @@ def on_notice(
block: 是否阻止事件向更低优先级传递
state: 默认 state
"""
plugin_chain = _current_plugin_chain.get()
matcher = Matcher.new(
"notice",
Rule() & rule,
Permission(),
temp=temp,
expire_time=expire_time,
priority=priority,
block=block,
handlers=handlers,
plugin=plugin_chain[-1] if plugin_chain else None,
module=_get_matcher_module(_depth + 1),
default_state=state,
)
_store_matcher(matcher)
return matcher
return on("notice", *args, **kwargs, _depth=_depth + 1)
def on_request(
rule: Optional[Union[Rule, T_RuleChecker]] = None,
*,
handlers: Optional[List[Union[T_Handler, Dependent]]] = None,
temp: bool = False,
expire_time: Optional[Union[datetime, timedelta]] = None,
priority: int = 1,
block: bool = False,
state: Optional[T_State] = None,
_depth: int = 0,
) -> Type[Matcher]:
def on_request(*args, _depth: int = 0, **kwargs) -> Type[Matcher]:
"""注册一个请求事件响应器。
参数:
rule: 事件响应规则
permission: 事件响应权限
handlers: 事件处理函数列表
temp: 是否为临时事件响应器(仅执行一次)
expire_time: 事件响应器最终有效时间点,过时即被删除
@@ -233,22 +208,7 @@ def on_request(
block: 是否阻止事件向更低优先级传递
state: 默认 state
"""
plugin_chain = _current_plugin_chain.get()
matcher = Matcher.new(
"request",
Rule() & rule,
Permission(),
temp=temp,
expire_time=expire_time,
priority=priority,
block=block,
handlers=handlers,
plugin=plugin_chain[-1] if plugin_chain else None,
module=_get_matcher_module(_depth + 1),
default_state=state,
)
_store_matcher(matcher)
return matcher
return on("request", *args, **kwargs, _depth=_depth + 1)
def on_startswith(
@@ -349,6 +309,7 @@ def on_command(
cmd: Union[str, Tuple[str, ...]],
rule: Optional[Union[Rule, T_RuleChecker]] = None,
aliases: Optional[Set[Union[str, Tuple[str, ...]]]] = None,
force_whitespace: Optional[Union[str, bool]] = None,
_depth: int = 0,
**kwargs,
) -> Type[Matcher]:
@@ -360,6 +321,7 @@ def on_command(
cmd: 指定命令内容
rule: 事件响应规则
aliases: 命令别名
force_whitespace: 是否强制命令后必须有指定空白符
permission: 事件响应权限
handlers: 事件处理函数列表
temp: 是否为临时事件响应器(仅执行一次)
@@ -370,9 +332,11 @@ def on_command(
"""
commands = {cmd} | (aliases or set())
block = kwargs.pop("block", False)
kwargs.setdefault("block", False)
return on_message(
command(*commands) & rule, block=block, **kwargs, _depth=_depth + 1
command(*commands, force_whitespace=force_whitespace) & rule,
**kwargs,
_depth=_depth + 1,
)
@@ -388,7 +352,8 @@ def on_shell_command(
与普通的 `on_command` 不同的是,在添加 `parser` 参数时, 响应器会自动处理消息。
并将用户输入的原始参数列表保存在 `state["argv"]`, `parser` 处理的参数保存在 `state["args"]` 中
可以通过 {ref}`nonebot.params.ShellCommandArgv` 获取原始参数列表,
通过 {ref}`nonebot.params.ShellCommandArgs` 获取解析后的参数字典。
参数:
cmd: 指定命令内容
@@ -439,7 +404,7 @@ def on_regex(
def on_type(
types: Union[Type[Event], Tuple[Type[Event]]],
types: Union[Type[Event], Tuple[Type[Event], ...]],
rule: Optional[Union[Rule, T_RuleChecker]] = None,
*,
_depth: int = 0,
@@ -493,6 +458,7 @@ class CommandGroup(_Group):
参数:
cmd: 指定命令内容
prefix_aliases: 是否影响命令别名,给命令别名加前缀
rule: 事件响应规则
permission: 事件响应权限
handlers: 事件处理函数列表
@@ -503,11 +469,14 @@ class CommandGroup(_Group):
state: 默认 state
"""
def __init__(self, cmd: Union[str, Tuple[str, ...]], **kwargs):
def __init__(
self, cmd: Union[str, Tuple[str, ...]], prefix_aliases: bool = False, **kwargs
):
"""命令前缀"""
super().__init__(**kwargs)
self.basecmd: Tuple[str, ...] = (cmd,) if isinstance(cmd, str) else cmd
self.base_kwargs.pop("aliases", None)
self.prefix_aliases = prefix_aliases
def __repr__(self) -> str:
return f"CommandGroup(cmd={self.basecmd}, matchers={len(self.matchers)})"
@@ -518,6 +487,7 @@ class CommandGroup(_Group):
参数:
cmd: 指定命令内容
aliases: 命令别名
force_whitespace: 是否强制命令后必须有指定空白符
rule: 事件响应规则
permission: 事件响应权限
handlers: 事件处理函数列表
@@ -529,6 +499,11 @@ class CommandGroup(_Group):
"""
sub_cmd = (cmd,) if isinstance(cmd, str) else cmd
cmd = self.basecmd + sub_cmd
if self.prefix_aliases and (aliases := kwargs.get("aliases")):
kwargs["aliases"] = {
self.basecmd + ((alias,) if isinstance(alias, str) else alias)
for alias in aliases
}
matcher = on_command(cmd, **self._get_final_kwargs(kwargs))
self.matchers.append(matcher)
return matcher
@@ -553,6 +528,11 @@ class CommandGroup(_Group):
"""
sub_cmd = (cmd,) if isinstance(cmd, str) else cmd
cmd = self.basecmd + sub_cmd
if self.prefix_aliases and (aliases := kwargs.get("aliases")):
kwargs["aliases"] = {
self.basecmd + ((alias,) if isinstance(alias, str) else alias)
for alias in aliases
}
matcher = on_shell_command(cmd, **self._get_final_kwargs(kwargs))
self.matchers.append(matcher)
return matcher
@@ -587,6 +567,7 @@ class MatcherGroup(_Group):
参数:
rule: 事件响应规则
permission: 事件响应权限
handlers: 事件处理函数列表
temp: 是否为临时事件响应器(仅执行一次)
expire_time: 事件响应器最终有效时间点,过时即被删除
@@ -622,6 +603,7 @@ class MatcherGroup(_Group):
参数:
rule: 事件响应规则
permission: 事件响应权限
handlers: 事件处理函数列表
temp: 是否为临时事件响应器(仅执行一次)
expire_time: 事件响应器最终有效时间点,过时即被删除
@@ -639,6 +621,7 @@ class MatcherGroup(_Group):
参数:
rule: 事件响应规则
permission: 事件响应权限
handlers: 事件处理函数列表
temp: 是否为临时事件响应器(仅执行一次)
expire_time: 事件响应器最终有效时间点,过时即被删除
@@ -736,6 +719,7 @@ class MatcherGroup(_Group):
self,
cmd: Union[str, Tuple[str, ...]],
aliases: Optional[Set[Union[str, Tuple[str, ...]]]] = None,
force_whitespace: Optional[Union[str, bool]] = None,
**kwargs,
) -> Type[Matcher]:
"""注册一个消息事件响应器,并且当消息以指定命令开头时响应。
@@ -745,6 +729,7 @@ class MatcherGroup(_Group):
参数:
cmd: 指定命令内容
aliases: 命令别名
force_whitespace: 是否强制命令后必须有指定空白符
rule: 事件响应规则
permission: 事件响应权限
handlers: 事件处理函数列表
@@ -755,7 +740,9 @@ class MatcherGroup(_Group):
state: 默认 state
"""
final_kwargs = self._get_final_kwargs(kwargs, exclude={"type"})
matcher = on_command(cmd, aliases=aliases, **final_kwargs)
matcher = on_command(
cmd, aliases=aliases, force_whitespace=force_whitespace, **final_kwargs
)
self.matchers.append(matcher)
return matcher
@@ -770,7 +757,8 @@ class MatcherGroup(_Group):
与普通的 `on_command` 不同的是,在添加 `parser` 参数时, 响应器会自动处理消息。
并将用户输入的原始参数列表保存在 `state["argv"]`, `parser` 处理的参数保存在 `state["args"]` 中
可以通过 {ref}`nonebot.params.ShellCommandArgv` 获取原始参数列表,
通过 {ref}`nonebot.params.ShellCommandArgs` 获取解析后的参数字典。
参数:
cmd: 指定命令内容

View File

@@ -1,395 +1,421 @@
import re
from typing import Any
from types import ModuleType
from datetime import datetime, timedelta
from typing import Set, List, Type, Tuple, Union, Optional
from nonebot.adapters import Event
from nonebot.matcher import Matcher
from nonebot.permission import Permission
from nonebot.dependencies import Dependent
from nonebot.rule import Rule, ArgumentParser
from nonebot.matcher import Matcher, MatcherSource
from nonebot.typing import T_State, T_Handler, T_RuleChecker, T_PermissionChecker
from .plugin import Plugin
def store_matcher(matcher: type[Matcher]) -> None: ...
def get_matcher_plugin(depth: int = ...) -> Plugin | None: ...
def get_matcher_module(depth: int = ...) -> ModuleType | None: ...
def get_matcher_source(depth: int = ...) -> MatcherSource | None: ...
def on(
type: str = "",
rule: Optional[Union[Rule, T_RuleChecker]] = ...,
permission: Optional[Union[Permission, T_PermissionChecker]] = ...,
rule: Rule | T_RuleChecker | None = ...,
permission: Permission | T_PermissionChecker | None = ...,
*,
handlers: Optional[List[Union[T_Handler, Dependent]]] = ...,
handlers: list[T_Handler | Dependent] | None = ...,
temp: bool = ...,
expire_time: Optional[Union[datetime, timedelta]] = ...,
expire_time: datetime | timedelta | None = ...,
priority: int = ...,
block: bool = ...,
state: Optional[T_State] = ...,
) -> Type[Matcher]: ...
state: T_State | None = ...,
) -> type[Matcher]: ...
def on_metaevent(
rule: Optional[Union[Rule, T_RuleChecker]] = ...,
rule: Rule | T_RuleChecker | None = ...,
permission: Permission | T_PermissionChecker | None = ...,
*,
handlers: Optional[List[Union[T_Handler, Dependent]]] = ...,
handlers: list[T_Handler | Dependent] | None = ...,
temp: bool = ...,
expire_time: Optional[Union[datetime, timedelta]] = ...,
expire_time: datetime | timedelta | None = ...,
priority: int = ...,
block: bool = ...,
state: Optional[T_State] = ...,
) -> Type[Matcher]: ...
state: T_State | None = ...,
) -> type[Matcher]: ...
def on_message(
rule: Optional[Union[Rule, T_RuleChecker]] = ...,
permission: Optional[Union[Permission, T_PermissionChecker]] = ...,
rule: Rule | T_RuleChecker | None = ...,
permission: Permission | T_PermissionChecker | None = ...,
*,
handlers: Optional[List[Union[T_Handler, Dependent]]] = ...,
handlers: list[T_Handler | Dependent] | None = ...,
temp: bool = ...,
expire_time: Optional[Union[datetime, timedelta]] = ...,
expire_time: datetime | timedelta | None = ...,
priority: int = ...,
block: bool = ...,
state: Optional[T_State] = ...,
) -> Type[Matcher]: ...
state: T_State | None = ...,
) -> type[Matcher]: ...
def on_notice(
rule: Optional[Union[Rule, T_RuleChecker]] = ...,
rule: Rule | T_RuleChecker | None = ...,
permission: Permission | T_PermissionChecker | None = ...,
*,
handlers: Optional[List[Union[T_Handler, Dependent]]] = ...,
handlers: list[T_Handler | Dependent] | None = ...,
temp: bool = ...,
expire_time: Optional[Union[datetime, timedelta]] = ...,
expire_time: datetime | timedelta | None = ...,
priority: int = ...,
block: bool = ...,
state: Optional[T_State] = ...,
) -> Type[Matcher]: ...
state: T_State | None = ...,
) -> type[Matcher]: ...
def on_request(
rule: Optional[Union[Rule, T_RuleChecker]] = ...,
rule: Rule | T_RuleChecker | None = ...,
permission: Permission | T_PermissionChecker | None = ...,
*,
handlers: Optional[List[Union[T_Handler, Dependent]]] = ...,
handlers: list[T_Handler | Dependent] | None = ...,
temp: bool = ...,
expire_time: Optional[Union[datetime, timedelta]] = ...,
expire_time: datetime | timedelta | None = ...,
priority: int = ...,
block: bool = ...,
state: Optional[T_State] = ...,
) -> Type[Matcher]: ...
state: T_State | None = ...,
) -> type[Matcher]: ...
def on_startswith(
msg: Union[str, Tuple[str, ...]],
rule: Optional[Union[Rule, T_RuleChecker]] = ...,
msg: str | tuple[str, ...],
rule: Rule | T_RuleChecker | None = ...,
ignorecase: bool = ...,
*,
permission: Optional[Union[Permission, T_PermissionChecker]] = ...,
handlers: Optional[List[Union[T_Handler, Dependent]]] = ...,
permission: Permission | T_PermissionChecker | None = ...,
handlers: list[T_Handler | Dependent] | None = ...,
temp: bool = ...,
expire_time: Optional[Union[datetime, timedelta]] = ...,
expire_time: datetime | timedelta | None = ...,
priority: int = ...,
block: bool = ...,
state: Optional[T_State] = ...,
) -> Type[Matcher]: ...
state: T_State | None = ...,
) -> type[Matcher]: ...
def on_endswith(
msg: Union[str, Tuple[str, ...]],
rule: Optional[Union[Rule, T_RuleChecker]] = ...,
msg: str | tuple[str, ...],
rule: Rule | T_RuleChecker | None = ...,
ignorecase: bool = ...,
*,
permission: Optional[Union[Permission, T_PermissionChecker]] = ...,
handlers: Optional[List[Union[T_Handler, Dependent]]] = ...,
permission: Permission | T_PermissionChecker | None = ...,
handlers: list[T_Handler | Dependent] | None = ...,
temp: bool = ...,
expire_time: Optional[Union[datetime, timedelta]] = ...,
expire_time: datetime | timedelta | None = ...,
priority: int = ...,
block: bool = ...,
state: Optional[T_State] = ...,
) -> Type[Matcher]: ...
state: T_State | None = ...,
) -> type[Matcher]: ...
def on_fullmatch(
msg: Union[str, Tuple[str, ...]],
rule: Optional[Union[Rule, T_RuleChecker]] = ...,
msg: str | tuple[str, ...],
rule: Rule | T_RuleChecker | None = ...,
ignorecase: bool = ...,
*,
permission: Optional[Union[Permission, T_PermissionChecker]] = ...,
handlers: Optional[List[Union[T_Handler, Dependent]]] = ...,
permission: Permission | T_PermissionChecker | None = ...,
handlers: list[T_Handler | Dependent] | None = ...,
temp: bool = ...,
expire_time: Optional[Union[datetime, timedelta]] = ...,
expire_time: datetime | timedelta | None = ...,
priority: int = ...,
block: bool = ...,
state: Optional[T_State] = ...,
) -> Type[Matcher]: ...
state: T_State | None = ...,
) -> type[Matcher]: ...
def on_keyword(
keywords: Set[str],
rule: Optional[Union[Rule, T_RuleChecker]] = ...,
keywords: set[str],
rule: Rule | T_RuleChecker | None = ...,
*,
permission: Optional[Union[Permission, T_PermissionChecker]] = ...,
handlers: Optional[List[Union[T_Handler, Dependent]]] = ...,
permission: Permission | T_PermissionChecker | None = ...,
handlers: list[T_Handler | Dependent] | None = ...,
temp: bool = ...,
expire_time: Optional[Union[datetime, timedelta]] = ...,
expire_time: datetime | timedelta | None = ...,
priority: int = ...,
block: bool = ...,
state: Optional[T_State] = ...,
) -> Type[Matcher]: ...
state: T_State | None = ...,
) -> type[Matcher]: ...
def on_command(
cmd: Union[str, Tuple[str, ...]],
rule: Optional[Union[Rule, T_RuleChecker]] = ...,
aliases: Optional[Set[Union[str, Tuple[str, ...]]]] = ...,
cmd: str | tuple[str, ...],
rule: Rule | T_RuleChecker | None = ...,
aliases: set[str | tuple[str, ...]] | None = ...,
force_whitespace: str | bool | None = ...,
*,
permission: Optional[Union[Permission, T_PermissionChecker]] = ...,
handlers: Optional[List[Union[T_Handler, Dependent]]] = ...,
permission: Permission | T_PermissionChecker | None = ...,
handlers: list[T_Handler | Dependent] | None = ...,
temp: bool = ...,
expire_time: Optional[Union[datetime, timedelta]] = ...,
expire_time: datetime | timedelta | None = ...,
priority: int = ...,
block: bool = ...,
state: Optional[T_State] = ...,
) -> Type[Matcher]: ...
state: T_State | None = ...,
) -> type[Matcher]: ...
def on_shell_command(
cmd: Union[str, Tuple[str, ...]],
rule: Optional[Union[Rule, T_RuleChecker]] = ...,
aliases: Optional[Set[Union[str, Tuple[str, ...]]]] = ...,
parser: Optional[ArgumentParser] = ...,
cmd: str | tuple[str, ...],
rule: Rule | T_RuleChecker | None = ...,
aliases: set[str | tuple[str, ...]] | None = ...,
parser: ArgumentParser | None = ...,
*,
permission: Optional[Union[Permission, T_PermissionChecker]] = ...,
handlers: Optional[List[Union[T_Handler, Dependent]]] = ...,
permission: Permission | T_PermissionChecker | None = ...,
handlers: list[T_Handler | Dependent] | None = ...,
temp: bool = ...,
expire_time: Optional[Union[datetime, timedelta]] = ...,
expire_time: datetime | timedelta | None = ...,
priority: int = ...,
block: bool = ...,
state: Optional[T_State] = ...,
) -> Type[Matcher]: ...
state: T_State | None = ...,
) -> type[Matcher]: ...
def on_regex(
pattern: str,
flags: Union[int, re.RegexFlag] = ...,
rule: Optional[Union[Rule, T_RuleChecker]] = ...,
flags: int | re.RegexFlag = ...,
rule: Rule | T_RuleChecker | None = ...,
*,
permission: Optional[Union[Permission, T_PermissionChecker]] = ...,
handlers: Optional[List[Union[T_Handler, Dependent]]] = ...,
permission: Permission | T_PermissionChecker | None = ...,
handlers: list[T_Handler | Dependent] | None = ...,
temp: bool = ...,
expire_time: Optional[Union[datetime, timedelta]] = ...,
expire_time: datetime | timedelta | None = ...,
priority: int = ...,
block: bool = ...,
state: Optional[T_State] = ...,
) -> Type[Matcher]: ...
state: T_State | None = ...,
) -> type[Matcher]: ...
def on_type(
types: Union[Type[Event], Tuple[Type[Event]]],
rule: Optional[Union[Rule, T_RuleChecker]] = ...,
types: type[Event] | tuple[type[Event], ...],
rule: Rule | T_RuleChecker | None = ...,
*,
permission: Optional[Union[Permission, T_PermissionChecker]] = ...,
handlers: Optional[List[Union[T_Handler, Dependent]]] = ...,
permission: Permission | T_PermissionChecker | None = ...,
handlers: list[T_Handler | Dependent] | None = ...,
temp: bool = ...,
expire_time: Optional[Union[datetime, timedelta]] = ...,
expire_time: datetime | timedelta | None = ...,
priority: int = ...,
block: bool = ...,
state: Optional[T_State] = ...,
) -> Type[Matcher]: ...
state: T_State | None = ...,
) -> type[Matcher]: ...
class CommandGroup:
class _Group:
matchers: list[type[Matcher]] = ...
base_kwargs: dict[str, Any] = ...
def _get_final_kwargs(
self, update: dict[str, Any], *, exclude: set[str] | None = None
) -> dict[str, Any]: ...
class CommandGroup(_Group):
basecmd: tuple[str, ...] = ...
prefix_aliases: bool = ...
def __init__(
self,
cmd: Union[str, Tuple[str, ...]],
cmd: str | tuple[str, ...],
prefix_aliases: bool = ...,
*,
rule: Optional[Union[Rule, T_RuleChecker]] = ...,
permission: Optional[Union[Permission, T_PermissionChecker]] = ...,
handlers: Optional[List[Union[T_Handler, Dependent]]] = ...,
rule: Rule | T_RuleChecker | None = ...,
permission: Permission | T_PermissionChecker | None = ...,
handlers: list[T_Handler | Dependent] | None = ...,
temp: bool = ...,
expire_time: Optional[Union[datetime, timedelta]] = ...,
expire_time: datetime | timedelta | None = ...,
priority: int = ...,
block: bool = ...,
state: Optional[T_State] = ...,
state: T_State | None = ...,
): ...
def command(
self,
cmd: Union[str, Tuple[str, ...]],
cmd: str | tuple[str, ...],
*,
rule: Optional[Union[Rule, T_RuleChecker]] = ...,
aliases: Optional[Set[Union[str, Tuple[str, ...]]]] = ...,
permission: Optional[Union[Permission, T_PermissionChecker]] = ...,
handlers: Optional[List[Union[T_Handler, Dependent]]] = ...,
rule: Rule | T_RuleChecker | None = ...,
aliases: set[str | tuple[str, ...]] | None = ...,
force_whitespace: str | bool | None = ...,
permission: Permission | T_PermissionChecker | None = ...,
handlers: list[T_Handler | Dependent] | None = ...,
temp: bool = ...,
expire_time: Optional[Union[datetime, timedelta]] = ...,
expire_time: datetime | timedelta | None = ...,
priority: int = ...,
block: bool = ...,
state: Optional[T_State] = ...,
) -> Type[Matcher]: ...
state: T_State | None = ...,
) -> type[Matcher]: ...
def shell_command(
self,
cmd: Union[str, Tuple[str, ...]],
cmd: str | tuple[str, ...],
*,
rule: Optional[Union[Rule, T_RuleChecker]] = ...,
aliases: Optional[Set[Union[str, Tuple[str, ...]]]] = ...,
parser: Optional[ArgumentParser] = ...,
permission: Optional[Union[Permission, T_PermissionChecker]] = ...,
handlers: Optional[List[Union[T_Handler, Dependent]]] = ...,
rule: Rule | T_RuleChecker | None = ...,
aliases: set[str | tuple[str, ...]] | None = ...,
parser: ArgumentParser | None = ...,
permission: Permission | T_PermissionChecker | None = ...,
handlers: list[T_Handler | Dependent] | None = ...,
temp: bool = ...,
expire_time: Optional[Union[datetime, timedelta]] = ...,
expire_time: datetime | timedelta | None = ...,
priority: int = ...,
block: bool = ...,
state: Optional[T_State] = ...,
) -> Type[Matcher]: ...
state: T_State | None = ...,
) -> type[Matcher]: ...
class MatcherGroup:
class MatcherGroup(_Group):
def __init__(
self,
*,
type: str = ...,
rule: Optional[Union[Rule, T_RuleChecker]] = ...,
permission: Optional[Union[Permission, T_PermissionChecker]] = ...,
handlers: Optional[List[Union[T_Handler, Dependent]]] = ...,
rule: Rule | T_RuleChecker | None = ...,
permission: Permission | T_PermissionChecker | None = ...,
handlers: list[T_Handler | Dependent] | None = ...,
temp: bool = ...,
expire_time: Optional[Union[datetime, timedelta]] = ...,
expire_time: datetime | timedelta | None = ...,
priority: int = ...,
block: bool = ...,
state: Optional[T_State] = ...,
state: T_State | None = ...,
): ...
def on(
self,
*,
type: str = ...,
rule: Optional[Union[Rule, T_RuleChecker]] = ...,
permission: Optional[Union[Permission, T_PermissionChecker]] = ...,
handlers: Optional[List[Union[T_Handler, Dependent]]] = ...,
rule: Rule | T_RuleChecker | None = ...,
permission: Permission | T_PermissionChecker | None = ...,
handlers: list[T_Handler | Dependent] | None = ...,
temp: bool = ...,
expire_time: Optional[Union[datetime, timedelta]] = ...,
expire_time: datetime | timedelta | None = ...,
priority: int = ...,
block: bool = ...,
state: Optional[T_State] = ...,
) -> Type[Matcher]: ...
state: T_State | None = ...,
) -> type[Matcher]: ...
def on_metaevent(
self,
*,
rule: Optional[Union[Rule, T_RuleChecker]] = ...,
handlers: Optional[List[Union[T_Handler, Dependent]]] = ...,
rule: Rule | T_RuleChecker | None = ...,
permission: Permission | T_PermissionChecker | None = ...,
handlers: list[T_Handler | Dependent] | None = ...,
temp: bool = ...,
expire_time: Optional[Union[datetime, timedelta]] = ...,
expire_time: datetime | timedelta | None = ...,
priority: int = ...,
block: bool = ...,
state: Optional[T_State] = ...,
) -> Type[Matcher]: ...
state: T_State | None = ...,
) -> type[Matcher]: ...
def on_message(
self,
*,
rule: Optional[Union[Rule, T_RuleChecker]] = ...,
permission: Optional[Union[Permission, T_PermissionChecker]] = ...,
handlers: Optional[List[Union[T_Handler, Dependent]]] = ...,
rule: Rule | T_RuleChecker | None = ...,
permission: Permission | T_PermissionChecker | None = ...,
handlers: list[T_Handler | Dependent] | None = ...,
temp: bool = ...,
expire_time: Optional[Union[datetime, timedelta]] = ...,
expire_time: datetime | timedelta | None = ...,
priority: int = ...,
block: bool = ...,
state: Optional[T_State] = ...,
) -> Type[Matcher]: ...
state: T_State | None = ...,
) -> type[Matcher]: ...
def on_notice(
self,
*,
rule: Optional[Union[Rule, T_RuleChecker]] = ...,
handlers: Optional[List[Union[T_Handler, Dependent]]] = ...,
rule: Rule | T_RuleChecker | None = ...,
permission: Permission | T_PermissionChecker | None = ...,
handlers: list[T_Handler | Dependent] | None = ...,
temp: bool = ...,
expire_time: Optional[Union[datetime, timedelta]] = ...,
expire_time: datetime | timedelta | None = ...,
priority: int = ...,
block: bool = ...,
state: Optional[T_State] = ...,
) -> Type[Matcher]: ...
state: T_State | None = ...,
) -> type[Matcher]: ...
def on_request(
self,
*,
rule: Optional[Union[Rule, T_RuleChecker]] = ...,
handlers: Optional[List[Union[T_Handler, Dependent]]] = ...,
rule: Rule | T_RuleChecker | None = ...,
permission: Permission | T_PermissionChecker | None = ...,
handlers: list[T_Handler | Dependent] | None = ...,
temp: bool = ...,
expire_time: Optional[Union[datetime, timedelta]] = ...,
expire_time: datetime | timedelta | None = ...,
priority: int = ...,
block: bool = ...,
state: Optional[T_State] = ...,
) -> Type[Matcher]: ...
state: T_State | None = ...,
) -> type[Matcher]: ...
def on_startswith(
self,
msg: Union[str, Tuple[str, ...]],
msg: str | tuple[str, ...],
*,
ignorecase: bool = ...,
rule: Optional[Union[Rule, T_RuleChecker]] = ...,
permission: Optional[Union[Permission, T_PermissionChecker]] = ...,
handlers: Optional[List[Union[T_Handler, Dependent]]] = ...,
rule: Rule | T_RuleChecker | None = ...,
permission: Permission | T_PermissionChecker | None = ...,
handlers: list[T_Handler | Dependent] | None = ...,
temp: bool = ...,
expire_time: Optional[Union[datetime, timedelta]] = ...,
expire_time: datetime | timedelta | None = ...,
priority: int = ...,
block: bool = ...,
state: Optional[T_State] = ...,
) -> Type[Matcher]: ...
state: T_State | None = ...,
) -> type[Matcher]: ...
def on_endswith(
self,
msg: Union[str, Tuple[str, ...]],
msg: str | tuple[str, ...],
*,
ignorecase: bool = ...,
rule: Optional[Union[Rule, T_RuleChecker]] = ...,
permission: Optional[Union[Permission, T_PermissionChecker]] = ...,
handlers: Optional[List[Union[T_Handler, Dependent]]] = ...,
rule: Rule | T_RuleChecker | None = ...,
permission: Permission | T_PermissionChecker | None = ...,
handlers: list[T_Handler | Dependent] | None = ...,
temp: bool = ...,
expire_time: Optional[Union[datetime, timedelta]] = ...,
expire_time: datetime | timedelta | None = ...,
priority: int = ...,
block: bool = ...,
state: Optional[T_State] = ...,
) -> Type[Matcher]: ...
state: T_State | None = ...,
) -> type[Matcher]: ...
def on_fullmatch(
self,
msg: Union[str, Tuple[str, ...]],
msg: str | tuple[str, ...],
*,
ignorecase: bool = ...,
rule: Optional[Union[Rule, T_RuleChecker]] = ...,
permission: Optional[Union[Permission, T_PermissionChecker]] = ...,
handlers: Optional[List[Union[T_Handler, Dependent]]] = ...,
rule: Rule | T_RuleChecker | None = ...,
permission: Permission | T_PermissionChecker | None = ...,
handlers: list[T_Handler | Dependent] | None = ...,
temp: bool = ...,
expire_time: Optional[Union[datetime, timedelta]] = ...,
expire_time: datetime | timedelta | None = ...,
priority: int = ...,
block: bool = ...,
state: Optional[T_State] = ...,
) -> Type[Matcher]: ...
state: T_State | None = ...,
) -> type[Matcher]: ...
def on_keyword(
self,
keywords: Set[str],
keywords: set[str],
*,
rule: Optional[Union[Rule, T_RuleChecker]] = ...,
permission: Optional[Union[Permission, T_PermissionChecker]] = ...,
handlers: Optional[List[Union[T_Handler, Dependent]]] = ...,
rule: Rule | T_RuleChecker | None = ...,
permission: Permission | T_PermissionChecker | None = ...,
handlers: list[T_Handler | Dependent] | None = ...,
temp: bool = ...,
expire_time: Optional[Union[datetime, timedelta]] = ...,
expire_time: datetime | timedelta | None = ...,
priority: int = ...,
block: bool = ...,
state: Optional[T_State] = ...,
) -> Type[Matcher]: ...
state: T_State | None = ...,
) -> type[Matcher]: ...
def on_command(
self,
cmd: Union[str, Tuple[str, ...]],
aliases: Optional[Set[Union[str, Tuple[str, ...]]]] = ...,
cmd: str | tuple[str, ...],
aliases: set[str | tuple[str, ...]] | None = ...,
force_whitespace: str | bool | None = ...,
*,
rule: Optional[Union[Rule, T_RuleChecker]] = ...,
permission: Optional[Union[Permission, T_PermissionChecker]] = ...,
handlers: Optional[List[Union[T_Handler, Dependent]]] = ...,
rule: Rule | T_RuleChecker | None = ...,
permission: Permission | T_PermissionChecker | None = ...,
handlers: list[T_Handler | Dependent] | None = ...,
temp: bool = ...,
expire_time: Optional[Union[datetime, timedelta]] = ...,
expire_time: datetime | timedelta | None = ...,
priority: int = ...,
block: bool = ...,
state: Optional[T_State] = ...,
) -> Type[Matcher]: ...
state: T_State | None = ...,
) -> type[Matcher]: ...
def on_shell_command(
self,
cmd: Union[str, Tuple[str, ...]],
aliases: Optional[Set[Union[str, Tuple[str, ...]]]] = ...,
parser: Optional[ArgumentParser] = ...,
cmd: str | tuple[str, ...],
aliases: set[str | tuple[str, ...]] | None = ...,
parser: ArgumentParser | None = ...,
*,
rule: Optional[Union[Rule, T_RuleChecker]] = ...,
permission: Optional[Union[Permission, T_PermissionChecker]] = ...,
handlers: Optional[List[Union[T_Handler, Dependent]]] = ...,
rule: Rule | T_RuleChecker | None = ...,
permission: Permission | T_PermissionChecker | None = ...,
handlers: list[T_Handler | Dependent] | None = ...,
temp: bool = ...,
expire_time: Optional[Union[datetime, timedelta]] = ...,
expire_time: datetime | timedelta | None = ...,
priority: int = ...,
block: bool = ...,
state: Optional[T_State] = ...,
) -> Type[Matcher]: ...
state: T_State | None = ...,
) -> type[Matcher]: ...
def on_regex(
self,
pattern: str,
flags: Union[int, re.RegexFlag] = ...,
flags: int | re.RegexFlag = ...,
*,
rule: Optional[Union[Rule, T_RuleChecker]] = ...,
permission: Optional[Union[Permission, T_PermissionChecker]] = ...,
handlers: Optional[List[Union[T_Handler, Dependent]]] = ...,
rule: Rule | T_RuleChecker | None = ...,
permission: Permission | T_PermissionChecker | None = ...,
handlers: list[T_Handler | Dependent] | None = ...,
temp: bool = ...,
expire_time: Optional[Union[datetime, timedelta]] = ...,
expire_time: datetime | timedelta | None = ...,
priority: int = ...,
block: bool = ...,
state: Optional[T_State] = ...,
) -> Type[Matcher]: ...
state: T_State | None = ...,
) -> type[Matcher]: ...
def on_type(
self,
types: Union[Type[Event], Tuple[Type[Event]]],
types: type[Event] | tuple[type[Event]],
*,
rule: Optional[Union[Rule, T_RuleChecker]] = ...,
permission: Optional[Union[Permission, T_PermissionChecker]] = ...,
handlers: Optional[List[Union[T_Handler, Dependent]]] = ...,
rule: Rule | T_RuleChecker | None = ...,
permission: Permission | T_PermissionChecker | None = ...,
handlers: list[T_Handler | Dependent] | None = ...,
temp: bool = ...,
expire_time: Optional[Union[datetime, timedelta]] = ...,
expire_time: datetime | timedelta | None = ...,
priority: int = ...,
block: bool = ...,
state: Optional[T_State] = ...,
) -> Type[Matcher]: ...
state: T_State | None = ...,
) -> type[Matcher]: ...

View File

@@ -1,9 +1,11 @@
"""本模块定义插件对象
"""本模块定义插件相关信息
FrontMatter:
sidebar_position: 3
description: nonebot.plugin.plugin 模块
"""
import contextlib
from types import ModuleType
from dataclasses import field, dataclass
from typing import TYPE_CHECKING, Any, Set, Dict, Type, Optional
@@ -11,10 +13,11 @@ from typing import TYPE_CHECKING, Any, Set, Dict, Type, Optional
from pydantic import BaseModel
from nonebot.matcher import Matcher
from . import _plugins as plugins # FIXME: backport for nonebug
from nonebot.utils import resolve_dot_notation
if TYPE_CHECKING:
from nonebot.adapters import Adapter
from .manager import PluginManager
@@ -23,14 +26,39 @@ class PluginMetadata:
"""插件元信息,由插件编写者提供"""
name: str
"""插件可阅读名称"""
"""插件名称"""
description: str
"""插件功能介绍"""
usage: str
"""插件使用方法"""
type: Optional[str] = None
"""插件类型,用于商店分类"""
homepage: Optional[str] = None
"""插件主页"""
config: Optional[Type[BaseModel]] = None
"""插件配置项"""
supported_adapters: Optional[Set[str]] = None
"""插件支持的适配器模块路径
格式为 `<module>[:<Adapter>]``~` 为 `nonebot.adapters.` 的缩写。
`None` 表示支持**所有适配器**。
"""
extra: Dict[Any, Any] = field(default_factory=dict)
"""插件额外信息,可由插件编写者自由扩展定义"""
def get_supported_adapters(self) -> Optional[Set[Type["Adapter"]]]:
"""获取当前已安装的插件支持适配器类列表"""
if self.supported_adapters is None:
return None
adapters = set()
for adapter in self.supported_adapters:
with contextlib.suppress(ModuleNotFoundError, AttributeError):
adapters.add(
resolve_dot_notation(adapter, "Adapter", "nonebot.adapters.")
)
return adapters
@dataclass(eq=False)
@@ -46,7 +74,7 @@ class Plugin:
manager: "PluginManager"
"""导入该插件的插件管理器"""
matcher: Set[Type[Matcher]] = field(default_factory=set)
"""插件定义的 `Matcher`"""
"""插件加载时定义的 `Matcher`"""
parent_plugin: Optional["Plugin"] = None
"""父插件"""
sub_plugins: Set["Plugin"] = field(default_factory=set)

View File

@@ -1,7 +1,18 @@
from nonebot import on_command
from nonebot.rule import to_me
from nonebot.adapters import Message
from nonebot.params import CommandArg
from nonebot.plugin import on_command
from nonebot.plugin import PluginMetadata
__plugin_meta__ = PluginMetadata(
name="echo",
description="重复你说的话",
usage="/echo [text]",
type="application",
homepage="https://github.com/nonebot/nonebot2/blob/master/nonebot/plugins/echo.py",
config=None,
supported_adapters=None,
)
echo = on_command("echo", to_me())

View File

@@ -2,8 +2,19 @@ from typing import Dict, AsyncGenerator
from nonebot.adapters import Event
from nonebot.params import Depends
from nonebot.plugin import PluginMetadata
from nonebot.message import IgnoredException, event_preprocessor
__plugin_meta__ = PluginMetadata(
name="唯一会话",
description="限制同一会话内同时只能运行一个响应器",
usage="加载插件后自动生效",
type="application",
homepage="https://github.com/nonebot/nonebot2/blob/master/nonebot/plugins/single_session.py",
config=None,
supported_adapters=None,
)
_running_matcher: Dict[str, int] = {}
@@ -15,7 +26,7 @@ async def matcher_mutex(event: Event) -> AsyncGenerator[bool, None]:
yield result
else:
current_event_id = id(event)
if event_id := _running_matcher.get(session_id, None):
if event_id := _running_matcher.get(session_id):
result = event_id != current_event_id
else:
_running_matcher[session_id] = current_event_id

View File

@@ -1,7 +1,8 @@
"""本模块是 {ref}`nonebot.matcher.Matcher.rule` 的类型定义。
每个事件响应器 {ref}`nonebot.matcher.Matcher` 拥有一个匹配规则 {ref}`nonebot.rule.Rule`
其中是 `RuleChecker` 的集合,只有当所有 `RuleChecker` 检查结果为 `True` 时继续运行
每个{ref}`事件响应器 <nonebot.matcher.Matcher>`拥有一个
{ref}`nonebot.rule.Rule`,其中是 `RuleChecker` 的集合
只有当所有 `RuleChecker` 检查结果为 `True` 时继续运行。
FrontMatter:
sidebar_position: 5
@@ -11,7 +12,9 @@ FrontMatter:
import re
import shlex
from argparse import Action
from gettext import gettext
from argparse import ArgumentError
from contextvars import ContextVar
from itertools import chain, product
from argparse import Namespace as Namespace
from argparse import ArgumentParser as ArgParser
@@ -38,40 +41,41 @@ from nonebot.log import logger
from nonebot.typing import T_State
from nonebot.exception import ParserExit
from nonebot.internal.rule import Rule as Rule
from nonebot.params import Command, EventToMe, CommandArg
from nonebot.adapters import Bot, Event, Message, MessageSegment
from nonebot.params import Command, EventToMe, CommandArg, CommandWhitespace
from nonebot.consts import (
CMD_KEY,
PREFIX_KEY,
REGEX_DICT,
SHELL_ARGS,
SHELL_ARGV,
CMD_ARG_KEY,
KEYWORD_KEY,
RAW_CMD_KEY,
REGEX_GROUP,
ENDSWITH_KEY,
CMD_START_KEY,
FULLMATCH_KEY,
REGEX_MATCHED,
STARTSWITH_KEY,
CMD_WHITESPACE_KEY,
)
T = TypeVar("T")
CMD_RESULT = TypedDict(
"CMD_RESULT",
{
"command": Optional[Tuple[str, ...]],
"raw_command": Optional[str],
"command_arg": Optional[Message[MessageSegment]],
"command_start": Optional[str],
},
)
TRIE_VALUE = NamedTuple(
"TRIE_VALUE", [("command_start", str), ("command", Tuple[str, ...])]
)
class CMD_RESULT(TypedDict):
command: Optional[Tuple[str, ...]]
raw_command: Optional[str]
command_arg: Optional[Message]
command_start: Optional[str]
command_whitespace: Optional[str]
class TRIE_VALUE(NamedTuple):
command_start: str
command: Tuple[str, ...]
parser_message: ContextVar[str] = ContextVar("parser_message")
class TrieRule:
@@ -87,7 +91,11 @@ class TrieRule:
@classmethod
def get_value(cls, bot: Bot, event: Event, state: T_State) -> CMD_RESULT:
prefix = CMD_RESULT(
command=None, raw_command=None, command_arg=None, command_start=None
command=None,
raw_command=None,
command_arg=None,
command_start=None,
command_whitespace=None,
)
state[PREFIX_KEY] = prefix
if event.get_type() != "message":
@@ -102,9 +110,23 @@ class TrieRule:
prefix[RAW_CMD_KEY] = pf.key
prefix[CMD_START_KEY] = value.command_start
prefix[CMD_KEY] = value.command
msg = message.copy()
msg.pop(0)
new_message = msg.__class__(segment_text[len(pf.key) :].lstrip())
# check whitespace
arg_str = segment_text[len(pf.key) :]
arg_str_stripped = arg_str.lstrip()
has_arg = arg_str_stripped or msg
if (
has_arg
and (stripped_len := len(arg_str) - len(arg_str_stripped)) > 0
):
prefix[CMD_WHITESPACE_KEY] = arg_str[:stripped_len]
# construct command arg
if arg_str_stripped:
new_message = msg.__class__(arg_str_stripped)
for new_segment in reversed(new_message):
msg.insert(0, new_segment)
prefix[CMD_ARG_KEY] = msg
@@ -140,8 +162,6 @@ class StartswithRule:
return hash((frozenset(self.msg), self.ignorecase))
async def __call__(self, event: Event, state: T_State) -> bool:
if event.get_type() != "message":
return False
try:
text = event.get_plaintext()
except Exception:
@@ -197,8 +217,6 @@ class EndswithRule:
return hash((frozenset(self.msg), self.ignorecase))
async def __call__(self, event: Event, state: T_State) -> bool:
if event.get_type() != "message":
return False
try:
text = event.get_plaintext()
except Exception:
@@ -254,8 +272,6 @@ class FullmatchRule:
return hash((frozenset(self.msg), self.ignorecase))
async def __call__(self, event: Event, state: T_State) -> bool:
if event.get_type() != "message":
return False
try:
text = event.get_plaintext()
except Exception:
@@ -306,8 +322,6 @@ class KeywordsRule:
return hash(frozenset(self.keywords))
async def __call__(self, event: Event, state: T_State) -> bool:
if event.get_type() != "message":
return False
try:
text = event.get_plaintext()
except Exception:
@@ -335,12 +349,18 @@ class CommandRule:
参数:
cmds: 指定命令元组列表
force_whitespace: 是否强制命令后必须有指定空白符
"""
__slots__ = ("cmds",)
__slots__ = ("cmds", "force_whitespace")
def __init__(self, cmds: List[Tuple[str, ...]]):
def __init__(
self,
cmds: List[Tuple[str, ...]],
force_whitespace: Optional[Union[str, bool]] = None,
):
self.cmds = tuple(cmds)
self.force_whitespace = force_whitespace
def __repr__(self) -> str:
return f"Command(cmds={self.cmds})"
@@ -353,11 +373,25 @@ class CommandRule:
def __hash__(self) -> int:
return hash((frozenset(self.cmds),))
async def __call__(self, cmd: Optional[Tuple[str, ...]] = Command()) -> bool:
return cmd in self.cmds
async def __call__(
self,
cmd: Optional[Tuple[str, ...]] = Command(),
cmd_arg: Optional[Message] = CommandArg(),
cmd_whitespace: Optional[str] = CommandWhitespace(),
) -> bool:
if cmd not in self.cmds:
return False
if self.force_whitespace is None or not cmd_arg:
return True
if isinstance(self.force_whitespace, str):
return self.force_whitespace == cmd_whitespace
return self.force_whitespace == (cmd_whitespace is not None)
def command(*cmds: Union[str, Tuple[str, ...]]) -> Rule:
def command(
*cmds: Union[str, Tuple[str, ...]],
force_whitespace: Optional[Union[str, bool]] = None,
) -> Rule:
"""匹配消息命令。
根据配置里提供的 {ref}``command_start` <nonebot.config.Config.command_start>`,
@@ -369,9 +403,10 @@ def command(*cmds: Union[str, Tuple[str, ...]]) -> Rule:
参数:
cmds: 命令文本或命令元组
force_whitespace: 是否强制命令后必须有指定空白符
用法:
使用默认 `command_start`, `command_sep` 配置
使用默认 `command_start`, `command_sep` 配置情况下:
命令 `("test",)` 可以匹配: `/test` 开头的消息
命令 `("test", "sub")` 可以匹配: `/test.sub` 开头的消息
@@ -400,12 +435,14 @@ def command(*cmds: Union[str, Tuple[str, ...]]) -> Rule:
f"{start}{sep.join(command)}", TRIE_VALUE(start, command)
)
return Rule(CommandRule(commands))
return Rule(CommandRule(commands, force_whitespace))
class ArgumentParser(ArgParser):
"""`shell_like` 命令参数解析器,解析出错时不会退出程序。
支持 {ref}`nonebot.adapters.Message` 富文本解析。
用法:
用法与 `argparse.ArgumentParser` 相同,
参考文档: [argparse](https://docs.python.org/3/library/argparse.html)
@@ -414,16 +451,39 @@ class ArgumentParser(ArgParser):
if TYPE_CHECKING:
@overload
def parse_args(
self, args: Optional[Sequence[Union[str, MessageSegment]]] = ...
) -> Namespace:
def parse_known_args(
self,
args: Optional[Sequence[Union[str, MessageSegment]]] = None,
namespace: None = None,
) -> Tuple[Namespace, List[Union[str, MessageSegment]]]:
...
@overload
def parse_known_args(
self, args: Optional[Sequence[Union[str, MessageSegment]]], namespace: T
) -> Tuple[T, List[Union[str, MessageSegment]]]:
...
@overload
def parse_known_args(
self, *, namespace: T
) -> Tuple[T, List[Union[str, MessageSegment]]]:
...
def parse_known_args(
self,
args: Optional[Sequence[Union[str, MessageSegment]]] = None,
namespace: Optional[T] = None,
) -> Tuple[Union[Namespace, T], List[Union[str, MessageSegment]]]:
...
@overload
def parse_args(
self, args: Optional[Sequence[Union[str, MessageSegment]]], namespace: None
self,
args: Optional[Sequence[Union[str, MessageSegment]]] = None,
namespace: None = None,
) -> Namespace:
... # type: ignore[misc]
...
@overload
def parse_args(
@@ -431,12 +491,20 @@ class ArgumentParser(ArgParser):
) -> T:
...
@overload
def parse_args(self, *, namespace: T) -> T:
...
def parse_args(
self,
args: Optional[Sequence[Union[str, MessageSegment]]] = None,
namespace: Optional[T] = None,
) -> Union[Namespace, T]:
...
result, argv = self.parse_known_args(args, namespace)
if argv:
msg = gettext("unrecognized arguments: %s")
self.error(msg % " ".join(map(str, argv)))
return cast(Union[Namespace, T], result)
def _parse_optional(
self, arg_string: Union[str, MessageSegment]
@@ -446,13 +514,15 @@ class ArgumentParser(ArgParser):
)
def _print_message(self, message: str, file: Optional[IO[str]] = None):
if message:
setattr(self, "_message", getattr(self, "_message", "") + message)
if (msg := parser_message.get(None)) is not None:
parser_message.set(msg + message)
else:
super()._print_message(message, file)
def exit(self, status: int = 0, message: Optional[str] = None):
if message:
self._print_message(message)
raise ParserExit(status=status, message=getattr(self, "_message", None))
raise ParserExit(status=status, message=parser_message.get(None))
class ShellCommandRule:
@@ -499,13 +569,16 @@ class ShellCommandRule:
)
if self.parser:
t = parser_message.set("")
try:
args = self.parser.parse_args(state[SHELL_ARGV])
state[SHELL_ARGS] = args
except ArgumentError as e:
except ArgumentError as e: # pragma: py-gte-39
state[SHELL_ARGS] = ParserExit(status=2, message=str(e))
except ParserExit as e:
state[SHELL_ARGS] = e
finally:
parser_message.reset(t)
return True
@@ -517,10 +590,14 @@ def shell_command(
根据配置里提供的 {ref}``command_start` <nonebot.config.Config.command_start>`,
{ref}``command_sep` <nonebot.config.Config.command_sep>` 判断消息是否为命令。
可以通过 {ref}`nonebot.params.Command` 获取匹配成功的命令(例: `("test",)`
通过 {ref}`nonebot.params.RawCommand` 获取匹配成功的原始命令文本(例: `"/test"`
通过 {ref}`nonebot.params.ShellCommandArgv` 获取解析前的参数列表(例: `["arg", "-h"]`
通过 {ref}`nonebot.params.ShellCommandArgs` 获取解析后的参数字典(例: `{"arg": "arg", "h": True}`
可以通过 {ref}`nonebot.params.Command` 获取匹配成功的命令
(例: `("test",)`
通过 {ref}`nonebot.params.RawCommand` 获取匹配成功的原始命令文本
(例: `"/test"`
通过 {ref}`nonebot.params.ShellCommandArgv` 获取解析前的参数列表
(例: `["arg", "-h"]`
通过 {ref}`nonebot.params.ShellCommandArgs` 获取解析后的参数字典
(例: `{"arg": "arg", "h": True}`)。
:::warning 警告
如果参数解析失败,则通过 {ref}`nonebot.params.ShellCommandArgs`
@@ -532,7 +609,8 @@ def shell_command(
parser: {ref}`nonebot.rule.ArgumentParser` 对象
用法:
使用默认 `command_start`, `command_sep` 配置,更多示例参考 `argparse` 标准库文档。
使用默认 `command_start`, `command_sep` 配置,更多示例参考
[argparse](https://docs.python.org/3/library/argparse.html) 标准库文档。
```python
from nonebot.rule import ArgumentParser
@@ -600,16 +678,12 @@ class RegexRule:
return hash((self.regex, self.flags))
async def __call__(self, event: Event, state: T_State) -> bool:
if event.get_type() != "message":
return False
try:
msg = event.get_message()
except Exception:
return False
if matched := re.search(self.regex, str(msg), self.flags):
state[REGEX_MATCHED] = matched.group()
state[REGEX_GROUP] = matched.groups()
state[REGEX_DICT] = matched.groupdict()
state[REGEX_MATCHED] = matched
return True
else:
return False
@@ -618,7 +692,7 @@ class RegexRule:
def regex(regex: str, flags: Union[int, re.RegexFlag] = 0) -> Rule:
"""匹配符合正则表达式的消息字符串。
可以通过 {ref}`nonebot.params.RegexMatched` 获取匹配成功的字符串,
可以通过 {ref}`nonebot.params.RegexStr` 获取匹配成功的字符串,
通过 {ref}`nonebot.params.RegexGroup` 获取匹配成功的 group 元组,
通过 {ref}`nonebot.params.RegexDict` 获取匹配成功的 group 字典。
@@ -631,7 +705,8 @@ def regex(regex: str, flags: Union[int, re.RegexFlag] = 0) -> Rule:
:::
:::tip 提示
正则表达式匹配使用 `EventMessage` 的 `str` 字符串,而非 `EventMessage` 的 `PlainText` 纯文本字符串
正则表达式匹配使用 `EventMessage` 的 `str` 字符串,
而非 `EventMessage` 的 `PlainText` 纯文本字符串
:::
"""

View File

@@ -1,17 +1,17 @@
"""本模块定义了 NoneBot 模块中共享的一些类型。
下面的文档中,「类型」部分使用 Python 的 Type Hint 语法,
使用 Python 的 Type Hint 语法,
参考 [`PEP 484`](https://www.python.org/dev/peps/pep-0484/),
[`PEP 526`](https://www.python.org/dev/peps/pep-0526/) 和
[`typing`](https://docs.python.org/3/library/typing.html)。
除了 Python 内置的类型,下面还出现了如下 NoneBot 自定类型,实际上它们是 Python 内置类型的别名。
FrontMatter:
sidebar_position: 11
description: nonebot.typing 模块
"""
import warnings
from typing_extensions import ParamSpec, TypeAlias, override
from typing import (
TYPE_CHECKING,
Any,
@@ -30,28 +30,31 @@ if TYPE_CHECKING:
from nonebot.permission import Permission
T = TypeVar("T")
P = ParamSpec("P")
T_Wrapped = TypeVar("T_Wrapped", bound=Callable)
T_Wrapped: TypeAlias = Callable[P, T]
def overrides(InterfaceClass: object) -> Callable[[T_Wrapped], T_Wrapped]:
def overrides(InterfaceClass: object):
"""标记一个方法为父类 interface 的 implement"""
def overrider(func: T_Wrapped) -> T_Wrapped:
assert func.__name__ in dir(InterfaceClass), f"Error method: {func.__name__}"
return func
return overrider
warnings.warn(
"overrides is deprecated and will be removed in a future version, "
"use @typing_extensions.override instead. "
"See [PEP 698](https://peps.python.org/pep-0698/) for more details.",
DeprecationWarning,
)
return override
# state
T_State = Dict[Any, Any]
T_State: TypeAlias = Dict[Any, Any]
"""事件处理状态 State 类型"""
_DependentCallable = Union[Callable[..., T], Callable[..., Awaitable[T]]]
_DependentCallable: TypeAlias = Union[Callable[..., T], Callable[..., Awaitable[T]]]
# driver hooks
T_BotConnectionHook = _DependentCallable[Any]
T_BotConnectionHook: TypeAlias = _DependentCallable[Any]
"""Bot 连接建立时钩子函数
依赖参数:
@@ -60,7 +63,7 @@ T_BotConnectionHook = _DependentCallable[Any]
- BotParam: Bot 对象
- DefaultParam: 带有默认值的参数
"""
T_BotDisconnectionHook = _DependentCallable[Any]
T_BotDisconnectionHook: TypeAlias = _DependentCallable[Any]
"""Bot 连接断开时钩子函数
依赖参数:
@@ -71,15 +74,15 @@ T_BotDisconnectionHook = _DependentCallable[Any]
"""
# api hooks
T_CallingAPIHook = Callable[["Bot", str, Dict[str, Any]], Awaitable[Any]]
T_CallingAPIHook: TypeAlias = Callable[["Bot", str, Dict[str, Any]], Awaitable[Any]]
"""`bot.call_api` 钩子函数"""
T_CalledAPIHook = Callable[
T_CalledAPIHook: TypeAlias = Callable[
["Bot", Optional[Exception], str, Dict[str, Any], Any], Awaitable[Any]
]
"""`bot.call_api` 后执行的函数,参数分别为 bot, exception, api, data, result"""
# event hooks
T_EventPreProcessor = _DependentCallable[Any]
T_EventPreProcessor: TypeAlias = _DependentCallable[Any]
"""事件预处理函数 EventPreProcessor 类型
依赖参数:
@@ -90,7 +93,7 @@ T_EventPreProcessor = _DependentCallable[Any]
- StateParam: State 对象
- DefaultParam: 带有默认值的参数
"""
T_EventPostProcessor = _DependentCallable[Any]
T_EventPostProcessor: TypeAlias = _DependentCallable[Any]
"""事件预处理函数 EventPostProcessor 类型
依赖参数:
@@ -103,7 +106,7 @@ T_EventPostProcessor = _DependentCallable[Any]
"""
# matcher run hooks
T_RunPreProcessor = _DependentCallable[Any]
T_RunPreProcessor: TypeAlias = _DependentCallable[Any]
"""事件响应器运行前预处理函数 RunPreProcessor 类型
依赖参数:
@@ -115,7 +118,7 @@ T_RunPreProcessor = _DependentCallable[Any]
- MatcherParam: Matcher 对象
- DefaultParam: 带有默认值的参数
"""
T_RunPostProcessor = _DependentCallable[Any]
T_RunPostProcessor: TypeAlias = _DependentCallable[Any]
"""事件响应器运行后后处理函数 RunPostProcessor 类型
依赖参数:
@@ -130,7 +133,7 @@ T_RunPostProcessor = _DependentCallable[Any]
"""
# rule, permission
T_RuleChecker = _DependentCallable[bool]
T_RuleChecker: TypeAlias = _DependentCallable[bool]
"""RuleChecker 即判断是否响应事件的处理函数。
依赖参数:
@@ -141,7 +144,7 @@ T_RuleChecker = _DependentCallable[bool]
- StateParam: State 对象
- DefaultParam: 带有默认值的参数
"""
T_PermissionChecker = _DependentCallable[bool]
T_PermissionChecker: TypeAlias = _DependentCallable[bool]
"""PermissionChecker 即判断事件是否满足权限的处理函数。
依赖参数:
@@ -152,10 +155,11 @@ T_PermissionChecker = _DependentCallable[bool]
- DefaultParam: 带有默认值的参数
"""
T_Handler = _DependentCallable[Any]
T_Handler: TypeAlias = _DependentCallable[Any]
"""Handler 处理函数。"""
T_TypeUpdater = _DependentCallable[str]
"""TypeUpdater 在 Matcher.pause, Matcher.reject 时被运行,用于更新响应的事件类型。默认会更新为 `message`。
T_TypeUpdater: TypeAlias = _DependentCallable[str]
"""TypeUpdater 在 Matcher.pause, Matcher.reject 时被运行,用于更新响应的事件类型。
默认会更新为 `message`。
依赖参数:
@@ -166,8 +170,9 @@ T_TypeUpdater = _DependentCallable[str]
- MatcherParam: Matcher 对象
- DefaultParam: 带有默认值的参数
"""
T_PermissionUpdater = _DependentCallable["Permission"]
"""PermissionUpdater 在 Matcher.pause, Matcher.reject 时被运行,用于更新会话对象权限。默认会更新为当前事件的触发对象。
T_PermissionUpdater: TypeAlias = _DependentCallable["Permission"]
"""PermissionUpdater 在 Matcher.pause, Matcher.reject 时被运行,用于更新会话对象权限。
默认会更新为当前事件的触发对象。
依赖参数:
@@ -178,5 +183,5 @@ T_PermissionUpdater = _DependentCallable["Permission"]
- MatcherParam: Matcher 对象
- DefaultParam: 带有默认值的参数
"""
T_DependencyCache = Dict[_DependentCallable[Any], "Task[Any]"]
T_DependencyCache: TypeAlias = Dict[_DependentCallable[Any], "Task[Any]"]
"""依赖缓存, 用于存储依赖函数的返回值"""

View File

@@ -9,16 +9,19 @@ import re
import json
import asyncio
import inspect
import importlib
import dataclasses
from pathlib import Path
from contextvars import copy_context
from functools import wraps, partial
from contextlib import asynccontextmanager
from typing_extensions import ParamSpec, get_args, get_origin
from typing_extensions import ParamSpec, get_args, override, get_origin
from typing import (
Any,
Type,
Tuple,
Union,
Generic,
TypeVar,
Callable,
Optional,
@@ -31,7 +34,6 @@ from typing import (
from pydantic.typing import is_union, is_none_type
from nonebot.log import logger
from nonebot.typing import overrides
P = ParamSpec("P")
R = TypeVar("R")
@@ -56,8 +58,13 @@ def generic_check_issubclass(
) -> bool:
"""检查 cls 是否是 class_or_tuple 中的一个类型子类。
特别的,如果 cls 是 `typing.Union` 或 `types.UnionType` 类型,
则会检查其中的类型是否是 class_or_tuple 中的一个类型子类。None 会被忽略)
特别的
- 如果 cls 是 `typing.Union` 或 `types.UnionType` 类型,
则会检查其中的所有类型是否是 class_or_tuple 中一个类型的子类或 None。
- 如果 cls 是 `typing.TypeVar` 类型,
则会检查其 `__bound__` 或 `__constraints__`
是否是 class_or_tuple 中一个类型的子类或 None。
"""
try:
return issubclass(cls, class_or_tuple)
@@ -68,8 +75,18 @@ def generic_check_issubclass(
is_none_type(type_) or generic_check_issubclass(type_, class_or_tuple)
for type_ in get_args(cls)
)
# ensure generic List, Dict can be checked
elif origin:
return issubclass(origin, class_or_tuple)
elif isinstance(cls, TypeVar):
if cls.__constraints__:
return all(
is_none_type(type_)
or generic_check_issubclass(type_, class_or_tuple)
for type_ in cls.__constraints__
)
elif cls.__bound__:
return generic_check_issubclass(cls.__bound__, class_or_tuple)
return False
@@ -110,7 +127,8 @@ def run_sync(call: Callable[P, R]) -> Callable[P, Coroutine[None, None, R]]:
async def _wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
loop = asyncio.get_running_loop()
pfunc = partial(call, *args, **kwargs)
result = await loop.run_in_executor(None, pfunc)
context = copy_context()
result = await loop.run_in_executor(None, partial(context.run, pfunc))
return result
return _wrapper
@@ -135,6 +153,7 @@ async def run_sync_ctx_manager(
async def run_coro_with_catch(
coro: Coroutine[Any, Any, T],
exc: Tuple[Type[Exception], ...],
return_on_err: None = None,
) -> Union[T, None]:
...
@@ -153,6 +172,17 @@ async def run_coro_with_catch(
exc: Tuple[Type[Exception], ...],
return_on_err: Optional[R] = None,
) -> Optional[Union[T, R]]:
"""运行协程并当遇到指定异常时返回指定值。
参数:
coro: 要运行的协程
exc: 要捕获的异常
return_on_err: 当发生异常时返回的值
返回:
协程的返回值或发生异常时的指定值
"""
try:
return await coro
except exc:
@@ -167,17 +197,44 @@ def get_name(obj: Any) -> str:
def path_to_module_name(path: Path) -> str:
rel_path = path.resolve().relative_to(Path(".").resolve())
"""转换路径为模块名"""
rel_path = path.resolve().relative_to(Path.cwd().resolve())
if rel_path.stem == "__init__":
return ".".join(rel_path.parts[:-1])
else:
return ".".join(rel_path.parts[:-1] + (rel_path.stem,))
class DataclassEncoder(json.JSONEncoder):
"""在JSON序列化 {re}`nonebot.adapters._message.Message` (List[Dataclass]) 时使用的 `JSONEncoder`"""
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
@overrides(json.JSONEncoder)
class classproperty(Generic[T]):
"""类属性装饰器"""
def __init__(self, func: Callable[[Any], T]) -> None:
self.func = func
def __get__(self, instance: Any, owner: Optional[Type[Any]] = None) -> T:
return self.func(type(instance) if owner is None else owner)
class DataclassEncoder(json.JSONEncoder):
"""可以序列化 {ref}`nonebot.adapters.Message`(List[Dataclass]) 的 `JSONEncoder`"""
@override
def default(self, o):
if dataclasses.is_dataclass(o):
return {f.name: getattr(o, f.name) for f in dataclasses.fields(o)}
@@ -193,6 +250,8 @@ def logger_wrapper(logger_name: str):
返回:
日志记录函数
日志记录函数的参数:
- level: 日志等级
- message: 日志信息
- exception: 异常信息

View File

@@ -11,10 +11,12 @@
"start": "yarn workspace nonebot start",
"serve": "yarn workspace nonebot serve",
"clear": "yarn workspace nonebot clear",
"prettier": "prettier --config ./.prettierrc --write \"./website/**/*.md\""
"prettier": "prettier --config ./.prettierrc --write \"./website/\"",
"pyright": "pyright"
},
"devDependencies": {
"cross-env": "^7.0.3",
"prettier": "^2.5.0"
"prettier": "^2.5.0",
"pyright": "^1.1.317"
}
}

View File

@@ -1,5 +1,5 @@
<p align="center">
<a href="https://v2.nonebot.dev/"><img src="https://raw.githubusercontent.com/nonebot/nonebot2/master/docs/.vuepress/public/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>
<div align="center">
@@ -22,6 +22,6 @@ _✨ NoneBot 本地文档插件 ✨_
## 使用方式
加载插件并启动 Bot ,在浏览器内打开 `http://host:port/docs/`
加载插件并启动 Bot ,在浏览器内打开 `http://host:port/website/`
具体网址会在控制台内输出。

View File

@@ -2,6 +2,17 @@ import importlib
import nonebot
from nonebot.log import logger
from nonebot.plugin import PluginMetadata
__plugin_meta__ = PluginMetadata(
name="NoneBot 离线文档",
description="在本地查看 NoneBot 文档",
usage="启动机器人后访问 http://localhost:port/website/ 查看文档",
type="application",
homepage="https://github.com/nonebot/nonebot2/blob/master/packages/nonebot-plugin-docs",
config=None,
supported_adapters=None,
)
def init():
@@ -17,7 +28,7 @@ def init():
register_route(driver)
host = str(driver.config.host)
port = driver.config.port
if host in ["0.0.0.0", "127.0.0.1"]:
if host in {"0.0.0.0", "127.0.0.1"}:
host = "localhost"
logger.opt(colors=True).info(
f"Nonebot docs will be running at: "

View File

@@ -1,6 +1,6 @@
[tool.poetry]
name = "nonebot-plugin-docs"
version = "2.0.0-beta.1"
version = "2.0.0"
description = "View NoneBot2 Docs Locally"
authors = ["yanyongyu <yyy@nonebot.dev>"]
license = "MIT"
@@ -13,7 +13,7 @@ include = ["nonebot_plugin_docs/dist/**/*"]
[tool.poetry.dependencies]
python = "^3.8"
nonebot2 = "^2.0.0-beta.1"
nonebot2 = "^2.0.0"
[tool.poetry.dev-dependencies]

3856
poetry.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,71 +1,77 @@
[tool.poetry]
name = "nonebot2"
version = "2.0.0rc2"
version = "2.1.0"
description = "An asynchronous python bot framework."
authors = ["yanyongyu <yyy@nonebot.dev>"]
license = "MIT"
readme = "README.md"
homepage = "https://v2.nonebot.dev/"
homepage = "https://nonebot.dev/"
repository = "https://github.com/nonebot/nonebot2"
documentation = "https://v2.nonebot.dev/"
documentation = "https://nonebot.dev/"
keywords = ["bot", "qq", "qqbot", "mirai", "coolq"]
classifiers = [
"Development Status :: 5 - Production/Stable",
"Framework :: Robot Framework",
"Framework :: Robot Framework :: Library",
"Operating System :: OS Independent",
"Programming Language :: Python :: 3"
]
packages = [
{ include = "nonebot" },
"Programming Language :: Python :: 3",
]
packages = [{ include = "nonebot" }]
include = ["nonebot/py.typed"]
[tool.poetry.urls]
"Bug Tracker" = "https://github.com/nonebot/nonebot2/issues"
"Changelog" = "https://nonebot.dev/changelog"
"Funding" = "https://afdian.net/@nonebot"
[tool.poetry.dependencies]
python = "^3.8"
yarl = "^1.7.2"
loguru = "^0.6.0"
pygtrie = "^2.4.1"
fastapi = ">=0.87.0,<1.0.0"
tomlkit = ">=0.10.0,<1.0.0"
typing-extensions = ">=3.10.0,<5.0.0"
loguru = ">=0.6.0,<1.0.0"
typing-extensions = ">=4.4.0,<5.0.0"
tomli = { version = "^2.0.1", python = "<3.11" }
pydantic = { version = "^1.10.0", extras = ["dotenv"] }
uvicorn = { version = ">=0.20.0,<1.0.0", extras = ["standard"] }
websockets = { version="^10.0", optional = true }
websockets = { version = ">=10.0", optional = true }
Quart = { version = ">=0.18.0,<1.0.0", optional = true }
fastapi = { version = ">=0.93.0,<1.0.0", optional = true }
aiohttp = { version = "^3.7.4", extras = ["speedups"], optional = true }
httpx = { version = ">=0.20.0,<1.0.0", extras = ["http2"], optional = true }
uvicorn = { version = ">=0.20.0,<1.0.0", extras = [
"standard",
], optional = true }
[tool.poetry.group.dev.dependencies]
isort = "^5.10.1"
black = "^22.1.0"
black = "^23.1.0"
nonemoji = "^0.1.2"
pre-commit = "^2.16.0"
pre-commit = "^3.0.0"
ruff = ">=0.0.272,<1.0.0"
[tool.poetry.group.test.dependencies]
nonebug = "^0.3.0"
werkzeug = "^2.3.6"
pytest-cov = "^4.0.0"
pytest-xdist = "^3.0.2"
pytest-asyncio = "^0.20.0"
nonebug = { git = "https://github.com/nonebot/nonebug.git" }
pytest-asyncio = "^0.21.0"
coverage-conditional-plugin = "^0.9.0"
[tool.poetry.group.docs.dependencies]
nb-autodoc = { git = "https://github.com/nonebot/nb-autodoc.git" }
nb-autodoc = "^1.0.0a5"
[tool.poetry.extras]
quart = ["quart"]
httpx = ["httpx"]
aiohttp = ["aiohttp"]
websockets = ["websockets"]
all = ["quart", "aiohttp", "httpx", "websockets"]
quart = ["quart", "uvicorn"]
fastapi = ["fastapi", "uvicorn"]
all = ["fastapi", "quart", "aiohttp", "httpx", "websockets", "uvicorn"]
[tool.pytest.ini_options]
asyncio_mode = "auto"
addopts = "--cov=nonebot --cov-report=term-missing"
filterwarnings = [
"error",
"ignore::DeprecationWarning",
]
asyncio_mode = "strict"
addopts = "--cov=nonebot --cov-append --cov-report=term-missing"
filterwarnings = ["error", "ignore::DeprecationWarning"]
[tool.black]
line-length = 88
@@ -83,6 +89,31 @@ force_sort_within_sections = true
src_paths = ["nonebot", "tests"]
extra_standard_library = ["typing_extensions"]
[tool.ruff]
select = ["E", "W", "F", "UP", "C", "T", "PYI", "PT", "Q"]
ignore = ["E402", "C901", "UP037"]
line-length = 88
target-version = "py38"
[tool.ruff.flake8-pytest-style]
fixture-parentheses = false
mark-parentheses = false
[tool.pyright]
pythonVersion = "3.8"
pythonPlatform = "All"
executionEnvironments = [
{ root = "./tests", extraPaths = [
"./",
] },
{ root = "./" },
]
typeCheckingMode = "basic"
reportShadowedImports = false
[build-system]
requires = ["poetry_core>=1.0.0"]
build-backend = "poetry.core.masonry.api"

View File

@@ -1,9 +1,26 @@
[run]
plugins =
coverage_conditional_plugin
[report]
exclude_lines =
pragma: no cover
def __repr__
def __str__
pragma: no cover
if TYPE_CHECKING:
@(typing\.)?overload
if (typing\.)?TYPE_CHECKING( is True)?:
@(abc\.)?abstractmethod
raise NotImplementedError
warnings\.warn
\.\.\.
pass
if __name__ == .__main__.:
[coverage_conditional_plugin]
rules =
"sys_platform != 'win32'": py-win32
"sys_platform != 'linux'": py-linux
"sys_platform != 'darwin'": py-darwin
"sys_version_info < (3, 9)": py-gte-39
"sys_version_info < (3, 11)": py-gte-311
"sys_version_info >= (3, 11)": py-lt-311

View File

@@ -1,6 +1,8 @@
LOG_LEVEL=TRACE
NICKNAME=["test"]
SUPERUSERS=["test", "fake:faketest"]
API_TIMEOUT
SIMPLE_NONE
COMMON_OVERRIDE=new
CONFIG_FROM_ENV=
CONFIG_OVERRIDE=old

View File

@@ -1,21 +1,64 @@
import os
import threading
from pathlib import Path
from typing import TYPE_CHECKING, Set
from typing import TYPE_CHECKING, Set, Generator
import pytest
from nonebug import NONEBOT_INIT_KWARGS
from werkzeug.serving import BaseWSGIServer, make_server
import nonebot
from nonebot.config import Env
from fake_server import request_handler
from nonebot.drivers import URL, Driver
from nonebot import _resolve_combine_expr
os.environ["CONFIG_FROM_ENV"] = '{"test": "test"}'
os.environ["CONFIG_OVERRIDE"] = "new"
if TYPE_CHECKING:
from nonebot.plugin import Plugin
@pytest.fixture
def load_plugin(nonebug_init: None) -> Set["Plugin"]:
import nonebot
def pytest_configure(config: pytest.Config) -> None:
config.stash[NONEBOT_INIT_KWARGS] = {"config_from_init": "init"}
@pytest.fixture(name="driver")
def load_driver(request: pytest.FixtureRequest) -> Driver:
driver_name = getattr(request, "param", None)
global_driver = nonebot.get_driver()
if driver_name is None:
return global_driver
DriverClass = _resolve_combine_expr(driver_name)
return DriverClass(Env(environment=global_driver.env), global_driver.config)
@pytest.fixture(scope="session", autouse=True)
def load_plugin(nonebug_init: None) -> Set["Plugin"]:
# preload global plugins
return nonebot.load_plugins(str(Path(__file__).parent / "plugins"))
@pytest.fixture
def load_example(nonebug_init: None) -> Set["Plugin"]:
import nonebot
@pytest.fixture(scope="session", autouse=True)
def load_builtin_plugin(nonebug_init: None) -> Set["Plugin"]:
# preload builtin plugins
return nonebot.load_builtin_plugins("echo", "single_session")
return nonebot.load_plugins(str(Path(__file__).parent / "examples"))
@pytest.fixture(scope="session", autouse=True)
def server() -> Generator[BaseWSGIServer, None, None]:
server = make_server("127.0.0.1", 0, app=request_handler)
thread = threading.Thread(target=server.serve_forever)
thread.start()
try:
yield server
finally:
server.shutdown()
thread.join()
@pytest.fixture(scope="session")
def server_url(server: BaseWSGIServer) -> URL:
return URL(f"http://{server.host}:{server.port}")

0
tests/dynamic/path.py Normal file
View File

View File

View File

0
tests/dynamic/simple.py Normal file
View File

View File

@@ -1,29 +0,0 @@
from nonebot import on_command
from nonebot.rule import to_me
from nonebot.matcher import Matcher
from nonebot.adapters import Message
from nonebot.params import Arg, CommandArg, ArgPlainText
weather = on_command("weather", rule=to_me(), aliases={"天气", "天气预报"}, priority=5)
@weather.handle()
async def handle_first_receive(matcher: Matcher, args: Message = CommandArg()):
plain_text = args.extract_plain_text() # 首次发送命令时跟随的参数,例:/天气 上海则args为上海
if plain_text:
matcher.set_arg("city", args) # 如果用户发送了参数则直接赋值
@weather.got("city", prompt="你想查询哪个城市的天气呢?")
async def handle_city(city: Message = Arg(), city_name: str = ArgPlainText("city")):
if city_name not in ["北京", "上海"]: # 如果参数不符合要求,则提示用户重新输入
# 可以使用平台的 Message 类直接构造模板消息
await weather.reject(city.template("你想查询的城市 {city} 暂不支持,请重新输入!"))
city_weather = await get_weather(city_name)
await weather.finish(city_weather)
# 在这里编写获取天气信息的函数
async def get_weather(city: str) -> str:
return f"{city}的天气是..."

69
tests/fake_server.py Normal file
View File

@@ -0,0 +1,69 @@
import json
import base64
from typing import Dict, List, Union, TypeVar
from werkzeug import Request, Response
from werkzeug.datastructures import MultiDict
K = TypeVar("K")
V = TypeVar("V")
def json_safe(string, content_type="application/octet-stream") -> str:
try:
string = string.decode("utf-8")
json.dumps(string)
return string
except (ValueError, TypeError):
return b"".join(
[
b"data:",
content_type.encode("utf-8"),
b";base64,",
base64.b64encode(string),
]
).decode("utf-8")
def flattern(d: "MultiDict[K, V]") -> Dict[K, Union[V, List[V]]]:
return {k: v[0] if len(v) == 1 else v for k, v in d.to_dict(flat=False).items()}
@Request.application
def request_handler(request: Request) -> Response:
try:
_json = json.loads(request.data.decode("utf-8"))
except (ValueError, TypeError):
_json = None
return Response(
json.dumps(
{
"url": request.url,
"method": request.method,
"origin": request.headers.get("X-Forwarded-For", request.remote_addr),
"headers": flattern(
MultiDict((k, v) for k, v in request.headers.items())
),
"args": flattern(request.args),
"form": flattern(request.form),
"data": json_safe(request.data),
"json": _json,
"files": flattern(
MultiDict(
(
k,
json_safe(
v.read(),
request.files[k].content_type
or "application/octet-stream",
),
)
for k, v in request.files.items()
)
),
}
),
status=200,
content_type="application/json",
)

View File

@@ -1,4 +1,4 @@
{
"plugins": [],
"plugin_dirs": ["plugins"]
"plugin_dirs": []
}

View File

@@ -1,3 +1,3 @@
[tool.nonebot]
plugins = []
plugin_dirs = ["plugins"]
plugin_dirs = []

View File

@@ -1 +1,3 @@
assert False
import pytest
pytest.fail("should not be imported")

View File

@@ -0,0 +1,3 @@
from nonebot import on
matcher = on("message", temp=False, expire_time=None, priority=1, block=True)

View File

@@ -2,6 +2,7 @@ from nonebot.matcher import Matcher
from nonebot.permission import USER, Permission
default_permission = Permission()
new_permission = Permission()
test_permission_updater = Matcher.new(permission=default_permission)
@@ -14,4 +15,4 @@ test_custom_updater = Matcher.new(permission=default_permission)
@test_custom_updater.permission_updater
async def _() -> Permission:
return default_permission
return new_permission

View File

@@ -90,3 +90,6 @@ async def overload(event: FakeEvent):
@test_overload.handle()
async def finish():
await test_overload.finish()
test_destroy = on_message()

View File

@@ -1,5 +1,6 @@
from pydantic import BaseModel
from nonebot.adapters import Adapter
from nonebot.plugin import PluginMetadata
@@ -7,10 +8,17 @@ class Config(BaseModel):
custom: str = ""
class FakeAdapter(Adapter):
...
__plugin_meta__ = PluginMetadata(
name="测试插件",
description="测试插件元信息",
usage="无法使用",
type="application",
homepage="https://nonebot.dev",
config=Config,
supported_adapters={"~onebot.v11", "plugins.metadata:FakeAdapter"},
extra={"author": "NoneBot"},
)

View File

@@ -0,0 +1,11 @@
from nonebot.plugin import PluginMetadata
__plugin_meta__ = PluginMetadata(
name="测试插件2",
description="测试继承适配器",
usage="无法使用",
type="application",
homepage="https://nonebot.dev",
supported_adapters={"~onebot.v11", "~onebot.v12"},
extra={"author": "NoneBot"},
)

View File

@@ -1,6 +1,5 @@
from pathlib import Path
import nonebot
from nonebot.plugin import PluginManager, _managers
manager = PluginManager(

View File

@@ -1 +1 @@
from .nested_subplugin2 import a
from .nested_subplugin2 import a # noqa: F401

View File

@@ -1,4 +1,6 @@
from nonebot.adapters import Event, Message
from typing_extensions import Annotated
from nonebot.adapters import Message
from nonebot.params import Arg, ArgStr, ArgPlainText
@@ -12,3 +14,15 @@ async def arg_str(key: str = ArgStr()) -> str:
async def arg_plain_text(key: str = ArgPlainText()) -> str:
return key
async def annotated_arg(key: Annotated[Message, Arg()]) -> Message:
return key
async def annotated_arg_str(key: Annotated[str, ArgStr()]) -> str:
return key
async def annotated_arg_plain_text(key: Annotated[str, ArgPlainText()]) -> str:
return key

View File

@@ -1,4 +1,4 @@
from typing import Union
from typing import Union, TypeVar
from nonebot.adapters import Bot
@@ -31,5 +31,19 @@ async def union_bot(b: Union[FooBot, BarBot]) -> Union[FooBot, BarBot]:
return b
B = TypeVar("B", bound=Bot)
async def generic_bot(b: B) -> B:
return b
CB = TypeVar("CB", Bot, None)
async def generic_bot_none(b: CB) -> CB:
return b
async def not_bot(b: Union[int, Bot]):
...

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