Compare commits

...

209 Commits

Author SHA1 Message Date
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
164 changed files with 19306 additions and 1336 deletions

View File

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

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

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

View File

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

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

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

View File

@@ -1,14 +1,5 @@
blank_issues_enabled: false blank_issues_enabled: false
contact_links: contact_links:
- name: Question - name: NoneBot 论坛
url: https://discussions.nonebot.dev/ url: https://discussions.nonebot.dev/
about: Ask questions about nonebot about: 前往 NoneBot 论坛提问
- name: Plugin Publish
url: https://v2.nonebot.dev/store
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

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,57 @@
name: 发布插件
title: "Plugin: {name}"
description: 发布插件到 NoneBot 官方商店
labels: ["Plugin"]
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-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: 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

View File

@@ -11,7 +11,7 @@ runs:
using: "composite" using: "composite"
steps: steps:
- name: Install poetry - name: Install poetry
run: pipx install poetry==1.3.2 run: pipx install poetry
shell: bash shell: bash
- uses: actions/setup-python@v4 - uses: actions/setup-python@v4

View File

@@ -1,4 +1,4 @@
name: NoneBot2 Publish Bot name: NoneFlow
on: on:
issues: issues:
@@ -7,6 +7,8 @@ on:
types: [closed] types: [closed]
issue_comment: issue_comment:
types: [created] types: [created]
pull_request_review:
types: [submitted]
concurrency: concurrency:
group: ${{ github.workflow }}-${{ github.event.issue.number || github.run_id }} group: ${{ github.workflow }}-${{ github.event.issue.number || github.run_id }}
@@ -16,7 +18,23 @@ jobs:
plugin_test: plugin_test:
runs-on: ubuntu-latest runs-on: ubuntu-latest
name: nonebot2 plugin test name: nonebot2 plugin test
if: github.event_name != 'issue_comment' || !github.event.issue.pull_request 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: permissions:
issues: read issues: read
outputs: outputs:
@@ -26,28 +44,36 @@ jobs:
- name: Install Poetry - name: Install Poetry
if: ${{ !startsWith(github.event_name, 'pull_request') }} if: ${{ !startsWith(github.event_name, 'pull_request') }}
run: pipx install poetry run: pipx install poetry
- name: Setup Python - name: Setup Python
uses: actions/setup-python@v4 uses: actions/setup-python@v4
with: with:
python-version: "3.10" python-version: "3.10"
- name: Test Plugin - name: Test Plugin
id: plugin-test id: plugin-test
run: | run: |
curl -sSL https://github.com/nonebot/nonebot2-publish-bot/releases/latest/download/plugin_test.py -o plugin_test.py curl -sSL https://github.com/nonebot/noneflow/releases/latest/download/plugin_test.py | python -
python plugin_test.py noneflow:
publish_bot:
runs-on: ubuntu-latest runs-on: ubuntu-latest
name: nonebot2 publish bot name: noneflow
needs: plugin_test needs: plugin_test
steps: 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 - name: Checkout Code
uses: actions/checkout@v3 uses: actions/checkout@v3
with: with:
token: ${{ secrets.GH_TOKEN }} token: ${{ steps.generate-token.outputs.token }}
- name: NoneBot2 Publish Bot
uses: docker://ghcr.io/nonebot/nonebot2-publish-bot:latest - name: NoneFlow
uses: docker://ghcr.io/nonebot/noneflow:latest
with: with:
token: ${{ secrets.GH_TOKEN }}
config: > config: >
{ {
"base": "master", "base": "master",
@@ -58,3 +84,5 @@ jobs:
env: env:
PLUGIN_TEST_RESULT: ${{ needs.plugin_test.outputs.result }} PLUGIN_TEST_RESULT: ${{ needs.plugin_test.outputs.result }}
PLUGIN_TEST_OUTPUT: ${{ needs.plugin_test.outputs.output }} PLUGIN_TEST_OUTPUT: ${{ needs.plugin_test.outputs.output }}
APP_ID: ${{ secrets.APP_ID }}
PRIVATE_KEY: ${{ secrets.APP_KEY }}

View File

@@ -18,9 +18,16 @@ jobs:
group: pull-request-changelog group: pull-request-changelog
cancel-in-progress: true cancel-in-progress: true
steps: steps:
- name: Generate token
id: generate-token
uses: tibdex/github-app-token@v1
with:
app_id: ${{ secrets.APP_ID }}
private_key: ${{ secrets.APP_KEY }}
- uses: actions/checkout@v3 - uses: actions/checkout@v3
with: with:
token: ${{ secrets.GH_TOKEN }} token: ${{ steps.generate-token.outputs.token }}
- name: Setup Node Environment - name: Setup Node Environment
uses: ./.github/actions/setup-node uses: ./.github/actions/setup-node
@@ -43,8 +50,8 @@ jobs:
- name: Commit and Push - name: Commit and Push
run: | run: |
yarn prettier yarn prettier
git config user.name github-actions[bot] git config user.name noneflow[bot]
git config user.email github-actions[bot]@users.noreply.github.com git config user.email 129742071+noneflow[bot]@users.noreply.github.com
git add . git add .
git diff-index --quiet HEAD || git commit -m ":memo: Update changelog" git diff-index --quiet HEAD || git commit -m ":memo: Update changelog"
git push git push

View File

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

View File

@@ -19,20 +19,20 @@ repos:
stages: [commit] stages: [commit]
- repo: https://github.com/psf/black - repo: https://github.com/psf/black
rev: 23.1.0 rev: 23.3.0
hooks: hooks:
- id: black - id: black
stages: [commit] stages: [commit]
- repo: https://github.com/pre-commit/mirrors-prettier - repo: https://github.com/pre-commit/mirrors-prettier
rev: v3.0.0-alpha.6 rev: v3.0.0-alpha.9-for-vscode
hooks: hooks:
- id: prettier - id: prettier
types_or: [javascript, jsx, ts, tsx, markdown, yaml, json] types_or: [javascript, jsx, ts, tsx, markdown, yaml, json]
stages: [commit] stages: [commit]
- repo: https://github.com/nonebot/nonemoji - repo: https://github.com/nonebot/nonemoji
rev: v0.1.3 rev: v0.1.4
hooks: hooks:
- id: nonemoji - id: nonemoji
stages: [prepare-commit-msg] stages: [prepare-commit-msg]

View File

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

View File

@@ -84,7 +84,7 @@ NoneBot2 的代码风格遵循 [PEP 8](https://www.python.org/dev/peps/pep-0008/
## 为社区做贡献 ## 为社区做贡献
你可以在 NoneBot 商店上架自己的适配器、插件、机器人,具体步骤可参考 [发布插件](https://v2.nonebot.dev/docs/developer/plugin-publishing) 一节。 你可以在 NoneBot 商店上架自己的适配器、插件、机器人,具体步骤可参考 [发布插件](https://nonebot.dev/docs/developer/plugin-publishing) 一节。
我们仅对插件的兼容性进行简单测试,并会在下一个版本发布前对与该版本不兼容的插件作出处理。 我们仅对插件的兼容性进行简单测试,并会在下一个版本发布前对与该版本不兼容的插件作出处理。

View File

@@ -1,6 +1,6 @@
<!-- markdownlint-disable MD033 MD041 --> <!-- markdownlint-disable MD033 MD041 -->
<p align="center"> <p align="center">
<a href="https://v2.nonebot.dev/"><img src="https://v2.nonebot.dev/logo.png" width="200" height="200" alt="nonebot"></a> <a href="https://nonebot.dev/"><img src="https://nonebot.dev/logo.png" width="200" height="200" alt="nonebot"></a>
</p> </p>
<div align="center"> <div align="center">
@@ -49,9 +49,9 @@ _✨ 跨平台 Python 异步机器人框架 ✨_
</a> </a>
<a href="https://bot.q.qq.com/wiki/"> <a href="https://bot.q.qq.com/wiki/">
<img src="https://img.shields.io/badge/QQ%E9%A2%91%E9%81%93-Bot-lightgrey?style=social&logo=data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMTIuODIgMTMwLjg5Ij48ZyBkYXRhLW5hbWU9IuWbvuWxgiAyIj48ZyBkYXRhLW5hbWU9IuWbvuWxgiAxIj48cGF0aCBkPSJNNTUuNjMgMTMwLjhjLTcgMC0xMy45LjA4LTIwLjg2IDAtMTkuMTUtLjI1LTMxLjcxLTExLjQtMzQuMjItMzAuMy00LjA3LTMwLjY2IDE0LjkzLTU5LjIgNDQuODMtNjYuNjQgMi0uNTEgNS4yMS0uMzEgNS4yMS0xLjYzIDAtMi4xMy4xNC0yLjEzLjE0LTUuNTcgMC0uODktMS4zLTEuNDYtMi4yMi0yLjMxLTYuNzMtNi4yMy03LjY3LTEzLjQxLTEtMjAuMTggNS40LTUuNTIgMTEuODctNS40IDE3LjgtLjU5IDYuNDkgNS4yNiA2LjMxIDEzLjA4LS44NiAyMS0uNjguNzQtMS43OCAxLjYtMS43OCAyLjY3djQuMjFjMCAxLjM1IDIuMiAxLjYyIDQuNzkgMi4zNSAzMS4wOSA4LjY1IDQ4LjE3IDM0LjEzIDQ1IDY2LjM3LTEuNzYgMTguMTUtMTQuNTYgMzAuMjMtMzIuNyAzMC42My04LjAyLjE5LTE2LjA3LS4wMS0yNC4xMy0uMDF6IiBmaWxsPSIjMDI5OWZlIi8+PHBhdGggZD0iTTMxLjQ2IDExOC4zOGMtMTAuNS0uNjktMTYuOC02Ljg2LTE4LjM4LTE3LjI3LTMtMTkuNDIgMi43OC0zNS44NiAxOC40Ni00Ny44MyAxNC4xNi0xMC44IDI5Ljg3LTEyIDQ1LjM4LTMuMTkgMTcuMjUgOS44NCAyNC41OSAyNS44MSAyNCA0NS4yOS0uNDkgMTUuOS04LjQyIDIzLjE0LTI0LjM4IDIzLjUtNi41OS4xNC0xMy4xOSAwLTE5Ljc5IDAiIGZpbGw9IiNmZWZlZmUiLz48cGF0aCBkPSJNNDYuMDUgNzkuNThjLjA5IDUgLjIzIDkuODItNyA5Ljc3LTcuODItLjA2LTYuMS01LjY5LTYuMjQtMTAuMTktLjE1LTQuODItLjczLTEwIDYuNzMtOS44NHM2LjM3IDUuNTUgNi41MSAxMC4yNnoiIGZpbGw9IiMxMDlmZmUiLz48cGF0aCBkPSJNODAuMjcgNzkuMjdjLS41MyAzLjkxIDEuNzUgOS42NC01Ljg4IDEwLTcuNDcuMzctNi44MS00LjgyLTYuNjEtOS41LjItNC4zMi0xLjgzLTEwIDUuNzgtMTAuNDJzNi41OSA0Ljg5IDYuNzEgOS45MnoiIGZpbGw9IiMwODljZmUiLz48L2c+PC9nPjwvc3ZnPg==" alt="QQ频道"> <img src="https://img.shields.io/badge/QQ%E9%A2%91%E9%81%93-Bot-lightgrey?style=social&logo=data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMTIuODIgMTMwLjg5Ij48ZyBkYXRhLW5hbWU9IuWbvuWxgiAyIj48ZyBkYXRhLW5hbWU9IuWbvuWxgiAxIj48cGF0aCBkPSJNNTUuNjMgMTMwLjhjLTcgMC0xMy45LjA4LTIwLjg2IDAtMTkuMTUtLjI1LTMxLjcxLTExLjQtMzQuMjItMzAuMy00LjA3LTMwLjY2IDE0LjkzLTU5LjIgNDQuODMtNjYuNjQgMi0uNTEgNS4yMS0uMzEgNS4yMS0xLjYzIDAtMi4xMy4xNC0yLjEzLjE0LTUuNTcgMC0uODktMS4zLTEuNDYtMi4yMi0yLjMxLTYuNzMtNi4yMy03LjY3LTEzLjQxLTEtMjAuMTggNS40LTUuNTIgMTEuODctNS40IDE3LjgtLjU5IDYuNDkgNS4yNiA2LjMxIDEzLjA4LS44NiAyMS0uNjguNzQtMS43OCAxLjYtMS43OCAyLjY3djQuMjFjMCAxLjM1IDIuMiAxLjYyIDQuNzkgMi4zNSAzMS4wOSA4LjY1IDQ4LjE3IDM0LjEzIDQ1IDY2LjM3LTEuNzYgMTguMTUtMTQuNTYgMzAuMjMtMzIuNyAzMC42My04LjAyLjE5LTE2LjA3LS4wMS0yNC4xMy0uMDF6IiBmaWxsPSIjMDI5OWZlIi8+PHBhdGggZD0iTTMxLjQ2IDExOC4zOGMtMTAuNS0uNjktMTYuOC02Ljg2LTE4LjM4LTE3LjI3LTMtMTkuNDIgMi43OC0zNS44NiAxOC40Ni00Ny44MyAxNC4xNi0xMC44IDI5Ljg3LTEyIDQ1LjM4LTMuMTkgMTcuMjUgOS44NCAyNC41OSAyNS44MSAyNCA0NS4yOS0uNDkgMTUuOS04LjQyIDIzLjE0LTI0LjM4IDIzLjUtNi41OS4xNC0xMy4xOSAwLTE5Ljc5IDAiIGZpbGw9IiNmZWZlZmUiLz48cGF0aCBkPSJNNDYuMDUgNzkuNThjLjA5IDUgLjIzIDkuODItNyA5Ljc3LTcuODItLjA2LTYuMS01LjY5LTYuMjQtMTAuMTktLjE1LTQuODItLjczLTEwIDYuNzMtOS44NHM2LjM3IDUuNTUgNi41MSAxMC4yNnoiIGZpbGw9IiMxMDlmZmUiLz48cGF0aCBkPSJNODAuMjcgNzkuMjdjLS41MyAzLjkxIDEuNzUgOS42NC01Ljg4IDEwLTcuNDcuMzctNi44MS00LjgyLTYuNjEtOS41LjItNC4zMi0xLjgzLTEwIDUuNzgtMTAuNDJzNi41OSA0Ljg5IDYuNzEgOS45MnoiIGZpbGw9IiMwODljZmUiLz48L2c+PC9nPjwvc3ZnPg==" alt="QQ频道">
<a 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>
<!-- <a href="https://ding-doc.dingtalk.com/document#/org-dev-guide/elzz1p">
<img src="https://img.shields.io/badge/%E9%92%89%E9%92%89-Bot-lightgrey?style=social&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAMAAACdt4HsAAAAnFBMVEUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD4jUzeAAAAM3RSTlMAQKSRaA+/f0YyFevh29R3cyklIfrlyrGsn41tVUs48c/HqJm9uZdhX1otGwkF9IN8V1CX0Q+IAAABY0lEQVRYw+3V2W7CMBAF0JuNQAhhX9OEfYdu9///rUVWpagE27Ef2gfO+0zGozsKnv6bMGzAhkNytIe5gDdzrwtTCwrbI8x4/NF668NAxgI3Q3UtFi3TyPwNQtPLUUmDd8YfqGLNe4v22XwEYb5zoOuF5baHq2UHtsKe5ivWfGAwrWu2mC34QM0PoCAuqZdOmiwV+5BLyMRtZ7dTSEcs48rzWfzwptMLyzpApka1SJ5FtR4kfCqNIBPEVDmqoqgwUYY5plQOlf6UEjNoOPnuKB6wzDyCrks///TDza8+PnR109WQdxLo8RKWq0PPnuXG0OXKQ6wWLFnCg75uYYbhmMIVVdQ709q33aHbGIj6Duz+2k1HQFX9VwqmY8xYsEJll2ahvhWgsjYLHFRXvIi2Qb0jzMQCzC3FAoydxCma88UCzE3JCWwkjCNYyMUCzHX4DiuTMawEwwhW6hnshPhjZzzJfAH0YacpbmRd7QAAAABJRU5ErkJggg==" alt="dingtalk"> -->
</a> </a>
<br /> <br />
<a href="https://jq.qq.com/?_wv=1027&k=5OFifDh"> <a href="https://jq.qq.com/?_wv=1027&k=5OFifDh">
@@ -69,16 +69,16 @@ _✨ 跨平台 Python 异步机器人框架 ✨_
</p> </p>
<p align="center"> <p align="center">
<a href="https://v2.nonebot.dev/">文档</a> <a href="https://nonebot.dev/">文档</a>
· ·
<a href="https://v2.nonebot.dev/docs/quick-start">快速上手</a> <a href="https://nonebot.dev/docs/quick-start">快速上手</a>
· ·
<a href="#插件">文档打不开?</a> <a href="#插件">文档打不开?</a>
</p> </p>
<p align="center"> <p align="center">
<a href="https://asciinema.org/a/569440"> <a href="https://asciinema.org/a/569440">
<img src="https://v2.nonebot.dev/img/setup.svg"> <img src="https://nonebot.dev/img/setup.svg">
</a> </a>
</p> </p>
@@ -90,36 +90,37 @@ NoneBot2 是一个现代、跨平台、可扩展的 Python 聊天机器人框架
- 异步优先:基于 Python 的异步特性,即使是~~非常~~大量的消息,也能吞吐自如 - 异步优先:基于 Python 的异步特性,即使是~~非常~~大量的消息,也能吞吐自如
- 易于开发:配合 NB-CLI 脚手架,代码编写上手简单,没有过多的冗余代码,可以让开发者专注于业务逻辑 - 易于开发:配合 NB-CLI 脚手架,代码编写上手简单,没有过多的冗余代码,可以让开发者专注于业务逻辑
- 生而可靠100% 类型注解覆盖,配合编辑器的类型推导功能,能将绝大多数的 Bug 杜绝在编辑器中 ([编辑器支持](https://v2.nonebot.dev/docs/editor-support)) - 生而可靠100% 类型注解覆盖,配合编辑器的类型推导功能,能将绝大多数的 Bug 杜绝在编辑器中 ([编辑器支持](https://nonebot.dev/docs/editor-support))
- 社区丰富:社区用户众多,直接和间接用户超过十万人,每天都有大量的活跃用户 ([社区资源](#社区资源)) - 社区丰富:社区用户众多,直接和间接用户超过十万人,每天都有大量的活跃用户 ([社区资源](#社区资源))
- 海纳百川:一个框架,支持多个聊天软件平台,可自定义通信协议 - 海纳百川:一个框架,支持多个聊天软件平台,可自定义通信协议
| 协议名称 | 状态 | 注释 | | 协议名称 | 状态 | 注释 |
| :-----------------------------------------------------------------------: | :--: | :----------------------------------------------------------------: | | :-------------------------------------------------------------------------------------------------------------------: | :--: | :-----------------------------------------------------------------------: |
| [OneBot 协议](https://onebot.dev/) | ✅ | 支持 QQ、TG、微信公众号等[平台](https://onebot.dev/ecosystem.html) | | OneBot[仓库](https://github.com/nonebot/adapter-onebot)[协议](https://onebot.dev/) | ✅ | 支持 QQ、TG、微信公众号、KOOK 等[平台](https://onebot.dev/ecosystem.html) |
| [Telegram](https://core.telegram.org/bots/api) | ✅ | | | Telegram[仓库](https://github.com/nonebot/adapter-telegram)[协议](https://core.telegram.org/bots/api) | ✅ | |
| [飞书](https://open.feishu.cn/document/home/index) | ✅ | | | 飞书[仓库](https://github.com/nonebot/adapter-feishu)[协议](https://open.feishu.cn/document/home/index) | ✅ | |
| [GitHub](https://docs.github.com/en/developers/apps) | ✅ | GitHub APP & OAuth APP | | GitHub[仓库](https://github.com/nonebot/adapter-github)[协议](https://docs.github.com/en/apps) | ✅ | GitHub APP & OAuth APP |
| [QQ 频道](https://bot.q.qq.com/wiki/) | ✅ | 官方接口调整较多 | | QQ 频道[仓库](https://github.com/nonebot/adapter-qqguild)[协议](https://bot.q.qq.com/wiki/) | ✅ | 官方接口调整较多 |
| [钉钉](https://open.dingtalk.com/document/) | 🤗 | 寻找 Maintainer | | 钉钉[仓库](https://github.com/nonebot/adapter-ding)[协议](https://open.dingtalk.com/document/) | 🤗 | 寻找 Maintainer(暂不可用) |
| Console | ✅ | 控制台交互 | | Console[仓库](https://github.com/nonebot/adapter-console) | ✅ | 控制台交互 |
| [开黑啦](https://developer.kookapp.cn/) | ↗️ | 由社区贡献 | | 开黑啦[仓库](https://github.com/Tian-que/nonebot-adapter-kaiheila)[协议](https://developer.kookapp.cn/) | ↗️ | 由社区贡献 |
| [Mirai](https://docs.mirai.mamoe.net/mirai-api-http/) | ↗️ | 由社区贡献 | | Mirai[仓库](https://github.com/ieew/nonebot_adapter_mirai2)[协议](https://docs.mirai.mamoe.net/mirai-api-http/) | ↗️ | QQ 协议,由社区贡献 |
| [Ntchat](https://github.com/JustUndertaker/adapter-ntchat) | ↗️ | 由社区贡献 | | Ntchat[仓库](https://github.com/JustUndertaker/adapter-ntchat) | ↗️ | 微信协议,由社区贡献 |
| [MineCraft (Spigot)](https://github.com/17TheWord/nonebot-adapter-spigot) | ↗️ | 由社区贡献 | | MineCraft[仓库](https://github.com/17TheWord/nonebot-adapter-minecraft) | ↗️ | 由社区贡献 |
| [BiliBili Live](https://github.com/wwweww/adapter-bilibili) | ↗️ | 由社区贡献 | | BiliBili Live[仓库](https://github.com/wwweww/adapter-bilibili) | ↗️ | 由社区贡献 |
| Walle-Q[仓库](https://github.com/onebot-walle/nonebot_adapter_walleq) | ↗️ | QQ 协议,由社区贡献 |
- 坚实后盾:支持多种 web 框架,可自定义替换、组合 - 坚实后盾:支持多种 web 框架,可自定义替换、组合
| 驱动框架 | 类型 | | 驱动框架 | 类型 |
| :--------------------------------------------------------: | :----: | | :-----------------------------------------------------------------: | :----: |
| [FastAPI](https://fastapi.tiangolo.com/) | 服务端 | | [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/) | 客户端 | | [aiohttp](https://docs.aiohttp.org/en/stable/) | 客户端 |
| [httpx](https://www.python-httpx.org/) | 客户端 | | [httpx](https://www.python-httpx.org/) | 客户端 |
| [websockets](https://websockets.readthedocs.io/en/stable/) | 客户端 | | [websockets](https://websockets.readthedocs.io/en/stable/) | 客户端 |
更多:[概览](https://v2.nonebot.dev/docs/) 更多:[概览](https://nonebot.dev/docs/)
## 什么不是 NoneBot2 ## 什么不是 NoneBot2
@@ -131,7 +132,7 @@ NoneBot2 不是 NoneBot1 的替代品。事实上,它们都在被积极的维
## 即刻开始 ## 即刻开始
~~完整~~文档可以在 [这里](https://v2.nonebot.dev/) 查看。 ~~完整~~文档可以在 [这里](https://nonebot.dev/) 查看。
懒得看文档?下面是快速安装指南: 懒得看文档?下面是快速安装指南:
@@ -188,7 +189,7 @@ NoneBot2 不是 NoneBot1 的替代品。事实上,它们都在被积极的维
- [文档镜像(中国境内)](https://nb2.baka.icu) - [文档镜像(中国境内)](https://nb2.baka.icu)
- [文档镜像(Vercel)](https://nonebot2-vercel-mirror.vercel.app) - [文档镜像(Vercel)](https://nonebot2-vercel-mirror.vercel.app)
- 其他插件请查看 [商店](https://v2.nonebot.dev/store) - 其他插件请查看 [商店](https://nonebot.dev/store)
## 许可证 ## 许可证

View File

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

View File

@@ -158,7 +158,7 @@ class Config(BaseConfig):
除了 NoneBot 的配置项外,还可以自行添加配置项到 `.env.{environment}` 文件中。 除了 NoneBot 的配置项外,还可以自行添加配置项到 `.env.{environment}` 文件中。
这些配置将会在 json 反序列化后一起带入 `Config` 类中。 这些配置将会在 json 反序列化后一起带入 `Config` 类中。
配置方法参考: [配置](https://v2.nonebot.dev/docs/appendices/config) 配置方法参考: [配置](https://nonebot.dev/docs/appendices/config)
""" """
_env_file: DotenvType = ".env", ".env.prod" _env_file: DotenvType = ".env", ".env.prod"

View File

@@ -42,12 +42,6 @@ SHELL_ARGV: Literal["_argv"] = "_argv"
REGEX_MATCHED: Literal["_matched"] = "_matched" REGEX_MATCHED: Literal["_matched"] = "_matched"
"""正则匹配结果存储 key""" """正则匹配结果存储 key"""
REGEX_STR: Literal["_matched_str"] = "_matched_str"
"""正则匹配文本存储 key"""
REGEX_GROUP: Literal["_matched_groups"] = "_matched_groups"
"""正则匹配 group 元组存储 key"""
REGEX_DICT: Literal["_matched_dict"] = "_matched_dict"
"""正则匹配 group 字典存储 key"""
STARTSWITH_KEY: Literal["_startswith"] = "_startswith" STARTSWITH_KEY: Literal["_startswith"] = "_startswith"
"""响应触发前缀 key""" """响应触发前缀 key"""
ENDSWITH_KEY: Literal["_endswith"] = "_endswith" ENDSWITH_KEY: Literal["_endswith"] = "_endswith"

View File

@@ -55,16 +55,12 @@ class Driver(BaseDriver):
@overrides(BaseDriver) @overrides(BaseDriver)
def on_startup(self, func: LIFESPAN_FUNC) -> LIFESPAN_FUNC: def on_startup(self, func: LIFESPAN_FUNC) -> LIFESPAN_FUNC:
""" """注册一个启动时执行的函数"""
注册一个启动时执行的函数
"""
return self._lifespan.on_startup(func) return self._lifespan.on_startup(func)
@overrides(BaseDriver) @overrides(BaseDriver)
def on_shutdown(self, func: LIFESPAN_FUNC) -> LIFESPAN_FUNC: def on_shutdown(self, func: LIFESPAN_FUNC) -> LIFESPAN_FUNC:
""" """注册一个停止时执行的函数"""
注册一个停止时执行的函数
"""
return self._lifespan.on_shutdown(func) return self._lifespan.on_shutdown(func)
@overrides(BaseDriver) @overrides(BaseDriver)
@@ -146,7 +142,15 @@ class Driver(BaseDriver):
signal.signal(sig, self._handle_exit) signal.signal(sig, self._handle_exit)
def _handle_exit(self, sig, frame): def _handle_exit(self, sig, frame):
if self.should_exit.is_set(): self.exit(force=self.should_exit.is_set())
self.force_exit = True
else: def exit(self, force: bool = False):
"""退出 none driver
参数:
force: 强制退出
"""
if not self.should_exit.is_set():
self.should_exit.set() self.should_exit.set()
if force:
self.force_exit = True

View File

@@ -1,5 +1,6 @@
import abc import abc
from copy import deepcopy from copy import deepcopy
from typing_extensions import Self
from dataclasses import field, asdict, dataclass from dataclasses import field, asdict, dataclass
from typing import ( from typing import (
Any, Any,
@@ -12,6 +13,7 @@ from typing import (
TypeVar, TypeVar,
Iterable, Iterable,
Optional, Optional,
SupportsIndex,
overload, overload,
) )
@@ -19,7 +21,6 @@ from pydantic import parse_obj_as
from .template import MessageTemplate from .template import MessageTemplate
T = TypeVar("T")
TMS = TypeVar("TMS", bound="MessageSegment") TMS = TypeVar("TMS", bound="MessageSegment")
TM = TypeVar("TM", bound="Message") TM = TypeVar("TM", bound="Message")
@@ -47,7 +48,7 @@ class MessageSegment(abc.ABC, Generic[TM]):
def __len__(self) -> int: def __len__(self) -> int:
return len(str(self)) return len(str(self))
def __ne__(self: T, other: T) -> bool: def __ne__(self, other: Self) -> bool:
return not self == other return not self == other
def __add__(self: TMS, other: Union[str, TMS, Iterable[TMS]]) -> TM: def __add__(self: TMS, other: Union[str, TMS, Iterable[TMS]]) -> TM:
@@ -61,7 +62,7 @@ class MessageSegment(abc.ABC, Generic[TM]):
yield cls._validate yield cls._validate
@classmethod @classmethod
def _validate(cls, value): def _validate(cls, value) -> Self:
if isinstance(value, cls): if isinstance(value, cls):
return value return value
if not isinstance(value, dict): if not isinstance(value, dict):
@@ -84,7 +85,10 @@ class MessageSegment(abc.ABC, Generic[TM]):
def items(self): def items(self):
return asdict(self).items() 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) return deepcopy(self)
@abc.abstractmethod @abc.abstractmethod
@@ -117,7 +121,7 @@ class Message(List[TMS], abc.ABC):
self.extend(self._construct(message)) # pragma: no cover self.extend(self._construct(message)) # pragma: no cover
@classmethod @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` 对象作为消息模板 用法和 `str.format` 大致相同, 但是可以输出消息对象, 并且支持以 `Message` 对象作为消息模板
@@ -146,7 +150,7 @@ class Message(List[TMS], abc.ABC):
yield cls._validate yield cls._validate
@classmethod @classmethod
def _validate(cls, value): def _validate(cls, value) -> Self:
if isinstance(value, cls): if isinstance(value, cls):
return value return value
elif isinstance(value, Message): elif isinstance(value, Message):
@@ -169,16 +173,16 @@ class Message(List[TMS], abc.ABC):
"""构造消息数组""" """构造消息数组"""
raise NotImplementedError 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 = self.copy()
result += other result += other
return result 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) result = self.__class__(other)
return result + self 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): if isinstance(other, str):
self.extend(self._construct(other)) self.extend(self._construct(other))
elif isinstance(other, MessageSegment): elif isinstance(other, MessageSegment):
@@ -190,57 +194,62 @@ class Message(List[TMS], abc.ABC):
return self return self
@overload @overload
def __getitem__(self: TM, __args: str) -> TM: def __getitem__(self, args: str) -> Self:
""" """获取仅包含指定消息段类型的消息
参数: 参数:
__args: 消息段类型 args: 消息段类型
返回: 返回:
所有类型为 `__args` 的消息段 所有类型为 `args` 的消息段
""" """
@overload @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 @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 @overload
def __getitem__(self, __args: int) -> TMS: def __getitem__(self, args: int) -> TMS:
""" """索引消息段
参数: 参数:
__args: 索引 args: 索引
返回: 返回:
第 `__args` 个消息段 第 `args` 个消息段
""" """
@overload @overload
def __getitem__(self: TM, __args: slice) -> TM: def __getitem__(self, args: slice) -> Self:
""" """切片消息段
参数: 参数:
__args: 切片 args: 切片
返回: 返回:
消息切片 `__args` 消息切片 `args`
""" """
def __getitem__( def __getitem__(
self: TM, self,
args: Union[ args: Union[
str, str,
Tuple[str, int], Tuple[str, int],
@@ -248,7 +257,7 @@ class Message(List[TMS], abc.ABC):
int, int,
slice, slice,
], ],
) -> Union[TMS, TM]: ) -> Union[TMS, Self]:
arg1, arg2 = args if isinstance(args, tuple) else (args, None) arg1, arg2 = args if isinstance(args, tuple) else (args, None)
if isinstance(arg1, int) and arg2 is None: if isinstance(arg1, int) and arg2 is None:
return super().__getitem__(arg1) return super().__getitem__(arg1)
@@ -263,15 +272,52 @@ class Message(List[TMS], abc.ABC):
else: else:
raise ValueError("Incorrect arguments to slice") # pragma: no cover 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): if isinstance(value, str):
first_segment = next((seg for seg in self if seg.type == value), None) first_segment = next((seg for seg in self if seg.type == value), None)
if first_segment is 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(first_segment, *args)
return super().index(value, *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: if count is None:
return self[type_] return self[type_]
@@ -286,9 +332,30 @@ class Message(List[TMS], abc.ABC):
return filtered return filtered
def count(self, value: Union[TMS, str]) -> int: def count(self, value: Union[TMS, str]) -> int:
"""计算指定消息段的个数
参数:
value: 消息段或消息段类型
返回:
个数
"""
return len(self[value]) if isinstance(value, str) else super().count(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 raise ValueError(f"Unexpected type: {type(obj)} {obj}") # pragma: no cover
return self 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) self.append(segment)
return self 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) 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: def extract_plain_text(self) -> str:
"""提取消息内纯文本消息""" """提取消息内纯文本消息"""
return "".join(str(seg) for seg in self if seg.is_text()) 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

@@ -71,7 +71,12 @@ def Depends(
class DependParam(Param): class DependParam(Param):
"""子依赖参数""" """子依赖注入参数
本注入解析所有子依赖注入,然后将它们的返回值作为参数值传递给父依赖。
本注入应该具有最高优先级,因此应该在其他参数之前检查。
"""
def __repr__(self) -> str: def __repr__(self) -> str:
return f"Depends({self.extra['dependent']})" return f"Depends({self.extra['dependent']})"
@@ -168,7 +173,12 @@ class DependParam(Param):
class BotParam(Param): class BotParam(Param):
"""{ref}`nonebot.adapters.Bot` 参数""" """{ref}`nonebot.adapters.Bot` 注入参数
本注入解析所有类型为且仅为 {ref}`nonebot.adapters.Bot` 及其子类或 `None` 的参数。
为保证兼容性,本注入还会解析名为 `bot` 且没有类型注解的参数。
"""
def __repr__(self) -> str: def __repr__(self) -> str:
return ( return (
@@ -187,21 +197,22 @@ class BotParam(Param):
) -> Optional["BotParam"]: ) -> Optional["BotParam"]:
from nonebot.adapters import Bot 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): if generic_check_issubclass(param.annotation, Bot):
checker: Optional[ModelField] = None checker: Optional[ModelField] = None
if param.annotation is not Bot: if param.annotation is not Bot:
checker = ModelField( checker = ModelField(
name=param.name, name=param.name,
type_=param.annotation, type_=param.annotation,
class_validators=None, class_validators=None,
model_config=CustomConfig, model_config=CustomConfig,
default=None, default=None,
required=True, required=True,
) )
return cls(Required, checker=checker) return cls(Required, checker=checker)
elif param.annotation == param.empty and param.name == "bot": # legacy: param is named "bot" and has no type annotation
return cls(Required) elif param.annotation == param.empty and param.name == "bot":
return cls(Required)
async def _solve(self, bot: "Bot", **kwargs: Any) -> Any: async def _solve(self, bot: "Bot", **kwargs: Any) -> Any:
return bot return bot
@@ -212,7 +223,12 @@ class BotParam(Param):
class EventParam(Param): class EventParam(Param):
"""{ref}`nonebot.adapters.Event` 参数""" """{ref}`nonebot.adapters.Event` 注入参数
本注入解析所有类型为且仅为 {ref}`nonebot.adapters.Event` 及其子类或 `None` 的参数。
为保证兼容性,本注入还会解析名为 `event` 且没有类型注解的参数。
"""
def __repr__(self) -> str: def __repr__(self) -> str:
return ( return (
@@ -231,21 +247,22 @@ class EventParam(Param):
) -> Optional["EventParam"]: ) -> Optional["EventParam"]:
from nonebot.adapters import Event 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): if generic_check_issubclass(param.annotation, Event):
checker: Optional[ModelField] = None checker: Optional[ModelField] = None
if param.annotation is not Event: if param.annotation is not Event:
checker = ModelField( checker = ModelField(
name=param.name, name=param.name,
type_=param.annotation, type_=param.annotation,
class_validators=None, class_validators=None,
model_config=CustomConfig, model_config=CustomConfig,
default=None, default=None,
required=True, required=True,
) )
return cls(Required, checker=checker) return cls(Required, checker=checker)
elif param.annotation == param.empty and param.name == "event": # legacy: param is named "event" and has no type annotation
return cls(Required) elif param.annotation == param.empty and param.name == "event":
return cls(Required)
async def _solve(self, event: "Event", **kwargs: Any) -> Any: async def _solve(self, event: "Event", **kwargs: Any) -> Any:
return event return event
@@ -256,7 +273,12 @@ class EventParam(Param):
class StateParam(Param): class StateParam(Param):
"""事件处理状态参数""" """事件处理状态注入参数
本注入解析所有类型为 `T_State` 的参数。
为保证兼容性,本注入还会解析名为 `state` 且没有类型注解的参数。
"""
def __repr__(self) -> str: def __repr__(self) -> str:
return "StateParam()" return "StateParam()"
@@ -265,18 +287,24 @@ class StateParam(Param):
def _check_param( def _check_param(
cls, param: inspect.Parameter, allow_types: Tuple[Type[Param], ...] cls, param: inspect.Parameter, allow_types: Tuple[Type[Param], ...]
) -> Optional["StateParam"]: ) -> Optional["StateParam"]:
if param.default == param.empty: # param type is T_State
if param.annotation is T_State: if param.annotation is T_State:
return cls(Required) return cls(Required)
elif param.annotation == param.empty and param.name == "state": # legacy: param is named "state" and has no type annotation
return cls(Required) elif param.annotation == param.empty and param.name == "state":
return cls(Required)
async def _solve(self, state: T_State, **kwargs: Any) -> Any: async def _solve(self, state: T_State, **kwargs: Any) -> Any:
return state return state
class MatcherParam(Param): class MatcherParam(Param):
"""事件响应器实例参数""" """事件响应器实例注入参数
本注入解析所有类型为且仅为 {ref}`nonebot.matcher.Matcher` 及其子类或 `None` 的参数。
为保证兼容性,本注入还会解析名为 `matcher` 且没有类型注解的参数。
"""
def __repr__(self) -> str: def __repr__(self) -> str:
return "MatcherParam()" return "MatcherParam()"
@@ -287,9 +315,11 @@ class MatcherParam(Param):
) -> Optional["MatcherParam"]: ) -> Optional["MatcherParam"]:
from nonebot.matcher import Matcher from nonebot.matcher import Matcher
if generic_check_issubclass(param.annotation, Matcher) or ( # param type is Matcher(s) or subclass(es) of Matcher or None
param.annotation == param.empty and param.name == "matcher" if generic_check_issubclass(param.annotation, Matcher):
): return cls(Required)
# legacy: param is named "matcher" and has no type annotation
elif param.annotation == param.empty and param.name == "matcher":
return cls(Required) return cls(Required)
async def _solve(self, matcher: "Matcher", **kwargs: Any) -> Any: async def _solve(self, matcher: "Matcher", **kwargs: Any) -> Any:
@@ -308,22 +338,28 @@ class ArgInner:
def Arg(key: Optional[str] = None) -> Any: def Arg(key: Optional[str] = None) -> Any:
"""`got` 的 Arg 参数消息""" """Arg 参数消息"""
return ArgInner(key, "message") return ArgInner(key, "message")
def ArgStr(key: Optional[str] = None) -> str: def ArgStr(key: Optional[str] = None) -> str:
"""`got` 的 Arg 参数消息文本""" """Arg 参数消息文本"""
return ArgInner(key, "str") # type: ignore return ArgInner(key, "str") # type: ignore
def ArgPlainText(key: Optional[str] = None) -> str: def ArgPlainText(key: Optional[str] = None) -> str:
"""`got` 的 Arg 参数消息纯文本""" """Arg 参数消息纯文本"""
return ArgInner(key, "plaintext") # type: ignore return ArgInner(key, "plaintext") # type: ignore
class ArgParam(Param): class ArgParam(Param):
"""`got` 的 Arg 参数""" """Arg 注入参数
本注入解析事件响应器操作 `got` 所获取的参数。
可以通过 `Arg`、`ArgStr`、`ArgPlainText` 等函数参数 `key` 指定获取的参数,
留空则会根据参数名称获取。
"""
def __repr__(self) -> str: def __repr__(self) -> str:
return f"ArgParam(key={self.extra['key']!r}, type={self.extra['type']!r})" return f"ArgParam(key={self.extra['key']!r}, type={self.extra['type']!r})"
@@ -338,7 +374,8 @@ class ArgParam(Param):
) )
async def _solve(self, matcher: "Matcher", **kwargs: Any) -> Any: 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: if message is None:
return message return message
if self.extra["type"] == "message": if self.extra["type"] == "message":
@@ -350,7 +387,12 @@ class ArgParam(Param):
class ExceptionParam(Param): class ExceptionParam(Param):
"""`run_postprocessor` 的异常参数""" """{ref}`nonebot.message.run_postprocessor` 的异常注入参数
本注入解析所有类型为 `Exception` 或 `None` 的参数。
为保证兼容性,本注入还会解析名为 `exception` 且没有类型注解的参数。
"""
def __repr__(self) -> str: def __repr__(self) -> str:
return "ExceptionParam()" return "ExceptionParam()"
@@ -359,9 +401,11 @@ class ExceptionParam(Param):
def _check_param( def _check_param(
cls, param: inspect.Parameter, allow_types: Tuple[Type[Param], ...] cls, param: inspect.Parameter, allow_types: Tuple[Type[Param], ...]
) -> Optional["ExceptionParam"]: ) -> Optional["ExceptionParam"]:
if generic_check_issubclass(param.annotation, Exception) or ( # param type is Exception(s) or subclass(es) of Exception or None
param.annotation == param.empty and param.name == "exception" 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) return cls(Required)
async def _solve(self, exception: Optional[Exception] = None, **kwargs: Any) -> Any: async def _solve(self, exception: Optional[Exception] = None, **kwargs: Any) -> Any:
@@ -369,7 +413,12 @@ class ExceptionParam(Param):
class DefaultParam(Param): class DefaultParam(Param):
"""默认值参数""" """默认值注入参数
本注入解析所有剩余未能解析且具有默认值的参数。
本注入参数应该具有最低优先级,因此应该在所有其他注入参数之后使用。
"""
def __repr__(self) -> str: def __repr__(self) -> str:
return f"DefaultParam(default={self.default!r})" return f"DefaultParam(default={self.default!r})"

View File

@@ -2,7 +2,7 @@
NoneBot 使用 [`loguru`][loguru] 来记录日志信息。 NoneBot 使用 [`loguru`][loguru] 来记录日志信息。
自定义 logger 请参考 [自定义日志](https://v2.nonebot.dev/docs/appendices/log) 自定义 logger 请参考 [自定义日志](https://nonebot.dev/docs/appendices/log)
以及 [`loguru`][loguru] 文档。 以及 [`loguru`][loguru] 文档。
[loguru]: https://github.com/Delgan/loguru [loguru]: https://github.com/Delgan/loguru

View File

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

View File

@@ -5,8 +5,7 @@ FrontMatter:
description: nonebot.params 模块 description: nonebot.params 模块
""" """
import warnings from typing import Any, Dict, List, Match, Tuple, Union, Optional
from typing import Any, Dict, List, Tuple, Union, Optional
from nonebot.typing import T_State from nonebot.typing import T_State
from nonebot.matcher import Matcher from nonebot.matcher import Matcher
@@ -25,15 +24,12 @@ from nonebot.internal.params import MatcherParam as MatcherParam
from nonebot.internal.params import ExceptionParam as ExceptionParam from nonebot.internal.params import ExceptionParam as ExceptionParam
from nonebot.consts import ( from nonebot.consts import (
CMD_KEY, CMD_KEY,
REGEX_STR,
PREFIX_KEY, PREFIX_KEY,
REGEX_DICT,
SHELL_ARGS, SHELL_ARGS,
SHELL_ARGV, SHELL_ARGV,
CMD_ARG_KEY, CMD_ARG_KEY,
KEYWORD_KEY, KEYWORD_KEY,
RAW_CMD_KEY, RAW_CMD_KEY,
REGEX_GROUP,
ENDSWITH_KEY, ENDSWITH_KEY,
CMD_START_KEY, CMD_START_KEY,
FULLMATCH_KEY, FULLMATCH_KEY,
@@ -142,23 +138,17 @@ def ShellCommandArgv() -> Any:
return Depends(_shell_command_argv, use_cache=False) 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] return state[REGEX_MATCHED]
def RegexMatched() -> str: def RegexMatched() -> Match[str]:
"""正则匹配结果""" """正则匹配结果"""
warnings.warn(
'"RegexMatched()" will be changed to "re.Match" object, '
'use "RegexStr()" instead. '
"See https://github.com/nonebot/nonebot2/pull/1453 .",
DeprecationWarning,
)
return Depends(_regex_matched, use_cache=False) return Depends(_regex_matched, use_cache=False)
def _regex_str(state: T_State) -> str: def _regex_str(state: T_State) -> str:
return state[REGEX_STR] return _regex_matched(state).group()
def RegexStr() -> str: def RegexStr() -> str:
@@ -167,7 +157,7 @@ def RegexStr() -> str:
def _regex_group(state: T_State) -> Tuple[Any, ...]: def _regex_group(state: T_State) -> Tuple[Any, ...]:
return state[REGEX_GROUP] return _regex_matched(state).groups()
def RegexGroup() -> Tuple[Any, ...]: def RegexGroup() -> Tuple[Any, ...]:
@@ -176,7 +166,7 @@ def RegexGroup() -> Tuple[Any, ...]:
def _regex_dict(state: T_State) -> Dict[str, Any]: def _regex_dict(state: T_State) -> Dict[str, Any]:
return state[REGEX_DICT] return _regex_matched(state).groupdict()
def RegexDict() -> Dict[str, Any]: def RegexDict() -> Dict[str, Any]:

View File

@@ -13,10 +13,10 @@ from nonebot.utils import path_to_module_name
from .plugin import Plugin from .plugin import Plugin
from .manager import PluginManager 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 try: # pragma: py-gte-311
import tomllib # pyright: reportMissingImports=false import tomllib # pyright: ignore[reportMissingImports]
except ModuleNotFoundError: # pragma: py-lt-311 except ModuleNotFoundError: # pragma: py-lt-311
import tomli as tomllib import tomli as tomllib
@@ -161,11 +161,19 @@ def require(name: str) -> ModuleType:
RuntimeError: 插件无法加载 RuntimeError: 插件无法加载
""" """
plugin = get_plugin(_module_name_to_plugin_name(name)) plugin = get_plugin(_module_name_to_plugin_name(name))
# if plugin not loaded
if not plugin: if not plugin:
# plugin already declared
if manager := _find_manager_by_name(name): if manager := _find_manager_by_name(name):
plugin = manager.load_plugin(name) plugin = manager.load_plugin(name)
# plugin not declared, try to declare and load it
else: else:
plugin = load_plugin(name) # clear current plugin chain, ensure plugin loaded in a new context
_t = _current_plugin_chain.set(())
try:
plugin = load_plugin(name)
finally:
_current_plugin_chain.reset(_t)
if not plugin: if not plugin:
raise RuntimeError(f'Cannot load plugin "{name}"!') raise RuntimeError(f'Cannot load plugin "{name}"!')
return plugin.module return plugin.module

View File

@@ -228,6 +228,7 @@ class PluginLoader(SourceFileLoader):
# detect parent plugin before entering current plugin context # detect parent plugin before entering current plugin context
parent_plugins = _current_plugin_chain.get() parent_plugins = _current_plugin_chain.get()
for pre_plugin in reversed(parent_plugins): 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): if _managers.index(pre_plugin.manager) < _managers.index(self.manager):
plugin.parent_plugin = pre_plugin plugin.parent_plugin = pre_plugin
pre_plugin.sub_plugins.add(plugin) pre_plugin.sub_plugins.add(plugin)

View File

@@ -1,9 +1,11 @@
"""本模块定义插件对象 """本模块定义插件相关信息
FrontMatter: FrontMatter:
sidebar_position: 3 sidebar_position: 3
description: nonebot.plugin.plugin 模块 description: nonebot.plugin.plugin 模块
""" """
import contextlib
from types import ModuleType from types import ModuleType
from dataclasses import field, dataclass from dataclasses import field, dataclass
from typing import TYPE_CHECKING, Any, Set, Dict, Type, Optional from typing import TYPE_CHECKING, Any, Set, Dict, Type, Optional
@@ -11,11 +13,11 @@ from typing import TYPE_CHECKING, Any, Set, Dict, Type, Optional
from pydantic import BaseModel from pydantic import BaseModel
from nonebot.matcher import Matcher from nonebot.matcher import Matcher
from nonebot.utils import resolve_dot_notation
# FIXME: backport for nonebug
from . import _plugins as plugins # nopycln: import
if TYPE_CHECKING: if TYPE_CHECKING:
from nonebot.adapters import Adapter
from .manager import PluginManager from .manager import PluginManager
@@ -24,14 +26,39 @@ class PluginMetadata:
"""插件元信息,由插件编写者提供""" """插件元信息,由插件编写者提供"""
name: str name: str
"""插件可阅读名称""" """插件名称"""
description: str description: str
"""插件功能介绍""" """插件功能介绍"""
usage: str usage: str
"""插件使用方法""" """插件使用方法"""
type: Optional[str] = None
"""插件类型,用于商店分类"""
homepage: Optional[str] = None
"""插件主页"""
config: Optional[Type[BaseModel]] = 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) 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) @dataclass(eq=False)

View File

@@ -11,6 +11,7 @@ FrontMatter:
import re import re
import shlex import shlex
from argparse import Action from argparse import Action
from gettext import gettext
from argparse import ArgumentError from argparse import ArgumentError
from contextvars import ContextVar from contextvars import ContextVar
from itertools import chain, product from itertools import chain, product
@@ -43,15 +44,12 @@ from nonebot.adapters import Bot, Event, Message, MessageSegment
from nonebot.params import Command, EventToMe, CommandArg, CommandWhitespace from nonebot.params import Command, EventToMe, CommandArg, CommandWhitespace
from nonebot.consts import ( from nonebot.consts import (
CMD_KEY, CMD_KEY,
REGEX_STR,
PREFIX_KEY, PREFIX_KEY,
REGEX_DICT,
SHELL_ARGS, SHELL_ARGS,
SHELL_ARGV, SHELL_ARGV,
CMD_ARG_KEY, CMD_ARG_KEY,
KEYWORD_KEY, KEYWORD_KEY,
RAW_CMD_KEY, RAW_CMD_KEY,
REGEX_GROUP,
ENDSWITH_KEY, ENDSWITH_KEY,
CMD_START_KEY, CMD_START_KEY,
FULLMATCH_KEY, FULLMATCH_KEY,
@@ -67,7 +65,7 @@ CMD_RESULT = TypedDict(
{ {
"command": Optional[Tuple[str, ...]], "command": Optional[Tuple[str, ...]],
"raw_command": Optional[str], "raw_command": Optional[str],
"command_arg": Optional[Message[MessageSegment]], "command_arg": Optional[Message],
"command_start": Optional[str], "command_start": Optional[str],
"command_whitespace": Optional[str], "command_whitespace": Optional[str],
}, },
@@ -378,11 +376,12 @@ class CommandRule:
async def __call__( async def __call__(
self, self,
cmd: Optional[Tuple[str, ...]] = Command(), cmd: Optional[Tuple[str, ...]] = Command(),
cmd_arg: Optional[Message] = CommandArg(),
cmd_whitespace: Optional[str] = CommandWhitespace(), cmd_whitespace: Optional[str] = CommandWhitespace(),
) -> bool: ) -> bool:
if cmd not in self.cmds: if cmd not in self.cmds:
return False return False
if self.force_whitespace is None: if self.force_whitespace is None or not cmd_arg:
return True return True
if isinstance(self.force_whitespace, str): if isinstance(self.force_whitespace, str):
return self.force_whitespace == cmd_whitespace return self.force_whitespace == cmd_whitespace
@@ -450,30 +449,61 @@ class ArgumentParser(ArgParser):
if TYPE_CHECKING: if TYPE_CHECKING:
@overload @overload
def parse_args( def parse_known_args(
self, args: Optional[Sequence[Union[str, MessageSegment]]] = ... self,
) -> Namespace: args: Optional[Sequence[Union[str, MessageSegment]]] = None,
namespace: None = None,
) -> Tuple[Namespace, List[Union[str, MessageSegment]]]:
... ...
@overload @overload
def parse_args( def parse_known_args(
self, args: Optional[Sequence[Union[str, MessageSegment]]], namespace: None
) -> Namespace:
... # type: ignore[misc]
@overload
def parse_args(
self, args: Optional[Sequence[Union[str, MessageSegment]]], namespace: T self, args: Optional[Sequence[Union[str, MessageSegment]]], namespace: T
) -> T: ) -> Tuple[T, List[Union[str, MessageSegment]]]:
... ...
def parse_args( @overload
def parse_known_args(
self, *, namespace: T
) -> Tuple[T, List[Union[str, MessageSegment]]]:
...
def parse_known_args(
self, self,
args: Optional[Sequence[Union[str, MessageSegment]]] = None, args: Optional[Sequence[Union[str, MessageSegment]]] = None,
namespace: Optional[T] = None, namespace: Optional[T] = None,
) -> Union[Namespace, T]: ) -> Tuple[Union[Namespace, T], List[Union[str, MessageSegment]]]:
... ...
@overload
def parse_args(
self,
args: Optional[Sequence[Union[str, MessageSegment]]] = None,
namespace: None = None,
) -> Namespace:
...
@overload
def parse_args(
self, args: Optional[Sequence[Union[str, MessageSegment]]], namespace: T
) -> 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( def _parse_optional(
self, arg_string: Union[str, MessageSegment] self, arg_string: Union[str, MessageSegment]
) -> Optional[Tuple[Optional[Action], str, Optional[str]]]: ) -> Optional[Tuple[Optional[Action], str, Optional[str]]]:
@@ -646,10 +676,7 @@ class RegexRule:
except Exception: except Exception:
return False return False
if matched := re.search(self.regex, str(msg), self.flags): if matched := re.search(self.regex, str(msg), self.flags):
state[REGEX_MATCHED] = matched.group() state[REGEX_MATCHED] = matched
state[REGEX_STR] = matched.group()
state[REGEX_GROUP] = matched.groups()
state[REGEX_DICT] = matched.groupdict()
return True return True
else: else:
return False return False

View File

@@ -12,6 +12,7 @@ import inspect
import importlib import importlib
import dataclasses import dataclasses
from pathlib import Path from pathlib import Path
from contextvars import copy_context
from functools import wraps, partial from functools import wraps, partial
from contextlib import asynccontextmanager from contextlib import asynccontextmanager
from typing_extensions import ParamSpec, get_args, get_origin from typing_extensions import ParamSpec, get_args, get_origin
@@ -58,7 +59,7 @@ def generic_check_issubclass(
"""检查 cls 是否是 class_or_tuple 中的一个类型子类。 """检查 cls 是否是 class_or_tuple 中的一个类型子类。
特别的,如果 cls 是 `typing.Union` 或 `types.UnionType` 类型, 特别的,如果 cls 是 `typing.Union` 或 `types.UnionType` 类型,
则会检查其中的类型是否是 class_or_tuple 中一个类型子类。(None 会被忽略) 则会检查其中的所有类型是否是 class_or_tuple 中一个类型子类None
""" """
try: try:
return issubclass(cls, class_or_tuple) return issubclass(cls, class_or_tuple)
@@ -111,7 +112,8 @@ def run_sync(call: Callable[P, R]) -> Callable[P, Coroutine[None, None, R]]:
async def _wrapper(*args: P.args, **kwargs: P.kwargs) -> R: async def _wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
loop = asyncio.get_running_loop() loop = asyncio.get_running_loop()
pfunc = partial(call, *args, **kwargs) 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 result
return _wrapper return _wrapper
@@ -136,6 +138,7 @@ async def run_sync_ctx_manager(
async def run_coro_with_catch( async def run_coro_with_catch(
coro: Coroutine[Any, Any, T], coro: Coroutine[Any, Any, T],
exc: Tuple[Type[Exception], ...], exc: Tuple[Type[Exception], ...],
return_on_err: None = None,
) -> Union[T, None]: ) -> Union[T, None]:
... ...
@@ -193,7 +196,7 @@ def resolve_dot_notation(
class DataclassEncoder(json.JSONEncoder): class DataclassEncoder(json.JSONEncoder):
"""在JSON序列化 {re}`nonebot.adapters._message.Message` (List[Dataclass]) 时使用的 `JSONEncoder`""" """在JSON序列化 {ref}`nonebot.adapters.Message` (List[Dataclass]) 时使用的 `JSONEncoder`"""
@overrides(json.JSONEncoder) @overrides(json.JSONEncoder)
def default(self, o): def default(self, o):

View File

@@ -1,5 +1,5 @@
<p align="center"> <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> </p>
<div align="center"> <div align="center">

954
poetry.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,13 +1,13 @@
[tool.poetry] [tool.poetry]
name = "nonebot2" name = "nonebot2"
version = "2.0.0rc4" version = "2.0.0"
description = "An asynchronous python bot framework." description = "An asynchronous python bot framework."
authors = ["yanyongyu <yyy@nonebot.dev>"] authors = ["yanyongyu <yyy@nonebot.dev>"]
license = "MIT" license = "MIT"
readme = "README.md" readme = "README.md"
homepage = "https://v2.nonebot.dev/" homepage = "https://nonebot.dev/"
repository = "https://github.com/nonebot/nonebot2" repository = "https://github.com/nonebot/nonebot2"
documentation = "https://v2.nonebot.dev/" documentation = "https://nonebot.dev/"
keywords = ["bot", "qq", "qqbot", "mirai", "coolq"] keywords = ["bot", "qq", "qqbot", "mirai", "coolq"]
classifiers = [ classifiers = [
"Development Status :: 5 - Production/Stable", "Development Status :: 5 - Production/Stable",
@@ -21,16 +21,21 @@ packages = [
] ]
include = ["nonebot/py.typed"] 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] [tool.poetry.dependencies]
python = "^3.8" python = "^3.8"
yarl = "^1.7.2" yarl = "^1.7.2"
loguru = "^0.6.0"
pygtrie = "^2.4.1" pygtrie = "^2.4.1"
loguru = ">=0.6.0,<1.0.0"
typing-extensions = ">=4.0.0,<5.0.0" typing-extensions = ">=4.0.0,<5.0.0"
tomli = { version = "^2.0.1", python = "<3.11" } tomli = { version = "^2.0.1", python = "<3.11" }
pydantic = { version = "^1.10.0", extras = ["dotenv"] } pydantic = { version = "^1.10.0", extras = ["dotenv"] }
websockets = { version = "^10.0", optional = true } websockets = { version = ">=10.0", optional = true }
Quart = { version = ">=0.18.0,<1.0.0", optional = true } Quart = { version = ">=0.18.0,<1.0.0", optional = true }
fastapi = { version = ">=0.93.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 } aiohttp = { version = "^3.7.4", extras = ["speedups"], optional = true }

View File

@@ -25,6 +25,6 @@ def load_plugin(nonebug_init: None) -> Set["Plugin"]:
@pytest.fixture(scope="session", autouse=True) @pytest.fixture(scope="session", autouse=True)
def load_example(nonebug_init: None) -> Set["Plugin"]: def load_builtin_plugin(nonebug_init: None) -> Set["Plugin"]:
# preload example plugins # preload builtin plugins
return nonebot.load_plugins(str(Path(__file__).parent / "examples")) return nonebot.load_builtin_plugins("echo", "single_session")

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}的天气是..."

View File

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

View File

@@ -1,4 +1,4 @@
from typing import List, Tuple from typing import List, Match, Tuple
from nonebot.typing import T_State from nonebot.typing import T_State
from nonebot.adapters import Message from nonebot.adapters import Message
@@ -73,12 +73,12 @@ async def regex_group(regex_group: Tuple = RegexGroup()) -> Tuple:
return regex_group return regex_group
async def regex_matched(regex_matched: str = RegexMatched()) -> str: async def regex_matched(regex_matched: Match[str] = RegexMatched()) -> Match[str]:
return regex_matched return regex_matched
async def regex_str(regex_matched: str = RegexStr()) -> str: async def regex_str(regex_str: str = RegexStr()) -> str:
return regex_matched return regex_str
async def startswith(startswith: str = Startswith()) -> str: async def startswith(startswith: str = Startswith()) -> str:

View File

@@ -0,0 +1,23 @@
from typing import Optional
from nonebot.typing import T_State
from nonebot.matcher import Matcher
from nonebot.params import Arg, Depends
from nonebot.adapters import Bot, Event, Message
def dependency():
return 1
async def complex_priority(
sub: int = Depends(dependency),
bot: Optional[Bot] = None,
event: Optional[Event] = None,
state: T_State = {},
matcher: Optional[Matcher] = None,
arg: Message = Arg(),
exception: Optional[Exception] = None,
default: int = 1,
):
...

View File

@@ -41,6 +41,26 @@ def test_segment_validate():
parse_obj_as(MessageSegment, {"data": {}}) parse_obj_as(MessageSegment, {"data": {}})
def test_segment_join():
Message = make_fake_message()
MessageSegment = Message.get_segment_class()
seg = MessageSegment.text("test")
iterable = [
MessageSegment.text("first"),
Message([MessageSegment.text("second"), MessageSegment.text("third")]),
]
assert seg.join(iterable) == Message(
[
MessageSegment.text("first"),
MessageSegment.text("test"),
MessageSegment.text("second"),
MessageSegment.text("third"),
]
)
def test_segment(): def test_segment():
Message = make_fake_message() Message = make_fake_message()
MessageSegment = Message.get_segment_class() MessageSegment = Message.get_segment_class()
@@ -146,3 +166,124 @@ def test_message_validate():
with pytest.raises(ValidationError): with pytest.raises(ValidationError):
parse_obj_as(Message, object()) parse_obj_as(Message, object())
def test_message_contains():
Message = make_fake_message()
MessageSegment = Message.get_segment_class()
message = Message(
[
MessageSegment.text("test"),
MessageSegment.image("test2"),
MessageSegment.image("test3"),
MessageSegment.text("test4"),
]
)
assert message.has(MessageSegment.text("test")) is True
assert MessageSegment.text("test") in message
assert message.has("image") is True
assert "image" in message
assert message.has(MessageSegment.text("foo")) is False
assert MessageSegment.text("foo") not in message
assert message.has("foo") is False
assert "foo" not in message
def test_message_only():
Message = make_fake_message()
MessageSegment = Message.get_segment_class()
message = Message(
[
MessageSegment.text("test"),
MessageSegment.text("test2"),
]
)
assert message.only("text") is True
assert message.only(MessageSegment.text("test")) is False
message = Message(
[
MessageSegment.text("test"),
MessageSegment.image("test2"),
MessageSegment.image("test3"),
MessageSegment.text("test4"),
]
)
assert message.only("text") is False
message = Message(
[
MessageSegment.text("test"),
MessageSegment.text("test"),
]
)
assert message.only(MessageSegment.text("test")) is True
def test_message_join():
Message = make_fake_message()
MessageSegment = Message.get_segment_class()
msg = Message([MessageSegment.text("test")])
iterable = [
MessageSegment.text("first"),
Message([MessageSegment.text("second"), MessageSegment.text("third")]),
]
assert msg.join(iterable) == Message(
[
MessageSegment.text("first"),
MessageSegment.text("test"),
MessageSegment.text("second"),
MessageSegment.text("third"),
]
)
def test_message_include():
Message = make_fake_message()
MessageSegment = Message.get_segment_class()
message = Message(
[
MessageSegment.text("test"),
MessageSegment.image("test2"),
MessageSegment.image("test3"),
MessageSegment.text("test4"),
]
)
assert message.include("text") == Message(
[
MessageSegment.text("test"),
MessageSegment.text("test4"),
]
)
def test_message_exclude():
Message = make_fake_message()
MessageSegment = Message.get_segment_class()
message = Message(
[
MessageSegment.text("test"),
MessageSegment.image("test2"),
MessageSegment.image("test3"),
MessageSegment.text("test4"),
]
)
assert message.exclude("image") == Message(
[
MessageSegment.text("test"),
MessageSegment.text("test4"),
]
)

388
tests/test_broadcast.py Normal file
View File

@@ -0,0 +1,388 @@
import sys
from typing import Optional
import pytest
from nonebug import App
from nonebot import on_message
import nonebot.message as message
from utils import make_fake_event
from nonebot.params import Depends
from nonebot.typing import T_State
from nonebot.matcher import Matcher
from nonebot.adapters import Bot, Event
from nonebot.exception import IgnoredException
from nonebot.log import logger, default_filter, default_format
from nonebot.message import (
run_preprocessor,
run_postprocessor,
event_preprocessor,
event_postprocessor,
)
async def _dependency() -> int:
return 1
@pytest.mark.asyncio
async def test_event_preprocessor(app: App, monkeypatch: pytest.MonkeyPatch):
with monkeypatch.context() as m:
m.setattr(message, "_event_preprocessors", set())
runned = False
@event_preprocessor
async def test_preprocessor(
bot: Bot,
event: Event,
state: T_State,
sub: int = Depends(_dependency),
default: int = 1,
):
nonlocal runned
runned = True
assert test_preprocessor in {
dependent.call for dependent in message._event_preprocessors
}
with app.provider.context({}):
matcher = on_message()
async with app.test_matcher(matcher) as ctx:
bot = ctx.create_bot()
event = make_fake_event()()
ctx.receive_event(bot, event)
assert runned, "event_preprocessor should runned"
@pytest.mark.asyncio
async def test_event_preprocessor_ignore(app: App, monkeypatch: pytest.MonkeyPatch):
with monkeypatch.context() as m:
m.setattr(message, "_event_preprocessors", set())
@event_preprocessor
async def test_preprocessor():
raise IgnoredException("pass")
assert test_preprocessor in {
dependent.call for dependent in message._event_preprocessors
}
runned = False
async def handler():
nonlocal runned
runned = True
with app.provider.context({}):
matcher = on_message(handlers=[handler])
async with app.test_matcher(matcher) as ctx:
bot = ctx.create_bot()
event = make_fake_event()()
ctx.receive_event(bot, event)
assert not runned, "matcher should not runned"
@pytest.mark.asyncio
async def test_event_preprocessor_exception(
app: App, monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
):
with monkeypatch.context() as m:
m.setattr(message, "_event_preprocessors", set())
@event_preprocessor
async def test_preprocessor():
raise RuntimeError("test")
assert test_preprocessor in {
dependent.call for dependent in message._event_preprocessors
}
runned = False
async def handler():
nonlocal runned
runned = True
handler_id = logger.add(
sys.stdout,
level=0,
diagnose=False,
filter=default_filter,
format=default_format,
)
try:
with app.provider.context({}):
matcher = on_message(handlers=[handler])
async with app.test_matcher(matcher) as ctx:
bot = ctx.create_bot()
event = make_fake_event()()
ctx.receive_event(bot, event)
finally:
logger.remove(handler_id)
assert not runned, "matcher should not runned"
assert "RuntimeError: test" in capsys.readouterr().out
@pytest.mark.asyncio
async def test_event_postprocessor(app: App, monkeypatch: pytest.MonkeyPatch):
with monkeypatch.context() as m:
m.setattr(message, "_event_postprocessors", set())
runned = False
@event_postprocessor
async def test_postprocessor(
bot: Bot,
event: Event,
state: T_State,
sub: int = Depends(_dependency),
default: int = 1,
):
nonlocal runned
runned = True
assert test_postprocessor in {
dependent.call for dependent in message._event_postprocessors
}
with app.provider.context({}):
matcher = on_message()
async with app.test_matcher(matcher) as ctx:
bot = ctx.create_bot()
event = make_fake_event()()
ctx.receive_event(bot, event)
assert runned, "event_postprocessor should runned"
@pytest.mark.asyncio
async def test_event_postprocessor_exception(
app: App, monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
):
with monkeypatch.context() as m:
m.setattr(message, "_event_postprocessors", set())
@event_postprocessor
async def test_postprocessor():
raise RuntimeError("test")
assert test_postprocessor in {
dependent.call for dependent in message._event_postprocessors
}
handler_id = logger.add(
sys.stdout,
level=0,
diagnose=False,
filter=default_filter,
format=default_format,
)
try:
with app.provider.context({}):
matcher = on_message()
async with app.test_matcher(matcher) as ctx:
bot = ctx.create_bot()
event = make_fake_event()()
ctx.receive_event(bot, event)
finally:
logger.remove(handler_id)
assert "RuntimeError: test" in capsys.readouterr().out
@pytest.mark.asyncio
async def test_run_preprocessor(app: App, monkeypatch: pytest.MonkeyPatch):
with monkeypatch.context() as m:
m.setattr(message, "_run_preprocessors", set())
runned = False
@run_preprocessor
async def test_preprocessor(
bot: Bot,
event: Event,
state: T_State,
matcher: Matcher,
sub: int = Depends(_dependency),
default: int = 1,
):
nonlocal runned
runned = True
await matcher.send("test")
assert test_preprocessor in {
dependent.call for dependent in message._run_preprocessors
}
with app.provider.context({}):
matcher = on_message()
async with app.test_matcher(matcher) as ctx:
bot = ctx.create_bot()
event = make_fake_event()()
ctx.receive_event(bot, event)
ctx.should_call_send(event, "test", True, bot)
assert runned, "run_preprocessor should runned"
@pytest.mark.asyncio
async def test_run_preprocessor_ignore(app: App, monkeypatch: pytest.MonkeyPatch):
with monkeypatch.context() as m:
m.setattr(message, "_run_preprocessors", set())
@run_preprocessor
async def test_preprocessor():
raise IgnoredException("pass")
assert test_preprocessor in {
dependent.call for dependent in message._run_preprocessors
}
runned = False
async def handler():
nonlocal runned
runned = True
with app.provider.context({}):
matcher = on_message(handlers=[handler])
async with app.test_matcher(matcher) as ctx:
bot = ctx.create_bot()
event = make_fake_event()()
ctx.receive_event(bot, event)
assert not runned, "matcher should not runned"
@pytest.mark.asyncio
async def test_run_preprocessor_exception(
app: App, monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
):
with monkeypatch.context() as m:
m.setattr(message, "_run_preprocessors", set())
@run_preprocessor
async def test_preprocessor():
raise RuntimeError("test")
assert test_preprocessor in {
dependent.call for dependent in message._run_preprocessors
}
runned = False
async def handler():
nonlocal runned
runned = True
handler_id = logger.add(
sys.stdout,
level=0,
diagnose=False,
filter=default_filter,
format=default_format,
)
try:
with app.provider.context({}):
matcher = on_message(handlers=[handler])
async with app.test_matcher(matcher) as ctx:
bot = ctx.create_bot()
event = make_fake_event()()
ctx.receive_event(bot, event)
finally:
logger.remove(handler_id)
assert not runned, "matcher should not runned"
assert "RuntimeError: test" in capsys.readouterr().out
@pytest.mark.asyncio
async def test_run_postprocessor(app: App, monkeypatch: pytest.MonkeyPatch):
with monkeypatch.context() as m:
m.setattr(message, "_run_postprocessors", set())
runned = False
@run_postprocessor
async def test_postprocessor(
bot: Bot,
event: Event,
state: T_State,
matcher: Matcher,
exception: Optional[Exception],
sub: int = Depends(_dependency),
default: int = 1,
):
nonlocal runned
runned = True
await matcher.send("test")
assert test_postprocessor in {
dependent.call for dependent in message._run_postprocessors
}
with app.provider.context({}):
matcher = on_message()
async with app.test_matcher(matcher) as ctx:
bot = ctx.create_bot()
event = make_fake_event()()
ctx.receive_event(bot, event)
ctx.should_call_send(event, "test", True, bot)
assert runned, "run_postprocessor should runned"
@pytest.mark.asyncio
async def test_run_postprocessor_exception(
app: App, monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
):
with monkeypatch.context() as m:
m.setattr(message, "_run_postprocessors", set())
@run_postprocessor
async def test_postprocessor():
raise RuntimeError("test")
assert test_postprocessor in {
dependent.call for dependent in message._run_postprocessors
}
handler_id = logger.add(
sys.stdout,
level=0,
diagnose=False,
filter=default_filter,
format=default_format,
)
try:
with app.provider.context({}):
matcher = on_message()
async with app.test_matcher(matcher) as ctx:
bot = ctx.create_bot()
event = make_fake_event()()
ctx.receive_event(bot, event)
finally:
logger.remove(handler_id)
assert "RuntimeError: test" in capsys.readouterr().out

View File

@@ -1,76 +0,0 @@
import pytest
from nonebug import App
from utils import make_fake_event, make_fake_message
@pytest.mark.asyncio
async def test_weather(app: App):
from examples.weather import weather
# 将此处的 make_fake_message() 替换为你要发送的平台消息 Message 类型
# from nonebot.adapters.console import Message
Message = make_fake_message()
async with app.test_matcher(weather) as ctx:
bot = ctx.create_bot()
msg = Message("/天气 上海")
# 将此处的 make_fake_event() 替换为你要发送的平台事件 Event 类型
# from nonebot.adapters.console import MessageEvent
# event = MessageEvent(message=msg, to_me=True, ...)
event = make_fake_event(_message=msg, _to_me=True)()
ctx.receive_event(bot, event)
ctx.should_call_send(event, "上海的天气是...", True)
ctx.should_finished()
async with app.test_matcher(weather) as ctx:
bot = ctx.create_bot()
msg = Message("/天气 南京")
# 将此处的 make_fake_event() 替换为你要发送的平台事件 Event 类型
event = make_fake_event(_message=msg, _to_me=True)()
ctx.receive_event(bot, event)
ctx.should_call_send(
event,
Message.template("你想查询的城市 {} 暂不支持,请重新输入!").format("南京"),
True,
)
ctx.should_rejected()
msg = Message("北京")
event = make_fake_event(_message=msg)()
ctx.receive_event(bot, event)
ctx.should_call_send(event, "北京的天气是...", True)
ctx.should_finished()
async with app.test_matcher(weather) as ctx:
bot = ctx.create_bot()
msg = Message("/天气")
# 将此处的 make_fake_event() 替换为你要发送的平台事件 Event 类型
event = make_fake_event(_message=msg, _to_me=True)()
ctx.receive_event(bot, event)
ctx.should_call_send(event, "你想查询哪个城市的天气呢?", True)
msg = Message("杭州")
event = make_fake_event(_message=msg)()
ctx.receive_event(bot, event)
ctx.should_call_send(
event,
Message.template("你想查询的城市 {} 暂不支持,请重新输入!").format("杭州"),
True,
)
ctx.should_rejected()
msg = Message("北京")
event = make_fake_event(_message=msg)()
ctx.receive_event(bot, event)
ctx.should_call_send(event, "北京的天气是...", True)
ctx.should_finished()

View File

@@ -2,8 +2,8 @@ import pytest
from nonebug import App from nonebug import App
from nonebot.permission import User from nonebot.permission import User
from nonebot.message import _check_matcher
from nonebot.matcher import Matcher, matchers from nonebot.matcher import Matcher, matchers
from nonebot.message import check_and_run_matcher
from utils import make_fake_event, make_fake_message from utils import make_fake_event, make_fake_message
@@ -200,19 +200,19 @@ async def test_expire(app: App):
async with app.test_api() as ctx: async with app.test_api() as ctx:
bot = ctx.create_bot() bot = ctx.create_bot()
assert test_temp_matcher in matchers[test_temp_matcher.priority] assert test_temp_matcher in matchers[test_temp_matcher.priority]
await _check_matcher(test_temp_matcher, bot, event, {}) await check_and_run_matcher(test_temp_matcher, bot, event, {})
assert test_temp_matcher not in matchers[test_temp_matcher.priority] assert test_temp_matcher not in matchers[test_temp_matcher.priority]
event = make_fake_event()() event = make_fake_event()()
async with app.test_api() as ctx: async with app.test_api() as ctx:
bot = ctx.create_bot() bot = ctx.create_bot()
assert test_datetime_matcher in matchers[test_datetime_matcher.priority] assert test_datetime_matcher in matchers[test_datetime_matcher.priority]
await _check_matcher(test_datetime_matcher, bot, event, {}) await check_and_run_matcher(test_datetime_matcher, bot, event, {})
assert test_datetime_matcher not in matchers[test_datetime_matcher.priority] assert test_datetime_matcher not in matchers[test_datetime_matcher.priority]
event = make_fake_event()() event = make_fake_event()()
async with app.test_api() as ctx: async with app.test_api() as ctx:
bot = ctx.create_bot() bot = ctx.create_bot()
assert test_timedelta_matcher in matchers[test_timedelta_matcher.priority] assert test_timedelta_matcher in matchers[test_timedelta_matcher.priority]
await _check_matcher(test_timedelta_matcher, bot, event, {}) await check_and_run_matcher(test_timedelta_matcher, bot, event, {})
assert test_timedelta_matcher not in matchers[test_timedelta_matcher.priority] assert test_timedelta_matcher not in matchers[test_timedelta_matcher.priority]

View File

@@ -1,7 +1,10 @@
import re
import pytest import pytest
from nonebug import App from nonebug import App
from nonebot.matcher import Matcher from nonebot.matcher import Matcher
from nonebot.dependencies import Dependent
from nonebot.exception import TypeMisMatch from nonebot.exception import TypeMisMatch
from utils import make_fake_event, make_fake_message from utils import make_fake_event, make_fake_message
from nonebot.params import ( from nonebot.params import (
@@ -16,15 +19,12 @@ from nonebot.params import (
) )
from nonebot.consts import ( from nonebot.consts import (
CMD_KEY, CMD_KEY,
REGEX_STR,
PREFIX_KEY, PREFIX_KEY,
REGEX_DICT,
SHELL_ARGS, SHELL_ARGS,
SHELL_ARGV, SHELL_ARGV,
CMD_ARG_KEY, CMD_ARG_KEY,
KEYWORD_KEY, KEYWORD_KEY,
RAW_CMD_KEY, RAW_CMD_KEY,
REGEX_GROUP,
ENDSWITH_KEY, ENDSWITH_KEY,
CMD_START_KEY, CMD_START_KEY,
FULLMATCH_KEY, FULLMATCH_KEY,
@@ -226,6 +226,7 @@ async def test_state(app: App):
) )
fake_message = make_fake_message()("text") fake_message = make_fake_message()("text")
fake_matched = re.match(r"\[cq:(?P<type>.*?),(?P<arg>.*?)\]", "[cq:test,arg=value]")
fake_state = { fake_state = {
PREFIX_KEY: { PREFIX_KEY: {
CMD_KEY: ("cmd",), CMD_KEY: ("cmd",),
@@ -236,10 +237,7 @@ async def test_state(app: App):
}, },
SHELL_ARGV: ["-h"], SHELL_ARGV: ["-h"],
SHELL_ARGS: {"help": True}, SHELL_ARGS: {"help": True},
REGEX_MATCHED: "[cq:test,arg=value]", REGEX_MATCHED: fake_matched,
REGEX_STR: "[cq:test,arg=value]",
REGEX_GROUP: ("test", "arg=value"),
REGEX_DICT: {"type": "test", "arg": "value"},
STARTSWITH_KEY: "startswith", STARTSWITH_KEY: "startswith",
ENDSWITH_KEY: "endswith", ENDSWITH_KEY: "endswith",
FULLMATCH_KEY: "fullmatch", FULLMATCH_KEY: "fullmatch",
@@ -312,19 +310,19 @@ async def test_state(app: App):
regex_str, allow_types=[StateParam, DependParam] regex_str, allow_types=[StateParam, DependParam]
) as ctx: ) as ctx:
ctx.pass_params(state=fake_state) ctx.pass_params(state=fake_state)
ctx.should_return(fake_state[REGEX_STR]) ctx.should_return("[cq:test,arg=value]")
async with app.test_dependent( async with app.test_dependent(
regex_group, allow_types=[StateParam, DependParam] regex_group, allow_types=[StateParam, DependParam]
) as ctx: ) as ctx:
ctx.pass_params(state=fake_state) ctx.pass_params(state=fake_state)
ctx.should_return(fake_state[REGEX_GROUP]) ctx.should_return(("test", "arg=value"))
async with app.test_dependent( async with app.test_dependent(
regex_dict, allow_types=[StateParam, DependParam] regex_dict, allow_types=[StateParam, DependParam]
) as ctx: ) as ctx:
ctx.pass_params(state=fake_state) ctx.pass_params(state=fake_state)
ctx.should_return(fake_state[REGEX_DICT]) ctx.should_return({"type": "test", "arg": "arg=value"})
async with app.test_dependent( async with app.test_dependent(
startswith, allow_types=[StateParam, DependParam] startswith, allow_types=[StateParam, DependParam]
@@ -416,3 +414,41 @@ async def test_default(app: App):
async with app.test_dependent(default, allow_types=[DefaultParam]) as ctx: async with app.test_dependent(default, allow_types=[DefaultParam]) as ctx:
ctx.should_return(1) ctx.should_return(1)
@pytest.mark.asyncio
async def test_priority():
from plugins.param.priority import complex_priority
dependent = Dependent.parse(
call=complex_priority,
allow_types=[
DependParam,
BotParam,
EventParam,
StateParam,
MatcherParam,
ArgParam,
ExceptionParam,
DefaultParam,
],
)
for param in dependent.params:
if param.name == "sub":
assert isinstance(param.field_info, DependParam)
elif param.name == "bot":
assert isinstance(param.field_info, BotParam)
elif param.name == "event":
assert isinstance(param.field_info, EventParam)
elif param.name == "state":
assert isinstance(param.field_info, StateParam)
elif param.name == "matcher":
assert isinstance(param.field_info, MatcherParam)
elif param.name == "arg":
assert isinstance(param.field_info, ArgParam)
elif param.name == "exception":
assert isinstance(param.field_info, ExceptionParam)
elif param.name == "default":
assert isinstance(param.field_info, DefaultParam)
else:
raise ValueError(f"unknown param {param.name}")

View File

@@ -22,11 +22,11 @@ async def test_load_plugin():
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_load_plugins(load_plugin: Set[Plugin], load_example: Set[Plugin]): async def test_load_plugins(load_plugin: Set[Plugin], load_builtin_plugin: Set[Plugin]):
loaded_plugins = { loaded_plugins = {
plugin for plugin in nonebot.get_loaded_plugins() if not plugin.parent_plugin plugin for plugin in nonebot.get_loaded_plugins() if not plugin.parent_plugin
} }
assert loaded_plugins >= load_plugin | load_example assert loaded_plugins >= load_plugin | load_builtin_plugin
# check simple plugin # check simple plugin
assert "plugins.export" in sys.modules assert "plugins.export" in sys.modules
@@ -128,7 +128,7 @@ async def test_require_not_found():
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_plugin_metadata(): async def test_plugin_metadata():
from plugins.metadata import Config from plugins.metadata import Config, FakeAdapter
plugin = nonebot.get_plugin("metadata") plugin = nonebot.get_plugin("metadata")
assert plugin assert plugin
@@ -137,6 +137,11 @@ async def test_plugin_metadata():
"name": "测试插件", "name": "测试插件",
"description": "测试插件元信息", "description": "测试插件元信息",
"usage": "无法使用", "usage": "无法使用",
"type": "application",
"homepage": "https://nonebot.dev",
"config": Config, "config": Config,
"supported_adapters": {"~onebot.v11", "plugins.metadata:FakeAdapter"},
"extra": {"author": "NoneBot"}, "extra": {"author": "NoneBot"},
} }
assert plugin.metadata.get_supported_adapters() == {FakeAdapter}

View File

@@ -1,5 +1,6 @@
import re
import sys import sys
from typing import Dict, Tuple, Union, Optional from typing import Match, Tuple, Union, Optional
import pytest import pytest
from nonebug import App from nonebug import App
@@ -9,14 +10,11 @@ from utils import make_fake_event, make_fake_message
from nonebot.exception import ParserExit, SkippedException from nonebot.exception import ParserExit, SkippedException
from nonebot.consts import ( from nonebot.consts import (
CMD_KEY, CMD_KEY,
REGEX_STR,
PREFIX_KEY, PREFIX_KEY,
REGEX_DICT,
SHELL_ARGS, SHELL_ARGS,
SHELL_ARGV, SHELL_ARGV,
CMD_ARG_KEY, CMD_ARG_KEY,
KEYWORD_KEY, KEYWORD_KEY,
REGEX_GROUP,
ENDSWITH_KEY, ENDSWITH_KEY,
FULLMATCH_KEY, FULLMATCH_KEY,
REGEX_MATCHED, REGEX_MATCHED,
@@ -275,22 +273,34 @@ async def test_keyword(
@pytest.mark.asyncio @pytest.mark.asyncio
@pytest.mark.parametrize( @pytest.mark.parametrize(
"cmds, cmd, force_whitespace, whitespace, expected", "cmds, force_whitespace, cmd, whitespace, arg_text, expected",
[ [
[(("help",),), ("help",), None, None, True], # command tests
[(("help",),), ("foo",), None, None, False], [(("help",),), None, ("help",), None, None, True],
[(("help", "foo"),), ("help", "foo"), True, " ", True], [(("help",),), None, ("foo",), None, None, False],
[(("help",), ("foo",)), ("help",), " ", " ", True], [(("help", "foo"),), None, ("help", "foo"), None, None, True],
[(("help",),), ("help",), False, " ", False], [(("help", "foo"),), None, ("help", "bar"), None, None, False],
[(("help",),), ("help",), True, None, False], [(("help",), ("foo",)), None, ("help",), None, None, True],
[(("help",),), ("help",), "\n", " ", False], [(("help",), ("foo",)), None, ("bar",), None, None, False],
# whitespace tests
[(("help",),), True, ("help",), " ", "arg", True],
[(("help",),), True, ("help",), None, "arg", False],
[(("help",),), True, ("help",), None, None, True],
[(("help",),), False, ("help",), " ", "arg", False],
[(("help",),), False, ("help",), None, "arg", True],
[(("help",),), False, ("help",), None, None, True],
[(("help",),), " ", ("help",), " ", "arg", True],
[(("help",),), " ", ("help",), "\n", "arg", False],
[(("help",),), " ", ("help",), None, "arg", False],
[(("help",),), " ", ("help",), None, None, True],
], ],
) )
async def test_command( async def test_command(
cmds: Tuple[Tuple[str, ...]], cmds: Tuple[Tuple[str, ...]],
cmd: Tuple[str, ...],
force_whitespace: Optional[Union[str, bool]], force_whitespace: Optional[Union[str, bool]],
cmd: Tuple[str, ...],
whitespace: Optional[str], whitespace: Optional[str],
arg_text: Optional[str],
expected: bool, expected: bool,
): ):
test_command = command(*cmds, force_whitespace=force_whitespace) test_command = command(*cmds, force_whitespace=force_whitespace)
@@ -300,7 +310,10 @@ async def test_command(
assert isinstance(checker, CommandRule) assert isinstance(checker, CommandRule)
assert checker.cmds == cmds assert checker.cmds == cmds
state = {PREFIX_KEY: {CMD_KEY: cmd, CMD_WHITESPACE_KEY: whitespace}} arg = arg_text if arg_text is None else make_fake_message()(arg_text)
state = {
PREFIX_KEY: {CMD_KEY: cmd, CMD_WHITESPACE_KEY: whitespace, CMD_ARG_KEY: arg}
}
assert await dependent(state=state) == expected assert await dependent(state=state) == expected
@@ -371,6 +384,19 @@ async def test_shell_command():
assert state[SHELL_ARGS].status != 0 assert state[SHELL_ARGS].status != 0
assert state[SHELL_ARGS].message.startswith(parser.format_usage() + "test: error:") assert state[SHELL_ARGS].message.startswith(parser.format_usage() + "test: error:")
test_parser_remain_args = shell_command(CMD, parser=parser)
dependent = list(test_parser_remain_args.checkers)[0]
checker = dependent.call
assert isinstance(checker, ShellCommandRule)
message = MessageSegment.text("-a 1 2") + MessageSegment.image("test")
event = make_fake_event(_message=message)()
state = {PREFIX_KEY: {CMD_KEY: CMD, CMD_ARG_KEY: message}}
assert await dependent(event=event, state=state)
assert state[SHELL_ARGV] == ["-a", "1", "2", MessageSegment.image("test")]
assert isinstance(state[SHELL_ARGS], ParserExit)
assert state[SHELL_ARGS].status != 0
assert state[SHELL_ARGS].message.startswith(parser.format_usage() + "test: error:")
test_message_parser = shell_command(CMD, parser=parser) test_message_parser = shell_command(CMD, parser=parser)
dependent = list(test_message_parser.checkers)[0] dependent = list(test_message_parser.checkers)[0]
checker = dependent.call checker = dependent.call
@@ -401,21 +427,18 @@ async def test_shell_command():
@pytest.mark.asyncio @pytest.mark.asyncio
@pytest.mark.parametrize( @pytest.mark.parametrize(
"pattern, type, text, expected, matched, string, group, dict", "pattern, type, text, expected, matched",
[ [
( (
r"(?P<key>key\d)", r"(?P<key>key\d)",
"message", "message",
"_key1_", "_key1_",
True, True,
"key1", re.search(r"(?P<key>key\d)", "_key1_"),
"key1",
("key1",),
{"key": "key1"},
), ),
(r"foo", "message", None, False, None, None, None, None), (r"foo", "message", None, False, None),
(r"foo", "notice", "foo", True, "foo", "foo", tuple(), {}), (r"foo", "notice", "foo", True, re.search(r"foo", "foo")),
(r"foo", "notice", "bar", False, None, None, None, None), (r"foo", "notice", "bar", False, None),
], ],
) )
async def test_regex( async def test_regex(
@@ -423,10 +446,7 @@ async def test_regex(
type: str, type: str,
text: Optional[str], text: Optional[str],
expected: bool, expected: bool,
matched: Optional[str], matched: Optional[Match[str]],
string: Optional[str],
group: Optional[Tuple[str, ...]],
dict: Optional[Dict[str, str]],
): ):
test_regex = regex(pattern) test_regex = regex(pattern)
dependent = list(test_regex.checkers)[0] dependent = list(test_regex.checkers)[0]
@@ -439,10 +459,13 @@ async def test_regex(
event = make_fake_event(_type=type, _message=message)() event = make_fake_event(_type=type, _message=message)()
state = {} state = {}
assert await dependent(event=event, state=state) == expected assert await dependent(event=event, state=state) == expected
assert state.get(REGEX_MATCHED) == matched result: Optional[Match[str]] = state.get(REGEX_MATCHED)
assert state.get(REGEX_STR) == string if matched is None:
assert state.get(REGEX_GROUP) == group assert result is None
assert state.get(REGEX_DICT) == dict else:
assert isinstance(result, Match)
assert result.group() == matched.group()
assert result.span() == matched.span()
@pytest.mark.asyncio @pytest.mark.asyncio

View File

@@ -13,7 +13,7 @@ def escape_text(s: str, *, escape_comma: bool = True) -> str:
def make_fake_message(): def make_fake_message():
class FakeMessageSegment(MessageSegment): class FakeMessageSegment(MessageSegment["FakeMessage"]):
@classmethod @classmethod
def get_message_class(cls): def get_message_class(cls):
return FakeMessage return FakeMessage
@@ -36,7 +36,7 @@ def make_fake_message():
def is_text(self) -> bool: def is_text(self) -> bool:
return self.type == "text" return self.type == "text"
class FakeMessage(Message): class FakeMessage(Message[FakeMessageSegment]):
@classmethod @classmethod
def get_segment_class(cls): def get_segment_class(cls):
return FakeMessageSegment return FakeMessageSegment
@@ -50,7 +50,9 @@ def make_fake_message():
yield FakeMessageSegment(**seg) yield FakeMessageSegment(**seg)
return return
def __add__(self, other): def __add__(
self, other: Union[str, FakeMessageSegment, Iterable[FakeMessageSegment]]
):
other = escape_text(other) if isinstance(other, str) else other other = escape_text(other) if isinstance(other, str) else other
return super().__add__(other) return super().__add__(other)

View File

@@ -35,7 +35,7 @@ driver = nonebot.get_driver()
driver.register_adapter(Adapter) driver.register_adapter(Adapter)
``` ```
我们首先需要从适配器模块中导入所需要的适配器类,然后通过驱动器的 `register_adapter` 方法将适配器注册到驱动器中即可。 我们首先需要从适配器模块中导入所需要的适配器类,然后通过驱动器的 `register_adapter` 方法将适配器注册到驱动器中即可。如果我们需要多平台支持,可以多次调用 `register_adapter` 方法来注册多个适配器。
## 获取已注册的适配器 ## 获取已注册的适配器

View File

@@ -212,19 +212,18 @@ async def _(e: Union[ActionFailed, NetworkError]): ...
from typing import Annotated from typing import Annotated
from nonebot import on_command from nonebot import on_command
from nonebot.adapters import Event
from nonebot.params import Depends from nonebot.params import Depends
from nonebot.matcher import Matcher
from nonebot.adapters.console import MessageEvent
test = on_command("test") test = on_command("test")
async def check(event: MessageEvent, matcher: Matcher) -> MessageEvent: async def check(event: Event) -> Event:
if event.get_user_id() in BLACKLIST: if event.get_user_id() in BLACKLIST:
await matcher.finish() await test.finish()
return event return event
@test.handle() @test.handle()
async def _(event: Annotated[MessageEvent, Depends(check)]): async def _(event: Annotated[Event, Depends(check)]):
... ...
``` ```
@@ -233,19 +232,18 @@ async def _(event: Annotated[MessageEvent, Depends(check)]):
```python {2,14} ```python {2,14}
from nonebot import on_command from nonebot import on_command
from nonebot.adapters import Event
from nonebot.params import Depends from nonebot.params import Depends
from nonebot.matcher import Matcher
from nonebot.adapters.console import MessageEvent
test = on_command("test") test = on_command("test")
async def check(event: MessageEvent, matcher: Matcher) -> MessageEvent: async def check(event: Event) -> Event:
if event.get_user_id() in BLACKLIST: if event.get_user_id() in BLACKLIST:
await matcher.finish() await test.finish()
return event return event
@test.handle() @test.handle()
async def _(event: MessageEvent = Depends(check)): async def _(event: Event = Depends(check)):
... ...
``` ```
@@ -256,6 +254,24 @@ async def _(event: MessageEvent = Depends(check)):
通过将 `Depends` 包裹的子依赖作为参数的默认值,我们就可以在执行事件处理函数之前执行子依赖,并将其返回值作为参数传入事件处理函数。子依赖和普通的事件处理函数并没有区别,同样可以使用依赖注入,并且可以返回任何类型的值。但需要注意的是,如果事件处理函数参数的类型注解与子依赖返回值的类型**不一致**,将会触发[重载](../appendices/overload.md)而跳过当前事件处理函数。 通过将 `Depends` 包裹的子依赖作为参数的默认值,我们就可以在执行事件处理函数之前执行子依赖,并将其返回值作为参数传入事件处理函数。子依赖和普通的事件处理函数并没有区别,同样可以使用依赖注入,并且可以返回任何类型的值。但需要注意的是,如果事件处理函数参数的类型注解与子依赖返回值的类型**不一致**,将会触发[重载](../appendices/overload.md)而跳过当前事件处理函数。
特别的,我们可以为 `Dependent` 对象定义一系列前置子依赖,它们会在参数执行前被顺序执行,且返回值将会被忽略,例如:
```python {2,14}
from nonebot import on_command
from nonebot.adapters import Event
from nonebot.params import Depends
test = on_command("test")
async def check(event: Event):
if event.get_user_id() in BLACKLIST:
await test.finish()
@test.handle(parameterless=[Depends(check)])
async def _():
...
```
### 依赖缓存 ### 依赖缓存
NoneBot 在执行子依赖时,会将其返回值缓存起来。当我们在使用子依赖时,`Depends` 具有一个参数 `use_cache`,默认为 `True`。此时在事件处理流程中,多次使用同一个子依赖时,将会使用缓存中的结果而不会重复执行。这在很多情景中非常有用,例如: NoneBot 在执行子依赖时,会将其返回值缓存起来。当我们在使用子依赖时,`Depends` 具有一个参数 `use_cache`,默认为 `True`。此时在事件处理流程中,多次使用同一个子依赖时,将会使用缓存中的结果而不会重复执行。这在很多情景中非常有用,例如:
@@ -428,7 +444,7 @@ async def get_client() -> AsyncGenerator[httpx.AsyncClient, None]:
@test.handle() @test.handle()
async def _(x: Annotated[httpx.AsyncClient, Depends(get_client)]): async def _(x: Annotated[httpx.AsyncClient, Depends(get_client)]):
resp = await x.get("https://v2.nonebot.dev") resp = await x.get("https://nonebot.dev")
``` ```
</TabItem> </TabItem>
@@ -450,7 +466,7 @@ async def get_client() -> AsyncGenerator[httpx.AsyncClient, None]:
@test.handle() @test.handle()
async def _(x: httpx.AsyncClient = Depends(get_client)): async def _(x: httpx.AsyncClient = Depends(get_client)):
resp = await x.get("https://v2.nonebot.dev") resp = await x.get("https://nonebot.dev")
``` ```
</TabItem> </TabItem>

View File

@@ -10,7 +10,7 @@ options:
# 事件响应器进阶 # 事件响应器进阶
在[指南](../tutorial/matcher.md)与[深入](../appendices/rule.md)中,我们已经介绍了事件响应器的基本用法以及响应规则、权限控制等功能。在这一节中,我们将介绍事件响应器的组成,以及内置的响应规则。 在[指南](../tutorial/matcher.md)与[深入](../appendices/rule.md)中,我们已经介绍了事件响应器的基本用法以及响应规则、权限控制等功能。在这一节中,我们将介绍事件响应器的组成,内置的响应规则,与第三方响应规则拓展
## 事件响应器组成 ## 事件响应器组成
@@ -302,3 +302,119 @@ group = MatcherGroup(rule=to_me())
matcher1 = group.on_message() matcher1 = group.on_message()
matcher2 = group.on_message() matcher2 = group.on_message()
``` ```
## 第三方响应规则
### Alconna
[`nonebot-plugin-alconna`](https://github.com/ArcletProject/nonebot-plugin-alconna) 是一类提供了拓展响应规则的插件。
该插件使用 [Alconna](https://github.com/ArcletProject/Alconna) 作为命令解析器,
是一个简单、灵活、高效的命令参数解析器, 并且不局限于解析命令式字符串。
特点包括:
- 高效
- 直观的命令组件创建方式
- 强大的类型解析与类型转换功能
- 自定义的帮助信息格式
- 多语言支持
- 易用的快捷命令创建与使用
- 可创建命令补全会话, 以实现多轮连续的补全提示
- 可嵌套的多级子命令
- 正则匹配支持
该插件提供了一类新的事件响应器辅助函数 `on_alconna`,以及 `AlconnaResult` 等依赖注入函数。
同时,基于 [Annotated 支持](https://github.com/nonebot/nonebot2/pull/1832), 添加了两类注解 `AlcMatches``AlcResult`
该插件还可以通过 `handle(parameterless)` 来控制一个具体的响应函数是否在不满足条件时跳过响应:
- `pip.handle([Check(assign("add.name", "nb"))])` 表示仅在命令为 `role-group add` 并且 name 为 `nb` 时响应
- `pip.handle([Check(assign("list"))])` 表示仅在命令为 `role-group list` 时响应
- `pip.handle([Check(assign("add"))])` 表示仅在命令为 `role-group add` 时响应
基于 `Alconna` 的特性,该插件同时提供了一系列便捷的消息段标注。
标注可用于在 `Alconna` 中匹配消息中除 text 外的其他消息段,也可用于快速创建各适配器下的消息段。所有标注位于 `nonebot_plugin_alconna.adapters` 中。
#### 插件安装
```shell
nb plugin install nonebot-plugin-alconna
```
```shell
pip install nonebot-plugin-alconna
```
#### 示例
```python
from nonebot_plugin_alconna.adapters import At
from nonebot.adapters.onebot.v12 import Message
from nonebot_plugin_alconna.adapters.onebot12 import Image
from nonebot_plugin_alconna import AlconnaMatches, on_alconna
from nonebot.adapters.onebot.v12 import MessageSegment as Ob12MS
from arclet.alconna import Args, Option, Alconna, Arparma, MultiVar, Subcommand
alc = Alconna(
"role-group",
Subcommand(
"add",
Args["name", str],
Option("member", Args["target", MultiVar(At)]),
),
Option("list"),
)
rg = on_alconna(alc, auto_send_output=True)
@rg.handle()
async def _(result: Arparma = AlconnaMatches()):
if result.find("list"):
img = await gen_role_group_list_image()
await rg.finish(Message([Image(img)]))
if result.find("add"):
group = await create_role_group(result["add.name"])
if result.find("add.member"):
ats: tuple[Ob12MS, ...] = result["add.member.target"]
group.extend(member.data["user_id"] for member in ats)
await rg.finish("添加成功")
```
我们可以看到主要的两大组件:`Option``Subcommand`
`Option` 可以传入一组别名,如 `Option("--foo|-F|--FOO|-f")``Option("--foo", alias=["-F"]`
`Subcommand` 则可以传入自己的 `Option``Subcommand`
他们拥有如下共同参数:
- `help_text`: 传入该组件的帮助信息
- `dest`: 被指定为解析完成时标注匹配结果的标识符,不传入时默认为选项或子命令的名称 (name)
- `requires`: 一段指定顺序的字符串列表,作为唯一的前置序列与命令嵌套替换
- `default`: 默认值,在该组件未被解析时使用使用该值替换。
其次使用了消息段标注,其中 `At` 属于通用标注,而 `Image` 属于 `onebot12` 适配器下的标注。
`on_alconna` 的所有参数如下:
- `command: Alconna | str`: Alconna 命令
- `skip_for_unmatch: bool = True`: 是否在命令不匹配时跳过该响应
- `auto_send_output: bool = False`: 是否自动发送输出信息并跳过响应
- `output_converter: TConvert | None = None`: 输出信息字符串转换为消息序列方法
- `aliases: set[str | tuple[str, ...]] | None = None`: 命令别名, 作用类似于 `on_command` 中的 aliases
- `comp_config: CompConfig | None = None`: 补全会话配置, 不传入则不启用补全会话
`AlconnaMatches` 是一个依赖注入函数,可注入 `Alconna` 命令解析结果。
#### 参考
插件文档: [📦 这里](https://github.com/ArcletProject/nonebot-plugin-alconna/blob/master/docs.md)
官方文档: [👉 指路](https://arclet.top/)
QQ 交流群: [🔗 链接](https://jq.qq.com/?_wv=1027&k=PUPOnCSH)
友链: [📚 文档](https://graiax.cn/guide/message_parser/alconna.html)

View File

@@ -68,3 +68,7 @@ async def handle_onebot(bot: OneBot):
但 Bot 和 Event 二者的参数类型注解具有最高检查优先级,如果二者类型注解不匹配,那么其他依赖注入将不会执行(如:`Depends`)。 但 Bot 和 Event 二者的参数类型注解具有最高检查优先级,如果二者类型注解不匹配,那么其他依赖注入将不会执行(如:`Depends`)。
::: :::
:::tip 提示
如何更好地编写一个跨平台的插件,我们将在[最佳实践](../best-practice/multi-adapter.mdx)中介绍。
:::

View File

@@ -94,28 +94,59 @@ nb docker up
当看到 `Running` 字样时,说明机器人已经启动成功。我们可以通过以下命令来查看机器人的运行日志: 当看到 `Running` 字样时,说明机器人已经启动成功。我们可以通过以下命令来查看机器人的运行日志:
<Tabs groupId="deploy-tool">
<TabItem value="nb-cli" label="NB CLI" default>
```bash ```bash
nb docker logs nb docker logs
``` ```
</TabItem>
<TabItem value="docker-compose" label="Docker Compose">
```bash
docker compose logs
```
</TabItem>
</Tabs>
如果需要停止机器人,我们可以使用以下命令: 如果需要停止机器人,我们可以使用以下命令:
<Tabs groupId="deploy-tool">
<TabItem value="nb-cli" label="NB CLI" default>
```bash ```bash
nb docker down nb docker down
``` ```
</TabItem>
<TabItem value="docker-compose" label="Docker Compose">
```bash
docker compose down
```
</TabItem>
</Tabs>
### 自定义部署 ### 自定义部署
通常情况下,自动生成的配置文件并不能满足复杂场景,我们需要根据实际需求手动修改配置文件。使用以下命令来生成基础配置文件: 在部分情况下,我们需要事先生成 Docker 配置文件,再到生产环境进行部署;或者自动生成的配置文件并不能满足复杂场景,需要根据实际需求手动修改配置文件。我们可以使用以下命令来生成基础配置文件:
```bash ```bash
nb docker generate nb docker generate
``` ```
nb-cli 将会在项目目录下生成 `docker-compose.yml` 和 `Dockerfile` 等配置文件,我们可以参考 [Dockerfile 文件规范](https://docs.docker.com/engine/reference/builder/)和 [Compose 文件规范](https://docs.docker.com/compose/compose-file/)修改这两个文件 nb-cli 将会在项目目录下生成 `docker-compose.yml` 和 `Dockerfile` 等配置文件。在 nb-cli 完成配置文件的生成后,我们可以根据部署环境的实际情况使用 nb-cli 或者 Docker Compose 来启动机器人
我们可以参考 [Dockerfile 文件规范](https://docs.docker.com/engine/reference/builder/)和 [Compose 文件规范](https://docs.docker.com/compose/compose-file/)修改这两个文件。
修改完成后我们可以直接启动或者手动构建镜像: 修改完成后我们可以直接启动或者手动构建镜像:
<Tabs groupId="deploy-tool">
<TabItem value="nb-cli" label="NB CLI" default>
```bash ```bash
# 启动机器人 # 启动机器人
nb docker up nb docker up
@@ -123,6 +154,19 @@ nb docker up
nb docker build nb docker build
``` ```
</TabItem>
<TabItem value="docker-compose" label="Docker Compose">
```bash
# 启动机器人
docker compose up -d
# 手动构建镜像
docker compose build
```
</TabItem>
</Tabs>
### 持续集成 ### 持续集成
我们可以使用 GitHub Actions 来实现持续集成CI我们只需要在 GitHub 上发布 Release 即可自动构建镜像并推送至镜像仓库。 我们可以使用 GitHub Actions 来实现持续集成CI我们只需要在 GitHub 上发布 Release 即可自动构建镜像并推送至镜像仓库。

View File

@@ -0,0 +1,183 @@
---
sidebar_position: 4
description: 插件跨平台支持
---
# 插件跨平台支持
import Tabs from "@theme/Tabs";
import TabItem from "@theme/TabItem";
由于不同平台的事件与接口之间存在着极大的差异性NoneBot 通过[重载](../appendices/overload.md)的方式,使得插件可以在不同平台上正确响应。但为了减少跨平台的兼容性问题,我们应该尽可能的使用基类方法实现原生跨平台,而不是使用特定平台的方法。当基类方法无法满足需求时,我们可以使用依赖注入的方式,将特定平台的事件或机器人注入到事件处理函数中,实现针对特定平台的处理。
:::tip 提示
如果需要在多平台上**使用**跨平台插件,首先应该根据[注册适配器](../advanced/adapter.md#注册适配器)一节,为机器人注册各平台对应的适配器。
:::
## 基于基类的跨平台
在[事件通用信息](../advanced/adapter.md#获取事件通用信息)中,我们了解了事件基类能够提供的通用信息。同时,[事件响应器操作](../appendices/session-control.mdx#更多事件响应器操作)也为我们提供了基本的用户交互方式。使用这些方法,可以让我们的插件运行在任何平台上。例如,一个简单的命令处理插件:
```python {5,11}
from nonebot import on_command
from nonebot.adapters import Event
async def is_blacklisted(event: Event) -> bool:
return event.get_user_id() not in BLACKLIST
weather = on_command("天气", rule=is_blacklisted, priority=10, block=True)
@weather.handle()
async def handle_function():
await weather.finish("今天的天气是...")
```
由于此插件仅使用了事件通用信息和事件响应器操作的纯文本交互方式,这些方法不使用特定平台的信息或接口,因此是原生跨平台的,并不需要额外处理。但在一些较为复杂的需求下,例如发送图片消息时,并非所有平台都具有统一的接口,因此基类便无能为力,我们需要引入特定平台的适配器了。
## 基于重载的跨平台
重载是 NoneBot 跨平台操作的核心,在[事件类型与重载](../appendices/overload.md#重载)一节中,我们初步了解了如何通过类型注解来实现针对不同平台事件的处理方式。在[依赖注入](../advanced/dependency.mdx)一节中,我们又对依赖注入的使用方法进行了详细的介绍。结合这两节内容,我们可以实现更复杂的跨平台操作。
### 处理近似事件
对于一系列**差异不大**的事件,我们往往具有相同的处理逻辑。这时,我们不希望将相同的逻辑编写两遍,而应该复用代码,以实现在同一个事件处理函数中处理多个近似事件。我们可以使用[事件重载](../advanced/dependency.mdx#Event)的特性来实现这一功能。例如:
<Tabs groupId="python">
<TabItem value="3.10" label="Python 3.10+" default>
```python
from nonebot import on_command
from nonebot.adapters import Message
from nonebot.params import CommandArg
from nonebot.adapters.onebot.v11 import MessageEvent as OnebotV11MessageEvent
from nonebot.adapters.onebot.v12 import MessageEvent as OnebotV12MessageEvent
echo = on_command("echo", priority=10, block=True)
@echo.handle()
async def handle_function(event: OnebotV11MessageEvent | OnebotV12MessageEvent, args: Message = CommandArg()):
await echo.finish(args)
```
</TabItem>
<TabItem value="3.8" label="Python 3.8+">
```python
from typing import Union
from nonebot import on_command
from nonebot.adapters import Message
from nonebot.params import CommandArg
from nonebot.adapters.onebot.v11 import MessageEvent as OnebotV11MessageEvent
from nonebot.adapters.onebot.v12 import MessageEvent as OnebotV12MessageEvent
echo = on_command("echo", priority=10, block=True)
@echo.handle()
async def handle_function(event: Union[OnebotV11MessageEvent, OnebotV12MessageEvent], args: Message = CommandArg()):
await echo.finish(args)
```
</TabItem>
</Tabs>
### 在依赖注入中使用重载
NoneBot 依赖注入系统提供了自定义子依赖的方法,子依赖的类型同样会影响到事件处理函数的重载行为。例如:
```python
from datetime import datetime
from nonebot import on_command
from nonebot.adapters.console import MessageEvent
echo = on_command("echo", priority=10, block=True)
def get_event_time(event: MessageEvent):
return event.time
# 处理控制台消息事件
@echo.handle()
async def handle_function(time: datetime = Depends(get_event_time)):
await echo.finish(time.strftime("%Y-%m-%d %H:%M:%S"))
```
示例中 ,我们为 `handle_function` 事件处理函数注入了自定义的 `get_event_time` 子依赖,而此子依赖注入参数为 Console 适配器的 `MessageEvent`。因此 `handle_function` 仅会响应 Console 适配器的 `MessageEvent` ,而不能响应其他事件。
### 处理多平台事件
不同平台的事件之间,往往存在着极大的差异性。为了满足我们插件的跨平台运行,通常我们需要抽离业务逻辑,以保证代码的复用性。一个合理的做法是,在事件响应器的处理流程中,首先先针对不同平台的事件分别进行处理,提取出核心业务逻辑所需要的信息;然后再将这些信息传递给业务逻辑处理函数;最后将业务逻辑的输出以各平台合适的方式返回给用户。也就是说,与平台绑定的处理部分应该与平台无关部分尽量分离。例如:
```python
import inspect
from nonebot import on_command
from nonebot.typing import T_State
from nonebot.matcher import Matcher
from nonebot.adapters import Message
from nonebot.params import CommandArg, ArgPlainText
from nonebot.adapters.console import Bot as ConsoleBot
from nonebot.adapters.onebot.v11 import Bot as OnebotBot
from nonebot.adapters.console import MessageSegment as ConsoleMessageSegment
weather = on_command("天气", priority=10, block=True)
@weather.handle()
async def handle_function(matcher: Matcher, args: Message = CommandArg()):
if args.extract_plain_text():
matcher.set_arg("location", args)
async def get_weather(state: T_State, location: str = ArgPlainText()):
if location not in ["北京", "上海", "广州", "深圳"]:
await weather.reject(f"你想查询的城市 {location} 暂不支持,请重新输入!")
state["weather"] = "⛅ 多云 20℃~24℃"
# 处理控制台询问
@weather.got(
"location",
prompt=ConsoleMessageSegment.emoji("question") + "请输入地名",
parameterless=[Depends(get_weather)],
)
async def handle_console(bot: ConsoleBot):
pass
# 处理 OneBot 询问
@weather.got(
"location",
prompt="请输入地名",
parameterless=[Depends(get_weather)],
)
async def handle_onebot(bot: OnebotBot):
pass
# 通过依赖注入或事件处理函数来进行业务逻辑处理
# 处理控制台回复
@weather.handle()
async def handle_console_reply(bot: ConsoleBot, state: T_State, location: str = ArgPlainText()):
await weather.send(
ConsoleMessageSegment.markdown(
inspect.cleandoc(
f"""
# {location}
- 今天
{state['weather']}
"""
)
)
)
# 处理 OneBot 回复
@weather.handle()
async def handle_onebot_reply(bot: OnebotBot, state: T_State, location: str = ArgPlainText()):
await weather.send(f"今天{location}的天气是{state['weather']}")
```
:::tip 提示
NoneBot 社区中有一些插件,例如[all4one](https://github.com/nonepkg/nonebot-plugin-all4one)、[send-anything-anywhere](https://github.com/felinae98/nonebot-plugin-send-anything-anywhere),可以帮助你更好地处理跨平台功能,包括事件处理和消息发送等。
:::

View File

@@ -1,4 +1,4 @@
{ {
"label": "单元测试", "label": "单元测试",
"position": 4 "position": 5
} }

191
website/docs/ospp/2021.md Normal file
View File

@@ -0,0 +1,191 @@
---
sidebar_position: 0
description: 开源软件供应链点亮计划 - 暑期 2021
---
# 暑期 2021
**开源软件供应链点亮计划 - 暑期 2021** 是**中国科学院软件研究所**与 **openEuler 社区**共同举办的一项面向高校学生的暑期活动,旨在鼓励在校学生积极参与开源软件的开发维护,促进优秀开源软件社区的蓬勃发展。关于具体的活动规划、报名方式,请查看该活动的 [官网](https://summer.iscas.ac.cn/) 和 [帮助文档](https://summer.iscas.ac.cn/help/)。
NoneBot 社区有幸作为开源社区参与了本次活动,下面列出了目前我们已经发布的项目,欢迎感兴趣的同学在上面给出的活动官网报名,或通过 <contact@nonebot.dev> 联系我们。
## NoneBot v1
### 更新 NoneBot v1 文档中的“指南”部分
由于 NoneBot v1 和 aiocqhttp 最初基于的 QQ 机器人平台不再提供服务CQHTTP 接口也转型且改名为 OneBot 标准,目前 NoneBot v1 文档的“指南”部分和 aiocqhttp 文档有部分过时内容需要更新。我们希望将其中与旧的机器人平台相关的内容改为基于 go-cqhttp 或通用的 OneBot 表述,同时对 NoneBot v1 的 awesome-bot 示例做一次全面检查,修改其中可能已经不可用的部分。
**难度**:低
**导师**[@cleoold](https://github.com/cleoold)
**产出要求**
- 修改“指南”文档和 aiocqhttp 文档中与旧的 QQ 机器人平台相关的部分
- 检查 awesome-bot 示例是否有已经过时/不可用的地方,并更新/修复
- 修改“图灵机器人”案例,使用其它 AI 聊天 API 提供商(需先做简单调研)
**技术要求**
- 熟悉 Python 编程语言及 asyncio 机制
- 了解 Git 基本用法
- 了解聊天机器人基本开发过程
- 了解 VuePress 更佳
### NoneBot v1 API 文档自动生成
目前 NoneBot v1 的文档中“API”部分是手动编写的在更新代码接口的同时需要手动更新文档可能造成文档与代码不匹配形成额外的维护成本。我们希望将 API 文档改为直接编写在 Python docstring 中,通过工具自动生成 API 文档。
**难度**:中
**导师**[@cleoold](https://github.com/cleoold)
**产出要求**
- 调研市面上常见的 Python API 文档生成工具
- 在代码中补充 API 文档
- 编写或应用开源工具自动生成 API 文档
- 配置 GitHub Actions 或其它 CI 自动化构建和部署 API 文档
**技术要求**
- 熟悉 Python 编程语言及 asyncio 和 Type Hints
- 了解 Git 基本用法
- 了解 Sphinx 等文档生成工具更佳
- 了解 GitHub Actions 等 CI 工具更佳
## NoneBot v2
### NoneBot v2 自动化测试框架“NoneBug”
在聊天机器人的开发过程中,一套自动化的测试机制是非常重要的,特别是对于 NoneBot 2 这类为大型机器人开发而设计的项目来说,需要手动测试每一个边际条件是非常痛苦的。我们希望能够开发一款基于 NoneBot 2 插件机制的自动化测试框架,为 NoneBot 2 用户提供一套易用便捷、高度灵活的自动化测试框架。
**难度**:高
**导师**[@yanyongyu](https://github.com/yanyongyu)
**产出要求**
- 调研现有的 Python 和其它语言集成测试框架
- 设计 NoneBug 的用户 API 和实现方式
- 实现 NoneBug 自动化测试框架
- 编写详细的使用文档
**技术要求**
- 熟悉 Python 编程语言及 asyncio 和 Type Hints
- 了解 Git 基本用法
- 了解 NoneBot v2 的基本原理和使用方式
- 了解主流的 Python 自动化测试框架
### NoneBot v2 Telegram 适配器
目前 NoneBot v2 已支持 OneBot、Mirai HTTP API、钉钉协议社区反馈有更多的平台需求希望能在 NoneBot v2 获得更多的跨平台支持,提高机器人的便携性。同时,我们也希望随着新平台加入,提升现有 NoneBot v2 核心代码的平台通用性。Telegram 是一款较为广泛使用的安全即时聊天软件,同时其官方提供了丰富的聊天机器人 API因此我们希望为 NoneBot v2 编写一个 Telegram 适配器来支持 Telegram 机器人的开发。
**难度**:中
**导师**[@yanyongyu](https://github.com/yanyongyu)
**产出要求**
- 调研 Telegram Bot API 以及 WebHook 等官方接口
- 编写 Telegram 适配器并能够使用
- 代码遵守项目 Contributing 规范
**技术要求**
- 熟悉 Python 编程语言及 asyncio 和 Type Hints
- 了解 Git 基本用法
- 了解 Web 开发相关知识
- 了解 Sphinx 等文档生成工具更佳
### NoneBot v2 飞书适配器
目前 NoneBot v2 已支持 OneBot、Mirai HTTP API、钉钉协议社区反馈有更多的平台需求希望能在 NoneBot v2 获得更多的跨平台支持,提高机器人的便携性。同时,我们也希望随着新平台加入,提升现有 NoneBot v2 核心代码的平台通用性。飞书是目前企业用户广泛使用的即时聊天和协作软件,其官方提供了丰富的聊天机器人 API因此我们希望为 NoneBot v2 编写一个飞书适配器来支持飞书机器人的开发。
**难度**:中
**导师**[@yanyongyu](https://github.com/yanyongyu)
**产出要求**
- 调研飞书机器人 API 以及 WebHook 等官方接口
- 编写飞书适配器并能够使用
- 代码遵守项目 Contributing 规范
**技术要求**
- 熟悉 Python 编程语言及 asyncio 和 Type Hints
- 了解 Git 基本用法
- 了解 Web 开发相关知识
- 了解 Sphinx 等文档生成工具更佳
## OneBot
### 设计 OneBot v12 接口标准
目前的 OneBot 标准的 v11 版本仍然与 QQ 平台有较多耦合,我们希望在 v12 去掉与 QQ 耦合的历史包袱,形成一个通用的、可扩展的、易于使用的同时易于实现的聊天机器人接口标准。
**难度**:中
**导师**[@richardchien](https://github.com/richardchien)
**产出要求**
- 调研各聊天机器人平台的官方/非官方接口特点
- 通用化 OneBot 核心 API分离 QQ 特定的 API去掉无用 API
- 优化现有的通信、消息表示机制
- 补充 QQ 特定的缺失 API
- 文档需符合风格指南
**技术要求**
- 熟悉至少两个聊天平台的聊天机器人开发
- 了解 Git 基本用法
- 了解使用不同语言编写聊天机器人时的常用实践
- 对文档的优雅性与美观性有追求更佳
### 实现 Rust 版 libonebot
目前最常用的 OneBot 实现包括 go-cqhttp、onebot-kotlin、node-onebot 等,这些实现都各自重复实现了 Web 通信、消息解析、配置读写等功能,当社区中的开发者想针对一个新的聊天平台实现 OneBot 时,他们往往同样需要再次实现类似逻辑。我们希望使用 Rust 编写一个 libonebot 模块,该模块实现所有 OneBot 实现所共享的功能,从而方便其他开发者们使用 Rust 快速编写具体的 OneBot 实现。同时,我们希望借此项目在聊天机器人社区中推广 Rust 编程语言。
> 注:这里的逻辑是 libonebot + 针对某聊天平台的对接代码 = 某聊天平台的 OneBot 实现libonebot 要做的是让 OneBot 实现的开发者只需编写针对特定聊天平台的对接代码,而无需关心 OneBot 标准定义的通信方式、消息格式等。
**难度**:高
**导师**[@richardchien](https://github.com/richardchien)
**产出要求**
- 实现所有 OneBot 实现所共享的功能,包括 Web 通信、消息解析、配置读写等
- 充分考虑同时兼容 OneBot v11 和 v12 接口
- 能够根据用户OneBot 实现的开发者)所实现的接口自动实现类似 get_available_apis 等接口
- 编写详细的使用文档
- 如果可能,与 v12 设计项目联动,实现第一手 v12 支持
**技术要求**
- 熟悉聊天机器人开发
- 熟悉 Rust Web 开发
### 实现自选语言版 libonebot
目前最常用的 OneBot 实现包括 go-cqhttp、onebot-kotlin、node-onebot 等,这些实现都各自重复实现了 Web 通信、消息解析、配置读写等功能,当社区中的开发者想针对一个新的聊天平台实现 OneBot 时,他们往往同样需要再次实现类似逻辑。我们希望使用 Python、Go、Kotlin、Node、PHP、C#.NET 等主流语言(任选一个)编写 libonebot 模块,该模块实现所有 OneBot 实现所共享的功能,从而方便其他开发者们使用对应语言快速编写具体的 OneBot 实现。
> 注:这里的逻辑是 libonebot + 针对某聊天平台的对接代码 = 某聊天平台的 OneBot 实现libonebot 要做的是让 OneBot 实现的开发者只需编写针对特定聊天平台的对接代码,而无需关心 OneBot 标准定义的通信方式、消息格式等。
**难度**:中
**导师**[@richardchien](https://github.com/richardchien)
**产出要求**
- 实现所有 OneBot 实现所共享的功能,包括 Web 通信、消息解析、配置读写等
- 充分考虑同时兼容 OneBot v11 和 v12 接口
- 编写详细的使用文档
- 如果可能实现更多附加特性如根据用户OneBot 实现的开发者)所实现的接口自动实现类似 get_available_apis 等接口、实现第一手 v12 支持等
**技术要求**
- 熟悉聊天机器人开发
- 熟悉所选语言的 Web 开发

96
website/docs/ospp/2022.md Normal file
View File

@@ -0,0 +1,96 @@
---
sidebar_position: 1
description: 开源之夏 - 暑期 2022
---
# 暑期 2022
**开源之夏 - 暑期 2022** 是由**开源软件供应链点亮计划**发起、由**中国科学院软件研究所**与 **openEuler 社区**主办的一项面向高校学生的暑期活动,类似 Google Summer of CodeGSoC旨在鼓励在校学生积极参与开源软件的开发维护促进优秀开源软件社区的蓬勃发展。关于具体的活动规划、报名方式请查看该活动的 [官网](https://summer-ospp.ac.cn/) 和 [帮助文档](https://summer-ospp.ac.cn/help/)。
NoneBot 社区有幸作为开源社区 [参与](https://summer-ospp.ac.cn/#/org/orgdetail/e1fb5b8d-125a-4138-b756-25bd32c0a31a/) 了本次活动,下面列出了目前我们已经发布的项目,欢迎感兴趣的同学加入 QQ 群 [737131827](https://jq.qq.com/?_wv=1027&k=PEgyGeEu) 或通过 <contact@nonebot.dev> 联系我们。
## NoneBot2 命令行 CLI 交互体验升级
NoneBot2 为用户提供了命令行脚手架 ──`nb-cli`辅助用户更好地上手项目以及进行开发。nb-cli 主要包括:创建项目、运行项目、安装与卸载插件、部署项目等功能。随着 NoneBot2 Beta 版本的发布,脚手架功能存在一定的定位不明确、功能体验不佳。本项目旨在重新设计 nb-cli 功能框架,完善功能,优化用户体验。
**难度**:进阶
**导师**[@yanyongyu](https://github.com/yanyongyu)
**产出要求**
- 设计 nb-cli 功能框架
- 明确各功能模块
- 设计用户交互模式
- 完成 nb-cli 主要功能代码
- 项目管理
- 插件管理
- 其它
- 同步更新使用文档
**技术要求**
- 熟悉 Python 命令行交互代码编写
- 熟悉 NoneBot2 框架功能
- 熟悉 NoneBot2 项目组织方式
**成果仓库**
- <https://github.com/nonebot/nb-cli>
- <https://github.com/nonebot/nonebot2>
## NoneBot2 命令行即时交互通信设计与实现
NoneBot2 在早期提供了基于网页的 nonebot-plugin-test 插件,无需平台适配接入即可对机器人进行测试,方便了开发者直观的感受机器人文本交互功能。我们希望提供一款基于命令行的适配器/驱动器,用于无平台适配接入、可以运行机器人的场景进行功能体验或测试。
**难度**:进阶
**导师**[@mnixry](https://github.com/mnixry)
**产出要求**
- 设计命令行与 NoneBot2 通信模式
- 直接调用/HTTP/WebSocket
- 设计命令行交互界面
- 实现相应适配器/驱动器
- 同步更新使用说明文档
**技术要求**
- 熟悉 Python 命令行交互代码编写
- 熟悉 NoneBot2 框架功能
- 熟悉 NoneBot2 项目组织方式
**成果仓库**
- <https://github.com/nonebot/adapter-console>
## NoneBot2 用户上手与深入教程设计
NoneBot2 为用户提供了详细的文档介绍,辅助用户更好的上手项目以及进行开发。文档分为基础与进阶两个部分。基础部分帮助新用户快速上手开发,主要包括:安装 NoneBot2、使用脚手架、创建配置项目、使用适配器、加载插件、定义消息事件、处理消息事件、调用平台 API 等。进阶部分向已经熟悉开发流程的用户介绍更多高级技巧主要包括NoneBot2 工作原理、定时任务、权限控制、钩子函数、跨插件访问、单元测试、发布插件等。目前文档对于用户而言过于费解,导致用户难以理解 NoneBot2 开发。本项目旨在优化文档内容,使其更加通俗易懂,不让文档成为用户上手的阻碍,同时完善进阶内容,让有更复杂需求的用户,同样能从文档中受益。
相关 issue
- <https://github.com/nonebot/nonebot2/issues/793>
- <https://github.com/nonebot/nonebot2/issues/295>
**难度**:进阶
**导师**[@SK-415](https://github.com/SK-415)
**产出要求**
- 文档通俗易懂
- 附有适当的图片指引(如 asciinema
- 内容完整,由浅入深
- 适当的界面美化,合理分配布局
**技术要求**
- 熟悉文档结构组织与语言表达
- 熟悉 NoneBot2 框架功能
- 熟悉 NoneBot2 项目组织方式
**成果仓库**
- <https://github.com/nonebot/nonebot2>

89
website/docs/ospp/2023.md Normal file
View File

@@ -0,0 +1,89 @@
---
sidebar_position: 2
description: 开源之夏 - 暑期 2023
---
# 暑期 2023
**开源之夏 - 暑期 2023** 是由**开源软件供应链点亮计划**发起、由**中国科学院软件研究所**与 **openEuler 社区**主办的一项面向高校学生的暑期活动,类似 Google Summer of CodeGSoC旨在鼓励在校学生积极参与开源软件的开发维护促进优秀开源软件社区的蓬勃发展。关于具体的活动规划、报名方式请查看该活动的 [官网](https://summer-ospp.ac.cn/) 和 [帮助文档](https://summer-ospp.ac.cn/help/)。
NoneBot 社区有幸作为开源社区 [参与](https://summer-ospp.ac.cn/org/orgdetail/e1fb5b8d-125a-4138-b756-25bd32c0a31a?lang=zh) 了本次活动,下面列出了目前我们已经发布的项目,欢迎感兴趣的同学通过 <contact@nonebot.dev> 联系我们。
## NoneBot 项目管理图形化面板
NoneBot 目前提供了开箱即用的命令行脚手架来帮助初次使用的用户更快的上手编写应用。但是,对于未有一定开发经验的用户,命令行的使用仍具有一定的困难。此外,其他项目如 koishi、vue 等,均可通过图形化界面的形式为用户提供更便捷的项目开发。因此,我们希望借助现有命令行脚手架的可扩展特性,提供一个项目管理面板服务,以网页的形式帮助用户开发 NoneBot 应用。
**难度**:进阶
**导师**[@mnixry](https://github.com/mnixry)
**产出要求**
- 设计并实现项目管理面板相关功能
- 创建与管理项目
- 配置与运行项目
- NoneBot 插件管理
- 实现相应 nb-cli 插件提供面板服务
- 代码符合 NoneBot Contributing 规范
**技术要求**
- 熟悉 nb-cli 相关功能
- 熟悉 NoneBot 框架功能
- 熟悉前后端相关实现方式
**成果仓库**
- <https://github.com/nonebot/cli-plugin-webui>
## NoneBot Discord 适配器
NoneBot 作为一个跨平台聊天机器人框架,目前已有 OneBot、飞书、Telegram、QQ 频道等诸多平台的适配支持。作为众多用户期待的平台适配之一,我们希望借此机会接入 Discord 聊天机器人。
**难度**:进阶
**导师**[@iyume](https://github.com/iyume)
**产出要求**
- 调研 Discord Bot 相关功能与接口
- 设计与编写 NoneBot Discord 适配器
- 代码符合 NoneBot Contributing 规范
**技术要求**
- 熟悉 NoneBot 框架功能
- 熟悉 NoneBot 各模块职责与适配器编写
**成果仓库**
- <https://github.com/nonebot/adapter-discord>
## NoneBot 数据库支持插件
NoneBot 的插件系统为用户实现应用提供了极高的便捷性,但因此也增加了插件统一管理的难度。目前,我们发现许多用户发布的插件中存在文件存储结构化数据、数据存放散乱等现象,同时插件间也可能产生冲突。因此,我们希望提供一个统一的数据存储与管理方式,便于用户读写应用数据。
**难度**:进阶
**导师**[@yanyongyu](https://github.com/yanyongyu)
**产出要求**
- 设计并实现 ORM 插件
- 提供关系模型定义功能
- 提供模型迁移与管理功能
- 能较好的支持 Python 类型检查与推导
- 编写相应的用户使用文档
- 代码符合 NoneBot Contributing 规范
**技术要求**
- 熟悉 NoneBot 框架功能与插件编写
- 熟悉 SQLAlchemy 等 ORM 框架
- 熟悉 SQLAlchemy ORM
- 熟悉 alembic 等迁移工具
- 熟悉 nb-cli 插件编写
**成果仓库**
- <https://github.com/nonebot/plugin-orm>

View File

@@ -26,8 +26,9 @@ import Messenger from "@site/src/components/Messenger";
例如,我们可以继续改进上一章节中的 `weather` 插件,使其可以获取到 `天气` 命令的地名参数,并根据地名返回天气信息。 例如,我们可以继续改进上一章节中的 `weather` 插件,使其可以获取到 `天气` 命令的地名参数,并根据地名返回天气信息。
```python {8,10} title=weather/__init__.py ```python {9,11} title=weather/__init__.py
from nonebot import on_command from nonebot import on_command
from nonebot.rule import to_me
from nonebot.adapters import Message from nonebot.adapters import Message
from nonebot.params import CommandArg from nonebot.params import CommandArg

View File

@@ -26,7 +26,8 @@ import Messenger from "@site/src/components/Messenger";
顾名思义,“事件处理函数装饰器”是一个[装饰器decorator](https://docs.python.org/zh-cn/3/glossary.html#term-decorator),那么它的使用方法也同[函数定义](https://docs.python.org/zh-cn/3/reference/compound_stmts.html#function-definitions)中所展示的包装用法相同。例如: 顾名思义,“事件处理函数装饰器”是一个[装饰器decorator](https://docs.python.org/zh-cn/3/glossary.html#term-decorator),那么它的使用方法也同[函数定义](https://docs.python.org/zh-cn/3/reference/compound_stmts.html#function-definitions)中所展示的包装用法相同。例如:
```python {5-7} title=weather/__init__.py ```python {6-8} title=weather/__init__.py
from nonebot.rule import to_me
from nonebot.plugin import on_command from nonebot.plugin import on_command
weather = on_command("天气", rule=to_me(), aliases={"weather", "查天气"}, priority=10, block=True) weather = on_command("天气", rule=to_me(), aliases={"weather", "查天气"}, priority=10, block=True)
@@ -44,7 +45,8 @@ async def handle_function():
事件响应器操作与事件处理函数装饰器类似,通常作为事件响应器 `Matcher` 的[类方法](https://docs.python.org/zh-cn/3/library/functions.html#classmethod)存在,因此事件响应器操作的调用方法也是 `Matcher.func()` 的形式。不过不同的是,事件响应器操作并不是装饰器,因此并不需要@进行标注。 事件响应器操作与事件处理函数装饰器类似,通常作为事件响应器 `Matcher` 的[类方法](https://docs.python.org/zh-cn/3/library/functions.html#classmethod)存在,因此事件响应器操作的调用方法也是 `Matcher.func()` 的形式。不过不同的是,事件响应器操作并不是装饰器,因此并不需要@进行标注。
```python {7,8} title=weather/__init__.py ```python {8,9} title=weather/__init__.py
from nonebot.rule import to_me
from nonebot.plugin import on_command from nonebot.plugin import on_command
weather = on_command("天气", rule=to_me(), aliases={"weather", "查天气"}, priority=10, block=True) weather = on_command("天气", rule=to_me(), aliases={"weather", "查天气"}, priority=10, block=True)

View File

@@ -120,16 +120,37 @@ Message(
### 遍历 ### 遍历
`Message` 继承自 `List[MessageSegment]` ,因此可以使用 `for` 循环遍历消息段。 消息序列继承自 `List[MessageSegment]` ,因此可以使用 `for` 循环遍历消息段。
```python ```python
for segment in message: for segment in message:
... ...
``` ```
### 索引与切片 ### 比较
`Message` 对列表的索引与切片进行了增强,在原有列表 int 索引与切片的基础上,支持 `type` 过滤索引与切片 消息和消息段都可以使用 `==``!=` 运算符比较是否相同
```python
MessageSegment.text("text") != MessageSegment.text("foo")
some_message == Message([MessageSegment.text("text")])
```
### 检查消息段
我们可以通过 `in` 运算符或消息序列的 `has` 方法来:
```python
# 是否存在消息段
MessageSegment.text("text") in message
# 是否存在指定类型的消息段
"text" in message
```
### 过滤、索引与切片
消息序列对列表的索引与切片进行了增强,在原有列表 `int` 索引与 `slice` 切片的基础上,支持 `type` 过滤索引与切片。
```python ```python
from nonebot.adapters.console import Message, MessageSegment from nonebot.adapters.console import Message, MessageSegment
@@ -160,7 +181,14 @@ message["markdown", 0:2] == Message(
) )
``` ```
同样的,`Message` 对列表`index``count` 方法进行了增强,可以用于索引指定类型的消息段 我们也可以通过消息序列`include``exclude` 方法进行类型过滤
```python
message.include("text", "markdown")
message.exclude("text")
```
同样的,消息序列对列表的 `index``count` 方法也进行了增强,可以用于索引指定类型的消息段。
```python ```python
# 指定类型首个消息段索引 # 指定类型首个消息段索引
@@ -169,7 +197,7 @@ message.index("markdown") == 1
message.count("markdown") == 2 message.count("markdown") == 2
``` ```
此外,`Message` 添加了一个 `get` 方法,可以用于获取指定类型指定个数的消息段。 此外,消息序列添加了一个 `get` 方法,可以用于获取指定类型指定个数的消息段。
```python ```python
# 获取指定类型指定个数的消息段 # 获取指定类型指定个数的消息段
@@ -214,6 +242,31 @@ msg.append(MessageSegment.text("text"))
msg.extend([MessageSegment.text("text")]) msg.extend([MessageSegment.text("text")])
``` ```
我们也可以通过消息段或消息序列的 `join` 方法来拼接一串消息:
```python
seg = MessageSegment.text("text")
msg = seg.join(
[
MessageSegment.text("first"),
Message(
[
MessageSegment.text("second"),
MessageSegment.text("third"),
]
)
]
)
msg == Message(
[
MessageSegment.text("first"),
MessageSegment.text("text"),
MessageSegment.text("second"),
MessageSegment.text("third"),
]
)
```
### 使用消息模板 ### 使用消息模板
为了提供安全可靠的跨平台模板字符, 我们提供了一个消息模板功能来构建消息序列 为了提供安全可靠的跨平台模板字符, 我们提供了一个消息模板功能来构建消息序列

View File

@@ -8,7 +8,7 @@ const darkCodeTheme = require("prism-react-renderer/themes/dracula");
const config = { const config = {
title: "NoneBot", title: "NoneBot",
tagline: "跨平台 Python 异步机器人框架", tagline: "跨平台 Python 异步机器人框架",
url: "https://v2.nonebot.dev", url: "https://nonebot.dev",
baseUrl: process.env.BASE_URL || "/", baseUrl: process.env.BASE_URL || "/",
onBrokenLinks: "throw", onBrokenLinks: "throw",
onBrokenMarkdownLinks: "warn", onBrokenMarkdownLinks: "warn",
@@ -100,8 +100,10 @@ const config = {
docId: "developer/plugin-publishing", docId: "developer/plugin-publishing",
}, },
{ label: "社区", type: "docLink", docId: "community/contact" }, { label: "社区", type: "docLink", docId: "community/contact" },
{ label: "开源之夏", type: "docLink", docId: "ospp/2023" },
{ label: "商店", to: "/store" }, { label: "商店", to: "/store" },
{ label: "更新日志", to: "/changelog" }, { label: "更新日志", to: "/changelog" },
{ label: "论坛", href: "https://discussions.nonebot.dev" },
], ],
}, },
{ {
@@ -110,14 +112,14 @@ const config = {
}, },
], ],
docsVersionItemAfter: [ docsVersionItemAfter: [
{
label: "2.0.0rc3",
href: "https://63ccf1c05efb245d36e901fa--nonebot2.netlify.app/",
},
{ {
label: "2.0.0a16", label: "2.0.0a16",
href: "https://61d3d9dbcadf413fd3238e89--nonebot2.netlify.app/", href: "https://61d3d9dbcadf413fd3238e89--nonebot2.netlify.app/",
}, },
{
label: "1.x",
href: "https://v1.nonebot.dev/",
},
], ],
}, },
hideableSidebar: true, hideableSidebar: true,

View File

@@ -3,7 +3,7 @@
"version": "2.0.0", "version": "2.0.0",
"description": "跨平台 Python 异步机器人框架", "description": "跨平台 Python 异步机器人框架",
"private": true, "private": true,
"homepage": "https://v2.nonebot.dev/", "homepage": "https://nonebot.dev/",
"repository": "https://github.com/nonebot/nonebot2/", "repository": "https://github.com/nonebot/nonebot2/",
"bugs": { "bugs": {
"url": "https://github.com/nonebot/nonebot2/issues" "url": "https://github.com/nonebot/nonebot2/issues"

View File

@@ -84,6 +84,17 @@ const sidebars = {
}, },
], ],
}, },
{
type: "category",
label: "开源之夏",
collapsible: false,
items: [
{
type: "autogenerated",
dirName: "ospp",
},
],
},
{ {
type: "category", type: "category",
label: "社区资源", label: "社区资源",
@@ -94,6 +105,11 @@ const sidebars = {
label: "商店", label: "商店",
href: "/store", href: "/store",
}, },
{
type: "link",
label: "论坛",
href: "https://discussions.nonebot.dev",
},
], ],
}, },
], ],

View File

@@ -41,42 +41,27 @@ export default function Adapter(): JSX.Element {
const [label, setLabel] = useState<string>(""); const [label, setLabel] = useState<string>("");
const [color, setColor] = useState<string>("#ea5252"); const [color, setColor] = useState<string>("#ea5252");
const urlEncode = (str: string) =>
encodeURIComponent(str).replace(/%2B/gi, "+");
const onSubmit = () => { const onSubmit = () => {
setModalOpen(false); setModalOpen(false);
const title = encodeURIComponent(`Adapter: ${form.name}`).replace( const queries: { key: string; value: string }[] = [
/%2B/gi, { key: "template", value: "adapter_publish.yml" },
"+" { key: "title", value: form.name && `Adapter: ${form.name}` },
); { key: "labels", value: "Adapter" },
const body = encodeURIComponent( { key: "name", value: form.name },
` { key: "description", value: form.desc },
**协议名称:** { key: "pypi", value: form.projectLink },
{ key: "module", value: form.moduleName },
${form.name} { key: "homepage", value: form.homepage },
{ key: "tags", value: JSON.stringify(tags) },
**协议功能:** ];
const urlQueries = queries
${form.desc} .filter((query) => !!query.value)
.map((query) => `${query.key}=${urlEncode(query.value)}`)
**PyPI 项目名:** .join("&");
window.open(`https://github.com/nonebot/nonebot2/issues/new?${urlQueries}`);
${form.projectLink}
**协议 import 包名:**
${form.moduleName}
**协议项目仓库/主页链接:**
${form.homepage}
**标签:**
${JSON.stringify(tags)}
`.trim()
).replace(/%2B/gi, "+");
window.open(
`https://github.com/nonebot/nonebot2/issues/new?title=${title}&body=${body}&labels=Adapter`
);
}; };
const onChange = (event) => { const onChange = (event) => {
const target = event.target; const target = event.target;

View File

@@ -39,31 +39,25 @@ export default function Bot(): JSX.Element {
const [label, setLabel] = useState<string>(""); const [label, setLabel] = useState<string>("");
const [color, setColor] = useState<string>("#ea5252"); const [color, setColor] = useState<string>("#ea5252");
const urlEncode = (str: string) =>
encodeURIComponent(str).replace(/%2B/gi, "+");
const onSubmit = () => { const onSubmit = () => {
setModalOpen(false); setModalOpen(false);
const title = encodeURIComponent(`Bot: ${form.name}`).replace(/%2B/gi, "+"); const queries: { key: string; value: string }[] = [
const body = encodeURIComponent( { key: "template", value: "bot_publish.yml" },
` { key: "title", value: form.name && `Bot: ${form.name}` },
**机器人名称:** { key: "labels", value: "Bot" },
{ key: "name", value: form.name },
${form.name} { key: "description", value: form.desc },
{ key: "homepage", value: form.homepage },
**机器人功能:** { key: "tags", value: JSON.stringify(tags) },
];
${form.desc} const urlQueries = queries
.filter((query) => !!query.value)
**机器人项目仓库/主页链接:** .map((query) => `${query.key}=${urlEncode(query.value)}`)
.join("&");
${form.homepage} window.open(`https://github.com/nonebot/nonebot2/issues/new?${urlQueries}`);
**标签:**
${JSON.stringify(tags)}
`.trim()
).replace(/%2B/gi, "+");
window.open(
`https://github.com/nonebot/nonebot2/issues/new?title=${title}&body=${body}&labels=Bot`
);
}; };
const onChange = (event) => { const onChange = (event) => {
const target = event.target; const target = event.target;

View File

@@ -41,42 +41,27 @@ export default function Plugin(): JSX.Element {
const [label, setLabel] = useState<string>(""); const [label, setLabel] = useState<string>("");
const [color, setColor] = useState<string>("#ea5252"); const [color, setColor] = useState<string>("#ea5252");
const urlEncode = (str: string) =>
encodeURIComponent(str).replace(/%2B/gi, "+");
const onSubmit = () => { const onSubmit = () => {
setModalOpen(false); setModalOpen(false);
const title = encodeURIComponent(`Plugin: ${form.name}`).replace( const queries: { key: string; value: string }[] = [
/%2B/gi, { key: "template", value: "plugin_publish.yml" },
"+" { key: "title", value: form.name && `Plugin: ${form.name}` },
); { key: "labels", value: "Plugin" },
const body = encodeURIComponent( { key: "name", value: form.name },
` { key: "description", value: form.desc },
**插件名称:** { key: "pypi", value: form.projectLink },
{ key: "module", value: form.moduleName },
${form.name} { key: "homepage", value: form.homepage },
{ key: "tags", value: JSON.stringify(tags) },
**插件功能:** ];
const urlQueries = queries
${form.desc} .filter((query) => !!query.value)
.map((query) => `${query.key}=${urlEncode(query.value)}`)
**PyPI 项目名:** .join("&");
window.open(`https://github.com/nonebot/nonebot2/issues/new?${urlQueries}`);
${form.projectLink}
**插件 import 包名:**
${form.moduleName}
**插件项目仓库/主页链接:**
${form.homepage}
**标签:**
${JSON.stringify(tags)}
`.trim()
).replace(/%2B/gi, "+");
window.open(
`https://github.com/nonebot/nonebot2/issues/new?title=${title}&body=${body}&labels=Plugin`
);
}; };
const onChange = (event) => { const onChange = (event) => {
const target = event.target; const target = event.target;

View File

@@ -5,6 +5,136 @@ toc_max_heading_level: 2
# 更新日志 # 更新日志
## v2.0.0
### 💥 破坏性变更
- Feature: 支持 `re.Match` 依赖注入 [@yanyongyu](https://github.com/yanyongyu) ([#1950](https://github.com/nonebot/nonebot2/pull/1950))
### 🚀 新功能
- Feature: 优化事件分发方法 [@yanyongyu](https://github.com/yanyongyu) ([#2067](https://github.com/nonebot/nonebot2/pull/2067))
- Feature: 移除部分依赖注入参数默认值检查 [@yanyongyu](https://github.com/yanyongyu) ([#2034](https://github.com/nonebot/nonebot2/pull/2034))
- Feature: 添加插件元数据字段 `type` `homepage` `supported_adapters` [@yanyongyu](https://github.com/yanyongyu) ([#2012](https://github.com/nonebot/nonebot2/pull/2012))
- Feature: 支持 `re.Match` 依赖注入 [@yanyongyu](https://github.com/yanyongyu) ([#1950](https://github.com/nonebot/nonebot2/pull/1950))
- Feature: 支持主动停止 `none` 系列驱动器 [@yanyongyu](https://github.com/yanyongyu) ([#1951](https://github.com/nonebot/nonebot2/pull/1951))
- Feature: 为消息类添加 `has` `join` `include` `exclude` 方法 [@yanyongyu](https://github.com/yanyongyu) ([#1895](https://github.com/nonebot/nonebot2/pull/1895))
### 🐛 Bug 修复
- Fix: 修复插件 require 未声明插件会识别为子插件 [@yanyongyu](https://github.com/yanyongyu) ([#2040](https://github.com/nonebot/nonebot2/pull/2040))
- Fix: 修复命令强制空白符影响无参数情况 [@yanyongyu](https://github.com/yanyongyu) ([#1975](https://github.com/nonebot/nonebot2/pull/1975))
- Fix: `run_sync` 上下文 [@synodriver](https://github.com/synodriver) ([#1968](https://github.com/nonebot/nonebot2/pull/1968))
- Fix: shell command 包含富文本时报错信息出错 [@yanyongyu](https://github.com/yanyongyu) ([#1923](https://github.com/nonebot/nonebot2/pull/1923))
### 📝 文档
- Docs: 添加 Alconna 响应器介绍 [@RF-Tar-Railt](https://github.com/RF-Tar-Railt) ([#2069](https://github.com/nonebot/nonebot2/pull/2069))
- Docs: 更新 README 适配器链接 [@yanyongyu](https://github.com/yanyongyu) ([#2068](https://github.com/nonebot/nonebot2/pull/2068))
- Docs: 使用 issue form 进行商店发布 [@yanyongyu](https://github.com/yanyongyu) ([#2010](https://github.com/nonebot/nonebot2/pull/2010))
- Docs: 修复获取事件信息文档代码范例中的高亮行 [@Lptr-byte](https://github.com/Lptr-byte) ([#1983](https://github.com/nonebot/nonebot2/pull/1983))
- Docs: 修复事件处理函数文档代码范例中缺失的 import [@Lptr-byte](https://github.com/Lptr-byte) ([#1982](https://github.com/nonebot/nonebot2/pull/1982))
- Docs: 修复获取事件信息文档代码范例中缺失的 import [@Lptr-byte](https://github.com/Lptr-byte) ([#1980](https://github.com/nonebot/nonebot2/pull/1980))
- Docs: 新增插件跨平台指南 [@Well2333](https://github.com/Well2333) ([#1938](https://github.com/nonebot/nonebot2/pull/1938))
- Docs: 开启 blank issues [@yanyongyu](https://github.com/yanyongyu) ([#1945](https://github.com/nonebot/nonebot2/pull/1945))
- Docs: 使用 issue 表单替换 issue 模板 [@A-kirami](https://github.com/A-kirami) ([#1928](https://github.com/nonebot/nonebot2/pull/1928))
- Docs: 修正教程中部分 import 缺失的问题 [@Well2333](https://github.com/Well2333) ([#1927](https://github.com/nonebot/nonebot2/pull/1927))
- Docs: 添加 Walle-Q 到 Readme [@yanyongyu](https://github.com/yanyongyu) ([#1891](https://github.com/nonebot/nonebot2/pull/1891))
- Docs: 更新部署文档 [@yanyongyu](https://github.com/yanyongyu) ([#1890](https://github.com/nonebot/nonebot2/pull/1890))
### 💫 杂项
- Plugin: Hello World 添加 tag [@A-kirami](https://github.com/A-kirami) ([#2056](https://github.com/nonebot/nonebot2/pull/2056))
- Plugin: 修改 nonebot-plugin-logpile 的名称和描述 [@A-kirami](https://github.com/A-kirami) ([#2057](https://github.com/nonebot/nonebot2/pull/2057))
- Plugin: 移除 `nonebot_paddle_ocr``nonebot_poe_chat` [@canxin121](https://github.com/canxin121) ([#2039](https://github.com/nonebot/nonebot2/pull/2039))
- Plugin: 移除 `nonebot-plugin-rtfm` 插件 [@MingxuanGame](https://github.com/MingxuanGame) ([#2037](https://github.com/nonebot/nonebot2/pull/2037))
- Plugin: 移除 extrautils 工具拓展插件(暂停维护) [@NCBM](https://github.com/NCBM) ([#2033](https://github.com/nonebot/nonebot2/pull/2033))
- Adapter: 更新 Minecraft 适配器 [@17TheWord](https://github.com/17TheWord) ([#1972](https://github.com/nonebot/nonebot2/pull/1972))
- Docs: 更正 issue 表单部分内容 [@A-kirami](https://github.com/A-kirami) ([#1961](https://github.com/nonebot/nonebot2/pull/1961))
- Plugin: 更新 AutoReply 插件描述 [@lgc2333](https://github.com/lgc2333) ([#1949](https://github.com/nonebot/nonebot2/pull/1949))
- Plugin: 移除 `MC_QQ_MCRcon` [@17TheWord](https://github.com/17TheWord) ([#1948](https://github.com/nonebot/nonebot2/pull/1948))
- Plugin: 更新 lgc2333 插件仓库地址 [@lgc2333](https://github.com/lgc2333) ([#1935](https://github.com/nonebot/nonebot2/pull/1935))
- Plugin: 更新多功能哔哩哔哩解析工具 [@djkcyl](https://github.com/djkcyl) ([#1913](https://github.com/nonebot/nonebot2/pull/1913))
- CI: 跳过 PR 仓库为 fork 的情况 [@he0119](https://github.com/he0119) ([#1905](https://github.com/nonebot/nonebot2/pull/1905))
- Plugin: 移除旧版本的 GenshinUID [@KimigaiiWuyi](https://github.com/KimigaiiWuyi) ([#1904](https://github.com/nonebot/nonebot2/pull/1904))
- CI: 使用最新的 NoneFlow [@he0119](https://github.com/he0119) ([#1899](https://github.com/nonebot/nonebot2/pull/1899))
- CI: 使用 NoneFlow 管理工作流 [@yanyongyu](https://github.com/yanyongyu) ([#1892](https://github.com/nonebot/nonebot2/pull/1892))
- CI: 移除 poetry 版本限制 [@yanyongyu](https://github.com/yanyongyu) ([#1872](https://github.com/nonebot/nonebot2/pull/1872))
### 🍻 插件发布
- Plugin: stablediffusion 绘画插件 [@noneflow](https://github.com/noneflow) ([#2066](https://github.com/nonebot/nonebot2/pull/2066))
- Plugin: 随机抽取自定义内容 [@noneflow](https://github.com/noneflow) ([#2064](https://github.com/nonebot/nonebot2/pull/2064))
- Plugin: NAGA 公交车 [@noneflow](https://github.com/noneflow) ([#2062](https://github.com/nonebot/nonebot2/pull/2062))
- Plugin: 本子标题关键词提取 [@noneflow](https://github.com/noneflow) ([#2058](https://github.com/nonebot/nonebot2/pull/2058))
- Plugin: puzzle [@noneflow](https://github.com/noneflow) ([#2054](https://github.com/nonebot/nonebot2/pull/2054))
- Plugin: homo_mathematician [@noneflow](https://github.com/noneflow) ([#2052](https://github.com/nonebot/nonebot2/pull/2052))
- Plugin: cuber [@noneflow](https://github.com/noneflow) ([#2048](https://github.com/nonebot/nonebot2/pull/2048))
- Plugin: nonebot-plugin-lua [@noneflow](https://github.com/noneflow) ([#2049](https://github.com/nonebot/nonebot2/pull/2049))
- Plugin: Github 仓库卡片 [@noneflow](https://github.com/noneflow) ([#2042](https://github.com/nonebot/nonebot2/pull/2042))
- Plugin: 股票看盘助手 [@noneflow](https://github.com/noneflow) ([#2032](https://github.com/nonebot/nonebot2/pull/2032))
- Plugin: 便携插件安装器 [@noneflow](https://github.com/noneflow) ([#2027](https://github.com/nonebot/nonebot2/pull/2027))
- Plugin: 会话 id [@noneflow](https://github.com/noneflow) ([#2025](https://github.com/nonebot/nonebot2/pull/2025))
- Plugin: SD 绘画插件 [@noneflow](https://github.com/noneflow) ([#2023](https://github.com/nonebot/nonebot2/pull/2023))
- Plugin: 《女神异闻录 5》预告信生成器 [@noneflow](https://github.com/noneflow) ([#2021](https://github.com/nonebot/nonebot2/pull/2021))
- Plugin: 小小的 WEBAPI 调用插件 [@noneflow](https://github.com/noneflow) ([#2020](https://github.com/nonebot/nonebot2/pull/2020))
- Plugin: MultiNCM [@noneflow](https://github.com/noneflow) ([#2018](https://github.com/nonebot/nonebot2/pull/2018))
- Plugin: 签到 [@noneflow](https://github.com/noneflow) ([#2014](https://github.com/nonebot/nonebot2/pull/2014))
- Plugin: 链接解析 [@noneflow](https://github.com/noneflow) ([#2011](https://github.com/nonebot/nonebot2/pull/2011))
- Plugin: 信鸽巴夫 [@noneflow](https://github.com/noneflow) ([#2008](https://github.com/nonebot/nonebot2/pull/2008))
- Plugin: 明日方舟抽卡模拟 [@noneflow](https://github.com/noneflow) ([#2005](https://github.com/nonebot/nonebot2/pull/2005))
- Plugin: 雷神工业 [@noneflow](https://github.com/noneflow) ([#2003](https://github.com/nonebot/nonebot2/pull/2003))
- Plugin: nonebot-plugin-logpile [@noneflow](https://github.com/noneflow) ([#1999](https://github.com/nonebot/nonebot2/pull/1999))
- Plugin: Spark-GPT [@noneflow](https://github.com/noneflow) ([#1997](https://github.com/nonebot/nonebot2/pull/1997))
- Plugin: 企鹅物流统计数据查询 [@noneflow](https://github.com/noneflow) ([#1995](https://github.com/nonebot/nonebot2/pull/1995))
- Plugin: CallAPI [@noneflow](https://github.com/noneflow) ([#1990](https://github.com/nonebot/nonebot2/pull/1990))
- Plugin: 群聊人数锁定 [@noneflow](https://github.com/noneflow) ([#1988](https://github.com/nonebot/nonebot2/pull/1988))
- Plugin: CSGO 开箱模拟器 [@noneflow](https://github.com/noneflow) ([#1986](https://github.com/nonebot/nonebot2/pull/1986))
- Plugin: wordle_help [@noneflow](https://github.com/noneflow) ([#1974](https://github.com/nonebot/nonebot2/pull/1974))
- Plugin: 星穹铁道活动日历 [@noneflow](https://github.com/noneflow) ([#1970](https://github.com/nonebot/nonebot2/pull/1970))
- Plugin: 水印大师 [@noneflow](https://github.com/noneflow) ([#1965](https://github.com/nonebot/nonebot2/pull/1965))
- Plugin: 图片/漫画翻译 [@noneflow](https://github.com/noneflow) ([#1955](https://github.com/nonebot/nonebot2/pull/1955))
- Plugin: 为美好群聊献上爆炎 [@noneflow](https://github.com/noneflow) ([#1953](https://github.com/nonebot/nonebot2/pull/1953))
- Plugin: 公共画板插件 [@noneflow](https://github.com/noneflow) ([#1957](https://github.com/nonebot/nonebot2/pull/1957))
- Plugin: 运行代码 [@noneflow](https://github.com/noneflow) ([#1942](https://github.com/nonebot/nonebot2/pull/1942))
- Plugin: brainfuck [@noneflow](https://github.com/noneflow) ([#1944](https://github.com/nonebot/nonebot2/pull/1944))
- Plugin: Mixin [@noneflow](https://github.com/noneflow) ([#1947](https://github.com/nonebot/nonebot2/pull/1947))
- Plugin: AppInsights 日志监控 [@noneflow](https://github.com/noneflow) ([#1940](https://github.com/nonebot/nonebot2/pull/1940))
- Plugin: nonebot_poe_chat [@noneflow](https://github.com/noneflow) ([#1937](https://github.com/nonebot/nonebot2/pull/1937))
- Plugin: 更改 BOT 群名片 [@noneflow](https://github.com/noneflow) ([#1934](https://github.com/nonebot/nonebot2/pull/1934))
- Plugin: Akinator [@noneflow](https://github.com/noneflow) ([#1925](https://github.com/nonebot/nonebot2/pull/1925))
- Plugin: Bilifan [@noneflow](https://github.com/noneflow) ([#1921](https://github.com/nonebot/nonebot2/pull/1921))
- Plugin: osu!入群审批 [@noneflow](https://github.com/noneflow) ([#1919](https://github.com/nonebot/nonebot2/pull/1919))
- Plugin: 与 ChatGpt 聊天 [@noneflow](https://github.com/noneflow) ([#1917](https://github.com/nonebot/nonebot2/pull/1917))
- Plugin: TataruBot2 [@noneflow](https://github.com/noneflow) ([#1915](https://github.com/nonebot/nonebot2/pull/1915))
- Plugin: 宝可梦融合 [@noneflow](https://github.com/noneflow) ([#1912](https://github.com/nonebot/nonebot2/pull/1912))
- Plugin: FuckYou [@noneflow](https://github.com/noneflow) ([#1910](https://github.com/nonebot/nonebot2/pull/1910))
- Plugin: SDGPT [@noneflow](https://github.com/noneflow) ([#1908](https://github.com/nonebot/nonebot2/pull/1908))
- Plugin: nonebot clock 群闹钟 ⏰ [@noneflow](https://github.com/noneflow) ([#1906](https://github.com/nonebot/nonebot2/pull/1906))
- Plugin: B 站直播间路灯 [@noneflow](https://github.com/noneflow) ([#1901](https://github.com/nonebot/nonebot2/pull/1901))
- Plugin: GenshinUID [@noneflow](https://github.com/noneflow) ([#1903](https://github.com/nonebot/nonebot2/pull/1903))
- Plugin: 多功能哔哩哔哩解析工具 [@noneflow](https://github.com/noneflow) ([#1898](https://github.com/nonebot/nonebot2/pull/1898))
- Plugin: Steam 游戏状态播报 [@yanyongyu](https://github.com/yanyongyu) ([#1887](https://github.com/nonebot/nonebot2/pull/1887))
- Plugin: AI 生成 PPT [@yanyongyu](https://github.com/yanyongyu) ([#1884](https://github.com/nonebot/nonebot2/pull/1884))
- Plugin: nonebot_paddle_ocr [@yanyongyu](https://github.com/yanyongyu) ([#1882](https://github.com/nonebot/nonebot2/pull/1882))
- Plugin: nonebot_api_paddle [@yanyongyu](https://github.com/yanyongyu) ([#1880](https://github.com/nonebot/nonebot2/pull/1880))
- Plugin: 来份睡眠套餐 [@yanyongyu](https://github.com/yanyongyu) ([#1876](https://github.com/nonebot/nonebot2/pull/1876))
- Plugin: 今日老婆 [@yanyongyu](https://github.com/yanyongyu) ([#1874](https://github.com/nonebot/nonebot2/pull/1874))
- Plugin: 激战 2 [@yanyongyu](https://github.com/yanyongyu) ([#1871](https://github.com/nonebot/nonebot2/pull/1871))
- Plugin: ROLL [@yanyongyu](https://github.com/yanyongyu) ([#1868](https://github.com/nonebot/nonebot2/pull/1868))
### 🍻 机器人发布
- Bot: 狐尾 [@noneflow](https://github.com/noneflow) ([#2009](https://github.com/nonebot/nonebot2/pull/2009))
- Bot: ay 机器人 [@noneflow](https://github.com/noneflow) ([#1993](https://github.com/nonebot/nonebot2/pull/1993))
- Bot: March7th [@noneflow](https://github.com/noneflow) ([#1978](https://github.com/nonebot/nonebot2/pull/1978))
- Bot: XDbot2 [@noneflow](https://github.com/noneflow) ([#1932](https://github.com/nonebot/nonebot2/pull/1932))
- Bot: CoolQBot [@noneflow](https://github.com/noneflow) ([#1894](https://github.com/nonebot/nonebot2/pull/1894))
### 🍻 适配器发布
- Adapter: Walle-Q [@yanyongyu](https://github.com/yanyongyu) ([#1889](https://github.com/nonebot/nonebot2/pull/1889))
## v2.0.0rc4 ## v2.0.0rc4
### 🚀 新功能 ### 🚀 新功能

View File

@@ -19,31 +19,96 @@ function FooterCopyright() {
Deployed by Deployed by
<Link to="https://www.netlify.com" className="ml-1 opacity-100"> <Link to="https://www.netlify.com" className="ml-1 opacity-100">
<svg <svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 147 40"
height="1rem" height="1rem"
viewBox="0 0 256 105"
fill="none"
xmlns="http://www.w3.org/2000/svg"
> >
<radialGradient <g clip-path="url(#clip0_236_25)">
id="netlify-logo"
cy="0%"
r="100.11%"
gradientTransform="matrix(0 .9989 -1.152 0 .5 -.5)"
>
<stop offset="0" stop-color="#20c6b7" />
<stop offset="1" stop-color="#4d9abf" />
</radialGradient>
<g fill="none" fill-rule="evenodd">
<path <path
fill="currentcolor" d="M58.4704 103.765V77.4144L59.0165 76.8683H65.6043L66.1504 77.4144V103.765L65.6043 104.311H59.0165L58.4704 103.765Z"
d="m53.37 12.978.123 2.198c1.403-1.7 3.245-2.55 5.525-2.55 3.951 0 5.962 2.268 6.032 6.804v12.568h-4.26v-12.322c0-1.207-.26-2.1-.78-2.681-.52-.58-1.371-.87-2.552-.87-1.719 0-3 .78-3.84 2.338v13.535h-4.262v-19.02h4.016zm24.378 19.372c-2.7 0-4.89-.852-6.567-2.557-1.678-1.705-2.517-3.976-2.517-6.812v-.527c0-1.898.365-3.595 1.096-5.089.73-1.494 1.757-2.657 3.078-3.49 1.321-.831 2.794-1.247 4.42-1.247 2.583 0 4.58.826 5.988 2.478 1.41 1.653 2.114 3.99 2.114 7.014v1.723h-12.4c.13 1.57.652 2.812 1.57 3.726s2.073 1.371 3.464 1.371c1.952 0 3.542-.79 4.77-2.373l2.297 2.198c-.76 1.136-1.774 2.018-3.042 2.645-1.269.627-2.692.94-4.27.94zm-.508-16.294c-1.17 0-2.113.41-2.832 1.23-.72.82-1.178 1.963-1.377 3.428h8.12v-.317c-.094-1.43-.474-2.51-1.14-3.243-.667-.732-1.59-1.098-2.771-1.098zm16.765-7.7v4.623h3.35v3.164h-3.35v10.617c0 .726.144 1.25.43 1.573.286.322.798.483 1.535.483a6.55 6.55 0 0 0 1.49-.176v3.305c-.97.27-1.905.404-2.806.404-3.273 0-4.91-1.81-4.91-5.431v-10.776h-3.124v-3.164h3.122v-4.623h4.261zm11.137 23.643h-4.262v-27h4.262zm9.172 0h-4.262v-19.02h4.262zm-4.525-23.96c0-.655.207-1.2.622-1.634.416-.433 1.009-.65 1.78-.65.772 0 1.368.217 1.79.65.42.434.63.979.63 1.635 0 .644-.21 1.18-.63 1.608-.422.428-1.018.642-1.79.642-.771 0-1.364-.214-1.78-.642-.415-.427-.622-.964-.622-1.608zm10.663 23.96v-15.857h-2.894v-3.164h2.894v-1.74c0-2.11.584-3.738 1.753-4.887 1.17-1.148 2.806-1.722 4.91-1.722.749 0 1.544.105 2.386.316l-.105 3.34a8.375 8.375 0 0 0 -1.631-.14c-2.035 0-3.052 1.048-3.052 3.146v1.687h3.858v3.164h-3.858v15.856h-4.261zm17.87-6.117 3.858-12.903h4.542l-7.54 21.903c-1.158 3.199-3.122 4.799-5.893 4.799-.62 0-1.304-.106-2.052-.317v-3.305l.807.053c1.075 0 1.885-.196 2.429-.589.543-.392.973-1.051 1.289-1.977l.613-1.635-6.664-18.932h4.595z" fill="#05BDBA"
/> />
<path <path
fill="url(#netlify-logo)" d="M58.4704 26.8971V0.546133L59.0165 0H65.6043L66.1504 0.546133V26.8971L65.6043 27.4432H59.0165L58.4704 26.8971Z"
fill-rule="nonzero" fill="#05BDBA"
d="m28.589 14.135-.014-.006c-.008-.003-.016-.006-.023-.013a.11.11 0 0 1 -.028-.093l.773-4.726 3.625 3.626-3.77 1.604a.083.083 0 0 1 -.033.006h-.015c-.005-.003-.01-.007-.02-.017a1.716 1.716 0 0 0 -.495-.381zm5.258-.288 3.876 3.876c.805.806 1.208 1.208 1.355 1.674.022.069.04.138.054.209l-9.263-3.923a.728.728 0 0 0 -.015-.006c-.037-.015-.08-.032-.08-.07s.044-.056.081-.071l.012-.005zm5.127 7.003c-.2.376-.59.766-1.25 1.427l-4.37 4.369-5.652-1.177-.03-.006c-.05-.008-.103-.017-.103-.062a1.706 1.706 0 0 0 -.655-1.193c-.023-.023-.017-.059-.01-.092 0-.005 0-.01.002-.014l1.063-6.526.004-.022c.006-.05.015-.108.06-.108a1.73 1.73 0 0 0 1.16-.665c.009-.01.015-.021.027-.027.032-.015.07 0 .103.014l9.65 4.082zm-6.625 6.801-7.186 7.186 1.23-7.56.002-.01c.001-.01.003-.02.006-.029.01-.024.036-.034.061-.044l.012-.005a1.85 1.85 0 0 0 .695-.517c.024-.028.053-.055.09-.06a.09.09 0 0 1 .029 0l5.06 1.04zm-8.707 8.707-.81.81-8.955-12.942a.424.424 0 0 0 -.01-.014c-.014-.019-.029-.038-.026-.06 0-.016.011-.03.022-.042l.01-.013c.027-.04.05-.08.075-.123l.02-.035.003-.003c.014-.024.027-.047.051-.06.021-.01.05-.006.073-.001l9.921 2.046a.164.164 0 0 1 .076.033c.013.013.016.027.019.043a1.757 1.757 0 0 0 1.028 1.175c.028.014.016.045.003.078a.238.238 0 0 0 -.015.045c-.125.76-1.197 7.298-1.485 9.063zm-1.692 1.691c-.597.591-.949.904-1.347 1.03a2 2 0 0 1 -1.206 0c-.466-.148-.869-.55-1.674-1.356l-8.993-8.993 2.349-3.643c.011-.018.022-.034.04-.047.025-.018.061-.01.091 0a2.434 2.434 0 0 0 1.638-.083c.027-.01.054-.017.075.002a.19.19 0 0 1 .028.032l8.999 13.059zm-14.087-10.186-2.063-2.063 4.074-1.738a.084.084 0 0 1 .033-.007c.034 0 .054.034.072.065a2.91 2.91 0 0 0 .13.184l.013.016c.012.017.004.034-.008.05l-2.25 3.493zm-2.976-2.976-2.61-2.61c-.444-.444-.766-.766-.99-1.043l7.936 1.646a.84.84 0 0 0 .03.005c.049.008.103.017.103.063 0 .05-.059.073-.109.092l-.023.01zm-4.056-4.995a2 2 0 0 1 .09-.495c.148-.466.55-.868 1.356-1.674l3.34-3.34a2175.525 2175.525 0 0 0 4.626 6.687c.027.036.057.076.026.106-.146.161-.292.337-.395.528a.16.16 0 0 1 -.05.062c-.013.008-.027.005-.042.002h-.002l-8.949-1.877zm5.68-6.403 4.489-4.491c.423.185 1.96.834 3.333 1.414 1.04.44 1.988.84 2.286.97.03.012.057.024.07.054.008.018.004.041 0 .06a2.003 2.003 0 0 0 .523 1.828c.03.03 0 .073-.026.11l-.014.021-4.56 7.063c-.012.02-.023.037-.043.05-.024.015-.058.008-.086.001a2.274 2.274 0 0 0 -.543-.074c-.164 0-.342.03-.522.063h-.001c-.02.003-.038.007-.054-.005a.21.21 0 0 1 -.045-.051l-4.808-7.013zm5.398-5.398 5.814-5.814c.805-.805 1.208-1.208 1.674-1.355a2 2 0 0 1 1.206 0c.466.147.869.55 1.674 1.355l1.26 1.26-4.135 6.404a.155.155 0 0 1 -.041.048c-.025.017-.06.01-.09 0a2.097 2.097 0 0 0 -1.92.37c-.027.028-.067.012-.101-.003-.54-.235-4.74-2.01-5.341-2.265zm12.506-3.676 3.818 3.818-.92 5.698v.015a.135.135 0 0 1 -.008.038c-.01.02-.03.024-.05.03a1.83 1.83 0 0 0 -.548.273.154.154 0 0 0 -.02.017c-.011.012-.022.023-.04.025a.114.114 0 0 1 -.043-.007l-5.818-2.472-.011-.005c-.037-.015-.081-.033-.081-.071a2.198 2.198 0 0 0 -.31-.915c-.028-.046-.059-.094-.035-.141zm-3.932 8.606 5.454 2.31c.03.014.063.027.076.058a.106.106 0 0 1 0 .057c-.016.08-.03.171-.03.263v.153c0 .038-.039.054-.075.069l-.011.004c-.864.369-12.13 5.173-12.147 5.173s-.035 0-.052-.017c-.03-.03 0-.072.027-.11a.76.76 0 0 0 .014-.02l4.482-6.94.008-.012c.026-.042.056-.089.104-.089l.045.007c.102.014.192.027.283.027.68 0 1.31-.331 1.69-.897a.16.16 0 0 1 .034-.04c.027-.02.067-.01.098.004zm-6.246 9.185 12.28-5.237s.018 0 .035.017c.067.067.124.112.179.154l.027.017c.025.014.05.03.052.056 0 .01 0 .016-.002.025l-1.052 6.462-.004.026c-.007.05-.014.107-.061.107a1.729 1.729 0 0 0 -1.373.847l-.005.008c-.014.023-.027.045-.05.057-.021.01-.048.006-.07.001l-9.793-2.02c-.01-.002-.152-.519-.163-.52z" />
transform="translate(-.702)" <path
d="M35.7973 85.2395H34.8928L30.3616 80.7083V79.8037L38.8523 71.3045L43.648 71.3131L44.288 71.9445V76.7403L35.7973 85.2395Z"
fill="#05BDBA"
/>
<path
d="M30.3616 24.7467V23.8336L34.8928 19.3109H35.7973L44.288 27.8016V32.5888L43.648 33.2373H38.8523L30.3616 24.7467Z"
fill="#05BDBA"
/>
<path
d="M0.546133 48.3072H37.8795L38.4256 48.8533V55.4496L37.8795 55.9958H0.546133L0 55.4496V48.8533L0.546133 48.3072Z"
fill="#05BDBA"
/>
<path
d="M255.445 48.3157L255.991 48.8619V55.4496L255.445 55.9957H217.566L217.02 55.4496L219.759 48.8619L220.305 48.3157H255.445Z"
fill="#05BDBA"
/>
<path
d="M74.6667 65.8859H68.0789L67.5328 65.3397V49.92C67.5328 47.1723 66.4576 45.0475 63.1467 44.9792C61.44 44.9365 59.4944 44.9792 57.4123 45.0645L57.0965 45.3803V65.3312L56.5504 65.8773H49.9627L49.4165 65.3312V38.9803L49.9627 38.4341H64.7851C70.5451 38.4341 75.2128 43.1019 75.2128 48.8619V65.3312L74.6667 65.8773V65.8859Z"
fill="#014847"
/>
<path
d="M106.573 54.3488L106.027 54.8949H88.9941L88.448 55.4411C88.448 56.5419 89.5488 59.8357 93.9435 59.8357C95.5904 59.8357 97.2373 59.2896 97.792 58.1888L98.3381 57.6427H104.926L105.472 58.1888C104.926 61.4827 102.178 66.432 93.9349 66.432C84.5995 66.432 80.2048 59.8443 80.2048 52.1472C80.2048 44.4501 84.5995 37.8624 93.3888 37.8624C102.178 37.8624 106.573 44.4501 106.573 52.1472V54.3488ZM98.3296 48.8533C98.3296 48.3072 97.7835 44.4587 93.3888 44.4587C88.9941 44.4587 88.448 48.3072 88.448 48.8533L88.9941 49.3995H97.7835L98.3296 48.8533Z"
fill="#014847"
/>
<path
d="M121.95 57.6427C121.95 58.7435 122.496 59.2896 123.597 59.2896H128.538L129.084 59.8358V65.3312L128.538 65.8773H123.597C118.656 65.8773 114.261 63.6758 114.261 57.6342V45.5509L113.715 45.0048H109.867L109.321 44.4587V38.9632L109.867 38.4171H113.715L114.261 37.8709V32.9301L114.807 32.384H121.395L121.941 32.9301V37.8709L122.487 38.4171H128.529L129.075 38.9632V44.4587L128.529 45.0048H122.487L121.941 45.5509V57.6342L121.95 57.6427Z"
fill="#014847"
/>
<path
d="M142.276 65.8859H135.689L135.142 65.3397V27.9808L135.689 27.4347H142.276L142.822 27.9808V65.3312L142.276 65.8773V65.8859Z"
fill="#014847"
/>
<path
d="M157.107 34.0224H150.519L149.973 33.4763V27.9808L150.519 27.4347H157.107L157.653 27.9808V33.4763L157.107 34.0224ZM157.107 65.8859H150.519L149.973 65.3397V38.9717L150.519 38.4256H157.107L157.653 38.9717V65.3397L157.107 65.8859Z"
fill="#014847"
/>
<path
d="M182.929 27.9808V33.4763L182.383 34.0224H177.442C176.341 34.0224 175.795 34.5685 175.795 35.6693V37.8709L176.341 38.4171H181.837L182.383 38.9632V44.4587L181.837 45.0048H176.341L175.795 45.5509V65.3227L175.249 65.8688H168.661L168.115 65.3227V45.5509L167.569 45.0048H163.721L163.174 44.4587V38.9632L163.721 38.4171H167.569L168.115 37.8709V35.6693C168.115 29.6277 172.51 27.4261 177.451 27.4261H182.391L182.938 27.9723L182.929 27.9808Z"
fill="#014847"
/>
<path
d="M203.247 66.432C201.045 71.9275 198.852 75.2213 191.164 75.2213H188.416L187.87 74.6752V69.1797L188.416 68.6336H191.164C193.911 68.6336 194.458 68.0875 195.012 66.4405V65.8944L186.223 44.4672V38.9717L186.769 38.4256H191.71L192.256 38.9717L198.844 57.6512H199.39L205.978 38.9717L206.524 38.4256H211.465L212.011 38.9717V44.4672L203.221 66.4405L203.247 66.432Z"
fill="#014847"
/> />
</g> </g>
<defs>
<clipPath id="clip0_236_25">
<rect width="256" height="104.311" fill="white" />
</clipPath>
</defs>
</svg>
</Link>
<Link to="https://www.cloudflare.com/" className="ml-1 opacity-100">
<svg
height="1rem"
viewBox="0 0 651.29 94.76"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill="#f78100"
d="M143.05,93.42l1.07-3.71c1.27-4.41.8-8.48-1.34-11.48-2-2.76-5.26-4.38-9.25-4.57L58,72.7a1.47,1.47,0,0,1-1.35-2,2,2,0,0,1,1.75-1.34l76.26-1c9-.41,18.84-7.75,22.27-16.71l4.34-11.36a2.68,2.68,0,0,0,.18-1,3.31,3.31,0,0,0-.06-.54,49.67,49.67,0,0,0-95.49-5.14,22.35,22.35,0,0,0-35,23.42A31.73,31.73,0,0,0,.34,93.45a1.47,1.47,0,0,0,1.45,1.27l139.49,0h0A1.83,1.83,0,0,0,143.05,93.42Z"
/>
<path
fill="#fcad32"
d="M168.22,41.15q-1,0-2.1.06a.88.88,0,0,0-.32.07,1.17,1.17,0,0,0-.76.8l-3,10.26c-1.28,4.41-.81,8.48,1.34,11.48a11.65,11.65,0,0,0,9.24,4.57l16.11,1a1.44,1.44,0,0,1,1.14.62,1.5,1.5,0,0,1,.17,1.37,2,2,0,0,1-1.75,1.34l-16.73,1c-9.09.42-18.88,7.75-22.31,16.7l-1.21,3.16a.9.9,0,0,0,.79,1.22h57.63A1.55,1.55,0,0,0,208,93.63a41.34,41.34,0,0,0-39.76-52.48Z"
/>
<polygon points="273.03 59.66 282.56 59.66 282.56 85.72 299.23 85.72 299.23 94.07 273.03 94.07 273.03 59.66" />
<path d="M309.11,77v-.09c0-9.88,8-17.9,18.58-17.9s18.48,7.92,18.48,17.8v.1c0,9.88-8,17.89-18.58,17.89S309.11,86.85,309.11,77m27.33,0v-.09c0-5-3.59-9.29-8.85-9.29s-8.7,4.22-8.7,9.19v.1c0,5,3.59,9.29,8.8,9.29s8.75-4.23,8.75-9.2" />
<path d="M357.84,79V59.66h9.69V78.78c0,5,2.5,7.33,6.34,7.33s6.34-2.26,6.34-7.08V59.66h9.68V78.73c0,11.11-6.34,16-16.12,16s-15.93-5-15.93-15.73" />
<path d="M404.49,59.66h13.27c12.29,0,19.42,7.08,19.42,17v.1c0,9.93-7.23,17.3-19.61,17.3H404.49Zm13.42,26c5.7,0,9.49-3.15,9.49-8.71v-.09c0-5.51-3.79-8.71-9.49-8.71H414V85.62Z" />
<polygon points="451.04 59.66 478.56 59.66 478.56 68.02 460.58 68.02 460.58 73.87 476.85 73.87 476.85 81.78 460.58 81.78 460.58 94.07 451.04 94.07 451.04 59.66" />
<polygon points="491.84 59.66 501.37 59.66 501.37 85.72 518.04 85.72 518.04 94.07 491.84 94.07 491.84 59.66" />
<path d="M543,59.42h9.19L566.8,94.07H556.58l-2.51-6.14H540.79l-2.45,6.14h-10Zm8.35,21.08-3.83-9.78L543.6,80.5Z" />
<path d="M579.08,59.66h16.27c5.27,0,8.9,1.38,11.21,3.74a10.64,10.64,0,0,1,3.05,8v.1a10.88,10.88,0,0,1-7.08,10.57l8.21,12h-11L592.8,83.65h-4.18V94.07h-9.54Zm15.83,16.52c3.25,0,5.12-1.58,5.12-4.08V72c0-2.71-2-4.08-5.17-4.08h-6.24v8.26Z" />
<polygon points="623.37 59.66 651.05 59.66 651.05 67.77 632.81 67.77 632.81 72.98 649.33 72.98 649.33 80.5 632.81 80.5 632.81 85.96 651.29 85.96 651.29 94.07 623.37 94.07 623.37 59.66" />
<path d="M252.15,81a8.44,8.44,0,0,1-7.88,5.16c-5.22,0-8.8-4.33-8.8-9.29v-.1c0-5,3.49-9.2,8.7-9.2a8.64,8.64,0,0,1,8.18,5.71h10C260.79,65.09,253.6,59,244.27,59c-10.62,0-18.58,8-18.58,17.9V77c0,9.88,7.86,17.8,18.48,17.8,9.08,0,16.18-5.88,18.05-13.76Z" />
</svg> </svg>
</Link> </Link>
</span> </span>

View File

@@ -115,12 +115,12 @@
"is_official": false "is_official": false
}, },
{ {
"module_name": "nonebot.adapters.spigot", "module_name": "nonebot.adapters.minecraft",
"project_link": "nonebot-adapter-spigot", "project_link": "nonebot-adapter-minecraft",
"name": "Spigot", "name": "Minecraft",
"desc": "MineCraft通信适配", "desc": "MineCraft通信适配支持Rcon",
"author": "17TheWord", "author": "17TheWord",
"homepage": "https://github.com/17TheWord/nonebot-adapter-spigot", "homepage": "https://github.com/17TheWord/nonebot-adapter-minecraft",
"tags": [ "tags": [
{ {
"label": "Minecraft", "label": "Minecraft",
@@ -138,5 +138,20 @@
"homepage": "https://github.com/wwweww/adapter-bilibili", "homepage": "https://github.com/wwweww/adapter-bilibili",
"tags": [], "tags": [],
"is_official": false "is_official": false
},
{
"module_name": "nonebot_adapter_walleq",
"project_link": "nonebot-adapter-walleq",
"name": "Walle-Q",
"desc": "内置 QQ 协议实现",
"author": "abrahum",
"homepage": "https://github.com/onebot-walle/nonebot_adapter_walleq",
"tags": [
{
"label": "QQ",
"color": "#34a9cc"
}
],
"is_official": false
} }
] ]

View File

@@ -454,5 +454,81 @@
} }
], ],
"is_official": false "is_official": false
},
{
"name": "CoolQBot",
"desc": "基于 NoneBot2 的聊天机器人",
"author": "he0119",
"homepage": "https://github.com/he0119/CoolQBot",
"tags": [],
"is_official": false
},
{
"name": "XDbot2",
"desc": "简单的QQ功能型机器人",
"author": "This-is-XiaoDeng",
"homepage": "https://github.com/ITCraftDevelopmentTeam/XDbot2",
"tags": [
{
"label": "a:onebot",
"color": "#ea5252"
}
],
"is_official": false
},
{
"name": "March7th",
"desc": "三月七 - 崩坏:星穹铁道机器人",
"author": "mobyw",
"homepage": "https://github.com/Mar-7th/March7th",
"tags": [
{
"label": "StarRail",
"color": "#5a8ccc"
},
{
"label": "星穹铁道",
"color": "#6faec6"
}
],
"is_official": false
},
{
"name": "ay机器人",
"desc": "codeforces和洛谷卷王监视、股票监控、ai聊天",
"author": "863109569",
"homepage": "https://github.com/863109569/qqbot",
"tags": [
{
"label": "acm",
"color": "#ea5252"
},
{
"label": "洛谷",
"color": "#81ea52"
},
{
"label": "codeforces",
"color": "#5261ea"
}
],
"is_official": false
},
{
"name": "狐尾",
"desc": "一个整合了兽云祭api的机器人支持账号令牌操作以及上传兽图",
"author": "bingqiu456",
"homepage": "https://github.com/bingqiu456/shouyun",
"tags": [
{
"label": "a:onebot",
"color": "#ea5252"
},
{
"label": "t:shouyun",
"color": "#52ea7a"
}
],
"is_official": false
} }
] ]

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,49 @@
---
sidebar_position: 0
id: index
slug: /
---
# 概览
NoneBot2 是一个现代、跨平台、可扩展的 Python 聊天机器人框架(下称 NoneBot它基于 Python 的类型注解和异步优先特性兼容同步能够为你的需求实现提供便捷灵活的支持。同时NoneBot 拥有大量的开发者为其开发插件,用户无需编写任何代码,仅需完成环境配置及插件安装,就可以正常使用 NoneBot。
需要注意的是NoneBot 仅支持 **Python 3.8 以上版本**
## 特色
### 异步优先
NoneBot 基于 Python [asyncio](https://docs.python.org/zh-cn/3/library/asyncio.html) 编写,并在异步机制的基础上进行了一定程度的同步函数兼容。
### 完整的类型注解
NoneBot 参考 [PEP 484](https://www.python.org/dev/peps/pep-0484/) 等 PEP 完整实现了类型注解,通过 PyrightPylance 检查。配合编辑器的类型推导功能,能将绝大多数的 Bug 杜绝在编辑器中([编辑器支持](./editor-support))。
### 开箱即用
NoneBot 提供了使用便捷、具有交互式功能的命令行工具--`nb-cli`,使得用户初次接触 NoneBot 时更容易上手。使用方法请阅读本文档[指南](./quick-start.mdx)以及 [CLI 文档](https://cli.nonebot.dev/)。
### 插件系统
插件系统是 NoneBot 的核心,通过它可以实现机器人的模块化以及功能扩展,便于维护和管理。
### 依赖注入系统
NoneBot 采用了一套自行定义的依赖注入系统,可以让事件的处理过程更加的简洁、清晰,增加代码的可读性,减少代码冗余。
#### 什么是依赖注入
[**『依赖注入』**](https://zh.m.wikipedia.org/wiki/%E6%8E%A7%E5%88%B6%E5%8F%8D%E8%BD%AC)意思是,在编程中,有一种方法可以让你的代码声明它工作和使用所需要的东西,即**『依赖』**。
系统(在这里是指 NoneBot将负责做任何需要的事情为你的代码提供这些必要依赖即**『注入』**依赖性)
这在你有以下情形的需求时非常有用:
- 这部分代码拥有共享的逻辑(同样的代码逻辑多次重复)
- 共享数据库以及网络请求连接会话
- 比如 `httpx.AsyncClient``aiohttp.ClientSession``sqlalchemy.Session`
- 机器人用户权限检查以及认证
- 还有更多...
它在完成上述工作的同时,还能尽量减少代码的耦合和重复

View File

@@ -0,0 +1,161 @@
---
sidebar_position: 1
description: 注册适配器与指定平台交互
options:
menu:
weight: 20
category: advanced
---
# 使用适配器
适配器 (Adapter) 是机器人与平台交互的核心桥梁,它负责在驱动器和机器人插件之间转换与传递消息。
## 适配器功能与组成
适配器通常有两种功能,分别是**接收事件**和**调用平台接口**。其中,接收事件是指将驱动器收到的事件消息转换为 NoneBot 定义的事件模型,然后交由机器人插件处理;调用平台接口是指将机器人插件调用平台接口的数据转换为平台指定的格式,然后交由驱动器发送,并接收接口返回数据。
为了实现这两种功能,适配器通常由四个部分组成:
- **Adapter**:负责转换事件和调用接口,正确创建 Bot 对象并注册到 NoneBot 中。
- **Bot**:负责存储平台机器人相关信息,并提供回复事件的方法。
- **Event**:负责定义事件内容,以及事件主体对象。
- **Message**:负责正确序列化消息,以便机器人插件处理。
## 注册适配器
在使用适配器之前,我们需要先将适配器注册到驱动器中,这样适配器就可以通过驱动器接收事件和调用接口了。我们以 Console 适配器为例,来看看如何注册适配器:
```python {2,5} title=bot.py
import nonebot
from nonebot.adapters.console import Adapter
driver = nonebot.get_driver()
driver.register_adapter(Adapter)
```
我们首先需要从适配器模块中导入所需要的适配器类,然后通过驱动器的 `register_adapter` 方法将适配器注册到驱动器中即可。如果我们需要多平台支持,可以多次调用 `register_adapter` 方法来注册多个适配器。
## 获取已注册的适配器
NoneBot 提供了 `get_adapter` 方法来获取已注册的适配器,我们可以通过适配器的名称或类型来获取指定的适配器实例:
```python
import nonebot
from nonebot.adapters.console import Adapter
adapters = nonebot.get_adapters()
console_adapter = nonebot.get_adapter(Adapter)
console_adapter = nonebot.get_adapter(Adapter.get_name())
```
## 获取 Bot 对象
当前所有适配器已连接的 Bot 对象可以通过 `get_bots` 方法获取,这是一个以机器人 ID 为键的字典:
```python
import nonebot
bots = nonebot.get_bots()
```
我们也可以通过 `get_bot` 方法获取指定 ID 的 Bot 对象。如果省略 ID 参数,将会返回所有 Bot 中的第一个:
```python
import nonebot
bot = nonebot.get_bot("bot_id")
```
如果需要获取指定适配器连接的 Bot 对象,我们可以通过适配器的 `bots` 属性获取,这也是一个以机器人 ID 为键的字典:
```python
import nonebot
from nonebot.adapters.console import Adapter
console_adapter = nonebot.get_adapter(Adapter)
bots = console_adapter.bots
```
Bot 对象都具有一个 `self_id` 属性,它是机器人的唯一 ID由适配器填写通常为机器人的帐号 ID 或者 APP ID。
## 获取事件通用信息
适配器的所有事件模型均继承自 `Event` 基类,在[事件类型与重载](../appendices/overload.md)一节中,我们也提到了如何使用基类抽象方法来获取事件通用信息。基类能提供如下信息:
### 事件类型
事件类型通常为 `meta_event`、`message`、`notice`、`request`。
```python
type: str = event.get_type()
```
### 事件名称
事件名称由适配器定义,通常用于日志记录。
```python
name: str = event.get_event_name()
```
### 事件描述
事件描述由适配器定义,通常用于日志记录。
```python
description: str = event.get_event_description()
```
### 事件日志字符串
事件日志字符串由事件名称和事件描述组成,用于日志记录。
```python
log: str = event.get_log_string()
```
### 事件主体 ID
事件主体 ID 通常为机器人用户 ID。
```python
user_id: str = event.get_user_id()
```
### 事件会话 ID
事件会话 ID 通常为机器人用户 ID 与群聊/频道 ID 组合而成。
```python
session_id: str = event.get_session_id()
```
### 事件消息
如果事件包含消息,则可以通过该方法获取,否则会产生异常。
```python
message: Message = event.get_message()
```
### 事件纯文本消息
通常为事件消息的纯文本内容,如果事件不包含消息,则会产生异常。
```python
text: str = event.get_plaintext()
```
### 事件是否与机器人有关
由适配器实现的判断,通常将事件目标主体为机器人、消息中包含“@机器人”或以“机器人的昵称”开始视为与机器人有关。
```python
is_tome: bool = event.is_tome()
```
## 更多
官方支持的适配器和社区贡献的适配器均可在[商店](/store)中查看。如果你想要开发自己的适配器,可以参考[开发文档](../developer/adapter-writing.md)。欢迎通过商店发布你的适配器。

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,286 @@
---
sidebar_position: 0
description: 选择合适的驱动器运行机器人
options:
menu:
weight: 10
category: advanced
---
# 选择驱动器
驱动器 (Driver) 是机器人运行的基石,它是机器人初始化的第一步,主要负责数据收发。
:::important 提示
驱动器的选择通常与机器人所使用的协议适配器相关,如果不知道该选择哪个驱动器,可以先阅读相关协议适配器文档说明。
:::
:::tip 提示
如何**安装**驱动器请参考[安装驱动器](../tutorial/store.mdx#安装驱动器)。
:::
## 驱动器类型
驱动器的类型有两种:
- `ForwardDriver`:即客户端型驱动器,多用于使用 HTTP 轮询,连接 WebSocket 服务器等情形。
- `ReverseDriver`:即服务端型驱动器,多用于使用 WebHook接收 WebSocket 客户端连接等情形。
客户端型驱动器具有以下两种功能:
1. 异步发送 HTTP 请求,自定义 `HTTP Method``URL``Header``Body``Cookie``Proxy``Timeout` 等。
2. 异步建立 WebSocket 连接上下文,自定义 `WebSocket URL``Header``Cookie``Proxy``Timeout` 等。
服务端型驱动器通常为 ASGI 应用框架,具有以下功能:
1. 协议适配器自定义 HTTP 上报地址以及对上报数据处理的回调函数。
2. 协议适配器自定义 WebSocket 连接请求地址以及对 WebSocket 请求处理的回调函数。
3. 用户可以向 ASGI 应用添加任何服务端相关功能,如:[添加自定义路由](./routing.md)。
## 配置驱动器
驱动器的配置方法已经在[配置](../appendices/config.mdx)章节中简单进行了介绍,这里将详细介绍驱动器配置的格式。
NoneBot 中的客户端和服务端型驱动器可以相互配合使用,但服务端型驱动器**仅能选择一个**。所有驱动器模块都会包含一个 `Driver` 子类,即驱动器类,他可以作为驱动器单独运行。同时,客户端驱动器模块中还会提供一个 `Mixin` 子类,用于在与其他驱动器配合使用时加载。因此,驱动器配置格式采用特殊语法:`<module>[:<Driver>][+<module>[:<Mixin>]]*`
其中,`<module>` 代表**驱动器模块路径**`<Driver>` 代表**驱动器类名**,默认为 `Driver``<Mixin>` 代表**驱动器混入类名**,默认为 `Mixin`。即,我们需要选择一个主要驱动器,然后在其基础上配合使用其他驱动器的功能。主要驱动器可以为客户端或服务端类型,但混入类驱动器只能为客户端类型。
特别的,为了简化内置驱动器模块路径,我们可以使用 `~` 符号作为内置驱动器模块路径的前缀,如 `~fastapi` 代表使用内置驱动器 `fastapi`。NoneBot 内置了多个驱动器适配,但需要安装额外依赖才能使用,具体请参考[安装驱动器](../tutorial/store.mdx#安装驱动器)。常见的驱动器配置如下:
```dotenv
DRIVER=~fastapi
DRIVER=~aiohttp
DRIVER=~httpx+~websockets
DRIVER=~fastapi+~httpx+~websockets
```
## 获取驱动器
在 NoneBot 框架初始化完成后,我们就可以通过 `get_driver()` 方法获取全局驱动器实例:
```python
from nonebot import get_driver
driver = get_driver()
```
## 内置驱动器
### None
**类型:**服务端驱动器
NoneBot 内置的空驱动器,不提供任何收发数据功能,可以在不需要外部网络连接时使用。
```env
DRIVER=~none
```
### FastAPI默认
**类型:**服务端驱动器
> FastAPI is a modern, fast (high-performance), web framework for building APIs with Python 3.6+ based on standard Python type hints.
[FastAPI](https://fastapi.tiangolo.com/) 是一个易上手、高性能的异步 Web 框架,具有极佳的编写体验。 FastAPI 可以通过类型注解、依赖注入等方式实现输入参数校验、自动生成 API 文档等功能,也可以挂载其他 ASGI、WSGI 应用。
```env
DRIVER=~fastapi
```
#### FastAPI 配置项
##### `fastapi_openapi_url`
类型:`str | None`
默认值:`None`
说明:`FastAPI` 提供的 `OpenAPI` JSON 定义地址,如果为 `None`,则不提供 `OpenAPI` JSON 定义。
##### `fastapi_docs_url`
类型:`str | None`
默认值:`None`
说明:`FastAPI` 提供的 `Swagger` 文档地址,如果为 `None`,则不提供 `Swagger` 文档。
##### `fastapi_redoc_url`
类型:`str | None`
默认值:`None`
说明:`FastAPI` 提供的 `ReDoc` 文档地址,如果为 `None`,则不提供 `ReDoc` 文档。
##### `fastapi_include_adapter_schema`
类型:`bool`
默认值:`True`
说明:`FastAPI` 提供的 `OpenAPI` JSON 定义中是否包含适配器路由的 `Schema`
##### `fastapi_reload`
:::warning 警告
不推荐开启该配置项,在 Windows 平台上开启该功能有可能会造成预料之外的影响!替代方案:使用 `nb-cli` 命令行工具以及参数 `--reload` 启动 NoneBot。
```bash
nb run --reload
```
开启该功能后,在 uvicorn 运行时FastAPI 提供的 ASGI 底层,即 reload 功能的实际来源asyncio 使用的事件循环会被 uvicorn 从默认的 `ProactorEventLoop` 强制切换到 `SelectorEventLoop`
> 相关信息参考 [uvicorn#529](https://github.com/encode/uvicorn/issues/529)[uvicorn#1070](https://github.com/encode/uvicorn/pull/1070)[uvicorn#1257](https://github.com/encode/uvicorn/pull/1257)
后者(`SelectorEventLoop`)在 Windows 平台的可使用性不如前者(`ProactorEventLoop`),包括但不限于
1. 不支持创建子进程
2. 最多只支持 512 个套接字
3. ...
> 具体信息参考 [Python 文档](https://docs.python.org/zh-cn/3/library/asyncio-platforms.html#windows)
所以,一些使用了 asyncio 的库因此可能无法正常工作,如:
1. [playwright](https://playwright.dev/python/docs/library#incompatible-with-selectoreventloop-of-asyncio-on-windows)
如果在开启该功能后,原本**正常运行**的代码报错,且打印的异常堆栈信息和 asyncio 有关(异常一般为 `NotImplementedError`
你可能就需要考虑相关库对事件循环的支持,以及是否启用该功能。
:::
类型:`bool`
默认值:`False`
说明:是否开启 `uvicorn``reload` 功能,需要在机器人入口文件提供 ASGI 应用路径。
```python title=bot.py
app = nonebot.get_asgi()
nonebot.run(app="bot:app")
```
##### `fastapi_reload_dirs`
类型:`List[str] | None`
默认值:`None`
说明:重载监控文件夹列表,默认为 uvicorn 默认值
##### `fastapi_reload_delay`
类型:`float | None`
默认值:`None`
说明:重载延迟,默认为 uvicorn 默认值
##### `fastapi_reload_includes`
类型:`List[str] | None`
默认值:`None`
说明:要监听的文件列表,支持 glob pattern默认为 uvicorn 默认值
##### `fastapi_reload_excludes`
类型:`List[str] | None`
默认值:`None`
说明:不要监听的文件列表,支持 glob pattern默认为 uvicorn 默认值
##### `fastapi_extra`
类型:`Dist[str, Any]`
默认值:`{}`
说明:传递给 `FastAPI` 的其他参数
### Quart
**类型:**`ReverseDriver`
> Quart is an asyncio reimplementation of the popular Flask microframework API.
[Quart](https://quart.palletsprojects.com/) 是一个类 Flask 的异步版本,拥有与 Flask 非常相似的接口和使用方法。
```env
DRIVER=~quart
```
#### Quart 配置项
##### `quart_reload`
:::warning 警告
不推荐开启该配置项,在 Windows 平台上开启该功能有可能会造成预料之外的影响!替代方案:使用 `nb-cli` 命令行工具以及参数 `--reload` 启动 NoneBot。
```bash
nb run --reload
```
:::
类型:`bool`
默认值:`False`
说明:是否开启 `uvicorn` 的 `reload` 功能,需要在机器人入口文件提供 ASGI 应用路径。
```python title=bot.py
app = nonebot.get_asgi()
nonebot.run(app="bot:app")
```
##### `quart_reload_dirs`
类型:`List[str] | None`
默认值:`None`
说明:重载监控文件夹列表,默认为 uvicorn 默认值
##### `quart_reload_delay`
类型:`float | None`
默认值:`None`
说明:重载延迟,默认为 uvicorn 默认值
##### `quart_reload_includes`
类型:`List[str] | None`
默认值:`None`
说明:要监听的文件列表,支持 glob pattern默认为 uvicorn 默认值
##### `quart_reload_excludes`
类型:`List[str] | None`
默认值:`None`
说明:不要监听的文件列表,支持 glob pattern默认为 uvicorn 默认值
##### `quart_extra`
类型:`Dist[str, Any]`
默认值:`{}`
说明:传递给 `Quart` 的其他参数
### HTTPX
**类型:**`ForwardDriver`
:::warning 注意
本驱动器仅支持 HTTP 请求,不支持 WebSocket 连接请求。
:::
> [HTTPX](https://www.python-httpx.org/) is a fully featured HTTP client for Python 3, which provides sync and async APIs, and support for both HTTP/1.1 and HTTP/2.
```env
DRIVER=~httpx
```
### websockets
**类型:**`ForwardDriver`
:::warning 注意
本驱动器仅支持 WebSocket 连接请求,不支持 HTTP 请求。
:::
> [websockets](https://websockets.readthedocs.io/) is a library for building WebSocket servers and clients in Python with a focus on correctness, simplicity, robustness, and performance.
```env
DRIVER=~websockets
```
### AIOHTTP
**类型:**`ForwardDriver`
> [AIOHTTP](https://docs.aiohttp.org/): Asynchronous HTTP Client/Server for asyncio and Python.
```env
DRIVER=~aiohttp
```

View File

@@ -0,0 +1,40 @@
---
sidebar_position: 10
description: 自定义事件响应器存储
options:
menu:
weight: 110
category: advanced
---
# 事件响应器存储
事件响应器是 NoneBot 处理事件的核心,它们默认存储在一个字典中。在进入会话状态后,事件响应器将会转为临时响应器,作为最高优先级同样存储于该字典中。因此,事件响应器的存储类似于会话存储,它决定了整个 NoneBot 对事件的处理行为。
NoneBot 默认使用 Python 的字典将事件响应器存储于内存中,但是我们也可以自定义事件响应器存储,将事件响应器存储于其他地方,例如 Redis 等。这样我们就可以实现持久化、在多实例间共享会话状态等功能。
## 编写存储提供者
事件响应器的存储提供者 `MatcherProvider` 抽象类继承自 `MutableMapping[int, list[type[Matcher]]]`,即以优先级为键,以事件响应器列表为值的映射。我们可以方便地进行逐优先级事件传播。
编写一个自定义的存储提供者,只需要继承并实现 `MatcherProvider` 抽象类:
```python
from nonebot.matcher import MatcherProvider
class CustomProvider(MatcherProvider):
...
```
## 设置存储提供者
我们可以通过 `matchers.set_provider` 方法设置存储提供者:
```python {3}
from nonebot.matcher import matchers
matchers.set_provider(CustomProvider)
assert isinstance(matchers.provider, CustomProvider)
```

View File

@@ -0,0 +1,420 @@
---
sidebar_position: 5
description: 事件响应器组成与内置响应规则
options:
menu:
weight: 60
category: advanced
---
# 事件响应器进阶
在[指南](../tutorial/matcher.md)与[深入](../appendices/rule.md)中,我们已经介绍了事件响应器的基本用法以及响应规则、权限控制等功能。在这一节中,我们将介绍事件响应器的组成,内置的响应规则,与第三方响应规则拓展。
## 事件响应器组成
### 事件响应器类型
事件响应器类型 `type` 即是该响应器所要响应的事件类型,只有在接收到的事件类型与该响应器的类型相同时,才会触发该响应器。如果类型为空字符串 `""`,则响应器将会响应所有类型的事件。事件响应器类型的检查在所有其他检查(权限控制、响应规则)之前进行。
NoneBot 内置了四种常用事件类型:`meta_event``message``notice``request`,分别对应元事件、消息、通知、请求。通常情况下,协议适配器会将事件合理地分类至这四种类型中。如果有其他类型的事件需要响应,可以自行定义新的类型。
### 事件触发权限
事件触发权限 `permission` 是一个 `Permission` 对象,这在[权限控制](../appendices/permission.mdx)一节中已经介绍过。事件触发权限会在事件响应器的类型检查通过后进行检查,如果权限检查通过,则执行响应规则检查。
### 事件响应规则
事件响应规则 `rule` 是一个 `Rule` 对象,这在[响应规则](../appendices/rule.md)一节中已经介绍过。事件响应器的响应规则会在事件响应器的权限检查通过后进行匹配,如果响应规则检查通过,则触发该响应器。
### 响应优先级
响应优先级 `priority` 是一个正整数,用于指定响应器的优先级。响应器的优先级越小,越先被触发。如果响应器的优先级相同,则按照响应器的注册顺序进行触发。
### 阻断
阻断 `block` 是一个布尔值,用于指定响应器是否阻断事件的传播。如果阻断为 `True`,则在该响应器被触发后,事件将不会再传播给其他下一优先级的响应器。
NoneBot 内置的事件响应器中,所有非 `command` 规则的 `message` 类型的事件响应器都会阻断事件传递,其他则不会。
在部分情况中,可以使用 [`stop_propagation`](../appendices/session-control.mdx#stop_propagation) 方法动态阻止事件传播,该方法需要 handler 在参数中获取 matcher 实例后调用方法。
### 有效期
事件响应器的有效期分为 `temp``expire_time``temp` 是一个布尔值,用于指定响应器是否为临时响应器。如果为 `True`,则该响应器在被触发后会被自动销毁。`expire_time` 是一个 `datetime` 对象,用于指定响应器的过期时间。如果 `expire_time` 不为 `None`,则在该时间点后,该响应器会被自动销毁。
### 默认状态
事件响应器的默认状态 `default_state` 是一个 `dict` 对象,用于指定响应器的默认状态。在响应器被触发时,响应器将会初始化默认状态然后开始执行事件处理流程。
## 基本辅助函数
NoneBot 为四种类型的事件响应器提供了五个基本的辅助函数:
- `on`:创建任何类型的事件响应器。
- `on_metaevent`:创建元事件响应器。
- `on_message`:创建消息事件响应器。
- `on_request`:创建请求事件响应器。
- `on_notice`:创建通知事件响应器。
除了 `on` 函数具有一个 `type` 参数外,其余参数均相同:
- `rule`:响应规则,可以是 `Rule` 对象或者 `RuleChecker` 函数。
- `permission`:事件触发权限,可以是 `Permission` 对象或者 `PermissionChecker` 函数。
- `handlers`:事件处理函数列表。
- `temp`:是否为临时响应器。
- `expire_time`:响应器的过期时间。
- `priority`:响应器的优先级。
- `block`:是否阻断事件传播。
- `state`:响应器的默认状态。
在消息类型的事件响应器的基础上NoneBot 还内置了一些常用的响应规则,并结合为辅助函数来方便我们快速创建指定功能的响应器。下面我们逐个介绍。
## 内置响应规则
### `startswith`
`startswith` 响应规则用于匹配消息纯文本部分的开头是否与指定字符串(或一系列字符串)相同。可选参数 `ignorecase` 用于指定是否忽略大小写,默认为 `False`
例如,我们可以创建一个匹配消息开头为 `!` 或者 `/` 的规则:
```python
from nonebot.rule import startswith
rule = startswith(("!", "/"), ignorecase=False)
```
也可以直接使用辅助函数新建一个响应器:
```python
from nonebot import on_startswith
matcher = on_startswith(("!", "/"), ignorecase=False)
```
### `endswith`
`endswith` 响应规则用于匹配消息纯文本部分的结尾是否与指定字符串(或一系列字符串)相同。可选参数 `ignorecase` 用于指定是否忽略大小写,默认为 `False`
例如,我们可以创建一个匹配消息结尾为 `.` 或者 `。` 的规则:
```python
from nonebot.rule import endswith
rule = endswith((".", "。"), ignorecase=False)
```
也可以直接使用辅助函数新建一个响应器:
```python
from nonebot import on_endswith
matcher = on_endswith((".", "。"), ignorecase=False)
```
### `fullmatch`
`fullmatch` 响应规则用于匹配消息纯文本部分是否与指定字符串(或一系列字符串)完全相同。可选参数 `ignorecase` 用于指定是否忽略大小写,默认为 `False`
例如,我们可以创建一个匹配消息为 `ping` 或者 `pong` 的规则:
```python
from nonebot.rule import fullmatch
rule = fullmatch(("ping", "pong"), ignorecase=False)
```
也可以直接使用辅助函数新建一个响应器:
```python
from nonebot import on_fullmatch
matcher = on_fullmatch(("ping", "pong"), ignorecase=False)
```
### `keyword`
`keyword` 响应规则用于匹配消息纯文本部分是否包含指定字符串(或一系列字符串)。
例如,我们可以创建一个匹配消息中包含 `hello` 或者 `hi` 的规则:
```python
from nonebot.rule import keyword
rule = keyword("hello", "hi")
```
也可以直接使用辅助函数新建一个响应器:
```python
from nonebot import on_keyword
matcher = on_keyword("hello", "hi")
```
### `command`
`command` 是最常用的响应规则,它用于匹配消息是否为命令。它会根据配置中的 [Command Start 和 Command Separator](../appendices/config.mdx#command-start-和-command-separator) 来判断消息是否为命令。
例如,当我们配置了 `Command Start``/``Command Separator``.` 时:
```python
from nonebot.rule import command
# 匹配 "/help" 或者 "/帮助" 开头的消息
rule = command("help", "帮助")
# 匹配 "/help.cmd" 开头的消息
rule = command(("help", "cmd"))
```
也可以直接使用辅助函数新建一个响应器:
```python
from nonebot import on_command
matcher = on_command("help", aliases={"帮助"})
```
此外,`command` 响应规则默认允许消息命令与参数间不加空格,如果需要严格匹配命令与参数间的空白符,可以使用 `command` 函数的 `force_whitespace` 参数。`force_whitespace` 参数可以是 bool 类型或者具体的字符串,默认为 `False`。如果为 `True`,则命令与参数间必须有任意个数的空白符;如果为字符串,则命令与参数间必须有且与给定字符串一致的空白符。
```python
rule = command("help", force_whitespace=True)
rule = command("help", force_whitespace=" ")
```
命令解析后的结果可以通过 [`Command`](./dependency.mdx#command)、[`RawCommand`](./dependency.mdx#rawcommand)、[`CommandArg`](./dependency.mdx#commandarg)、[`CommandStart`](./dependency.mdx#commandstart)、[`CommandWhitespace`](./dependency.mdx#commandwhitespace) 依赖注入获取。
### `shell_command`
`shell_command` 响应规则用于匹配类 shell 命令形式的消息。它首先与 [`command`](#command) 响应规则一样进行命令匹配,如果匹配成功,则会进行进一步的参数解析。参数解析采用 `argparse` 标准库进行,在此基础上添加了消息序列 `Message` 支持。
例如,我们可以创建一个匹配 `/cmd` 命令并且带有 `-v` 选项与默认 `-h` 帮助选项的规则:
```python
from nonebot.rule import shell_command, ArgumentParser
parser = ArgumentParser()
parser.add_argument("-v", "--verbose", action="store_true")
rule = shell_command("cmd", parser=parser)
```
更多关于 `argparse` 的使用方法请参考 [argparse 文档](https://docs.python.org/zh-cn/3/library/argparse.html)。我们也可以选择不提供 `parser` 参数,这样 `shell_command` 将不会解析参数,但会提供参数列表 `argv`
直接使用辅助函数新建一个响应器:
```python
from nonebot import on_shell_command
from nonebot.rule import ArgumentParser
parser = ArgumentParser()
parser.add_argument("-v", "--verbose", action="store_true")
matcher = on_shell_command("cmd", parser=parser)
```
参数解析后的结果可以通过 [`ShellCommandArgv`](./dependency.mdx#shellcommandargv)、[`ShellCommandArgs`](./dependency.mdx#shellcommandargs) 依赖注入获取。
### `regex`
`regex` 响应规则用于匹配消息是否与指定正则表达式匹配。
:::tip 提示
正则表达式匹配使用 search 而非 match如需从头匹配请使用 `r"^xxx"` 模式来确保匹配开头。
:::
例如,我们可以创建一个匹配消息中包含字母并且忽略大小写的规则:
```python
from nonebot.rule import regex
rule = regex(r"[a-z]+", flags=re.IGNORECASE)
```
也可以直接使用辅助函数新建一个响应器:
```python
from nonebot import on_regex
matcher = on_regex(r"[a-z]+", flags=re.IGNORECASE)
```
正则匹配后的结果可以通过 [`RegexStr`](./dependency.mdx#regexstr)、[`RegexGroup`](./dependency.mdx#regexgroup)、[`RegexDict`](./dependency.mdx#regexdict) 依赖注入获取。
### `to_me`
`to_me` 响应规则用于匹配事件是否与机器人相关。
例如:
```python
from nonebot.rule import to_me
rule = to_me()
```
### `is_type`
`is_type` 响应规则用于匹配事件类型是否为指定类型(或者一系列类型)。
例如,我们可以创建一个匹配 OneBot v11 私聊和群聊消息事件的规则:
```python
from nonebot.rule import is_type
from nonebot.adapters.onebot.v11 import PrivateMessageEvent, GroupMessageEvent
rule = is_type(PrivateMessageEvent, GroupMessageEvent)
```
## 响应器组
为了更方便的管理一系列功能相近的响应器NoneBot 提供了两种响应器组,它们可以帮助我们进行响应器的统一管理。
### `CommandGroup`
`CommandGroup` 可以用于管理一系列具有相同前置命令的子命令响应器。
例如,我们创建 `/cmd``/cmd.sub``/cmd.help` 三个命令,他们具有相同的优先级:
```python
from nonebot import CommandGroup
group = CommandGroup("cmd", priority=10)
cmd = group.command(tuple())
sub_cmd = group.command("sub")
help_cmd = group.command("help")
```
### `MatcherGroup`
`MatcherGroup` 可以用于管理一系列具有相同属性的响应器。
例如,我们创建一个具有相同响应规则的响应器组:
```python
from nonebot.rule import to_me
from nonebot import MatcherGroup
group = MatcherGroup(rule=to_me())
matcher1 = group.on_message()
matcher2 = group.on_message()
```
## 第三方响应规则
### Alconna
[`nonebot-plugin-alconna`](https://github.com/ArcletProject/nonebot-plugin-alconna) 是一类提供了拓展响应规则的插件。
该插件使用 [Alconna](https://github.com/ArcletProject/Alconna) 作为命令解析器,
是一个简单、灵活、高效的命令参数解析器, 并且不局限于解析命令式字符串。
特点包括:
- 高效
- 直观的命令组件创建方式
- 强大的类型解析与类型转换功能
- 自定义的帮助信息格式
- 多语言支持
- 易用的快捷命令创建与使用
- 可创建命令补全会话, 以实现多轮连续的补全提示
- 可嵌套的多级子命令
- 正则匹配支持
该插件提供了一类新的事件响应器辅助函数 `on_alconna`,以及 `AlconnaResult` 等依赖注入函数。
同时,基于 [Annotated 支持](https://github.com/nonebot/nonebot2/pull/1832), 添加了两类注解 `AlcMatches``AlcResult`
该插件还可以通过 `handle(parameterless)` 来控制一个具体的响应函数是否在不满足条件时跳过响应:
- `pip.handle([Check(assign("add.name", "nb"))])` 表示仅在命令为 `role-group add` 并且 name 为 `nb` 时响应
- `pip.handle([Check(assign("list"))])` 表示仅在命令为 `role-group list` 时响应
- `pip.handle([Check(assign("add"))])` 表示仅在命令为 `role-group add` 时响应
基于 `Alconna` 的特性,该插件同时提供了一系列便捷的消息段标注。
标注可用于在 `Alconna` 中匹配消息中除 text 外的其他消息段,也可用于快速创建各适配器下的消息段。所有标注位于 `nonebot_plugin_alconna.adapters` 中。
#### 插件安装
```shell
nb plugin install nonebot-plugin-alconna
```
```shell
pip install nonebot-plugin-alconna
```
#### 示例
```python
from nonebot_plugin_alconna.adapters import At
from nonebot.adapters.onebot.v12 import Message
from nonebot_plugin_alconna.adapters.onebot12 import Image
from nonebot_plugin_alconna import AlconnaMatches, on_alconna
from nonebot.adapters.onebot.v12 import MessageSegment as Ob12MS
from arclet.alconna import Args, Option, Alconna, Arparma, MultiVar, Subcommand
alc = Alconna(
"role-group",
Subcommand(
"add",
Args["name", str],
Option("member", Args["target", MultiVar(At)]),
),
Option("list"),
)
rg = on_alconna(alc, auto_send_output=True)
@rg.handle()
async def _(result: Arparma = AlconnaMatches()):
if result.find("list"):
img = await gen_role_group_list_image()
await rg.finish(Message([Image(img)]))
if result.find("add"):
group = await create_role_group(result["add.name"])
if result.find("add.member"):
ats: tuple[Ob12MS, ...] = result["add.member.target"]
group.extend(member.data["user_id"] for member in ats)
await rg.finish("添加成功")
```
我们可以看到主要的两大组件:`Option``Subcommand`
`Option` 可以传入一组别名,如 `Option("--foo|-F|--FOO|-f")``Option("--foo", alias=["-F"]`
`Subcommand` 则可以传入自己的 `Option``Subcommand`
他们拥有如下共同参数:
- `help_text`: 传入该组件的帮助信息
- `dest`: 被指定为解析完成时标注匹配结果的标识符,不传入时默认为选项或子命令的名称 (name)
- `requires`: 一段指定顺序的字符串列表,作为唯一的前置序列与命令嵌套替换
- `default`: 默认值,在该组件未被解析时使用使用该值替换。
其次使用了消息段标注,其中 `At` 属于通用标注,而 `Image` 属于 `onebot12` 适配器下的标注。
`on_alconna` 的所有参数如下:
- `command: Alconna | str`: Alconna 命令
- `skip_for_unmatch: bool = True`: 是否在命令不匹配时跳过该响应
- `auto_send_output: bool = False`: 是否自动发送输出信息并跳过响应
- `output_converter: TConvert | None = None`: 输出信息字符串转换为消息序列方法
- `aliases: set[str | tuple[str, ...]] | None = None`: 命令别名, 作用类似于 `on_command` 中的 aliases
- `comp_config: CompConfig | None = None`: 补全会话配置, 不传入则不启用补全会话
`AlconnaMatches` 是一个依赖注入函数,可注入 `Alconna` 命令解析结果。
#### 参考
插件文档: [📦 这里](https://github.com/ArcletProject/nonebot-plugin-alconna/blob/master/docs.md)
官方文档: [👉 指路](https://arclet.top/)
QQ 交流群: [🔗 链接](https://jq.qq.com/?_wv=1027&k=PUPOnCSH)
友链: [📚 文档](https://graiax.cn/guide/message_parser/alconna.html)

View File

@@ -0,0 +1,97 @@
---
sidebar_position: 2
description: 填写与获取插件相关的信息
options:
menu:
weight: 30
category: advanced
---
# 插件信息
NoneBot 是一个插件化的框架,可以通过加载插件来扩展功能。同时,我们也可以通过 NoneBot 的插件系统来获取相关信息,例如插件的名称、使用方法,用于收集帮助信息等。下面我们将介绍如何为插件添加元数据,以及如何获取插件信息。
## 插件元数据
在 NoneBot 中,插件 [`Plugin`](../api/plugin/plugin.md#Plugin) 对象中存储了插件系统所需要的一系列信息。包括插件的索引名称、插件模块、插件中的事件响应器、插件父子关系等。通常,只有插件开发者才需要关心这些信息,而插件使用者或者机器人用户想要看到的是插件使用方法等帮助信息。因此,我们可以为插件添加插件元数据 `PluginMetadata`,它允许插件开发者为插件添加一些额外的信息。这些信息编写于插件模块的顶层,可以直接通过源码查看,或者通过 NoneBot 插件系统获取收集到的信息,通过其他方式发送给机器人用户等。
现在,假设我们有一个插件 `example`, 它的模块结构如下:
```tree {4-6} title=Project
📦 awesome-bot
├── 📂 awesome_bot
│ └── 📂 plugins
| └── 📂 example
| ├── 📜 __init__.py
| └── 📜 config.py
├── 📜 pyproject.toml
└── 📜 README.md
```
我们需要在插件顶层模块 `example/__init__.py` 中添加插件元数据,如下所示:
```python {1,5-11} title=example/__init__.py
from nonebot.plugin import PluginMetadata
from .config import Config
__plugin_meta__ = PluginMetadata(
name="示例插件",
description="这是一个示例插件",
usage="没什么用",
config=Config,
extra={},
)
```
我们可以看到,插件元数据 `PluginMetadata` 有三个基本属性:插件名称、插件描述、插件使用方法。除此之外,还有两个可选的属性。`config` 属性用于指定插件的[配置类](../appendices/config.mdx#插件配置)`extra` 属性,它是一个字典,可以用于存储任意信息。其他插件可以通过约定 `extra` 字典的键名来达成收集某些特殊信息的目的。
请注意,这里的**插件名称**是供使用者或机器人用户查看的,与插件索引名称无关。**插件索引名称(插件模块名称)**仅用于 NoneBot 插件系统**内部索引**。
## 获取插件信息
NoneBot 提供了多种获取插件对象的方法,例如获取当前所有已导入的插件:
```python
import nonebot
plugins: set[Plugin] = nonebot.get_loaded_plugins()
```
也可以通过插件索引名称获取插件对象:
```python
import nonebot
plugin: Plugin | None = nonebot.get_plugin("example")
```
或者通过模块路径获取插件对象:
```python
import nonebot
plugin: Plugin | None = nonebot.get_plugin_by_module_name("awesome_bot.plugins.example")
```
如果需要获取所有当前声明的插件名称(可能还未加载),可以使用 `get_available_plugin_names` 函数:
```python
import nonebot
plugin_names: set[str] = nonebot.get_available_plugin_names()
```
插件对象 `Plugin` 中包含了多个属性:
- `name`:插件索引名称
- `module`:插件模块
- `module_name`:插件模块路径
- `manager`:插件管理器
- `matcher`:插件中定义的事件响应器
- `parent_plugin`:插件的父插件
- `sub_plugins`:插件的子插件集合
- `metadata`:插件元数据
通过这些属性以及插件元数据,我们就可以收集所需要的插件信息了。

View File

@@ -0,0 +1,41 @@
---
sidebar_position: 3
description: 编写与加载嵌套插件
options:
menu:
weight: 40
category: advanced
---
# 嵌套插件
NoneBot 支持嵌套插件,即一个插件可以包含其他插件。通过这种方式,我们可以将一个大型插件拆分成多个功能子插件,使得插件更加清晰、易于维护。我们可以直接在插件中使用 NoneBot 加载插件的方法来加载子插件。
## 创建嵌套插件
我们可以在使用 `nb-cli` 命令[创建插件](../tutorial/create-plugin.md#创建插件)时,选择直接通过模板创建一个嵌套插件:
```bash
$ nb plugin create
[?] 插件名称: parent
[?] 使用嵌套插件? (y/N) Y
[?] 输出目录: awesome_bot/plugins
```
或者使用 `nb plugin create --sub-plugin` 选项直接创建一个嵌套插件。
## 已有插件
如果你已经有一个插件,想要在其中嵌套加载子插件,可以在插件的 `__init__.py` 中添加如下代码:
```python title=parent/__init__.py
import nonebot
from pathlib import Path
sub_plugins = nonebot.load_plugins(
str(Path(__file__).parent.joinpath("plugins").resolve())
)
```
这样,`parent` 插件就会加载 `parent/plugins` 目录下的所有插件。NoneBot 会正确识别这些插件的父子关系,你可以在 `parent` 的插件信息中看到这些子插件的信息,也可以在子插件信息中看到它们的父插件信息。

View File

@@ -0,0 +1,37 @@
---
sidebar_position: 4
description: 使用其他插件提供的功能
options:
menu:
weight: 50
category: advanced
---
# 跨插件访问
NoneBot 插件化系统的设计使得插件之间可以功能独立、各司其职我们可以更好地维护和扩展插件。但是有时候我们可能需要在不同插件之间调用功能。NoneBot 生态中就有一类插件,它们专为其他插件提供功能支持,如:[定时任务插件](../best-practice/scheduler.md)、[数据存储插件](../best-practice/data-storing.md)等。这时候我们就需要在插件之间进行跨插件访问。
## 插件跟踪
由于 NoneBot 插件系统通过 [Import Hooks](https://docs.python.org/3/reference/import.html#import-hooks) 的方式实现插件加载与跟踪管理,因此我们**不能**在 NoneBot 跟踪插件前进行模块 import这会导致插件加载失败。即我们不能在使用 NoneBot 提供的加载插件方法前,直接使用 `import` 语句导入插件。
对于在项目目录下的插件,我们通常直接使用 `load_from_toml` 等方法一次性加载所有插件。由于这些插件已经被声明即便插件导入顺序不同NoneBot 也能正确跟踪插件。此时我们不需要对跨插件访问进行特殊处理。但当我们使用了外部插件如果没有事先声明或加载插件NoneBot 并不会将其当作插件进行跟踪,可能会出现意料之外的错误出现。
简单来说,我们必须在 `import` 外部插件之前,确保依赖的外部插件已经被声明或加载。
## 插件依赖声明
NoneBot 提供了一种方法来确保我们依赖的插件已经被正确加载,即使用 `require` 函数。通过 `require` 函数我们可以在当前插件中声明依赖的插件NoneBot 会在加载当前插件时,检查依赖的插件是否已经被加载,如果没有,会尝试优先加载依赖的插件。
假设我们有一个插件 `a` 依赖于插件 `b`,我们可以在插件 `a` 中使用 `require` 函数声明其依赖于插件 `b`
```python {3} title=a/__init__.py
from nonebot import require
require("b")
from b import some_function
```
其中,`require` 函数的参数为插件索引名称或者外部插件的模块名称。在完成依赖声明后,我们可以在插件 `a` 中直接导入插件 `b` 所提供的功能。

View File

@@ -0,0 +1,134 @@
---
sidebar_position: 9
description: 添加服务端路由规则
options:
menu:
weight: 100
category: advanced
---
# 添加路由
在[驱动器](./driver.md)一节中,我们了解了驱动器的两种类型。既然驱动器可以作为服务端运行,那么我们就可以向驱动器添加路由规则,从而实现自定义的 API 接口等功能。在添加路由规则时,我们需要注意驱动器的类型,详情可以参考[选择驱动器](./driver.md#配置驱动器)。
NoneBot 中,我们可以通过两种途径向驱动器添加路由规则:
1. 通过 NoneBot 的兼容层建立路由规则。
2. 直接向 ASGI 应用添加路由规则。
这两种途径各有优劣,前者可以在各种服务端型驱动器下运行,但并不能直接使用 ASGI 应用框架提供的特性与功能;后者直接使用 ASGI 应用,更自由、功能完整,但只能在特定类型驱动器下运行。
在向驱动器添加路由规则时,我们需要注意驱动器是否为服务端类型,我们可以通过以下方式判断:
```python {3}
from nonebot import get_driver
from nonebot.drivers import ReverseDriver
can_use = isinstance(get_driver(), ReverseDriver)
```
## 通过兼容层添加路由
NoneBot 兼容层定义了两个数据类 `HTTPServerSetup` 和 `WebSocketServerSetup`,分别用于定义 HTTP 服务端和 WebSocket 服务端的路由规则。
### HTTP 路由
`HTTPServerSetup` 具有四个属性:
- `path`:路由路径,不支持特殊占位表达式。类型为 `URL`。
- `method`:请求方法。类型为 `str`。
- `name`:路由名称,不可重复。类型为 `str`。
- `handle_func`:路由处理函数。类型为 `Callable[[Request], Awaitable[Response]]`。
例如,我们添加一个 `/hello` 的路由,当请求方法为 `GET` 时,返回 `200 OK` 以及返回体信息:
```python
from nonebot import get_driver
from nonebot.drivers import URL, Request, Response, HTTPServerSetup
async def hello(request: Request) -> Response:
return Response(200, content="Hello, world!")
if isinstance((driver := get_driver()), ReverseDriver):
driver.setup_http_server(
HTTPServerSetup(
path=URL("/hello"),
method="GET",
name="hello",
handle_func=hello,
)
)
```
对于 `Request` 和 `Response` 的详细信息,可以参考 [API 文档](../api/drivers/index.md)。
### WebSocket 路由
`WebSocketServerSetup` 具有三个属性:
- `path`:路由路径,不支持特殊占位表达式。类型为 `URL`。
- `name`:路由名称,不可重复。类型为 `str`。
- `handle_func`:路由处理函数。类型为 `Callable[[WebSocket], Awaitable[Any]]`。
例如,我们添加一个 `/ws` 的路由,发送所有接收到的数据:
```python
from nonebot import get_driver
from nonebot.drivers import URL, WebSocket, WebSocketServerSetup
async def ws_handler(ws: WebSocket):
await ws.accept()
try:
while True:
data = await ws.receive()
await ws.send(data)
except WebSocketClosed as e:
# handle closed
...
finally:
with contextlib.suppress(Exception):
await websocket.close()
# do some cleanup
if isinstance((driver := get_driver()), ReverseDriver):
driver.setup_websocket_server(
WebSocketServerSetup(
path=URL("/ws"),
name="ws",
handle_func=ws_handler,
)
)
```
对于 `WebSocket` 的详细信息,可以参考 [API 文档](../api/drivers/index.md)。
## 使用 ASGI 应用添加路由
### 获取 ASGI 应用
NoneBot 服务端类型的驱动器具有两个属性 `server_app` 和 `asgi`,分别对应驱动框架应用和 ASGI 应用。通常情况下,这两个应用是同一个对象。我们可以通过 `get_app()` 方法快速获取:
```python
import nonebot
app = nonebot.get_app()
asgi = nonebot.get_asgi()
```
### 添加路由规则
在获取到了 ASGI 应用后,我们就可以直接使用 ASGI 应用框架提供的功能来添加路由规则了。这里我们以 [FastAPI](./driver.md#fastapi默认) 为例,演示如何添加路由规则。
在下面的代码中,我们添加了一个 `GET` 类型的 `/api` 路由,具体方法参考 [FastAPI 文档](https://fastapi.tiangolo.com/)。
```python
import nonebot
from fastapi import FastAPI
app: FastAPI = nonebot.get_app()
@app.get("/api")
async def custom_api():
return {"message": "Hello, world!"}
```

View File

@@ -0,0 +1,141 @@
---
sidebar_position: 8
description: 在特定的生命周期中执行代码
options:
menu:
weight: 90
category: advanced
---
# 钩子函数
> [钩子编程](https://zh.wikipedia.org/wiki/%E9%92%A9%E5%AD%90%E7%BC%96%E7%A8%8B)hooking也称作“挂钩”是计算机程序设计术语指通过拦截软件模块间的函数调用、消息传递、事件传递来修改或扩展操作系统、应用程序或其他软件组件的行为的各种技术。处理被拦截的函数调用、事件、消息的代码被称为钩子hook
在 NoneBot 中有一系列预定义的钩子函数,可以分为两类:**全局钩子函数**和**事件处理钩子函数**,这些钩子函数可以用装饰器的形式来使用。
## 全局钩子函数
全局钩子函数是指 NoneBot 针对其本身运行过程的钩子函数。
这些钩子函数是由驱动器来运行的,故需要先[获得全局驱动器](./driver.md#获取驱动器)。
### 启动准备
这个钩子函数会在 NoneBot 启动时运行。很多时候,我们并不希望在模块被导入时就执行一些耗时操作,如:连接数据库,这时候我们可以在这个钩子函数中进行这些操作。
```python
@driver.on_startup
async def do_something():
pass
```
### 终止处理
这个钩子函数会在 NoneBot 终止时运行。我们可以在这个钩子函数中进行一些清理工作,如:关闭数据库连接。
```python
@driver.on_shutdown
async def do_something():
pass
```
### Bot 连接处理
这个钩子函数会在任何协议适配器连接 `Bot` 对象至 NoneBot 时运行。支持依赖注入,可以直接注入 `Bot` 对象。
```python
@driver.on_bot_connect
async def do_something(bot: Bot):
pass
```
### Bot 断开处理
这个钩子函数会在 `Bot` 断开与 NoneBot 的连接时运行。支持依赖注入,可以直接注入 `Bot` 对象。
```python
@driver.on_bot_disconnect
async def do_something(bot: Bot):
pass
```
## 事件处理钩子函数
这些钩子函数指的是影响 NoneBot 进行**事件处理**的函数, 这些函数可以跟普通的事件处理函数一样接受相应的参数。
### 事件预处理
这个钩子函数会在 NoneBot 接收到新的事件时运行。支持依赖注入,可以注入 `Bot` 对象、事件、会话状态。
```python
from nonebot.message import event_preprocessor
@event_preprocessor
async def do_something(event: Event):
pass
```
### 事件后处理
这个钩子函数会在 NoneBot 处理事件完成后运行。支持依赖注入,可以注入 `Bot` 对象、事件、会话状态。
```python
from nonebot.message import event_postprocessor
@event_postprocessor
async def do_something(event: Event):
pass
```
### 运行预处理
这个钩子函数会在 NoneBot 运行事件响应器前运行。支持依赖注入,可以注入 `Bot` 对象、事件、事件响应器、会话状态。
```python
from nonebot.message import run_preprocessor
@run_preprocessor
async def do_something(event: Event, matcher: Matcher):
pass
```
### 运行后处理
这个钩子函数会在 NoneBot 运行事件响应器后运行。支持依赖注入,可以注入 `Bot` 对象、事件、事件响应器、会话状态、运行中产生的异常。
```python
from nonebot.message import run_postprocessor
@run_postprocessor
async def do_something(event: Event, matcher: Matcher, exception: Optional[Exception]):
pass
```
### 平台接口调用钩子
这个钩子函数会在 `Bot` 对象调用平台接口时运行。在这个钩子函数中,我们可以通过引起 `MockApiException` 异常来阻止 `Bot` 对象调用平台接口并返回指定的结果。
```python
from nonebot.adapters import Bot
from nonebot.exception import MockApiException
@Bot.on_calling_api
async def handle_api_call(bot: Bot, api: str, data: Dict[str, Any]):
if api == "send_msg":
raise MockApiException(result={"message_id": 123})
```
### 平台接口调用后钩子
这个钩子函数会在 `Bot` 对象调用平台接口后运行。在这个钩子函数中,我们可以通过引起 `MockApiException` 异常来忽略平台接口返回的结果并返回指定的结果。
```python
from nonebot.adapters import Bot
from nonebot.exception import MockApiException
@Bot.on_called_api
async def handle_api_result(
bot: Bot, exception: Optional[Exception], api: str, data: Dict[str, Any], result: Any
):
if not exception and api == "send_msg":
raise MockApiException(result={**result, "message_id": 123})
```

View File

@@ -0,0 +1,59 @@
---
sidebar_position: 7
description: 控制会话响应对象
options:
menu:
weight: 80
category: advanced
---
# 会话更新
在 NoneBot 中在某个事件响应器对事件响应后即是进入了会话状态会话状态会持续到整个事件响应流程结束。会话过程中机器人可以与用户进行多次交互。每次需要等待用户事件时NoneBot 将会复制一个新的临时事件响应器,并更新该事件响应器使其响应当前会话主体的消息,这个过程称为会话更新。
会话更新分为两部分:**更新[事件响应器类型](./matcher.md#事件响应器类型)**和**更新[事件触发权限](./matcher.md#事件触发权限)**。
## 更新事件响应器类型
通常情况下,与机器人用户进行的会话都是通过消息事件进行的,因此会话更新后的默认响应事件类型为 `message`。如果希望接收一个特定类型的消息,比如 `notice` 等,我们需要自定义响应事件类型更新函数。响应事件类型更新函数是一个 `Dependent`,可以使用依赖注入。
```python {3-5}
foo = on_message()
@foo.type_updater
async def _() -> str:
return "notice"
```
在注册了上述响应事件类型更新函数后,当我们需要等待用户事件时,将只会响应 `notice` 类型的事件。如果希望在会话过程中的不同阶段响应不同类型的事件,我们就需要使用更复杂的逻辑来更新响应事件类型(如:根据会话状态),这里将不再展示。
## 更新事件触发权限
会话通常是由机器人与用户进行的一对一交互,因此会话更新后的默认触发权限为当前事件的会话 ID。这个会话 ID 由协议适配器生成,通常由用户 ID 和群 ID 等组成。如果希望实现更复杂的会话功能(如:多用户同时参与的会话),我们需要自定义触发权限更新函数。触发权限更新函数是一个 `Dependent`,可以使用依赖注入。
```python {5-7}
from nonebot.permission import User
foo = on_message()
@foo.permission_updater
async def _(event: Event, matcher: Matcher) -> Permission:
return Permission(User.from_event(event, perm=matcher.permission))
```
上述权限更新函数是默认的权限更新函数,它将会话的触发权限更新为当前事件的会话 ID。如果我们希望响应多个用户的消息我们可以如下修改
```python {5-7}
from nonebot.permission import USER
foo = on_message()
@foo.permission_updater
async def _(matcher: Matcher) -> Permission:
return USER("session1", "session2", perm=matcher.permission)
```
请注意,此处为全大写字母的 `USER` 权限,它可以匹配多个会话 ID。通过这种方式我们可以实现多用户同时参与的会话。
我们已经了解了如何控制会话的更新,相信你已经能够实现更复杂的会话功能了,例如多人小游戏等等。欢迎将你的作品分享到[插件商店](/store)。

View File

@@ -0,0 +1,3 @@
{
"position": 15
}

View File

@@ -0,0 +1,871 @@
---
sidebar_position: 0
description: nonebot.adapters 模块
---
# nonebot.adapters
本模块定义了协议适配基类,各协议请继承以下基类。
使用 [Driver.register_adapter](../drivers/index.md#Driver-register_adapter) 注册适配器。
## _abstract class_ `Bot(adapter, self_id)` {#Bot}
- **说明**
Bot 基类。
用于处理上报消息,并提供 API 调用接口。
- **参数**
- `adapter` ([Adapter](#Adapter)): 协议适配器实例
- `self_id` (str): 机器人 ID
### _instance-var_ `adapter` {#Bot-adapter}
- **类型:** [Adapter](#Adapter)
- **说明:** 协议适配器实例
### _instance-var_ `self_id` {#Bot-self_id}
- **类型:** str
- **说明:** 机器人 ID
### _property_ `type` {#Bot-type}
- **类型:** str
- **说明:** 协议适配器名称
### _property_ `config` {#Bot-config}
- **类型:** [Config](../config.md#Config)
- **说明:** 全局 NoneBot 配置
### _async method_ `call_api(api, **data)` {#Bot-call_api}
- **说明:** 调用机器人 API 接口,可以通过该函数或直接通过 bot 属性进行调用
- **参数**
- `api` (str): API 名称
- `**data` (Any): API 数据
- **返回**
- Any
- **用法**
```python
await bot.call_api("send_msg", message="hello world")
await bot.send_msg(message="hello world")
```
### _abstract async method_ `send(event, message, **kwargs)` {#Bot-send}
- **说明:** 调用机器人基础发送消息接口
- **参数**
- `event` ([Event](#Event)): 上报事件
- `message` (str | [Message](#Message) | [MessageSegment](#MessageSegment)): 要发送的消息
- `**kwargs` (Any): 任意额外参数
- **返回**
- Any
### _classmethod_ `on_calling_api(func)` {#Bot-on_calling_api}
- **说明**
调用 api 预处理。
钩子函数参数:
- bot: 当前 bot 对象
- api: 调用的 api 名称
- data: api 调用的参数字典
- **参数**
- `func` ([T_CallingAPIHook](../typing.md#T_CallingAPIHook))
- **返回**
- [T_CallingAPIHook](../typing.md#T_CallingAPIHook)
### _classmethod_ `on_called_api(func)` {#Bot-on_called_api}
- **说明**
调用 api 后处理。
钩子函数参数:
- bot: 当前 bot 对象
- exception: 调用 api 时发生的错误
- api: 调用的 api 名称
- data: api 调用的参数字典
- result: api 调用的返回
- **参数**
- `func` ([T_CalledAPIHook](../typing.md#T_CalledAPIHook))
- **返回**
- [T_CalledAPIHook](../typing.md#T_CalledAPIHook)
## _abstract class_ `Event(<auto>)` {#Event}
- **说明:** Event 基类。提供获取关键信息的方法,其余信息可直接获取。
- **参数**
auto
### _classmethod_ `validate(value)` {#Event-validate}
- **参数**
- `value` (Any)
- **返回**
- E
### _abstract method_ `get_type()` {#Event-get_type}
- **说明:** 获取事件类型的方法,类型通常为 NoneBot 内置的四种类型。
- **参数**
empty
- **返回**
- str
### _abstract method_ `get_event_name()` {#Event-get_event_name}
- **说明:** 获取事件名称的方法。
- **参数**
empty
- **返回**
- str
### _abstract method_ `get_event_description()` {#Event-get_event_description}
- **说明:** 获取事件描述的方法,通常为事件具体内容。
- **参数**
empty
- **返回**
- str
### _method_ `get_log_string()` {#Event-get_log_string}
- **说明**
获取事件日志信息的方法。
通常你不需要修改这个方法,只有当希望 NoneBot 隐藏该事件日志时,可以抛出 `NoLogException` 异常。
- **参数**
empty
- **返回**
- str
- **异常**
- NoLogException
### _abstract method_ `get_user_id()` {#Event-get_user_id}
- **说明:** 获取事件主体 id 的方法,通常是用户 id 。
- **参数**
empty
- **返回**
- str
### _abstract method_ `get_session_id()` {#Event-get_session_id}
- **说明:** 获取会话 id 的方法,用于判断当前事件属于哪一个会话,通常是用户 id、群组 id 组合。
- **参数**
empty
- **返回**
- str
### _abstract method_ `get_message()` {#Event-get_message}
- **说明:** 获取事件消息内容的方法。
- **参数**
empty
- **返回**
- [Message](#Message)
### _method_ `get_plaintext()` {#Event-get_plaintext}
- **说明**
获取消息纯文本的方法。
通常不需要修改,默认通过 `get_message().extract_plain_text` 获取。
- **参数**
empty
- **返回**
- str
### _abstract method_ `is_tome()` {#Event-is_tome}
- **说明:** 获取事件是否与机器人有关的方法。
- **参数**
empty
- **返回**
- bool
## _abstract class_ `Adapter(driver, **kwargs)` {#Adapter}
- **说明**
协议适配器基类。
通常,在 Adapter 中编写协议通信相关代码,如: 建立通信连接、处理接收与发送 data 等。
- **参数**
- `driver` ([Driver](../drivers/index.md#Driver)): [Driver](../drivers/index.md#Driver) 实例
- `**kwargs` (Any): 其他由 [Driver.register_adapter](../drivers/index.md#Driver-register_adapter) 传入的额外参数
### _instance-var_ `driver` {#Adapter-driver}
- **类型:** [Driver](../drivers/index.md#Driver)
- **说明:** 实例
### _instance-var_ `bots` {#Adapter-bots}
- **类型:** dict[str, [Bot](#Bot)]
- **说明:** 本协议适配器已建立连接的 [Bot](#Bot) 实例
### _abstract classmethod_ `get_name()` {#Adapter-get_name}
- **说明:** 当前协议适配器的名称
- **参数**
empty
- **返回**
- str
### _property_ `config` {#Adapter-config}
- **类型:** [Config](../config.md#Config)
- **说明:** 全局 NoneBot 配置
### _method_ `bot_connect(bot)` {#Adapter-bot_connect}
- **说明**
告知 NoneBot 建立了一个新的 [Bot](#Bot) 连接。
当有新的 [Bot](#Bot) 实例连接建立成功时调用。
- **参数**
- `bot` ([Bot](#Bot)): [Bot](#Bot) 实例
- **返回**
- None
### _method_ `bot_disconnect(bot)` {#Adapter-bot_disconnect}
- **说明**
告知 NoneBot [Bot](#Bot) 连接已断开。
当有 [Bot](#Bot) 实例连接断开时调用。
- **参数**
- `bot` ([Bot](#Bot)): [Bot](#Bot) 实例
- **返回**
- None
### _method_ `setup_http_server(setup)` {#Adapter-setup_http_server}
- **说明:** 设置一个 HTTP 服务器路由配置
- **参数**
- `setup` ([HTTPServerSetup](../drivers/index.md#HTTPServerSetup))
- **返回**
- untyped
### _method_ `setup_websocket_server(setup)` {#Adapter-setup_websocket_server}
- **说明:** 设置一个 WebSocket 服务器路由配置
- **参数**
- `setup` ([WebSocketServerSetup](../drivers/index.md#WebSocketServerSetup))
- **返回**
- untyped
### _async method_ `request(setup)` {#Adapter-request}
- **说明:** 进行一个 HTTP 客户端请求
- **参数**
- `setup` ([Request](../drivers/index.md#Request))
- **返回**
- [Response](../drivers/index.md#Response)
### _method_ `websocket(setup)` {#Adapter-websocket}
- **说明:** 建立一个 WebSocket 客户端连接请求
- **参数**
- `setup` ([Request](../drivers/index.md#Request))
- **返回**
- AsyncGenerator[[WebSocket](../drivers/index.md#WebSocket), None]
## _abstract class_ `Message(<auto>)` {#Message}
- **说明:** 消息数组
- **参数**
- `message`: 消息内容
### _classmethod_ `template(format_string)` {#Message-template}
- **说明**
创建消息模板。
用法和 `str.format` 大致相同, 但是可以输出消息对象, 并且支持以 `Message` 对象作为消息模板
并且提供了拓展的格式化控制符, 可以用适用于该消息类型的 `MessageSegment` 的工厂方法创建消息
- **参数**
- `format_string` (str | TM): 格式化模板
- **返回**
- [MessageTemplate](#MessageTemplate)[Self]: 消息格式化器
### _abstract classmethod_ `get_segment_class()` {#Message-get_segment_class}
- **说明:** 获取消息段类型
- **参数**
empty
- **返回**
- type[TMS]
### _abstract staticmethod_ `_construct(msg)` {#Message-\_construct}
- **说明:** 构造消息数组
- **参数**
- `msg` (str)
- **返回**
- Iterable[TMS]
### _method_ `__getitem__(args)` {#Message-**getitem**}
- **重载**
**1.** `(self, args) -> Self`
- **参数**
- `self`
- `args` (str): 消息段类型
- **返回**
- Self: 所有类型为 `args` 的消息段
**2.** `(self, args) -> TMS`
- **参数**
- `self`
- `args` (tuple[str, int]): 消息段类型和索引
- **返回**
- TMS: 类型为 `args[0]` 的消息段第 `args[1]` 个
**3.** `(self, args) -> Self`
- **参数**
- `self`
- `args` (tuple[str, slice]): 消息段类型和切片
- **返回**
- Self: 类型为 `args[0]` 的消息段切片 `args[1]`
**4.** `(self, args) -> TMS`
- **参数**
- `self`
- `args` (int): 索引
- **返回**
- TMS: 第 `args` 个消息段
**5.** `(self, args) -> Self`
- **参数**
- `self`
- `args` (slice): 切片
- **返回**
- Self: 消息切片 `args`
### _method_ `__contains__(value)` {#Message-**contains**}
- **说明:** 检查消息段是否存在
- **参数**
- `value` (TMS | str): 消息段或消息段类型
- **返回**
- bool: 消息内是否存在给定消息段或给定类型的消息段
### _method_ `has(value)` {#Message-has}
- **说明:** 与 [`__contains__`](#Message-__contains__) 相同
- **参数**
- `value` (TMS | str)
- **返回**
- bool
### _method_ `index(value, *args)` {#Message-index}
- **说明:** 索引消息段
- **参数**
- `value` (TMS | str): 消息段或者消息段类型
- `*args` (SupportsIndex)
- `arg`: start 与 end
- **返回**
- int: 索引 index
- **异常**
- ValueError: 消息段不存在
### _method_ `get(type_, count=None)` {#Message-get}
- **说明:** 获取指定类型的消息段
- **参数**
- `type_` (str): 消息段类型
- `count` (int | None): 获取个数
- **返回**
- Self: 构建的新消息
### _method_ `count(value)` {#Message-count}
- **说明:** 计算指定消息段的个数
- **参数**
- `value` (TMS | str): 消息段或消息段类型
- **返回**
- int: 个数
### _method_ `only(value)` {#Message-only}
- **说明:** 检查消息中是否仅包含指定消息段
- **参数**
- `value` (TMS | str): 指定消息段或消息段类型
- **返回**
- bool: 是否仅包含指定消息段
### _method_ `append(obj)` {#Message-append}
- **说明:** 添加一个消息段到消息数组末尾。
- **参数**
- `obj` (str | TMS): 要添加的消息段
- **返回**
- Self
### _method_ `extend(obj)` {#Message-extend}
- **说明:** 拼接一个消息数组或多个消息段到消息数组末尾。
- **参数**
- `obj` (Self | Iterable[TMS]): 要添加的消息数组
- **返回**
- Self
### _method_ `join(iterable)` {#Message-join}
- **说明:** 将多个消息连接并将自身作为分割
- **参数**
- `iterable` (Iterable[TMS | Self]): 要连接的消息
- **返回**
- Self: 连接后的消息
### _method_ `copy()` {#Message-copy}
- **说明:** 深拷贝消息
- **参数**
empty
- **返回**
- Self
### _method_ `include(*types)` {#Message-include}
- **说明:** 过滤消息
- **参数**
- `*types` (str): 包含的消息段类型
- **返回**
- Self: 新构造的消息
### _method_ `exclude(*types)` {#Message-exclude}
- **说明:** 过滤消息
- **参数**
- `*types` (str): 不包含的消息段类型
- **返回**
- Self: 新构造的消息
### _method_ `extract_plain_text()` {#Message-extract_plain_text}
- **说明:** 提取消息内纯文本消息
- **参数**
empty
- **返回**
- str
## _abstract class_ `MessageSegment(<auto>)` {#MessageSegment}
- **说明:** 消息段基类
- **参数**
auto
### _instance-var_ `type` {#MessageSegment-type}
- **类型:** str
- **说明:** 消息段类型
### _class-var_ `data` {#MessageSegment-data}
- **类型:** dict[str, Any]
- **说明:** 消息段数据
### _abstract classmethod_ `get_message_class()` {#MessageSegment-get_message_class}
- **说明:** 获取消息数组类型
- **参数**
empty
- **返回**
- type[TM]
### _abstract method_ `__str__()` {#MessageSegment-**str**}
- **说明:** 该消息段所代表的 str在命令匹配部分使用
- **参数**
empty
- **返回**
- str
### _method_ `__add__(other)` {#MessageSegment-**add**}
- **参数**
- `other` (str | TMS | Iterable[TMS])
- **返回**
- TM
### _method_ `get(key, default=None)` {#MessageSegment-get}
- **参数**
- `key` (str)
- `default` (Any)
- **返回**
- untyped
### _method_ `keys()` {#MessageSegment-keys}
- **参数**
empty
- **返回**
- untyped
### _method_ `values()` {#MessageSegment-values}
- **参数**
empty
- **返回**
- untyped
### _method_ `items()` {#MessageSegment-items}
- **参数**
empty
- **返回**
- untyped
### _method_ `join(iterable)` {#MessageSegment-join}
- **参数**
- `iterable` (Iterable[TMS | TM])
- **返回**
- TM
### _method_ `copy()` {#MessageSegment-copy}
- **参数**
empty
- **返回**
- Self
### _abstract method_ `is_text()` {#MessageSegment-is_text}
- **说明:** 当前消息段是否为纯文本
- **参数**
empty
- **返回**
- bool
## _class_ `MessageTemplate(template, factory=str)` {#MessageTemplate}
- **说明:** 消息模板格式化实现类。
- **参数**
- `template` (str | TM): 模板
- `factory` (type[str] | type[TM]): 消息类型工厂,默认为 `str`
### _method_ `add_format_spec(spec, name=None)` {#MessageTemplate-add_format_spec}
- **参数**
- `spec` (FormatSpecFunc_T)
- `name` (str | None)
- **返回**
- FormatSpecFunc_T
### _method_ `format(*args, **kwargs)` {#MessageTemplate-format}
- **说明:** 根据传入参数和模板生成消息对象
- **参数**
- `*args`
- `**kwargs`
- **返回**
- untyped
### _method_ `format_map(mapping)` {#MessageTemplate-format_map}
- **说明:** 根据传入字典和模板生成消息对象, 在传入字段名不是有效标识符时有用
- **参数**
- `mapping` (Mapping[str, Any])
- **返回**
- TF
### _method_ `vformat(format_string, args, kwargs)` {#MessageTemplate-vformat}
- **参数**
- `format_string` (str)
- `args` (Sequence[Any])
- `kwargs` (Mapping[str, Any])
- **返回**
- TF
### _method_ `format_field(value, format_spec)` {#MessageTemplate-format_field}
- **参数**
- `value` (Any)
- `format_spec` (str)
- **返回**
- Any

View File

@@ -0,0 +1,156 @@
---
sidebar_position: 1
description: nonebot.config 模块
---
# nonebot.config
本模块定义了 NoneBot 本身运行所需的配置项。
NoneBot 使用 [`pydantic`](https://pydantic-docs.helpmanual.io/) 以及 [`python-dotenv`](https://saurabh-kumar.com/python-dotenv/) 来读取配置。
配置项需符合特殊格式或 json 序列化格式。详情见 [`pydantic Field Type`](https://pydantic-docs.helpmanual.io/usage/types/) 文档。
## _class_ `Env(<auto>)` {#Env}
- **说明**
运行环境配置。大小写不敏感。
将会从 `环境变量` > `.env 环境配置文件` 的优先级读取环境信息。
- **参数**
auto
### _class-var_ `environment` {#Env-environment}
- **类型:** str
- **说明**
当前环境名。
NoneBot 将从 `.env.{environment}` 文件中加载配置。
## _class_ `Config(<auto>)` {#Config}
- **说明**
NoneBot 主要配置。大小写不敏感。
除了 NoneBot 的配置项外,还可以自行添加配置项到 `.env.{environment}` 文件中。
这些配置将会在 json 反序列化后一起带入 `Config` 类中。
配置方法参考: [配置](https://nonebot.dev/docs/appendices/config)
- **参数**
auto
### _class-var_ `driver` {#Config-driver}
- **类型:** str
- **说明**
NoneBot 运行所使用的 `Driver` 。继承自 [Driver](drivers/index.md#Driver) 。
配置格式为 `<module>[:<Driver>][+<module>[:<Mixin>]]*`
`~``nonebot.drivers.` 的缩写。
### _class-var_ `host` {#Config-host}
- **类型:** IPvAnyAddress
- **说明:** NoneBot [ReverseDriver](drivers/index.md#ReverseDriver) 服务端监听的 IP/主机名。
### _class-var_ `port` {#Config-port}
- **类型:** int
- **说明:** NoneBot [ReverseDriver](drivers/index.md#ReverseDriver) 服务端监听的端口。
### _class-var_ `log_level` {#Config-log_level}
- **类型:** int | str
- **说明**
NoneBot 日志输出等级,可以为 `int` 类型等级或等级名称
参考 [`loguru 日志等级`](https://loguru.readthedocs.io/en/stable/api/logger.html#levels)。
:::tip 提示
日志等级名称应为大写,如 `INFO`
:::
- **用法**
```conf
LOG_LEVEL=25
LOG_LEVEL=INFO
```
### _class-var_ `api_timeout` {#Config-api_timeout}
- **类型:** float | None
- **说明:** API 请求超时时间,单位: 秒。
### _class-var_ `superusers` {#Config-superusers}
- **类型:** set[str]
- **说明:** 机器人超级用户。
- **用法**
```conf
SUPERUSERS=["12345789"]
```
### _class-var_ `nickname` {#Config-nickname}
- **类型:** set[str]
- **说明:** 机器人昵称。
### _class-var_ `command_start` {#Config-command_start}
- **类型:** set[str]
- **说明:** 命令的起始标记,用于判断一条消息是不是命令。
- **用法**
```conf
COMMAND_START=["/", ""]
```
### _class-var_ `command_sep` {#Config-command_sep}
- **类型:** set[str]
- **说明:** 命令的分隔标记,用于将文本形式的命令切分为元组(实际的命令名)。
- **用法**
```conf
COMMAND_SEP=["."]
```
### _class-var_ `session_expire_timeout` {#Config-session_expire_timeout}
- **类型:** timedelta
- **说明:** 等待用户回复的超时时间。
- **用法**
```conf
SESSION_EXPIRE_TIMEOUT=120 # 单位: 秒
SESSION_EXPIRE_TIMEOUT=[DD ][HH:MM]SS[.ffffff]
SESSION_EXPIRE_TIMEOUT=P[DD]DT[HH]H[MM]M[SS]S # ISO 8601
```

View File

@@ -0,0 +1,116 @@
---
sidebar_position: 9
description: nonebot.consts 模块
---
# nonebot.consts
本模块包含了 NoneBot 事件处理过程中使用到的常量。
## _var_ `RECEIVE_KEY` {#RECEIVE_KEY}
- **类型:** Literal['_receive_{id}']
- **说明:** `receive` 存储 key
## _var_ `LAST_RECEIVE_KEY` {#LAST_RECEIVE_KEY}
- **类型:** Literal['_last_receive']
- **说明:** `last_receive` 存储 key
## _var_ `ARG_KEY` {#ARG_KEY}
- **类型:** Literal['{key}']
- **说明:** `arg` 存储 key
## _var_ `REJECT_TARGET` {#REJECT_TARGET}
- **类型:** Literal['_current_target']
- **说明:** 当前 `reject` 目标存储 key
## _var_ `REJECT_CACHE_TARGET` {#REJECT_CACHE_TARGET}
- **类型:** Literal['_next_target']
- **说明:** 下一个 `reject` 目标存储 key
## _var_ `PREFIX_KEY` {#PREFIX_KEY}
- **类型:** Literal['_prefix']
- **说明:** 命令前缀存储 key
## _var_ `CMD_KEY` {#CMD_KEY}
- **类型:** Literal['command']
- **说明:** 命令元组存储 key
## _var_ `RAW_CMD_KEY` {#RAW_CMD_KEY}
- **类型:** Literal['raw_command']
- **说明:** 命令文本存储 key
## _var_ `CMD_ARG_KEY` {#CMD_ARG_KEY}
- **类型:** Literal['command_arg']
- **说明:** 命令参数存储 key
## _var_ `CMD_START_KEY` {#CMD_START_KEY}
- **类型:** Literal['command_start']
- **说明:** 命令开头存储 key
## _var_ `CMD_WHITESPACE_KEY` {#CMD_WHITESPACE_KEY}
- **类型:** Literal['command_whitespace']
- **说明:** 命令与参数间空白符存储 key
## _var_ `SHELL_ARGS` {#SHELL_ARGS}
- **类型:** Literal['_args']
- **说明:** shell 命令 parse 后参数字典存储 key
## _var_ `SHELL_ARGV` {#SHELL_ARGV}
- **类型:** Literal['_argv']
- **说明:** shell 命令原始参数列表存储 key
## _var_ `REGEX_MATCHED` {#REGEX_MATCHED}
- **类型:** Literal['_matched']
- **说明:** 正则匹配结果存储 key
## _var_ `STARTSWITH_KEY` {#STARTSWITH_KEY}
- **类型:** Literal['_startswith']
- **说明:** 响应触发前缀 key
## _var_ `ENDSWITH_KEY` {#ENDSWITH_KEY}
- **类型:** Literal['_endswith']
- **说明:** 响应触发后缀 key
## _var_ `FULLMATCH_KEY` {#FULLMATCH_KEY}
- **类型:** Literal['_fullmatch']
- **说明:** 响应触发完整消息 key
## _var_ `KEYWORD_KEY` {#KEYWORD_KEY}
- **类型:** Literal['_keyword']
- **说明:** 响应触发关键字 key

View File

@@ -0,0 +1,3 @@
{
"position": 13
}

View File

@@ -0,0 +1,94 @@
---
sidebar_position: 0
description: nonebot.dependencies 模块
---
# nonebot.dependencies
本模块模块实现了依赖注入的定义与处理。
## _abstract class_ `Param(<auto>)` {#Param}
- **说明**
依赖注入的基本单元 —— 参数。
继承自 `pydantic.fields.FieldInfo`,用于描述参数信息(不包括参数名)。
- **参数**
auto
## _class_ `Dependent(<auto>)` {#Dependent}
- **说明:** 依赖注入容器
- **参数**
- `call`: 依赖注入的可调用对象,可以是任何 Callable 对象
- `pre_checkers`: 依赖注入解析前的参数检查
- `params`: 具名参数列表
- `parameterless`: 匿名参数列表
- `allow_types`: 允许的参数类型
### _staticmethod_ `parse_params(call, allow_types)` {#Dependent-parse_params}
- **参数**
- `call` (\_DependentCallable[R])
- `allow_types` (tuple[type[Param], ...])
- **返回**
- tuple[ModelField]
### _staticmethod_ `parse_parameterless(parameterless, allow_types)` {#Dependent-parse_parameterless}
- **参数**
- `parameterless` (tuple[Any, ...])
- `allow_types` (tuple[type[Param], ...])
- **返回**
- tuple[Param, ...]
### _classmethod_ `parse(*, call, parameterless=None, allow_types)` {#Dependent-parse}
- **参数**
- `call` (\_DependentCallable[R])
- `parameterless` (Iterable[Any] | None)
- `allow_types` (Iterable[type[Param]])
- **返回**
- Dependent[R]
### _async method_ `check(**params)` {#Dependent-check}
- **参数**
- `**params` (Any)
- **返回**
- None
### _async method_ `solve(**params)` {#Dependent-solve}
- **参数**
- `**params` (Any)
- **返回**
- dict[str, Any]

View File

@@ -0,0 +1,44 @@
---
sidebar_position: 1
description: nonebot.dependencies.utils 模块
---
# nonebot.dependencies.utils
## _def_ `get_typed_signature(call)` {#get_typed_signature}
- **说明:** 获取可调用对象签名
- **参数**
- `call` ((...) -> Any)
- **返回**
- inspect.Signature
## _def_ `get_typed_annotation(param, globalns)` {#get_typed_annotation}
- **说明:** 获取参数的类型注解
- **参数**
- `param` (inspect.Parameter)
- `globalns` (dict[str, Any])
- **返回**
- Any
## _def_ `check_field_type(field, value)` {#check_field_type}
- **参数**
- `field` (ModelField)
- `value` (V)
- **返回**
- V

View File

@@ -0,0 +1,3 @@
{
"position": 14
}

View File

@@ -0,0 +1,47 @@
# nonebot.drivers.\_lifespan
## _class_ `Lifespan()` {#Lifespan}
- **参数**
empty
### _method_ `on_startup(func)` {#Lifespan-on_startup}
- **参数**
- `func` (LIFESPAN_FUNC)
- **返回**
- LIFESPAN_FUNC
### _method_ `on_shutdown(func)` {#Lifespan-on_shutdown}
- **参数**
- `func` (LIFESPAN_FUNC)
- **返回**
- LIFESPAN_FUNC
### _async method_ `startup()` {#Lifespan-startup}
- **参数**
empty
- **返回**
- None
### _async method_ `shutdown()` {#Lifespan-shutdown}
- **参数**
empty
- **返回**
- None

View File

@@ -0,0 +1,134 @@
---
sidebar_position: 2
description: nonebot.drivers.aiohttp 模块
---
# nonebot.drivers.aiohttp
[AIOHTTP](https://aiohttp.readthedocs.io/en/stable/) 驱动适配器。
```bash
nb driver install aiohttp
# 或者
pip install nonebot2[aiohttp]
```
:::tip 提示
本驱动仅支持客户端连接
:::
## _class_ `Mixin(<auto>)` {#Mixin}
- **说明:** AIOHTTP Mixin
- **参数**
auto
### _async method_ `request(setup)` {#Mixin-request}
- **参数**
- `setup` ([Request](index.md#Request))
- **返回**
- [Response](index.md#Response)
### _method_ `websocket(setup)` {#Mixin-websocket}
- **参数**
- `setup` ([Request](index.md#Request))
- **返回**
- AsyncGenerator[[WebSocket](index.md#WebSocket), None]
## _class_ `WebSocket(*, request, session, websocket)` {#WebSocket}
- **说明:** AIOHTTP Websocket Wrapper
- **参数**
- `request` ([Request](index.md#Request))
- `session` (aiohttp.ClientSession)
- `websocket` (aiohttp.ClientWebSocketResponse)
### _async method_ `accept()` {#WebSocket-accept}
- **参数**
empty
- **返回**
- untyped
### _async method_ `close(code=1000)` {#WebSocket-close}
- **参数**
- `code` (int)
- **返回**
- untyped
### _async method_ `receive()` {#WebSocket-receive}
- **参数**
empty
- **返回**
- str
### _async method_ `receive_text()` {#WebSocket-receive_text}
- **参数**
empty
- **返回**
- str
### _async method_ `receive_bytes()` {#WebSocket-receive_bytes}
- **参数**
empty
- **返回**
- bytes
### _async method_ `send_text(data)` {#WebSocket-send_text}
- **参数**
- `data` (str)
- **返回**
- None
### _async method_ `send_bytes(data)` {#WebSocket-send_bytes}
- **参数**
- `data` (bytes)
- **返回**
- None
## _var_ `Driver` {#Driver}
- **类型:** type[[ForwardDriver](index.md#ForwardDriver)]
- **说明:** AIOHTTP Driver

View File

@@ -0,0 +1,260 @@
---
sidebar_position: 1
description: nonebot.drivers.fastapi 模块
---
# nonebot.drivers.fastapi
[FastAPI](https://fastapi.tiangolo.com/) 驱动适配
```bash
nb driver install fastapi
# 或者
pip install nonebot2[fastapi]
```
:::tip 提示
本驱动仅支持服务端连接
:::
## _class_ `Config(<auto>)` {#Config}
- **说明:** FastAPI 驱动框架设置,详情参考 FastAPI 文档
- **参数**
auto
### _class-var_ `fastapi_openapi_url` {#Config-fastapi_openapi_url}
- **类型:** str | None
- **说明:** `openapi.json` 地址,默认为 `None` 即关闭
### _class-var_ `fastapi_docs_url` {#Config-fastapi_docs_url}
- **类型:** str | None
- **说明:** `swagger` 地址,默认为 `None` 即关闭
### _class-var_ `fastapi_redoc_url` {#Config-fastapi_redoc_url}
- **类型:** str | None
- **说明:** `redoc` 地址,默认为 `None` 即关闭
### _class-var_ `fastapi_include_adapter_schema` {#Config-fastapi_include_adapter_schema}
- **类型:** bool
- **说明:** 是否包含适配器路由的 schema默认为 `True`
### _class-var_ `fastapi_reload` {#Config-fastapi_reload}
- **类型:** bool
- **说明:** 开启/关闭冷重载
### _class-var_ `fastapi_reload_dirs` {#Config-fastapi_reload_dirs}
- **类型:** list[str] | None
- **说明:** 重载监控文件夹列表,默认为 uvicorn 默认值
### _class-var_ `fastapi_reload_delay` {#Config-fastapi_reload_delay}
- **类型:** float
- **说明:** 重载延迟,默认为 uvicorn 默认值
### _class-var_ `fastapi_reload_includes` {#Config-fastapi_reload_includes}
- **类型:** list[str] | None
- **说明:** 要监听的文件列表,支持 glob pattern默认为 uvicorn 默认值
### _class-var_ `fastapi_reload_excludes` {#Config-fastapi_reload_excludes}
- **类型:** list[str] | None
- **说明:** 不要监听的文件列表,支持 glob pattern默认为 uvicorn 默认值
### _class-var_ `fastapi_extra` {#Config-fastapi_extra}
- **类型:** dict[str, Any]
- **说明:** 传递给 `FastAPI` 的其他参数。
## _class_ `Driver(env, config)` {#Driver}
- **说明:** FastAPI 驱动框架。
- **参数**
- `env` ([Env](../config.md#Env))
- `config` (NoneBotConfig)
### _property_ `type` {#Driver-type}
- **类型:** str
- **说明:** 驱动名称: `fastapi`
### _property_ `server_app` {#Driver-server_app}
- **类型:** FastAPI
- **说明:** `FastAPI APP` 对象
### _property_ `asgi` {#Driver-asgi}
- **类型:** FastAPI
- **说明:** `FastAPI APP` 对象
### _property_ `logger` {#Driver-logger}
- **类型:** logging.Logger
- **说明:** fastapi 使用的 logger
### _method_ `setup_http_server(setup)` {#Driver-setup_http_server}
- **参数**
- `setup` ([HTTPServerSetup](index.md#HTTPServerSetup))
- **返回**
- untyped
### _method_ `setup_websocket_server(setup)` {#Driver-setup_websocket_server}
- **参数**
- `setup` ([WebSocketServerSetup](index.md#WebSocketServerSetup))
- **返回**
- None
### _method_ `on_startup(func)` {#Driver-on_startup}
- **参数**
- `func` (LIFESPAN_FUNC)
- **返回**
- LIFESPAN_FUNC
### _method_ `on_shutdown(func)` {#Driver-on_shutdown}
- **参数**
- `func` (LIFESPAN_FUNC)
- **返回**
- LIFESPAN_FUNC
### _method_ `run(host=None, port=None, *, app=None, **kwargs)` {#Driver-run}
- **说明:** 使用 `uvicorn` 启动 FastAPI
- **参数**
- `host` (str | None)
- `port` (int | None)
- `app` (str | None)
- `**kwargs`
- **返回**
- untyped
## _class_ `FastAPIWebSocket(*, request, websocket)` {#FastAPIWebSocket}
- **说明:** FastAPI WebSocket Wrapper
- **参数**
- `request` (BaseRequest)
- `websocket` ([WebSocket](index.md#WebSocket))
### _async method_ `accept()` {#FastAPIWebSocket-accept}
- **参数**
empty
- **返回**
- None
### _async method_ `close(code=status.WS_1000_NORMAL_CLOSURE, reason="")` {#FastAPIWebSocket-close}
- **参数**
- `code` (int)
- `reason` (str)
- **返回**
- None
### _async method_ `receive()` {#FastAPIWebSocket-receive}
- **参数**
empty
- **返回**
- str | bytes
### _async method_ `receive_text()` {#FastAPIWebSocket-receive_text}
- **参数**
empty
- **返回**
- str
### _async method_ `receive_bytes()` {#FastAPIWebSocket-receive_bytes}
- **参数**
empty
- **返回**
- bytes
### _async method_ `send_text(data)` {#FastAPIWebSocket-send_text}
- **参数**
- `data` (str)
- **返回**
- None
### _async method_ `send_bytes(data)` {#FastAPIWebSocket-send_bytes}
- **参数**
- `data` (bytes)
- **返回**
- None

View File

@@ -0,0 +1,52 @@
---
sidebar_position: 3
description: nonebot.drivers.httpx 模块
---
# nonebot.drivers.httpx
[HTTPX](https://www.python-httpx.org/) 驱动适配
```bash
nb driver install httpx
# 或者
pip install nonebot2[httpx]
```
:::tip 提示
本驱动仅支持客户端 HTTP 连接
:::
## _class_ `Mixin(<auto>)` {#Mixin}
- **说明:** HTTPX Mixin
- **参数**
auto
### _async method_ `request(setup)` {#Mixin-request}
- **参数**
- `setup` ([Request](index.md#Request))
- **返回**
- [Response](index.md#Response)
### _method_ `websocket(setup)` {#Mixin-websocket}
- **参数**
- `setup` ([Request](index.md#Request))
- **返回**
- AsyncGenerator[[WebSocket](index.md#WebSocket), None]
## _var_ `Driver` {#Driver}
- **类型:** type[[ForwardDriver](index.md#ForwardDriver)]
- **说明:** HTTPX Driver

View File

@@ -0,0 +1,510 @@
---
sidebar_position: 0
description: nonebot.drivers 模块
---
# nonebot.drivers
本模块定义了驱动适配器基类。
各驱动请继承以下基类。
## _abstract class_ `Driver(env, config)` {#Driver}
- **说明:** Driver 基类。
- **参数**
- `env` ([Env](../config.md#Env)): 包含环境信息的 Env 对象
- `config` ([Config](../config.md#Config)): 包含配置信息的 Config 对象
### _instance-var_ `env` {#Driver-env}
- **类型:** str
- **说明:** 环境名称
### _instance-var_ `config` {#Driver-config}
- **类型:** [Config](../config.md#Config)
- **说明:** 全局配置对象
### _property_ `bots` {#Driver-bots}
- **类型:** dict[str, [Bot](../adapters/index.md#Bot)]
- **说明:** 获取当前所有已连接的 Bot
### _method_ `register_adapter(adapter, **kwargs)` {#Driver-register_adapter}
- **说明:** 注册一个协议适配器
- **参数**
- `adapter` (type[[Adapter](../adapters/index.md#Adapter)]): 适配器类
- `**kwargs`: 其他传递给适配器的参数
- **返回**
- None
### _abstract property_ `type` {#Driver-type}
- **类型:** str
- **说明:** 驱动类型名称
### _abstract property_ `logger` {#Driver-logger}
- **类型:**
- **说明:** 驱动专属 logger 日志记录器
### _abstract method_ `run(*args, **kwargs)` {#Driver-run}
- **说明:** 启动驱动框架
- **参数**
- `*args`
- `**kwargs`
- **返回**
- untyped
### _abstract method_ `on_startup(func)` {#Driver-on_startup}
- **说明:** 注册一个在驱动器启动时执行的函数
- **参数**
- `func` (Callable)
- **返回**
- Callable
### _abstract method_ `on_shutdown(func)` {#Driver-on_shutdown}
- **说明:** 注册一个在驱动器停止时执行的函数
- **参数**
- `func` (Callable)
- **返回**
- Callable
### _classmethod_ `on_bot_connect(func)` {#Driver-on_bot_connect}
- **说明**
装饰一个函数使他在 bot 连接成功时执行。
钩子函数参数:
- bot: 当前连接上的 Bot 对象
- **参数**
- `func` ([T_BotConnectionHook](../typing.md#T_BotConnectionHook))
- **返回**
- [T_BotConnectionHook](../typing.md#T_BotConnectionHook)
### _classmethod_ `on_bot_disconnect(func)` {#Driver-on_bot_disconnect}
- **说明**
装饰一个函数使他在 bot 连接断开时执行。
钩子函数参数:
- bot: 当前连接上的 Bot 对象
- **参数**
- `func` ([T_BotDisconnectionHook](../typing.md#T_BotDisconnectionHook))
- **返回**
- [T_BotDisconnectionHook](../typing.md#T_BotDisconnectionHook)
## _class_ `Cookies(cookies=None)` {#Cookies}
- **参数**
- `cookies` (CookieTypes)
### _method_ `set(name, value, domain="", path="/")` {#Cookies-set}
- **参数**
- `name` (str)
- `value` (str)
- `domain` (str)
- `path` (str)
- **返回**
- None
### _method_ `get(name, default=None, domain=None, path=None)` {#Cookies-get}
- **参数**
- `name` (str)
- `default` (str | None)
- `domain` (str | None)
- `path` (str | None)
- **返回**
- str | None
### _method_ `delete(name, domain=None, path=None)` {#Cookies-delete}
- **参数**
- `name` (str)
- `domain` (str | None)
- `path` (str | None)
- **返回**
- None
### _method_ `clear(domain=None, path=None)` {#Cookies-clear}
- **参数**
- `domain` (str | None)
- `path` (str | None)
- **返回**
- None
### _method_ `update(cookies=None)` {#Cookies-update}
- **参数**
- `cookies` (CookieTypes)
- **返回**
- None
### _method_ `as_header(request)` {#Cookies-as_header}
- **参数**
- `request` (Request)
- **返回**
- dict[str, str]
## _class_ `Request(method, url, *, params=None, headers=None, cookies=None, content=None, data=None, json=None, files=None, version=HTTPVersion.H11, timeout=None, proxy=None)` {#Request}
- **参数**
- `method` (str | bytes)
- `url` (URL | str | RawURL)
- `params` (QueryTypes)
- `headers` (HeaderTypes)
- `cookies` (CookieTypes)
- `content` (ContentTypes)
- `data` (DataTypes)
- `json` (Any)
- `files` (FilesTypes)
- `version` (str | HTTPVersion)
- `timeout` (float | None)
- `proxy` (str | None)
## _class_ `Response(status_code, *, headers=None, content=None, request=None)` {#Response}
- **参数**
- `status_code` (int)
- `headers` (HeaderTypes)
- `content` (ContentTypes)
- `request` (Request | None)
## _abstract class_ `WebSocket(*, request)` {#WebSocket}
- **参数**
- `request` (Request)
### _abstract property_ `closed` {#WebSocket-closed}
- **类型:** bool
- **说明:** 连接是否已经关闭
### _abstract async method_ `accept()` {#WebSocket-accept}
- **说明:** 接受 WebSocket 连接请求
- **参数**
empty
- **返回**
- None
### _abstract async method_ `close(code=1000, reason="")` {#WebSocket-close}
- **说明:** 关闭 WebSocket 连接请求
- **参数**
- `code` (int)
- `reason` (str)
- **返回**
- None
### _abstract async method_ `receive()` {#WebSocket-receive}
- **说明:** 接收一条 WebSocket text/bytes 信息
- **参数**
empty
- **返回**
- str | bytes
### _abstract async method_ `receive_text()` {#WebSocket-receive_text}
- **说明:** 接收一条 WebSocket text 信息
- **参数**
empty
- **返回**
- str
### _abstract async method_ `receive_bytes()` {#WebSocket-receive_bytes}
- **说明:** 接收一条 WebSocket binary 信息
- **参数**
empty
- **返回**
- bytes
### _async method_ `send(data)` {#WebSocket-send}
- **说明:** 发送一条 WebSocket text/bytes 信息
- **参数**
- `data` (str | bytes)
- **返回**
- None
### _abstract async method_ `send_text(data)` {#WebSocket-send_text}
- **说明:** 发送一条 WebSocket text 信息
- **参数**
- `data` (str)
- **返回**
- None
### _abstract async method_ `send_bytes(data)` {#WebSocket-send_bytes}
- **说明:** 发送一条 WebSocket binary 信息
- **参数**
- `data` (bytes)
- **返回**
- None
## _enum_ `HTTPVersion` {#HTTPVersion}
- **说明:** An enumeration.
- **参数**
auto
- `H10: '1.0'`
- `H11: '1.1'`
- `H2: '2'`
## _abstract class_ `ForwardMixin(<auto>)` {#ForwardMixin}
- **说明:** 客户端混入基类。
- **参数**
auto
### _abstract property_ `type` {#ForwardMixin-type}
- **类型:** str
- **说明:** 客户端驱动类型名称
### _abstract async method_ `request(setup)` {#ForwardMixin-request}
- **说明:** 发送一个 HTTP 请求
- **参数**
- `setup` ([Request](#Request))
- **返回**
- [Response](#Response)
### _abstract method_ `websocket(setup)` {#ForwardMixin-websocket}
- **说明:** 发起一个 WebSocket 连接
- **参数**
- `setup` ([Request](#Request))
- **返回**
- AsyncGenerator[[WebSocket](#WebSocket), None]
## _abstract class_ `ForwardDriver(env, config)` {#ForwardDriver}
- **说明:** 客户端基类。将客户端框架封装,以满足适配器使用。
- **参数**
- `env` ([Env](../config.md#Env))
- `config` ([Config](../config.md#Config))
## _abstract class_ `ReverseDriver(env, config)` {#ReverseDriver}
- **说明:** 服务端基类。将后端框架封装,以满足适配器使用。
- **参数**
- `env` ([Env](../config.md#Env))
- `config` ([Config](../config.md#Config))
### _abstract property_ `server_app` {#ReverseDriver-server_app}
- **类型:** Any
- **说明:** 驱动 APP 对象
### _abstract property_ `asgi` {#ReverseDriver-asgi}
- **类型:** Any
- **说明:** 驱动 ASGI 对象
### _abstract method_ `setup_http_server(setup)` {#ReverseDriver-setup_http_server}
- **说明:** 设置一个 HTTP 服务器路由配置
- **参数**
- `setup` ([HTTPServerSetup](#HTTPServerSetup))
- **返回**
- None
### _abstract method_ `setup_websocket_server(setup)` {#ReverseDriver-setup_websocket_server}
- **说明:** 设置一个 WebSocket 服务器路由配置
- **参数**
- `setup` ([WebSocketServerSetup](#WebSocketServerSetup))
- **返回**
- None
## _def_ `combine_driver(driver, *mixins)` {#combine_driver}
- **说明:** 将一个驱动器和多个混入类合并。
- **参数**
- `driver` (type[Driver])
- `*mixins` (type[ForwardMixin])
- **返回**
- type[Driver]
## _class_ `HTTPServerSetup(<auto>)` {#HTTPServerSetup}
- **说明:** HTTP 服务器路由配置。
- **参数**
auto
## _class_ `WebSocketServerSetup(<auto>)` {#WebSocketServerSetup}
- **说明:** WebSocket 服务器路由配置。
- **参数**
auto

View File

@@ -0,0 +1,84 @@
---
sidebar_position: 6
description: nonebot.drivers.none 模块
---
# nonebot.drivers.none
None 驱动适配
:::tip 提示
本驱动不支持任何服务器或客户端连接
:::
## _class_ `Driver(env, config)` {#Driver}
- **说明:** None 驱动框架
- **参数**
- `env` ([Env](../config.md#Env))
- `config` ([Config](../config.md#Config))
### _property_ `type` {#Driver-type}
- **类型:** str
- **说明:** 驱动名称: `none`
### _property_ `logger` {#Driver-logger}
- **类型:**
- **说明:** none driver 使用的 logger
### _method_ `on_startup(func)` {#Driver-on_startup}
- **说明:** 注册一个启动时执行的函数
- **参数**
- `func` (LIFESPAN_FUNC)
- **返回**
- LIFESPAN_FUNC
### _method_ `on_shutdown(func)` {#Driver-on_shutdown}
- **说明:** 注册一个停止时执行的函数
- **参数**
- `func` (LIFESPAN_FUNC)
- **返回**
- LIFESPAN_FUNC
### _method_ `run(*args, **kwargs)` {#Driver-run}
- **说明:** 启动 none driver
- **参数**
- `*args`
- `**kwargs`
- **返回**
- untyped
### _method_ `exit(force=False)` {#Driver-exit}
- **说明:** 退出 none driver
- **参数**
- `force` (bool): 强制退出
- **返回**
- untyped

View File

@@ -0,0 +1,240 @@
---
sidebar_position: 5
description: nonebot.drivers.quart 模块
---
# nonebot.drivers.quart
[Quart](https://pgjones.gitlab.io/quart/index.html) 驱动适配
```bash
nb driver install quart
# 或者
pip install nonebot2[quart]
```
:::tip 提示
本驱动仅支持服务端连接
:::
## _class_ `Config(<auto>)` {#Config}
- **说明:** Quart 驱动框架设置
- **参数**
auto
### _class-var_ `quart_reload` {#Config-quart_reload}
- **类型:** bool
- **说明:** 开启/关闭冷重载
### _class-var_ `quart_reload_dirs` {#Config-quart_reload_dirs}
- **类型:** list[str] | None
- **说明:** 重载监控文件夹列表,默认为 uvicorn 默认值
### _class-var_ `quart_reload_delay` {#Config-quart_reload_delay}
- **类型:** float
- **说明:** 重载延迟,默认为 uvicorn 默认值
### _class-var_ `quart_reload_includes` {#Config-quart_reload_includes}
- **类型:** list[str] | None
- **说明:** 要监听的文件列表,支持 glob pattern默认为 uvicorn 默认值
### _class-var_ `quart_reload_excludes` {#Config-quart_reload_excludes}
- **类型:** list[str] | None
- **说明:** 不要监听的文件列表,支持 glob pattern默认为 uvicorn 默认值
### _class-var_ `quart_extra` {#Config-quart_extra}
- **类型:** dict[str, Any]
- **说明:** 传递给 `Quart` 的其他参数。
## _class_ `Driver(env, config)` {#Driver}
- **说明:** Quart 驱动框架
- **参数**
- `env` ([Env](../config.md#Env))
- `config` (NoneBotConfig)
### _property_ `type` {#Driver-type}
- **类型:** str
- **说明:** 驱动名称: `quart`
### _property_ `server_app` {#Driver-server_app}
- **类型:** Quart
- **说明:** `Quart` 对象
### _property_ `asgi` {#Driver-asgi}
- **类型:**
- **说明:** `Quart` 对象
### _property_ `logger` {#Driver-logger}
- **类型:**
- **说明:** Quart 使用的 logger
### _method_ `setup_http_server(setup)` {#Driver-setup_http_server}
- **参数**
- `setup` ([HTTPServerSetup](index.md#HTTPServerSetup))
- **返回**
- untyped
### _method_ `setup_websocket_server(setup)` {#Driver-setup_websocket_server}
- **参数**
- `setup` ([WebSocketServerSetup](index.md#WebSocketServerSetup))
- **返回**
- None
### _method_ `on_startup(func)` {#Driver-on_startup}
- **说明:** 参考文档: [`Startup and Shutdown`](https://pgjones.gitlab.io/quart/how_to_guides/startup_shutdown.html)
- **参数**
- `func` (\_AsyncCallable)
- **返回**
- \_AsyncCallable
### _method_ `on_shutdown(func)` {#Driver-on_shutdown}
- **说明:** 参考文档: [`Startup and Shutdown`](https://pgjones.gitlab.io/quart/how_to_guides/startup_shutdown.html)
- **参数**
- `func` (\_AsyncCallable)
- **返回**
- \_AsyncCallable
### _method_ `run(host=None, port=None, *, app=None, **kwargs)` {#Driver-run}
- **说明:** 使用 `uvicorn` 启动 Quart
- **参数**
- `host` (str | None)
- `port` (int | None)
- `app` (str | None)
- `**kwargs`
- **返回**
- untyped
## _class_ `WebSocket(*, request, websocket)` {#WebSocket}
- **说明:** Quart WebSocket Wrapper
- **参数**
- `request` (BaseRequest)
- `websocket` (QuartWebSocket)
### _async method_ `accept()` {#WebSocket-accept}
- **参数**
empty
- **返回**
- untyped
### _async method_ `close(code=1000, reason="")` {#WebSocket-close}
- **参数**
- `code` (int)
- `reason` (str)
- **返回**
- untyped
### _async method_ `receive()` {#WebSocket-receive}
- **参数**
empty
- **返回**
- str | bytes
### _async method_ `receive_text()` {#WebSocket-receive_text}
- **参数**
empty
- **返回**
- str
### _async method_ `receive_bytes()` {#WebSocket-receive_bytes}
- **参数**
empty
- **返回**
- bytes
### _async method_ `send_text(data)` {#WebSocket-send_text}
- **参数**
- `data` (str)
- **返回**
- untyped
### _async method_ `send_bytes(data)` {#WebSocket-send_bytes}
- **参数**
- `data` (bytes)
- **返回**
- untyped

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